ng2-charts icon indicating copy to clipboard operation
ng2-charts copied to clipboard

can anyone provide example of custom legends using ng2-charts, ts,html files both. I am new to ng2 charts, and not sure where to call generateLegends method. I want Custom Legends while rendering the chart itself

Open vishranti5 opened this issue 6 years ago • 6 comments
trafficstars

Reproduction of the problem

ng2-charts is a port & modification of Chart.js component for Angular 2. Sometimes the issue is related with Chart.js instead of ng2-charts. To confirm, if the issue shows in a pure Chart.js project, it is a issue of Chart.js. Pure Chart.js starting template: https://jsfiddle.net/Hongbo_Miao/mvct2uwo/3/

If the issue does not show in a pure Chart.js project, and shows in a ng2-charts project, please try to provide a minimal demo of the problem. ng2-charts starting template: http://plnkr.co/edit/Ka4NXG3pZ1mXnaN95HX5?p=preview

vishranti5 avatar May 15 '19 11:05 vishranti5

The generateLegend() method exists on the Chart object so we can access it by grabbing it from the Viewchild of the BaseChartDirective like so

` @ViewChild(BaseChartDirective) private _chart: BaseChartDirective;

public ngOnInit(): void {
    this._chart.chart.generateLegend();
    
}`

But i'm yet to have luck creating a custom legend

aidanbiggs avatar Sep 04 '19 16:09 aidanbiggs

I've managed to get a custom legend using a implementation similar to this one https://github.com/valor-software/ng2-charts/issues/464

graph.component.html

<div *ngIf="legendData" class="bg-dark mb-2 pb-2 px-2 text-white mb-2">

    <p class="bold ">Data Filters</p>
    <div class="d-flex flex-row mb-1">
        <span class="badge badge-pill c-pointer px-3 mr-2"
              *ngFor="let legendItem of legendData"
              [ngClass]="!legendItem.hidden ? 'badge-primary' : 'badge-secondary'"
              (click)="toggleDataVisibility(legendItem.datasetIndex)">
            {{legendItem.text}}
        </span>
    </div>
</div>
<div>
    <canvas baseChart
            [datasets]="barChartData"
            [labels]="allDataTime"
            [options]="barChartOptions"
            [legend]="false"
            (chartClick)="getClickedBar($event)"
            [chartType]="'bar'">
    </canvas>
</div>

graph.component.ts

public getLegendCallback: any = ((self: this): any => {
        function handle(chart: any): any {
            return chart.legend.legendItems;
        }

        return function(chart: Chart): any {

            return handle(chart);
        };
    })(this);

    public barChartOptions: ChartOptions = {
        responsive: true,
        scales: {
            xAxes: [{
                stacked: true,
                type: 'time',
                time: {
                    unit: 'hour'
                },
            }], yAxes: [{
                stacked: true,
                ticks: {
                    beginAtZero: true,

                },
            }]
        },
        plugins: {
            datalabels: {
                anchor: 'end',
                align: 'end',
            }
        },
        legendCallback: this.getLegendCallback
    };
    public legendData: Array<ChartLegendItem> = [];

    @ViewChild(BaseChartDirective) private _chart: BaseChartDirective

 public ngAfterViewInit(): void {
        setTimeout(() => {
            this.legendData.push(...this._chart.chart.generateLegend() as any);
        });
    }

    public toggleDataVisibility(dataSetId: number): void {
        this.barChartData[dataSetId].hidden = !this.barChartData[dataSetId].hidden;
        this.legendData[dataSetId].hidden = !this.legendData[dataSetId].hidden;
        this._chart.update();
    };

aidanbiggs avatar Sep 09 '19 12:09 aidanbiggs

@aidanbiggs do you know that when using arrow functions you keep the context and you dont have to do the self stuff ?

rubenCodeforges avatar May 18 '20 17:05 rubenCodeforges

@rubenCodeforges I couldn't manage to covert the getLegendCallback() and have it still work, if you could show me it'd be appreciated

aidanbiggs avatar May 18 '20 17:05 aidanbiggs

I know it kinda old but i strgguled with it and couldn't find a good example so here is my take on it:

Pie-chart.component.ts

import {
  Component,
  EventEmitter,
  Input,
  OnInit,
  Output,
  ViewChild,
} from "@angular/core";
import { ChartData, ChartLegendItem, ChartOptions, ChartType } from "chart.js";
import { Label } from "ng2-charts/lib/base-chart.directive";
import ChartDataLabels from "chartjs-plugin-datalabels";
import { sum } from "lodash";
import { IPieChartData } from "../../../maintenance/models";
import { hexToRgba } from "../../helpers/hex-to-rgba.helper";
import { BaseChartDirective } from "ng2-charts";
import { Chart } from "chart.js";

@Component({
  selector: "spl-pie-chart",
  templateUrl: "./pie-chart.component.html",
  styleUrls: ["./pie-chart.component.scss"],
})
export class PieChartComponent implements OnInit {
  @Input() set pieChartData(pieChartData: IPieChartData) {
    this.dColors = [
      {
        backgroundColor: pieChartData.pieChartColors.map((color) => {
          return hexToRgba(color, 0.4);
        }),
        borderWidth: [2, 2, 2],
        borderColor: pieChartData.pieChartColors,
      },
    ];
    const chartDataCopy = JSON.parse(JSON.stringify(pieChartData.pieChartData));
    this.pieChartLabels = pieChartData.pieChartLabels;

    pieChartData.pieChartLabels.forEach((label, index) => {
      const labelKey = label.replace(" ", "_");
      if (this.activeFilters[labelKey]) {
        chartDataCopy[index] = null;
      }
    });
    this.filteredData = chartDataCopy;

    this.pieChartDataset = pieChartData.pieChartData;
  }

  @Input() legendLabels: Array<string>;

  @Output() legendClick: EventEmitter<{
    [filterKey: string]: boolean;
  }> = new EventEmitter();

  @ViewChild("myChart", { static: true }) myChart: Chart; // typing is an issue

  private activeFilters: { [filterKey: string]: boolean } = {};

  public data: ChartData;
  public dataFilterIndex: number;
  public filteredData: Array<number>;

  public pieChartOptions: ChartOptions = {
    responsive: true,

    legend: {
      position: "bottom",
      display: false,
      align: "start",
      labels: {
        fontColor: "rgba(255,255,255,0.8)",
        boxWidth: 12,
      },
      onClick: (e: Event, legendItem: ChartLegendItem) => {
        this.onLegendItemClick(e, legendItem);
      },
    },
    plugins: {
      datalabels: {
        color: "rgba(255,255,255,0.8)",
        formatter: (value, ctx) => {
          let dataArr = ctx.chart.data.datasets[0].data;
          let total = sum(dataArr); // sum from lodash
          if (total === 0) {
            this.filteredData = [];
            return "";
          }
          let percentage = ((value * 100) / total).toFixed(2) + "%";
          if ((value * 100) / total === 0) {
            return "";
          }
          return percentage;
        },
      },
    },
    maintainAspectRatio: false,
    layout: {
      padding: {
        left: 0,
        right: 0,
        top: 0,
        bottom: 0,
      },
    },
  };
  public pieChartLabels: Label[];
  public pieChartType: ChartType = "pie";
  public pieChartLegend = true;
  public pieChartPlugins = [ChartDataLabels];
  public pieChartDataset: Array<number>;
  public defaultLegendClickHandler;
  public dColors: any[] = [
    {
      backgroundColor: ["rgba(25, 255, 125,0.4)", "rgba(255,0,0,0.4)"],
      borderWidth: [2, 2, 2],
      borderColor: ["rgba(25, 255, 125,1)", "rgba(255,0,0,1)"],
    },
  ];

  constructor() {}

  ngOnInit() {
    this.defaultLegendClickHandler = Chart.defaults.global.legend.onClick;
    this.pieChartLabels = this.legendLabels;
  }

  public onLegendItemClick(e: Event, legendItem: ChartLegendItem): void {
    const filterKey = legendItem.text.replace(" ", "_");
    this.activeFilters[filterKey] = this.activeFilters[filterKey]
      ? false
      : true;

    this.legendClick.emit(this.activeFilters);
  }

  public isItemFilterActive(item: ChartLegendItem): boolean {
    const filterKey = item.text.replace(" ", "_");
    return this.activeFilters[filterKey];
  }
}

Pie-chart.component.html

<div class="chart">
  <canvas
    style="max-height: 140px"
    #myChart="base-chart"
    width="203px"
    height="140px"
    baseChart
    [data]="filteredData"
    [labels]="pieChartLabels"
    [chartType]="pieChartType"
    [options]="pieChartOptions"
    [plugins]="pieChartPlugins"
    [colors]="dColors"
    [legend]="pieChartLegend"
  >
  </canvas>
  <div class="chart__legend" *ngIf="myChart?.chart as pieChart">
    <div
      class="chart__legend-item"
      *ngFor="let item of pieChart.legend.legendItems"
      (click)="onLegendItemClick($event, item)"
    >
      <span
        class="chart__item-color"
        [ngStyle]="{
          background: item.fillStyle,
          border: '1px solid',
          'border-color': item.strokeStyle
        }"
      ></span>
      <span
        class="chart__item-text"
        [ngClass]="{
          'chart__item-text--active-filter': isItemFilterActive(item)
        }"
      >
        {{ item.text }}
      </span>
    </div>
  </div>
</div>

Pie-chart.component.scss


.chart {
  &__item-color {
    display: inline-block;
    width: 10px;
    height: 10px;
    position: relative;
    margin-right: 5px;
    padding: 4px;
  }

  &__legend-item {
    cursor: pointer;
  }

  &__item-text {
    &--active-filter {
      text-decoration: line-through;
    }
  }
}

yuda85 avatar Oct 21 '20 13:10 yuda85

in My solution, I had to play around with the typing of the chart object in order to get the legend object in the HTML, and added the default legend behavior (filtering the data) and added custom behavior - outputting an event with active filters to use later on a different action

yuda85 avatar Oct 21 '20 13:10 yuda85

closing this issue

vishranti5 avatar May 22 '23 11:05 vishranti5