I'm having an issue with highcharts displaying some of my datapoints with a duplicate label, then skipping subsequent labels, and I can't figure out how to fix it.
I gather from reading various posts that this is an issue with the PointInterval option; I'd like to set its value to 1 month, but since months a different lengths, this is not simple.
I am not sure of what a proper solution is though. Here is my code:
http://jsfiddle.net/KJ3tk/
The graph is displaying January and March twice, and skipping February.
The solution suggested in this thread ( http://highslide.com/forum/viewtopic.php?f=9&t=17269 ) seems to be what I need, but I can't figure out how to implement it in the javascript that I currently have. This is the code that they suggest will fix the problem:
var data = [3,2,5,3,5,6,2,3,1];
var month = 4; // the first month
data = $.map(data, function(value) {
return {
x: Date.UTC(2000, month++, 1),
y: value
};
});
I'd really appreciate any assistance! Thanks.
var mydata = [
7418,7386,7552,8903,8439,9356,9861,9411,10257,10169,9622,9940,
/*2009*/
10551,10326,12817,11739,11694,12209,12132,11842,11908,11909,11884,12450,
/*2010*/
15545,14495,16195,15064,13788,14452,14908,15858,16213,15994,15213,15651,
/*2011*/
17233,15515,19629,18960,19834,19997,19358,21042,20189,20660,20034,19815,
/*2012*/
22611,21435,23327,22876,22986,23692,21581,21832,22337
];
var month = 0; // the first month
mydata = $.map(mydata, function(value) {
return {
x: Date.UTC(2008, month++, 1),
y: value
}; });
Live Demo
Related
I'm feeding two daily statistics datasets into my chart. As you can see, each element represents the value for a particular day.
"data":[[{"y":"1", "x":"2018-04-01T04:00:00Z"},
{"y":"14", "x":"2018-04-02T04:00:00Z"},
{"y":"5", "x":"2018-04-03T04:00:00Z"},
{"y":"7", "x":"2018-04-04T04:00:00Z"},
...
The x axis is defined as follows:
xAxes: [{
type: 'time',
distribution: 'series',
time: {
unit: 'month'
}
}]
I (naively?) thought that the chart would be rolling up (summing) the day values into the appropriate month buckets but that's not what I got. Instead, I got monthly tick marks along the x-axis but data points are plotted within the chart at daily precision. (See screenshot.)
Before I go ahead and reprocess my dataset to manually roll up days into their respective month buckets, I'd like to hear whether the chart can in fact do this for me but I'm just setting this up wrong, or whether I do in fact need to take care of this summarization myself, before supplying the dataset to the chart for plotting.
Thanks for your advice!
I solved this by doing the rollup myself during the assembly of the underlying dataset which is then supplied to the chart.
var dayDate = new Date($scope.insights.locationMetrics[lm].metricValues[metric].dimensionalValues[dim].timeDimension.timeRange.startTime);
var monthDate = dayDate.getFullYear() + "-" + (dayDate.getMonth() + 1);
var hitCount = {
y: $scope.safeNumber($scope.insights.locationMetrics[lm].metricValues[metric].dimensionalValues[dim].value),
x: monthDate
}
var alreadyRecorded = hits[labelIdx].findIndex(obj => obj.x == hitCount.x)
if (alreadyRecorded > -1) {
hits[labelIdx][alreadyRecorded].y += Number(hitCount.y);
}
else {
hits[labelIdx].push(hitCount);
}
Extract the date from the underlying data source
Extract yyyy-mm from the date
Create the hitCount object
Check if the hitCount object is already in the array
If the object is already in the array then increment the hitCount (y) within the array.
Otherwise, push the object into the array.
I've already figured out how to make a chart using highcharts where there are three variables- one on the X axis, one on the Y axis, and one on the tooltip. The way to do this is to add the following to the tooltip:
tooltip: {
formatter () {
// this.point.x is the timestamp in my original chartData array
const pointData = chartData.find(row => row.timestamp === this.point.x)
return pointData.somethingElse
}
}
See this fiddle for the full code:
https://jsfiddle.net/m9e6thwn/
I would simply like to do the same, but with two series instead of one. I can't get it to work. I tried this:
tooltip: {
formatter () {
// this.point.x is the timestamp in my original chartData array
const pointData = chartData1.find(row => row.timestamp === this.point.x)
return pointData.somethingElse
const pointData2 = chartData2.find(row => row.timestamp === this.point.x)
return pointData2.somethingElse
}
}
Here is the fiddle of the above: https://jsfiddle.net/hdeg9x02/ As you can see, the third variable only appears on one of the two series. What am I getting wrong?
There are some issues with the way you are using the formatter now. For one, you cannot have two returns in the same function without any if clauses. That will mean that only the first return will be used.
Anyway, here are some improvements I suggest you do for your code.
Add the extra information for each point to highcharts, that makes it a lot easier to access this information through highcharts. E.g. in a tooltip. You can set the data like this:
chartData1.map(function(row) {
return {
x: row.timestamp,
y: row.value,
somethingElse: row.somethingElse
}
})
If you do that, then returning the correct tooltip for each series is a simple matter of doing this:
tooltip: {
formatter () {
// this.point.x is the timestamp in my original chartData array
return this.point.somethingElse
}
}
Working JSFiddle example: https://jsfiddle.net/ewolden/dq7L64jg/6/
If you wanted more info in the tooltip you could then do:
tooltip: {
formatter () {
// this.point.x is the timestamp in my original chartData array
return this.point.somethingElse + ", time: " + str(this.x) + ", value: " + str(this.y)
}
}
Addtionally, you need to ensure that xAxis elements, i.e. your timestamps are sorted. This is a requirement for highcharts to function properly. As it is, your example is reporting
Highcharts error #15: www.highcharts.com/errors/15
in console, because chartData2 is in reverse order. It looks okay for this example, but more complicated examples can lead to the chart not looking as you expect it to.
For this example using reverse is easy enough: data: chartData2.reverse().map(function(row) {return {x: row.timestamp, y: row.value, somethingElse: row.somethingElse}})
Working JSFiddle example: https://jsfiddle.net/ewolden/dq7L64jg/7/
The issue is that the percentage area function does not seem to work when a datetime series is missing a datapoint for some t.
E.g. removing
{
x: new Date("2008-10-31T00:00:00.000Z"),
y: 0,
drilldown: 'my_drilldown'
}
from the series in http://jsfiddle.net/qtoas0jg/3/, giving http://jsfiddle.net/qtoas0jg/4/.
The expected/desired behaviour is that the area should fill for all times t. After going through the documentation I conclude that connectNulls:false, also using step:"left" I can achieve the desire behaviour for one series, the one holding values for all t, but then again not for the second series.
Any ideas?
EDIT:
I was not able to solve the issue using Highcharts functionality. Instead I (quite unrigiously) manipulated the series that I put in by adding value 0 for the missing t. If someone stumbles apon this issue, the following might be useful:
let asd = []
// Firstly finding which t:s that need to be added for respective series
let to_be_added = series.map(arg_a => {
return([arg_a.name,arg_a.data.reduce((acc_b,arg_b) => {
// Adding all available t:s
(asd.indexOf(arg_b.x.toString())>=0?0:asd.push(arg_b.x.toString()))
acc_b.push(arg_b.x.toString())
return acc_b
},[])])
}).map(arg_a => {
return [arg_a[0],asd.filter(arg_b => arg_a[1].indexOf(arg_b)<0)]
})
// Adding the new timestamps with y:0
to_be_added.map(arg_a => {
series.map((arg_b,ind) => {
if (arg_b.name == arg_a[0]) {
arg_a[1].reverse().map(arg_c => {
series[ind].data.unshift({x:new Date(arg_c),y:0})
})
}
})
})
This problem is caused by Highcharts bug: https://github.com/highcharts/highcharts/issues/5634
To workaround use timestamps instead of Date object:
data: [{
x: new Date("2008-10-31T00:00:00.000Z").getTime(),
...
}, ...]
Live demo: http://jsfiddle.net/BlackLabel/0wr3kvt1/
I'm using highcharts.js to visualize data series from a database. There's lots of data series and they can potantially change from the database they are collected from with ajax. I can't guarantee that they are flawless and sometimes they will have blank gaps in the dates, which is a problem. Highcharts simply draws a line through the entire gap to the next available date, and that's bad in my case.
The series exists in different resolutions. Hours, Days and Weeks. Meaning that a couple of hours, days or weeks can be missing. A chart will only show 1 resolution at a time on draw, and redraw if the resolution is changed.
The 'acutal' question is how to get highcharts to not draw those gaps in an efficient way that works for hous, days and weeks
I know highcharts (line type) can have that behaviour where it doesn't draw a single line over a gap if the gap begins with a null.
What I tried to do is use the resolution (noted as 0, 1, 2 for hour day or week), to loop through the array that contains the values for and detect is "this date + 1 != (what this date + 1 should be)
The code where I need to work this out is here. Filled with psudo
for (var k in data.values) {
//help start, psudo code.
if(object-after-k != k + resolution){ //The date after "this date" is not as expected
data.values.push(null after k)
}
//help end
HC_datamap.push({ //this is what I use to fill the highchart later, so not important
x: Date.parse(k),
y: data.values[k]
});
}
the k objects in data.values look like this
2015-05-19T00:00:00
2015-05-20T00:00:00
2015-05-21T00:00:00
...and more dates
as strings. They can number in thousands, and I don't want the user to have to wait forever. So performance is an issue and I'm not an expert here either
Please ask away for clarifications.
I wrote this loop.
In my case my data is always keyed to a date (12am) and it moves either in intervals of 1 day, 1 week or 1 month. Its designed to work on an already prepared array of points ({x,y}). Thats what dataPoints is, these are mapped to finalDataPoints which also gets the nulls. finalDataPoints is what is ultimately used as the series data. This is using momentjs, forwardUnit is the interval (d, w, or M).
It assumes that the data points are already ordered from earliest x to foremost x.
dataPoints.forEach(function (point, index) {
var plotDate = moment(point.x);
finalDataPoints.push(point);
var nextPoint = dataPoints[index+1];
if (!nextPoint) {
return;
}
var nextDate = moment(nextPoint.x);
while (plotDate.add(1, forwardUnit).isBefore(nextDate)) {
finalDataPoints.push({x: plotDate.toDate(), y: null});
}
});
Personally, object with property names as dates may be a bit problematic, I think. Instead I would create an array of data. Then simple loop to fill gaps shouldn't be very slow. Example: http://jsfiddle.net/4mxtvotv/ (note: I'm changing format to array, as suggested).
var origData = {
"2015-05-19T00:00:00": 20,
"2015-05-20T00:00:00": 30,
"2015-05-21T00:00:00": 50,
"2015-06-21T00:00:00": 50,
"2015-06-22T00:00:00": 50
};
// let's change to array format
var data = (function () {
var d = [];
for (var k in origData) {
d.push([k, origData[k]]);
}
return d;
})();
var interval = 'Date'; //or Hour or Month or Year etc.
function fillData(data, interval) {
var d = [],
now = new Date(data[0][0]), // first x-point
len = data.length,
last = new Date(data[len - 1][0]), // last x-point
iterator = 0,
y;
while (now <= last) { // loop over all items
y = null;
if (now.getTime() == new Date(data[iterator][0]).getTime()) { //compare times
y = data[iterator][1]; // get y-value
iterator++; // jump to next date in the data
}
d.push([now.getTime(), y]); // set point
now["set" + interval](now.getDate() + 1); // jump to the next period
}
return d;
}
var chart = new Highcharts.StockChart({
chart: {
renderTo: 'container'
},
series: [{
data: fillData(data, interval)
}]
});
Second note: I'm using Date.setDay() or Date.setMonth(), of course if your data is UTC-based, then should be: now["setUTC" + interval].
Using Highstock to chart a sorted time serie: [[timestamp, value], ...]
The datasource is sampled at irregular intervals. As result the distances between two points (in the time axis) varies.
If two adjacent points are separated for more than 5 minutes I want to show a gap in the chart.
Using the gapSize option doesn't work, because it doesn't allows to specify the 'size' of the gap as a function of time.
Showing gaps is already a part of Highstock, I just need a way to specify it as a fixed amount of time (5 minutes). Ideas?
Btw, beside that the plot works great.
Here's a slightly unclean way to "manipulate" gapSize to work so that it's value is the amount of milliseconds required to create a gap.
(function (H) {
// Wrap getSegments to change gapSize functionality to work based on time (milliseconds)
H.wrap(H.Series.prototype, 'getSegments', function (proceed) {
var cPR = this.xAxis.closestPointRange;
this.xAxis.closestPointRange = 1;
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
this.xAxis.closestPointRange = cPR;
});
}(Highcharts));
This utilizes that gapSize is only used within the getSegments function (see source), and it works based on the closestPointRange of the axis. It wraps the getSegments, sets closestPointRange to 1, calls the original method and then resets closestPointRange to its original value.
With the code above you could do gaps for 5 minutes like this:
plotOptions: {
line: {
gapSize: 300000 // 5 minutes in milliseconds
}
}
See this JSFiddle demonstration of how it may work.
Halvor Strand function wrapper did not work for me as long as getSegments is not part of highstock source code anymore to calculate that gap. Anyway, you can find an approximation to solve the problem combining this other topic and the previows answer like this:
(function(H) {
H.wrap(H.Series.prototype, 'gappedPath', function(proceed) {
var gapSize = this.options.gapSize,
xAxis = this.xAxis,
points = this.points.slice(),
i = points.length - 1;
if (gapSize && i > 0) { // #5008
while (i--) {
if (points[i + 1].x - points[i].x > gapSize) { // gapSize redefinition to be the real threshold instead of using this.closestPointRange * gapSize
points.splice( // insert after this one
i + 1,
0, {
isNull: true
}
);
}
}
}
return this.getGraphPath(points);
});
}(Highcharts))
setting gapSize in plotOptions to the desired size (in ms) like Halvor said:
plotOptions: {
line: {
gapSize: 300000 // 5 minutes in milliseconds
}
}
In case anyone comes across this and is spending hours trying to figure out why gapSize is not working like me. Make sure your time series data is sorted, only then will the gaps appear in the graph.
Another issue I ran into was my data series was in this format
[
{x: 1643967900000, y: 72},
{x: 1643967600000, y: 72},
{x: 1643967300000, y: 72}
]
However this does not seem to work with gapSize and needs to be in the format below
[
[1643967900000, 72],
[1643967600000, 91],
[1643967300000, 241]
]