ng2-charts
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
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
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
closing this issue