Click on chartjs pie slice to hide the slice - javascript

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>

Related

Chart.js hover over label

This bounty has ended. Answers to this question are eligible for a +50 reputation bounty. Bounty grace period ends in 3 hours.
Software Dev wants to draw more attention to this question.
I have a bar chart in Chart.js (using the latest version), and I want to make some visual change when the mouse is hovering over a category label. How would I implement either or both of the following visual changes?
Make the cursor be a pointer while hovering over a label.
Make the label be in a different color while it is being hovered on.
A related question is here: How to detect click on chart js 3.7.1 axis label?. However, my question is about hovering over a label, without clicking on the label.
In the example below, I want something to happen when hovering on these texts: Item A, Item B, Item C.
window.onload = function() {
var ctx = document.getElementById('myChart').getContext('2d');
window.myBar = new Chart(ctx, {
type: 'bar',
data: {
labels: ['Item A', 'Item B', 'Item C'],
datasets: [{
data: [1, 2, 3],
backgroundColor: 'lightblue'
}]
},
options: {
responsive: true,
indexAxis: 'y',
plugins: {
legend: {
display: false
},
tooltip: {
enabled: false
},
}
}
});
};
.chart-container {
position: relative;
height: 90vh;
}
<script src="https://cdn.jsdelivr.net/npm/chart.js#4.2.0"></script>
<div class="chart-container">
<canvas id="myChart"></canvas>
</div>
You can just use the custom plugin from that question and ignore everything but mousemove events instead of ignoring everything but click events:
const findLabel = (labels, evt) => {
let found = false;
let res = null;
labels.forEach(l => {
l.labels.forEach((label, index) => {
if (evt.x > label.x && evt.x < label.x2 && evt.y > label.y && evt.y < label.y2) {
res = {
label: label.label,
index
};
found = true;
}
});
});
return [found, res];
};
const getLabelHitboxes = (scales) => (Object.values(scales).map((s) => ({
scaleId: s.id,
labels: s._labelItems.map((e, i) => ({
x: e.translation[0] - s._labelSizes.widths[i],
x2: e.translation[0] + s._labelSizes.widths[i] / 2,
y: e.translation[1] - s._labelSizes.heights[i] / 2,
y2: e.translation[1] + s._labelSizes.heights[i] / 2,
label: e.label,
index: i
}))
})));
const plugin = {
id: 'customHover',
afterEvent: (chart, event, opts) => {
const evt = event.event;
if (evt.type !== 'mousemove') {
return;
}
const [found, labelInfo] = findLabel(getLabelHitboxes(chart.scales), evt);
if (found) {
console.log(labelInfo);
}
}
}
Chart.register(plugin);
const options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderColor: 'pink'
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
borderColor: 'orange'
}
]
},
options: {}
}
const ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.js"></script>
</body>
To change the cursor to a pointer when hovering over a category label in a Chart.js bar chart, you can add:
options: {
plugins: {
tooltip: {
mode: 'index',
intersect: false
},
},
interaction: {
mode: 'index',
intersect: false
},
onHover: function(evt, elements) {
if (elements.length) {
document.getElementById("myChart").style.cursor = "pointer";
} else {
document.getElementById("myChart").style.cursor = "default";
}
},
// ...
}
To change the color of a label when it is being hovered on, you can add:
options: {
plugins: {
tooltip: {
mode: 'index',
intersect: false
},
},
interaction: {
mode: 'index',
intersect: false
},
onHover: function(evt, elements) {
if (elements.length) {
var chart = evt.chart;
var datasetIndex = elements[0].datasetIndex;
var index = elements[0].index;
chart.data.labels[index] = '<span style="color: red;">' + chart.data.labels[index] + '</span>';
chart.update();
} else {
var chart = evt.chart;
chart.data.labels = ['Item A', 'Item B', 'Item C'];
chart.update();
}
},
// ...
}
To make the cursor a pointer while hovering over a label, you can try to assign a CSS cursor value to event.native.target.style.cursor when hover is triggered.
event.native.target.style.cursor = 'pointer';
To make the label a different color while it is being hovered on, you can try this
myChart.config.options.scales.y.ticks.color = hoverColors; // ['black','red','black'], ['black','black','red'], ['red','black','black']
UPDATE
Thanks to LeeLenalee for giving an almost correct answer. I've edited the code above so it fits what is required in the problem. Don't forget to change source of the library in the HTML from :
https://cdn.jsdelivr.net/npm/chart.js#4.2.0
to :
https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.js
Updated code:
window.onload = function() {
const findLabel = (labels, evt) => {
let found = false;
let res = null;
try {
labels.forEach(l => {
l.labels.forEach((label, index) => {
if (evt.x > label.x && evt.x < label.x2 && evt.y > label.y && evt.y < label.y2) {
res = {
label: label.label,
index
};
found = true;
}
});
});
} catch (e) {}
return [found, res];
};
const getLabelHitboxes = (scales) => {
try {
return Object.values(scales).map((s) => ({
scaleId: s.id,
labels: s._labelItems.map((e, i) => ({
x: e.translation[0] - s._labelSizes.widths[i],
x2: e.translation[0] + s._labelSizes.widths[i] / 2,
y: e.translation[1] - s._labelSizes.heights[i] / 2,
y2: e.translation[1] + s._labelSizes.heights[i] / 2,
label: e.label,
index: i
}))
}));
} catch (e) {}
};
const changeCursorAndLabelColor = (event, chart, index, hoverMode) => {
// your hover color here
// const hoverColor = '#ff0000';
const hoverColor = 'red';
const hoverColors = [];
for (let i = 0; i < myChart.data.datasets[0].data.length; i++) {
if (hoverMode) {
// change cursor
event.native.target.style.cursor = 'pointer';
if (index === i) {
hoverColors.push(hoverColor);
} else {
hoverColors.push(defaultLabelColor);
}
} else {
// change cursor
event.native.target.style.cursor = 'default';
hoverColors.push(defaultLabelColor);
}
}
// change label to your hover color
myChart.config.options.scales.y.ticks.color = hoverColors;
// update chart when hover is triggered
myChart.update();
}
let foundMode = false;
const plugin = {
id: 'customHover',
afterEvent: (chart, event, opts) => {
const evt = event.event;
if (evt.type !== 'mousemove') {
return;
}
const [found, labelInfo] = findLabel(getLabelHitboxes(chart.scales), evt);
if (found && myChart.data.labels.includes(labelInfo.label)) {
changeCursorAndLabelColor(evt, chart, labelInfo.index, true);
foundMode = true;
} else {
if (foundMode) changeCursorAndLabelColor(evt, chart, null, false);
foundMode = false;
}
}
}
Chart.register(plugin);
var ctx = document.getElementById('myChart');
const myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['Item A', 'Item B', 'Item C'],
datasets: [{
label: 'My Data',
data: [1, 2, 3],
backgroundColor: 'lightblue'
}]
},
options: {
responsive: true,
indexAxis: 'y',
plugins: {
legend: {
display: false
},
tooltip: {
enabled: false
},
},
onHover: (event, chart) => {
if (foundMode) changeCursorAndLabelColor(event, chart, null, false);
foundMode = false;
}
}
});
const defaultLabelColor = myChart.config.options.scales.y.ticks.color;
};
.chart-container {
position: relative;
height: 90vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.js"></script>
<div class="chart-container">
<canvas id="myChart"></canvas>
</div>

Display Percentage on HTML Tag by fetching percentage data from Doughnut Chart

I am new in JavaScript and I really want to know that how can we display data in form of percentage by fetching percentage data through doughnut chart using JS
<div class="block-text">
<div class="flex-chart"> <div class="box-file"></div><p class="spacing">abc</p>
<p id = "count">20%</p></div>
<div class="flex-chart"> <div class="box-url"></div><p class="spacing">xyz</p>
<p>30%</p>
</div>
</div>
Here is the Doughnut Chart JS code:
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.9.4"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels#0.7.0"></script> -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-datalabels/2.0.0/chartjs-plugin-datalabels.min.js"></script>
<script>
// setup
const data = {
//labels: ['xyz', 'abc'],
datasets: [{
// label: 'Weekly Sales',
data: [12, 20],
backgroundColor: [
'rgb(254, 214, 10)',
'rgb(255, 90, 48)'
],
borderColor: [
"#ffffff",
],
borderWidth: 1
}]
};
// config
const config = {
type: 'doughnut',
data,
options: {
plugins: {
datalabels: {
formatter: (value, ctx) => {
let datasets = ctx.chart.data.datasets;
if (datasets.indexOf(ctx.dataset) === datasets.length - 1) {
var sum = datasets[0].data.reduce((a, b) => a + b, 0);
var percentage = Math.round((value / sum) * 100) + '%';
return percentage;
} else {
return percentage;
}
},
color: '#fff',
}
}
}
}
// render init block
const myChart = new Chart(
document.getElementById('chart'),
config
);
Now I want to display Percentage to get it from doughnut chart percentage variable and post it on 'abc' tag
abc
20%
I want percentage data in replace of 20% because 20% is static at this time
Check this code may be it will help you.
var data = [{
data: [50, 55, 60, 33],
labels: ["India", "China", "US", "Canada"],
backgroundColor: [
"#4b77a9",
"#5f255f",
"#d21243",
"#B27200"
],
borderColor: "#fff"
}];
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
});
JSFIDDLE

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 2 bars with one customize label on top

I have few values like A and B now I have pair of them I am showing them on chart-js like this
Now for my use case, I am calculating the third value using A and B. E.g a=100 and b=50, and dividing them will give me c=2.0. Now I want to show this c value on top of A and B bar as a common label like this
"chart.js": "^3.3.0",
react-js
const newChartInstance = new Chart(chartContainer.current, {
type: "bar",
options: {
scales: {
yAxes: [
{
ticks: {
beginAtZero: true,
},
},
],
},
...config.options,
onClick: (e) => {
const points = newChartInstance.getElementsAtEventForMode(
e,
"nearest",
{ intersect: true },
true
);
if (points.length) {
const firstPoint = points[0];
var type =
newChartInstance.data.datasets[firstPoint.datasetIndex].label;
var label = newChartInstance.data.labels[firstPoint.index];
var value =
newChartInstance.data.datasets[firstPoint.datasetIndex].data[
firstPoint.index
];
// This is the result that you will use to breakdown the chart
//console.log(label, value, type);
dispatch(setClickedBar({ label, value, type, tile: props.tile }));
}
},
},
data: data,
});
You can use a custom plugin for that:
var options = {
type: 'bar',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: 'red'
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
backgroundColor: 'blue'
}
]
},
options: {
plugins: {
customValue: {
name: 'ROI',
}
}
},
plugins: [{
id: 'customValue',
afterDraw: (chart, args, opts) => {
const {
ctx,
data: {
datasets
},
_metasets
} = chart;
datasets[0].data.forEach((dp, i) => {
let barValue = `${(datasets[1].data[i] + dp) / 2}%`;
const lineHeight = ctx.measureText('M').width;
const textVal = opts.name || 'fill'
ctx.textAlign = 'center';
ctx.fillText(barValue, _metasets[0].data[i].x, (_metasets[0].data[i].y - lineHeight * 1.5), _metasets[0].data[i].width);
ctx.fillText(textVal, _metasets[0].data[i].x, (_metasets[0].data[i].y - lineHeight * 3), _metasets[0].data[i].width);
});
}
}]
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.4.1/chart.js"></script>
</body>

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

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);
}
}

Categories

Resources