I've looked everywhere online and I can't find a way to select a dataset where it won't remove or hide the dataset(because I want you to be able to select the others).
This is ChartJS in React so I'm using the react-chartjs wrapper.
Here is how the example looks:
And I want to be able to click on dataset 1 and then dataset 2 and dataset 3 get greyed out(not deleted), basically showing that dataset 1 was selected.
I've put a sandbox together: https://codesandbox.io/s/chartjs-select-dataset-r1kixh
I'm unable to update the color and I'm assuming with the way I have it that if I was able to update the color I probably wouldn't be able to get back the original color unless I find a way to re-render it? Not sure how I could also deselect all?
TL;DR:
How to select one dataset without removing the other?
How to be able to select a different dataset(hence using its original color)
How to "reset" the colors by deselecting(clicking in the space where there isn't a graph maybe)
The first two questions are this stack overflow question.
Here is the code:
import React, { MouseEvent, useRef } from "react";
import {
Chart as ChartJS,
LinearScale,
CategoryScale,
BarElement,
PointElement,
LineElement,
Legend,
Tooltip
} from "chart.js";
import { Chart, getDatasetAtEvent } from "react-chartjs-2";
import faker from "faker";
ChartJS.register(
LinearScale,
CategoryScale,
BarElement,
PointElement,
LineElement,
Legend,
Tooltip
);
export const options = {
scales: {
y: {
beginAtZero: true
}
}
};
const labels = ["January", "February", "March", "April", "May", "June", "July"];
export const data = {
labels,
datasets: [
{
type: "line" as const,
label: "Dataset 1",
backgroundColor: "rgba(75, 192, 192, 1)",
borderColor: "rgba(255, 99, 132,1)",
borderWidth: 2,
fill: false,
data: labels.map(() => faker.datatype.number({ min: -1000, max: 1000 }))
},
{
type: "bar" as const,
label: "Dataset 2",
backgroundColor: "rgba(75, 192, 192, 1)",
data: labels.map(() => faker.datatype.number({ min: -1000, max: 1000 })),
borderColor: "white",
borderWidth: 2
},
{
type: "bar" as const,
label: "Dataset 3",
backgroundColor: "rgba(53, 162, 235, 1)",
data: labels.map(() => faker.datatype.number({ min: -1000, max: 1000 }))
}
]
};
export function App() {
const chartRef = useRef<ChartJS>(null);
const onClick = (event: MouseEvent<HTMLCanvasElement>) => {
const { current: chart } = chartRef;
const element = getDatasetAtEvent(chart, event);
const datasetIndex = element[0].datasetIndex;
const selectedDataset = data.datasets[datasetIndex].label;
if (!chart) {
return;
}
console.log("SELECTED: ", selectedDataset);
for (let i = 0; i < data.datasets.length; i++) {
if (data.datasets[i].label !== selectedDataset) {
console.log("NOT SELECTED: ", data.datasets[i].label);
data.datasets[i].backgroundColor = "rgba(255,255,255,0.2)";
}
}
chartRef.current.update();
};
return (
<Chart
ref={chartRef}
type="bar"
onClick={onClick}
options={options}
data={data}
/>
);
}
Thanks for any help!
Related
In my project I wish to toggle between two sets of data: total hours worked each day in current week, and total hours worked each month in the current year. I am able to achieve Chart.js label manipulation in my button handlers, but I am unable to modify the state's data property. What am I missing here? Thank you for helping.
Note: The commented code produce this error statement when uncommented:
Uncaught TypeError: nextDatasets.map is not a function
LineChart.js
const [userData, setUserData] = useState({
labels: Object.keys(daysData),
datasets: [
{
label: "Hours worked",
data: Object.values(daysData),
backgroundColor: "red",
borderColor: "black",
borderWidth: 2,
},
],
});
const onShowWeeklyHandler = () => {
setUserData({ labels: Object.keys(daysData) });
// setUserData({ datasets: { data: Object.keys(daysData) } });
};
const onShowMonthlyHandler = () => {
setUserData({ labels: Object.keys(monthsData) });
// setUserData({ datasets: { data: Object.keys(monthsData) } });
};
You may be encountering issues because you are trying to shallow merge state with the useState hook. React does not support this. See this answer for more info.
If that does not solve your issue, have you looked into react-chartjs-2? You might be able to achieve what you want doing something like the following.
In your parent component (the one that contains the chart):
import { Bar } from "react-chartjs-2"; // import whatever chart type you need
...
const [userData, setUserData] = useState({
labels: Object.keys(daysData),
datasets: [
{
label: "Hours worked",
data: Object.values(daysData),
backgroundColor: "red",
borderColor: "black",
borderWidth: 2,
},
],
});
Then your handlers can be:
const onShowWeeklyHandler = () => {
setUserData({
...userData,
datasets: {
...userData.datasets,
labels: Object.keys(daysData),
data: Object.values(daysData),
},
});
};
const onShowMonthlyHandler = () => {
setUserData({
...userData,
datasets: {
...userData.datasets,
labels: Object.keys(monthsData),
data: Object.values(monthsData),
},
});
};
Finally:
return <Bar data={userData.datasets} />; // adapt for whatever chart type you need
I'm trying to create a ref to the chart I'm building with chart.js and react-chartjs-2.
Here's the line where I'm trying to create the ref specifying the type: const chartRef = useRef<Line>(null);
And this is the error I get:
'Line' refers to a value, but is being used as a type here. Did you mean 'typeof Line'?
I found the following documentation but since I'm new to TypeScript I don't know how to interpret it yet. Here's the complete code I'm using:
import { Line } from "react-chartjs-2";
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend
} from "chart.js";
import { useRef } from "react";
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend
);
export const data = {
labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
datasets: [
{
label: "First dataset",
data: [33, 53, 85, 41, 44, 65],
fill: true,
backgroundColor: "rgba(75,192,192,0.2)",
borderColor: "rgba(75,192,192,1)"
},
{
label: "Second dataset",
data: [33, 25, 35, 51, 54, 76],
fill: false,
borderColor: "#742774"
}
]
};
export default function App() {
const chartRef = useRef<Line>(null);
return (
<div className="App">
<h1>This is a chart.</h1>
<Line data={data} ref={chartRef} />
</div>
);
}
Can you point me in the right direction? Thanks!
I'm not terribly familiar with ChartJS, but when I changed your existing code from const chartRef = useRef<Line>(null); to const chartRef = useRef<'line'>(null); (per the docs), the linter gave me a better idea as to the what needed to be fixed (on the ref).
In your case:
export function App() {
const chartRef = useRef<ChartJS<"line", number[], string>>(null);
return (
<div className="App">
<h1>This is a chart.</h1>
<Line data={data} ref={chartRef} />
</div>
);
}
Working CodeSandbox
i have a multilevel donut chart but it is not rendering correctly here is code
the problem is, onmouseover on all green parts it says objects, on all grey parts it says products, solution i would like is, on outer ring it should say products, in middle objects, and inner most should be materials, grey areas should just show number. here is a jsfiddle of the problem
Code:
var op=93;
var ap=99;
var mp=66;
var ctx = new Chart(myChart, {
type: 'doughnut',
data: {
labels: ['Objects', 'Products', 'Materials'],
datasets: [{
label: 'Objects',
data: [op, 100 - op],
backgroundColor: ['#006a4e','#eeeeee'],
hoverOffset: 4
},{
label: 'Products',
data: [ap, 100 - ap],
backgroundColor: ['#2e856e', '#eeeeee'],
hoverOffset: 4
},
{
label: 'Materials',
data: [mp, 100 - mp],
backgroundColor: ['#5ca08e', '#eeeeee'],
hoverOffset: 4
}
]
},
options: {
//cutoutPercentage: 40,
height: 200,
width:200
}
});
You can achieve that fairly simple with Chart.JS 2.7.2. Add labels to each dataset like this:
data: {
labels: ['Existing', 'Non'],
datasets: [
{
labels: ['Objects', 'Non-objects'],
...
}, {
labels: ['Products', 'Non-products'],
...
},
{
labels: ['Materials', 'Non-materials'],
...
}
]
}
And add the following label tooltip callback:
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var dataset = data.datasets[tooltipItem.datasetIndex];
var index = tooltipItem.index;
return dataset.labels[index] + ": " + dataset.data[index];
}
}
}
Demo: https://jsfiddle.net/adelriosantiago/fxd6vops/3/
I am sure it is possible with Chart.JS > 3.0 too but I have no idea how since quite a few things changed in the structure.
I had a same problem which took a lot of time but what worked in the end was importing these elements and initialising them ..
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js';
ChartJS.register(ArcElement, Tooltip, Legend);
these are the elements that helps make the entire chart. you can't skip them
vue3 chart-js showing previous data on hover.
I updated my chart data filed using API data but after updating the chat data, chart is showing previous API data on hover
i also tried using distory()
here are my codes
methods: {
barChart() {
var Chart = require("chart.js");
let options = new Chart(document.getElementById("bar-chart-grouped"), {
type: "bar",
data: {
labels: this.llabels,
datasets: [
{
label: "पुरुष जनसंख्या",
backgroundColor: "rgba(0, 143, 251, 0.8)",
data: this.mdata,
},
{
label: "महिला जनसंख्या",
backgroundColor: "rgba(0, 227, 150, 0.8)",
data: this.Fdata,
},
{
label: "पअन्य जनसंख्या",
backgroundColor: "rgb(254, 176, 25,0.8)",
data: this.Odata,
},
],
},
});
if (!this.chart) {
this.chart = options;
} else {
this.chart.options = options;
this.chart.update();
}
},
}
Instead of this.chart.options = options;, use this.chart.options = options.options;
The variable options is holding your chart, not only the options.
A better approach would be to only create the chart on the first run of that method (=> place the new Chart inside the if)
Is there any way to expand the slice of donut chart onclick event or mouse hover in chartjs ?.
I am using chart.js#2.8.0 version.
I have fixed this issue using the following code:
let segment;
this.chart = new Chart(this.canvas, {
type: this.type,
data: this.data,
options: {
...this.options,
onHover: function (evt, elements) {
if (elements && elements.length) {
segment = elements[0];
this.chart.update();
selectedIndex = segment["_index"];
segment._model.outerRadius += 10;
} else {
if (segment) {
segment._model.outerRadius -= 10;
}
segment = null;
}
},
layout: {
padding: 30
}
}
});
I hope it helps you.
Shift-zooming the currently selected slice is not a feature but it has been discussed several times on various forums and the project's GitHub community:
https://forum.mendixcloud.com/link/questions/85582
https://github.com/chartjs/Chart.js/issues/1451
The Github discussion contains a fiddle someone wrote for a Pie chart with Chart.js 1.0. Here is an updated version for the current Chart.js version that supports Donut charts.
Code:
This part only shows the part that zooms the active element, it is just to give you the idea of how to use the active elements .innerRadius and .outerRadius properties to shift the element. The fiddle contains the complete code that also handles shrinking the previously selected element.
<div style="width:400px;">
<canvas id="myChart" width="200" height="200"></canvas>
</div>
var ctx = document.getElementById('myChart');
var myChart = new Chart(ctx, {
type: 'doughnut',
options: {
layout: {
padding: 30
}
},
data: {
labels: ['Red', 'Blue', 'Yellow'],
datasets: [
{
label: '# of Votes',
data: [4, 5, 3],
backgroundColor: [
'rgba(255, 0, 0, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)'
],
borderWidth: 3
}]
}
});
var addRadiusMargin = 10;
$('#myChart').on('click', function(event) {
var activePoints = myChart.getElementsAtEvent(event);
if (activePoints.length > 0) {
// update the newly selected piece
activePoints[0]['_model'].innerRadius = activePoints[0]['_model'].innerRadius +
addRadiusMargin;
activePoints[0]['_model'].outerRadius = activePoints[0]['_model'].outerRadius +
addRadiusMargin;
}
myChart.render(300, false);
}
Sample image:
Here is a sample of the highlighted slice:
Limitations:
There are two limitations of the sample that I haven't included:
Chart.js does not allow to define a margin between legend and chart content so when you are "zooming"/"moving", the zoomed slice might overlap parts of the legend. You may solve this by extending the legend as shown in Jordan Willis' Codepen which was the result of this SO question.
The selected slice will stay in contact with the remaining slices. If you want it to have a gap, you need to translate x and y of the active slice based on the .startAngle and .endAngleproperties of the active slice.
Another simple trick : hoverBorderWidth
var ctx = document.getElementById("chart").getContext('2d');
var chart = new Chart(ctx, {
type: 'pie',
data: {
labels: ["Microsoft Internet Explorer", "Google Chrome", "Mozilla Firefox", "Opera", "Safari"],
datasets: [{
label: 'Number of votes',
data: [60, 20, 10, 8, 5],
backgroundColor: ['#96ceff', '#424348', '#91ee7c', '#f7a35b', '#8286e9'],
borderColor: '#fff',
borderWidth: 1,
hoverBorderColor: ['#96ceff', '#424348', '#91ee7c', '#f7a35b', '#8286e9'],
hoverBorderWidth: 8
}]
},
options: {
responsive: false,
legend: {
display: false
},
tooltips: {
displayColors: false
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script>
<canvas id="chart" width="350" height="350"></canvas>