How can I show extra data in chart.js tooltip? - javascript

I'm trying to show weight_id retrieved from mysql data in a chart.js tooltip (shown as (weight_ids[index]) in the image). And later, I intend to show a modal instead of a tooltip to let users update or delete that data. I presume I cannot achieve that without linking the linechart's point data with id stored in mysql. How can I incorporate this id data?
I would appreciate any help very much.
enter image description here
My code is as follows:
<canvas id="myChart"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.9.4/dist/Chart.min.js"></script>
{{-- グラフを描画--}}
<script>
//ラベル
const labels = #json($date_labels);
// id
const weight_ids = #json($weight_ids);
//体重ログ
const weight_logs = #json($weight_logs);
const aryMax = function(a, b) {
return Math.max(a, b);
};
const aryMin = function(a, b) {
return Math.min(a, b);
};
let min_label = Math.floor((weight_logs).reduce(aryMin) - 0.5);
let max_label = Math.ceil((weight_logs).reduce(aryMax) + 0.5);
console.log(weight_ids);
console.log(weight_logs);
console.log(min_label, max_label);
//グラフを描画
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data : {
labels: labels, // x軸ラベル
datasets: [
{
label: `Weight (weight_ids[index])`,
data: weight_logs,
tension: 0,
borderColor: "rgba(37,78,255,1)",
backgroundColor: "rgba(0,0,0,0)",
pointRadius: 3
}
]
},
options: {
title: {
display: false,
text: ''
},
legend: {
display: false,
},
scales: {
yAxes: [
{
ticks: {
min: min_label, // ラベル最小値
max: max_label, // ラベル最大値
},
scaleLabel: {
display: true,
fontSize: 16,
labelString: '体重 (kg)'
}
}
],
},
hover: {
mode: 'point'
},
onClick: function clickHandler(evt) {
var firstPoint = myChart.getElementAtEvent(evt)[0];
if (firstPoint) {
var label = myChart.data.labels[firstPoint._index];
var value = myChart.data.datasets[firstPoint._datasetIndex].data[firstPoint._index];
console.log(label);
console.log(value);
if (value) {
$('#weidhtModal').modal('show');
}
}
}
}
});
</script>
Thank you!

I found a way to retrieve weight_id using the following function.
onClick: function clickHandler(evt, activeElements) {
if (activeElements.length) {
var element = this.getElementAtEvent(evt);
var index = element[0]._index;
var _datasetIndex = element[0]._datasetIndex;
var weightId = weight_ids[index];
var weightLog = weight_logs[index];
console.log(index);
console.log(weightId);
console.log(this.data.labels[index]);
console.log(weightLog);
}
}

Related

undefined labels piechart - chartjs

can you help me. i want show my data in percententage value. but my label is undefined. how i show my label to?
this my chart
var options = {
tooltips: {
custom: true
},
plugins: {
datalabels: {
display: true,
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
});
Labels array is not supposed to be in the dataset itself but is supposed to be in the main data object like so:
var data = [{
data: [50, 55, 60, 33],
backgroundColor: [
"#4b77a9",
"#5f255f",
"#d21243",
"#B27200"
],
borderColor: "#fff"
}];
var options = {
plugins: {
datalabels: {
formatter: (value, ctx) => {
let sum = 0;
let dataArr = ctx.chart.data.datasets[0].data;
dataArr.map(data => {
sum += data;
});
let percentage = (value * 100 / sum).toFixed(2) + "%";
return percentage;
},
color: '#fff',
}
}
};
var ctx = document.getElementById("pie-chart").getContext('2d');
var myChart = new Chart(ctx, {
type: 'pie',
data: {
labels: ["India", "China", "US", "Canada"],
datasets: data
},
options: options
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels#0.4.0/dist/chartjs-plugin-datalabels.min.js"></script>
<canvas id="pie-chart"></canvas>

Chart js legend are being cut off if the bar height is equal to port height - chart js

I am using chart js to display barchart as :
$(function(){
let aid;
let $radio_input;
let none_obs;
let display = true;
let $js_dom_array = ["76.44", "120.00"];
let $js_am_label_arr = ["None $0", "Terrace - Large $2000"];
if($js_am_label_arr.length > 20){
display = false;
}
let $js_backgroundColor = ["rgba(26,179,148,0.5)", "rgba(26,179,148,0.5)"];
// let $div = document.getElementById("barChart");
// $div.height="140";
let ctx2 = document.getElementById("barChart").getContext("2d");
let chart = new Chart(ctx2, {
type: 'bar',
data: {
labels: $js_am_label_arr,
datasets: [{
label: 'DOM',
data: $js_dom_array,
backgroundColor: $js_backgroundColor,
barPercentage: 0.4,
maxBarThickness: 50,
// maxBarLength: 5,
}]
},
options: {
legend: {
display: false
},
responsive: true,
maintainAspectRatio: false,
legendCallback: function(chart) {
var text = [];
for (var i=0; i<chart.data.datasets.length; i++) {
text.push(chart.data.labels[i]);
}
return text.join("");
},
tooltips: {
mode: 'index',
callbacks: {
// Use the footer callback to display the sum of the items showing in the tooltip
title: function(tooltipItem, data) {
let title_str = data['labels'][tooltipItem[0]['index']];
// let lastIndex = title_str.lastIndexOf(" ");
// return title_str.substring(0, lastIndex);
return title_str;
},
label: function(tooltipItem, data) {
return 'Avg. DOM: '+data['datasets'][0]['data'][tooltipItem['index']];
},
},
},
scales: {
xAxes: [{
stacked: false,
beginAtZero: true,
// scaleLabel: {
// labelString: 'Month'
// },
ticks: {
display: display,
min: 0,
autoSkip: false,
maxRotation: 60,
callback: function(label, index, labels) {
// let lastIndex = label.lastIndexOf(" ");
// let avg_amount = label.split(" ").pop();
// let am_name = label.substring(0, lastIndex);
// let truncated_am_name = am_name.length > 30 ? (am_name.substring(0, 30)+'...') : am_name;
// return truncated_am_name+' '+avg_amount;
return label;
}
}
}]
},
layout: {
margin: {
top: 5
}
},
},
plugins:[{
afterDatasetsDraw: function(chart,options) {
// var chartInstance = chart,
let ctx = chart.ctx;
ctx.font = Chart.defaults.global.defaultFontStyle;
ctx.fillStyle = Chart.defaults.global.textColor;
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
chart.data.datasets.forEach(function (dataset, i) {
var meta = chart.controller.getDatasetMeta(i);
meta.data.forEach(function (bar, index) {
ctx.fillText(Math.round(dataset.data[index]), bar._model.x, bar._model.y); //bar._model.y - 5
});
})
}
}]
});
document.getElementById('barChart').innerHTML = chart.generateLegend();
})
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.9.3"></script>
<div style="height:400px;">
<canvas id="barChart" xheight="140px"></canvas>
</div>
If you need jsfiddle
Here, the value of second barchart is 120 and it is being cut off. I could pull text little down as: ctx.fillText(Math.round(dataset.data[index]), bar._model.x, bar._model.y + 5); But I don't wat the value to be overlapped with the bar. I have also tried with following options
layout: {
margin: {
top: 5
}
},
and
axisY:{
viewportMaximum: 130
},
But, none of these seems to work. Is there any way to increase the height or viewport of the chart js? It would be helpful if you could provide the js fiddle as well.
Instead of layout.margin.top, you need to define layout.padding.top as follows:
layout: {
padding: {
top: 20
}
},

How to display different datasets from different ajax calls in the same ChartJS?

I'm trying to display different datasets, from different ajax calls, in the same chartJS.
The idea is to show different stats per month, on 13 consecutive months.
1 ajax call = 1 stat for each of the 13 months.
The JSON data for on stat looks like this :
[{"month":"June 2020","value":0},{"month":"May 2020","value":0},{"month":"April 2020","value":0},{"month":"March 2020","value":0},{"month":"February 2020","value":30},{"month":"January 2020","value":182},{"month":"December 2019","value":143},{"month":"November 2019","value":111},{"month":"October 2019","value":103},{"month":"September 2019","value":128},{"month":"August 2019","value":71},{"month":"July 2019","value":129},{"month":"June 2019","value":98}]
Of course, the values determinate the bars, and the months are the chart's labels.
I can display one stat for every month, but I can't add the others (3 more stats to show).
The expected result : 4 bars for each month.
Here is my working code for one dataset :
function getJsonDataForStat1() {
return $.ajax
({
url: 'getJsonDataForStat1',
type: 'get'
});
}
function setChart() {
$.when(getJsonDataForStat1()).done(function (stats) {
stats = JSON.parse(stats);
var data = [], labels = [];
stats.forEach(function (s) {
labels.push(s.month);
data.push(s.value);
});
var ctx = document.getElementById('graph').getContext('2d');
var graph = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [
{
label: 'stat 1',
data: data,
backgroundColor: '#735288',
borderColor: '#3c8dbc',
borderWidth: 1
}],
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
}
});
});
}
How to add the 3 others ?
Any help would be appreciated.
Thanks a lot !
Let's suppose you fetched your data and have multiple statistics/arrays containing the data for each month.
You can compute a lookup that maps the month to the values of the different statistics.
The labels are simply the keys, although you'd probably want to sort them based on the date. The datasets can easily be computed by mapping the labels.
var lkp = [stat1, stat2].flat().reduce((acc, cur) => {
const {
month,
value
} = cur;
acc[month] = acc[month] || [];
acc[month].push(value);
return acc;
}, {});
//{'June 2020': [0, 10], ...}
var labels = Object.keys(lkp).sort((a, b) => (Date.parse(a) % (1000 * 60 * 60 * 24 * 365)) - (Date.parse(b) % (1000 * 60 * 60 * 24 * 365)))
// ['June 2020', 'May 2020']
var colors = ['red', 'green'];
var datasets = [0, 1].map((index) => {
const data = labels.map(label => lkp[label][index]);
return {
data,
label: `Statistic ${index}`,
backgroundColor: colors[index],
}
});
var options = {
type: 'bar',
data: {
labels,
datasets,
},
options: {
scales: {
yAxes: [{
ticks: {
reverse: false
}
}]
}
}
}
var ctx = document.getElementById('chart').getContext('2d');
try {
new Chart(ctx, options);
} catch (e) {
//catch co error
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.js"></script>
<script>
var stat1 = [{"month":"June 2020","value":0},{"month":"May 2020","value":0},{"month":"April 2020","value":0},{"month":"March 2020","value":0},{"month":"February 2020","value":30},{"month":"January 2020","value":182},{"month":"December 2019","value":143},{"month":"November 2019","value":111},{"month":"October 2019","value":103},{"month":"September 2019","value":128},{"month":"August 2019","value":71},{"month":"July 2019","value":129},{"month":"June 2019","value":98}]
var stat2 = [{"month":"June 2020","value":10},{"month":"May 2020","value":25},{"month":"April 2020","value":75},{"month":"March 2020","value":80},{"month":"February 2020","value":30},{"month":"January 2020","value":182},{"month":"December 2019","value":23},{"month":"November 2019","value":123},{"month":"October 2019","value":50},{"month":"September 2019","value":128},{"month":"August 2019","value":71},{"month":"July 2019","value":129},{"month":"June 2019","value":98}];
</script>
<canvas id="chart" width="600" height="400"></canvas>
I have finally succeeded, based on Martin M.'s idea.
Here is my code (maybe it can help someone who has the same issue).
It works perfectly.
var graph = setChart();
updateChart();
function setChart() {
$.when(getData1()).done(function (stats) {
stats = JSON.parse(stats);
var data = [], labels = [];
stats.forEach(function (s) {
labels.push(s.month);
data.push(s.value);
});
var ctx = document.getElementById('graph').getContext('2d');
return graph = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [
{
label: 'data 1',
data: data,
backgroundColor: '#00c0ef',
}],
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
}
});
});
}
function updateChart() {
$.when(setChart()).done(function () {
// I've repeated the following block for data 3 and 4 (still inside updateChart())
$.when(getData2()).done(function (stats) {
stats = JSON.parse(stats);
var data = [];
stats.forEach(function (s) {
data.push(s.value);
});
var dataset = {};
dataset.label = "data 2";
dataset.backgroundColor = '#061834';
dataset.data = data;
graph.data.datasets.push(dataset);
graph.update();
})
})
}
Thank you all for your help !

Click on chartjs pie slice to hide the slice

I'm trying to get chartjs to hide pie slices on click; the same behavior seen when clicking on the legend.
Here is the code I'm using - I commented out the code that actually removes the data from the chart since it doesn't re-render the pie chart correctly.
const values = [50, 55, 60, 33];
const data = {
labels: ["India", "China", "US", "Canada"],
datasets: [{
data: values,
backgroundColor: [
"#4b77a9",
"#5f255f",
"#d21243",
"#B27200"
],
borderColor: "#fff"
}]
};
const options = {
tooltips: {
enabled: false
},
legend: {
enabled: true,
position: 'bottom'
},
animation: false,
onClick: handleClick,
plugins: {
datalabels: {
formatter: (value = 0, ctxx) => {
const sum = values.reduce((acc, val = 0) => (acc + val), 0);
return `${(value * 100 / sum).toFixed(2)}%`;
},
color: '#fff',
}
}
};
const ctx = document.getElementById("pie-chart").getContext('2d');
const myChart = new Chart(ctx, {
type: 'pie',
data,
options: options
});
function handleClick(e, slice) {
setTimeout(() => {
const legend = myChart.legend.legendItems;
if (slice && slice[0]) {
// get clicked on slice index
const sliceIndex = slice[0]._index;
// Removing the data "really" messes up the chart
// myChart.data.labels.splice(sliceIndex, 1);
// myChart.data.datasets[0].data.splice(sliceIndex, 1);
legend[sliceIndex].hidden = true;
chart.update();
}
const visibleSlices = legend.filter(l => !l.hidden);
console.log('visible slices', visibleSlices);
});
}
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels#0.4.0/dist/chartjs-plugin-datalabels.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="pie-chart"></canvas>
It seems like setting the hidden flag then updating the chart should work because that's what the plugin is doing: https://github.com/chartjs/Chart.js/blob/master/src/plugins/plugin.legend.js#L20-L30.
The code below is hiding a slice by the index
myChart.getDatasetMeta(0).data[sliceIndex].hidden = true
const values = [50, 55, 60, 33];
const data = {
labels: ["India", "China", "US", "Canada"],
datasets: [{
data: values,
backgroundColor: [
"#4b77a9",
"#5f255f",
"#d21243",
"#B27200"
],
borderColor: "#fff"
}]
};
const options = {
tooltips: {
enabled: false
},
legend: {
enabled: true,
position: 'bottom'
},
animation: false,
onClick: handleClick,
plugins: {
datalabels: {
formatter: (value = 0, ctxx) => {
const sum = values.reduce((acc, val = 0) => (acc + val), 0);
return `${(value * 100 / sum).toFixed(2)}%`;
},
color: '#fff',
}
}
};
const ctx = document.getElementById("pie-chart").getContext('2d');
const myChart = new Chart(ctx, {
type: 'pie',
data,
options: options
});
function handleClick(e, slice) {
setTimeout(() => {
const legend = myChart.legend.legendItems;
if (slice && slice[0]) {
const sliceIndex = slice[0]._index;
myChart.getDatasetMeta(0).data[sliceIndex].hidden = true
myChart.update();
}
});
}
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels#0.4.0/dist/chartjs-plugin-datalabels.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="pie-chart"></canvas>

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/

Categories

Resources