ChartJS – How to show border on empty pie chart? - javascript

I have a couple of pie/doughnut charts displayed using ChartJS. Usually, they contain data like [200, 1200, 300] and [30, 500] to give an estimate, but at rare occasions, they will contain [0, 0, 0] and [0, 0]. The problem then, is that the chart disappears, even though I have a border enabled. I have solved this by adding dummy values to the first element in the arrays, but I don't like how the code looks and want a better way.
Is it possible to make the border visible (a circle) when the array contains only zeroes?
Edit:
I don't seem to get any answers to this question. Is it considered a bug when the border is not showing on empty data or is this intended behavior? I don't know if anyone else has had this problem. I still need to find an elegant way to deal with this issue, and I haven't found one yet.

This is not a bug, as the borders are rendered per data item. If all the data is 0s, then every slice has no width, so no border can show (the only other way to handle this scenario for ChartJS would be to give each item equal sizing, which isn't better).
You can add a dummy value without a label and filter the legend and tooltips so that the data is treated like a blank space to the user. In the example below, a check before rendering the chart ensures that if all data points are 0, a data point with a value of 1 is added to the chart with no label. Two datasets are shown to highlight the difference.
let ctx = document.getElementById('chartContainer').getContext('2d');
let data = [[0, 0, 0], [1,2,3]];
let labels = ["A", "B", "C"];
let bgColors = ['yellow', 'orange', 'aquamarine'];
let options = {
borderWidth: 1,
borderColor: 'black',
legend: {
labels: {
// Prevent items with undefined labels from appearing in the legend
filter: (item) => item.text !== undefined
}
},
tooltips: {
// Prevent items with undefined labels from showing tooltips
filter: (item, chart) => chart.labels[item.index] !== undefined
}
}
let chartConfig = {
type: 'pie',
data: {
labels: labels,
datasets: [{
data: data[0],
backgroundColor: bgColors,
label: "data",
borderColor: 'black',
borderWidth: 2
}, {
data: data[1],
backgroundColor: bgColors,
label: "data",
borderColor: 'black',
borderWidth: 2
}]
},
options: options
}
// Check if data is all 0s; if it is, add dummy data to end with empty label
chartConfig.data.datasets.forEach(dataset => {
if (dataset.data.every(el => el === 0)) {
dataset.backgroundColor.push('rgba(255,255,255,0)');
dataset.data.push(1);
}
})
let pieChart = new Chart(ctx, chartConfig);
.chartContainer {
height: 200px;
width: 200px;
}
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.8.0/dist/Chart.min.js"></script>
<div class="chartContainer">
<canvas id="chartContainer" width="200" height="200"></canvas>
</div>

Related

Is it possible to remove the cutout border on a doughnut graph using ChartJS 3.9?

Note: I'm quite new to javascript and ChartJS, so I have done my best to look at similar questions but I'm unsure how to apply the solutions to my issue. I'm really quite confused by the plugins, and it seems whenever I try to literally copy and paste in solutions from other posted questions, it either breaks the graph or just doesn't do anything at all. So, with that in mind, I appreciate any help!
I'm trying to format the outer of two overlaid doughnut graphs using ChartJS so that the outer graph looks like a rounded bracket. The intention is to be able to group certain slices of the inner doughnut together, EG: Inner graph shows number of years John has lived in 5 different places, the outer graph is grouping the 2nd, 3rd, and 4th slice to indicate that he lived in these locations while he was a wildland forest firefighter for the US Forest Service.
See below. Please note, the tick marks on the edges are important. It should look like a rounded bracket -> ] <-
Things I have tried:
Doing borderWidth: { top: 1, right: 1, bottom: 0, left: 1 } breaks the graph, as I'm not sure there's a "bottom" persay to a doughnut graph
Trying to change borderColor breaks the graph in the same way
I'm not sure how to select just the cutout to do any custom styling, as I'm not well versed on HTML canvases and how they work.
Although I am currently attempting to remove one of the borders, I have also considered solutions like:
Adding an additional border to the inner doughnut chart and forcing it to clip over the innermost edge of the outer doughnut
adding a round, white background to the inner doughnut that clips over the innermost edge of the outer doughnut
Changing just the color of the innermost edge of the outer doughnut to transparent or white
Hiding the border entirely, letting the background color of the outer doughnut act like the spine of the rounded bracket I'm trying to create, then adding two new data segments to the outer doughnut at the beginning and end each with a different cutout percentage than the spine. (I hope that makes sense. Essentially the data would look like {1, 98, 1} with the cutout specified on each slice so it would look like cutout: {85, 90, 85}, in theory. This version is untested.)
Pardon the comments in the js, those are just there for me to remember what's going on.
var chart1 = document.getElementById('chart1').getContext('2d');
let doughnut1 = new Chart(chart1, {
type: 'doughnut',
data: {
datasets: [{ // OUTER ring
data: [100], //leave at 100
backgroundColor: ['#fff'],
circumference: 300, //determines circumference of outer border X out of 360
weight: 0.15,
radius: '100%',
borderColor: 'black',
borderWidth: 4
}, {
data: [14, 14, 22, 37, 13],
backgroundColor: ['#f5ce42', '#ccc3a3', '#fc95f2', '#cdb2ed', '#423225'],
radius: '95%',
borderColor: 'black',
borderAlign: 'inside'
}]
}
});
.wrap__chart {width: 50vw; margin: 0 auto;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
<div class="wrap__chart">
<canvas id="chart1"></canvas>
</div>
Parameters for this graph are:
The graph has to be dynamic, cannot include any static images or the like as the data will be filled from a calculator
The graph must be responsive (Ignoring the fact that it largely isn't right now lol)
Can't use JQuery, unfortunately...
You can leave a single line instead of a rectangle by modifying the width property weight: 0.15 to weight: 0.001 and the border property borderWidth: 4 to borderWidth: 1. There is no other property you can change to make it more like what you want.
The new code:
var chart1 = document.getElementById('chart1').getContext('2d');
let doughnut1 = new Chart(chart1, {
type: 'doughnut',
data: {
datasets: [{ // OUTER ring
data: [100], //leave at 100
backgroundColor: ['#fff'],
circumference: 300, //determines circumference of outer border X out of 360
weight: 0.001,
radius: '100%',
borderColor: 'black',
borderWidth: 1
}, {
data: [14, 14, 22, 37, 13],
backgroundColor: ['#f5ce42', '#ccc3a3', '#fc95f2', '#cdb2ed', '#423225'],
radius: '95%',
borderColor: 'black',
borderAlign: 'inside'
}]
}
});
Ok I finally found a solution for my own question. It's quite roundabout, so I don't know if it's an actual answer for anyone else.
It involves using three graphs, unfortunately:
var sliceA = 60000,
sliceB = 24000,
sliceC = 36000;
var protected = (((sliceA + sliceB)/(sliceA + sliceC + sliceB)) * 360);
//colors
var outerRing = 'green';
var ctx = document.getElementById("chart");
var chart = new Chart(ctx, {
type: 'doughnut',
data: {
datasets: [{
data: [sliceA, sliceB, sliceC],
backgroundColor: ['limegreen', 'skyblue', 'firebrick'],
radius: '82%',
cutout: '50%',
}]
}
});
var chart2 = document.getElementById('chart2').getContext('2d');
let doughnut2 = new Chart(chart2, {
type: 'doughnut',
data: {
datasets: [{ // OUTER ring
data: [1, 98, 1], //leave at 100
backgroundColor: [outerRing, 'transparent', outerRing],
circumference: (protected + 5), //determines circumference of outer border X out of 360
weight: 0.5,
radius: '100%',
borderWidth: 0,
borderAlign: 'inside',
borderColor: 'transparent',
rotation: -2
},
{ data: //14, 14, 22, 37, 13
[100],
backgroundColor: [outerRing],
radius: '97%',
cutout: '92%',
circumference: (protected + 2),
borderWidth: '5px',
borderColor: outerRing,
rotation: -1
}
]
},
options: {
parsing: {
key: 'nested.value'
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
<div class="container">
<canvas id="chart" style="position:absolute;"></canvas>
<canvas id="chart2" style="position:absolute;"></canvas>
</div>
it's not perfect, i'm still working out how to make the back rotation and addition to the variable "protected" in the outer rings responsive, with the intention of making it so that the inside of each tick mark aligns perfectly with 0 degrees and var protected degrees on the circle, to properly indicate that the outer ring encompasses all slices of the doughnut chart within the specified range.
you can change the outer ring style from a capital i to a [ style by changing the radius of each of the two layers of the outer ring. Switching the second dataset to be 100% and the first to be ~95% will change it accordingly.

Echarts multicolored line chart

Is it possible to make echarts line chart with 1 line painted in different colors? For example if value <= 0 the color is red, if > 0 the color is green?
Echarts has an option called visualMap that does exactly what you are looking for.
visualMap doc
visualMap code example (I selected the example that fits best, but there are others)
In your case you'll have something like that :
visualMap: {
show: false, // Wether to show the legend or not (default: true)
pieces: [
{
min: -9999, // Normally not needed but doesn't work without that (1)
max: 0,
color: '#F35E07' // Red
},
{
min: 0,
color: '#93CE07' // Green
},
],
outOfRange: {
color: '#F35E07'
}
},
It'll split your line in 2 pieces :
below 0 (written max: 0) : red
above 0 (written min: 0) : green
In addition, the visualMap option has more to offer : you can have more than 2 pieces (like in this example), have a smooth gradient instead of pieces (using type: 'continuous' like in this example), and many other things that are explained in its doc.
(1) Note about the bug: Normally if you don't specify min or max,
it's set to -Infinity or Infinity. But here you have to specify
min AND max in one of the two pieces (I don't know why).
If you consider plotting two charts and ignoring some of its points with '-', you can achieve the same visual result of a multicolored line chart.
Although the documentation does not provide an immediate example of how to color specific segments of a line chart, it actually instructs about how you can use empty data,
While there are empty elements, the lines chart will ignore that point without passing through it----empty elements will not be connected by the points next. In ECharts, we use '-' to represent null data, It is applicable for data in other series.
With that in mind, by overlaying two equal charts and emptying some points, you can actually construct the logic of a multicolored chart. Notice that it is better if you can do it programmatically so that you can vary the number of line segments to be colored.
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
option = {
xAxis: {},
yAxis: {},
series: [
{
data: [
[-20, -20],
[-10, 30],
[0, 40],
['-', '-'],
['-', '-']
],
type: 'line',
lineStyle: {
color: "red"
}
},
{
data: [
['-', '-'],
['-', '-'],
[0, 40],
[10, 100],
[20, 60]
],
type: 'line',
lineStyle: {
color: "green"
}
}
]
};
option && myChart.setOption(option);
.as-console-wrapper { max-heght: 100% !important }
<script src="https://cdn.jsdelivr.net/npm/echarts#5.3.2/dist/echarts.js"></script>
<body>
<div id="main" style="width: 225px;height:225px;"></div>
</body>

Load Tooltip faster during onHover of Legend Doughnut chart

I currently have an implementation exactly like the answer shown in this answer which I am going to include here for clarity. If you run the code and hover between items in the legend, you will notice that if you hover over items quickly, the tooltip on the chart will not display.
Compare this to hovering over items in the Doughnut chart. The functionality is much faster.
var options = {
type: 'doughnut',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"]
}]
},
options: {
plugins: {
legend: {
position: 'left',
onHover: (evt, item, legend) => {
const chart = legend.chart;
const tooltip = chart.tooltip;
const chartArea = chart.chartArea;
tooltip.setActiveElements([{
datasetIndex: 0,
index: item.index,
}], {
x: (chartArea.left + chartArea.right) / 2,
y: (chartArea.top + chartArea.bottom) / 2,
});
chart.update();
},
},
}
}
}
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>
For those who have ended reached this same problem I would like to share my findings.
What ends up happening is the tooltip requires too much time for it to "disappear". There are different 'transitions', 'animation', or 'animations' that you can edit from the documentation, that will all do different things. I could not find out how to speed the "disappearing" up.
Instead, implement the example External tooltip that is given in the documentation for Chart.js. This tooltip, by default, does not have the same problems that the default tooltip has with appearing/disappearing.
Edit:
I would like to provide some things that I learned that may be useful for implementing the external tooltip.
The example external tooltip is coded so that it uses the logic of the original tooltip. This can be good, however if you want the functionality of hovering over the legend and showing the tooltip, I would advise adding a 'flag' to that is true if you are hovering over a label, and false otherwise. This should be added as a check to show the tooltip.
When you are setting active elements in the onHover of the legend and you have set 'external' to the externalTooltipHandler, you the externalTooltipHandler will automatically know to make items out of the active elements array. You should NOT call or pass the externalTooltipHandler, you should just set 'external: externalTooltipHandler'
Let me know if I can clarify anything here, I have spent more time than I would like on this tooltip.

Mixed chart scatter plot with chart.js

I'm trying to create a scatter-plot chart in chart.js mixing a line chart and a bubble chart to graph the dispersion of some data vs the "ideal" of a predictive math model.
I'm using the scales to scatter the xAxes and make a straight line (which represents the "ideal"). The problem is in the bubble chart, the data of bubble can't be exactly over the line, is technically impossible (in theory), but they are exactly over the "ideal" line.
Here is my example code for the chart:
var chart = new Chart(ctx, {
type: 'bubble',
data: {
labels: makeLabels().labels,
datasets: [
{
type: 'line',
label: 'Data',
data: makeLabels().labels,
fill: false,
backgroundColor: "rgba(218,83,79, .7)",
borderColor: "rgba(218,83,79, .7)",
pointRadius: 0
},
{
type: 'bubble',
label: 'Data2',
data: makeBubbles(),
backgroundColor: "rgba(76,78,80, .7)",
borderColor: "transparent"
}
]
},
options: {
scales: {
xAxes: [{
type: 'linear',
position: 'bottom',
ticks: {
min: 0,
max: Math.max(...makeLabels().array)
}
}]
}
}
});
And here is the complete code with the dummie data and the playground in codepen.
Anyway, this is the actual result:
And this is the expected result:
The position of the line is irrelevant (the charts in the captures aren't plotted with the same data), but what i want is to cause the bubbles to disperse.
¿Any idea how to achieve it?
The reason your points are coming out linearly is because you are setting each data point's x and y value to be the same in makeBubbles(). This results in points that are positioned linearly.
To get the points to scatter just use point that don't fall into a linear pattern. To demonstrate what I mean, I have modified your makeBubbles() function so that your x and y values aren't equal. Now you get a very broad scatter.
arr = arr.map(function(item, i) {
return {x: item, y:arr[i - 1]}
});
Here is an example.

Pie-like chart with a percentage of the slices being filled

I am trying to make chart, sort of like a pie chart, but with each slice being the same size, and having a percentage of it filled.
Something like this.
My question is similar to this one: Pie chart with different fill percentage.
But it is 4 years old so I thought I would ask again.
I have tried using Canvasjs, Google charts, and highcharts but none of them support anything similar. I also want to have the slices functioning as buttons that can rotate the chart, having the selected one being positioned in the bottom.
You can achieve that result in Highcharts. Each slice should be a different series with different size. Each series should have points in the number of the series, all points should be invisible except the one - also, it is needed for disabling ignoreHiddenPoint so empty space will be drawn.
For example, you have an array of points ['20%', '30%'] - you need to map the points to series array:
[{
size: '20%',
keys: ['y', 'visible'],
data: [[1/2, true], [1/2, false]]
}, {
size: '30%',
keys: ['y', 'visible'],
data: [[1/2, false], [1/2, true]]
}]
You also might create an additional series which will be the background of the pie
const backgroundSeries = [{
size: '100%',
data: [{y: 1, color: 'rgba(0, 0, 0, 0.4)'}],
enableMouseTracking: false,
borderWidth: 0,
}];
For rotating the pie, you need to update startAngle property
chart.update({
plotOptions: {
pie: {
startAngle: startAngle
}
}
});
Live example and output
http://jsfiddle.net/1yjc4ogb/

Categories