Chartjs duration horizontal bar chart - chart.js

I'm trying to figure out how to approach drawing this chart.
It looks like a stacked horizontal bar chart, but I'm having troubles with defining a datasets format for duration intervals. I still haven't found the right way to structure the data source to achieve this result.
Another option could be a line/scatter chart. And the final one is writing custom plugin and drawing this on a canvas manually, shape by shape. I would like to avoid this though :)
Any idea would be really helpful.
Thanks!

I found the way to work this out with the help of #John Go-Soco. Some very key pieces of the graph config are bellow. In short, each line is a separate dataset with two data points defining a start date and the end date.
const yMap = [ 'amlodipine', 'simvastatin', 'lisinopril' ];
const mapDataPoint = function(xValue, yValue) {
return {
x: xValue,
y: yMap.indexOf(yValue)
};
};
const config = {
type: 'line',
data: {
datasets: [
{
//other dataset config options here
data: [
mapDataPoint('1/1/2007', 'simvastatin'),
mapDataPoint('6/1/2010', 'simvastatin'),
]
},
{
//other dataset config options here
data: [
mapDataPoint('9/1/2010', 'simvastatin'),
mapDataPoint('11/1/2018', 'simvastatin'),
]
},
{
//other dataset config options here
data: [
mapDataPoint('1/1/2007', 'lisinopril'),
mapDataPoint('9/1/2015', 'lisinopril'),
]
},
{
//other dataset config options here
data: [
mapDataPoint('1/1/2014', 'amlodipine'),
mapDataPoint('11/1/2022', 'amlodipine'),
]
},
]
},
options: {
//other chart config options here
scales: {
xAxes: [
{
type: 'time',
time: {
unit: 'year',
round: 'day',
displayFormats: {
year: 'YYYY'
},
min: moment('2006', 'YYYY'),
max: moment('2019', 'YYYY')
},
}
],
yAxes: [
{
ticks: {
min: -1,
max: yMap.length,
//setting custom labels on the Y-axes
callback: function(value) {
return yMap[ value ];
}
}
}
]
}
},
};
const ctx = 'reference to your ctx here';
const chart = new Chart(ctx, config)

Related

Multiple line types in ChartJS

I have a project to recreate a Parametric EQ and am using Angular and Chartjs (angular-chart) to do so. Most of it's working, but, I have a need for two different line types in the same graph. Most of the filter types have lines connecting the points, but one of the filter types (band stop) should show a gap at the frequency. I can plot the X/Y of the points, but how do I get the span gaps to only apply to the one data set (and not the other 9)?
Thank you for your help!
//I think the relevant data:
$scope.data = [
[{x:-100,y:-90},{x:-60,y:-10},{x:-50,y:0}],
[{x:-100,y:-80},{x:-50,y:-20},{x:0,y:0}],
[{x:-100,y:-70},{x:-40,y:-30},{x:0,y:0}],
[{x:-100,y:-60},{x:-30,y:-40},{x:0,y:0}],
[{x:-100,y:-50},{x:-20,y:-50},{x:0,y:0}],
[{x:-100,y:-40},{x:-30,y:-60},{x:0,y:0}],
[{x:-100,y:-30},{x:-40,y:-70},{x:0,y:0}],
[{x:-100,y:-20},{x:-50,y:-80},{x:0,y:0}],
[{x:-100,y:-10},{x:-60,y:-90},{x:0,y:0}],
[{x:-100,y:0},{x:-70,y:-100},{x:0,y:0}]
];
$scope.options = {
tooltips:{enabled:false},
elements:{
point:{
radius:0
},
line:{
tension:.25,
fill:false
}
},
scales: {
xAxes:[{
type:'logarithmic',
display:true,
ticks:{
min:10,
max:10000,
callback: function(...args) {
const value = Chart.Ticks.formatters.logarithmic.call(this, ...args);
if (value.length) {
return Number(value).toLocaleString()
}
return value;
}
},
}],
yAxes: [
{
id: 'y-axis-1',
ticks:{min:-20,max:20,stepSize:10},
type: 'linear',
display: true,
position: 'left'
}
]
}
};
Seems like you can add it in the specific dataset as an option in which you want it:
https://www.chartjs.org/docs/latest/charts/line.html?h=spangaps

Conditional in ChartJS axes tick callback function isn't returning the expected labels

I have a chart containing data for each day of the year and I'm wanting to show the x-axis simply as months.
I've set up the following callback function which (crudely) grabs the month from the set of labels, checks to see whether it already exists and if not, returns it as an axis label
let rollingLabel;
...
function(label, index, labels) {
let _label = label.replace(/[0-9]/g, '');
if (rollingLabel != _label) {
rollingLabel = _label;
return rollingLabel;
}
}
However, it's only returning two of the expected four labels.
What's confusing me more is that if I add console.log(rollingLabel) within the conditional I can see that the variable is updating how I'd expect but it's not returning the value, or it is and the chart isn't picking it up for whatever reason. Even more confusing is that if I uncomment line 48 // return _label the chart updates with all the labels so I don't believe it's an issue with max/min settings for the chart.
If anyone has any ideas I'd be most grateful. I've been staring at it for hours now!
The expected output for the below snippet should have the following x-axis labels:
Aug | Sep | Oct | Nov
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
let data = [
1,6,3,11,5,1,2,6,2,10,5,8,1,1,2,4,5,2,3,1
];
let labels = [
"Aug 1","Aug 2","Aug 3","Aug 4","Aug 5","Sep 1","Sep 2","Sep 3","Sep 4","Sep 5","Oct 1","Oct 2","Oct 3","Oct 4","Oct 5","Nov 1","Nov 2", "Nov 3","Nov 4","Nov 5"
];
let rollingLabel;
chart = new Chart(ctx, {
type: "line",
data: {
datasets: [
{
backgroundColor: '#12263A',
data: data,
pointRadius: 0
}
],
labels: labels,
},
options: {
legend: {
display: false
},
responsive: false,
scales: {
xAxes: [
{
gridLines: {
display: false
},
ticks: {
display: true,
autoSkip: true,
callback: function(label, index, labels) {
let _label = label.replace(/[0-9]/g, '');
if (rollingLabel != _label) {
rollingLabel = _label;
return rollingLabel;
}
// return _label;
}
}
}
]
},
tooltips: {
mode: "index",
intersect: false
},
hover: {
mode: "index",
intersect: false
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart"></canvas>
You need to define ticks.autoSkip: false on the x-axis to make it work as expected:
autoSkip: If true, automatically calculates how many labels can be shown and hides labels accordingly. Labels will be rotated up to maxRotation before skipping any. Turn autoSkip off to show all labels no matter what.
Please take a look at your amended code below:
let data = [
1,6,3,11,5,1,2,6,2,10,5,8,1,1,2,4,5,2,3,1
];
let labels = [
"Aug 1","Aug 2","Aug 3","Aug 4","Aug 5","Sep 1","Sep 2","Sep 3","Sep 4","Sep 5","Oct 1","Oct 2","Oct 3","Oct 4","Oct 5","Nov 1","Nov 2", "Nov 3","Nov 4","Nov 5"
];
let rollingLabel;
chart = new Chart('chart', {
type: "line",
data: {
datasets: [
{
backgroundColor: '#12263A',
data: data,
pointRadius: 0
}
],
labels: labels,
},
options: {
legend: {
display: false
},
responsive: false,
scales: {
xAxes: [
{
gridLines: {
display: false
},
ticks: {
display: true,
autoSkip: false,
callback: function(label, index, labels) {
let _label = label.replace(/[0-9]/g, '');
if (rollingLabel != _label) {
rollingLabel = _label;
return rollingLabel;
}
}
}
}
]
},
tooltips: {
mode: "index",
intersect: false
},
hover: {
mode: "index",
intersect: false
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart"></canvas>
I found a easy solution via the chart.js documentation.
const config = {
type: 'line',
data: data,
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Chart with Tick Configuration'
}
},
scales: {
x: {
ticks: {
// For a category axis, the val is the index so the lookup via getLabelForValue is needed
callback: function(val, index) {
// Hide the label of every 2nd dataset
return index % 2 === 0 ? this.getLabelForValue(val) : '';
},
color: 'red',
}
}
}
},
};
The callback function decides what labels will be shown. Current setup shows every 2nd label, if you want to show every 3rd for example you would change:
return index % 2 === 0 ? this.getLabelForValue(val) : '';
to:
return index % 3 === 0 ? this.getLabelForValue(val) : '';

Is it possible to define data attributes for each dataset value in a Chart.js chart?

Is it possible to define HTML5 data attributes for each dataset value in a Chart.js chart? The goal is to enhance the default tooltip information (e.g. X-axis value & Y-axis value) with additional information related to the target dataset value.
In the example below, a chart is displayed that plots the total number of diapers used by 6 children. When the tooltip displays, it says the name of the child and the number of diapers. I would like the tooltip to display age and gender, as well, and figured the tooltip callback could get these from data attributes attached to each dataset value.
I've reviewed the Chart.js documentation and searched online forums, like StackOverflow, without success.
var myChart = new Chart(document.getElementById('myChart'), {
type: 'bar',
data: {
labels: ['Gabriel', 'Zelie', 'Santiago', 'Serafina', 'Berenice', 'Pia'],
datasets: [{ label: 'Diapers', data: [1050, 1224, 1382, 1166, 1471, 0] }]
},
options: {
legend: { display: false },
scales: { yAxes: [{ ticks: { beginAtZero: true } }] },
title: { display: true, text: 'Total Diapers Used' },
tooltips: {
titleFontSize: 18,
callbacks: {
label: function(tooltipItem, data) {
var defaultLabel = data.datasets[tooltipItem.datasetIndex].label + ': ' + tooltipItem.yLabel;
var age = "Age: <age>";
var gender = "Gender: <gender>";
var labels = [defaultLabel, age, gender];
return labels;
}
}
}
}
});
</script>
It depends how you structure your data. In my example I made an object that contains all the data.
let dataObject = {
Gabriel: {
diapers: 40,
age: 4
},
// Same structure here...
Zelie: {...},
Santiago: {...},
Serafina: {...},
Berenice: {...},
Pia: {...}
}
This way you can easily access your data in your tooltip with
tooltips: {
callbacks: {
label: function(tooltipItem, data){
return ['Name: '+tooltipItem.label, 'Diapers: '+tooltipItem.value, 'Age: '+dataObject[tooltipItem.label].age]
}
}
}
You should get all the information you need in the jsbin.
https://jsbin.com/qiyeyohavo/1/edit?html,js,output

problem with multiple datasets in chart.js

I'm trying to use chart.js to create a bar chart that shows the number of ad impressions in an ad buy by publication. The desired chart would show a bar for each publication representing the number of impressions for the ad on that website.
I thought that this needs to happen as multiple datasets, one for each publication, where each dataset contains one data point. Here's the code I'm using for this approach:
var chartData_webbanner_300x600 = {
labels: ["Publication 1", "Publication 2"],
datasets: [
{
label: "Publication 1",
backgroundColor: "#971317",
data: [30000]
},
{
label: "Publication 2",
backgroundColor: "#0b72ba",
data: [40000]
},
]
};
window.onload = function() {
var ctx_webbanner_300x600 = document.getElementById('chart_webbanner_300x600').getContext('2d');
window.myBar = new Chart(ctx_webbanner_300x600, {
type: 'bar',
data: chartData_webbanner_300x600,
options: {
title: {
display: true,
text: 'Web Banner Impressions'
},
responsive: true,
}
});
}; //window.onload = function()
The resulting chart only shows one bar. Here's a screenshot:
I also tried this as a single dataset, but had no luck there. This is the approach I tried with that:
var chartData_webbanner_300x600 = {
labels: ["Total Impressions"],
datasets: [
{
label: ["Publication 1", "Publication 2"],
backgroundColor: ["#971317","#0b72ba"],
data: [30000,40000]
}
]
};
window.onload = function() {
var ctx_webbanner_300x600 = document.getElementById('chart_webbanner_300x600').getContext('2d');
window.myBar = new Chart(ctx_webbanner_300x600, {
type: 'bar',
data: chartData_webbanner_300x600,
options: {
title: {
display: true,
text: 'Web Banner Impressions'
},
responsive: true,
}
});
}; //window.onload = function()
Here's how that is displaying (with no bars):
Please let me know if you have any ideas on what I'm doing wrong. Thank you for taking the time to help!
I was able to get it working with this code:
var graphData = {
labels: ['Publication 1', 'Publication 2'],
datasets: [{
label: 'Impressions',
data: [30000, 40000],
backgroundColor: [
"#971317",
"#0b72ba"
],
}, ]
};
var ctx_webbanner_300x600 = document.getElementById('chart_webbanner_300x600').getContext('2d');
var chr = new Chart(ctx_webbanner_300x600, {
data: graphData,
type: 'bar',
options: {
scales: {
yAxes: [{
display: true,
ticks: {
beginAtZero: true // minimum value will be 0.
}
}]
}
}
});
This is based on what I found here Setting specific color per label in chart.js and here How to set max and min value for Y axis - which overcame a problem where the scale was starting at the lowest value in my data set.

Chartjs - data format for bar chart with multi-level x-axes

Input data
Engine,Car,Brand,Efficiency
ABC212,Toyota Corolla,Toyota,1.95
ABC212,Toyota Yaris,Toyota,1.94
ABC212,Totyota Etios,Toyota,1.93
ABC212,Honda City,Honda,1.93
ABC212A,Honda Brio,Honda,1.91
DEF311,Toyota Camry,Toyota,1.90
DEF310,Toyota Prius,Toyota,1.82
DEF310,Ford Explorer,Ford,1.85
DEF310,Ford Endeavour,Ford,1.83
DEF305,Ford Fugo,Ford,1.79
With data like above, I need to create a chart in ChartJs with multi-level x-axes. An expected output created using MS excel pivot chart is as below. Here the efficiency of each model is plotted as a bar. Bars in each group is sorted in the descending order of the efficiency value. How should I create the data for this kind of chart ?
I have made such graph in my POC , where I used :
data: {
labels: ["ABC212", "ABC212A",...],
datasets: [
{
label: "ABC212",
data: [val1, val2,....]
}, {
label: "ABC212A",
data: [val3, val4,...]
}
]
}
Without being confident that this is the simplest solution, I provide some example where I use a second axis, containing labels for the groups, but in a scaled-up array, for correct alignment.
The only problem is that if you enable rotation for the group maxRotation>0, the text will always be rotated, since (due to scaling) it is bound to a very small area.
https://jsfiddle.net/h6z4apvo/
var myData = [
["ABC212","Toyota Corolla","Toyota",1.95],
["ABC212","Toyota Yaris","Toyota",1.94],
["ABC212","Totyota Etios","Toyota",1.93],
["ABC212","Honda City","Honda",1.93],
["ABC212A","Honda Brio","Honda",1.91],
["DEF311","Toyota Camry","Toyota",1.90],
["DEF310","Toyota Prius","Toyota",1.82],
["DEF310","Ford Explorer","Ford",1.85],
["DEF310","Ford Endeavour","Ford",1.83],
["DEF305","Ford Fugo","Ford",1.79]
];
/*Calculates group labels to a scaled array so that they can align better*/
function calculateGroupLabels(data){
const scaleFactor=100;
var labels = _(data)
.groupBy((elem)=>elem[0])
.map((entriesOnSameGroup, key)=>{
var newSize = entriesOnSameGroup.length*scaleFactor;
var newArray = new Array(newSize);
newArray[0]="";
newArray[newArray.length-1]="";
newArray[parseInt((newArray.length-1)/2)]=key;
return newArray;
}).flatten().value()
return labels;
}
var labels = calculateGroupLabels(myData);
var ctx = $("#c");
var myChart = new Chart(ctx, {
type: 'bar',
data: {
datasets: [{
label: '# of Votes',
xAxisID:'modelAxis',
data: myData.map((entry)=>entry[3])
}]
},
options:{
scales:{
xAxes:[
{
id:'modelAxis',
type:"category",
ticks:{
//maxRotation:0,
autoSkip: false,
callback:function(label, x2, x3, x4){
console.log("modelAxis", label, x2, x3, x4)
return label;
}
},
labels:myData.map((entry=>entry[1]))
},
{
id:'groupAxis',
type:"category",
gridLines: {
drawOnChartArea: false,
},
ticks:{
padding:0,
maxRotation:0,
autoSkip: false,
callback:function(label){
return label;
}
},
labels:labels
}],
yAxes:[{
ticks:{
beginAtZero:true
}
}]
}
}
});