I have a very simple bar graph where each data point consists of a date (day) and a number. Maybe the only speciality is that not every date is covered (i.e. for some days there is no data point).
When drawing the graph, only those days are shown that have a data point associated with them. All other days are simply omitted. So the x axis is not evenly distributed and skips values.
How can I make sure the X axis is truly linear and does not leave any days out?
PS: This is how my graph is defined:
chartData.datasets[0].data.push( {t: new Date('2019-02-01'), y: value});
// And so on...
var chart = new Chart(ctx, {
type: 'bar',
data: chartData,
options: {
annotation: {
annotations: []
},
legend: {
display: false
},
scales: {
xAxes: [{
type: 'time',
unitStepSize: 1,
distribution: 'series',
time: {
unit: 'day'
},
}]
}
}
});
Here is a working solution to this issue, adapted for new version of Chart.js v3.x
Chart.js v3.2.1 (not backwards compatible with v2.xx)
const ctx = document.getElementById('timeSeriesChart').getContext('2d');
const chartData = [
{x:'2019-01-08', y: 4},
{x:'2019-01-13', y: 28},
{x:'2019-01-14', y: 8},
{x:'2019-01-15', y: 18},
{x:'2019-01-16', y: 68},
{x:'2019-01-18', y: 48},
{x:'2019-01-19', y: 105},
{x:'2019-01-21', y: 72},
{x:'2019-01-23', y: 36},
{x:'2019-01-24', y: 48},
{x:'2019-01-25', y: 17},
{x:'2019-01-28', y: 30},
{x:'2019-01-29', y: 28},
{x:'2019-01-30', y: 25},
{x:'2019-01-31', y: 18},
{x:'2019-02-04', y: 25},
{x:'2019-02-05', y: 22},
{x:'2019-02-06', y: 17},
{x:'2019-02-07', y: 15}
];
const chart = new Chart(ctx, {
type: 'bar',
data: {
datasets: [{
data: chartData,
backgroundColor: 'rgb(189,0,53)'
}]
},
options: {
plugins: {
legend: { //watch out: new syntax in v3.2.0, `legend` within `plugins`
display: false
},
title: { //watch out: new syntax in v3.2.0, `title` within `plugins`
display: false
}
},
scales: {
x: { //watch out: new syntax in v3.2.0 for xAxis
type: 'timeseries', // `time` vs `timeseries` later in images
time: {
unit: 'day'
},
ticks: { // Edit: added this to avoid overlapping - thanks for comment
minRotation: 85, // <-- just try any number
maxRotation: 90 // <-- just try any number
}
}
}
}
});
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- gets you latest version of Chart.js (now at v3.2.1) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment"></script>
<!-- for x-Axis type 'time' or 'timeseries' to work, you need additional libraries -->
<!-- (like moment.js and its adapter) -->
<div>
<canvas id="timeSeriesChart"></canvas>
</div>
Time Cartesian Axis
x: {
type: 'time'
Makes x-Axis "linear" as you requested, days without data are still displayed
Source: https://www.chartjs.org/docs/latest/axes/cartesian/time.html
Time Series Axis
x: {
type: 'timeseries'
Makes data-points equidistant and justifies x-Axis accordingly for bars to show equally distributed
Source: https://www.chartjs.org/docs/latest/axes/cartesian/timeseries.html
Edit: following avoiding overlapping of x-Axis ticks in reply to comment:
So far to show difference between types time and timeseries
I had a similar issue once, the solution that worked for me was first instantiating an array filled with 0s (My array held data for five working days, so I created array of size 5, filled with 0s. Then later on in code the array was overwritten if data for specific day existed. Otherwise it had the value 0).
I imagine this may work for you if the data you're passing is of known size, so you can create it beforehand and fill with 0s.
Bear in mind this may not be the best solution
Related
I am trying to make a chart which has years along the x-axis and dollar amounts along the y-axis. I finally got close to what I'm looking for, but I found that because the x coordinates are numbers, ChartJS is putting commas in them which looks really strange for years.
After some digging, I used the callbacks. options.plugin.tooltip.callbacks.label worked to let me remove commas in the tooltips, but when I use options.scales.x[0].ticks.callback to try to fix the labels on the bottom, not only does it not work, but I don't see the console.log statement in their ever being printed so it seems it's not even calling the callback. I've tried several variations of how to do the callback based on what I found online and on Stack Overflow which I think correspond to the different ways ChartJS did this in different versions. (I'm on version 3.5.1.)
Then, I realized that... none of the options under options.scales appear to have any effect. I change the min, the title, the tick settings (color to red, callback, etc.) and it has no effect. (This also explains why I was having trouble when using the line chart and had to switch to scatter; apparently type: 'linear' wasn't being picked up nor did it do anything different when I set it to type: 'date' or whatever the exact working was for that.)
Meanwhile, the other options like options.showLine or options.elements do have an effect and I'm seeing the chart and not getting any errors in the console. So, it is picking up the options, just ignoring everything I have in options.scales.
Here is the relevant code:
// Sample data added to make this example self-contained
// This is my internal data format
let data = {
"Series1": [ {x: 2001, y: 100 }, {x: 2002, y: 110 }, {x: 2003, y: 107 }, ],
"Series2": [ {x: 2001, y: 107 }, {x: 2002, y: 102 }, {x: 2004, y: 95 }, ],
}
// Define data //////////////////////////////////////////////////////
// I convert data to format ChartJS wants and add a few options
let datasets = [];
for(let label in data) {
let c = colorIterator.next().value
datasets.push({
label: label,
data: data[label],
backgroundColor: c,
borderColor: c,
});
}
// Define options //////////////////////////////////////////////////////
let chartConfig = {
type: 'scatter',
data: { datasets: datasets, },
options: {
title: { display: false },
indexAxis: 'x', responsive: true, maintainAspectRatio: false,
showLine: true,
elements: {
line: { display: true, tension: 0, borderWidth: 1, fill: false, },
point: { radius: 3 }
},
interaction: { mode: 'x', },
scales: {
x: [{
type: 'linear',
min: 1995, max: (new Date()).getFullYear()+1, stepSize: 1,
title: { display: true, text: 'Year' },
ticks: {
display: true,
major: { enabled: true },
color: 'red',
callback: function(value, index, ticks) {
console.log(value);
return Chart.Ticks.formatters.numeric.apply(this, [value, index, ticks])
.replace(",","");
}
}
}],
y: [{
title: { display: true, text: '$' },
ticks: {
display: true,
color: 'red',
},
}],
},
plugins: {
tooltip: {
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if(label) {
let x = context.label.replace(",","");
let y = context.formattedValue;
return 'Year ' + x + ' "' + label + '": $' + y;
} else { return 'x'; }
},
},
},
},
}
};
// MAKE CHART //////////////////////////////////////////////////////
let mainChart = new Chart(document.getElementById(c.id), chartConfig);
As described in the docs the scales are not arrays. All the scales are objects in the scale object.
So you will need to change your code to this:
options: {
scales: {
x: {
// x options
},
y: {
// y options
},
}
}
I want to achiev that the setp size of the xaxis depends on the actual value from the label. Here an example of excel how I expect my chart.js line chart should look the step on the x-axis is "2":
But my chart.js always distributes the labels equally on the xaxis:
My xaxis settings:
xAxes: [{
display: true,
scaleLabel: {
display: true,
labelString: 'Test'
},
ticks:
{
min: 1,
max:17,
stepSize: 5,
},
distribution: 'linear',
}],
Here a link to my chart.js example: https://jsfiddle.net/h97remq3/1/
I tried the whole afternoon different x-axis settings but never get my desired result like in excel. I think the solution should be an easy setting, but I didn't find it...
Instead of using line chart, use a 'scatter' chart. Then omit data.labels and define the data in point format as follows.
data: [
{ x: 1, y: 10 },
{ x: 2, y: 2 },
{ x: 3, y: 5 },
{ x: 5, y: 22 },
{ x: 10, y: 17 },
{ x: 15, y: 11 },
{ x: 17, y: 3 }
],
Please have a look at your amended JSFiddle.
I'm trying to implement a realtime streaming data chart that reads off a page that outputs the last few points of data every second, using the chart.js-plugin-streaming plugin by nagix.
I am reading off of an array in a URL because the chart "resolution" changes depending on which sensor is attached to the backend. Some of them output 1/second, but some of them output 10/second, and in the second case, refreshing the chart 10 times per second doesn't sound like a good idea, both because of processing power (old computers) and number of requests sent to the sensors.
So my URL outputs an array of objects for the last 20 points of data (roughly 2 to 20 seconds worth of data), and I refresh the chart 1 time a second.
I was hoping that the chart would just append only the new points, but I'm having weird behaviour. I've checked the timestamps and they are correct.
The data array is fetched using a simple jQuery $.get() call to the Django view URL that generates the array each time it is called.
This is an example of the URL output per call, for a 1/second sensor:
[
{y: 0.74, x: 1558531380957},
{y: 0.96, x: 1558531379950},
{y: 1.08, x: 1558531378942},
{y: 1.11, x: 1558531377939},
{y: 1.13, x: 1558531376932},
{y: 1.1, x: 1558531375930},
{y: 0.59, x: 1558531374914},
{y: 0.75, x: 1558531373911},
{y: 1.25, x: 1558531372902},
{y: 0.75, x: 1558531371898},
{y: 0.85, x: 1558531370893},
{y: 0.59, x: 1558531369889},
{y: 0.4, x: 1558531368887},
{y: 1.08, x: 1558531367879},
{y: 1.31, x: 1558531366871},
{y: 0.63, x: 1558531365866},
{y: 1.19, x: 1558531364859},
{y: 1.26, x: 1558531363854},
{y: 0.92, x: 1558531362848},
{y: 1.31, x: 1558531361837},
]
The next time it is called, the output will have removed the first point, the array would "scroll" and 1 new points will be show up at the tail end.
The objects inside the array have format: {"y": <float>, "x": <timestamp-in-ms>}
My chart config:
$(document).ready(function() {
var chartColors = {
red: 'rgb(255, 99, 132)',
blue: 'rgb(54, 162, 235)'
};
var color = Chart.helpers.color;
var config = {
type: 'line',
data: {
datasets: [{
label: 'Z-Score',
backgroundColor: color(chartColors.blue).alpha(0.75).rgbString(),
borderColor: chartColors.red,
fill: false,
lineTension: 0,
data: []
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
title: {
display: false,
},
scales: {
xAxes: [{
type: 'realtime',
realtime: {
duration: 60000,
refresh: 1000,
delay: 2000,
pause: false,
ttl: undefined,
frameRate: 48,
onRefresh: function(chart) {
var data = []
$.get( "{% url 'live_z_score' %}", function(zScoreJSON) {
data = zScoreJSON
Array.prototype.push.apply(
chart.data.datasets[0].data, data
);
});
}
}
}],
yAxes: [{
scaleLabel: {
display: true,
labelString: 'value'
}
}]
},
tooltips: {
mode: 'nearest',
intersect: false
},
hover: {
mode: 'nearest',
intersect: false
}
}
};
var ctx = document.getElementById('myChart').getContext('2d');
window.myChart = new Chart(ctx, config);
});
At this point it sort of works, except I'm having weird behaviour where a line would connect the first point to all subsequent points:
Example GIF (GIF size was too large, it's about 40 seconds long)
It seems like the graph is creating a new line each time it fetches new data, instead of dumping the previous line? I'm not sure how it handles data internally.
At this point I'm not sure exactly what the problem might be. The chart sort-of works, as it does create a line that is accurate to the graph, but then it seems like it's joining the first point in the array together with the latest point in the array. You can see this behaviour in the GIF.
This is because you are pushing 20 data points in onRefresh() every second, but only one data point out of 20 points is new and the rest are already inserted ones. This plugin doesn't dedup the data even if the same data with the same timestamp exists, so you see the line iterating the same points over and over.
A workaround would be to store the latest timestamp in your array when it is inserted, and to filter out the older data in the next onRefresh call so that only new data is pushed to chart.data.datasets[0].data.
I have a tornado chart created using bar-charts form Highcharts. I have set a threshold value and on either side of which there are low and high bars.
Now I need to make the threshold line visible. I also have a problem of labels getting overlapped when high, low and base values are equal.
JSFiddle Link: http://jsfiddle.net/xe7qL156/
Please help me to solve it. Thanks.
series: [{
threshold: 29.5,
name: 'Low',
grouping: false,
type: 'bar',
data: [{
x: 0,
y: 12.15,
}, {
x: 1,
y: 15.45,
}, {
x: 2,
y: 31.25,
}, {
x: 3,
y: 12.15,
}],
labels: [10, 5, 1, 2]
}, {
threshold: 29.5,
name: 'High',
grouping: false,
type: 'bar',
data: [{
x: 0,
y: 46.86,
}, {
x: 1,
y: 42.28,
}, {
x: 2,
y: 27.77,
}, {
x: 3,
y: 46.86,
}],
labels: [30, 10, 3, 4]
}]
Update: here is a mockup image of what I need
I'm not 100% sure what you want, but I think you want a vertical line overthe bars at the 29.5 position ?
If so, you can do that with another series:
series: [
{type:'line',color:'black',zIndex:505,data:[[0,29.5],[4,29.5]]},
http://jsfiddle.net/fsbqnw7m/
Alternatively, you can use the renderer API to draw a line wherever you want.
http://api.highcharts.com/highcharts#Renderer
Line can be added also as plotLine in particular place.
I there any easy way to load the data in the heatmap with dates on "Y".
My data is in the following format:
[{x:1, y: 1401292253, value:0.2, name:"a"},{x:2, y: 1401173762, value:0.3, name:"b"},{x:0, y: 1401173462 , value:0.6, name:"c"}]
I want Y of the heatmap to be build automatically based on the given value. But I cant figure out how to do it.
What I've tried is:
http://jsfiddle.net/tZ6GP/16/
You need to set rowsize (or colsize for xAxis) to tell highcharts what is the range for each point. Otherwise it will be 1ms which is really low value. Second thing is that your y-values are in seconds, while in JS timestamps are in ms.
When changed that two things, you will get nice chart: http://jsfiddle.net/tZ6GP/19/
series: [{
rowsize: 3600000, // one hour
data: [{
x: 0,
y: 1401292253000,
value: 0.2,
name: "a"
}, {
x: 1,
y: 1401173762000,
value: 0.3,
name: "b"
}, {
x: 2,
y: 1401173462000,
value: 0.6,
name: "c"
}]
}]
To do this you have to treat your yAxis as categories still but then apply a label.format. This should get you started:
xAxis: {
type: 'category',
categories: ['a', 'b', 'c']
},
yAxis: {
type: 'category',
categories: ['1401292253', '1401173762', '1401173462'],
labels: {
format: '{value: %H:%M:%S}'
}
}
I also cleaned up your series.data a bit. Basically you need to give the matrix coordinates (x/y) and the value.
series: [{
data: [{
x: 1,
y: 0,
value: 0.2
}, {
x: 2,
y: 1,
value: 0.3
}, {
x: 0,
y: 2,
value: 0.6
}]
}]
By looking at this you can make out the locations of your points.
Live demo.
Update for latest highcarts code. You need to modify the yAxis label formatter:
yAxis: {
categories: ['1401292253', '1401173762', '1401173462'],
labels: {
formatter: function () {
var theTime = parseFloat(this.value);
return Highcharts.dateFormat('%H:%M:%S', theTime);
}
}
},
Update live demo.