Related
Last few days I've been trying to convert this code to pure JS, but no luck until now...
This code basically starts a new Chart.JS instance when called via jQuery object.
(function ($) {
$.fn.createChartLine = function (labels, datasets, options) {
var settings = $.extend({}, $.fn.createChartLine.defaults, options);
this.each(function () {
let ctx = $(this);
new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: datasets
},
options: {
scales: {
y: {
title: {
display: true,
text: settings.labelString
},
beginAtZero: true
}
},
plugins: {
legend: {
display: settings.display_legend,
position: settings.legend_position,
labels: {
usePointStyle: settings.legend_pointStyle
}
}
}
}
});
});
};
$.fn.createChartLine.defaults = {
display_legend: true,
legend_position: 'top',
legend_pointStyle: true,
labelString: 'Clicks'
};
})(jQuery);
The initialization of a new chartline using the code above:
$("#chart1").createChartLine(['1', '2', '3'], [{
label: 'Clicks',
backgroundColor: Chart.helpers.color('#007bff').alpha(0.75).rgbString(),
borderColor: '#007bff',
data: [34, 56, 28],
borderWidth: 2,
tension: 0.4
}], {display_legend: false});
I tried thousands of ways to remove jQuery from it, but no luck. The intention is to get rid of jQuery in some pages, but this script is essential because with it I can create many Chart.JS instances in same page without needing repeating that amount of code.
I'm aiming to get something like this:
document.getElementById('chart1').createChartLine(...);
Is it possible?
This assumes you are using a version of ChartJS that accepts HTMLCanvasElement and other non-jQuery wrapped Elements to the new Chart() constructor.
You shouldn't extend the prototype of native elements, instead you should create a new function that gets the element(s) passed in.
// Instead of
document.getElementById('chart1').createChartLine(...);
// You'll want
createChartLine(document.getElementById('chart1'), ...);
or something similar.
I'd actually not pass in the element, but rather a selector, since that most closely matches how the jQuery plugin is working.
function createChartLine(selector, labels, datasets, options = {}) {
const settings = Object.assign(
{},
{
display_legend: true,
legend_position: "top",
legend_pointStyle: true,
labelString: "Clicks",
},
options
);
const elements = document.querySelectorAll(selector);
const charts = [];
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const newChart = new Chart(element, {
type: "line",
data: {
labels: labels,
datasets: datasets,
},
options: {
scales: {
y: {
title: {
display: true,
text: settings.labelString,
},
beginAtZero: true,
},
},
plugins: {
legend: {
display: settings.display_legend,
position: settings.legend_position,
labels: {
usePointStyle: settings.legend_pointStyle,
},
},
},
},
});
charts.push(newChart);
}
return charts;
}
Here is how you'd call the function.
createChartLine(
"#chart1", // This is the new argument, the selector of the element you are initializing
["1", "2", "3"],
[
{
label: "Clicks",
backgroundColor: Chart.helpers.color("#007bff").alpha(0.75).rgbString(),
borderColor: "#007bff",
data: [34, 56, 28],
borderWidth: 2,
tension: 0.4,
},
],
{ display_legend: false }
);
I want to display a bar chart - each bar is a user/student. And for each student, there will be an xAxis label displaying the students name
The below code is a VueJS computed property and called chartData
My data array for the bar chart is an array of objects. Here is how i generate it
let dataRow = this.responses.map((d) => {
return {
label: d.user.name,
data: [d.grade],
}
});
Here is how I generate my labels
let users = [];
this.responses.map((d) => {
users.push(d.user.name)
});
I then return on object with an array of labels and datasets with data being an array of objects
return {
labels: users,
datasets: [{
data: dataRow
}],
}
Here is how I render the chart:
{
extends: Bar,
props: ["chartdata"],
data() {
return {
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
max: 100
}
}],
},
}
}
},
mounted() {
this.renderChart(this.chartdata, this.options)
}
}
Issue: Nothing displays and there are no errors
The bar chart only seems to work when the data in the datasets is not an array of object like:
testData: {
labels: ['test', 'test', 'test'],
datasets: [{
data: [65, 59, 80],
}]
}
After Sayf-Eddine comment, i have managed to achieve this:
I changed how i returned the chartdata like:
return {
labels: users,
datasets: dataRow
}
However, all bars are mapping to the first label
After Vitaliy Rayets comment i figured out what was wrong
i needed to return the data row like:
let dataRow = this.responses.map((d) => {
return {
label: d.user.name,
y: [d.grade],
}
});
**I changed the property 'data' to 'y' **
I manage to generate a graph from an ajax call coming from a php file.
I want to modify this graphic from the selection of the customers field (chg_customer) via a drop-down list. My drop-down list works and shows the names of the customers. However, I cannot generate a new chart from this selection with the right number (count) corresponding to the right customer. Can you help me ? thank you
html :
<select id="filter">
<option>Tous</option>
</select>
<canvas id="graph4Canvas" style="height: 700px; width: 100%;"></canvas></select>
graph.js :
$(document).ready(function() {
$.ajax({
url: 'graph4.php',
type: 'GET',
dataType: 'json',
success: function(data) {
var chg_customer = [];
var count = [];
for (var i in data) {
chg_customer.push(data[i].chg_customer);
count.push(data[i].count);
}
var ctx = $('#graph4Canvas');
const barGraph = new Chart(document.getElementById('graph4Canvas'), {
type: 'bar',
data: {
labels: data.map(o => o.chg_customer),
datasets: [{
label: 'Clients',
data: data.map(o => o.count),
backgroundColor: "rgba(0, 0, 255, 0.5)",
yAxisID: 'Nombres',
xAxisID: 'Clients',
}]
},
options: {
scales: {
yAxes: [{
id: "Nombres",
ticks: {
beginAtZero: true,
stepSize: 1,
fontSize: 15,
},
scaleLabel: {
display: true,
labelString: 'Nombres'
},
gridLines: {
drawOnChartArea: false
},
}],
xAxes: [{
id: "Clients",
ticks: {
beginAtZero: true,
stepSize: 1,
fontSize: 10,
},
scaleLabel: {
display: false,
},
gridLines: {
drawOnChartArea: false
},
}],
},
title: {
display: false,
},
legend: {
display: false,
},
},
});
data.forEach(o => {
const opt = document.createElement('option');
opt.value = o.chg_customer;
opt.appendChild(document.createTextNode(o.chg_customer));
document.getElementById('filter').appendChild(opt);
});
$("#filter").change(function() {
//update data
});
There isn't a need to generate a new chart. Once the chart exists all you need to do is to edit the datasets and call the chart update function to re-render the chart.
e.g (example is using angular in my case but still should apply):
HTML:
<mat-select [(value)]="selectedFilter" (selectionChange)="filterStreamType()">
JS:
public filterStreamType() {
// code manipulates data....
this.chart.data.datasets = newData; // update our dataset so chart js can re-draw it
this.chart.update(); // This is a redundant call but I did a bad job here and mixed some things that should not be mixed and so
}
Also note that between V2 and V3 there have been breaking changes so this syntax may vary a bit depending on the version you use.
I'm essentially attempting to create a bar chart with 2-8 items where the label on the bottom/legend is the short product code(ex: 4380) and mousing over the bar shows the full SKU/product name.
I have gotten it mostly working but my implementation goes one of two undesirable ways.
The data points all combine into the first product number/chart label.
The blank spots make the bars tiny/not fill up the full width.
My code for rendering the chart is as follows:
var myBarChart2;
$.ajax({
url: "chartdata.php",
data: {
"skugroup": group
},
method: 'GET',
dataType: 'json',
success: function (d) {
Chart.defaults.global.defaultFontFamily = '-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif';
Chart.defaults.global.defaultFontColor = '#292b2c';
var ctx = document.getElementById("inventorybarchart");
myBarChart2 = new Chart(ctx, {
type: 'bar',
data: {
labels: d.labels,
datasets: d.datasets,
},
options: {
scales: {
xAxes: [{
gridLines: {
display: false
},
ticks: {
display: true
}
}],
yAxes: [{
ticks: {
min: 0,
beginAtZero: true
},
gridLines: {
display: true
}
}],
},
legend: {
display: false
}
}
});
}
});
The ajax response for the two versions is as follows:
Version 1:
{"datasets":[{"labels":"GRAY-DARK-GRAY","backgroundColor":"rgba(164,222,164,1)","borderColor":"rgba(164,222,164,1)","data":[5996]},{"labels":"CANARY-YELLOW","backgroundColor":"rgba(35,148,58,1)","borderColor":"rgba(35,148,58,1)","data":[4605]},{"labels":"PINK-WHITE-GRAY","backgroundColor":"rgba(101,24,125,1)","borderColor":"rgba(101,24,125,1)","data":[1288]},{"labels":"SEAFOAM-WHITE-GRAY","backgroundColor":"rgba(129,74,64,1)","borderColor":"rgba(129,74,64,1)","data":[3463]},{"labels":"YELLOW-WHITE-GRAY","backgroundColor":"rgba(91,216,70,1)","borderColor":"rgba(91,216,70,1)","data":[1537]},{"labels":"WHITE-YELLOW","backgroundColor":"rgba(101,225,237,1)","borderColor":"rgba(101,225,237,1)","data":[152]}],"labels":["4380","4311","4571","4588","4557","4373"]}
Version 2:
{"datasets":[{"label":"GRAY-DARK-GRAY","backgroundColor":"rgba(1,1,235,1)","borderColor":"rgba(1,1,235,1)","data":[5996,null,null,null,null]},{"label":"CANARY-YELLOW","backgroundColor":"rgba(12,87,184,1)","borderColor":"rgba(12,87,184,1)","data":[null,4605,null,null,null]},{"label":"PINK-WHITE-GRAY","backgroundColor":"rgba(85,107,126,1)","borderColor":"rgba(85,107,126,1)","data":[null,null,1288,null,null]},{"label":"SEAFOAM-WHITE-GRAY","backgroundColor":"rgba(181,150,65,1)","borderColor":"rgba(181,150,65,1)","data":[null,null,null,3463,null]},{"label":"YELLOW-WHITE-GRAY","backgroundColor":"rgba(132,66,28,1)","borderColor":"rgba(132,66,28,1)","data":[null,null,null,null,1537]},{"label":"WHITE-YELLOW","backgroundColor":"rgba(49,195,217,1)","borderColor":"rgba(49,195,217,1)","data":[null,null,null,null,null]}],"labels":["4380","4311","4571","4588","4557","4373"]}
The only difference is either I always use the 0 indexes for datasets[index].data or I fill in null depending on where it should be.
Should I be changing the way the chart is rendered or should I change the way the data is passed in?
For the record, the mouseover shows the proper sku/full name.
I would define the data in a single dataset and keep the full product names in a separate property.
const data = {
"labels": ["4380", "4311", "4571", "4588", "4557", "4373"],
"productNames": ["GRAY-DARK-GRAY", "CANARY-YELLOW", "PINK-WHITE-GRAY", "SEAFOAM-WHITE-GRAY", "YELLOW-WHITE-GRAY", "WHITE-YELLOW"],
"datasets": [{
"data": [5996, 4605, 1288, 3463, 1537, 152],
...
}]
};
To get the product names displayed in the tooltip, you would have to define a label callback function as follows:
tooltips: {
callbacks: {
label: (tooltipItem, data) => {
let i = tooltipItem.index;
return data.productNames[i] + ': ' + data.datasets[0].data[i];
}
}
}
Please take a look at your amended code and see how it works.
const data = {
"labels": ["4380", "4311", "4571", "4588", "4557", "4373"],
"productNames": ["GRAY-DARK-GRAY", "CANARY-YELLOW", "PINK-WHITE-GRAY", "SEAFOAM-WHITE-GRAY", "YELLOW-WHITE-GRAY", "WHITE-YELLOW"],
"datasets": [{
"data": [5996, 4605, 1288, 3463, 1537, 152],
"backgroundColor": ["rgba(1,1,235,1)", "rgba(12,87,184,1)", "rgba(85,107,126,1)", "rgba(181,150,65,1)", "rgba(132,66,28,1)", "rgba(49,195,217,1)"],
"borderColor": ["rgba(1,1,235,1)", "rgba(12,87,184,1)", "rgba(85,107,126,1)", "rgba(181,150,65,1)", "rgba(132,66,28,1)", "rgba(49,195,217,1)"]
}]
};
var ctx = document.getElementById("inventorybarchart");
myBarChart2 = new Chart(ctx, {
type: 'bar',
data: data,
options: {
scales: {
xAxes: [{
gridLines: {
display: false
}
}],
yAxes: [{
ticks: {
beginAtZero: true
}
}],
},
legend: {
display: false
},
tooltips: {
callbacks: {
label: (tooltipItem, data) => {
let i = tooltipItem.index;
return data.productNames[i] + ': ' + data.datasets[0].data[i];
}
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.0/Chart.min.js"></script>
<canvas id="inventorybarchart" height="90"></canvas>
I've just started working with Chart.js, and I am getting very frustrated very quickly. I have my stacked bar chart working, but I can't get the click "events" to work.
I have found a comment on GitHub by nnnick from Chart.js stating to use the function getBarsAtEvent, even though this function cannot be found in the Chart.js documentation at all (go ahead, do a search for it). The documentation does mention the getElementsAtEvent function of the chart reference, but that is for Line Charts only.
I set an event listener (the right way) on my canvas element:
canv.addEventListener('click', handleClick, false);
...yet in my handleClick function, chart.getBarsAtEvent is undefined!
Now, in the Chart.js document, there is a statement about a different way to register the click event for the bar chart. It is much different than nnnick's comment on GitHub from 2 years ago.
In the Global Chart Defaults you can set an onClick function for your chart. I added an onClick function to my chart configuration, and it did nothing...
So, how the heck do I get the on-click-callback to work for my bar chart?!
Any help would be greatly appreciated. Thanks!
P.S.: I am not using the master build from GitHub. I tried, but it kept screaming that require is undefined and I was not ready to include CommonJS just so that I could use this chart library. I would rather write my own dang charts. Instead, I downloaded and am using the Standard Build version that I downloaded straight from the link at the top of the documentation page.
EXAMPLE: Here is an example of the configuration I am using:
var chart_config = {
type: 'bar',
data: {
labels: ['One', 'Two', 'Three'],
datasets: [
{
label: 'Dataset 1',
backgroundColor: '#848484',
data: [4, 2, 6]
},
{
label: 'Dataset 2',
backgroundColor: '#848484',
data: [1, 6, 3]
},
{
label: 'Dataset 3',
backgroundColor: '#848484',
data: [7, 5, 2]
}
]
},
options: {
title: {
display: false,
text: 'Stacked Bars'
},
tooltips: {
mode: 'label'
},
responsive: true,
maintainAspectRatio: false,
scales: {
xAxes: [
{
stacked: true
}
],
yAxes: [
{
stacked: true
}
]
},
onClick: handleClick
}
};
I managed to find the answer to my question by looking through the Chart.js source code.
Provided at line 3727 of Chart.js, Standard Build, is the method .getElementAtEvent. This method returns me the "chart element" that was clicked on. There is sufficent data here to determine what data to show in a drill-down view of the dataset clicked on.
On the first index of the array returned by chart.getElementAtEvent is a value _datasetIndex. This value shows the index of the dataset that was clicked on.
The specific bar that was clicked on, I believe, is noted by the value _index. In my example in my question, _index would point to One in chart_config.data.labels.
My handleClick function now looks like this:
function handleClick(evt)
{
var activeElement = chart.getElementAtEvent(evt);
..where chart is the reference of the chart created by chart.js when doing:
chart = new Chart(canv, chart_config);
The specific set of data that was selected by the click can therefore be found as:
chart_config.data.datasets[activeElement[0]._datasetIndex].data[activeElement[0]._index];
And there you have it. I now have a datapoint that I can build a query from to display the data of the bar that was clicked on.
AUGUST 7TH, 2021. UPDATE
There is now a method for what we are looking for. Take a look at here
Hi this is the click event under options which is getting values from x and y-axis
onClick: function(c,i) {
e = i[0];
console.log(e._index)
var x_value = this.data.labels[e._index];
var y_value = this.data.datasets[0].data[e._index];
console.log(x_value);
console.log(y_value);
}
I found this solution at https://github.com/valor-software/ng2-charts/issues/489
public chartClicked(e: any): void {
if (e.active.length > 0) {
const chart = e.active[0]._chart;
const activePoints = chart.getElementAtEvent(e.event);
if ( activePoints.length > 0) {
// get the internal index of slice in pie chart
const clickedElementIndex = activePoints[0]._index;
const label = chart.data.labels[clickedElementIndex];
// get value by index
const value = chart.data.datasets[0].data[clickedElementIndex];
console.log(clickedElementIndex, label, value)
}
}
}
You can use onClick like this.
var worstCells3GBoxChart = new Chart(ctx, {
type: 'bar',
data: {
labels: lbls,
datasets: [{
label: 'Worst Cells by 3G',
data: datas,
backgroundColor: getColorsUptoArray('bg', datas.length),
borderColor: getColorsUptoArray('br', datas.length),
borderWidth: 1
}]
},
options: {
legend: {
display: false
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
},
onClick: function (e) {
debugger;
var activePointLabel = this.getElementsAtEvent(e)[0]._model.label;
alert(activePointLabel);
}
}
});
Chartjs V3.4.1
This is what worked for me in v3, after looking at solutions for older versions:
const onClick = (event, clickedElements) => {
if (clickedElements.length === 0) return
const { dataIndex, raw } = clickedElements[0].element.$context
const barLabel = event.chart.data.labels[dataIndex]
...
}
raw is the value of the clicked bar.
barLabel is the label of the clicked bar.
You need to pass the onClick to the bar chart config:
const barConfig = {
...
options: {
responsive: true,
onClick,
...
}
}
Well done! This seems to return the data value being charted though, which in many cases might be possible to appear more than once, thus making it unclear what was clicked on.
This will return the actual data label of the bar being clicked on. I found this more useful when drilling down into a category.
chart_config.data.labels[activeElement[0]._index]
I was able to make this work in another way.
Might not be supported, but sometimes, I find that neither the label nor the value is adequate to get me the necessary information to populate a drill-through.
So what I did was add a custom set of attributes to the data:
var ctx = document.getElementById("cnvMyChart").getContext("2d");
if(theChart != null) {theChart.destroy();}
theChart = new Chart(ctx, {
type: typ,
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datakeys: ["thefirstone","thesecondone","thethirdone","thefourthone","thefifthone","thesixthone"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
...etc
Then when I need to push the drillthrough key into another ajax call, I was able to get it with this:
var theDrillThroughKey = theChart.config.data.datakeys[activePoints[0]._index];
So I'm really not sure that it's appropriate to be adding custom elements into the data for the Chart, but it's working so far in Chrome, IE and Firefox. I needed to be able to put more information into the drillthrough than I really wanted displayed.
Example of the full thing: https://wa.rrdsb.com/chartExamples
Thoughts?
I had the same problem with multiple datasets, and used this workaround:
var clickOnChart = function(dataIndex){
...
}
var lastHoveredIndex = null;
var chart_options = {
...
tooltips: {
...
callbacks: {
label: function(tooltipItem, chart) {
var index = tooltipItem.datasetIndex;
var value = chart.datasets[index].data[0];
var label = chart.datasets[index].label;
lastHoveredIndex = index;
return value + "€";
}
}
},
onClick:function(e, items){
if ( items.length == 0 ) return; //Clicked outside any bar.
clickOnChart(lastHoveredIndex);
}
}
Let's say that you declared a chart using a method like so:
window.myBar = new Chart({chart_name}, {
type: xxx,
data: xxx,
events: ["click"],
options: {
...
}
});
A good way of declaring onclick events would involve listening for the canvas click, like so:
({chart_name}.canvas).onclick = function(evt) {
var activePoints = myBar.getElementsAtEvent(evt);
// let's say you wanted to perform different actions based on label selected
if (activePoints[0]._model.label == "label you are looking for") { ... }
}
In the chart options for Chart.js v3.5.1 which is latest
Check below sample code
let enterpriseChartOptions = {
responsive:true,
maintainAspectRatio: false,
onClick: (c,i) => {
console.log('Get the underlying label for click,', c.chart.config._config.data.labels[i[0].index]);
},
plugins: {
title:{
text:'Enterprise Dashboard (Health Status of 10 stores) updated every 30 minutes',
fontSize:20
},
},
scales: {
x: {
display: true,
type: 'category',
position: 'right',
ticks: {
padding: 8,
},
},
y: {
display: true,
ticks: {
callback: function(val, index) {
// Show the label
return val < 1 ? "All good" : (val < 2 && val >=1) ? "Warning": val === 2 ? "Critical" : "";
},
//color: 'red',
stepSize: 1,
padding: 8
}
}
},
layout: {
padding: {
left: 20,
right: 20,
top: 25,
bottom: 0
}
},
};
var employeeDetailsCtx = document.getElementById("employee-details").getContext("2d");
var employee_details_data = {
labels: ["Late Present", "On Leave", "Training", "Tour"],
datasets: [{
label: "Officer",
backgroundColor: "#5A8DEE",
data: [
...
]
}, {
label: "Staff",
backgroundColor: "#4BC0C0",
data: [
...
]
}]
};
var myoption = {
tooltips: {
enabled: true
},
hover: {
animationDuration: 1
},
onClick: function (evt, i) {
var activePoint = employeeDetailsBarChart.getElementAtEvent(evt)[0];
var data = activePoint._chart.data;
var datasetIndex = activePoint._datasetIndex;
var label = data.datasets[datasetIndex].label;
var value = data.datasets[datasetIndex].data[activePoint._index];
e = i[0];
var x_value = this.data.labels[e._index];
console.log(x_value)
console.log(label)
console.log(value)
},
animation: {
duration: 1,
onComplete: function () {
var chartInstance = this.chart,
ctx = chartInstance.ctx;
ctx.textAlign = 'center';
ctx.fillStyle = "rgba(0, 0, 0, 1)";
ctx.textBaseline = 'bottom';
this.data.datasets.forEach(function (dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
meta.data.forEach(function (bar, index) {
var data = dataset.data[index];
ctx.fillText(data, bar._model.x, bar._model.y - 5);
});
});
}
}
};
var employeeDetailsBarChart = new Chart(employeeDetailsCtx, {
type: 'bar',
data: employee_details_data,
options: myoption
});