Highcharts - Keep Zero Centered on Y-Axis with Negative Values
Asked Answered
J

7

17

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?

Joshuajoshuah answered 13/6, 2013 at 12:59 Comment(0)
J
24

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];
    },

    ...
}
Joshuajoshuah answered 13/6, 2013 at 17:57 Comment(0)
S
4

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);
        }
Scrutable answered 16/1, 2015 at 20:40 Comment(0)
U
1

You can do this with the getExtremes and setExtremes methods

example:

http://jsfiddle.net/jlbriggs/j3NTM/1/

var ext = chart.yAxis[0].getExtremes();
Urology answered 13/6, 2013 at 13:42 Comment(1)
Thanks for answering. I did find and try these methods, but in my question, I was looking for a way to do this through chart configuration without manipulating it externally. I did find an answer; see below.Joshuajoshuah
B
1

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;
  }
Benia answered 14/4, 2016 at 14:12 Comment(0)
N
0

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/

Nealah answered 13/11, 2015 at 17:33 Comment(1)
it doesnt look like it's centeredSalmi
C
0

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);
},
Cromlech answered 2/12, 2021 at 9:43 Comment(0)
N
0

as to date, this is enough:

yAxis: {
  crossing: 0
}

if you end up wanting something like an xy chart and want the axes symmetrical... inspired by the anwers above, but leverages highcharts internal calcs (like tickInterval) as much as possible)

xAxis & yAxis: {
  crossing: 0,
  startOnTick: true,
  endOnTick: true,
  tickPositioner: function () {
    // @ts-ignore
    const maxDeviation = Math.ceil(Math.max(Math.abs(this.dataMax), Math.abs(this.dataMin)))
    // @ts-ignore
    return this.getLinearTickPositions(this.tickInterval, -maxDeviation, maxDeviation)
  }
}
Neurosis answered 17/5 at 16:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.