app/assets/javascripts/highcharts.js in highcharts-rails-4.0.1 vs app/assets/javascripts/highcharts.js in highcharts-rails-4.0.3
- old
+ new
@@ -1,19 +1,19 @@
// ==ClosureCompiler==
// @compilation_level SIMPLE_OPTIMIZATIONS
/**
- * @license Highcharts JS v4.0.1 (2014-04-24)
+ * @license Highcharts JS v4.0.3 (2014-07-03)
*
* (c) 2009-2014 Torstein Honsi
*
* License: www.highcharts.com/license
*/
// JSLint options:
/*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console, each, grep */
-
+/*jslint ass: true, sloppy: true, forin: true, plusplus: true, nomen: true, vars: true, regexp: true, newcap: true, browser: true, continue: true, white: true */
(function () {
// encapsulated variables
var UNDEFINED,
doc = document,
win = window,
@@ -50,15 +50,16 @@
defaultOptions,
dateFormat, // function
globalAnimation,
pathAnim,
timeUnits,
- noop = function () {},
+ error,
+ noop = function () { return UNDEFINED; },
charts = [],
chartCount = 0,
PRODUCT = 'Highcharts',
- VERSION = '4.0.1',
+ VERSION = '4.0.3',
// some constants for frequently used strings
DIV = 'div',
ABSOLUTE = 'absolute',
RELATIVE = 'relative',
@@ -71,18 +72,10 @@
L = 'L',
numRegex = /^[0-9]+$/,
NORMAL_STATE = '',
HOVER_STATE = 'hover',
SELECT_STATE = 'select',
- MILLISECOND = 'millisecond',
- SECOND = 'second',
- MINUTE = 'minute',
- HOUR = 'hour',
- DAY = 'day',
- WEEK = 'week',
- MONTH = 'month',
- YEAR = 'year',
// Object for extending Axis
AxisPlotLineOrBandExtension,
// constants for attributes
@@ -103,15 +96,19 @@
setMonth,
setFullYear,
// lookup over the types and the associated classes
- seriesTypes = {};
+ seriesTypes = {},
+ Highcharts;
// The Highcharts namespace
-var Highcharts = win.Highcharts = win.Highcharts ? error(16, true) : {};
-
+if (win.Highcharts) {
+ error(16, true);
+} else {
+ Highcharts = win.Highcharts = {};
+}
/**
* Extend an object with the members of another
* @param {Object} a The object to be extended
* @param {Object} b The object to add to the first one
*/
@@ -178,26 +175,10 @@
return ret;
}
/**
- * Take an array and turn into a hash with even number arguments as keys and odd numbers as
- * values. Allows creating constants for commonly used style properties, attributes etc.
- * Avoid it in performance critical situations like looping
- */
-function hash() {
- var i = 0,
- args = arguments,
- length = args.length,
- obj = {};
- for (; i < length; i++) {
- obj[args[i++]] = args[i];
- }
- return obj;
-}
-
-/**
* Shortcut for parseInt
* @param {Object} s
* @param {Number} mag Magnitude
*/
function pInt(s, mag) {
@@ -215,11 +196,11 @@
/**
* Check for object
* @param {Object} obj
*/
function isObject(obj) {
- return typeof obj === 'object';
+ return obj && typeof obj === 'object';
}
/**
* Check for array
* @param {Object} obj
@@ -315,11 +296,11 @@
i,
arg,
length = args.length;
for (i = 0; i < length; i++) {
arg = args[i];
- if (typeof arg !== 'undefined' && arg !== null) {
+ if (arg !== UNDEFINED && arg !== null) {
return arg;
}
}
}
@@ -366,11 +347,11 @@
* Extend a prototyped class by new members
* @param {Object} parent
* @param {Object} members
*/
function extendClass(parent, members) {
- var object = function () {};
+ var object = function () { return UNDEFINED; };
object.prototype = new parent();
extend(object.prototype, members);
return object;
}
@@ -617,39 +598,10 @@
return interval;
}
/**
- * Helper class that contains variuos counters that are local to the chart.
- */
-function ChartCounters() {
- this.color = 0;
- this.symbol = 0;
-}
-
-ChartCounters.prototype = {
- /**
- * Wraps the color counter if it reaches the specified length.
- */
- wrapColor: function (length) {
- if (this.color >= length) {
- this.color = 0;
- }
- },
-
- /**
- * Wraps the symbol counter if it reaches the specified length.
- */
- wrapSymbol: function (length) {
- if (this.symbol >= length) {
- this.symbol = 0;
- }
- }
-};
-
-
-/**
* Utility method that sorts an object array and keeping the order of equal items.
* ECMA script standard does not specify the behaviour when items are equal.
*/
function stableSort(arr, sortFunction) {
var length = arr.length,
@@ -746,18 +698,20 @@
}
/**
* Provide error messages for debugging, with links to online explanation
*/
-function error(code, stop) {
+error = function (code, stop) {
var msg = 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code;
if (stop) {
throw msg;
- } else if (win.console) {
+ }
+ // else ...
+ if (win.console) {
console.log(msg);
}
-}
+};
/**
* Fix JS round off float errors
* @param {Number} num
*/
@@ -778,22 +732,20 @@
}
/**
* The time unit lookup
*/
-/*jslint white: true*/
-timeUnits = hash(
- MILLISECOND, 1,
- SECOND, 1000,
- MINUTE, 60000,
- HOUR, 3600000,
- DAY, 24 * 3600000,
- WEEK, 7 * 24 * 3600000,
- MONTH, 31 * 24 * 3600000,
- YEAR, 31556952000
-);
-/*jslint white: false*/
+timeUnits = {
+ millisecond: 1,
+ second: 1000,
+ minute: 60000,
+ hour: 3600000,
+ day: 24 * 3600000,
+ week: 7 * 24 * 3600000,
+ month: 31 * 24 * 3600000,
+ year: 31556952000
+};
/**
* Path interpolation algorithm used across adapters
*/
pathAnim = {
/**
@@ -1004,13 +956,13 @@
function (arr, fn) { // modern browsers
return Array.prototype.forEach.call(arr, fn);
} :
function (arr, fn) { // legacy
- var i = 0,
+ var i,
len = arr.length;
- for (; i < len; i++) {
+ for (i = 0; i < len; i++) {
if (fn.call(arr[i], arr[i], i, arr) === false) {
return i;
}
}
};
@@ -1296,11 +1248,11 @@
}
};
defaultOptions = {
colors: ['#7cb5ec', '#434348', '#90ed7d', '#f7a35c',
- '#8085e9', '#f15c80', '#e4d354', '#8085e8', '#8d4653', '#91e8e1'], // docs
+ '#8085e9', '#f15c80', '#e4d354', '#8085e8', '#8d4653', '#91e8e1'],
symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
lang: {
loading: 'Loading...',
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
'August', 'September', 'October', 'November', 'December'],
@@ -1313,12 +1265,12 @@
thousandsSep: ','
},
global: {
useUTC: true,
//timezoneOffset: 0,
- canvasToolsURL: 'http://code.highcharts.com/4.0.1/modules/canvas-tools.js',
- VMLRadialGradientURL: 'http://code.highcharts.com/4.0.1/gfx/vml-radial-gradient.png'
+ canvasToolsURL: 'http://code.highcharts.com/4.0.3/modules/canvas-tools.js',
+ VMLRadialGradientURL: 'http://code.highcharts.com/4.0.3/gfx/vml-radial-gradient.png'
},
chart: {
//animation: true,
//alignTicks: false,
//reflow: true,
@@ -1371,11 +1323,11 @@
margin: 15,
// x: 0,
// verticalAlign: 'top',
// y: null,
style: {
- color: '#333333', // docs
+ color: '#333333',
fontSize: '18px'
}
},
subtitle: {
@@ -1384,11 +1336,11 @@
// floating: false
// x: 0,
// verticalAlign: 'top',
// y: null,
style: {
- color: '#555555' // docs
+ color: '#555555'
}
},
plotOptions: {
line: { // base series options
@@ -1415,12 +1367,13 @@
radius: 4,
lineColor: '#FFFFFF',
//fillColor: null,
states: { // states for a single point
hover: {
- enabled: true
- //radius: base + 2
+ enabled: true,
+ lineWidthPlus: 1,
+ radiusPlus: 2
},
select: {
fillColor: '#FFFFFF',
lineColor: '#000000',
lineWidth: 2
@@ -1452,11 +1405,11 @@
//pointInterval: 1,
//showInLegend: null, // auto: true for standalone series, false for linked series
states: { // states for the entire series
hover: {
//enabled: false,
- //lineWidth: base + 1,
+ lineWidthPlus: 1,
marker: {
// lineWidth: base + 1,
// radius: base + 1
},
halo: {
@@ -1496,11 +1449,11 @@
labelFormatter: function () {
return this.name;
},
//borderWidth: 0,
borderColor: '#909090',
- borderRadius: 0, // docs
+ borderRadius: 0,
navigation: {
// animation: true,
activeColor: '#274b6d',
// arrowSize: 12
inactiveColor: '#CCC'
@@ -1512,13 +1465,13 @@
// backgroundColor: null,
/*style: {
padding: '5px'
},*/
itemStyle: {
- color: '#333333', // docs
+ color: '#333333',
fontSize: '12px',
- fontWeight: 'bold' // docs
+ fontWeight: 'bold'
},
itemHoverStyle: {
//cursor: 'pointer', removed as of #601
color: '#000'
},
@@ -1549,11 +1502,11 @@
loading: {
// hideDuration: 100,
labelStyle: {
fontWeight: 'bold',
position: RELATIVE,
- top: '1em'
+ top: '45%'
},
// showDuration: 0,
style: {
position: ABSOLUTE,
backgroundColor: 'white',
@@ -1579,13 +1532,13 @@
month: '%B %Y',
year: '%Y'
},
//formatter: defaultFormatter,
headerFormat: '<span style="font-size: 10px">{point.key}</span><br/>',
- pointFormat: '<span style="color:{series.color}">\u25CF</span> {series.name}: <b>{point.y}</b><br/>', // docs
+ pointFormat: '<span style="color:{series.color}">\u25CF</span> {series.name}: <b>{point.y}</b><br/>',
shadow: true,
- //shape: 'calout',
+ //shape: 'callout',
//shared: false,
snap: isTouchDevice ? 25 : 10,
style: {
color: '#333333',
cursor: 'default',
@@ -1814,10 +1767,17 @@
* A wrapper object for SVG elements
*/
function SVGElement() {}
SVGElement.prototype = {
+
+ // Default base for animation
+ opacity: 1,
+ // For labels, these CSS properties are applied to the <text> node directly
+ textProps: ['fontSize', 'fontWeight', 'fontFamily', 'color',
+ 'lineHeight', 'width', 'textDecoration', 'textShadow', 'HcTextStroke'],
+
/**
* Initialize the SVG renderer
* @param {Object} renderer
* @param {String} nodeName
*/
@@ -1826,15 +1786,12 @@
wrapper.element = nodeName === 'span' ?
createElement(nodeName) :
doc.createElementNS(SVG_NS, nodeName);
wrapper.renderer = renderer;
},
+
/**
- * Default base for animation
- */
- opacity: 1,
- /**
* Animate a given attribute
* @param {Object} params
* @param {Number} options The same options as in jQuery animation
* @param {Function} complete Function to perform at the end of animation
*/
@@ -1851,10 +1808,11 @@
this.attr(params);
if (complete) {
complete();
}
}
+ return this;
},
/**
* Build an SVG gradient out of a common JavaScript configuration object
*/
@@ -2106,11 +2064,11 @@
var wrapper = this,
key,
attribs = {},
normalizer,
- strokeWidth = rect.strokeWidth || wrapper.strokeWidth || (wrapper.attr && wrapper.attr('stroke-width')) || 0;
+ strokeWidth = rect.strokeWidth || wrapper.strokeWidth || 0;
normalizer = mathRound(strokeWidth) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors
// normalize for crisp edges
rect.x = mathFloor(rect.x || wrapper.x || 0) + normalizer;
@@ -2622,12 +2580,12 @@
each(shadows, function (shadow) {
wrapper.safeRemoveChild(shadow);
});
}
- // In case of useHTML, clean up empty containers emulating SVG groups (#1960, #2393).
- while (parentToClean && parentToClean.div.childNodes.length === 0) {
+ // In case of useHTML, clean up empty containers emulating SVG groups (#1960, #2393, #2697).
+ while (parentToClean && parentToClean.div && parentToClean.div.childNodes.length === 0) {
grandParent = parentToClean.parentGroup;
wrapper.safeRemoveChild(parentToClean.div);
delete parentToClean.div;
parentToClean = grandParent;
}
@@ -2710,11 +2668,11 @@
* for animation.
*/
_defaultGetter: function (key) {
var ret = pick(this[key], this.element ? this.element.getAttribute(key) : null, 0);
- if (/^[0-9\.]+$/.test(ret)) { // is numerical
+ if (/^[\-0-9\.]+$/.test(ret)) { // is numerical
ret = parseFloat(ret);
}
return ret;
},
@@ -2741,15 +2699,16 @@
.replace('shortdash', '3,1,')
.replace('longdash', '8,3,')
.replace(/dot/g, '1,3,')
.replace('dash', '4,3,')
.replace(/,$/, '')
+ .replace('solid', 1)
.split(','); // ending comma
i = value.length;
while (i--) {
- value[i] = pInt(value[i]) * this.element.getAttribute('stroke-width');
+ value[i] = pInt(value[i]) * this['stroke-width'];
}
value = value.join(',');
this.element.setAttribute('stroke-dasharray', value);
}
},
@@ -2758,19 +2717,10 @@
},
opacitySetter: function (value, key, element) {
this[key] = value;
element.setAttribute(key, value);
},
- // In Chrome/Win < 6 as well as Batik and PhantomJS as of 1.9.7, the stroke attribute can't be set when the stroke-
- // width is 0. #1369
- 'stroke-widthSetter': function (value, key, element) {
- if (value === 0) {
- value = 0.00001;
- }
- this.strokeWidth = value; // read in symbol paths like 'callout'
- element.setAttribute(key, value);
- },
titleSetter: function (value) {
var titleNode = this.element.getElementsByTagName('title')[0];
if (!titleNode) {
titleNode = doc.createElementNS(SVG_NS, 'title');
this.element.appendChild(titleNode);
@@ -2787,11 +2737,10 @@
this.renderer.buildText(this);
}
}
},
fillSetter: function (value, key, element) {
-
if (typeof value === 'string') {
element.setAttribute(key, value);
} else if (value) {
this.colorGradient(value, key, element);
}
@@ -2811,28 +2760,26 @@
SVGElement.prototype.rotationSetter = SVGElement.prototype.verticalAlignSetter =
SVGElement.prototype.scaleXSetter = SVGElement.prototype.scaleYSetter = function (value, key) {
this[key] = value;
this.doTransform = true;
};
-SVGElement.prototype.strokeSetter = SVGElement.prototype.fillSetter;
-
-
-// In Chrome/Win < 6 as well as Batik, the stroke attribute can't be set when the stroke-
-// width is 0. #1369
-/*SVGElement.prototype['stroke-widthSetter'] = SVGElement.prototype.strokeSetter = function (value, key) {
+// WebKit and Batik have problems with a stroke-width of zero, so in this case we remove the
+// stroke attribute altogether. #1270, #1369, #3065, #3072.
+SVGElement.prototype['stroke-widthSetter'] = SVGElement.prototype.strokeSetter = function (value, key, element) {
this[key] = value;
// Only apply the stroke attribute if the stroke width is defined and larger than 0
if (this.stroke && this['stroke-width']) {
- this.element.setAttribute('stroke', this.stroke);
- this.element.setAttribute('stroke-width', this['stroke-width']);
+ this.strokeWidth = this['stroke-width'];
+ SVGElement.prototype.fillSetter.call(this, this.stroke, 'stroke', element); // use prototype as instance may be overridden
+ element.setAttribute('stroke-width', this['stroke-width']);
this.hasStroke = true;
} else if (key === 'stroke-width' && value === 0 && this.hasStroke) {
- this.element.removeAttribute('stroke');
+ element.removeAttribute('stroke');
this.hasStroke = false;
}
-};*/
+};
/**
* The default SVG renderer
*/
@@ -3000,28 +2947,31 @@
hrefRegex,
parentX = attr(textNode, 'x'),
textStyles = wrapper.styles,
width = wrapper.textWidth,
textLineHeight = textStyles && textStyles.lineHeight,
+ textStroke = textStyles && textStyles.HcTextStroke,
i = childNodes.length,
getLineHeight = function (tspan) {
return textLineHeight ?
pInt(textLineHeight) :
renderer.fontMetrics(
/(px|em)$/.test(tspan && tspan.style.fontSize) ?
tspan.style.fontSize :
- ((textStyles && textStyles.fontSize) || renderer.style.fontSize || 12)
+ ((textStyles && textStyles.fontSize) || renderer.style.fontSize || 12),
+ tspan
).h;
};
/// remove old text
while (i--) {
textNode.removeChild(childNodes[i]);
}
- // Skip tspans, add text directly to text node
- if (!hasMarkup && textStr.indexOf(' ') === -1) {
+ // Skip tspans, add text directly to text node. The forceTSpan is a hook
+ // used in text outline hack.
+ if (!hasMarkup && !textStroke && textStr.indexOf(' ') === -1) {
textNode.appendChild(doc.createTextNode(textStr));
return;
// Complex strings, add more logic
} else {
@@ -3092,10 +3042,13 @@
}
// add attributes
attr(tspan, attributes);
+ // Append it
+ textNode.appendChild(tspan);
+
// first span on subsequent line, add the line height
if (!spanNo && lineNo) {
// allow getting the right offset height in exporting in IE
if (!hasSVG && forExport) {
@@ -3105,31 +3058,23 @@
// Set the line height based on the font size of either
// the text element or the tspan element
attr(
tspan,
'dy',
- getLineHeight(tspan),
- // Safari 6.0.2 - too optimized for its own good (#1539)
- // TODO: revisit this with future versions of Safari
- isWebKit && tspan.offsetHeight
+ getLineHeight(tspan)
);
}
- // Append it
- textNode.appendChild(tspan);
-
- spanNo++;
-
// check width and apply soft breaks
if (width) {
var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273
- hasWhiteSpace = words.length > 1 && textStyles.whiteSpace !== 'nowrap',
+ hasWhiteSpace = spans.length > 1 || (words.length > 1 && textStyles.whiteSpace !== 'nowrap'),
tooLong,
actualWidth,
- clipHeight = wrapper._clipHeight,
+ hcHeight = textStyles.HcHeight,
rest = [],
- dy = getLineHeight(),
+ dy = getLineHeight(tspan),
softLineNo = 1,
bBox;
while (hasWhiteSpace && (words.length || rest.length)) {
delete wrapper.bBox; // delete cache
@@ -3145,12 +3090,11 @@
if (!tooLong || words.length === 1) { // new line needed
words = rest;
rest = [];
if (words.length) {
softLineNo++;
-
- if (clipHeight && softLineNo * dy > clipHeight) {
+ if (hcHeight && softLineNo * dy > hcHeight) {
words = ['...'];
wrapper.attr('title', wrapper.textStr);
} else {
tspan = doc.createElementNS(SVG_NS, 'tspan');
@@ -3160,25 +3104,26 @@
});
if (spanStyle) { // #390
attr(tspan, 'style', spanStyle);
}
textNode.appendChild(tspan);
-
- if (actualWidth > width) { // a single word is pressing it out
- width = actualWidth;
- }
}
}
+ if (actualWidth > width) { // a single word is pressing it out
+ width = actualWidth;
+ }
} else { // append to existing line tspan
tspan.removeChild(tspan.firstChild);
rest.unshift(words.pop());
}
if (words.length) {
tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
}
}
}
+
+ spanNo++;
}
}
});
});
}
@@ -3501,12 +3446,11 @@
'href', src);
} else {
// could be exporting in IE
// using href throws "not supported" in ie7 and under, requries regex shim to fix later
elemWrapper.element.setAttribute('hc-svg-href', src);
- }
-
+ }
return elemWrapper;
},
/**
* Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object.
@@ -3821,18 +3765,19 @@
});
}
if (!useHTML) {
wrapper.xSetter = function (value, key, element) {
- var childNodes = element.childNodes,
- child,
+ var tspans = element.getElementsByTagName('tspan'),
+ tspan,
+ parentVal = element.getAttribute(key),
i;
- for (i = 1; i < childNodes.length; i++) {
- child = childNodes[i];
- // if the x values are equal, the tspan represents a linebreak
- if (child.getAttribute('x') === element.getAttribute('x')) {
- child.setAttribute('x', value);
+ for (i = 0; i < tspans.length; i++) {
+ tspan = tspans[i];
+ // If the x values are equal, the tspan represents a linebreak
+ if (tspan.getAttribute(key) === parentVal) {
+ tspan.setAttribute(key, value);
}
}
element.setAttribute(key, value);
};
}
@@ -3841,22 +3786,27 @@
},
/**
* Utility to return the baseline offset and total line height from the font size
*/
- fontMetrics: function (fontSize) {
+ fontMetrics: function (fontSize, elem) {
fontSize = fontSize || this.style.fontSize;
+ if (elem && win.getComputedStyle) {
+ elem = elem.element || elem; // SVGElement
+ fontSize = win.getComputedStyle(elem, "").fontSize;
+ }
fontSize = /px/.test(fontSize) ? pInt(fontSize) : /em/.test(fontSize) ? parseFloat(fontSize) * 12 : 12;
// Empirical values found by comparing font size and bounding box height.
// Applies to the default font family. http://jsfiddle.net/highcharts/7xvn7/
var lineHeight = fontSize < 24 ? fontSize + 4 : mathRound(fontSize * 1.2),
baseline = mathRound(lineHeight * 0.8);
return {
h: lineHeight,
- b: baseline
+ b: baseline,
+ f: fontSize
};
},
/**
* Add a label, a text item that can hold a colored or gradient background
@@ -3909,11 +3859,11 @@
text.getBBox();
wrapper.width = (width || bBox.width || 0) + 2 * padding + paddingLeft;
wrapper.height = (height || bBox.height || 0) + 2 * padding;
// update the label-scoped y offset
- baselineOffset = padding + renderer.fontMetrics(style && style.fontSize).b;
+ baselineOffset = padding + renderer.fontMetrics(style && style.fontSize, text).b;
if (needsBox) {
// create the border box if it is not already present
@@ -4085,11 +4035,11 @@
*/
css: function (styles) {
if (styles) {
var textStyles = {};
styles = merge(styles); // create a copy to avoid altering the original object (#537)
- each(['fontSize', 'fontWeight', 'fontFamily', 'color', 'lineHeight', 'width', 'textDecoration', 'textShadow'], function (prop) {
+ each(wrapper.textProps, function (prop) {
if (styles[prop] !== UNDEFINED) {
textStyles[prop] = styles[prop];
delete styles[prop];
}
});
@@ -4458,11 +4408,11 @@
if (!hasSVG && !useCanVG) {
/**
* The VML element wrapper.
*/
-Highcharts.VMLElement = VMLElement = {
+VMLElement = {
/**
* Initialize a new VML element wrapper. It builds the markup as a string
* to minimize DOM traffic.
* @param {Object} renderer
@@ -4823,11 +4773,11 @@
},
dSetter: function (value, key, element) {
var i,
shadows = this.shadows;
value = value || [];
- this.d = value.join(' '); // used in getter for animation
+ this.d = value.join && value.join(' '); // used in getter for animation
element.path = value = this.pathToVML(value);
// update shadows
if (shadows) {
@@ -4921,11 +4871,11 @@
},
zIndexSetter: function (value, key, element) {
element.style[key] = value;
}
};
-VMLElement = extendClass(SVGElement, VMLElement);
+Highcharts.VMLElement = VMLElement = extendClass(SVGElement, VMLElement);
// Some shared setters
VMLElement.prototype.ySetter =
VMLElement.prototype.widthSetter =
VMLElement.prototype.heightSetter =
@@ -5603,10 +5553,11 @@
horiz = axis.horiz,
categories = axis.categories,
names = axis.names,
pos = tick.pos,
labelOptions = options.labels,
+ rotation = labelOptions.rotation,
str,
tickPositions = axis.tickPositions,
width = (horiz && categories &&
!labelOptions.step && !labelOptions.staggerLines &&
!labelOptions.rotation &&
@@ -5649,18 +5600,18 @@
// first call
if (!defined(label)) {
attr = {
align: axis.labelAlign
};
- if (isNumber(labelOptions.rotation)) {
- attr.rotation = labelOptions.rotation;
+ if (isNumber(rotation)) {
+ attr.rotation = rotation;
}
if (width && labelOptions.ellipsis) {
- attr._clipHeight = axis.len / tickPositions.length;
+ css.HcHeight = axis.len / tickPositions.length;
}
- tick.label =
+ tick.label = label =
defined(str) && labelOptions.enabled ?
chart.renderer.text(
str,
0,
0,
@@ -5670,17 +5621,25 @@
// without position absolute, IE export sometimes is wrong
.css(css)
.add(axis.labelGroup) :
null;
+ // Set the tick baseline and correct for rotation (#1764)
+ axis.tickBaseline = chart.renderer.fontMetrics(labelOptions.style.fontSize, label).b;
+ if (rotation && axis.side === 2) {
+ axis.tickBaseline *= mathCos(rotation * deg2rad);
+ }
+
+
// update
} else if (label) {
label.attr({
text: str
})
.css(css);
}
+ tick.yOffset = label ? pick(labelOptions.y, axis.tickBaseline + (axis.side === 2 ? 8 : -(label.getBBox().height / 2))) : 0;
},
/**
* Get the offset height or width of the label
*/
@@ -5753,11 +5712,11 @@
// Find the firsth neighbour on the same line
do {
index += (isFirst ? 1 : -1);
neighbour = axis.ticks[tickPositions[index]];
- } while (tickPositions[index] && (!neighbour || neighbour.label.line !== line));
+ } while (tickPositions[index] && (!neighbour || !neighbour.label || neighbour.label.line !== line)); // #3044
neighbourEdge = neighbour && neighbour.label.xy && neighbour.label.xy.x + neighbour.getLabelSides()[isFirst ? 0 : 1];
if ((isFirst && !reversed) || (isLast && reversed)) {
// Is the label spilling out to the left of the plot area?
@@ -5818,29 +5777,17 @@
*/
getLabelPosition: function (x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
var axis = this.axis,
transA = axis.transA,
reversed = axis.reversed,
- staggerLines = axis.staggerLines,
- baseline = axis.chart.renderer.fontMetrics(labelOptions.style.fontSize).b,
- rotation = labelOptions.rotation;
+ staggerLines = axis.staggerLines;
x = x + labelOptions.x - (tickmarkOffset && horiz ?
tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
- y = y + labelOptions.y - (tickmarkOffset && !horiz ?
+ y = y + this.yOffset - (tickmarkOffset && !horiz ?
tickmarkOffset * transA * (reversed ? 1 : -1) : 0);
- // Correct for rotation (#1764)
- if (rotation && axis.side === 2) {
- y -= baseline - baseline * mathCos(rotation * deg2rad);
- }
-
- // Vertically centered
- if (!defined(labelOptions.y) && !rotation) { // #1951
- y += baseline - label.getBBox().height / 2;
- }
-
// Correct for staggered labels
if (staggerLines) {
label.line = (index / (step || 1) % staggerLines);
y += label.line * (axis.labelOffset / staggerLines);
}
@@ -5902,10 +5849,11 @@
xy = tick.getPosition(horiz, pos, tickmarkOffset, old),
x = xy.x,
y = xy.y,
reverseCrisp = ((horiz && x === axis.pos + axis.len) || (!horiz && y === axis.pos)) ? -1 : 1; // #1480, #1687
+ opacity = pick(opacity, 1);
this.isActive = true;
// create the grid line
if (gridLineWidth) {
gridLinePath = axis.getPlotLinePath(pos + tickmarkOffset, gridLineWidth * reverseCrisp, old, true);
@@ -6157,12 +6105,13 @@
.css(optionsLabel.style)
.add();
}
// get the bounding box and align the label
- xs = [path[1], path[4], pick(path[6], path[1])];
- ys = [path[2], path[5], pick(path[7], path[2])];
+ // #3000 changed to better handle choice between plotband or plotline
+ xs = [path[1], path[4], (isBand ? path[6] : path[1])];
+ ys = [path[2], path[5], (isBand ? path[7] : path[2])];
x = arrayMin(xs);
y = arrayMin(ys);
label.align(optionsLabel, false, {
x: x,
@@ -6218,15 +6167,15 @@
return path;
},
addPlotBand: function (options) {
- this.addPlotBandOrLine(options, 'plotBands');
+ return this.addPlotBandOrLine(options, 'plotBands');
},
addPlotLine: function (options) {
- this.addPlotBandOrLine(options, 'plotLines');
+ return this.addPlotBandOrLine(options, 'plotLines');
},
/**
* Add a plot band or plot line after render time
*
@@ -6431,11 +6380,11 @@
* These options extend the defaultOptions for bottom axes
*/
defaultBottomAxisOptions: {
labels: {
x: 0,
- y: 20
+ y: null // based on font size
// overflow: undefined,
// staggerLines: null
},
title: {
rotation: 0
@@ -7119,10 +7068,12 @@
*/
setTickPositions: function (secondPass) {
var axis = this,
chart = axis.chart,
options = axis.options,
+ startOnTick = options.startOnTick,
+ endOnTick = options.endOnTick,
isLog = axis.isLog,
isDatetimeAxis = axis.isDatetimeAxis,
isXAxis = axis.isXAxis,
isLinked = axis.isLinked,
tickPositioner = axis.options.tickPositioner,
@@ -7211,11 +7162,11 @@
// don't let it be more than the data range
(axis.max - axis.min) * tickPixelIntervalOption / mathMax(axis.len, tickPixelIntervalOption)
);
// For squished axes, set only two ticks
if (!defined(tickIntervalOption) && axis.len < tickPixelIntervalOption && !this.isRadial &&
- !this.isLog && !categories && options.startOnTick && options.endOnTick) {
+ !this.isLog && !categories && startOnTick && endOnTick) {
keepTwoTicksOnly = true;
axis.tickInterval /= 4; // tick extremes closer to the real values
}
}
@@ -7301,17 +7252,22 @@
var roundedMin = tickPositions[0],
roundedMax = tickPositions[tickPositions.length - 1],
minPointOffset = axis.minPointOffset || 0,
singlePad;
- if (options.startOnTick) {
+ // Prevent all ticks from being removed (#3195)
+ if (!startOnTick && !endOnTick && !categories && tickPositions.length === 2) {
+ tickPositions.splice(1, 0, (roundedMax + roundedMin) / 2);
+ }
+
+ if (startOnTick) {
axis.min = roundedMin;
} else if (axis.min - minPointOffset > roundedMin) {
tickPositions.shift();
}
- if (options.endOnTick) {
+ if (endOnTick) {
axis.max = roundedMax;
} else if (axis.max + minPointOffset < roundedMax) {
tickPositions.pop();
}
@@ -7537,11 +7493,11 @@
horiz = this.horiz,
width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight),
height = pick(options.height, chart.plotHeight),
top = pick(options.top, chart.plotTop),
left = pick(options.left, chart.plotLeft + offsetLeft),
- percentRegex = /%$/; // docs
+ percentRegex = /%$/;
// Check for percentage based input values
if (percentRegex.test(height)) {
height = parseInt(height, 10) / 100 * chart.plotHeight;
}
@@ -7636,10 +7592,11 @@
titleOffsetOption,
titleMargin = 0,
axisTitleOptions = options.title,
labelOptions = options.labels,
labelOffset = 0, // reset
+ labelOffsetPadded,
axisOffset = chart.axisOffset,
clipOffset = chart.clipOffset,
directionFactor = [-1, 1, 1, -1][side],
n,
i,
@@ -7651,11 +7608,11 @@
pos,
bBox,
x,
w,
lineNo,
- lineHeightCorrection = side === 2 ? renderer.fontMetrics(labelOptions.style.fontSize).b : 0;
+ lineHeightCorrection;
// For reuse in Axis.render
axis.hasData = hasData = (axis.hasVisibleSeries || (defined(axis.min) && defined(axis.max) && !!tickPositions));
axis.showAxis = showAxis = hasData || pick(options.showEmpty, true);
@@ -7732,12 +7689,12 @@
labelOffset = mathMax(
ticks[pos].getLabelSize(),
labelOffset
);
}
-
});
+
if (axis.staggerLines) {
labelOffset *= axis.staggerLines;
axis.labelOffset = labelOffset;
}
@@ -7770,30 +7727,30 @@
axis.axisTitle.isNew = true;
}
if (showAxis) {
titleOffset = axis.axisTitle.getBBox()[horiz ? 'height' : 'width'];
- titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10);
titleOffsetOption = axisTitleOptions.offset;
+ titleMargin = defined(titleOffsetOption) ? 0 : pick(axisTitleOptions.margin, horiz ? 5 : 10);
}
// hide or show the title depending on whether showEmpty is set
axis.axisTitle[showAxis ? 'show' : 'hide']();
}
// handle automatic or user set offset
axis.offset = directionFactor * pick(options.offset, axisOffset[side]);
- axis.axisTitleMargin =
- pick(titleOffsetOption,
- labelOffset + titleMargin +
- (labelOffset && (directionFactor * options.labels[horiz ? 'y' : 'x'] - lineHeightCorrection))
- );
+ lineHeightCorrection = side === 2 ? axis.tickBaseline : 0;
+ labelOffsetPadded = labelOffset + titleMargin +
+ (labelOffset && (directionFactor * (horiz ? pick(labelOptions.y, axis.tickBaseline + 8) : labelOptions.x) - lineHeightCorrection));
+ axis.axisTitleMargin = pick(titleOffsetOption, labelOffsetPadded);
axisOffset[side] = mathMax(
axisOffset[side],
- axis.axisTitleMargin + titleOffset + directionFactor * axis.offset
+ axis.axisTitleMargin + titleOffset + directionFactor * axis.offset,
+ labelOffsetPadded // #3027
);
clipOffset[invertedSide] = mathMax(clipOffset[invertedSide], mathFloor(options.lineWidth / 2) * 2);
},
/**
@@ -7959,11 +7916,11 @@
// render new ticks in old position
if (slideInTicks && ticks[pos].isNew) {
ticks[pos].render(i, true, 0.1);
}
- ticks[pos].render(i, false, 1);
+ ticks[pos].render(i);
}
});
// In a categorized axis, the tick marks are displayed between labels. So
// we need to add a tick mark and grid line at the left edge of the X axis.
@@ -8083,29 +8040,21 @@
/**
* Redraw the axis to reflect changes in the data or axis extremes
*/
redraw: function () {
- var axis = this,
- chart = axis.chart,
- pointer = chart.pointer;
-
- // hide tooltip and hover states
- if (pointer) {
- pointer.reset(true);
- }
-
+
// render the axis
- axis.render();
+ this.render();
// move plot lines and bands
- each(axis.plotLinesAndBands, function (plotLine) {
+ each(this.plotLinesAndBands, function (plotLine) {
plotLine.render();
});
// mark associated series as dirty and ready for redraw
- each(axis.series, function (series) {
+ each(this.series, function (series) {
series.isDirty = true;
});
},
@@ -8238,44 +8187,44 @@
minDate = new Date(min - timezoneOffset),
interval = normalizedInterval.unitRange,
count = normalizedInterval.count;
if (defined(min)) { // #1300
- if (interval >= timeUnits[SECOND]) { // second
+ if (interval >= timeUnits.second) { // second
minDate.setMilliseconds(0);
- minDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 :
+ minDate.setSeconds(interval >= timeUnits.minute ? 0 :
count * mathFloor(minDate.getSeconds() / count));
}
- if (interval >= timeUnits[MINUTE]) { // minute
- minDate[setMinutes](interval >= timeUnits[HOUR] ? 0 :
+ if (interval >= timeUnits.minute) { // minute
+ minDate[setMinutes](interval >= timeUnits.hour ? 0 :
count * mathFloor(minDate[getMinutes]() / count));
}
- if (interval >= timeUnits[HOUR]) { // hour
- minDate[setHours](interval >= timeUnits[DAY] ? 0 :
+ if (interval >= timeUnits.hour) { // hour
+ minDate[setHours](interval >= timeUnits.day ? 0 :
count * mathFloor(minDate[getHours]() / count));
}
- if (interval >= timeUnits[DAY]) { // day
- minDate[setDate](interval >= timeUnits[MONTH] ? 1 :
+ if (interval >= timeUnits.day) { // day
+ minDate[setDate](interval >= timeUnits.month ? 1 :
count * mathFloor(minDate[getDate]() / count));
}
- if (interval >= timeUnits[MONTH]) { // month
- minDate[setMonth](interval >= timeUnits[YEAR] ? 0 :
+ if (interval >= timeUnits.month) { // month
+ minDate[setMonth](interval >= timeUnits.year ? 0 :
count * mathFloor(minDate[getMonth]() / count));
minYear = minDate[getFullYear]();
}
- if (interval >= timeUnits[YEAR]) { // year
+ if (interval >= timeUnits.year) { // year
minYear -= minYear % count;
minDate[setFullYear](minYear);
}
// week is a special case that runs outside the hierarchy
- if (interval === timeUnits[WEEK]) {
+ if (interval === timeUnits.week) {
// get start of current week, independent of count
minDate[setDate](minDate[getDate]() - minDate[getDay]() +
pick(startOfWeek, 1));
}
@@ -8296,22 +8245,22 @@
// iterate and add tick positions at appropriate values
while (time < max) {
tickPositions.push(time);
// if the interval is years, use Date.UTC to increase years
- if (interval === timeUnits[YEAR]) {
+ if (interval === timeUnits.year) {
time = makeTime(minYear + i * count, 0);
// if the interval is months, use Date.UTC to increase months
- } else if (interval === timeUnits[MONTH]) {
+ } else if (interval === timeUnits.month) {
time = makeTime(minYear, minMonth + i * count);
// if we're using global time, the interval is not fixed as it jumps
// one hour at the DST crossover
- } else if (!useUTC && (interval === timeUnits[DAY] || interval === timeUnits[WEEK])) {
+ } else if (!useUTC && (interval === timeUnits.day || interval === timeUnits.week)) {
time = makeTime(minYear, minMonth, minDateDate +
- i * count * (interval === timeUnits[DAY] ? 1 : 7));
+ i * count * (interval === timeUnits.day ? 1 : 7));
// else, the interval is fixed and we use simple addition
} else {
time += interval * count;
}
@@ -8323,13 +8272,13 @@
tickPositions.push(time);
// mark new days if the time is dividible by day (#1649, #1760)
each(grep(tickPositions, function (time) {
- return interval <= timeUnits[HOUR] && time % timeUnits[DAY] === localTimezoneOffset;
+ return interval <= timeUnits.hour && time % timeUnits.day === localTimezoneOffset;
}), function (time) {
- higherRanks[time] = DAY;
+ higherRanks[time] = 'day';
});
}
// record information on the chosen unit - for dynamic label formatter
@@ -8349,32 +8298,32 @@
* prevent it for running over again for each segment having the same interval.
* #662, #697.
*/
Axis.prototype.normalizeTimeTickInterval = function (tickInterval, unitsOption) {
var units = unitsOption || [[
- MILLISECOND, // unit name
+ 'millisecond', // unit name
[1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
], [
- SECOND,
+ 'second',
[1, 2, 5, 10, 15, 30]
], [
- MINUTE,
+ 'minute',
[1, 2, 5, 10, 15, 30]
], [
- HOUR,
+ 'hour',
[1, 2, 3, 4, 6, 8, 12]
], [
- DAY,
+ 'day',
[1, 2]
], [
- WEEK,
+ 'week',
[1, 2]
], [
- MONTH,
+ 'month',
[1, 2, 3, 4, 6]
], [
- YEAR,
+ 'year',
null
]],
unit = units[units.length - 1], // default unit is years
interval = timeUnits[unit[0]],
multiples = unit[1],
@@ -8399,19 +8348,19 @@
}
}
}
// prevent 2.5 years intervals, though 25, 250 etc. are allowed
- if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) {
+ if (interval === timeUnits.year && tickInterval < 5 * interval) {
multiples = [1, 2, 5];
}
// get the count
count = normalizeTickInterval(
tickInterval / interval,
multiples,
- unit[0] === YEAR ? mathMax(getMagnitude(tickInterval / interval), 1) : 1 // #1913, #2360
+ unit[0] === 'year' ? mathMax(getMagnitude(tickInterval / interval), 1) : 1 // #1913, #2360
);
return {
unitRange: interval,
count: count,
@@ -8464,12 +8413,11 @@
for (i = roundedMin; i < max + 1 && !break2; i++) {
len = intermediate.length;
for (j = 0; j < len && !break2; j++) {
pos = log2lin(lin2log(i) * intermediate[j]);
-
- if (pos > min && (!minor || lastPos <= max)) { // #1670
+ if (pos > min && (!minor || lastPos <= max) && lastPos !== UNDEFINED) { // #1670, lastPos is #3113
positions.push(lastPos);
}
if (lastPos > max) {
break2 = true;
@@ -8595,32 +8543,34 @@
* @private
*/
move: function (x, y, anchorX, anchorY) {
var tooltip = this,
now = tooltip.now,
- animate = tooltip.options.animation !== false && !tooltip.isHidden,
+ animate = tooltip.options.animation !== false && !tooltip.isHidden &&
+ // When we get close to the target position, abort animation and land on the right place (#3056)
+ (mathAbs(x - now.x) > 1 || mathAbs(y - now.y) > 1),
skipAnchor = tooltip.followPointer || tooltip.len > 1;
- // get intermediate values for animation
+ // Get intermediate values for animation
extend(now, {
x: animate ? (2 * now.x + x) / 3 : x,
y: animate ? (now.y + y) / 2 : y,
anchorX: skipAnchor ? UNDEFINED : animate ? (2 * now.anchorX + anchorX) / 3 : anchorX,
anchorY: skipAnchor ? UNDEFINED : animate ? (now.anchorY + anchorY) / 2 : anchorY
});
- // move to the intermediate value
+ // Move to the intermediate value
tooltip.label.attr(now);
- // run on next tick of the mouse tracker
- if (animate && (mathAbs(x - now.x) > 1 || mathAbs(y - now.y) > 1)) {
+ // Run on next tick of the mouse tracker
+ if (animate) {
- // never allow two timeouts
+ // Never allow two timeouts
clearTimeout(this.tooltipTimeout);
- // set the fixed interval ticking for the smooth tooltip
+ // Set the fixed interval ticking for the smooth tooltip
this.tooltipTimeout = setTimeout(function () {
// The interval function may still be running during destroy, so check that the chart is really there before calling.
if (tooltip) {
tooltip.move(x, y, anchorX, anchorY);
}
@@ -8965,11 +8915,11 @@
for (n in timeUnits) {
if (timeUnits[n] >= closestPointRange ||
// If the point is placed every day at 23:59, we need to show
// the minutes as well. This logic only works for time units less than
// a day, since all higher time units are dividable by those. #2637.
- (timeUnits[n] <= timeUnits[DAY] && point.key % timeUnits[n] > 0)) {
+ (timeUnits[n] <= timeUnits.day && point.key % timeUnits[n] > 0)) {
xDateFormat = dateTimeLabelFormats[n];
break;
}
}
} else {
@@ -9324,11 +9274,12 @@
plotWidth = chart.plotWidth,
plotHeight = chart.plotHeight,
clickedInside,
size,
mouseDownX = this.mouseDownX,
- mouseDownY = this.mouseDownY;
+ mouseDownY = this.mouseDownY,
+ panKey = chartOptions.panKey && e[chartOptions.panKey + 'Key'];
// If the mouse is outside the plot area, adjust to cooordinates
// inside to prevent the selection marker from going outside
if (chartX < plotLeft) {
chartX = plotLeft;
@@ -9350,11 +9301,11 @@
if (this.hasDragged > 10) {
clickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop);
// make a selection
- if (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside) {
+ if (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside && !panKey) {
if (!this.selectionMarker) {
this.selectionMarker = chart.renderer.rect(
plotLeft,
plotTop,
zoomHor ? 1 : plotWidth,
@@ -9418,12 +9369,13 @@
// record each axis' min and max
each(chart.axes, function (axis) {
if (axis.zoomEnabled) {
var horiz = axis.horiz,
- selectionMin = axis.toValue((horiz ? selectionLeft : selectionTop)),
- selectionMax = axis.toValue((horiz ? selectionLeft + selectionWidth : selectionTop + selectionHeight));
+ minPixelPadding = e.type === 'touchend' ? axis.minPixelPadding: 0, // #1207, #3075
+ selectionMin = axis.toValue((horiz ? selectionLeft : selectionTop) + minPixelPadding),
+ selectionMax = axis.toValue((horiz ? selectionLeft + selectionWidth : selectionTop + selectionHeight) - minPixelPadding);
if (!isNaN(selectionMin) && !isNaN(selectionMax)) { // #859
selectionData[axis.coll].push({
axis: axis,
min: mathMin(selectionMin, selectionMax), // for reversed axes,
@@ -9511,12 +9463,12 @@
var chart = this.chart;
hoverChartIndex = chart.index;
- // normalize
e = this.normalize(e);
+ e.returnValue = false; // #2251, #3224
if (chart.mouseIsDown === 'mousedown') {
this.drag(e);
}
@@ -9788,12 +9740,12 @@
// Identify the data bounds in pixels
each(chart.axes, function (axis) {
if (axis.zoomEnabled) {
var bounds = chart.bounds[axis.horiz ? 'h' : 'v'],
minPixelPadding = axis.minPixelPadding,
- min = axis.toPixels(axis.dataMin),
- max = axis.toPixels(axis.dataMax),
+ min = axis.toPixels(pick(axis.options.min, axis.dataMin)),
+ max = axis.toPixels(pick(axis.options.max, axis.dataMax)),
absMin = mathMin(min, max),
absMax = mathMax(min, max);
// Store the bounds for use in the touchmove handler
bounds.min = mathMin(axis.pos, absMin - minPixelPadding);
@@ -9979,11 +9931,10 @@
if (!options.enabled) {
return;
}
- legend.baseline = pInt(itemStyle.fontSize) + 3 + itemMarginTop; // used in Series prototype
legend.itemStyle = itemStyle;
legend.itemHiddenStyle = merge(itemStyle, options.itemHiddenStyle);
legend.itemMarginTop = itemMarginTop;
legend.padding = padding;
legend.initialItemX = padding;
@@ -10177,11 +10128,11 @@
symbolWidth = legend.symbolWidth,
symbolPadding = options.symbolPadding,
itemStyle = legend.itemStyle,
itemHiddenStyle = legend.itemHiddenStyle,
padding = legend.padding,
- itemDistance = horizontal ? pick(options.itemDistance, 20) : 0, // docs
+ itemDistance = horizontal ? pick(options.itemDistance, 20) : 0,
ltr = !options.rtl,
itemHeight,
widthOption = options.width,
itemMarginBottom = options.itemMarginBottom || 0,
itemMarginTop = legend.itemMarginTop,
@@ -10200,27 +10151,33 @@
// A group to hold the symbol and text. Text is to be appended in Legend class.
item.legendGroup = renderer.g('legend-item')
.attr({ zIndex: 1 })
.add(legend.scrollGroup);
- // Draw the legend symbol inside the group box
- series.drawLegendSymbol(legend, item);
-
// Generate the list item text and add it to the group
item.legendItem = li = renderer.text(
options.labelFormat ? format(options.labelFormat, item) : options.labelFormatter.call(item),
ltr ? symbolWidth + symbolPadding : -symbolPadding,
- legend.baseline,
+ legend.baseline || 0,
useHTML
)
.css(merge(item.visible ? itemStyle : itemHiddenStyle)) // merge to prevent modifying original (#1021)
.attr({
align: ltr ? 'left' : 'right',
zIndex: 2
})
.add(item.legendGroup);
+ // Get the baseline for the first item - the font size is equal for all
+ if (!legend.baseline) {
+ legend.baseline = renderer.fontMetrics(itemStyle.fontSize, li).f + 3 + itemMarginTop;
+ li.attr('y', legend.baseline);
+ }
+
+ // Draw the legend symbol inside the group box
+ series.drawLegendSymbol(legend, item);
+
if (legend.setItemEvents) {
legend.setItemEvents(item, li, useHTML, itemStyle, itemHiddenStyle);
}
// Colorize the items
@@ -10469,11 +10426,11 @@
// Reset the legend height and adjust the clipping rectangle
pages.length = 0;
if (legendHeight > spaceHeight && !options.useHTML) {
- this.clipHeight = clipHeight = spaceHeight - 20 - this.titleHeight - this.padding;
+ this.clipHeight = clipHeight = mathMax(spaceHeight - 20 - this.titleHeight - this.padding, 0);
this.currentPage = pick(this.currentPage, 1);
this.fullHeight = legendHeight;
// Fill pages with Y positions so that the top of each a legend item defines
// the scroll top for each page (#2098)
@@ -10646,13 +10603,13 @@
legendOptions = legend.options,
legendSymbol,
symbolWidth = legend.symbolWidth,
renderer = this.chart.renderer,
legendItemGroup = this.legendGroup,
- verticalCenter = legend.baseline - mathRound(renderer.fontMetrics(legendOptions.itemStyle.fontSize).b * 0.3),
+ verticalCenter = legend.baseline - mathRound(renderer.fontMetrics(legendOptions.itemStyle.fontSize, this.legendItem).b * 0.3),
attr;
-
+
// Draw the line
if (options.lineWidth) {
attr = {
'stroke-width': options.lineWidth
};
@@ -10806,12 +10763,11 @@
chart.xAxis = [];
chart.yAxis = [];
// Expose methods and variables
chart.animation = useCanVG ? false : pick(optionsChart.animation, true);
- chart.pointCount = 0;
- chart.counters = new ChartCounters();
+ chart.pointCount = chart.colorCounter = chart.symbolCounter = 0;
chart.firstRender();
},
/**
@@ -10876,10 +10832,11 @@
pointer = chart.pointer,
legend = chart.legend,
redrawLegend = chart.isDirtyLegend,
hasStackedSeries,
hasDirtyStacks,
+ hasCartesianSeries = chart.hasCartesianSeries,
isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed?
seriesLength = series.length,
i = seriesLength,
serie,
renderer = chart.renderer,
@@ -10939,11 +10896,11 @@
if (hasStackedSeries) {
chart.getStacks();
}
- if (chart.hasCartesianSeries) {
+ if (hasCartesianSeries) {
if (!chart.isResizing) {
// reset maxTicks
chart.maxTicks = null;
@@ -10952,12 +10909,15 @@
axis.setScale();
});
}
chart.adjustTickAmounts();
- chart.getMargins();
+ }
+ chart.getMargins(); // #3098
+
+ if (hasCartesianSeries) {
// If one axis is dirty, all axes must be redrawn (#792, #2169)
each(axes, function (axis) {
if (axis.isDirty) {
isDirtyBox = true;
}
@@ -10977,13 +10937,12 @@
if (isDirtyBox || hasStackedSeries) {
axis.redraw();
}
});
-
-
}
+
// the plot areas size has changed
if (isDirtyBox) {
chart.drawChartBox();
}
@@ -11188,25 +11147,30 @@
subtitle = this.subtitle,
options = this.options,
titleOptions = options.title,
subtitleOptions = options.subtitle,
requiresDirtyBox,
+ renderer = this.renderer,
autoWidth = this.spacingBox.width - 44; // 44 makes room for default context button
if (title) {
title
.css({ width: (titleOptions.width || autoWidth) + PX })
- .align(extend({ y: 15 }, titleOptions), false, 'spacingBox');
+ .align(extend({
+ y: renderer.fontMetrics(titleOptions.style.fontSize, title).b - 3
+ }, titleOptions), false, 'spacingBox');
if (!titleOptions.floating && !titleOptions.verticalAlign) {
titleOffset = title.getBBox().height;
}
}
if (subtitle) {
subtitle
.css({ width: (subtitleOptions.width || autoWidth) + PX })
- .align(extend({ y: titleOffset + titleOptions.margin }, subtitleOptions), false, 'spacingBox');
+ .align(extend({
+ y: titleOffset + (titleOptions.margin - 13) + renderer.fontMetrics(titleOptions.style.fontSize, subtitle).b
+ }, subtitleOptions), false, 'spacingBox');
if (!subtitleOptions.floating && !subtitleOptions.verticalAlign) {
titleOffset = mathCeil(titleOffset + subtitle.getBBox().height);
}
}
@@ -11645,11 +11609,11 @@
clipY = mathCeil(mathMax(plotBorderWidth, clipOffset[0]) / 2);
chart.clipBox = {
x: clipX,
y: clipY,
width: mathFloor(chart.plotSizeX - mathMax(plotBorderWidth, clipOffset[1]) / 2 - clipX),
- height: mathFloor(chart.plotSizeY - mathMax(plotBorderWidth, clipOffset[2]) / 2 - clipY)
+ height: mathMax(0, mathFloor(chart.plotSizeY - mathMax(plotBorderWidth, clipOffset[2]) / 2 - clipY))
};
if (!skipAxes) {
each(chart.axes, function (axis) {
axis.setAxisSize();
@@ -11865,24 +11829,49 @@
serie.setTooltipPoints();
}
serie.render();
});
},
+
+ /**
+ * Render labels for the chart
+ */
+ renderLabels: function () {
+ var chart = this,
+ labels = chart.options.labels;
+ if (labels.items) {
+ each(labels.items, function (label) {
+ var style = extend(labels.style, label.style),
+ x = pInt(style.left) + chart.plotLeft,
+ y = pInt(style.top) + chart.plotTop + 12;
+ // delete to prevent rewriting in IE
+ delete style.left;
+ delete style.top;
+
+ chart.renderer.text(
+ label.html,
+ x,
+ y
+ )
+ .attr({ zIndex: 2 })
+ .css(style)
+ .add();
+
+ });
+ }
+ },
+
/**
* Render all graphics for the chart
*/
render: function () {
var chart = this,
axes = chart.axes,
renderer = chart.renderer,
options = chart.options;
- var labels = options.labels,
- credits = options.credits,
- creditsHref;
-
// Title
chart.setTitle();
// Legend
@@ -11925,57 +11914,43 @@
.add();
}
chart.renderSeries();
// Labels
- if (labels.items) {
- each(labels.items, function (label) {
- var style = extend(labels.style, label.style),
- x = pInt(style.left) + chart.plotLeft,
- y = pInt(style.top) + chart.plotTop + 12;
+ chart.renderLabels();
- // delete to prevent rewriting in IE
- delete style.left;
- delete style.top;
+ // Credits
+ chart.showCredits(options.credits);
- renderer.text(
- label.html,
- x,
- y
- )
- .attr({ zIndex: 2 })
- .css(style)
- .add();
+ // Set flag
+ chart.hasRendered = true;
- });
- }
+ },
- // Credits
- if (credits.enabled && !chart.credits) {
- creditsHref = credits.href;
- chart.credits = renderer.text(
+ /**
+ * Show chart credits based on config options
+ */
+ showCredits: function (credits) {
+ if (credits.enabled && !this.credits) {
+ this.credits = this.renderer.text(
credits.text,
0,
0
)
.on('click', function () {
- if (creditsHref) {
- location.href = creditsHref;
+ if (credits.href) {
+ location.href = credits.href;
}
})
.attr({
align: credits.position.align,
zIndex: 8
})
.css(credits.style)
.add()
.align(credits.position);
}
-
- // Set flag
- chart.hasRendered = true;
-
},
/**
* Clean up memory usage
*/
@@ -12221,11 +12196,11 @@
* @param {Object} options
*/
applyOptions: function (options, x) {
var point = this,
series = point.series,
- pointValKey = series.pointValKey;
+ pointValKey = series.options.pointValKey || series.pointValKey;
options = Point.prototype.optionsToObject.call(this, options);
// copy options directly to point
extend(point, options);
@@ -12692,64 +12667,49 @@
}
return options;
},
- /**
- * Get the series' color
- */
- getColor: function () {
- var options = this.options,
+
+ getCyclic: function (prop, value, defaults) {
+ var i,
userOptions = this.userOptions,
- defaultColors = this.chart.options.colors,
- counters = this.chart.counters,
- color,
- colorIndex;
+ indexName = '_' + prop + 'Index',
+ counterName = prop + 'Counter';
- color = options.color || defaultPlotOptions[this.type].color;
-
- if (!color && !options.colorByPoint) {
- if (defined(userOptions._colorIndex)) { // after Series.update()
- colorIndex = userOptions._colorIndex;
+ if (!value) {
+ if (defined(userOptions[indexName])) { // after Series.update()
+ i = userOptions[indexName];
} else {
- userOptions._colorIndex = counters.color;
- colorIndex = counters.color++;
+ userOptions[indexName] = i = this.chart[counterName] % defaults.length;
+ this.chart[counterName] += 1;
}
- color = defaultColors[colorIndex];
+ value = defaults[i];
}
+ this[prop] = value;
+ },
- this.color = color;
- counters.wrapColor(defaultColors.length);
+ /**
+ * Get the series' color
+ */
+ getColor: function () {
+ if (!this.options.colorByPoint) {
+ this.getCyclic('color', this.options.color || defaultPlotOptions[this.type].color, this.chart.options.colors);
+ }
},
/**
* Get the series' symbol
*/
getSymbol: function () {
- var series = this,
- userOptions = series.userOptions,
- seriesMarkerOption = series.options.marker,
- chart = series.chart,
- defaultSymbols = chart.options.symbols,
- counters = chart.counters,
- symbolIndex;
+ var seriesMarkerOption = this.options.marker;
- series.symbol = seriesMarkerOption.symbol;
- if (!series.symbol) {
- if (defined(userOptions._symbolIndex)) { // after Series.update()
- symbolIndex = userOptions._symbolIndex;
- } else {
- userOptions._symbolIndex = counters.symbol;
- symbolIndex = counters.symbol++;
- }
- series.symbol = defaultSymbols[symbolIndex];
- }
+ this.getCyclic('symbol', seriesMarkerOption.symbol, this.chart.options.symbols);
// don't substract radius in image symbols (#604)
- if (/^url/.test(series.symbol)) {
+ if (/^url/.test(this.symbol)) {
seriesMarkerOption.radius = 0;
}
- counters.wrapSymbol(defaultSymbols.length);
},
drawLegendSymbol: LegendSymbolMixin.drawLineMarker,
/**
@@ -12907,10 +12867,11 @@
i, // loop variable
options = series.options,
cropThreshold = options.cropThreshold,
activePointCount = 0,
isCartesian = series.isCartesian,
+ xExtremes,
min,
max;
// If the series data or axes haven't changed, don't go through this. Return false to pass
// the message on to override methods like in data grouping.
@@ -12920,12 +12881,13 @@
// optionally filter out points outside the plot area
if (isCartesian && series.sorted && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) {
- min = xAxis.min;
- max = xAxis.max;
+ xExtremes = xAxis.getExtremes(); // corrected for log axis (#3053)
+ min = xExtremes.min;
+ max = xExtremes.max;
// it's outside current extremes
if (processedXData[dataLength - 1] < min || processedXData[0] > max) {
processedXData = [];
processedYData = [];
@@ -13451,12 +13413,12 @@
// series type specific modifications
if (seriesOptions.marker) { // line, spline, area, areaspline, scatter
// if no hover radius is given, default to normal radius + 2
- stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2;
- stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1;
+ stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + stateOptionsHover.radiusPlus;
+ stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + stateOptionsHover.lineWidthPlus;
} else { // column, bar, pie
// if no hover color is given, brighten the normal color
stateOptionsHover.color = stateOptionsHover.color ||
@@ -14430,14 +14392,23 @@
* @param {String} str An optional text to show in the loading label instead of the default one
*/
showLoading: function (str) {
var chart = this,
options = chart.options,
- loadingDiv = chart.loadingDiv;
+ loadingDiv = chart.loadingDiv,
+ loadingOptions = options.loading,
+ setLoadingSize = function () {
+ if (loadingDiv) {
+ css(loadingDiv, {
+ left: chart.plotLeft + PX,
+ top: chart.plotTop + PX,
+ width: chart.plotWidth + PX,
+ height: chart.plotHeight + PX
+ });
+ }
+ };
- var loadingOptions = options.loading;
-
// create the layer at the first call
if (!loadingDiv) {
chart.loadingDiv = loadingDiv = createElement(DIV, {
className: PREFIX + 'loading'
}, extend(loadingOptions.style, {
@@ -14449,33 +14420,30 @@
'span',
null,
loadingOptions.labelStyle,
loadingDiv
);
-
+ addEvent(chart, 'redraw', setLoadingSize); // #1080
}
// update text
chart.loadingSpan.innerHTML = str || options.lang.loading;
// show it
if (!chart.loadingShown) {
css(loadingDiv, {
opacity: 0,
- display: '',
- left: chart.plotLeft + PX,
- top: chart.plotTop + PX,
- width: chart.plotWidth + PX,
- height: chart.plotHeight + PX
+ display: ''
});
animate(loadingDiv, {
opacity: loadingOptions.style.opacity
}, {
duration: loadingOptions.showDuration || 0
});
chart.loadingShown = true;
}
+ setLoadingSize();
},
/**
* Hide the loading layer
*/
@@ -14740,18 +14708,26 @@
/**
* Update the series with a new set of options
*/
update: function (newOptions, redraw) {
- var chart = this.chart,
+ var series = this,
+ chart = this.chart,
// must use user options when changing type because this.options is merged
// in with type specific plotOptions
oldOptions = this.userOptions,
oldType = this.type,
proto = seriesTypes[oldType].prototype,
+ preserve = ['group', 'markerGroup', 'dataLabelsGroup'],
n;
+ // Make sure groups are not destroyed (#3094)
+ each(preserve, function (prop) {
+ preserve[prop] = series[prop];
+ delete series[prop];
+ });
+
// Do the merge, with some forced options
newOptions = merge(oldOptions, {
animation: false,
index: this.index,
pointStart: this.xData[0] // when updating after addPoint
@@ -14764,12 +14740,18 @@
this[n] = UNDEFINED;
}
}
extend(this, seriesTypes[newOptions.type || oldType].prototype);
+ // Re-register groups (#3094)
+ each(preserve, function (prop) {
+ series[prop] = preserve[prop];
+ });
+
this.init(chart, newOptions);
+ chart.linkSeries(); // Links are lost in this.remove (#3028)
if (pick(redraw, true)) {
chart.redraw(false);
}
}
});
@@ -14872,22 +14854,22 @@
* For stacks, don't split segments on null values. Instead, draw null values with
* no marker. Also insert dummy points for any X position that exists in other series
* in the stack.
*/
getSegments: function () {
- var segments = [],
+ var series = this,
+ segments = [],
segment = [],
keys = [],
xAxis = this.xAxis,
yAxis = this.yAxis,
stack = yAxis.stacks[this.stackKey],
pointMap = {},
plotX,
plotY,
points = this.points,
connectNulls = this.options.connectNulls,
- val,
i,
x;
if (this.options.stacking && !this.cropped) { // cropped causes artefacts in Stock, and perf issue
// Create a map where we can quickly look up the points by their X value.
@@ -14904,10 +14886,13 @@
keys.sort(function (a, b) {
return a - b;
});
each(keys, function (x) {
+ var y = 0,
+ stackPoint;
+
if (connectNulls && (!pointMap[x] || pointMap[x].y === null)) { // #1836
return;
// The point exists, push it to the segment
} else if (pointMap[x]) {
@@ -14915,13 +14900,23 @@
// There is no point for this X value in this series, so we
// insert a dummy point in order for the areas to be drawn
// correctly.
} else {
+
+ // Loop down the stack to find the series below this one that has
+ // a value (#1991)
+ for (i = series.index; i <= yAxis.series.length; i++) {
+ stackPoint = stack[x].points[i + ',' + x];
+ if (stackPoint) {
+ y = stackPoint[1];
+ break;
+ }
+ }
+
plotX = xAxis.translate(x);
- val = stack[x].percent ? (stack[x].total ? stack[x].cum * 100 / stack[x].total : 0) : stack[x].cum; // #1991
- plotY = yAxis.toPixels(val, true);
+ plotY = yAxis.toPixels(y, true);
segment.push({
y: null,
plotX: plotX,
clientX: plotX,
plotY: plotY,
@@ -15353,32 +15348,38 @@
threshold = options.threshold,
translatedThreshold = series.translatedThreshold = yAxis.getThreshold(threshold),
minPointLength = pick(options.minPointLength, 5),
metrics = series.getColumnMetrics(),
pointWidth = metrics.width,
- seriesBarW = series.barW = mathCeil(mathMax(pointWidth, 1 + 2 * borderWidth)), // rounded and postprocessed for border width
+ seriesBarW = series.barW = mathMax(pointWidth, 1 + 2 * borderWidth), // postprocessed for border width
pointXOffset = series.pointXOffset = metrics.offset,
xCrisp = -(borderWidth % 2 ? 0.5 : 0),
yCrisp = borderWidth % 2 ? 0.5 : 1;
if (chart.renderer.isVML && chart.inverted) {
yCrisp += 1;
}
+ // When the pointPadding is 0, we want the columns to be packed tightly, so we allow individual
+ // columns to have individual sizes. When pointPadding is greater, we strive for equal-width
+ // columns (#2694).
+ if (options.pointPadding) {
+ seriesBarW = mathCeil(seriesBarW);
+ }
+
Series.prototype.translate.apply(series);
- // record the new values
+ // Record the new values
each(series.points, function (point) {
var yBottom = pick(point.yBottom, translatedThreshold),
plotY = mathMin(mathMax(-999 - yBottom, point.plotY), yAxis.len + 999 + yBottom), // Don't draw too far outside plot area (#1303, #2241)
barX = point.plotX + pointXOffset,
barW = seriesBarW,
barY = mathMin(plotY, yBottom),
right,
bottom,
fromTop,
- fromLeft,
barH = mathMax(plotY, yBottom) - barY;
// Handle options.minPointLength
if (mathAbs(barH) < minPointLength) {
if (minPointLength) {
@@ -15395,26 +15396,21 @@
point.pointWidth = pointWidth;
// Fix the tooltip on center of grouped columns (#1216)
point.tooltipPos = chart.inverted ? [yAxis.len - plotY, series.xAxis.len - barX - barW / 2] : [barX + barW / 2, plotY];
- // Round off to obtain crisp edges
- fromLeft = mathAbs(barX) < 0.5;
+ // Round off to obtain crisp edges and avoid overlapping with neighbours (#2694)
right = mathRound(barX + barW) + xCrisp;
barX = mathRound(barX) + xCrisp;
barW = right - barX;
fromTop = mathAbs(barY) < 0.5;
bottom = mathRound(barY + barH) + yCrisp;
barY = mathRound(barY) + yCrisp;
barH = bottom - barY;
- // Top and left edges are exceptions
- if (fromLeft) {
- barX += 1;
- barW -= 1;
- }
+ // Top edges are exceptions
if (fromTop) {
barY -= 1;
barH += 1;
}
@@ -15424,10 +15420,11 @@
x: barX,
y: barY,
width: barW,
height: barH
};
+
});
},
getSymbol: noop,
@@ -15453,24 +15450,27 @@
chart = this.chart,
options = series.options,
renderer = chart.renderer,
animationLimit = options.animationLimit || 250,
shapeArgs,
- pointAttr,
- borderAttr;
+ pointAttr;
// draw the columns
each(series.points, function (point) {
var plotY = point.plotY,
- graphic = point.graphic;
+ graphic = point.graphic,
+ borderAttr;
if (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {
shapeArgs = point.shapeArgs;
+
borderAttr = defined(series.borderWidth) ? {
'stroke-width': series.borderWidth
} : {};
+
pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE] || series.pointAttr[NORMAL_STATE];
+
if (graphic) { // update
stop(graphic);
graphic.attr(borderAttr)[chart.pointCount < animationLimit ? 'animate' : 'attr'](merge(shapeArgs));
} else {
@@ -15560,11 +15560,11 @@
* Set the default options for scatter
*/
defaultPlotOptions.scatter = merge(defaultSeriesOptions, {
lineWidth: 0,
tooltip: {
- headerFormat: '<span style="color:{series.color}">\u25CF</span> <span style="font-size: 10px;"> {series.name}</span><br/>', // docs
+ headerFormat: '<span style="color:{series.color}">\u25CF</span> <span style="font-size: 10px;"> {series.name}</span><br/>',
pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>'
},
stickyTracking: false
});
@@ -15574,11 +15574,11 @@
var ScatterSeries = extendClass(Series, {
type: 'scatter',
sorted: false,
requireSorting: false,
noSharedTooltip: true,
- trackerGroups: ['markerGroup'],
+ trackerGroups: ['markerGroup', 'dataLabelsGroup'],
takeOrdinalPosition: false, // #2342
singularTooltips: true,
drawGraph: function () {
if (this.options.lineWidth) {
Series.prototype.drawGraph.call(this);
@@ -15602,11 +15602,11 @@
// connectorWidth: 1,
// connectorColor: point.color,
// connectorPadding: 5,
distance: 30,
enabled: true,
- formatter: function () {
+ formatter: function () { // #2945
return this.point.name;
}
// softConnector: true,
//y: 0
},
@@ -15731,11 +15731,11 @@
haloPath: function (size) {
var shapeArgs = this.shapeArgs,
chart = this.series.chart;
- return this.series.chart.renderer.symbols.arc(chart.plotLeft + shapeArgs.x, chart.plotTop + shapeArgs.y, shapeArgs.r + size, shapeArgs.r + size, {
+ return this.sliced || !this.visible ? [] : this.series.chart.renderer.symbols.arc(chart.plotLeft + shapeArgs.x, chart.plotTop + shapeArgs.y, shapeArgs.r + size, shapeArgs.r + size, {
innerR: this.shapeArgs.r,
start: shapeArgs.start,
end: shapeArgs.end
});
}
@@ -16080,18 +16080,21 @@
// Create a separate group for the data labels to avoid rotation
dataLabelsGroup = series.plotGroup(
'dataLabelsGroup',
'data-labels',
- HIDDEN,
+ options.defer ? HIDDEN : VISIBLE,
options.zIndex || 6
);
if (!series.hasRendered && pick(options.defer, true)) {
dataLabelsGroup.attr({ opacity: 0 });
addEvent(series, 'afterAnimate', function () {
- series.dataLabelsGroup.show()[seriesOptions.animation ? 'animate' : 'attr']({ opacity: 1 }, { duration: 200 });
+ if (series.visible) { // #3023, #3024
+ dataLabelsGroup.show();
+ }
+ dataLabelsGroup[seriesOptions.animation ? 'animate' : 'attr']({ opacity: 1 }, { duration: 200 });
});
}
// Make the labels for each point
generalOptions = options;
@@ -16226,16 +16229,17 @@
height: bBox.height
});
// Allow a hook for changing alignment in the last moment, then do the alignment
if (options.rotation) { // Fancy box alignment isn't supported for rotated text
- alignAttr = {
- align: options.align,
- x: alignTo.x + options.x + alignTo.width / 2,
- y: alignTo.y + options.y + alignTo.height / 2
- };
- dataLabel[isNew ? 'attr' : 'animate'](alignAttr);
+ dataLabel[isNew ? 'attr' : 'animate']({
+ x: alignTo.x + options.x + alignTo.width / 2,
+ y: alignTo.y + options.y + alignTo.height / 2
+ })
+ .attr({ // #3003
+ align: options.align
+ });
} else {
dataLabel.align(options, null, alignTo);
alignAttr = dataLabel.alignAttr;
// Handle justify or crop
@@ -16373,17 +16377,10 @@
if (point.dataLabel && point.visible) { // #407, #2510
halves[point.half].push(point);
}
});
- // assume equal label heights
- i = 0;
- while (!labelHeight && data[i]) { // #1569
- labelHeight = data[i] && data[i].dataLabel && (data[i].dataLabel.getBBox().height || 21); // 21 is for #968
- i++;
- }
-
/* Loop over the points in each half, starting from the top and bottom
* of the pie to detect overlapping labels.
*/
i = 2;
while (i--) {
@@ -16391,42 +16388,69 @@
var slots = [],
slotsLength,
usedSlots = [],
points = halves[i],
pos,
+ bottom,
length = points.length,
slotIndex;
+ if (!length) {
+ continue;
+ }
+
// Sort by angle
series.sortByAngle(points, i - 0.5);
+ // Assume equal label heights on either hemisphere (#2630)
+ j = labelHeight = 0;
+ while (!labelHeight && points[j]) { // #1569
+ labelHeight = points[j] && points[j].dataLabel && (points[j].dataLabel.getBBox().height || 21); // 21 is for #968
+ j++;
+ }
+
// Only do anti-collision when we are outside the pie and have connectors (#856)
if (distanceOption > 0) {
- // build the slots
- for (pos = centerY - radius - distanceOption; pos <= centerY + radius + distanceOption; pos += labelHeight) {
+ // Build the slots
+ bottom = mathMin(centerY + radius + distanceOption, chart.plotHeight);
+ for (pos = mathMax(0, centerY - radius - distanceOption); pos <= bottom; pos += labelHeight) {
slots.push(pos);
+ }
+ slotsLength = slots.length;
- // visualize the slot
- /*
+
+ /* Visualize the slots
+ if (!series.slotElements) {
+ series.slotElements = [];
+ }
+ if (i === 1) {
+ series.slotElements.forEach(function (elem) {
+ elem.destroy();
+ });
+ series.slotElements.length = 0;
+ }
+
+ slots.forEach(function (pos, no) {
var slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0),
slotY = pos + chart.plotTop;
+
if (!isNaN(slotX)) {
- chart.renderer.rect(slotX, slotY - 7, 100, labelHeight, 1)
+ series.slotElements.push(chart.renderer.rect(slotX, slotY - 7, 100, labelHeight, 1)
.attr({
'stroke-width': 1,
- stroke: 'silver'
+ stroke: 'silver',
+ fill: 'rgba(0,0,255,0.1)'
})
- .add();
- chart.renderer.text('Slot '+ (slots.length - 1), slotX, slotY + 4)
+ .add());
+ series.slotElements.push(chart.renderer.text('Slot '+ no, slotX, slotY + 4)
.attr({
fill: 'silver'
- }).add();
+ }).add());
}
- */
- }
- slotsLength = slots.length;
+ });
+ // */
// if there are more values than available slots, remove lowest values
if (length > slotsLength) {
// create an array for sorting and ranking the points within each quarter
rankArr = [].concat(points);
@@ -16506,22 +16530,22 @@
// if the slot next to currrent slot is free, the y value is allowed
// to fall back to the natural position
y = slot.y;
if ((naturalY > y && slots[slotIndex + 1] !== null) ||
(naturalY < y && slots[slotIndex - 1] !== null)) {
- y = naturalY;
+ y = mathMin(mathMax(0, naturalY), chart.plotHeight);
}
} else {
y = naturalY;
}
// get the x - use the natural x position for first and last slot, to prevent the top
// and botton slice connectors from touching each other on either side
x = options.justify ?
seriesCenter[0] + (i ? -1 : 1) * (radius + distanceOption) :
- series.getX(slotIndex === 0 || slotIndex === slots.length - 1 ? naturalY : y, i);
+ series.getX(y === centerY - radius - distanceOption || y === centerY + radius + distanceOption ? naturalY : y, i);
// Record the placement and visibility
dataLabel._attr = {
visibility: visibility,
@@ -17190,13 +17214,13 @@
*/
onMouseOut: function () {
var chart = this.series.chart,
hoverPoints = chart.hoverPoints;
- if (!hoverPoints || inArray(this, hoverPoints) === -1) { // #887
- this.firePointEvent('mouseOut');
+ this.firePointEvent('mouseOut');
+ if (!hoverPoints || inArray(this, hoverPoints) === -1) { // #887, #2240
this.setState();
chart.hoverPoint = null;
}
},
@@ -17437,10 +17461,10 @@
if (stateOptions[state] && stateOptions[state].enabled === false) {
return;
}
if (state) {
- lineWidth = stateOptions[state].lineWidth || lineWidth + 1;
+ lineWidth = stateOptions[state].lineWidth || lineWidth + (stateOptions[state].lineWidthPlus || 0);
}
if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
attribs = {
'stroke-width': lineWidth