Highcharts - Keep Zero Centered on Y-Axis with Negative Values - highcharts

I have an area chart with negative values. Nothing insanely different from the example they give, but there's one twist: I'd like to keep zero centered on the Y axis.
I know this can be achieved by setting the yAxis.max to some value n and yAxis.min to −n, with n representing the absolute value of either the peak of the chart or the trough, whichever is larger (as in this fiddle). However, my data is dynamic, so I don't know ahead of time what n needs to be.
I'm relatively new to Highcharts, so it's possible I'm missing a way to do this through configuration and let Highcharts take care of it for me, but it's looking like I'll need to use Javascript to manually adjust the y axis myself when the page loads, and as new data comes in.
Is there an easy, configuration-driven way to keep zero centered on the Y axis?

I ended up finding a way to do this through configuration after digging even further into the Highcharts API. Each axis has a configuration option called tickPositioner for which you provide a function which returns an array. This array contains the exact values where you want ticks to appear on the axis. Here is my new tickPositioner configuration, which places five ticks on my Y axis, with zero neatly in the middle and the max at both extremes :
yAxis: {
tickPositioner: function () {
var maxDeviation = Math.ceil(Math.max(Math.abs(this.dataMax), Math.abs(this.dataMin)));
var halfMaxDeviation = Math.ceil(maxDeviation / 2);
return [-maxDeviation, -halfMaxDeviation, 0, halfMaxDeviation, maxDeviation];
},
...
}

I know this is an old post, but thought I would post my solution anyway (which is inspired from the one macserv suggested above in the accepted answer) as it may help others who are looking for a similar solution:
tickPositioner: function (min, max) {
var maxDeviation = Math.ceil(Math.max(Math.abs(this.dataMax), Math.abs(this.dataMin)));
return this.getLinearTickPositions(this.tickInterval, -maxDeviation, maxDeviation);
}

You can do this with the getExtremes and setExtremes methods
http://api.highcharts.com/highcharts#Axis.getExtremes%28%29
http://api.highcharts.com/highcharts#Axis.setExtremes%28%29
example:
http://jsfiddle.net/jlbriggs/j3NTM/1/
var ext = chart.yAxis[0].getExtremes();

Here is my solution. The nice thing about this is that you can maintain the tickInterval.
tickPositioner(min, max) {
let { tickPositions, tickInterval } = this;
tickPositions = _.map(tickPositions, (tickPos) => Math.abs(tickPos));
tickPositions = tickPositions.sort((a, b) => (b - a));
const maxTickPosition = _.first(tickPositions);
let minTickPosition = maxTickPosition * -1;
let newTickPositions = [];
while (minTickPosition <= maxTickPosition) {
newTickPositions.push(minTickPosition);
minTickPosition += tickInterval;
}
return newTickPositions;
}

Just in case someone is searching,
One option more. I ended up in a similar situation. Follows my solution:
tickPositioner: function () {
var dataMin,
dataMax = this.dataMax;
var positivePositions = [], negativePositions = [];
if(this.dataMin<0) dataMin = this.dataMin*-1;
if(this.dataMax<0) dataMax = this.dataMax*-1;
for (var i = 0; i <= (dataMin)+10; i+=10) {
negativePositions.push(i*-1)
}
negativePositions.reverse().pop();
for (var i = 0; i <= (dataMax)+10; i+=10) {
positivePositions.push(i)
}
return negativePositions.concat(positivePositions);
},
http://jsfiddle.net/j3NTM/21/

It is an old question but recently I have had the same problem, and here is my solution which might be generalized:
const TICK_PRECISION = 2;
const AXIS_MAX_EXPAND_RATE = 1.2;
function setAxisTicks(axis, tickCount) {
// first you calc the max from the data, then multiply with 1.1 or 1.2
// which can expand the max a little, in order to leave some space from the bottom/top to the max value.
// toPrecision decide the significant number.
let maxDeviation = (Math.max(Math.abs(axis.dataMax), Math.abs(axis.dataMin)) * AXIS_MAX_EXPAND_RATE).toPrecision(TICK_PRECISION);
// in case it is not a whole number
let wholeMaxDeviation = maxDeviation * 10 ** TICK_PRECISION;
// halfCount will be the tick counts on each side of 0
let halfCount = Math.floor(tickCount / 2);
// look for the nearest larger number which can mod the halfCount
while (wholeMaxDeviation % halfCount != 0) {
wholeMaxDeviation++;
}
// calc the unit tick amount, remember to divide by the precision
let unitTick = (wholeMaxDeviation / halfCount) / 10 ** TICK_PRECISION;
// finally get all ticks
let tickPositions = [];
for (let i = -halfCount; i <= halfCount; i++) {
// there are problems with the precision when multiply a float, make sure no anything like 1.6666666667 in your result
let tick = parseFloat((unitTick * i).toFixed(TICK_PRECISION));
tickPositions.push(tick);
}
return tickPositions;
}
So in your chart axis tickPositioner you may add :
tickPositioner: function () {
return setAxisTicks(this, 7);
},

Related

Highcharts Stock Chart - Custom X axis date times format

I'm wondering if it's possible to define a custom ordering or format for the xAxis in stock highcharts. My dataset has a date time which would be used for the xAxis however my client has specified that it should show in the middle T-0 on the xAxis. Rest of them from the left side should be like -3m -2m -1m and from the right side +1m +2m +3m(In case of year timeframe).
Example for 1 year timeframe
I have tried using formatter function on xAxis labels. However I can not figure out how to get the middle tick first and then start chaging labels to the left and to the right from that middle position tick.
If the formatting, amount and interval of the labels is static, you can use variables from outside of the chart.
For example:
const labels = [];
let labelIndex = 0;
for (let i = -6; i < 7; i++) {
labels.push(i);
}
Highcharts.stockChart('container', {
xAxis: {
...,
labels: {
formatter: function() {
labelIndex++;
if (this.isFirst) {
labelIndex = 0;
}
const label = labels[labelIndex];
if (label < 0) {
return label + 'm';
} else if (label === 0) {
return 'T-' + label;
}
return '+' + label + 'm';
}
}
},
...
});
Live demo: http://jsfiddle.net/BlackLabel/L7uy29kw/
API Reference: https://api.highcharts.com/highstock/xAxis

In Google Sheets how can I randomize the order of a set of values?

Maybe I'm missing a keyword in my searches for a solution, but I didn't find what I'm looking for.
In Google Sheets I want to take a set of numbers and reorder it randomly. For example, start with the set [1,2,3,4] and get back [4,2,1,3].
Any ideas which function or a combination of functions may achieve this goal?
The entire process that I want to achieve is something like this:
I have a set of 4 fields. Their sum is fixed. I want to assign them randomized values.
So, I was thinking to iterate through this process:
Create a random integer between 0 and the max possible value (in the first iteration it's the fixed sum)
The new max value is the last max value minus the new random number.
Check if the new max is zero.
If not:
Return to the 1st step and repeat - This goes on until there are four values
If needed the 4th value shall be increased so the total will match the fixed sum.
Else, continue.
Randomize the order of the 4 values.
Assign the values to the 4 fields.
try:
=INDEX(SORT({{1; 2; 3; 4}, RANDARRAY(4, 1)}, 2, ),, 1)
or:
=INDEX(SORT({ROW(1:4), RANDARRAY(4, 1)}, 2, ),, 1)
Here are a couple of app script examples as well
function DiceRolls(nNumRolls) {
var anRolls = [];
nNumRolls = DefaultTo(nNumRolls, 1000)
for (var i = 1;i <= nNumRolls; i++) {
anRolls.push(parseInt((Math.random() * 6))+1);
}
return anRolls;
}
function CoinFlips(nNumFlips) {
var anFlips = [];
nNumFlips = DefaultTo(nNumFlips, 1000)
for (var i = 1;i <= nNumFlips; i++) {
anFlips.push(getRndInteger(1,2));
}
return anFlips;
}
function getRndInteger(min, max) {
return Math.floor(Math.random() * (max - min + 1) ) + min;
}

Highcharts : Set an y offset to the series

Situation : Chart with some analogic-series and some digital-series (0-1).
The problem is with the digital series. I would like to make sure that the series do not overlap like the image 1.
My idea is to set an "y-offset" on the digital-series, to have a result like the image 2.
This is a part of the y Axis configuration of the digital series. All the digital-series is related to a single y Axis (with id digital).
id : "digital",
min: 0,
max : 1,
ceiling:1,
floor : 0,
tickInterval: 1
Image 1.
Image 2.
In the documentation i can't find anything that can help me. And this is not my case.
UPDATE
Example on JSFIDDLE. Look (yes it's impossible currently) at the digital series with green color.
If you add a function in the load event, you can change the value of y in a way that makes it look offset. Like this:
chart: {
events: {
load: function() {
var series = this.series;
for (var i = 0; i < series.length; i++) {
if (series[i].yAxis.userOptions.id == "digital") {
for (var j = 0; j < series[i].data.length; j++) {
if (series[i].data[j].y == 1) {
series[i].data[j].y = 1 + 0.1 * i;
} else {
series[i].data[j].y = 0
}
}
this.update(series, true, false);
}
}
}
}
}
This sets the new y value equal to 0.1 * the series index.
Working example: https://jsfiddle.net/u2pzrhgk/27/

Highcharts: plotbands in gauge charts

I'm using plotbands in a gauge chart to represent angle ranges. But I'm facing a problem with the plotband angles when the "from" value is higher than the "to" value.
JSFiddle
As you can see, the plotband is set as from: 270, to: 45 but it really is rendered as if it was set as from: 45, to: 270. That renders exactly the oposite angle range that I need.
The only way that I can find to do that is setting two plotbands, one from 270 to 360 and another one from 0 to 45, but that seems very unconvenient.
Is there any easy way to achieve what I'm trying to do?
As I have mentioned in my comment, I think that you should be able to override getPlotBand method in your code for enabling plotBands with bigger from value than to value:
(function(H) {
H.wrap(H.Axis.prototype, 'init', function(proceed, chart, userOptions) {
this.getPlotBandPath = function(from, to, options) {
var center = this.center,
startAngleRad = this.startAngleRad,
pick = H.pick,
map = H.map,
pInt = H.pInt,
fullRadius = center[2] / 2,
radii = [
pick(options.outerRadius, '100%'),
options.innerRadius,
pick(options.thickness, 10)
],
offset = Math.min(this.offset, 0),
percentRegex = /%$/,
start,
end,
open,
isCircular = this.isCircular, // X axis in a polar chart
ret;
// Polygonal plot bands
if (this.options.gridLineInterpolation === 'polygon') {
ret = this.getPlotLinePath(from).concat(this.getPlotLinePath(to, true));
// Circular grid bands
} else {
// Keep within bounds
from = Math.max(from, this.min);
to = Math.min(to, this.max);
// Plot bands on Y axis (radial axis) - inner and outer radius depend on to and from
if (!isCircular) {
radii[0] = this.translate(from);
radii[1] = this.translate(to);
}
// Convert percentages to pixel values
radii = map(radii, function(radius) {
if (percentRegex.test(radius)) {
radius = (pInt(radius, 10) * fullRadius) / 100;
}
return radius;
});
// Handle full circle
if (options.shape === 'circle' || !isCircular) {
start = -Math.PI / 2;
end = Math.PI * 1.5;
open = true;
} else {
start = startAngleRad + this.translate(from);
end = startAngleRad + this.translate(to);
}
radii[0] -= offset; // #5283
radii[2] -= offset; // #5283
ret = this.chart.renderer.symbols.arc(
this.left + center[0],
this.top + center[1],
radii[0],
radii[0], {
start: start, // Math is for reversed yAxis (#3606)
end: end,
innerR: pick(radii[1], radii[0] - radii[2]),
open: open
}
);
}
return ret;
}
proceed.call(this, chart, userOptions);
});
}(Highcharts))
Live example:
http://jsfiddle.net/2Ljk7usL/9/

Highcharts bar chart with varied bar widths?

I want to make stacked bar chart where each portion has a width that encodes one value (say "Change" in the data below) and a height that encodes another value ("Share")
In some ways this is like a histogram with different bin sizes. There are a few "histogram" questions but none seem to address this. Plot Histograms in Highcharts
So given data like this:
Category Share Price Change
Apples 14.35 0.1314192423
Horseradish 46.168 0.1761474117
Figs 2.871 0.018874249
Tomatoes 13.954 0.0106121298
Mangoes 7.264 0.1217297011
Raisins 5.738 0.0206787136
Eggplant 6.31 0.0110160732
Other produce 3.344 0.0945377722
I can make a stacked bar that captures the "share" column in widths:
And another that captures the "change" column in heights:
And I can use an image editor to combine those into this histogram-like beast:
Which really captures that horseradish is a huge deal. So my question is, can I do that within Highcharts?
You can realise that by using snippet.
(function (H) {
var seriesTypes = H.seriesTypes,
each = H.each,
extendClass = H.extendClass,
defaultPlotOptions = H.getOptions().plotOptions,
merge = H.merge;
defaultPlotOptions.marimekko = merge(defaultPlotOptions.column, {
pointPadding: 0,
groupPadding: 0
});
seriesTypes.marimekko = extendClass(seriesTypes.column, {
type: 'marimekko',
pointArrayMap: ['y', 'z'],
parallelArrays: ['x', 'y', 'z'],
processData: function () {
var series = this;
this.totalZ = 0;
this.relZ = [];
seriesTypes.column.prototype.processData.call(this);
each(this.zData, function (z, i) {
series.relZ[i] = series.totalZ;
series.totalZ += z;
});
},
translate: function () {
var series = this,
totalZ = series.totalZ,
xAxis = series.xAxis;
seriesTypes.column.prototype.translate.call(this);
// Distort the points to reflect z dimension
each(this.points, function (point, i) {
var shapeArgs = point.shapeArgs,
relZ = series.relZ[i];
shapeArgs.x *= (relZ / totalZ) / (shapeArgs.x / xAxis.len);
shapeArgs.width *= (point.z / totalZ) / (series.pointRange / series.xAxis.max);
});
}
});
}(Highcharts));
Example: http://jsfiddle.net/highcharts/75oucp3b/

Resources