Chart.js show data in chronological order - javascript

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;
}
}
}]
},
...

Related

Chart.js - how to have statis lables and populate with dynamic data?

I am working on chart.js and I have data coming from JSON via ajax. See the example below:
[{"timestamp":"06:00:00.000000","true_count":2},{"timestamp":"07:00:00.000000","true_count":5},{"timestamp":"08:00:00.000000","true_count":7},{"timestamp":"09:00:00.000000","true_count":8},{"timestamp":"10:00:00.000000","true_count":12},{"timestamp":"11:00:00.000000","true_count":15},{"timestamp":"12:00:00.000000","true_count":20},{"timestamp":"13:00:00.000000","true_count":17},{"timestamp":"14:00:00.000000","true_count":14},{"timestamp":"16:00:00.000000","true_count":11},{"timestamp":"17:00:00.000000","true_count":19},{"timestamp":"18:00:00.000000","true_count":22},{"timestamp":"19:00:00.000000","true_count":16},{"timestamp":"20:00:00.000000","true_count":14},{"timestamp":"22:00:00.000000","true_count":7}]
The JS code i am using for my chart is below:
// create initial empty chart
var ctx_live = document.getElementById("chLine");
var myChart = new Chart(ctx_live, {
type: 'bar',
data: {
labels: [],
datasets: [{
data: [],
borderWidth: 1,
borderColor:'#00c0ef',
label: 'liveCount',
}]
},
options: {
responsive: true,
title: {
display: true,
text: "Count Per Hour",
},
legend: {
display: false
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
}
}]
}
}
});
// logic to get new data
var getData = function() {
var _data =[];
var _labels = [];
$.ajax({
url: 'chart_data',
type: "get",
success: function(data) {
full_data = JSON.parse(data);
full_data.forEach(function(key,index) {
_data.push(key.true_count);
_labels.push(key.hour);
});
myChart.data.labels = _labels;
myChart.data.datasets[0].data = _data;
myChart.update();
}
});
};
// get new data every 3 seconds
setInterval(getData, 3000);
Now, this is working fine and shows the true_count over time which is a one-hour basis. Now, the chart is showing only hours with count but what I would like to do is to set the static hours from 12 AM to 11 PM, and for hours for which I don't have data the true_count will be zero, and for those that I have data for, the true count will be assigned to that hour and show on the chart.
Any ideas on how do I do that?
Here is an example:
// create initial empty chart
var ctx_live = document.getElementById("chLine");
var myChart = new Chart(ctx_live, {
type: 'bar',
data: {
labels: [],
datasets: [{
data: [],
borderWidth: 1,
borderColor: '#00c0ef',
label: 'liveCount',
}]
},
options: {
responsive: true,
title: {
display: true,
text: "Count Per Hour",
},
legend: {
display: false
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
}
}]
}
}
});
// Some constants to be changed later:
const HOUR_TO_START = 0;
const HOUR_TO_END = 23;
// helper:
const intToAmPm = (i) =>
i==0 ? '12 AM' :
i==12 ? '12 PM' :
i < 12 ? i + ' AM' :
(i-12) + ' PM';
// logic to get new data
var getData = function() {
var _data = [];
var _labels = [];
$ajax({
url: 'chart_data',
type: "get",
success: function(data) {
full_data = JSON.parse(data);
let preparedData = {};
full_data.forEach(function(key, index) {
let hour = parseInt(String(key.timestamp).substring(0, 2));
preparedData[hour] = key.true_count;
});
for (let i = HOUR_TO_START; i <= HOUR_TO_END; i++) {
_data.push(preparedData[i] === undefined ? 0 : preparedData[i]);
_labels.push(intToAmPm(i));
}
myChart.data.labels = _labels;
myChart.data.datasets[0].data = _data;
myChart.update();
}
});
};
// get new data every 3 seconds
//setInterval(getData, 3000);
getData();
// THIS IS FOR TESTING. IMITATE BACKEND
function $ajax(param) {
param.success('[{"timestamp":"06:00:00.000000","true_count":2},{"timestamp":"07:00:00.000000","true_count":5},{"timestamp":"08:00:00.000000","true_count":7},{"timestamp":"09:00:00.000000","true_count":8},{"timestamp":"10:00:00.000000","true_count":12},{"timestamp":"11:00:00.000000","true_count":15},{"timestamp":"12:00:00.000000","true_count":20},{"timestamp":"13:00:00.000000","true_count":17},{"timestamp":"14:00:00.000000","true_count":14},{"timestamp":"16:00:00.000000","true_count":11},{"timestamp":"17:00:00.000000","true_count":19},{"timestamp":"18:00:00.000000","true_count":22},{"timestamp":"19:00:00.000000","true_count":16},{"timestamp":"20:00:00.000000","true_count":14},{"timestamp":"22:00:00.000000","true_count":7}]');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chLine"></canvas>

React & Chartjs: Chart re-rendering on mouseover

My state is changing based on incoming props, which then triggers a rebuild of the chart. However, what seems to be happening is that when I mouseover the chart it reveals old data, or data that's disappeared then reappears.
Here's a gif showing the problem: https://imgur.com/a/SQbhi9p
And here's my chart code:
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.pricesData !== prevState.pricesData) {
return { pricesData: nextProps.pricesData };
} else {
return null;
}
}
componentDidMount() {
this.buildChart();
}
componentDidUpdate(prevProps) {
if (!_.isEqual(this.props.pricesData, prevProps.pricesData)) {
this.buildChart();
}
}
buildChart fn:
buildChart = () => {
let datasets = [];
if (this.state.pricesData) {
this.state.pricesData.forEach((set) => {
if (set.titel === "Competiors price") {
let obj = {};
obj.label = set.titel;
obj.backgroundColor = set.color;
obj.data = [0, set.price];
obj.tooltip = set.tooltip;
datasets.push(obj);
} else {
let obj = {};
obj.label = set.titel;
obj.backgroundColor = set.color;
obj.data = [set.price, 0];
obj.tooltip = set.tooltip;
datasets.push(obj);
}
});
}
const myChart = new Chart(this.chartRef.current, {
type: "bar",
data: {
labels: ["Change=", "Competitor"],
datasets: datasets,
},
options: {
legend: {
display: false,
},
title: {
display: true,
fontSize: 16,
text: "Your estimated monthly costs",
},
responsive: true,
maintainAspectRatio: false,
scales: {
xAxes: [
{
stacked: true,
},
],
yAxes: [
{
stacked: true,
ticks: {
callback: function (value) {
return "€" + value;
},
},
},
],
},
},
});
this.setState({ chart: myChart });
};

attempting to destroy previous graph on canvas

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

Handle zero values in react-chartjs-2

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!

Put the value into the doughnut chart

I need to input the value of the first percentage into the chart, like this image
In this case, put the value of pedro(33%) within the chart.
I am beginner with chartJS and do not know it completely. Is it possible to do that?
var randomScalingFactor = function() {
return Math.round(Math.random() * 100);
};
var config = {
type: 'doughnut',
data: {
datasets: [{
data: [
33,
67,
],
backgroundColor: [
"#F7464A",
"#46BFBD",
],
label: 'Expenditures'
}],
labels: [
"Pedro: 33 ",
"Henrique: 67 ",
]
},
options: {
responsive: true,
legend: {
position: 'bottom',
},
title: {
display: true,
text: 'Pedro Henrique Kuzminskas Miyazaki de Souza'
},
animation: {
animateScale: true,
animateRotate: true
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var dataset = data.datasets[tooltipItem.datasetIndex];
var total = dataset.data.reduce(function(previousValue, currentValue, currentIndex, array) {
return previousValue + currentValue;
});
var currentValue = dataset.data[tooltipItem.index];
var precentage = Math.floor(((currentValue / total) * 100) + 0.5);
return precentage + "%";
}
}
}
}
};
var ctx = document.getElementById("myChart").getContext("2d");
window.myDoughnut = new Chart(ctx, config); {
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.min.js"></script>
<canvas id="myChart" width="400" height="200"></canvas>
This is not part of the default behavior. You will need to modify the chart.js script.
How to add text inside the doughnut chart using Chart.js?

Categories