Closed chauanthuan closed 8 months ago
I don't know if you need this answer anymore but I had the same problem and would like to post my solution for others to benefit as well.
To create a custom legend using ng2-chart you have to do the following.
`import { BaseChartDirective } from 'ng2-charts';
@Component({ selector: 'my-selector', templateUrl: 'my-html-code.html' }) export class MyChartComponent { // ... stuff @ViewChild(BaseChartDirective) chartComponent: BaseChartDirective; legendData: any; // ... stuff }`
<canvas baseChart #myChart ... [options]="myChartOptions" [legend]="false" ... ></canvas>
This is so you can turn off the legend that is drawn directly to the canvas
private getLegendCallback = (function(self) { function handle(chart) { // Do stuff here to return an object model. // Do not return a string of html or anything like that. // You can return the legendItems directly or // you can use the reference to self to create // an object that contains whatever values you // need to customize your legend. return chart.legend.legendItems; } return function(chart) { return handle(chart); } })(this);
myChartOptions = { / ... stuff ... /, legendCallback: this.getLegendCallback, / ... stuff ... / };
this.legendData = this.chartComponent.chart.generateLegend( );
< div ngIf="legendData"> < ul class="my-legend"> < li class="legend-item" ngFor="let legendItem of legendData">{{legendItem.text}}</ li> </ ul> </ div>
The legendItem object will have the colors, fonts and all sorts of stuff. As I said before, if you need anything extra, just create and return an object that has whatever you need from your custom generateLegend( ) function. You can also add click events here the way you normally would in Angular. It worked beautifully for me.
I hope this helps.
Ricardo
Hi Ricardo, Can you please provide an example of what goes inside the method.
private getLegendCallback = (function(self) { function handle(chart) { // Do stuff here to return an object model. // Do not return a string of html or anything like that. // You can return the legendItems directly or // you can use the reference to self to create // an object that contains whatever values you // need to customize your legend. return chart.legend.legendItems; } return function(chart) { return handle(chart); } })(this);
The example I posted already has something inside the method. If you get rid of the comments I posted inside the function, you will see that what's left is a return statement with the legend items object provided by Chart.js. See below:
private getLegendCallback = (function(self) { function handle(chart) { return chart.legend.legendItems; // <-- THIS ... comes out in #5 of my orig post } return function(chart) { return handle(chart); } })(this);
This returns the legend items from the Chart as-is. You have the option of customizing that return value. In my case, I needed to add the value of the "slope" of the line to each legend item, which is not provided by Chart.js. This came from my calculations so I had to add the slope to each legend item by referencing the variable through the "self" reference.
Assuming you have something like this somewhere in your component ...
private slopes: any = { }; // populated from some ajax call or whatever
... then instead of this ...
return chart.legend.legendItems;
... you can do this ...
return chart.legend.legendItems.map(function(i) { i['slope'] = self.slopes[i.text]; return i; });
or something like that (pulling that from memory ... not tested ... but you get the gist).
I hope that helps.
Ricardo
hi Ricardo, I followed your instructions. Here is the snippet of my code.
legendData: any;
pieChartOptions: any = { legend: { legendCallback: this.getLegendCallback } }
private getLegendCallback = (function(self) { function handle(chart) { return chart.legend.legendItems; } return function(chart) { return handle(chart); } })(this);
HTML
With this code, i am getting the following error.
Error in ./PieChartComponent class PieChartComponent - inline template:19:33 caused by: Cannot find a differ supporting object '' of type 'string'. NgFor only supports binding to Iterables such as Arrays.
Do you see anything wrong in my code.?
The code you posted is incomplete. I don't see the ngFor in your code so I can't see what object it's trying to iterate through. Make sure your legendData variable is an object or array that ngFor can iterate through. Set a break point in the browser at the return statement in the custom getLegendCallback function so you can inspect the contents of the legendItems and ensure its an array. Bottom line is I don't have enough information to spot the problem.
Sorry, did not paste the html code properly. Here is my HTML code
<div >
<div>
<canvas baseChart
[(data)] = "totalCRCount"
[(labels)]="totalLabel"
[chartType]="chartType"
[options]="pieChartOptions"
[legend]="false">
</canvas>
</div>
<div *ngIf="legendData">
<ul class="my-legend">
<li class="legend-item" *ngFor="let legendItem of legendData">{{legendItem.text}}</li>
</ul>
</div>
</div>
Here is my ChartOptions code
pieChartOptions: any = {
legend: {
legendCallback: this.getLegendCallback
}
}
private getLegendCallback = (function (self) {
function handle(chart) {
return chart.legend.legendItems;
}
return function (chart) {
return handle(chart);
}
})(this);
When i debug, i see the this.chart.chart.generateLegend()
returns "<ul class="6-legend"></ul>"
and never hits the break point at getLegendCallback().
Here is the data
[ { "year": 2017, "priority": "High", "crCount": 1 }, { "year": 2017, "priority": "E.R", "crCount": 1 }, { "year": 2017, "priority": "Normal", "crCount": 263 }, { "year": 2016, "priority": "High", "crCount": 6 }, { "year": 2016, "priority": "E.R", "crCount": 7 }, { "year": 2016, "priority": "Normal", "crCount": 1452 }, { "year": 2015, "priority": "E.R", "crCount": 4 }, { "year": 2015, "priority": "High", "crCount": 35 }, { "year": 2015, "priority": "Normal", "crCount": 825 }, { "year": 2014, "priority": "E.R", "crCount": 2 }, { "year": 2014, "priority": "High", "crCount": 41 }, { "year": 2014, "priority": "Normal", "crCount": 640 }, { "year": 2013, "priority": "E.R", "crCount": 1 }, { "year": 2013, "priority": "High", "crCount": 21 }, { "year": 2013, "priority": "Normal", "crCount": 418 }, { "year": 2012, "priority": "E.R", "crCount": 1 }, { "year": 2012, "priority": "High", "crCount": 10 }, { "year": 2012, "priority": "Normal", "crCount": 105 } ]
As far as the break point not hitting the custom function, it means that somehow your options are not being set properly. You might want to add a semi-colon after the closing curly brace of your options object just in case (not sure that matters). Other than that, I cannot see the problem. My guess is that the options are not being set for the chart somehow but I don't see why that is looking at the code you posted. EDIT: Scratch my previous statement about *ngFor ... was looking at your ngIf. Sorry.
I'm using Chart.js v2.5.0 by the way. Perhaps you're using a different version. In that case the placement of the legendCallback in the options object may be different.
Hi @rickyricky74, I'm flowed the step you posted, but I'm getting this error:
ERROR TypeError: Cannot read property 'generateLegend' of undefined
Can you help me to solve this ?
This is my code:
import { BaseChartDirective } from 'ng2-charts'; import {Component, OnInit, ViewChild} from '@angular/core';
@Component({ selector: 'custom-chart', templateUrl: 'custom.chart.component.html' }) export class CustomChartComponent implements OnInit {
@ViewChild(BaseChartDirective) chartComponent: BaseChartDirective;
legendData: any;
private getLegendCallback = (function(self) {
function handle(chart) {
// Do stuff here to return an object model.
// Do not return a string of html or anything like that.
// You can return the legendItems directly or
// you can use the reference to self to create
// an object that contains whatever values you
// need to customize your legend.
return chart.legend.legendItems;
}
return function(chart) {
return handle(chart);
};
})(this);
myChartOptions = {
responsive: true,
legendCallback: this.getLegendCallback
};
public lineChartLabels: Array<any> = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FR', 'SAT'];
public lineChartType = 'line';
// public lineChartOptions: any = {
// responsive: true
// };
public lineChartColors: Array<any> = [
{
backgroundColor: 'rgba(101,120,196,0.3)',
borderColor: 'rgb(101,120,196)',
pointBackgroundColor: 'rgb(101,120,196)',
pointBorderColor: '#fff',
},
{
backgroundColor: 'rgba(25,209,185,0.3)',
borderColor: 'rgb(25,209,185)',
pointBackgroundColor: 'rgb(25,209,185)',
pointBorderColor: '#fff',
}
];
public lineChartColors2: Array<any> = [
{
backgroundColor: 'rgba(217,93,121,0.3)',
borderColor: 'rgb(217,93,121)',
pointBackgroundColor: 'rgb(217,93,121)',
pointBorderColor: '#fff',
},
{
backgroundColor: 'rgba(249,174,91,0.3)',
borderColor: 'rgb(249,174,91)',
pointBackgroundColor: 'rgb(249,174,91)',
pointBorderColor: '#fff',
}
];
ngOnInit() {
this.legendData = this.chartComponent.chart.generateLegend( );
}
}
And this is how I used in the view:
<custom-chart></custom-chart>
Hi @rickyricky74, I'm flowed the step you posted, but I'm getting this error:
ERROR TypeError: Cannot read property 'generateLegend' of undefined
Can you help me to solve this ?
The angular DOM not detect the changes when the Component receive the data. To fix it, you need to add the detectChanges of ChangeDetectorRef.
<canvas baseChart
[datasets]="chartConfig.data"
[labels]="chartConfig.labels"
chartType="line"
[options]="chartConfig.options"
[legend]="false">
</canvas>
import { ChangeDetectorRef } from '@angular/core';
@ViewChild(BaseChartDirective) baseChart: BaseChartDirective;
constructor(private cdRef: ChangeDetectorRef) { }
this.service.getChartData().subscribe(
chartData => {
this.chartConfig.data = chartData;
this.cdRef.detectChanges();
this.loadLegend();
}
}
private loadLegend() {
this.baseChart.chart.generateLegend();
}
This works for me.
@JacoboSegovia Can you please provide a full example with details on what code goes where? I am not sure what this.service
refers to.
Thanks!
I'm actually facing a similar issue. I"ve make a custom legend component and I'm able to show/hide using the above codes. However, this only works for bar chart. It raise the following exception when using it with donut chart:
TypeError: Cannot read property '_meta' of undefined at Chart.getDatasetMeta (core.controller.js:656)
Have anyone faced a similar issue please?
I am facing same issue. If a I populate label details from initialized array, the label field gets rendered on html page and gets populated in data label. However if I use the extracted data from my service class(of same data type and values), data does not populate on html page, even if the values are extracted correctly from service class. This is what I am getting now.
And this is what I expect:
Also, label data should be shown on hower
@GeraudWilling the problem is when you call the function. try like this only to see if work
in the component test(){ this.legendData = this.chartComponent.chart.generateLegend(); }
in the view <button (click)="test()">Test Labels
Sorry my english
There is a simple way to get access to the auto generated legend object properties separately. Use the following:
First add
import { ChangeDetectorRef } from '@angular/core'
import { BaseChartDirective } from 'ng2-charts'
..
..
@ViewChild('partsBaseChart') partsBaseChart: BaseChartDirective
After fetching your data, request defect change:
return this.fetch(url).subscribe(
(res: any)=> {
// ... do something
this.cdRef.detectChanges()
this.partsChartLegendItems = this.partsBaseChart.chart.legend.legendItems
// ...
},
(err: any)=> {
// do handle errors ..
}
)
Sample of the output of this.partsBaseChart.chart.legend.legendItems
[{"text":"...","fillStyle":"rgba(255,99,132,0.6)","strokeStyle":"#fff","lineWidth":2,"hidden":false,"index":0}, ....]
pieChartOptions: any = {
// no need for legend configuration at all
}
<canvas baseChart #partsBaseChart="base-chart" [legend]="!1" [data]="partsChartData" [options]="pieChartOptions" [labels]="partsChartLabels" [chartType]="pieChartType"></canvas>
<div *ngIf="partsChartLegendItems">
<ul class="custom-legend-list">
<li *ngFor="let item of partsChartLegendItems;let i = index" class="custom-legend-item" (click)="partsChartData[i]>0 ? legendOnClick(item.text):!1">
<span class="slice-color" [ngStyle]="{'background-color': item.fillStyle}"></span>
<a href="javascript: void(0)" class="slice-title">{{ item.text }} ({{ partsChartData[i]||0 }})</a>
</li>
</ul>
</div>
Hi, im trying to make the labels scrollable, is there any way with ng2-charts??? help pls :)
help me! :( I want to show percentage in the legend.
Hi @Danielapariona my solution was: First, download and install Chart Piece Label In your component:
component.ts
public pieOptions:any = {
pieceLabel: {
render: function (args) {
let total = args["dataset"].data.reduce((a, b) => a + b, 0);
let percent = args.value*100/total;
return percent.toFixed(1)+'%';
}
};
and in yours component.html
<canvas #pieChart baseChart width="550"
[chartType]="pieChartType"
[datasets]="datasets"
[labels]="pieLabels"
[colors]="colors"
[options]="pieOptions">
</canvas>
I want the legend to look like that:
any updates on this ?
Can anybody show a running stackblitz example of custom legend?
Hi @rickyricky74, I'm flowed the step you posted, but I'm getting this error:
ERROR TypeError: Cannot read property 'generateLegend' of undefined
Can you help me to solve this ?
This is my code:
import { BaseChartDirective } from 'ng2-charts'; import {Component, OnInit, ViewChild} from '@angular/core';
@component({ selector: 'custom-chart', templateUrl: 'custom.chart.component.html' }) export class CustomChartComponent implements OnInit {
@ViewChild(BaseChartDirective) chartComponent: BaseChartDirective; legendData: any; private getLegendCallback = (function(self) { function handle(chart) { // Do stuff here to return an object model. // Do not return a string of html or anything like that. // You can return the legendItems directly or // you can use the reference to self to create // an object that contains whatever values you // need to customize your legend. return chart.legend.legendItems; } return function(chart) { return handle(chart); }; })(this); myChartOptions = { responsive: true, legendCallback: this.getLegendCallback }; public lineChartLabels: Array<any> = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FR', 'SAT']; public lineChartType = 'line'; // public lineChartOptions: any = { // responsive: true // }; public lineChartColors: Array<any> = [ { backgroundColor: 'rgba(101,120,196,0.3)', borderColor: 'rgb(101,120,196)', pointBackgroundColor: 'rgb(101,120,196)', pointBorderColor: '#fff', }, { backgroundColor: 'rgba(25,209,185,0.3)', borderColor: 'rgb(25,209,185)', pointBackgroundColor: 'rgb(25,209,185)', pointBorderColor: '#fff', } ]; public lineChartColors2: Array<any> = [ { backgroundColor: 'rgba(217,93,121,0.3)', borderColor: 'rgb(217,93,121)', pointBackgroundColor: 'rgb(217,93,121)', pointBorderColor: '#fff', }, { backgroundColor: 'rgba(249,174,91,0.3)', borderColor: 'rgb(249,174,91)', pointBackgroundColor: 'rgb(249,174,91)', pointBorderColor: '#fff', } ]; ngOnInit() { this.legendData = this.chartComponent.chart.generateLegend( ); }
}
And this is how I used in the view:
<custom-chart></custom-chart>
@rickyricky74 solution works for me. There is only one change. To resolve "Cannot read property 'generateLegend' of undefined`", replace your ngOnInit function with this -
`ngOnInit() {
setInterval(() => {
this.legendData = this.chartComponent.chart.generateLegend( );
}, 10);
} `
Use only html template without manual change detection changes. HTML Template:
<canvas baseChart #partsBaseChart="base-chart" [legend]="!1" [data]="partsChartData" [options]="pieChartOptions" [labels]="partsChartLabels" [chartType]="pieChartType"></canvas>
<div *ngIf="partsBaseChart?.chart?.legend?.legendItems">
<ul class="custom-legend-list">
<li *ngFor="let item of partsBaseChart.chart.legend.legendItems; let i = index" class="custom-legend-item" (click)="legendOnClick(item.text)">
<span class="slice-color" [ngStyle]="{'background-color': item.fillStyle}"></span>
<span class="slice-title">{{ item.text }} </span>
</li>
</ul>
</div>
How to use the html code to display the data value and legend description
Any working solution for this ???
How to use the html code to display the data value and legend description
For the value I created a new array and accessing it with the same index of the legendItems:
into html template
<li *ngFor="let item of partsBaseChart.chart.legend.legendItems; let i = index" class="custom-legend-item" (click)="legendOnClick(item.text)">
<span class="slice-color" [ngStyle]="{'background-color': item.fillStyle}"></span>
<span class="slice-title">{{ item.text }} </span>
<span>{{chart.graphValues[i]?.value}}</span>
</li>
For anyone who comes to this looking at the original solution, I found an issue with @bkartik2005 's implementation that was tripping me up:
pieChartOptions: any = {
legend: {
legendCallback: this.getLegendCallback
}
}
Is incorrect. legendCallback
isn't part of the legend
object. legendCallback
is it's own option. If you follow the original solution provided by @rickyricky74 and place the legendCallback
outside the legend
object this works.
Use only html template without manual change detection changes. HTML Template:
<canvas baseChart #partsBaseChart="base-chart" [legend]="!1" [data]="partsChartData" [options]="pieChartOptions" [labels]="partsChartLabels" [chartType]="pieChartType"></canvas> <div *ngIf="partsBaseChart?.chart?.legend?.legendItems"> <ul class="custom-legend-list"> <li *ngFor="let item of partsBaseChart.chart.legend.legendItems; let i = index" class="custom-legend-item" (click)="legendOnClick(item.text)"> <span class="slice-color" [ngStyle]="{'background-color': item.fillStyle}"></span> <span class="slice-title">{{ item.text }} </span> </li> </ul> </div>
Hi @cantacell, I tried using this code to get rid of manual change detection, but it throws an error saying - "Property legend doesn't exist on type chart". Did you do anything else apart from this code in your HTML file?
So, I did some snooping around the baseChart
directive and found a bit of an easier solution for updating the chart while persisting those hidden values [stackblitz link below]. I really hope this helps someone!
For sake of clarity... I made the legend itself with simple buttons that use *ngFor index in html :
<a *ngFor="let data of chartData; let i = index;"
(click)="onSelect(i)">
<span [ngClass]="data.hidden ? 'hidden' : 'showing'">
{{ data.label }}
</span>
</a>
In the ts file, I added the BaseChartDirective
@ViewChild(BaseChartDirective) baseChart: BaseChartDirective;
Then, in the method that receives the index, I would change that item's hidden
value to true
and update the chart by calling update()
on the baseChart
directive (which I first assigned to a new ci
variable). If all hidden
values were set to true
then I'd Object.assign
them to be false again with another update()
call on the baseChart
:
onSelect(indexItem): void {
const ci = this.baseChart;
this.chartData[indexItem].hidden = true;
ci.update();
if (this.chartData.every(each => each.hidden === true)) {
this.chartData.map(item => Object.assign(item, {hidden: false}))
ci.update();
}
}
I put this on stackblitz to help: https://stackblitz.com/edit/ng2-chartjs-customlegend?file=src/app/line-chart/line-chart.component.ts
@riapacheco Your example doesn't show how to display chart color palette in the legend, so not a complete integration.
Hi everybody, Can anyone help me how to make custom legend in chartjs when using ng2-chart. In Chart.js V2 document, i using function generateLegend() to make it, but in angular 2 i dont see it. Thank you! Ps: Sorry, my english is not good.