i'm using react-chartjs-2 for customizing three donut pie charts. This library is amazing with many functionalities but i'm having a problem here. I dont know how to handle zero values. When all my values are zero not pie chart is drawn which is correct. Any ideas of how to handle zero values?? This is my code for drawing a doughnut pie chart:
const renderPortfolioSectorPie = (sectors, intl) => {
if (sectors.length > 0) {
const sectorsName = sectors
.map(sector => sector.name);
const sectorsValue = sectors
.map(sector => sector.subtotal);
const sectorsPercentage = sectors
.map(sector => sector.percentage);
const customeSectorsPercentage = sectorsPercentage.map(h =>
`(${h})`
);
let sectorsCounter = 0;
for (let i = 0; i < sectorsName.length; i += 1) {
if (sectorsName[i] !== sectorsName[i + 1]) {
sectorsCounter += 1;
}
}
const sectorsData = {
datasets: [{
data: sectorsValue,
backgroundColor: [
'#129CFF',
'#0c6db3',
'#4682B4',
'#00FFFF',
'#0099FF',
'#3E3BF5',
'#3366CC',
'#3399FF',
'#6600FF',
'#3366CC',
'#0099CC',
'#336699',
'#3333FF',
'#2178BA',
'#1F7AB8',
'#1C7DB5'
],
hoverBackgroundColor: [
'#129cff',
'#0c6db3',
'#4682B4',
'#00FFFF',
'#0099FF',
'#3E3BF5',
'#3366CC',
'#3399FF',
'#3366CC',
'#0099CC',
'#336699',
'#3333FF',
'#2178BA',
'#1F7AB8',
'#1C7DB5'
],
titles: sectorsName,
labels: sectorsValue,
afterLabels: customeSectorsPercentage,
}]
};
return (
<Doughnut
data={sectorsData}
width={250}
height={250}
redraw
options={{
legend: {
display: false
},
maintainAspectRatio: true,
responsive: true,
cutoutPercentage: 80,
animation: {
animateRotate: false,
animateScale: false
},
elements: {
center: {
textNumber: `${sectorsCounter}`,
text: intl.formatMessage({ id: 'pie.sectors' }),
fontColor: '#4a4a4a',
fontFamily: "'EurobankSans'",
fontStyle: 'normal',
minFontSize: 25,
maxFontSize: 25,
}
},
/*eslint-disable */
tooltips: {
custom: (tooltip) => {
tooltip.titleFontFamily = 'Helvetica';
tooltip.titleFontColor = 'rgb(0,255,255)';
},
/* eslint-enable */
callbacks: {
title: (tooltipItem, data) => {
const titles = data.datasets[tooltipItem[0]
.datasetIndex].titles[tooltipItem[0].index];
return (
titles
);
},
label: (tooltipItem, data) => {
const labels = data.datasets[tooltipItem.datasetIndex]
.labels[tooltipItem.index];
return (
labels
);
},
afterLabel: (tooltipItem, data) => {
const afterLabels = data.datasets[tooltipItem.datasetIndex]
.afterLabels[tooltipItem.index];
return (
afterLabels
);
},
},
},
}}
/>
);
}
I wanted to do the same thing, but I could not find a way to do it. So, that is what I found as a workaround:
After getting the data, you update your options:
this.setState({
doughnutOptions: {
tooltips: {
callbacks: {
label: function () {
let value = response.data.total;
let label = response.data.name;
return value === 0 ? label : label + ': ' + value;
}
}
}
}
});
My Doughnut looks like that:
<Doughnut data={this.state.myData} min-width={200} width={400}
height={400} options={this.state.doughnutOptions}/>
And initially, the doughnutOpitions are just one empty object defined in the state:
doughnutOptions: {}
That is how it will look when no data:
If you have found a better way of showing no data, please share. If not, I hope that workaround would be good enough for you!
Related
I am using the react wrapper for high charts btw.
What I have currently.
What I'm aiming for with but stacked waterfall chart
Just started using high charts and I love it but this one has me stumped. I figured out the data that I need to put that but now I just need to be able to place it in there..
Possible avenues of approach:
Is there a way to add custom HTML only for the top series?
Could I alter the data set to maybe re-render with the top label?
Could I just make the labels show but add an additional label above with the points I wants and make the rest transparent?
import React from "react";
import Highcharts from "highcharts";
import HighchartsReact from "highcharts-react-official";
import HC_more from "highcharts/highcharts-more";
HC_more(Highcharts);
let categories = ["bar 1", "bar 2", "bar 3", "bar 4", "total bar"];
let series = [
{
data: [
20,
10,
-15,
30,
{
isSum: true,
},
],
name: "custom series 1",
},
{
data: [
20,
50,
-25,
10,
{
isSum: true,
},
],
name: "custom series 2",
lineWidth: 0,
},
{
data: [
5,
10,
-5,
10,
{
isSum: true,
},
],
name: "custom series 3",
lineWidth: 0,
},
];
let grandTotal = true;
function _createLabelValuePairs(s, c) {
let collection = {};
//Step 0 - iterate over data to get the series
s.map((dataSet) => {
//since we have total bars. Something that shouldn't be calc into the dataset we still need to figure out how many series it has
//for all the set values
dataSet.data.map((item, idx) => {
if (typeof item == "number") {
if (collection[c[idx]]) {
collection[c[idx]]["values"].push(item);
} else {
collection[c[idx]] = {};
collection[c[idx]]["values"] = [];
collection[c[idx]]["values"].push(item);
}
}
});
});
//Step 1 - Get my totals for each dataset
for (const a in collection) {
collection[a]["barTotal"] = collection[a].values.reduce(
(partialSum, a) => partialSum + a,
0,
);
}
//Step 2 - Get grand total numbers of all datasets.
if (grandTotal) {
//Step 2a - Totals for each bar
let sum = 0;
for (const item in collection) {
sum = sum + collection[item].barTotal;
if (collection[c[c.length - 1]]) {
collection[c[c.length - 1]]["values"].push(collection[item].barTotal);
} else {
collection[c[c.length - 1]] = {};
collection[c[c.length - 1]]["values"] = [];
collection[c[c.length - 1]]["values"].push(collection[item].barTotal);
}
}
//grand total bar will be the last entry in the set you pass in
collection[c[c.length - 1]]["barTotal"] = sum;
//Step 2b - Totals for each series
options.series.map((item) => {
if (collection[c[c.length - 1]]["seriesTotal"]) {
collection[c[c.length - 1]]["seriesTotal"][
item["name"]
] = item.data.reduce((partialSum, a) => {
if (typeof partialSum == "number") {
return Number(partialSum) + a;
}
}, 0);
} else {
collection[c[c.length - 1]]["seriesTotal"] = {};
collection[c[c.length - 1]]["seriesTotal"][item["name"]] = 0;
collection[c[c.length - 1]]["seriesTotal"][
item["name"]
] = item.data.reduce((partialSum, a) => {
if (typeof partialSum == "number") {
return Number(partialSum) + a;
}
}, 0);
}
if (
collection[c[c.length - 1]]["seriesTotal"][item["name"]].indexOf(
"[object Object]",
)
) {
collection[c[c.length - 1]]["seriesTotal"][item["name"]] = Number(
collection[c[c.length - 1]]["seriesTotal"][item["name"]].split(
"[object Object]",
)[0],
);
}
});
}
return collection;
}
function _getDelta(key, sets) {
console.log(sets);
//not passing anything back into the datalabel
return null;
}
const options = {
chart: {
type: "waterfall",
className: "test",
showAxes: false,
},
colors: ["#00A9F4", "#B3B3B3", "#000000"],
legend: {
align: "right",
symbolRadius: 0,
verticalAlign: "top",
},
title: {
text: "Stacked waterfall example",
},
plotOptions: {
series: {
stacking: "normal",
},
waterfall: {
borderColor: "none",
dashStyle: "Solid",
dataLabels: {
useHTML: true,
className: "waterfall-label",
enabled: true,
formatter: function(a) {
return _getDelta(
this.key,
_createLabelValuePairs(series, categories),
);
},
inside: false,
},
states: {
hover: {
animation: {
duration: 0,
},
enabled: false,
},
inactive: {
enabled: false,
},
select: {
enabled: false,
},
},
},
},
xAxis: {
categories: categories,
labels: {
style: {
color: "#000000",
cursor: "default",
fontSize: "14px",
fontFamily: "Mckinsey Sans Regular",
width: "75px",
whiteSpace: "normal", //set to normal
},
},
},
yAxis: {
visible: false,
},
series: series,
credits: {
enabled: false,
},
tooltip: {
animation: false,
backgroundColor: "#333333",
borderColor: "inherit",
borderRadius: 0,
formatter: function() {
return this.x + ": " + this.y;
},
hideDelay: 0,
shadow: false,
style: {
color: "white",
},
},
};
function Waterfall() {
return <HighchartsReact highcharts={Highcharts} options={options} />;
}
export default Waterfall;
And if you want to know what all that code was for _createLabelPairs it's just to get the totals of the series and that of the bars as well( If I need to use those later). I can figure out the percentages with barTotals and total property in the total bar
result from _createLabelPairs function
I will attempt to explain my issue as clearly as possible while also avoid making this topic too long. I recently found the Chart.js library, which is excellent for what I need. Now, since I am using Node.js and need a png of the graph, I am utilizing the chartjs-node-canvas library. Having this information in mind, I will try to split my topic into multiple sections for a clearer understanding.
Ultimate Goal
Before getting into the problem itself, I would like to discuss my ultimate goal. This is to give a general idea on what I'm trying to do so the responses are fitted accordingly. To keep this short, I have data in the form of {awardedDate: "2022-06-22T12:21:17.22Z", badgeId: 1234567}, with awardedDate being a timestamp of when the badge was awarded, and the badgeId being the ID of the badge that was awarded (which is irrelevant to the graph, but it exists because it's part of the data). Now, I have a sample with around 2,787 of these objects, with all having different award dates and IDs, and with dates ranging from 2016 to 2022. My objective is to group these badges by month-year, and that month-year will have the amount of badges earned for that month during that year. With that data, I then want to make a waterfall graph which is based on the amount of badges earned that month of that year. As of right now, there isn't a specific structure on how this will look like, but it could range from an object that looks like {"02-2022": 10, "03-2022": 5} to anything else. I can of course restructure this format based on what is required for a waterfall graph.
Actual Questions
Now that you have a general idea of what my ultimate goal is, my actual question is how I'd be able to make a floating (we can leave the waterfall structure stuff for another topic) bar graph with that data. Since the data can have blank periods (it is possible for a dataset to have gaps that are months long), I cannot really utilize labels (unless I am saying something wrong), so an x-y relation works the best. I tried using the structure of {x: "2022-06-22T12:21:17.226Z", y: [10, 15]}, but that didn't really yield any results. As of right now, I am using a sample code to test how the graph reacts with the data, and of course I'll replace the test values with actual values once I have a finished product. Here is my code so far:
const config = {
type: "bar",
data: {
datasets: [{
label: "Badges",
data: [
{
x: "2022-06-22T12:41:17.226Z",
y: [10, 15]
}
],
borderColor: "rgb(75, 192, 192)",
borderSkipped: false
}]
},
options: {
plugins: {
legend: {
display: false
},
title: {
display: true,
text: "Test",
color: "#FFFFFF"
}
},
scales: {
x: {
type: 'time',
title: {
display: true,
text: 'Time',
color: "#FFFFFF"
},
min: "2022-06-22T12:21:17.226Z",
max: "2022-06-22T14:21:17.226Z",
grid: {
borderColor: "#FFFFFF",
color: "#FFFFFF"
},
ticks: {
color: "#FFFFFF"
}
},
y: {
title: {
display: true,
text: 'Number of Badges',
borderColor: "#FFFFFF",
color: "#FFFFFF"
},
min: 0,
max: 50,
grid: {
borderColor: "#FFFFFF",
color: "#FFFFFF"
},
ticks: {
color: "#FFFFFF"
}
}
}
},
plugins: [
{
id: 'custom_canvas_background_color',
beforeDraw: (chart) => {
const ctx = chart.ctx;
ctx.save();
ctx.fillStyle = '#303030';
ctx.fillRect(0, 0, chart.width, chart.height);
ctx.restore();
}
}
]
};
const imageBuffer = await canvasRenderService.renderToBuffer(config)
fs.writeFileSync("./chart2.png", imageBuffer)
And this is the graph that the code produces:
What is supposed to happen, of course, is that a float bar should be generated near the start that ranges from 5 to 10, but as seen above, nothing happens. If someone could assist me in my problem, that would be amazing. Thank you very much for your time and help, I greatly appreciate it.
Inspired by this answer, I came up with the following solution.
const baseData = [
{ awardedDate: "2022-06-22T12:21:17.22Z" },
{ awardedDate: "2022-06-18T12:21:17.22Z" },
{ awardedDate: "2022-06-15T12:21:17.22Z" },
{ awardedDate: "2022-05-20T12:21:17.22Z" },
{ awardedDate: "2022-05-10T12:21:17.22Z" },
{ awardedDate: "2022-04-16T12:21:17.22Z" },
{ awardedDate: "2022-04-09T12:21:17.22Z" },
{ awardedDate: "2022-04-03T12:21:17.22Z" },
{ awardedDate: "2022-04-01T12:21:17.22Z" },
{ awardedDate: "2022-02-18T12:21:17.22Z" },
{ awardedDate: "2022-02-12T12:21:17.22Z" },
{ awardedDate: "2022-01-17T12:21:17.22Z" }
];
const badgesPerMonth = baseData
.map(o => o.awardedDate)
.sort()
.map(v => moment(v))
.map(m => m.format('MMM YYYY'))
.reduce((acc, month) => {
const badges = acc[month] || 0;
acc[month] = badges + 1;
return acc;
}, {});
const months = Object.keys(badgesPerMonth);
const labels = months.concat('Total');
const data = [];
let total = 0;
for (let i = 0; i < months.length; i++) {
const vStart = total;
total += badgesPerMonth[months[i]];
data.push([vStart, total]);
}
data.push(total);
const backgroundColors = data
.map((o, i) => 'rgba(255, 99, 132, ' + (i + (11 - data.length)) * 0.1 + ')');
new Chart('badges', {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Badges',
data: data,
backgroundColor: backgroundColors,
barPercentage: 1,
categoryPercentage: 0.95
}]
},
options: {
plugins: {
tooltip: {
callbacks: {
label: ctx => {
const v = data[ctx.dataIndex];
return Array.isArray(v) ? v[1] - v[0] : v;
}
}
}
},
scales: {
y: {
ticks: {
beginAtZero: true,
stepSize: 2
}
}
}
}
});
<script src="https://rawgit.com/moment/moment/2.2.1/min/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.8.0/chart.min.js"></script>
<canvas id="badges" height="95"></canvas>
If you also want to see the gaps, you would first have to initialize badgesPerMonth with following months between the earliest and latest date, each with value zero. Please take a look at this answer to get an idea about how this could be done.
After reading #uminder's reply, I was able to create the following code which solved my problem:
dateGroups = Object.fromEntries(
Object.entries(dateGroups).sort(([d1,],[d2,]) => {return (d1 < d2) ? -1 : ((d1 > d2) ? 1 : 0)})
)
const dateTimesConst = Object.keys(dateGroups)
const dateValuesConst = Object.values(dateGroups)
let dateTimes = []
let dateValues = []
let prevLength = 0
let mostBadgesPerMonth = 0
for (let i = 0; i < dateValuesConst.length; i++) {
const currentMonth = new Date(Date.parse(dateTimesConst[i]))
const previousMonth = new Date(Date.UTC(currentMonth.getUTCFullYear(), currentMonth.getUTCMonth() - 1, 1, 0, 0, 0, 0)).toISOString()
const nextMonth = new Date(Date.UTC(currentMonth.getUTCFullYear(), currentMonth.getUTCMonth() + 1, 1, 0, 0, 0, 0)).toISOString()
// if (!dateTimesConst.includes(previousMonth)) prevLength = 0
const length = dateValuesConst[i].length
dateValues.push([prevLength, length])
dateTimes.push(dateTimesConst[i])
prevLength = length
if (length > mostBadgesPerMonth) mostBadgesPerMonth = length
// if (!dateTimesConst.includes(nextMonth) && i !== dateValuesConst.length - 1) {
// dateTimes.push(nextMonth)
// dateValues.push([length, 0])
// prevLength = 0
// }
}
function barColorCode() {
return (ctx) => {
const start = ctx.parsed._custom.start
const end = ctx.parsed._custom.end
return start <= end ? "rgba(50, 168, 82, 1)" : (start > end) ? "rgba(191, 27, 27, 1)" : "black"
}
}
const config = {
type: "bar",
data: {
labels: dateTimes,
datasets: [{
label: "Badges",
data: dateValues,
elements: {
bar: {
backgroundColor: barColorCode()
}
},
barPercentage: 1,
categoryPercentage: 0.95,
borderSkipped: false
}]
},
options: {
plugins: {
legend: {
display: false
},
title: {
display: true,
text: "Test",
color: "#FFFFFF"
}
},
scales: {
x: {
type: 'time',
title: {
display: true,
text: 'Date',
color: "#FFFFFF"
},
time: {
unit: "month",
round: "month"
},
min: dateTimesConst[0],
max: dateTimesConst[dateTimesConst.length - 1],
grid: {
borderColor: "#FFFFFF",
color: "#FFFFFF"
},
ticks: {
color: "#FFFFFF"
}
},
y: {
title: {
display: true,
text: 'Number of Badges',
borderColor: "#FFFFFF",
color: "#FFFFFF"
},
min: 0,
max: mostBadgesPerMonth + 1,
grid: {
borderColor: "#FFFFFF",
color: "#FFFFFF"
},
ticks: {
color: "#FFFFFF"
}
}
}
},
plugins: [
{
id: 'custom_canvas_background_color',
beforeDraw: (chart) => {
const ctx = chart.ctx;
ctx.save();
ctx.fillStyle = '#303030';
ctx.fillRect(0, 0, chart.width, chart.height);
ctx.restore();
}
}
]
};
const imageBuffer = await canvasRenderService.renderToBuffer(config)
fs.writeFileSync("./chart2.png", imageBuffer)
Again, big thanks to #uminder for the inspiration.
I am trying to hide the legend of my chart created with Chart.js.
According to the official documentation (https://www.chartjs.org/docs/latest/configuration/legend.html), to hide the legend, the display property of the options.display object must be set to false.
I have tried to do it in the following way:
const options = {
legend: {
display: false,
}
};
But it doesn't work, my legend is still there. I even tried this other way, but unfortunately, without success.
const options = {
legend: {
display: false,
labels: {
display: false
}
}
}
};
This is my full code.
import React, { useEffect, useState } from 'react';
import { Line } from "react-chartjs-2";
import numeral from 'numeral';
const options = {
legend: {
display: false,
},
elements: {
point: {
radius: 1,
},
},
maintainAspectRatio: false,
tooltips: {
mode: "index",
intersect: false,
callbacks: {
label: function (tooltipItem, data) {
return numeral(tooltipItem.value).format("+0,000");
},
},
},
scales: {
xAxes: [
{
type: "time",
time: {
format: "DD/MM/YY",
tooltipFormat: "ll",
},
},
],
yAxes: [
{
gridLines: {
display: false,
},
ticks: {
callback: function(value, index, values) {
return numeral(value).format("0a");
},
},
},
],
},
};
const buildChartData = (data, casesType = "cases") => {
let chartData = [];
let lastDataPoint;
for(let date in data.cases) {
if (lastDataPoint) {
let newDataPoint = {
x: date,
y: data[casesType][date] - lastDataPoint
}
chartData.push(newDataPoint);
}
lastDataPoint = data[casesType][date];
}
return chartData;
};
function LineGraph({ casesType }) {
const [data, setData] = useState({});
useEffect(() => {
const fetchData = async() => {
await fetch("https://disease.sh/v3/covid-19/historical/all?lastdays=120")
.then ((response) => {
return response.json();
})
.then((data) => {
let chartData = buildChartData(data, casesType);
setData(chartData);
});
};
fetchData();
}, [casesType]);
return (
<div>
{data?.length > 0 && (
<Line
data={{
datasets: [
{
backgroundColor: "rgba(204, 16, 52, 0.5)",
borderColor: "#CC1034",
data: data
},
],
}}
options={options}
/>
)}
</div>
);
}
export default LineGraph;
Could someone help me? Thank you in advance!
PD: Maybe is useful to try to find a solution, but I get 'undefined' in the text of my legend and when I try to change the text like this, the text legend still appearing as 'Undefindex'.
const options = {
legend: {
display: true,
text: 'Hello!'
}
};
As described in the documentation you linked the namespace where the legend is configured is: options.plugins.legend, if you put it there it will work:
var options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderColor: 'pink'
}
]
},
options: {
plugins: {
legend: {
display: false
}
}
}
}
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.5.0/chart.js"></script>
</body>
On another note, a big part of your options object is wrong, its in V2 syntax while you are using v3, please take a look at the migration guide
Reason why you get undefined as text in your legend is, is because you dont supply any label argument in your dataset.
in the newest versions this code works fine
const options = {
plugins: {
legend: {
display: false,
},
},
};
return <Doughnut data={data} options={options} />;
Import your options value inside the charts component like so:
const options = {
legend: {
display: false
}
};
<Line data={data} options={options} />
I am creating multiple graphs on the same canvas but I am unable to successfully use the destroy() API to clean up the previous data.
HERE IS MY JS CODE FOR CREATING A CHART
const getCountryDataByMonth = async (country) => {
document.getElementById('casesGraphHeader').innerHTML = "Loading....";
const response = await fetch ('https://cors-anywhere.herokuapp.com/https://pomber.github.io/covid19/timeseries.json');
const data = await response.json();
const reports = await data[country];
var i;
var dateList = [];
var caseByDay = [];
var deathsByDay = [];
for(i = 0; i < reports.length; i++){
dateList.push(reports[i].date);
caseByDay.push(reports[i].confirmed);
deathsByDay.push(reports[i].deaths);
}
//GRAPH FOR TOTAL CASES
var casesOptions = {
type: 'bar',
data: {
labels: dateList,
datasets: [
{
label: 'Total Cases',
data: caseByDay,
backgroundColor: '#f49d12',
borderColor: '#f49d12',
fill: false,
borderWidth: 2
}
]
},
options: {
legend: {
labels: {
fontSize: 15
}
},
scales: {
yAxes: [{
ticks: {
reverse: false,
fontSize: 15
}
}],
xAxes: [{
ticks: {
fontSize: 15
}
}],
}
}
}
var totalCasesChart = document.getElementById('totalCasesContainer').getContext('2d');
new Chart(totalCasesChart, casesOptions);
document.getElementById('casesGraphHeader').innerHTML = "Total Cases for "+country;
//GRAPH FOR TOTAL Deaths
var deathOptions = {
type: 'bar',
data: {
labels: dateList,
datasets: [
{
label: 'Total Deaths',
data: deathsByDay,
backgroundColor: '#e84c3d',
borderColor: '#e84c3d',
fill: false,
borderWidth: 2
}
]
},
options: {
legend: {
labels: {
fontSize: 15
}
},
scales: {
yAxes: [{
ticks: {
reverse: false,
fontSize: 15
}
}],
xAxes: [{
ticks: {
fontSize: 15
}
}],
}
}
}
var totalCasesChart = document.getElementById('totalDeathsContainer').getContext('2d');
new Chart(totalDeathsContainer, deathOptions);
document.getElementById('deathsGraphHeader').innerHTML = "Total Deaths for "+country;
};
function renderChart(){
getCountryDataByMonth(document.getElementById('myInput').value);
}
function defaultChart() {
getCountryDataByMonth('US');
}
window.onload = defaultChart;
This is what I tried. I basically did
if(caseBar){
caseBar.destroy();
}
However, this does not work. In my FIDDLE you can try to type China first click to create the graph and then type Italy. Then HOVER over the Italy graph and you will see the stats from china appear on the graph.
Your code is riddle with issues, here is some of the stuff I see:
Look at what you are doing when you create the new charts:
var totalCasesChart = document.getElementById('totalCasesContainer').getContext('2d');
var caseBar = new Chart(totalCasesChart, casesOptions);
document.getElementById('casesGraphHeader').innerHTML = "Total Cases for " + country;
vs
var totalCasesChart = document.getElementById('totalDeathsContainer').getContext('2d');
new Chart(totalDeathsContainer, deathOptions);
document.getElementById('deathsGraphHeader').innerHTML = "Total Deaths for " + country;
You are calling the:
await fetch('https://cors-anywhere.herokuapp.com/https://pomber.github.io/...');
again and again when you should do it just once...
There are many variables that should be global to reduce what you do in getCountryDataByMonth, a perfect example are the totalCasesChart and caseBar
I made a few tweaks to your code here:
https://raw.githack.com/heldersepu/hs-scripts/master/HTML/chart_test.html
I have a chart using Chart.js but regardless of what order I put the input data it is outputted out of order, here is a fiddle:
const response = [
{
"mmyy":"12/19",
"promocode":"promo1",
"amount":"2776"
},
{
"mmyy":"01/20",
"promocode":"promo1",
"amount":"1245"
},
{
"mmyy":"01/20",
"promocode":"promo2",
"amount":"179"
}
];
var chartColors = window.chartColors;
var color = Chart.helpers.color;
const colors = [color(chartColors.red).alpha(0.5).rgbString(),
color(chartColors.orange).alpha(0.5).rgbString(),
color(chartColors.yellow).alpha(0.5).rgbString(),
color(chartColors.green).alpha(0.5).rgbString(),
color(chartColors.blue).alpha(0.5).rgbString()];
const labels = Array.from(new Set(response.map(c => c.mmyy))).sort();
const promocodes = Array.from(new Set(response.map(c => c.promocode))).sort();
let i = 0;
const datasets = promocodes.map(pc => ({
label: pc,
data: [],
backgroundColor: colors[i++]
}));
labels.forEach(l => {
for (let pc of promocodes) {
let city = response.find(c => c.mmyy == l && c.promocode == pc);
datasets.find(ds => ds.label == pc).data.push(city ? Number(city.amount) : 0);
}
});
var ctx = document.getElementById('promorChart').getContext('2d');
var chartColors = window.chartColors;
var color = Chart.helpers.color;
var promorChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: datasets
},
options: {
scales: {
xAxes: [{
stacked: false
}],
yAxes: [{
stacked: false,
ticks: {
// Include a dollar sign in the ticks
callback: function(value, index, values) {
return '$' + value;
}
}
}]
},
tooltips: {
callbacks: {
label: function(tooltipItems, data) {
return "$" + tooltipItems.yLabel.toString();
}
}
},
responsive: true,
elements: {
}
}
});
<canvas id="promorChart"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<script src="https://www.chartjs.org/samples/latest/utils.js"></script>
As you can see it shows 01/20 and then 12/19 when it should be in reverse order.
Can someone tell me how to make it show in chronological order (oldest to newest)?
Thank you very much.
You can use moment.js to parse and help sorting the dates.
const labels = Array.from(new Set(response.map(c => c.mmyy))).sort((d1, d2) => moment(d1, 'MM/YY').diff(moment(d2, 'MM/YY')));
This results in the following runnable code snippet.
const response = [
{
"mmyy":"12/19",
"promocode":"promo1",
"amount":"2776"
},
{
"mmyy":"01/20",
"promocode":"promo1",
"amount":"1245"
},
{
"mmyy":"01/20",
"promocode":"promo2",
"amount":"179"
}
];
var chartColors = window.chartColors;
var color = Chart.helpers.color;
const colors = [color(chartColors.red).alpha(0.5).rgbString(),
color(chartColors.orange).alpha(0.5).rgbString(),
color(chartColors.yellow).alpha(0.5).rgbString(),
color(chartColors.green).alpha(0.5).rgbString(),
color(chartColors.blue).alpha(0.5).rgbString()];
const labels = Array.from(new Set(response.map(c => c.mmyy))).sort((d1, d2) => moment(d1, 'MM/YY').diff(moment(d2, 'MM/YY')));
const promocodes = Array.from(new Set(response.map(c => c.promocode))).sort();
let i = 0;
const datasets = promocodes.map(pc => ({
label: pc,
data: [],
backgroundColor: colors[i++]
}));
labels.forEach(l => {
for (let pc of promocodes) {
let city = response.find(c => c.mmyy == l && c.promocode == pc);
datasets.find(ds => ds.label == pc).data.push(city ? Number(city.amount) : 0);
}
});
var ctx = document.getElementById('promorChart').getContext('2d');
var chartColors = window.chartColors;
var color = Chart.helpers.color;
var promorChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: datasets
},
options: {
scales: {
xAxes: [{
stacked: false
}],
yAxes: [{
stacked: false,
ticks: {
// Include a dollar sign in the ticks
callback: function(value, index, values) {
return '$' + value;
}
}
}]
},
tooltips: {
callbacks: {
label: function(tooltipItems, data) {
return "$" + tooltipItems.yLabel.toString();
}
}
},
responsive: true,
elements: {
}
}
});
<canvas id="promorChart"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
<script src="https://www.chartjs.org/samples/latest/utils.js"></script>
You can use reverse for this
...
scales: {
xAxes: [{
stacked: false,
reverse: true // ADD THIS LINE
}],
yAxes: [{
stacked: false,
ticks: {
// Include a dollar sign in the ticks
callback: function(value, index, values) {
return '$' + value;
}
}
}]
},
...