Sum of visible Points in ChartJs Bar Chart - javascript

the chartjs has the option to hide datasets by clicking on the label. I want to sum up all points of the barchart, but only the visible ones. I know how to sum up all points, but don't know how to check if the dataset is Visible or not. To sum up the points I use the onComplete animation event:
animation: {
onComplete: function(animation) {
var sqm = 0;
this.data.datasets.forEach(function (dataset) {
dataset.data.forEach(function (points) {
sqm = sqm + points;
})
})
$("#SquareMeterSurface").val(sqm);
}
},
here is how it looks like:
How can I do the sum for visible datasets only (in the graph above the blue ones are not visible)? I use ChartJs 2.8
thanks

So I found the solution by myself. In the 'animation' variable passed to the onComplete callback is an array 'animation.chart.active' which can be looped to find the active dataset Indexes. The active array is only populated when hovering over the bars of the graph, thats why the sum of points are only displayed when hovering over the bars.
The whole code looks now like this:
function success_(data) {
var ctx = document.getElementById('canvas').getContext('2d');
window.myBar = new Chart(ctx, {
type: 'bar',
data: data,
options: {
title: {
display: true,
text: 'Square Meters done per day'
},
tooltips: {
mode: 'index',
intersect: false
},
maintainAspectRatio: false,
responsive: true,
scales: {
xAxes: [{
stacked: true
}],
yAxes: [{
stacked: true
}]
},
animation: {
onComplete: function(animation) {
//alert('onAnimationComplete');
if (typeof animation.chart !== 'undefined' && typeof animation.chart.active !== 'undefined') {
var active_datasets = new Array();
//loop through active dataset to get the dataset indexes
//which are visible to the user
animation.chart.active.forEach(function(active_ds) {
active_datasets.push(active_ds._datasetIndex);
})
//loop through datasets to get the points for active datasets
var sqm = 0;
var i = 0;
this.data.datasets.forEach(function (dataset) {
if (active_datasets.indexOf(i) != -1) {
dataset.data.forEach(function (points) {
sqm = sqm + points;
})
}
i = i + 1;
})
$("#SquareMeterSurface").val(parseFloat(sqm).toFixed(1) + ' m2');
}
}
},
}
});
};
and the ajax call to invoce a .Net Core MVC action is like this:
$.ajax({
url: '/YourController/YourActionForGraph/',
type: 'GET',
data: {
'param1': $('#param1').val(),
'param2': $('#param2').val()
},
dataType: 'json',
success: success_,
error: function (request, error) {
alert("Request: " + JSON.stringify(request));
}
});

Thanks for your hint. This one helped me, so I came up with the following solution:
`const options = {
radius: "100%",
cutout: "90%",
animation: {
onComplete: function (animation) {
console.log(animation.chart.getDatasetMeta(0));
},
},
};`
You can call getDatasetMeta(index) on the chart object and you will get an object with all current data used to construct the chart. Here you find the total property with the current sum.
See: https://www.chartjs.org/docs/latest/developers/api.html#getdatasetmeta-index

Related

Chart.js Console JS Error while destroy the Chart on click event

on Bar click event of first chart "config.categoriesChart" gets the console error "chart.js:10403 Uncaught TypeError: Cannot read property 'handleEvent' of undefined"
enter image description here which diplayed as chart.legend.handleEvent(args.event);
The issue happens after destroy the First chart on the bar click Event of first chart.
But if I use $('#how_i_spend_canvas').replaceWith($(''));
its working fine without destroy the chart.
enter image description here
Please let me what is the issue?
All code in document.ready
let Chart = require('chart.js');/
let ChartDataLabels = require('chartjs-plugin-datalabels');
let config = window.MvpFE.globalConfiguration.howISpend;
let dataChart = window.dataHowISpendCharts;
let labels = dataChart.howISpendDataCatgories.map(function (e) {
return e.label;
});
let data = dataChart.howISpendDataCatgories.map(function (e) {
return e.data;
});
//Chart Axis's
let scales = {
x: {
ticks: {
font: {
size: config.size,
},
color: config.dataLabelsColor,
},
},
y: {
display: false,
}
};
//Chart legend
let plugins = {
legend: {
display: false,
},
tooltip: {
enabled: true,
},
};
//Chart Data Labels
let dataLabels = {
color: config.dataLabelsColor,
anchor: 'end',
align: 'top',
offset: 0,
formatter: function (value) {
//Include a dollar sign
return '$' + value.toLocaleString();
},
};
//chart data
let howISpendChartdata = {
labels: labels,
datasets: [{
data: data,
backgroundColor: config.catogriesBackgroundColor,
borderColor: config.catogriesBorderColor,
hoverBackgroundColor: config.unSelectedColor,
hoverBorderColor: config.unSelectedColor,
borderWidth: config.barWidth,
borderRadius: config.barRadius,
borderSkipped: 'false',
datalabels: dataLabels
}]
}
// Category heading label text will be from json data
let categoryLabel = "";
//Array to store the bar background colors.
const barColors = [];
//Code to draw Chart
var ctx = document.getElementById('how_i_spend_canvas').getContext('2d');
config.categoriesChart = new Chart(ctx, {
type: 'bar',
data: howISpendChartdata,
// Chart pulgins & Options
plugins: [ChartDataLabels],
options: {
responsive: true,
maintainAspectRatio: false,
aspectRatio: 2,
plugins: plugins,
scales: scales,
onClick: function (evt, element) {
if (element.length > 0) {
const categoriesChart = config.categoriesChart;
let activeBarIndex = element[0].index;
categoryLabel = categoriesChart.data.labels[activeBarIndex];
// destroy any chart instances that are created
if (categoriesChart instanceof Chart) {
categoriesChart.destroy();
}
//$('#how_i_spend_canvas').replaceWith($('<canvas id="SelectedCategory" height="400px"></canvas>')); //replace current canvas
// Code to draw Chart
config.monthlyChart = new Chart(ctx, {
type: 'bar',
data: howISpendChartdata,
plugins: [ChartDataLabels],
options: {
responsive: true,
maintainAspectRatio: false,
aspectRatio: 2,
plugins: plugins,
scales: scales,
onClick: function (e, activeElements) {
//get the colors for bars
if (activeElements.length > 0) { // check the element is selected
const monthlyChart = config.monthlyChart;
monthlyChart.options.animation.colors = false;
monthlyChart.update();
}
}
}
},
});
config.monthlyChart.render();
}
},
}
}); // document.Ready Ends()
WARNING: This solution makes sense only if you're not using the legend plugin or don't need to handle legend item click event.
In my case I was getting this error even though I disabled legend plugin in chart options, like this:
plugins: {
legend: {
display: false
}
}
The error stopped happening after I filtered events handled by the legend plugin, like this:
plugins: {
legend: {
display: false,
events: [] // this line was the key
},
}
I came accros the same issue, reason for the exception is:
Inside the onclick event if you try to destroy the same chart, chart reference becomes null before the event callback function returns. And that's why exception is thrown.
You can fix this by destroying the chart instance after the event callback is completed i.e. using setTimeout function you can destroy the chart after 100ms or so. you can do it like this:
options: {
onClick: function (evt, element) {
if (element.length > 0) {
const categoriesChart = config.categoriesChart;
let activeBarIndex = element[0].index;
categoryLabel = categoriesChart.data.labels[activeBarIndex];
setTimeout(() => {
// destroy any chart instances that are created
if (categoriesChart instanceof Chart) {
categoriesChart.destroy();
}
//$('#how_i_spend_canvas').replaceWith($('<canvas id="SelectedCategory" height="400px"></canvas>')); //replace current canvas
// Code to draw Chart
config.monthlyChart = new Chart(ctx, {
type: 'bar',
data: howISpendChartdata,
plugins: [ChartDataLabels],
options: {
responsive: true,
maintainAspectRatio: false,
aspectRatio: 2,
plugins: plugins,
scales: scales,
onClick: function (e, activeElements) {
//get the colors for bars
if (activeElements.length > 0) { // check the element is selected
const monthlyChart = config.monthlyChart;
monthlyChart.options.animation.colors = false;
monthlyChart.update();
}
}
}
});
config.monthlyChart.render();
}, 100);
}
}
}
Below is solution for anyone facing similar problem:
options: {
onClick: function (evt, element) {
// get the require data from click event
let chart = Chart.getChart(e.chart.canvas.id);
const points = chart.getElementsAtEventForMode(e, 'nearest', { intersect: true }, true);
if (points.length) {
const firstPoint = points[0];
const elementIndex = firstPoint.index;
const datasetIndex = firstPoint.datasetIndex;
const dataset = chart.data.datasets[datasetIndex];
const datasetFieldLabel = dataset.label;
const itemLabel = chart.data.labels[elementIndex];
const itemValue = dataset.data[elementIndex];
setTimeout(() => {
// destroy the chart
// Render another chart
}, 100);
}
}
}

Bubble chart change X label values from a value in JSON response dynamically

I have a bubble chart using Chart.JS and getting my values dynamically from the database. The plotting of the data works absolutely fine, however I am trying to make a few formatting tweaks to the chart.
I want to change the X values to show the category (it is in my JSON output) on the horizontal axis rather than the i value. The JSON output contains category which is a string but I cant seem to do x: bubbleDatas[i].category?
The output currently shows on my x axis: 0,1,2,3,4,5 but i want it so show the value category from my JSON response which is in bubbleDatas?
data e.g.:
{
x: 0,
y: 60,
r: 10
}, {
x: 1,
y: 20,
r: 10
},
{
x: 2,
y: 40,
r: 10
}...
In my JSON response ajax request my X values i want it to be text:
e.g. 01_First, 02_Second
$(function () {
var bubbleData = [];
var xAxisLabels;
$.ajax({
type: "POST",
async: false,
url: "ExecView.aspx/ReturnData",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (data) {
var bubbleDatas = data.d;
bubbleData = new Array(bubbleDatas.length);
console.log(bubbleDatas);
for (i = 0; i < bubbleDatas.length; i++) {
if (bubbleDatas[i].score >= 60) {
rgbcolour = "#008000";
}
else if (bubbleDatas[i].score >= 50 && bubbleDatas[i].score < 60) {
rgbcolour = "#FFA500";
}
else {
rgbcolour = "#FF6347";
}
bubbleData[i] = { **x: i,** y: bubbleDatas[i].score, r: bubbleDatas[i].radius, backgroundcolor: rgbcolour };
console.log(bubbleData[i]);
}
}
});
var popData = {
datasets: [{
label: "Test",
data: bubbleData
}]
};
var bubbleOptions = {
responsive: true,
legend: {
display: false
},
tooltips: {
callbacks: {
label: function (t, d) {
return d.datasets[t.datasetIndex].label +
': (Category:' + t.xLabel + ', Score:' + t.yLabel + ')';
}
}
},
scales: {
yAxes: [{
ticks: {
// Include a % sign in the ticks
callback: function (value, index, values) {
return value + '%';
}
}
}]
}
};
var ctx5 = document.getElementById("bubble_chart").getContext("2d");
new Chart(ctx5, { type: 'bubble', data: popData, options: bubbleOptions });
});
Changing category to more meaning might be your specific requirement, check this fiddle if it helps bubble chartJS fiddle and check this labelling in chartJS
P.S. check out your condition for x-axis in the callback and print accordingly

Chartist line graphs animation starts from the bottom

I'm using Chartist.js to generate line graphs, It works great, but I notice lines animation starts at the beginning in a certain height from chart bottom by default:
How can I modify my code to start lines animation from the bottom of the chart like this:
This is my code:
function graph(month,shop,sale){
var data = {
labels: month,
series: [sale,shop]
};
var options = {
fullWidth:true,
height: 380,
showPoint: true,
low: 0,
showArea: true,
lineSmooth: false,
plugins: [
Chartist.plugins.legend({
legendNames: ['Sale', 'Shop'],
})
]
};
var chart = new Chartist.Line('.ct-chart', data, options);
chart.on('draw', function(data) {
if(data.type === 'point') {
var circle = new Chartist.Svg('circle', {
cx: [data.x], cy:[data.y], r:[5],
}, 'ct-circle');
data.element.replace(circle);
}
});
chart.on('draw', function(data) {
if(data.type === 'line' || data.type === 'area') {
data.element.animate({
d: {
begin: 1000 * data.index,
dur: 1000,
from: data.path.clone().scale(1, 0).translate(0, data.chartRect.height()).stringify(),
to: data.path.clone().stringify(),
easing: Chartist.Svg.Easing.easeOutQuint
}
});
}
});
}
var data = {
labels: ['Enero', 'Febrero', 'Marzo','Abril','Mayo','Junio']
};
var options = {
fullWidth:true,
height: 380
};
new Chartist.Line('.ct-chart', data, options);
$(document).ready(function(){
$.ajax({
url: "myfile.php",
type: "GET",
dataType: "json",
success: function(resp)
{
var month = resp[0];
var shop = resp[1];
var sale = resp[2];
graph(month,shop,sale);
}
});
});
I would like some help.
If you inspect the elements with the google developer tools, you will find out that the class "ct-series ct-series-b" from the chart, has an specific height that it doesn't go all the way down. If you then change in the animation when the chart is drawing to something like:
from: data.path.clone().scale(1, 0).translate(0, data.chartRect.height() + 15).stringify(),
The line will start from the height of the ct-series-b + 15px all the way up to the circle points.
Hope it helps, Leo.

Chart.js Formatting dual axis and labels

jsfiddle here.
First time using Chart.js and I cannot find an example with the whole code to review. I have the following data for 3 months to chart:
Project hours billed, Project hours not billed (stacked bar, for each month)
Billed Amount, To Be Billed amount (stacked bar for each month)
I want to stack the hours and also the billing totals, and want two Y axis, one for hours, the other for dollars.
I have got as far as this code, but it does not stack the hours or the invoiced amounts for each month. Also, I cannot seem to format either axis and the values in the labels to time and currency.
Is there an example you can point me to showing this. Thanks!
var barChartData = {
labels: ["January", "February", "March"],
datasets: [{
type: 'bar',
label: 'Billed Hours',
backgroundColor: "rgba(220,220,220,0.5)",
yAxisID: "y-axis-1",
data: [33.56, 68.45, 79.35]
}, {
type: 'bar',
label: 'Non Billed Hours',
backgroundColor: "rgba(222,220,220,0.5)",
yAxisID: "y-axis-1",
data: [3.50, 8.58, 7.53]
}, {
type: 'bar',
label: 'Income',
backgroundColor: "rgba(151,187,205,0.5)",
yAxisID: "y-axis-2",
data: [3800.00, 7565.65, 8500.96]
}, {
type: 'bar',
label: 'Income',
backgroundColor: "rgba(155,187,205,0.5)",
yAxisID: "y-axis-2",
data: [320.00, 780.65, 850.96]
}]
};
var ctx = document.getElementById("projectHours").getContext("2d");
window.myBar = new Chart(ctx, {
type: 'bar',
data: barChartData,
options: {
responsive: true,
hoverMode: 'label',
hoverAnimationDuration: 400,
stacked: true,
title: {
display: true,
text: "Billed / Billable Project Summary"
},
scales: {
yAxes: [{
type: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
display: true,
position: "left",
id: "y-axis-1",
}, {
type: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
display: true,
position: "right",
id: "y-axis-2",
gridLines: {
drawOnChartArea: false
},
}],
},
animation: {
onComplete: function () {
var ctx = this.chart.ctx;
ctx.textAlign = "center";
Chart.helpers.each(this.data.datasets.forEach(function (dataset) {
Chart.helpers.each(dataset.metaData.forEach(function (bar, index) {
ctx.fillText(dataset.data[index], bar._model.x, bar._model.y - 10);
}),this)
}),this);
}
}
}
});
You can extend the bar chart to do this
Preview
Script
Chart.defaults.groupableBar = Chart.helpers.clone(Chart.defaults.bar);
var helpers = Chart.helpers;
Chart.controllers.groupableBar = Chart.controllers.bar.extend({
calculateBarX: function (index, datasetIndex) {
// position the bars based on the stack index
var stackIndex = this.getMeta().stackIndex;
return Chart.controllers.bar.prototype.calculateBarX.apply(this, [index, stackIndex]);
},
// hide preceding datasets in groups other than the one we are in
hideOtherStacks: function (datasetIndex) {
var meta = this.getMeta();
var stackIndex = meta.stackIndex;
this.hiddens = [];
for (var i = 0; i < datasetIndex; i++) {
var dsMeta = this.chart.getDatasetMeta(i);
if (dsMeta.stackIndex !== stackIndex) {
this.hiddens.push(dsMeta.hidden);
dsMeta.hidden = true;
}
}
},
// reverse hideOtherStacks
unhideOtherStacks: function (datasetIndex) {
var meta = this.getMeta();
var stackIndex = meta.stackIndex;
for (var i = 0; i < datasetIndex; i++) {
var dsMeta = this.chart.getDatasetMeta(i);
if (dsMeta.stackIndex !== stackIndex) {
dsMeta.hidden = this.hiddens.unshift();
}
}
},
// we hide preceding datasets in groups other than the one we are in
// we then rely on the normal stacked logic to do its magic
calculateBarY: function (index, datasetIndex) {
this.hideOtherStacks(datasetIndex);
var barY = Chart.controllers.bar.prototype.calculateBarY.apply(this, [index, datasetIndex]);
this.unhideOtherStacks(datasetIndex);
return barY;
},
// similar to calculateBarY
calculateBarBase: function (datasetIndex, index) {
this.hideOtherStacks(datasetIndex);
var barBase = Chart.controllers.bar.prototype.calculateBarBase.apply(this, [datasetIndex, index]);
this.unhideOtherStacks(datasetIndex);
return barBase;
},
getBarCount: function () {
var stacks = [];
// put the stack index in the dataset meta
Chart.helpers.each(this.chart.data.datasets, function (dataset, datasetIndex) {
var meta = this.chart.getDatasetMeta(datasetIndex);
if (meta.bar && this.chart.isDatasetVisible(datasetIndex)) {
var stackIndex = stacks.indexOf(dataset.stack);
if (stackIndex === -1) {
stackIndex = stacks.length;
stacks.push(dataset.stack);
}
meta.stackIndex = stackIndex;
}
}, this);
this.getMeta().stacks = stacks;
return stacks.length;
},
});
and then
...
type: 'groupableBar',
options: {
scales: {
yAxes: [{
ticks: {
// we have to set this manually (or we could calculate it from our input data)
max: 160,
},
stacked: true,
}]
}
}
});
Note that we don't have any logic to set the y axis limits, we just hard code it. If you leave it unspecified, you'll end up with the limits you get if all the bars were stacked in one group.
Fiddle - http://jsfiddle.net/4rjge8sk/

Highcharts - column chart redraw animation

I'm trying to update an existing data series with a new data array and invoke the redraw function when done. While this works perfectly, I'm not quite satisfied as I'd like to have a sort of grow/shrink transition. I have seen an example by Highcharts (fiddle around with the existing data set then click on the button "Set new data to selected series") but I can't replicate this behavior.
This is what code that I've written:
var series, newSeriesThreshold = this.chart.series.length * 2;
for (var i = 0; i < data.length; i += 2) {
series = {
name: this.data[i].title,
data: this.data[i].data,
color: this.data[i].color
};
if (i >= newSeriesThreshold) {
this.chart.addSeries(series, false);
} else {
var currentSeries = this.chart.series[i / 2];
currentSeries.setData(series.data, false);
}
}
this.chart.redraw();
These are the options when creating the chart:
var config = {
chart: {
renderTo: $(this.container).attr('id'),
type: this.settings.type,
animation: {
duration: 500,
easing: 'swing'
}
},
title: {
text: null
},
legend: {
enabled: this.settings.legend.show
},
tooltip: {
formatter: function() {
return this.x.toFixed(0) + ": <b>" + this.y.toString().toCurrency(0) + '</b>';
}
},
xAxis: {
title: {
text: this.settings.xaxis.title,
style: {
color: '#666'
}
}
},
yAxis: {
title: {
text: this.settings.yaxis.title,
style: {
color: '#666'
}
}
},
series: series,
plotOptions: {
column: {
color: '#FF7400'
}
},
credits: {
enabled: false
}
};
This yields an immediate update without transitioning effects. Any ideas what I might be doing wrong?
I have solved this problem by destroying and creating again the chart.
Here is the link on highcharts forum that helps me : http://forum.highcharts.com/highcharts-usage/animation-on-redraw-t8636/#p93199
The answer comes from the highcharts support team.
$(document).ready(function() {
var chartOptions = {
// Your chart options
series: [
{name: 'Serie 1' , color: '#303AFF', data: [1,1,1,1,1,1,1}
]
};
var chart = new Highcharts.Chart(chartOptions);
$("#my_button").click(function(){
chart.destroy();
chartOptions.series[0].data = [10,5,2,10,5,2,10];
chart = new Highcharts.Chart(chartOptions);
});
});
This remain a mystery. I managed to make the axis update which suggests that there is some kind of animation going on, but it's applied only to the axis, and not the columns.
In the end, I've settled with this behavior.
This might help:
var mychart = $("#mychart").highcharts();
var height = mychart.renderTo.clientHeight;
var width = mychart.renderTo.clientWidth;
mychart.setSize(width, height);
to update all charts
$(Highcharts.charts).each(function(i,chart){
var height = chart.renderTo.clientHeight;
var width = chart.renderTo.clientWidth;
chart.setSize(width, height);
});

Categories

Resources