Ng2-charts: 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

Created on 15 May 2019  路  6Comments  路  Source: valor-software/ng2-charts

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

Most helpful comment

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();
    };

All 6 comments

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

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 do you know that when using arrow functions you keep the context and you dont have to do the self stuff ?

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

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;
    }
  }
}

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tushartgsit picture tushartgsit  路  5Comments

martinpinto picture martinpinto  路  3Comments

raychenfj picture raychenfj  路  3Comments

hggeorgiev picture hggeorgiev  路  4Comments

RKornu picture RKornu  路  3Comments