I'm using React ChartJS 2 to create some graphs but I want to show a label in top of them with the percentage and when I hover over them the real number. I found you can do something like this using the context on the options object.
var options = {
tooltips: {
enabled: false
},
plugins: {
datalabels: {
formatter: (value, ctx) => {
let datasets = ctx.chart.data.datasets;
if (datasets.indexOf(ctx.dataset) === datasets.length - 1) {
let sum = datasets[0].data.reduce((a, b) => a + b, 0);
let percentage = Math.round((value / sum) * 100) + '%';
return percentage;
} else {
return percentage;
}
},
color: '#fff',
}
}
};
var ctx = document.getElementById("pie-chart").getContext('2d');
var myChart = new Chart(ctx, {
type: 'pie',
data: {
datasets: data
},
options: options
});
Like this, but my problem is that I cannot get a way to use the context inside the options.
Does someone knows how to do this?
You don't have to manually pass the context to the datalabels' formatter function since the plugin takes care of this itself.
Here's a working example of the pie graph with the options specified above.
But if you want to access the chart's context in some other functions you want to pass to the options, then you can get it through the chart instance by using this.chart.ctx.
var options = {
animation: {
onComplete: function () {
var chartInstance = this.chart;
var ctx = chartInstance.ctx; // chart context
}
}
};
Related
I'm trying to label a pie chart with icons that I'd draw on with the drawImage() function, but it's not working. Here's my code:
var config = {
type: 'pie',
data: {
datasets: [{
data: [
"1",
"2"
]
}],
labels: [
"1",
"2"
]
}
};
var ctx = document.getElementById('Chart1').getContext('2d');
var myChart = new Chart(ctx, config);
var image = new Image();
image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAA1BMVEX/AAAZ4gk3AAAASElEQVR4nO3BgQAAAADDoPlTX+AIVQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwDcaiAAFXD1ujAAAAAElFTkSuQmCC';
image.onload = () => {
ctx.drawImage(image, 0, 0, 100, 100)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="Chart1"></canvas>
I want it to draw the image on top of the chart, but it's not working, any ideas?
The issue is that chartJs constantly updates the canvas by clearing it and re-rendering the whole thing, like on hovering and mouse movement to add the labels and do some animations, so anything you add to the canvas will be lost.
The workaround in the github discussion regarding the same issue is the way to go. You just need to create a new chart type extending the existing chart type you want to use where you can override the draw method of the new chart type to do the extra stuff you want, the syntax for extending the charts has changed since the github discussion, so we'll use the new one:
Chart.defaults.pieWithExtraStuff = Chart.defaults.pie; // the name of the new chart type is "pieWithExtraStuff"
Chart.controllers.pieWithExtraStuff = Chart.controllers.pie.extend({ // creating the controller for our "pieWithExtraStuff" chart by extending from the default pie chart controller
draw: function(ease) { // override the draw method to add the extra stuff
Chart.controllers.pie.prototype.draw.call(this, ease); // call the parent draw method (inheritance in javascript, whatcha gonna do?)
var ctx = this.chart.ctx; // get the context
if (this.labelIconImage) { // if the image is loaded
ctx.drawImage(this.labelIconImage, 0, 0, 100, 100); // draw it
}
},
initialize: function(chart, datasetIndex) { // override initialize too to preload the image, the image doesn't need to be outside as it is only used by this chart
Chart.controllers.pie.prototype.initialize.call(this, chart, datasetIndex);
var image = new Image();
image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAA1BMVEX/AAAZ4gk3AAAASElEQVR4nO3BgQAAAADDoPlTX+AIVQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwDcaiAAFXD1ujAAAAAElFTkSuQmCC';
image.onload = () => { // when the image loads
this.labelIconImage = image; // save it as a property so it can be accessed from the draw method
chart.render(); // and force re-render to include it
};
}
});
var config = {
type: "pieWithExtraStuff", // use our own chart type, otherwise what's the point?
data: { /* ... */ }
};
var ctx = document.getElementById('Chart1').getContext('2d');
var myChart = new Chart(ctx, config);
Now whenever the pieWithExtraStuff chart re-renders, it will also draw the image.
Demo:
Chart.defaults.pieWithExtraStuff = Chart.defaults.pie;
Chart.controllers.pieWithExtraStuff = Chart.controllers.pie.extend({
draw: function(ease) {
Chart.controllers.pie.prototype.draw.call(this, ease);
var ctx = this.chart.ctx;
if (this.labelIconImage) {
ctx.drawImage(this.labelIconImage, 0, 0, 100, 100);
}
},
initialize: function(chart, datasetIndex) {
Chart.controllers.pie.prototype.initialize.call(this, chart, datasetIndex);
var image = new Image();
image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAA1BMVEX/AAAZ4gk3AAAASElEQVR4nO3BgQAAAADDoPlTX+AIVQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwDcaiAAFXD1ujAAAAAElFTkSuQmCC';
image.onload = () => {
this.labelIconImage = image;
chart.render();
};
}
});
var config = {
type: "pieWithExtraStuff",
data: {
datasets: [{
data: [
"1",
"2"
]
}],
labels: [
"1",
"2"
]
}
};
var ctx = document.getElementById('Chart1').getContext('2d');
var myChart = new Chart(ctx, config);
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="Chart1"></canvas>
I'm trying to apply ZoomIn and ZoomOut in a line chart on a mobile device. The goal is to click on a zone of the chart and ZoomIn in the first click and ZoomOut on the second. The sequence will always be this one.
I already live to see the documentation / examples and I can not find anything to solve this situation.
I have already tried using this properties in the chart: property
pinchType : 'y',
zoomType: 'none'
I tried the zoomtype but the behavior is not what I expect. I want to have a click to zoom this specific area of the chart. I do not want to zoom with two fingers.
{
chart: {
pinchType : 'x'
},
legend: {
itemStyle: {
color: '#fff'
}
},
plotOptions: {
series: {
animation: {
duration: 2000
}
}
},
xAxis: {
tickInterval: 1
},
series: [
{
type: 'spline',
color : '#fff'
},
{
dashStyle: 'longdash',
color: '#b3be77'
}
],
}
As simple as clicking to get zoomin and zoomout
Yes, the second challenge can be easily achieved by adding this logic to plotOptions.series.events.click callback function:
chart: {
events: {
load: function() {
this.clickedOnce = false;
},
click: function() {
const chart = this;
if (chart.clickedOnce) {
chart.zoomOut();
chart.clickedOnce = false;
}
}
}
},
plotOptions: {
series: {
events: {
click: function(e) {
const chart = this.chart,
yAxis = chart.yAxis[0],
xAxis = chart.xAxis[0];
let x,
y,
rangeX,
rangeY;
if (!chart.clickedOnce) {
x = xAxis.toValue(e.chartX);
y = yAxis.toValue(e.chartY);
rangeX = xAxis.max - xAxis.min;
rangeY = yAxis.max - yAxis.min;
xAxis.setExtremes(x - rangeX / 10, x + rangeX / 10, false);
yAxis.setExtremes(y - rangeY / 10, y + rangeY / 10, false);
chart.redraw();
chart.clickedOnce = true;
} else {
chart.zoomOut();
chart.clickedOnce = false;
}
}
}
}
}
Demos:
https://jsfiddle.net/BlackLabel/kotgea5n/
https://jsfiddle.net/BlackLabel/s8w2xg3e/1/
This functionality is not implemented in Highcharts by default, but you can easily achieve it by adding your custom logic when the chart area is clicked.
When area is clicked the first time use axis.setExtremes() method to zoom in. On the second click use chart.zoomOut() to zoom out the chart. Check demo and code posted below.
Code:
chart: {
events: {
load: function() {
this.clickedOnce = false;
},
click: function(e) {
const chart = this,
yAxis = chart.yAxis[0],
xAxis = chart.xAxis[0];
let x,
y,
rangeX,
rangeY;
if (!chart.clickedOnce) {
x = xAxis.toValue(e.chartX);
y = yAxis.toValue(e.chartY);
rangeX = xAxis.max - xAxis.min;
rangeY = yAxis.max - yAxis.min;
xAxis.setExtremes(x - rangeX / 10, x + rangeX / 10, false);
yAxis.setExtremes(y - rangeY / 10, y + rangeY / 10, false);
chart.redraw();
chart.clickedOnce = true;
} else {
chart.zoomOut();
chart.clickedOnce = false;
}
}
}
}
Demo:
https://jsfiddle.net/BlackLabel/fxm812k4/
API reference:
https://api.highcharts.com/class-reference/Highcharts.Axis#setExtremes
https://api.highcharts.com/class-reference/Highcharts.Chart#zoomOut
https://api.highcharts.com/highcharts/chart.events.click
Using a customEvents plugin (see: https://github.com/blacklabel/custom_events) and adding plotBand on the whole chart area you can register a callback on click and double click events. Using this approach you can make a zoom in on click event and zoom out on double click (not working on mobile devices).
Demo:
https://jsfiddle.net/BlackLabel/6tpb5q2z/
I made a chart in chartJS and I would like to update it using new data based on what the user chooses from a drop down list, using the same canvas. The problem is when i do the update function, the chart updates with the new data but it keeps coming back to the original chart after a while. How can i solve this? Here's the code, thank you for any help:
/* Original Chart */
var ctx3 = document.getElementById("canvas3").getContext("2d");
var canvas3 = new Chart(ctx3, {
type: 'line',
data: {
labels: stationRentalsLabels,
datasets: [{
label: 'Wypożyczenia',
fillColor: "rgba(220,280,220,0.5)",
strokeColor: "rgba(220,220,220,1)",
backgroundColor: "rgba(153,255,51,0.4)",
data: stationRentalsData
}]
}
});
/* event listener on drop-down list, when triggered, update chart */
select.addEventListener('change', function() {
updateChart()
});
/* Update Chart */
function updateChart() {
var stationRentalsHoursTemp = [];
var stationRentalName = [];
var determineHour = selectNumber.options[selectNumber.selectedIndex].innerHTML;
for (var i = 0; i < stationRentalsHours.length; i++) {
if (determineHour == stationRentalsHours[i]) {
stationRentalsHoursTemp.push(stationRentalsData[i])
stationRentalName.push(stationRentalsLabels[i]);
}
}
/* Updated Chart */
var ctx3 = document.getElementById("canvas3").getContext("2d");
var canvas3 = new Chart(ctx3, {
type: 'line',
data: {
labels: stationRentalName,
datasets: [{
label: 'Wypożyczenia',
fillColor: "rgba(220,280,220,0.5)",
strokeColor: "rgba(220,220,220,1)",
backgroundColor: "rgba(153,255,51,0.4)",
data: stationRentalsHoursTemp
}]
}
});
}
You are creating new chart on update function in the same div as before but in order to do that you need to destroy the previous instance of the chart by calling the destroy function before calling the updateChart function.
canvas3.destroy();
Another approach to solving your problem is by replacing the data not the chart itself when the updateChart function is called by calling the update function(Not initializing a new chart)
function updateChart() {
var stationRentalsHoursTemp = [];
var stationRentalName = [];
var determineHour = selectNumber.options[selectNumber.selectedIndex].innerHTML;
for (var i = 0; i < stationRentalsHours.length; i++) {
if (determineHour == stationRentalsHours[i]) {
stationRentalsHoursTemp.push(stationRentalsData[i])
stationRentalName.push(stationRentalsLabels[i]);
}
}
// just update the label and dataset not the entire chart
canvas3.data.labels = stationRentalName;
canvas3.data.datasets[0].data = stationRentalsHoursTemp;
canvas3.update();
}
I'm wondering if there is a way to apply dynamic settings to individual marker of an highstock chart? I've searched for half a day and I have the feeling that there is a problem with the API. It seems that there is no ways to adjust marker setting on a specific datum. ex:
$('#container').highcharts('StockChart', {
chart : {
events : {
load : function () {
// set up the updating of the chart each second
var series = this.series[0];
setInterval(function () {
var x = (new Date()).getTime(), // current time
y = Math.round(Math.random() * 100);
series.addPoint([x, y], true, true);
}, 1000);
}
}
},
series : [{
data : (function () {
var data = [], time = (new Date()).getTime(), i;
for (i = -999; i <= 0; i += 1) {
data.push([
{ x: time + i * 1000,
y: Math.round(Math.random() * 100),
marker:{
fillColor:'red'
}
}
]);
}
return data;
}())
}]
}
I've fork a basic Highstock demo to illustrate my point. See the jsfiddle that demonstrate the problem: http://jsfiddle.net/9xj0nz72/1/
Maybe I have an error in my fiddle... or may I have to create an issue on Github?
Thanks a lot!!
I had to assign the style in the addPoint method, you can't just push to the data array. And you have to use it on the chart = new Highcharts.StockChart() variable.
I'm pretty sure I got what you were hoping for using the following. And to demonstrate I assigned a random color and radius to each new point.
$(function () {
var chart = new Highcharts.StockChart({
chart: {
renderTo: 'container'
},
plotOptions: {
series: {
marker: {
enabled: true
}
}
},
series: [{
name: 'Random data',
data: [],
time: (new Date()).getTime()
}]
});
/* add new random point every 1 second */
var i = 0;
setInterval(function () {
i++;
chart.series[0].addPoint({
marker: {
/* assign a random hex color and radius */
fillColor: '#' + (Math.random() * 0xFFFFFF << 0).toString(16),
radius: Math.floor(Math.random() * 10) + 1
},
y: Math.random() * 100,
x: i * 1000,
}, true, false);
}, 1000);
});
Your updated JSFiddle
The problem I am encountering is that the black line is doing some funky stuff compared to the blue line. If you scroll in the middle or somewhere else (use bottom scroll tool), you can clearly see that the black line is changing its shape, while the blue line holds its shape, strange. This only happens when you use the scroll tool.
How can I prevent the black line changing its shape? Copy this code and replace it with the JSfiddle to see the problem:
$(function () {
Highcharts.setOptions({
global : {
useUTC : false
}
});
// Create the chart
$('#container').highcharts('StockChart', {
chart : {
events : {
load : function () {
// set up the updating of the chart each second
var series = this.series[0];
setInterval(function () {
var x = (new Date()).getTime(), // current time
y = Math.round(Math.random() * 100);
series.addPoint([x, y], true, true);
}, 1000);
var series1 = this.series[1];
setInterval(function () {
var x = (new Date()).getTime(), // current time
y = Math.round(Math.random() * 100);
series1.addPoint([x, y], true, true);
}, 1000);
}
}
},
rangeSelector: {
buttons: [{
count: 1,
type: 'minute',
text: '1M'
}, {
count: 5,
type: 'minute',
text: '5M'
}, {
type: 'all',
text: 'All'
}],
inputEnabled: false,
selected: 0
},
title : {
text : 'Live random data'
},
exporting: {
enabled: false
},
series : [{
name : 'diagram1',
data : (function () {
// generate an array of random data
var data1 = [], time = (new Date()).getTime(), i;
for (i = -999; i <= 0; i += 1) {
data1.push([
time + i * 1000,
Math.round(Math.random() * 100)
]);
}
return data1;
}())
},
{
name : 'diagram2',
data : (function () {
// generate an array of random data
var data2 = [], time = (new Date()).getTime(), i;
for (i = -999; i <= 0; i += 1) {
data2.push([
time + i * 1000,
Math.round(Math.random() * 100)
]);
}
return data2;
}())
}]
});
});
The only thing I have done is to add an extra dynamic line (black one) to the diagram. Here is the original code without the black line. Review original code
The reason of the movement in the black line is the redraw and the animation function of the chart. For some reason the first series(blue line) doesn't show animation after calling addPoint so you don't see the movement. The second parameter in the addPoint function is redraw. Setting it to false for the second series(black line) will stop the movement when updating the points:
series1.addPoint([x, y], false, true);
Here's the DEMO.