set minimum and maximum values for axis' data in highchart - javascript

I want to set minimum and maximum values in an axis in highcharts. I have tried min, max, ceiling and floor attributes. They change the range of axis.
Link to the JS Fiddle
yAxis: {
floor: 0,
ceiling: 40,
title: {
text: 'Percentage'
}
},
series: [{
data: [0, 1, -5, 2, 3, 5, 8, 5, 50, 14, 25, 54]
}]
I want the data to be modified automatically based on the minimum/floor and maximum/ceiling values declared in the axis declaration.
For example, if in the above mentioned case, the last and last 4th elements of series should be automatically modified to 40 and the 3rd element to 0.
Is there any attribute of highchart using which I can achieve this without manually checking all the series elements to fall between the min and max values?

No I don't think there is no such a function. Highcharts does not alter your data, it just changes what is displayed. But checking for values beneath or above certain thresholds is really simple:
var data = [0, 1, -5, 2, 3, 5, 8, 5, 50, 14, 25, 54];
var max = 40;
var min = 0;
// clamping "by hand"
console.log( data.map(function(d) { return Math.max(min, Math.min(max, d)); }) );
If you use a library like lodash: these often provide a clamp function so you could write
console.log( _.map(data, function(d) { return _.clamp(d, min, max) }

Related

Flatten curve of array of numbers

I'm trying to find the technique of Normalising (not sure if that is the right word) a range of numbers.
Say I have an array:
[0, 1, 2, 3, 4, 70, 80, 900]
I want to flatten or average out the range curve, so it's more like:
[10, 11, 12, 13, 14, 50, 100, 300]. // not a real calculation
So increasing the smaller numbers in relation to reducing the larger numbers.
What is this technique called? Normalised scale? I wish to implement this in some Javascript.
UPDATE: Here is hopefully a better description of what I'm trying to do:
I have an original array of numbers:
[0, 10, 15, 50, 70, 100]
When processed through a function averageOutAllNumbers(array, slider) will produce and array that when slider is set to 100% looks like:
[0, 20, 40, 60, 80, 100] // the curve has been flattened
when slider is set to 0%, it will return the original array. If slider is set to 50%, the returned array will look something like:
[0, 12, 19, 52, 88, 100] // the curve is less steep [do not take these number literally, I'm guess what the output would be based on the slider].
the array.max() will alway be 100.
Thanks for the comments so far, they did point me closer to the solution.
No thanks to the trolls who down-voted; if you can't fix it no one can—right!
When I updated my question I realised that "increasing the smaller numbers in relation to reducing the larger numbers" would eventually lead to an evenly distributed set of numbers, e.g. [20, 20, 20, 20, 20]. However I did actually want something like I stated in the question: [0, 20, 40, 60, 80, 100] // the curve has been flattened. I did some more searching for things like:
Evenly space an array of oddly spaced numbers
Making a list of evenly spaced numbers in a certain range
Return evenly spaced numbers over a specified interval
Find what percent X is between two numbers
Amongst the list of search result I saw the answer to my original question: "What is this technique called?" Linear Interpolation
Based on that I was able to create the following:
var orig = [3, 11, 54, 72, 100];
function lerp(n, x0, x1) {
// returns a position: x that is n percent between y0 and y1
// As numbers in array are x only, y values are fixed to 0(start) - 1(end)
const y0 = 0;
const y1 = 1;
const x = ((y1 - n)*x0 + (n - y0)*x1) / (y1 - y0);
return x;
}
var flattenedEven = orig.map((value, index, arr) => {
return lerp(1, value, (Math.max(...arr)/arr.length) * (index + 1));
});
//=> [20, 40, 60, 80, 100]
var flattenedCurve = orig.map((value, index, arr) => {
return lerp(.7, value, (Math.max(...arr)/arr.length) * (index + 1));
});
//=> [14.9, 31.3, 58.2, 77.6, 100]

How to make mathematical chart with HighCharts?

Hi there, I'm doing a web project which aims to replace a back-end chart renderer with front-end chart renderer. The front-end charting library I'm using is HighCharts. The 1st image shows what the chart supposed to look, and the 2nd is the chart rendered by HighCharts based on same data. As you can see, at point 3 & 4 (count from right to left), since their values are equal, the line between them are considered horizontal, which is different in image 1 as desired.
Is there anyway we can use HighCharts to achieve the first image - like chart? Cheers.
In general, when two points have the same value, then line will be rendered as straight one. You can change that by wrapping getPointSpline. Here is simple POC:
(function(H) {
H.wrap(H.seriesTypes.spline.prototype, 'getPointSpline', function(p, points, point, i) {
var path = p.call(this, points, point, i),
offset = -10;
if (points[i - 1] && points[i - 1].y === point.y) {
path[2] += offset;
path[4] += offset;
}
return path;
});
})(Highcharts)
And working demo: http://jsfiddle.net/99w72efv/8/
Highcharts splice type series doesn't provide the kind of line approximation you are looking for. You could add more data points (that might be hidden) to get the shape that you are looking for.
Example: http://jsfiddle.net/99w72efv/9/
$(function() {
$('#container').highcharts({
chart: {
type: 'spline'
},
yAxis: {max: 3},
series: [{
data: [2, 1, 0, 1, 3, 1, 2, 3, 0, 1, 2, 3, {x: 11.5, y: 3.2, marker: {enabled:false}}, 3, 1, 2, {x: 14.5, y: 2.2, marker: {enabled:false}}, 2, 0, 1]
}]
});
});

How to plot time data on a D3.js line graph

I have a ton of data associated with time. I want to plot a, hopefully, simple line graph. A simplified example:
var data = [{ time: 13:30, size: 100 }, { time: 13:37, size: 500}, { time: 13: 42, size: 300 }
{ time: 13:51, size: 150 }, { time: 13:56, size: 175 }, { time: 15:59, size: 75 }
{ time: 16:11, size: 75 }, { time: 16:37, size: 125 }, { time: 15:27, size: 200 } [...]
{ time: 20:36, size: 500 }]
Many time values, and corresponding size values. Of course, I do not want to plot every single time value on this graph. Instead I want to take the lowest value, 13:30, then plot the time on the axes at 30 minute intervals (i.e. 13:30, 14:00, 14:30 ... 20:30) until I hit the latest time in my array of data. At the same time, I want to plot every size value. So, the graph will only display a tick for 13:30 and 14:00, but the values that correspond with 13.37, 13.42, 13.51, 13.56 will be plotted on the line.
How would I go about doing this?
My first attempt was something like this:
var graph = d3.select('.graph'),
WIDTH = 790,
HEIGHT = 250,
MARGINS = {
top: 20,
right: 20,
bottom: 20,
left: 50
}
xScale = d3.scale.linear().range([MARGINS.left, WIDTH - MARGINS.right]),
yScale = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([smallest, largest]),
xAxis = d3.svg.axis().scale(xScale),
yAxis = d3.svg.axis().scale(yScale).orient("left");
NB the "smallest" and "largest" variables are the smallest and largest size values in my data array, calculated elsewhere. Obviously this is wrong because this would plot every single time value on the axes (as explained above, I do not want this) and even if I did it errors and glitches horribly.
How would I go about achieving what I want to achieve?
Thx
-- Gaweyne
Solved it. d3.time.scale not d3.scale.linear()
xScale = d3.time.scale(range([MARGINS.left, WIDTH - MARGINS.right]).domain(new Date([//Earliest Date//]), new Date([//Latest Date//]))

How to add 2nd horizontal x axis scale to jqplot and customize axis settings?

Note: Although this is a self-answered question, I am always curious about better approaches.
sin(x) and cos(x) for x in degrees.
Goal: I would like to adjust this initial plot and add a 2nd X axis to show both degrees and radians.
How do I set up jqPlot PlotOptions to add x and y labels, change scales, and add a second X axis?
I am using a JavaScript library that I wrote called html5csv [License: GPL] that support various data analysis operations and interfaces to jqPlot for plotting. It allows the specification of the jqPlot plotOptions object for each plot.
The (currently blank) plotOptions are on the first line of code. You may assume the plotOptions are correctly delivered to jqPlot by the subsequent code invoking CSV().jqplot() from the html5csv library.
html5csv + jqplot dual line graph without special axes
plotOptions = {};
CSV.begin('%F', {dim:[36,4],header:['deg','rad','sin','cos'],
func: function(i,j){
var deg = 10*(i);
var rad = deg*2*Math.PI/360.0;
if (j===0) return deg;
if (j===1) return rad;
if (j===2) return Math.sin(rad);
if (j===3) return Math.cos(rad);
}
}).
jqplot([['chart1',[['deg','sin'],['deg','cos']], plotOptions]]).
table('tab1',{header:1}).
go();
jsfiddle of single axes sine, cosine wave plot
This jqPlot documentation shows up to 2 X axes and 9 Y axes but when calling new Axis() I get Uncaught ReferenceError: Axis is not defined in the console. To fix this I tried adding more of the jqplot .js files to the script headers but it did not help.
jqplot Axis formatting options documentation shows all the options to configure axis labels, ticks, etc. for a particular axis if I could create one.
How do I proceed from here?
Don't call new Axis(); This is done for you internally in jqPlot.
Basically, if you declare the right keys in plotOptions, the Axis will be set up for you. But if the keys are missing or misnamed, it will obviously fail.
Here are the finished examples:
Part 1: Customized set of single axes
Output
jqplot PlotOptions Input
plotOptions = {
axes: {
xaxis: {
show: true,
label: 'deg',
min: 0,
max: 360,
tickInterval: 45
},
yaxis: {
show: true,
label: 'y',
min: -2.0,
max: 2.0
}
}
};
Note: You don't need to call new Axis, but you do need to name the object fields as shown.
plotOptions = {
axes: {
xaxis: {
show: true,
label: 'deg',
min: 0,
max: 360,
tickInterval: 45
},
yaxis: {
show: true,
label: 'y',
min: -2.0,
max: 2.0
}
}
};
CSV.begin('%F', {
dim: [36, 4],
header: ['deg', 'rad', 'sin', 'cos'],
func: function(i, j) {
var deg = 10 * (i);
var rad = deg * 2 * Math.PI / 360.0;
if (j === 0) return deg;
if (j === 1) return rad;
if (j === 2) return Math.sin(rad);
if (j === 3) return Math.cos(rad);
}
}).
jqplot([
['chart1', [
['deg', 'sin'],
['deg', 'cos']
], plotOptions]
]).
table('tab1', {
header: 1
}).
go();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.9/jquery.jqplot.css" />
<script src="https://cdn.rawgit.com/DrPaulBrewer/html5csv/7f39da16/html5csv.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.9/jquery.jqplot.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.9/plugins/jqplot.canvasAxisLabelRenderer.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.9/plugins/jqplot.canvasAxisTickRenderer.js"></script>
Part 2: Dual X axes, with degrees and radians scales
Output
jqPlot PlotOptions Input
plotOptions = {
axes: {
xaxis: {
show: true,
label: 'deg',
min: 0,
max: 360,
tickInterval: 45
},
x2axis: {
show: true,
label: 'rad',
min: 0,
max: 2 * Math.PI,
numberTicks: 9
},
yaxis: {
show: true,
label: 'y',
min: -2.0,
max: 2.0
}
}
};
plotOptions = {
axes: {
xaxis: {
show: true,
label: 'deg',
min: 0,
max: 360,
tickInterval: 45
},
x2axis: {
show: true,
label: 'rad',
min: 0,
max: 2 * Math.PI,
numberTicks: 9
},
yaxis: {
show: true,
label: 'y',
min: -2.0,
max: 2.0
}
}
};
CSV.begin('%F', {
dim: [36, 4],
header: ['deg', 'rad', 'sin', 'cos'],
func: function(i, j) {
var deg = 10 * (i);
var rad = deg * 2 * Math.PI / 360.0;
if (j === 0) return deg;
if (j === 1) return rad;
if (j === 2) return Math.sin(rad);
if (j === 3) return Math.cos(rad);
}
}).
jqplot([
['chart1', [
['deg', 'sin'],
['deg', 'cos']
], plotOptions]
]).
table('tab1', {
header: 1
}).
go();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.9/jquery.jqplot.css" />
<script src="https://cdn.rawgit.com/DrPaulBrewer/html5csv/7f39da16/html5csv.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.9/jquery.jqplot.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.9/plugins/jqplot.canvasAxisLabelRenderer.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.9/plugins/jqplot.canvasAxisTickRenderer.js"></script>
Notes: No need to call new Axis() here, either. Name the plotOptions keys properly and it works.
Plotting of data used the original single X coordinate using the 1st X axis.
Reference
From the jqPlot Axis docs:
Axes options are specified within an axes object at the top level of the plot options like so:
{
axes: {
xaxis: {min: 5},
yaxis: {min: 2, max: 8, numberTicks:4},
x2axis: {pad: 1.5},
y2axis: {ticks:[22, 44, 66, 88]}
}
}
There are 2 x axes, ‘xaxis’ and ‘x2axis’, and 9 yaxes, ‘yaxis’, ‘y2axis’. ‘y3axis’, ... Any or all of which may be specified.
Useful axis options excerpted from the documentation
Note: Additional options do exist. These are the most basic ones.
In a few of these I edited slightly for clarity.
show
true to display the axis on the graph.
label
Label for the axis
showLabel
true to show the axis label.
min
minimum value of the axis (in data units, not pixels).
max
maximum value of the axis (in data units, not pixels).
autoscale
true to Autoscale the axis min and max values to provide sensible tick spacing.
If axis min or max are set, autoscale will be turned off. The numberTicks, tickInterval and pad options do work with autoscale, although tickInterval has not been tested yet. padMin and padMax do nothing when autoscale is on.
ticks
1D [val, val, ...] or 2D [[val, label], [val, label], ...] array of ticks for the axis. If no label is specified, the value is formatted into an appropriate label.
numberTicks
Desired number of ticks. Default is to compute automatically.
tickInterval
number of units between ticks. Mutually exclusive with numberTicks.
showTicks
true to show the ticks (both marks and labels) or not. Will not override showMark and showLabel options if specified on the ticks themselves.
showTickMarks
true to show the tick marks (line crossing grid) or not. Overridden by showTicks and showMark option of tick itself.
syncTicks
true to try and synchronize tick spacing across multiple axes so that ticks and grid lines line up. This has an impact on autoscaling algorithm, however. In general, autoscaling an individual axis will work better if it does not have to sync ticks.
tickSpacing
A number giving approximate pixel spacing between ticks on graph. Used during autoscaling. This number will be an upper bound, actual spacing will be less.

Optimal algorithm for segmenting set of integers into labels for a chart axis?

Say you get values anywhere from 0 to 1,000,000,000, and you want to plot 30 days. So one particular chart may have a set like:
[ 1, 465, 123, 9, ... ]
While another chart can have a set with much larger numbers:
[ 761010, 418781, ... ]
Is there an "optimal algorithm" that can take those values and segment them into "clean" numbers? Sorry for the wording, don't know the right terminology, I will try to explain.
By "optimal algorithm", I mean both in terms of minimum number of computational steps, given that it creates labels (say for the y-axis) that are simplest from a human perspective.
For example, say you always want to divide the y-axis into 5 labels. You could do this:
var max = Math.max.apply(Math, values); // 465 (from the first set of values)
var interval = max / 5;
var labels = [ interval * 0, interval * 1, interval * 2, ... ];
But that creates labels like:
[ 0, 93, 186, ... ]
And that would be complex for humans to understand. What would be better (but still not ideal) is to create labels like:
[ 0, 125, 250, 375, 500 ]
But that's still to specific. Somehow it should figure out that a better segmentation is:
[ 0, 200, 400, 600, 800 ]
That way, it's divided into more intuitive chunks.
Is there a standard way to solve this problem? What algorithm works best?
Some maths
var getLabelWidth = function(sep, max_value){
var l = (""+max_value).length;
var av = max_value/sep/Math.pow(10,l-2); // get the length max 2 digit
/// 15.22
var width = (Math.ceil(av)*Math.pow(10,l-2)); // do a ceil on the value retrieved
// and apply it to the width of max_value.
// 16 * 10 000
return width;
}
console.log(getLabelWidth(2,59)); // 30 : [0, 30, 60]
console.log(getLabelWidth(2,100)); // 50 : [0, 50, 100]
console.log(getLabelWidth(2,968)); // 490 : [0, 490, 980]
console.log(getLabelWidth(3,368)); // 130 : [0, 130, 260, 390]
console.log(getLabelWidth(3,859)); // 290 : [0, 290, 580, 870]
console.log(getLabelWidth(3,175)); // 60 : [0, 60, 120, 180]
console.log(getLabelWidth(3,580)); // 200 : [0, 200, 400, 600]
console.log(getLabelWidth(3,74)); // 25 : [0, 25, 50, 75]
console.log(getLabelWidth(4,1111)); // 300 :[0, 300, 600, 900, 1200]
console.log(getLabelWidth(4,761010)); // 200 000: [0, 200000, 400000, 600000, 800000]
It could be improved a little bit i guess,
sorry for my bad english .
For reference, here's what I ended up doing.
function computeLabels(count, max) {
var magnitude = orderOfMagnitude(max);
var multiplier = magnitude * count;
// 1
if (multiplier >= max) return buildLabels(count, multiplier);
// 2
multiplier *= 2;
if (multiplier >= max) return buildLabels(count, multiplier);
// 5
multiplier *= 5;
if (multiplier >= max) return buildLabels(count, multiplier);
// 10, don't think it will ever get here but just in case.
multiplier *= 10;
if (multiplier >= max) return buildLabels(count, multiplier);
}
function buildLabels(count, multiplier) {
var labels = new Array(count);
while (count--) labels[count] = formatLabel(count * multiplier);
return labels;
}
function formatLabel(value) {
if (value > 10e5) return (value / 10e5) + 'M'; // millions
if (value > 10e2) return (value / 10e2) + 'K'; // thousands
return value; // <= hundreds
}
function orderOfMagnitude(val) {
var order = Math.floor(log10(val) + 0.000000001);
return Math.pow(10, order);
}
After drawing it out on paper, the "desirable" labels seemed to follow a simple pattern:
Find the max value in the set.
Get the order of magnitude for it.
Multiply the order of magnitude by the number of ticks.
Iterate: If that previous calculation is greater than the max value, then use it. Otherwise, multiply the value times 2 and check. If not, try times 5. So the pattern is, 1, 2, 5.
This gives you labels that are like:
10, 20 (2 ticks)
20, 40
50, 100
100, 200
200, 400
500, 1000
...
10, 20, 30 (3 ticks)
20, 40, 60
50, 100, 150 (don't like this one too much but oh well)
100, 200, 300
10, 20, 30, 40 (4 ticks)
...
It seems like it can be improved, both in producing better quality "human readable" labels, and in using more optimized functionality, but don't quite see it yet. This works for now.
Would love to know if you find a better way!

Categories

Resources