Set min/max for each range handle in jQuery UI slider? - jquery-ui

I'm using a jQuery slider where users can select a time range between 00:00 and 1d+12:00. 36 hours all together.
Anyway.
I would like to apply min and max values to my handles based on what they're set to. These are my requirements:
left handle can never go over midnight on the next day (max is 24 hours)
left handle can never go more left than -24 hours from right handle (min is right handle value minus 24 hours)
right handle can never go more than +24 hours from the left handle (max is left handle value plus 24 hours)
As I understand, minimum and maximum values can only be applied to single handle slider control and not to range slider?
Is it possible to set minimums and maximums individually to both handles?
I've tried initializing it this way but no luck:
$(".timing-slider", timing).slider({
range: true,
min: [0, 0],
max: [24, 36],
}

This jQuery UI slider extension satisfies all upper requirements
I've managed to change default jQuery UI slider to include a few more configuration properties:
minRangeSize - sets minimum range size so ranges can't be narrower than this setting
maxRangeSize - sets maximum range size so ranges can't be wider than this setting
autoShift - when set to true it automatically drags the other handle along when range width reaches maximum; when set to false handle just can't be moved beyond maximum range width
lowMax - sets the lower handle upper boundary so it's impossible to set lower handle beyond this value
topMin - sets the upper handle lower boundary so it's impossible to set upper handle below this value
This is a working example of such range slider.
This is the extra code that has to be run after jQuery slider. It actually rewrites one of its internal functions to also check the new settings. This code will only change slider code when slider script has been loaded (hence the first if statement that checks whether slider widget has been loaded):
(function ($) {
if ($.ui.slider)
{
// add minimum range length option
$.extend($.ui.slider.prototype.options, {
minRangeSize: 0,
maxRangeSize: 100,
autoShift: false,
lowMax: 100,
topMin: 0
});
$.extend($.ui.slider.prototype, {
_slide: function (event, index, newVal) {
var otherVal,
newValues,
allowed,
factor;
if (this.options.values && this.options.values.length)
{
otherVal = this.values(index ? 0 : 1);
factor = index === 0 ? 1 : -1;
if (this.options.values.length === 2 && this.options.range === true)
{
// lower bound max
if (index === 0 && newVal > this.options.lowMax)
{
newVal = this.options.lowMax;
}
// upper bound min
if (index === 1 && newVal < this.options.topMin)
{
newVal = this.options.topMin;
}
// minimum range requirements
if ((otherVal - newVal) * factor < this.options.minRangeSize)
{
newVal = otherVal - this.options.minRangeSize * factor;
}
// maximum range requirements
if ((otherVal - newVal) * factor > this.options.maxRangeSize)
{
if (this.options.autoShift === true)
{
otherVal = newVal + this.options.maxRangeSize * factor;
}
else
{
newVal = otherVal - this.options.maxRangeSize * factor;
}
}
}
if (newVal !== this.values(index))
{
newValues = this.values();
newValues[index] = newVal;
newValues[index ? 0 : 1] = otherVal;
// A slide can be canceled by returning false from the slide callback
allowed = this._trigger("slide", event, {
handle: this.handles[index],
value: newVal,
values: newValues
});
if (allowed !== false)
{
this.values(index, newVal, true);
this.values((index + 1) % 2, otherVal, true);
}
}
} else
{
if (newVal !== this.value())
{
// A slide can be canceled by returning false from the slide callback
allowed = this._trigger("slide", event, {
handle: this.handles[index],
value: newVal
});
if (allowed !== false)
{
this.value(newVal);
}
}
}
}
});
}
})(jQuery);

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

Rounding a Duration to the nearest second based on desired precision

I recently started working with Dart, and was trying to format a countdown clock with numbers in a per-second precision.
When counting down time, there's often a precise-yet-imperfect way of representing the time - so if I started a Duration at 2 minutes, and asked to show the current time after one second has elapsed, it is almost guaranteed that the precision of the timer will report at 1:58:999999 (example), and if use Duration.inSeconds() to emit the value, it will be 118 (seconds) which is due to how the ~/ operator works, since it's rounding down to integers based on the Duration's microseconds.
If I render the value as a clock, I'll see the clock go from "2:00" to "1:58" after one second, and will end up displaying "0:00" twice, until the countdown is truly at 0:00:00.
As a human, this appears like the clock is skipping, so I figured since the delta is so small, I should round up to the nearest second, and that would be accurate enough for a countdown timer, and handle the slight imprecision measured in micro/milli-seconds to better serve the viewer.
I came up with this secondRounder approach:
Duration secondRounder(Duration duration) {
int roundedDuration;
if (duration.inMilliseconds > (duration.inSeconds * 1000)) {
roundedDuration = duration.inSeconds + 1;
} else {
roundedDuration = duration.inSeconds;
}
return new Duration(seconds: roundedDuration);
}
This can also be run in this DartPad: https://dartpad.dartlang.org/2a08161c5f889e018938316237c0e810
As I'm yet unfamiliar with all of the methods, I've read through a lot of the docs, and this is the best I've come up with so far. I think I was looking for a method that might looks like:
roundedDuration = duration.ceil(nearest: millisecond)
Is there a better way to go about solving this that I haven't figured out yet?
You can "add" your own method to Duration as an extension method:
extension RoundDurationExtension on Duration {
/// Rounds the time of this duration up to the nearest multiple of [to].
Duration ceil(Duration to) {
int us = this.inMicroseconds;
int toUs = to.inMicroseconds.abs(); // Ignore if [to] is negative.
int mod = us % toUs;
if (mod != 0) {
return Duration(microseconds: us - mod + toUs);
}
return this;
}
}
That should allow you to write myDuration = myDuration.ceil(Duration(seconds: 1)); and round the myDuration up to the nearest second.
The best solution according to the documentation is to use .toStringAsFixed() function
https://api.dart.dev/stable/2.4.0/dart-core/num/toStringAsFixed.html
Examples from the Documentation
1.toStringAsFixed(3); // 1.000
(4321.12345678).toStringAsFixed(3); // 4321.123
(4321.12345678).toStringAsFixed(5); // 4321.12346
123456789012345678901.toStringAsFixed(3); // 123456789012345683968.000
1000000000000000000000.toStringAsFixed(3); // 1e+21
5.25.toStringAsFixed(0); // 5
Another more flexible option can be...
You can use this function to roundup the time.
DateTime alignDateTime(DateTime dt, Duration alignment,
[bool roundUp = false]) {
assert(alignment >= Duration.zero);
if (alignment == Duration.zero) return dt;
final correction = Duration(
days: 0,
hours: alignment.inDays > 0
? dt.hour
: alignment.inHours > 0
? dt.hour % alignment.inHours
: 0,
minutes: alignment.inHours > 0
? dt.minute
: alignment.inMinutes > 0
? dt.minute % alignment.inMinutes
: 0,
seconds: alignment.inMinutes > 0
? dt.second
: alignment.inSeconds > 0
? dt.second % alignment.inSeconds
: 0,
milliseconds: alignment.inSeconds > 0
? dt.millisecond
: alignment.inMilliseconds > 0
? dt.millisecond % alignment.inMilliseconds
: 0,
microseconds: alignment.inMilliseconds > 0 ? dt.microsecond : 0);
if (correction == Duration.zero) return dt;
final corrected = dt.subtract(correction);
final result = roundUp ? corrected.add(alignment) : corrected;
return result;
}
and then use it the following way
void main() {
DateTime dt = DateTime.now();
var newDate = alignDateTime(dt,Duration(minutes:30));
print(dt); // prints 2022-01-07 15:35:56.288
print(newDate); // prints 2022-01-07 15:30:00.000
}

Shorthand comparison ends up being too long to understand

There's a "Clamp" function from a library of Ray Wenderlich class's - SKTUtils to be exact. This clamp function is written in shorthand but in a way that I can't seem to understand. This clamps purpose is to limit a position to an area - the games "camera" follows the _player.position, while making sure the the player never sees the nothingness outside the game map. Here's the function:
CGFloat Clamp(CGFloat value, CGFloat min, CGFloat max)
{
return value < min ? min : value > max ? max : value;
}
Here is the method it's used in, which the method it self gets used inside 'didFinishUpdate' method:
-(CGPoint)pointToCenterViewOn:(CGPoint)centerOn
{
CGFloat x = Clamp(centerOn.x, self.size.width/2, _backgroundLayer.layerSize.width - self.size.width/2); //Value, Min, Max.
CGFloat y = Clamp(centerOn.y, self.size.height/2, _backgroundLayer.layerSize.height - self.size.height/2);
return CGPointMake(-x, -y);
}
-(void)didFinishUpdate
{
_worldNode.position = [self centerViewOnPoint:_player.position];
}
Can someone explain this?
value < min ? min : value > max ? max : value
I could only partially understand the shorthands beginning:
if (value < min)
{
value = min;
}
else if (value > min)
{
value > max??????
}
Here is the explanation of value < min ? min : value > max ? max : value
if (value < min)
{
return min
}
else
{
if (value > max)
{
return max
}
else
{
return value
}
}
Operator precedence is partially involved here. This would be made a lot nicer with some parentheses to aid reading. The comparison operators bind tighter than the ternary conditional, so you have:
(value < min) ? min : ((value > max) ? max : value)
From there it's just evaluated left-to-right. The only tricky bit is that the else branch of the first conditional operator is itself another conditional operator. This would be the equivalent of an else if were you to expand it. The else branch of the second conditional is thus the else for the whole expression.
To convert this to if statements, then, you would do:
CGFloat retVal;
if( value < min ){
retVal = min;
}
else if( value > max ){
retVal = max;
}
else {
retVal = value;
}
return retVal;
You might also prefer this way to clamp a value:
MAX(min_limit, MIN(value, max_limit))
which uses the MAX and MIN macros to evaluate to the lower of max_limit or value and the higher of that or min_limit, producing a result in the range between min_limit and max_limit (inclusive). The effect is the same; I think that's easier to read.

stopping anchor scrolling on key press using smoothscroll.js

I'm trying to get a nice easing scroll effect on my site using smoothscroll.js from http://cferdinandi.github.io/smooth-scroll/ (shown as code below) and it works fine. However, scrolling with mousewheel while that's in effect causes the page to jitter all over the place because there are two animations taking place - the one from the the mousewheel and the other from the anchor scroll. So I'd like to either disable mousewheel on the anchor scroll animation or disable the anchor scroll animation on mousewheel. I'm not sure how to alter the code to do that. I'm pretty sure I should just be able to add a line or two to the code below but I've been trying for many hours and I can't get anything to work.
/* =============================================================
Smooth Scroll 3.2
Animate scrolling to anchor links, by Chris Ferdinandi.
http://gomakethings.com
Easing support contributed by Willem Liu.
https://github.com/willemliu
Easing functions forked from Gaëtan Renaudeau.
https://gist.github.com/gre/1650294
URL history support contributed by Robert Pate.
https://github.com/robertpateii
Fixed header support contributed by Arndt von Lucadou.
https://github.com/a-v-l
Infinite loop bugs in iOS and Chrome (when zoomed) by Alex Guzman.
https://github.com/alexguzman
Free to use under the MIT License.
http://gomakethings.com/mit/
* ============================================================= */
window.smoothScroll = (function (window, document, undefined) {
'use strict';
// Feature Test
if ( 'querySelector' in document && 'addEventListener' in window && Array.prototype.forEach ) {
// SELECTORS
var scrollToggles = document.querySelectorAll('[data-scroll]');
// METHODS
// Run the smooth scroll animation
var runSmoothScroll = function (anchor, duration, easing, url) {
// SELECTORS
var startLocation = window.pageYOffset;
// Get the height of a fixed header if one exists
var scrollHeader = document.querySelector('[data-scroll-header]');
var headerHeight = scrollHeader === null ? 0 : scrollHeader.offsetHeight;
// Set the animation variables to 0/undefined.
var timeLapsed = 0;
var percentage, position;
// METHODS
// Calculate the easing pattern
var easingPattern = function (type, time) {
if ( type == 'easeInQuad' ) return time * time; // accelerating from zero velocity
if ( type == 'easeOutQuad' ) return time * (2 - time); // decelerating to zero velocity
if ( type == 'easeInOutQuad' ) return time < 0.5 ? 2 * time * time : -1 + (4 - 2 * time) * time; // acceleration until halfway, then deceleration
if ( type == 'easeInCubic' ) return time * time * time; // accelerating from zero velocity
if ( type == 'easeOutCubic' ) return (--time) * time * time + 1; // decelerating to zero velocity
if ( type == 'easeInOutCubic' ) return time < 0.5 ? 4 * time * time * time : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; // acceleration until halfway, then deceleration
if ( type == 'easeInQuart' ) return time * time * time * time; // accelerating from zero velocity
if ( type == 'easeOutQuart' ) return 1 - (--time) * time * time * time; // decelerating to zero velocity
if ( type == 'easeInOutQuart' ) return time < 0.5 ? 8 * time * time * time * time : 1 - 8 * (--time) * time * time * time; // acceleration until halfway, then deceleration
if ( type == 'easeInQuint' ) return time * time * time * time * time; // accelerating from zero velocity
if ( type == 'easeOutQuint' ) return 1 + (--time) * time * time * time * time; // decelerating to zero velocity
if ( type == 'easeInOutQuint' ) return time < 0.5 ? 16 * time * time * time * time * time : 1 + 16 * (--time) * time * time * time * time; // acceleration until halfway, then deceleration
return time; // no easing, no acceleration
};
// Update the URL
var updateURL = function (url, anchor) {
if ( url === 'true' && history.pushState ) {
history.pushState( {pos:anchor.id}, '', '#' + anchor.id );
}
};
// Calculate how far to scroll
var getEndLocation = function (anchor) {
var location = 0;
if (anchor.offsetParent) {
do {
location += anchor.offsetTop;
anchor = anchor.offsetParent;
} while (anchor);
}
location = location - headerHeight;
if ( location >= 0 ) {
return location;
} else {
return 0;
}
};
var endLocation = getEndLocation(anchor);
var distance = endLocation - startLocation;
// Stop the scrolling animation when the anchor is reached (or at the top/bottom of the page)
var stopAnimation = function () {
var currentLocation = window.pageYOffset;
if ( position == endLocation || currentLocation == endLocation || ( (window.innerHeight + currentLocation) >= document.body.scrollHeight ) ) {
clearInterval(runAnimation);
}
};
// Scroll the page by an increment, and check if it's time to stop
var animateScroll = function () {
timeLapsed += 16;
percentage = ( timeLapsed / duration );
percentage = ( percentage > 1 ) ? 1 : percentage;
position = startLocation + ( distance * easingPattern(easing, percentage) );
window.scrollTo( 0, position );
stopAnimation();
};
// EVENTS, LISTENERS, AND INITS
updateURL(url, anchor);
var runAnimation = setInterval(animateScroll, 16);
};
// Check that anchor exists and run scroll animation
var handleToggleClick = function (event) {
// SELECTORS
// Get anchor link and calculate distance from the top
var dataID = this.getAttribute('href');
var dataTarget = document.querySelector(dataID);
var dataSpeed = this.getAttribute('data-speed');
var dataEasing = this.getAttribute('data-easing');
var dataURL = this.getAttribute('data-url');
// EVENTS, LISTENERS, AND INITS
event.preventDefault();
if (dataTarget) {
runSmoothScroll( dataTarget, dataSpeed || 500, dataEasing || 'easeInOutCubic', dataURL || 'false' );
}
};
// EVENTS, LISTENERS, AND INITS
// When a toggle is clicked, run the click handler
Array.prototype.forEach.call(scrollToggles, function (toggle, index) {
toggle.addEventListener('click', handleToggleClick, false);
});
// Return to the top of the page when back button is clicked and no hash is set
window.onpopstate = function (event) {
if ( event.state === null && window.location.hash === '' ) {
window.scrollTo( 0, 0 );
}
};
}
})(window, document);
I figured it out - I added
$("html").bind("scroll mousedown DOMMouseScroll mousewheel keyup", function(){
clearInterval(runAnimation);
});
directly under
var runSmoothScroll = function (anchor, duration, easing, url) {
as runSmoothScroll is the function called to run the scrolling animation. So whenever that function is called, it adds the function for ending the animation, clearInterval(runAnimation) to the mousewheel. I also figured out a way to get the script to temporarily disable mousewheel.
/* =============================================================
Smooth Scroll 3.2
Animate scrolling to anchor links, by Chris Ferdinandi.
http://gomakethings.com
Easing support contributed by Willem Liu.
https://github.com/willemliu
Easing functions forked from Gaëtan Renaudeau.
https://gist.github.com/gre/1650294
URL history support contributed by Robert Pate.
https://github.com/robertpateii
Fixed header support contributed by Arndt von Lucadou.
https://github.com/a-v-l
Infinite loop bugs in iOS and Chrome (when zoomed) by Alex Guzman.
https://github.com/alexguzman
Free to use under the MIT License.
http://gomakethings.com/mit/
* ============================================================= */
window.smoothScroll = (function (window, document, undefined) {
'use strict';
// Feature Test
if ( 'querySelector' in document && 'addEventListener' in window && Array.prototype.forEach ) {
// SELECTORS
var scrollToggles = document.querySelectorAll('[data-scroll]');
// METHODS
// Run the smooth scroll animation
var runSmoothScroll = function (anchor, duration, easing, url) {
/*
//2/23/2014 Figured out how to disable mousewheel for a set amount of time
//disable mousewheel function
$("html").bind("scroll mousedown DOMMouseScroll mousewheel keyup", false);
//pause until animated scrolling is finished, then enable mousewheel
setTimeout(function(){
$("html").unbind("scroll mousedown DOMMouseScroll mousewheel keyup", false);
}, duration);
*/
//alternatively, can end the animation prematurely if mousewheel is used
$("html, body").bind("scroll mousedown DOMMouseScroll mousewheel keyup", function(){
clearInterval(runAnimation);
});
// SELECTORS
var startLocation = window.pageYOffset;
// Get the height of a fixed header if one exists
var scrollHeader = document.querySelector('[data-scroll-header]');
var headerHeight = scrollHeader === null ? 0 : scrollHeader.offsetHeight;
// Set the animation variables to 0/undefined.
var timeLapsed = 0;
var percentage, position;
// METHODS
// Calculate the easing pattern
var easingPattern = function (type, time) {
if ( type == 'easeInQuad' ) return time * time; // accelerating from zero velocity
if ( type == 'easeOutQuad' ) return time * (2 - time); // decelerating to zero velocity
if ( type == 'easeInOutQuad' ) return time < 0.5 ? 2 * time * time : -1 + (4 - 2 * time) * time; // acceleration until halfway, then deceleration
if ( type == 'easeInCubic' ) return time * time * time; // accelerating from zero velocity
if ( type == 'easeOutCubic' ) return (--time) * time * time + 1; // decelerating to zero velocity
if ( type == 'easeInOutCubic' ) return time < 0.5 ? 4 * time * time * time : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; // acceleration until halfway, then deceleration
if ( type == 'easeInQuart' ) return time * time * time * time; // accelerating from zero velocity
if ( type == 'easeOutQuart' ) return 1 - (--time) * time * time * time; // decelerating to zero velocity
if ( type == 'easeInOutQuart' ) return time < 0.5 ? 8 * time * time * time * time : 1 - 8 * (--time) * time * time * time; // acceleration until halfway, then deceleration
if ( type == 'easeInQuint' ) return time * time * time * time * time; // accelerating from zero velocity
if ( type == 'easeOutQuint' ) return 1 + (--time) * time * time * time * time; // decelerating to zero velocity
if ( type == 'easeInOutQuint' ) return time < 0.5 ? 16 * time * time * time * time * time : 1 + 16 * (--time) * time * time * time * time; // acceleration until halfway, then deceleration
return time; // no easing, no acceleration
};
// Update the URL
var updateURL = function (url, anchor) {
if ( url === 'true' && history.pushState ) {
history.pushState( {pos:anchor.id}, '', '#' + anchor.id );
}
};
// Calculate how far to scroll
var getEndLocation = function (anchor) {
var location = 0;
if (anchor.offsetParent) {
do {
location += anchor.offsetTop;
anchor = anchor.offsetParent;
} while (anchor);
}
location = location - headerHeight;
if ( location >= 0 ) {
return location;
} else {
return 0;
}
};
var endLocation = getEndLocation(anchor);
var distance = endLocation - startLocation;
// Stop the scrolling animation when the anchor is reached (or at the top/bottom of the page)
var stopAnimation = function () {
var currentLocation = window.pageYOffset;
if ( position == endLocation || currentLocation == endLocation || ( (window.innerHeight + currentLocation) >= document.body.scrollHeight ) ) {
clearInterval(runAnimation);
}
};
// Scroll the page by an increment, and check if it's time to stop
var animateScroll = function () {
timeLapsed += 16;
percentage = ( timeLapsed / duration );
percentage = ( percentage > 1 ) ? 1 : percentage;
position = startLocation + ( distance * easingPattern(easing, percentage) );
window.scrollTo( 0, position );
stopAnimation();
};
// EVENTS, LISTENERS, AND INITS
updateURL(url, anchor);
var runAnimation = setInterval(animateScroll, 16);
};
// Check that anchor exists and run scroll animation
var handleToggleClick = function (event) {
// SELECTORS
// Get anchor link and calculate distance from the top
var dataID = this.getAttribute('href');
var dataTarget = document.querySelector(dataID);
var dataSpeed = this.getAttribute('data-speed');
var dataEasing = this.getAttribute('data-easing');
var dataURL = this.getAttribute('data-url');
// EVENTS, LISTENERS, AND INITS
event.preventDefault();
if (dataTarget) {
runSmoothScroll( dataTarget, dataSpeed || 500, dataEasing || 'easeInOutCubic', dataURL || 'false' );
}
};
// EVENTS, LISTENERS, AND INITS
// When a toggle is clicked, run the click handler
Array.prototype.forEach.call(scrollToggles, function (toggle, index) {
toggle.addEventListener('click', handleToggleClick, false);
});
// Return to the top of the page when back button is clicked and no hash is set
window.onpopstate = function (event) {
if ( event.state === null && window.location.hash === '' ) {
window.scrollTo( 0, 0 );
}
};
}
})(window, document);

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

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

Resources