I'm trying to plot some sparse values over a few hundred x-axis category labels. The labels represent points along a geographical entity, so it is necessary to space them according to the actual distance between each point.
I can easily make x-axis categories and plot the values against them, but each category is evenly spaced. If I change to using a complex marker with {x, y, name} properties, the categories are only displayed if they match the tick interval. If a category doesn't match the tick interval, then a number is displayed.
I can produce the following:
with this small fake sample of data, JSFiddle:
...
xAxis: {
type: 'category'
},
series: [{
name: 'Series 1',
data: [
{x:1, y:null, name: 'AB1.1'},
{x:4, y:1, name: 'AB1.2'},
{x:5, y:null, name: 'AB1.3'},
{x:11, y:1, name: 'AB1.4'},
{x:14, y:null, name: 'AB1.5'},
{x:14, y:null, name: 'AB1.6'},
{x:19, y:1, name: 'AB1.7'},
{x:27, y:1, name: 'AB1.8'},
{x:28, y:null, name: 'AB1.9'},
{x:30, y:1, name: 'AB2'},
{x:37, y:1, name: 'AB2.1'},
{x:37, y:1, name: 'AB2.2'},
{x:38, y:1, name: 'AB2.3'},
]
}]
...
As you can see, the x-axis at this zoom level shows:
0, 3, 6, 9, 12, 15, 18, 21, 24, AB1.8, AB2, 33, 36, 39
What I want is for the x-axis to show any of the actual labels (so long as the markers are placed relative to their distance, just like the x-values of the plot); but no generated numbers.
In reality, I have multiple series with around 1,000 points each, but they will all be on the same geographical entity so they all share the same categories. (JSFiddle with much more fake data). I can also guarantee that x-values are whole numbers.
I have already tried specifying various x-axis options around ticks, minimum ticks etc. but highcharts still wants to extrapolate evenly spaced labels.
Thanks!
I have got the results I want (JSFiddle) by setting xAxis.tickPositions dynamically on zoom. To begin with, I pick about 35 x-Axis positions to display my markers (an arbitrary number but I picked something that worked for my fixed-width chart size).
Then, on zoom, I update the chart to display about 30 markers that are within the new zoom level.
This way I always have a good number of markers displayed, and they are unevenly spaced because that's what my actual data is like.
Zoomed out (L) and zoomed in (R):
this is an array of all x-axis points that have a marker-a few thousand
allTicks = [1, 6, 9, 9, ...]
a function to select 30 points from the given array
function selectTickPositions(tickPositions) {
const maxTicks = 30;
if (tickPositions.length <= maxTicks) return tickPositions; // return all
let mod = Math.round(tickPositions.length / maxTicks);
// always select the first and the last, and then up to 30 more
return tickPositions.filter((val, idx) => idx == 0 || idx == tickPositions.length - 1 || idx % mod == 0);
}
setup the chart options:
let theChart = Highcharts.chart('container', {
chart: {
...
events: {
// the selection event is fired when a zoom selection is made
selection: (event) => {
let ticks;
if (event.xAxis) {
let min = event.xAxis[0].min;
let max = event.xAxis[0].max;
ticks = selectTickPositions(allTicks.filter(t => t >= min && t <= max));
} else {
ticks = selectTickPositions(allTicks);
}
theChart.update({
xAxis: {
tickPositions: ticks
}
});
return true;
}
}
},
...
xAxis: {
type: 'category',
tickPositions: selectTickPositions(allTicks)
},
});
I am not sure if I understand your requirement very well, but here is my attempt of showing just a xAxis.labels if the label is a category string.
Demo: https://jsfiddle.net/BlackLabel/4xdjw5L7/
xAxis: {
type: 'category',
endOnTick: true,
startOnTick: true,
labels: {
formatter() {
if (typeof this.value !== 'number') {
return this.value
}
}
}
},
API: https://api.highcharts.com/highcharts/xAxis.labels.formatter
Let me know if this is an expected output.
Related
I have a sparkline chart of temperature sensor data,
Its a year of data sampled at every 1 min
I first load a blank chart, then I do a server call, bring the results back and display it by calling
getchart.addSeries({
name: thisGroup,
id: probeItem[0],
data: probeDataArray,
keys: ['x', 'y', 'specialId'],
there could be upto 20 series, and they all load on the screen one by one
This renders quite quickly, however I now need to add a label annotation where the temperature goes over a certain Value (i.e. in add a warning symbol when its alarm state)
Currently I'm looping through each point and seeing if its over a certain value:
currentSeries.points.forEach(function (point) {
However this is very slow.
I have an array of the alarms, and can reference them as
['x', 'y', 'specialId']
However I cannot see how I can add an annotation label by x,y or specialId.
I can only seem to add the label if i loop through all the points already rendered
Is there a way to add a label by using my Id's?
I also need to resize the graph and the labels to remain in the same place
Alternatively if this is not possible, is there anyway to add the labels as i'm adding the series?:
getchart.addSeries({
name: thisGroup,
id: CurrentGroupID,
dashStyle: 'ShortDot',
data: groupLogArray,
keys: ['x', 'y', 'specialId'],
showInNavigator: true, //this shows the series data in the small bottom navigator
point: {
events: {
click: function () {
//alert("test click");
}
}
}
});
You can add an annotation by x and y axis values:
chart.addAnnotation({
labels: [{
text: 'Alarm',
point: {
x: 2,
y: 3,
xAxis: 0,
yAxis: 0
}
}]
});
Live demo: http://jsfiddle.net/BlackLabel/k6trha3e/
API Reference: https://api.highcharts.com/class-reference/Highcharts.Chart#addAnnotation
As an alternative, you can also use data labels: http://jsfiddle.net/BlackLabel/n4586xzq/
When data labels overlap in Highcharts, only one data label is displayed. This seems to be handled randomly. See fiddle:
http://jsfiddle.net/lamarant/rmxLd1d4/
$(function () {
$('#container').highcharts({
title: {
text: 'Label Test'
},
series: [{
type: 'line',
data: [ 10, 10, 10, 10, 10, 10, 50],
dataLabels: {
enabled: true,
color: 'blue',
zIndex: 10
},
zIndex: 10
},
{
type: 'line',
data: [ 11, 11, 11, 11, 11, 11, 49],
dataLabels: {
enabled: true,
color: 'red',
zIndex: 10
},
zIndex: 20
}]
});
});
Notice that the first data label displayed in the chart is from the second series while the rest are from the first series.
I tried setting the priority of the label display using zIndex in both series and series.dataLabel with no luck.
Is there any way to set it so that a designated series always takes priority when Highcharts determines which label to display?
One possible fix is supplying a labelrank for each point, which is used to evaluate their sorting order. If you supply this rank for each point in a series, that series should be considered "above" series without such a rank (or with a lower rank integer value).
In code, manually for each point:
series: [{
data: [
{ y: 11, labelrank: 1 },
{ y:11, labelrank: 1 }
// ...
],
// ...
}]
In code, using the callback function (JSFiddle):
$('#container').highcharts({
// Options ...
}, function() {
var mySeriesIndex = 1;
// For each point in the preferred series
for(i = 0; i < this.series[mySeriesIndex].points.length; i++)
// Set a labelrank
this.series[mySeriesIndex].points[i].labelrank = 1;
});
My current take on the issue itself is this:
With the current implementation of the overlap logic it seems to me that this is a bit of an unintended behavior. The source code uses Array.sort in a way that shifts the positions of the point labels, since Array.sort is not guaranteed to be stable (same-value items wont retain their original order).
If you check your example in Firefox it should work as intended (their implementation of Array.sort is different), while in Chrome it doesn't (not stable). You could hope that this little feature is fixed.
I'm drawing a really simple line chart with hours slept each day. So hours are on the y axis and dates on the x axis. The y axis goes from 0 to 23, but interesting values are usually between 3 and 12, so I only want to draw these values. This is easy, just define min and max on yaxis.
But now highcharts will still draw values above and below the min and max y valus. What I want is to have it just draw values above 12 on the 12 max grid line, and show the real value in the tooltip, and the inverse for values below 3.
Illustration of problem: https://jsfiddle.net/HhP39/10/
$(function () {
$('#container').highcharts({
chart: {},
yAxis: {
title: {
text: 'Hours'
},
min: 3,
max: 12,
tickInterval: 1
},
series: [{
data: [2,6,13,4,9,12,6,8,1,15,9,7,5,8,14,5,8,2,3]
}]
});
});
As you can see, values just out of range, such as 2, will still produce a tooltip if you hover over where they are. I want all values like these to move to be on the min and max lines.
Anybody got any ideas?
Take a look at this update to your fiddle: https://jsfiddle.net/HhP39/7/
It's creating an array of the original values and then an array of the adjusted values. The graph actually charts the adjusted values but the tooltip pulls from the original values.
data = [2,6,13,4,9,12,6,8,1,15,9,7,5,8,14,5,8,2,3];
series = [];
tooltips = [];
for (var i in data) {
tooltips.push(data[i]);
series.push( Math.max(3, Math.min(data[i], 12)) );
}
And then chart it with:
$('#container').highcharts({
chart: {},
yAxis: {
title: {
text: 'Hours'
},
min: 3,
max: 12,
tickInterval: 1
},
tooltip: {
formatter: function() {
return ('Real value: ' + data[this.x]);
}
},
series: [{
data: series
}]
});
I'm relatively new to HighCharts, and I would like to have two Y-axes where the data is always expressed in terms of the left-hand (primary) Y-Axis, but there is a constant function whereby you can translate the data into the terms on the right-hand (secondary) Y-Axes.
Take, for example, http://jsfiddle.net/M2EVb/
It's a constant well-known function to translate from Fahrenheit to Celsius. Even though I have specified the Primary Y-Axis as ranging from 32 to 212 with a tick interval of 18, and the Secondary Y-Axis as ranging from 0 to 100 with a tick interval of 10, the two axes don't line up properly; likely due to the "cels" data. But the point is that I would like to have the "cels" data just be another set of Fahrenheit temps, and have the right-hand Y-Axis values be the proper Celsius "translations" of their respective Fahrenheit values, and always show up.
Use the label's formatter function in the yAxis configuration to convert your yAxis ticks from F to C like this:
yAxis: [{
title: {
text: 'Temperature (C)'
},
labels: {
formatter: function() {
return YourConversionFunction(this.value) +' C';
}
}
}]
Make sure you are defining your Series using the same dataset (the F data):
series: [{
type: 'area',
name: 'Temp (F)',
data: Far_TempData,
yAxis: 0,
xAxis: 0
}, {
type: 'area',
name: 'Temp (C)',
data: Far_TempData,
yAxis: 1,
xAxis: 0,
lineWidth:0,
fillOpacity: 0.01,
visible:true
}]
I have a very simple example using Highcharts which uses "datetime" on one axis and categories on the other. It renders with no points and does not show the categories labels at all. I'm wondering now if you can't use that combination of types. Here is the code:
var chart = new Highcharts.Chart({
chart: {
renderTo: 'container'
},
xAxis: {
type: 'datetime'
},
yAxis: {
categories: ['p1', 'p2']
},
series: [{
type: 'scatter',
data: [
{
name: 'Deliv1',
x: Date.UTC(2011,0,1),
y: 'p1'
},
{
name: 'Deliv2',
x: Date.UTC(2012,0,1),
y: 'p2'
}
]
}]
});
The answer to my problem was given on the highcharts forum. I thought I'd report back here what the solution was. I was errantly using y: 'p1' and y: 'p2' for values on the points. The y values actually are the indexes of the categories. Here is the updated code which works:
data: [
{
name: 'Deliv1',
x: Date.UTC(2011,0,1),
y: 0
},
{
name: 'Deliv2',
x: Date.UTC(2012,0,1),
y: 1
}
]
It's possible but you'll need to pretend the y values are numeric.
Probably by having an array with the actual Y-value and a number (maybe index) then the point's y value to the number and for the y-axis settings add the label's formatter to return the actual y-value based on the value.
You'll also need to adjust the min, max, interval and if you're using tooltips add a similar formatter to get the y-value.
(If I get more time, I'll try to create an example).