Long array of small data points is rendered as a jagged line - javascript

The Goal
I'm attempting to render a long series of data (around 200 ticks, from small float values like 1.3223) into a line chart.
The Issue
When I use a series of data that changes only a small amount (around 0.0001 every tick), the chart is rendered as very jagged (scissor like). I would like to somehow fix it to have a "saner" radius between each point on the graph.
A Good Example
On the other hand, when rendering higher values (around 1382.21) with bigger difference between ticks (from 0.01 to 0.05 +/-) the graph is rendered more smooth and aesthetically pleasing.
Edit: As user Arie Shaw pointed out, the actual low or high values don't make a difference and it remains an issue of representing small "monotonous" changes is a less jagged form.
The Code
var initChart = function(data, container) {
new Highcharts.Chart({
chart: {
type: "area",
renderTo: container,
zoomType: 'x'
},
title: {
text: ''
},
xAxis: {
labels: {
enabled: false
}
},
yAxis: {
title: {
text: ''
}
},
legend: {
enabled: false
},
color: '#A3D8FF',
plotOptions: {
area: {
fillColor: '#C6E5F4',
lineWidth: 1,
marker: {
enabled: false
},
shadow: false,
states: {
hover: {
lineWidth: 1
}
},
threshold: null
}
},
exporting: {
enabled: false
},
series: [{
name: "TEST",
data: data
}]
});
};
Both graphs, and sample data sets are presented in the following fiddle:
http://jsfiddle.net/YKbxy/2/

The problem you're experiencing is unavoidable: If you have a lot of small variations over time, the graph is going to appear jagged provided that you show each data point.
The key point is that last phrase.
One way to 'smooth out' the rough parts would be to average the data. For example:
var myData = []; //... Some array of data; assuming just numbers
var averageData = function (data, factor) {
var i, j, results = [], sum = 0, length = data.length, avgWindow;
if (!factor || factor <= 0) {
factor = 1;
}
// Create a sliding window of averages
for(i = 0; i < length; i+= factor) {
// Slice from i to factor
avgWindow = data.slice(i, i+factor);
for (j = 0; j < avgWindow.length; j++) {
sum += avgWindow[j];
}
results.push(sum / avgWindow.length)
sum = 0;
}
return results;
};
var var initChart = function(data, container) {
new Highcharts.Chart({
series: [{
name: "TEST",
data: averageData(myData, 2)
}]
});
});
This method also has the advantage that you could (potentially) reuse the function to compare the averaged data to the regular data, or even toggle between how much to average the data.

You can always use areaspline instead of area, see: http://jsfiddle.net/YKbxy/3/

why dont you treat you .00001 data as 1, so times 10000, and then write it in your legend like that.
You should even do that as a test, since if the chart looks fine then, it means there is a problem in the dataset numbers when you return it to normal, since high charts takes the difference between high and low...

Either you must approximate your data by only using a few decimal places or you must average out the values using something like:
var data = new Array(200);
var smallArray = new Array(5);
var averagedData = new Array(20);
for (var index=0; index<=averagedData.length; index++){
for(var i = 0; i<=smallArray.length; i++){
smallArray[i] = data[i + index * 5];
}
averagedData[index] = (smallArray[1] + smallArray[2] + smallArray[3] + smallArray[4] + smallArray[5])/smallArray.length;
}
Then you will only need to plot 20 averaged points on an array of 200 data points. You can change the values for what you need.

In the end the issue is in the frequency of the points or their plotting on yAxis.
When I provide more realistic positioning (e.g timestamp) it will look good.
Meaning that jaggedness is a result of the small changes over constant yAxis progression, which is most similar to nt3rp's answer

Related

Add plotbonds to Gaps defined by GapSize in Highcharts

I am trying to add background color to gaps in data to be more visible on large intervals, I know that I can do that by adding plotbonds with the color I want, the problem is I don't have the start and end of the gap because it is created by defining the GapSize and GapUnit (no dates with null data, juste a gap in the dates).
I tried adding the plotbonds by calculating the difference between the dates and comparing it to the tickInterval but no luck so far,
here is an example of gaps set with gapsize
plotOptions: {
series: {
gapSize: 1
}
}
https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/stock/plotoptions/series-gapsize
Is there a simpler way of doing this ?
thanks
Highcharts internally adds null points to create gaps. You can get the calculated null points and based on their values, create plot-bands.
For example:
let plotBands = [];
let allowChartUpdate = true;
(function(H) {
H.wrap(H.seriesTypes.area.prototype, 'getGraphPath', function(proceed, points) {
const xAxis = this.xAxis;
plotBands = [];
points.forEach((p, index) => {
if (p.isNull) {
plotBands.push({
from: points[index - 1] ? points[index-1].x : xAxis.min,
to: points[index + 1] ? points[index+1].x : xAxis.max,
color: 'red'
});
}
});
return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
});
}(Highcharts));
Highcharts.stockChart('container', {
chart: {
type: 'area',
events: {
render: function() {
// prevent infinity loop
if (allowChartUpdate) {
allowChartUpdate = false;
this.xAxis[0].update({plotBands});
allowChartUpdate = true;
}
}
}
},
...
});
Live demo: https://jsfiddle.net/BlackLabel/jz0n28om/
API Reference: https://api.highcharts.com/highcharts/xAxis.plotBands
Docs: https://www.highcharts.com/docs/extending-highcharts/extending-highcharts

Highcharts yAxis.max manage tick intervals dynamically

I'm trying to set the max and min of y value dynamically based on the data but it is having some troubles for some values. I think the highchart couldn't set values that doesn't have a proper tick.
For eg: min: 3 and max 10. Then, the highchart won't set these values instead of that it will set a value ranging from 0...14. This is just an example value, sometimes I get 36000.14 as min and 454533.18 as max.
So my logic isn't working in that case.
This is the part of code which I currently have:
data = [3.43, 3.58, 4.86, 8.55, 8.77, 4.44, 9.67]
maximum_val = Math.ceil(maximum_val) eg: 10
minimum_val = Math.floor(minimum_val) eg: 3
evolution_options.yAxis[$('#div_count_'+div).find('#y_axis_lbl').text()] = {
min: minimum_val,
max: maximum_val,
title: false,
labels: {
style: {
color: 'black'
}
}
}
Update
Fiddle : http://jsfiddle.net/e3fy1vu9/
Further Brainstorming
I think it is because the min value isn't a multiple of the max value so Highchart couldn't calculate how to split the tick. If there was any way so I could set the max value as the multiple of the min value.
Here is my advice how to calculate the max and min for yAxis from the defined data.
Demo: https://jsfiddle.net/BlackLabel/0qwt1kx7/
yAxis: {
min: Math.floor(Math.min(...data)),
max: Math.round(Math.max(...data)),
tickAmount: Math.round(Math.max(...data)) - Math.floor(Math.min(...data)) + 1 //1 count the min or max as itself
},
Please test it and let me know if it fits to your requirements. Because the tickAmount value depends on how many values is included in the data array and probably some modifications are needed for larger data.
EDIT:
Try to use this custom solution to calculate the yAxis positions:
Demo: http://jsfiddle.net/BlackLabel/ztaj6Lkd/
var positions = [],
tickNumbers = 5,
tick = Math.floor(Math.min(...data));
for(let i = 0; i < tickNumbers; i++) {
positions.push((Math.round(tick * 100) / 100));
tick += Math.round(Math.max(...data)) / tickNumbers
}
//add the max
positions.push(Math.round(Math.max(...data)))
Hi I have fixed it by setting this alignTicks:false option.
evolution_options.yAxis[$('#div_count_'+div).find('#y_axis_lbl').text()] = {
min: minimum_val,
max: maximum_val,
title: false,
labels: {
style: {
color: 'black'
}
},
alignTicks: false
}

jqxChart with relative values

I was playing around with the waterfall series of the jqxChart.
According to its API, the following piece of code defines the values of the axis, in this case it's the y-axis:
valueAxis:
{
title: {text: 'Population<br>'},
unitInterval: 1000000,
labels:
{
formatFunction: function (value) {
return value / 1000000 + ' M';
}
}
}
Is it possible to define the intervals not with absolute values, but with relative values. So that the interval are e.g. 10% and the overall value is 100%?
Simply doing unitInterval: '10%' doesn't work.
This is how it should look like:
Here is a fiddle.
I think you're looking for these options :
logarithmicScale: true,
logarithmicScaleBase: 1.10,
Example:
valueAxis:
{
title: {text: 'Population<br>'},
logarithmicScale: true,
logarithmicScaleBase: 1.10,
labels:
{
formatFunction: function (value) {
return value / 1000000 + ' M';
}
}
},
Edit:
var accuracy = 2;
var first = data[0].population;
var last = data[data.length - 2].population;
var unit = (100 / last);
// convert raw data to differences
for (var i = 0; i < data.length - 2; i++)
data[i].population = (data[i].population * unit).toFixed(accuracy);

Highcharts grouped column labels

I have a highcharts grouped column chart with two columns for each value on the x axis. i would like to be able to add a label above each group with difference between the two in percent. I cant seem to find any way to reference the two columns in the formatter-option.
This is the section I'm having trouble with:
column: {
dataLabels: {
formatter: function()
{
return this.y;
}
}
}
Where this.y should be the difference.
This is how it is at this time http://jsfiddle.net/LLExL/4548/
All i want changed from this is a label above each of the two columns with a percent difference.
Inside the formatter callback you can use the series.chart.series[1].yData property to get the y values of the second column/series. yData is an array of all the y values. Then you can use the point.index property to get the corresponding point of the second column/series.
column: {
dataLabels: {
formatter: function()
{
var firstColumnValue = this.y;
var secondColumnValue = this.series.chart.series[1].yData[this.point.index];
var yourCalculation = (firstColumnValue - secondColumnValue) / firstColumnValue * 100;
return yourCalculation.toFixed(2) + '%';
}
}
}
Updated JSFiddle
One possibility is to pre-calculate all the differences, and simply reference them in your formatter. For example, define your series in a variable, and loop over it to create a separate diffs array of the differences, like this:
var series = [{
name: 'Omsetning',
data: [
// ...
]
}
// ...
];
var diffs = [];
for(i = 0; i < series[0].data.length; i++) {
var v1 = series[0].data[i].y;
var v2 = series[1].data[i].y;
diffs.push( (Math.abs(v1-v2) / ((v1+v2) / 2)) * 100 );
}
$('#container').highcharts({
plotOptions: {
column: {
dataLabels: {
formatter: function()
{
return Highcharts.numberFormat(diffs[this.x])+"%";
}
}
}
}
series: series
// ...
});
See this JSFiddle demonstration of how it looks.

How to access the value of a custom attribute in data from highcharts

I have this in my code
series: [{
name: 'Velocidad',
turboThreshold: 5000,
data: (function() {
// generate an array of random data
var data = [],
i;
for (i = 0; i < valor; i++) {
data.push({
x: dataUtc[i],
y: dataVel[i],
id: i
});
}
return data;
})(),
tooltip: {
valueDecimals: 0,
valueSuffix: 'Kms/H'
}
}]
When I try to get the maximum value I only get the x or y value.
I need to get the id value though.
What can I do?
I tried the following:
var chart = $('#containerVelocidad').highcharts();
min = chart.xAxis[0].min;
max = chart.xAxis[0].max;
Unfortunately that didn't help.
First of all, use yAxis.dataMax not yAxis.max. The second one is returning actual extremes, when sometimes you may have different extremes, than just from one series (zooming, setting extremes, multiple series etc.).
Demo: http://jsfiddle.net/ez4hjazk/1/
And code:
var max = chart.yAxis[0].dataMax,
maxIndex = chart.series[0].yData.indexOf(max), //get index from yData, read more about .indexOf() !
data = chart.series[0].data;
// make sure point is within actual extremes
if(maxIndex > -1) {
console.log("max = " + max, 'id = ' + data[maxIndex].id);
}
Do you need to get the maximum/minimum of id?
If so, try chart.series[0].data[i].id. Here's an example: http://jsfiddle.net/ez4hjazk/

Categories

Resources