How to use Chart.js plugin data-labels with ng2-chart? - javascript

Well, here I am again with my Angular and javascript woes feeling dumber for each question I ask.
But let me try to explain my initial steps and how it lead to this problem. So, in my latest project, I wanted to throw some fancy charts to make things cleaner and more approachable for the user. Chart.js came to mind... or I should say, ng2-charts.
Things were peachy, stuff looked nice, but... I had two problems. On a horizontal bar, I wanted to dynamically alter the size of the chart, so the user didn't have to scroll for an obscure amount of time to get down the page. So instead of this unwieldy beast...
I try to apply some Angular magic. Later on I want to calculate the size of it on my backend end. For now, a static value shall do.
#ViewChild('inventoryChart') itemChart : ElementRef;
constructor(public renderer: Renderer2) { }
ngAfterViewInit() {
this.renderer.setStyle(this.itemChart.nativeElement, 'height', '230px')
}
Which leads to...
Nice. But my second problem was a lot harder than I assumed it to be. I wanted the charts to have the respective values on each bar chart. I was somewhat shocked to learn, that it wasn't an innate feature of chart.js but a plugin instead. So I tried to narrow down my problems I had by looking at my chart configuration.
#ViewChild('itemChart') itemChart : ElementRef;
//public context: CanvasRenderingContext2D;
public chartType: string = 'horizontalBar';
chartDatalabel: ChartLabel;
public chartDatasets: Array<any> = [
{ data: [28, 20, 6, 5, 3], label: 'Inventory per country' }
];
public chartLabels: Array<any> = ['DEU', 'SVK', 'FRA', 'GBR', 'AUT'];
public chartColors: Array<any> = [
{
backgroundColor: '#0E599A',
pointBackgroundColor: 'rgba(220,220,220,1)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgba(220,220,220,1)'
},
];
public chartOptions: any = {
responsive: true,
maintainAspectRatio: false,
scales: {
yAxes: [{
barPercentage: .9,
categoryPercentage: .9,
gridLines: {
offsetGridLines: true,
display: true,
},
ticks: {
beginAtZero: true
}
}],
xAxes: [{
ticks: {
beginAtZero: true,
min: 0
},
gridLines: {
offsetGridLines: true,
display: true,
},
}]
},
};
Since I had a similar question and by looking at the documentation... it would require the chart to register the plugin.
So I added
import { Chart } from 'chart.js';
But quickly realized, I didn't know how to actually get this chart instance I was creating in my component. And how could I add this in the option for this specific chart only? I didn't find many sources with a similar problem to mine (which made me feel even more inept...).
Is there any clean 'angular-way' to add this plugin in ng2-charts?
EDIT: Judging from the documentation, I could define what the plugin can do in the following ways...
var plugin = { /* plugin implementation */ };
And then calling this plugin in the options of the chart or globally...
Chart.plugins.register({
// plugin implementation
});
Which I would like to avoid. Never a fan of global configuration unless absolutely necessary and time-saving.
EDIT2: Sorta gave up on NOT registering it globally, but it still doesn't help a whole lot. I added to my imports...
import { Chart } from 'chart.js';
import * as ChartLabel from 'chartjs-plugin-datalabels';
And then tried to register the plugin in the global Chart.
ngOnInit() {
Chart.plugins.register(ChartLabel);
}
Which has done 'something' as far as I can tell. So I attempted to do a very basic implementation of the plugin. The other tooltips don't work anymore but it only triggers an error when I hover around the bars.
plugins: {
datalabels: {
color: 'white',
display: true,
font: {
weight: 'bold'
},
formatter: Math.round
}
},
No clue what I can do anymore...

Well, since there was literally no answer, I could ultimately settle for the "Global" Chart solution.
Chart.pluginService.register({
// I gave it an id, so I could disable the plugin if needed
id: 'p1',
afterDatasetsDraw: function (chart, _ease) {
let width = chart.chart.width;
let ctx = chart.chart.ctx;
let datasets = chart.data.datasets;
datasets.forEach(function (_dataset, index) {
let meta = chart.getDatasetMeta(index);
// grabbing the array with the data...
let barLabelData = meta.controller._data;
// console.log(meta.controller._data)
if (!meta.hidden) {
meta.data.forEach(function (segment, _index) {
let model = segment._model;
let position = segment.tooltipPosition();
let x = position.x;
let y = position.y;
let height = model.height;
ctx.restore();
ctx.textBaseline = "middle";
// var fontSize = (height / 114).toFixed(2);
ctx.font = 'bold ' + height / 2 + 'px Arial';
ctx.fillStyle = '#777'; //first label's font color
let text = barLabelData[_index];
ctx.fillText(text, x, y);
ctx.save();
})
}
});
}
});
Which ultimately graced me with a sorta okay looking chart. So yeah, it's not at clean as I would like to. But there are still issues with implementation.
My Chart component globally implements my plugin logic. That's awful deisgn here and I have to decouple it. The minor problem is, I have to make sure the labels work always and are properly depicted. But for now I'm pleased it works.

Here's an example done by #paviad:
https://stackblitz.com/edit/ng2-charts-bar-labels
import { Component, OnInit } from '#angular/core';
import { ChartOptions, ChartType, ChartDataSets } from 'chart.js';
import * as pluginDataLabels from 'chartjs-plugin-datalabels';
import { Label } from 'ng2-charts';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
public barChartOptions: ChartOptions = {
responsive: true,
// We use these empty structures as placeholders for dynamic theming.
scales: { xAxes: [{}], yAxes: [{}] },
plugins: {
datalabels: {
anchor: 'end',
align: 'end',
font: {
size: 20,
}
}
}
};
...
public barChartPlugins = [pluginDataLabels];
...
Detailed options are here:
https://chartjs-plugin-datalabels.netlify.app/

Related

PowerBI Embedded - how to customize menu operation/context and custom layout

I have integrated powerbi-embedded with Angular 9 web app also add code for custom layut but it's not working.
I want to customize operations menu /context and custom layout.
Configuration object -
const config: any = {
type: 'report',
uniqueId: 'report-id',
permissions: this.model.Permissions.All,
embedUrl: 'embed-url',
accessToken: 'access-token',
settings: {
layoutType: this.models.LayoutType.Custom,
customLayout: {
pageSize: {
type: this.models.PageSizeType.Custom,
width: 1600,
height: 1200
},
displayOption: this.models.DisplayOption.ActualSize,
pagesLayout: {
"MyReportSection007" : {
defaultLayout: {
displayState: {
mode: this.models.VisualContainerDisplayMode.Hidden
}
},
visualsLayout: {
"VisualContainer1": {
x: 1,
y: 1,
z: 1,
width: 400,
height: 300,
displayState: {
mode: this.models.VisualContainerDisplayMode.Visible
}
},
"VisualContainer2": {
displayState: {
mode: this.models.VisualContainerDisplayMode.Visible
}
},
}
}
}
}
}
};
Above config. not working. any idea how can i achived customization in menu operation and layout.
I'm following below docs -
https://microsoft.github.io/PowerBI-JavaScript/demo/v2-demo/index.html#
https://github.com/microsoft/PowerBI-JavaScript/wiki/Custom-Layout
Thanks,
Your query is not clear since you have not shared any error message.
Though, code snippet that you shared offers a correct solution but, it might be possible that you are using wrong names for visual containers (i.e. VisualContainer1 or VisualContainer2) or the report page name (i.e. MyReportSection007). Ensure that you are providing correct names.
And if possible, please share the error message as well.

Show Labels on Pie pieces instead of Data values Chart.js

I use Chart.js for making charts. I discovered today new plugin for the original Chart.js.
Plugin
After i added <script> tags with the plugin, it applied automatically values to all my charts. It looks great, but it shows number values. How do i make it show labels instead of values on the pieces of the pie? I have found some posts about the subject, but they contain different commands, and i tried all i could but it didn't change anything. Also for the future, tell me please how to turn off showing values for specific chart :)
fillPie()
{
// Three arrays have same length
let labelsArr = []; // Array with some names
let values = []; // Array with values
let randomColor = [];
var ctx = document.getElementById('pie-chart').getContext('2d');
var chart = new Chart(ctx, {
// The type of chart we want to create
type: 'pie',
// The data for our dataset
data: {
labels: labelsArr, // I want it to show these labels
datasets: [{
backgroundColor: randomColor,
data: values, // It shows these values
hoverBackgroundColor: "#fba999"
}]
},
// Configuration options go here
options: {
legend: {
display: false
}
}
});
}
You can find the answer in the docs of the plugin:
https://chartjs-plugin-datalabels.netlify.com/guide/formatting.html#custom-labels
options: {
plugins: {
datalabels: {
formatter: function(value, context) {
return context.chart.data.labels[context.dataIndex];
}
}
}
}
The dev simonbrunel explained on GitHub how you can disable the plugin globally or for specific datasets. The following is a quote from the GitHub link:
That should be possible by disabling labels for all datasets via the plugin options at the chart level using the display option, then enable labels per dataset at the dataset level (dataset.datalabels.*):
new Chart('id', {
data: {
datasets: [{
// no datalabels for this dataset
}, {
datalabels: {
// display labels for this specific dataset
display: true
}
}
},
options: {
plugins: {
datalabels: {
// hide datalabels for all datasets
display: false
}
}
}
})
You can also globally disable labels for all charts using:
// Globally disable datalabels
Chart.defaults.global.plugins.datalabels.display = false

angular chartjs not showing data properly

I'm using Angular 6 and trying to implement a Chart.js line chart. Basically, I'm calling my API for two arrays: a weight_data array and a weight_date array and using them for the chart.
I store the arrays I got from the API request in two instances: this.weight_date and this.weight_data.
Here's the code for the chart:
// code for chart here
this.chart = new Chart(this.chartRef.nativeElement, {
type: 'line',
data: {
labels: this.weight_date, // your labels array
datasets: [
{
data: this.weight_data, // your data array
borderColor: '#e0573a',
fill: true
}
]
},
options: {
legend: {
display: false
},
scales: {
xAxes: [{
display: true
}],
yAxes: [{
display: true
}],
},
title: {
display: true,
text: 'Weight Tracker',
fontFamily: "'Montserrat', sans-serif",
fontColor: '#272727',
fontSize: 18,
padding: 12
},
layout: {
padding: {
left: 10,
right: 20,
top: 0,
bottom: 0
}
},
gridLines: {
drawOnChartArea: true
},
}
});
It's works okay when I use pre-coded arrays (["11/02/18", "11/03/18", "11/04/18"] for dates and [65, 66, 67] for weight). But when I try to use the two instances, the chart comes up blank. There's no error of sorts, it's just blank.
I'm doing the API call first then initializing the chart, respectively, in ngOnInit. What am I missing here?
It looks like you want to subscribe to an observable which can provide the data, and once it comes in, you can then initialize your chart. Usually, we would want to have the HTTP call occur within a service, but to simplify, this is how we could do with within a component:
import {HttpClient} from '#angular/common/http';
import {Observable} from 'rxjs/Observable';
import {Component, OnInit} from '#angular/core';
export class MyChart implements OnInit {
constructor(private http: HttpClient) { }
private getData() {
let url = '[SOME-URL]' // your path to the data API endpoint
return this.http.get(url) as Observable<Array<any>>; // you would want to use correct typings here, instead of <any>
}
ngOnInit() {
this.getData().subscribe((data: any) => {
console.log(data);
// assign your data here, and then initialize your chart.
});
}
}
I just found out what I was doing wrong: I'm using axios, which is promise-based, so taking #JeffFhol's suggestion into account, my two instances were blank since I was initializing the chart at the same time I was making the API request.
The solution was to initialize the chart in the then clause of the axios request, which ensures that my instances aren't empty.
Here's the snippet that worked:
axios({
method: 'post',
url: 'url_to_api',
headers: {
'Content-Type' : 'application/json'
// custom data headers, etc.
},
data: {
// data to be sent
}
})
.then(response => {
this.weight_dates = response.data.weight_date;
this.weight_data = response.data.weight_data;
// code for chart here
})
.catch(error => {
console.log(error)
})

D3.js V4 Zoom not working in Angular

I'm using D3 v4 and Datamaps in my angular project.
I try to make the zoom working but I have no clue what's the problem.
I have a function that creates the map:
public createChart() {
this.geoChart = new Datamap({
element: this.geolocationChart.nativeElement,
scope: 'world',
width: null,
height: null,
projection: 'mercator',
responsive: true,
geographyConfig: {
hideAntarctica: true,
borderWidth: 1,
borderOpacity: 1,
borderColor: '#ffffff',
popupTemplate: function(geo, data) {
return ['<div class="hoverinfo"><strong>', geo.properties.name, ': ' + data.countryData, '</strong></div>'].join('');
},
popupOnHover: true,
highlightOnHover: true,
highlightFillColor: '#ff4200',
highlightBorderWidth: 2,
highlightBorderOpacity: 1
},
fills: {
defaultFill: '#f5f5f5',
highlight: '#5005a0'
},
data: this.convertData(this.defaultCountryData),
done: function(datamap) {
datamap.svg.call(d3.zoom().on('zoom', redraw));
function redraw() {
datamap.svg.selectAll('g').attr('transform', d3.event.transform);
}
}
});
}
I trigger it in ngAfterContentInit.
When I try to use the mouse wheel to zoom or drag the map, I receive this error message in the console:
"Cannot read property 'button' of null"
I'm importing D3 and Datamaps like this:
import * as d3 from 'd3';
import Datamap from 'datamaps/dist/datamaps.world.min.js';
I'm really clueless what could be the problem. I've seen other posts and everyone mentioned to import D3 just like I did. Any ideas?
So after more research I write this code:
function(datamap) {
d3.select('.datamap').call(
d3
.zoom()
.scaleExtent([0.7, 6])
.on('zoom', function() {
datamap.svg.selectAll('g').attr('transform', d3.event.transform);
})
);
}
It runs after the chart has loaded, and fortunately it works. For some reason when you call the zoom() function on the datamap.svg it cause an error while datamap.svg and d3.select('.datamap') creates a similar array but d3.select create a selection. If anyone know why it doesn't work the other way please explain it.

Highcharts annotations not rendering

I'm trying to dynamically add annotations to my high charts in React. I'm using the addAnnotation function to add a new annotation whenever a hover event is triggered on my app, but the annotation does not render. I dropped a debugger into my code, and when I call chart.annotations I can see there is currently an array of annotations, but they are not rendering. I even have make a call to the addPlotLine in this function and the plotline is rendered on the chart. My config file looks like this
chart: {
annotations: [{
labelOptions: {
backgroundColor: 'rgba(0, 0, 0, 0.5)',
verticalAlign: 'top',
align: 'right',
}
}],
annotationsOptions: {
},
.... some lots more config options
}
and my on hover function to add the annotation is as follows
if( isNumber(value) )
//this renders a plotline
chart.xAxis[0].addPlotLine(baseChartHandle.generateInteractivePlotLine(value));
// this doesn't render my annotation however
chart.addAnnotation({
linkedTo: value,
title: {
text: "It works!"
}
});
}
I found that when I added annotations using Highcharts that the "linkedTo" function never worked despite my trials.
Despite adding a guid to my point, it never was able to apply it. I would suggest in your case adding it by x and y value, then finding the point that way instead. Here is how I have added annotations in the past successfully:
series.data.forEach(function (point) {
var annotationObject =
{
id: point.id,
labelOptions: {
y: 15,
verticalAlign: 'bottom',
distance: 25
},
labels: []
};
}
var text = <get text you want to push>;
annotationObject.labels.push(
{
point: {
xAxis: 0,
yAxis: 0,
x: point.x,
y: point.y
},
text: text
}
);
_chart.addAnnotation(annotationObject);
});
I also found a bug in HighCharts when using remove annotations, as a heads up. Each point will need an id like above, but it should be called by this line:
_chart.removeAnnotation(id);
It will remove the annotation, however you will get lots of complaints from highcharts if you try to do anything after, and it will break. I found the answer here, in annotations.js :
The red box is code I added. If you do removeAnnotation(ann.id), and it rerenders, it will fail because the labels object is null. Adding this check lets you do that without the labelCollectors failing.
Happy charting.

Categories

Resources