diff --git a/app/assets/javascripts/highcharts.js b/app/assets/javascripts/highcharts.js
index 0a660aa..4d3d408 100644
--- a/app/assets/javascripts/highcharts.js
+++ b/app/assets/javascripts/highcharts.js
@@ -1,48 +1,77 @@
/**
- * @license Highcharts JS v6.0.3 (2017-11-14)
+ * @license Highcharts JS v7.0.3 (2019-02-06)
*
- * (c) 2009-2016 Torstein Honsi
+ * (c) 2009-2018 Torstein Honsi
*
* License: www.highcharts.com/license
*/
'use strict';
-(function(root, factory) {
+(function (root, factory) {
if (typeof module === 'object' && module.exports) {
+ factory['default'] = factory;
module.exports = root.document ?
factory(root) :
factory;
+ } else if (typeof define === 'function' && define.amd) {
+ define(function () {
+ return factory(root);
+ });
} else {
root.Highcharts = factory(root);
}
-}(typeof window !== 'undefined' ? window : this, function(win) {
- var Highcharts = (function() {
+}(typeof window !== 'undefined' ? window : this, function (win) {
+ var Highcharts = (function () {
/**
- * (c) 2010-2017 Torstein Honsi
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+ /**
+ * Reference to the global SVGElement class as a workaround for a name conflict
+ * in the Highcharts namespace.
+ *
+ * @global
+ * @typedef {global.SVGElement} GlobalSVGElement
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGElement
+ */
+
+
/* global win, window */
// glob is a temporary fix to allow our es-modules to work.
- var glob = typeof win === 'undefined' ? window : win,
+ var glob = typeof win === 'undefined' ?
+ (typeof window !== 'undefined' ? window : {}) :
+ win,
doc = glob.document,
SVG_NS = 'http://www.w3.org/2000/svg',
userAgent = (glob.navigator && glob.navigator.userAgent) || '',
- svg = doc && doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,
+ svg = (
+ doc &&
+ doc.createElementNS &&
+ !!doc.createElementNS(SVG_NS, 'svg').createSVGRect
+ ),
isMS = /(edge|msie|trident)/i.test(userAgent) && !glob.opera,
- isFirefox = /Firefox/.test(userAgent),
- hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4; // issue #38;
+ isFirefox = userAgent.indexOf('Firefox') !== -1,
+ isChrome = userAgent.indexOf('Chrome') !== -1,
+ hasBidiBug = (
+ isFirefox &&
+ parseInt(userAgent.split('Firefox/')[1], 10) < 4 // issue #38
+ );
var Highcharts = glob.Highcharts ? glob.Highcharts.error(16, true) : {
product: 'Highcharts',
- version: '6.0.3',
+ version: '7.0.3',
deg2rad: Math.PI * 2 / 360,
doc: doc,
hasBidiBug: hasBidiBug,
hasTouch: doc && doc.documentElement.ontouchstart !== undefined,
isMS: isMS,
- isWebKit: /AppleWebKit/.test(userAgent),
+ isWebKit: userAgent.indexOf('AppleWebKit') !== -1,
isFirefox: isFirefox,
+ isChrome: isChrome,
+ isSafari: !isChrome && userAgent.indexOf('Safari') !== -1,
isTouchDevice: /(Mobile|Android|Windows Phone)/.test(userAgent),
SVG_NS: SVG_NS,
chartCount: 0,
@@ -51,27 +80,365 @@
svg: svg,
win: glob,
marginNames: ['plotTop', 'marginRight', 'marginBottom', 'plotLeft'],
- noop: function() {
+ noop: function () {
return undefined;
},
/**
* An array containing the current chart objects in the page. A chart's
* position in the array is preserved throughout the page's lifetime. When
* a chart is destroyed, the array item becomes `undefined`.
- * @type {Array.}
- * @memberOf Highcharts
+ *
+ * @name Highcharts.charts
+ * @type {Array}
*/
charts: []
};
+
+
return Highcharts;
}());
- (function(H) {
+ (function (H) {
+ /* *
+ *
+ * (c) 2010-2019 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ *
+ * */
+
/**
- * (c) 2010-2017 Torstein Honsi
+ * An animation configuration. Animation configurations can also be defined as
+ * booleans, where `false` turns off animation and `true` defaults to a duration
+ * of 500ms.
*
- * License: www.highcharts.com/license
+ * @interface Highcharts.AnimationOptionsObject
+ *//**
+ * A callback function to exectute when the animation finishes.
+ * @name Highcharts.AnimationOptionsObject#complete
+ * @type {Function|undefined}
+ *//**
+ * The animation duration in milliseconds.
+ * @name Highcharts.AnimationOptionsObject#duration
+ * @type {number}
+ *//**
+ * The name of an easing function as defined on the `Math` object.
+ * @name Highcharts.AnimationOptionsObject#easing
+ * @type {string|undefined}
+ *//**
+ * A callback function to execute on each step of each attribute or CSS property
+ * that's being animated. The first argument contains information about the
+ * animation and progress.
+ * @name Highcharts.AnimationOptionsObject#step
+ * @type {Function|undefined}
+ */
+
+ /**
+ * A style object with camel case property names to define visual appearance of
+ * a SVG element or HTML element. The properties can be whatever styles are
+ * supported on the given SVG or HTML element.
+ *
+ * @example
+ * {
+ * fontFamily: 'monospace',
+ * fontSize: '1.2em'
+ * }
+ *
+ * @interface Highcharts.CSSObject
+ *//**
+ * @name Highcharts.CSSObject#[key:string]
+ * @type {boolean|number|string|undefined}
+ *//**
+ * Background style for the element.
+ * @name Highcharts.CSSObject#background
+ * @type {string|undefined}
+ *//**
+ * Background color of the element.
+ * @name Highcharts.CSSObject#backgroundColor
+ * @type {Highcharts.ColorString|undefined}
+ *//**
+ * Border style for the element.
+ * @name Highcharts.CSSObject#border
+ * @type {string|undefined}
+ *//**
+ * Radius of the element border.
+ * @name Highcharts.CSSObject#borderRadius
+ * @type {number|undefined}
+ *//**
+ * Color used in the element. The "contrast" option is a Highcharts custom
+ * property that results in black or white, depending on the background of the
+ * element.
+ * @name Highcharts.CSSObject#color
+ * @type {"contrast"|Highcharts.ColorString|undefined}
+ *//**
+ * Style of the mouse cursor when resting over the element.
+ * @name Highcharts.CSSObject#cursor
+ * @type {Highcharts.CursorType|undefined}
+ *//**
+ * Font family of the element text. Multiple values have to be in decreasing
+ * preference order and separated by comma.
+ * @name Highcharts.CSSObject#fontFamily
+ * @type {string|undefined}
+ *//**
+ * Font size of the element text.
+ * @name Highcharts.CSSObject#fontSize
+ * @type {string|undefined}
+ *//**
+ * Font weight of the element text.
+ * @name Highcharts.CSSObject#fontWeight
+ * @type {string|undefined}
+ *//**
+ * Height of the element.
+ * @name Highcharts.CSSObject#height
+ * @type {number|undefined}
+ *//**
+ * Width of the element border.
+ * @name Highcharts.CSSObject#lineWidth
+ * @type {number|undefined}
+ *//**
+ * Opacity of the element.
+ * @name Highcharts.CSSObject#opacity
+ * @type {number|undefined}
+ *//**
+ * Space around the element content.
+ * @name Highcharts.CSSObject#padding
+ * @type {string|undefined}
+ *//**
+ * Behaviour of the element when the mouse cursor rests over it.
+ * @name Highcharts.CSSObject#pointerEvents
+ * @type {string|undefined}
+ *//**
+ * Positioning of the element.
+ * @name Highcharts.CSSObject#position
+ * @type {string|undefined}
+ *//**
+ * Alignment of the element text.
+ * @name Highcharts.CSSObject#textAlign
+ * @type {string|undefined}
+ *//**
+ * Outline style of the element text.
+ * @name Highcharts.CSSObject#textOutline
+ * @type {string|undefined}
+ *//**
+ * Additional decoration of the element text.
+ * @name Highcharts.CSSObject#textDecoration
+ * @type {string|undefined}
+ *//**
+ * Line break style of the element text. Highcharts SVG elements support
+ * `ellipsis` when a `width` is set.
+ * @name Highcharts.CSSObject#textOverflow
+ * @type {string|undefined}
+ *//**
+ * Top spacing of the element relative to the parent element.
+ * @name Highcharts.CSSObject#top
+ * @type {string|undefined}
+ *//**
+ * Animated transition of selected element properties.
+ * @name Highcharts.CSSObject#transition
+ * @type {string|undefined}
+ *//**
+ * Line break style of the element text.
+ * @name Highcharts.CSSObject#whiteSpace
+ * @type {string|undefined}
+ *//**
+ * Width of the element.
+ * @name Highcharts.CSSObject#width
+ * @type {number|undefined}
+ */
+
+ /**
+ * All possible cursor styles.
+ *
+ * @typedef {"alias"|"all-scroll"|"auto"|"cell"|"col-resize"|"context-menu"|"copy"|"crosshair"|"default"|"e-resize"|"ew-resize"|"grab"|"grabbing"|"help"|"move"|"n-resize"|"ne-resize"|"nesw-resize"|"no-drop"|"none"|"not-allowed"|"ns-resize"|"nw-resize"|"nwse-resize"|"pointer"|"progress"|"row-resize"|"s-resize"|"se-resize"|"sw-resize"|"text"|"vertical-text"|"w-resize"|"wait"|"zoom-in"|"zoom-out"} Highcharts.CursorType
+ */
+
+ /**
+ * All possible dash styles.
+ *
+ * @typedef {"Dash"|"DashDot"|"Dot"|"LongDash"|"LongDashDot"|"LongDashDotDot"|"ShortDash"|"ShortDashDot"|"ShortDashDotDot"|"ShortDot"|"Solid"} Highcharts.DashStyleType
+ */
+
+ /**
+ * Generic dictionary in TypeScript notation.
+ *
+ * @interface Highcharts.Dictionary
+ *//**
+ * @name Highcharts.Dictionary#[key:string]
+ * @type {T}
+ */
+
+ /**
+ * The function callback to execute when the event is fired. The `this` context
+ * contains the instance, that fired the event.
+ *
+ * @callback Highcharts.EventCallbackFunction
+ *
+ * @param {T} this
+ *
+ * @param {Highcharts.Dictionary<*>} [eventArguments]
+ * Event arguments.
+ */
+
+ /**
+ * The event options for adding function callback.
+ *
+ * @interface Highcharts.EventOptionsObject
+ *//**
+ * The order the event handler should be called. This opens for having one
+ * handler be called before another, independent of in which order they were
+ * added.
+ * @name Highcharts.EventOptionsObject#order
+ * @type {number}
+ */
+
+ /**
+ * Formats data as a string. Usually the data is accessible throught the `this`
+ * keyword.
+ *
+ * @callback Highcharts.FormatterCallbackFunction
+ *
+ * @param {T} this
+ *
+ * @return {string}
+ */
+
+ /**
+ * An object of key-value pairs for HTML attributes.
+ *
+ * @typedef {Highcharts.Dictionary} Highcharts.HTMLAttributes
+ */
+
+ /**
+ * An HTML DOM element. The type is a reference to the regular HTMLElement in
+ * the global scope.
+ *
+ * @typedef {global.HTMLElement} Highcharts.HTMLDOMElement
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement
+ */
+
+ /**
+ * The iterator callback.
+ *
+ * @callback Highcharts.ObjectEachCallbackFunction
+ *
+ * @param {*} value
+ * The property value.
+ *
+ * @param {string} key
+ * The property key.
+ *
+ * @param {*} obj
+ * The object that objectEach is being applied to.
+ */
+
+ /**
+ * An object containing `left` and `top` properties for the position in the
+ * page.
+ *
+ * @interface Highcharts.OffsetObject
+ *//**
+ * Left distance to the page border.
+ * @name Highcharts.OffsetObject#left
+ * @type {number}
+ *//**
+ * Top distance to the page border.
+ * @name Highcharts.OffsetObject#top
+ * @type {number}
+ */
+
+ /**
+ * If a number is given, it defines the pixel length. If a percentage string is
+ * given, like for example `'50%'`, the setting defines a length relative to a
+ * base size, for example the size of a container.
+ *
+ * @typedef {number|string} Highcharts.RelativeSize
+ */
+
+ /**
+ * An object of key-value pairs for SVG attributes. Attributes in Highcharts
+ * elements for the most parts correspond to SVG, but some are specific to
+ * Highcharts, like `zIndex`, `rotation`, `rotationOriginX`,
+ * `rotationOriginY`, `translateX`, `translateY`, `scaleX` and `scaleY`. SVG
+ * attributes containing a hyphen are _not_ camel-cased, they should be
+ * quoted to preserve the hyphen.
+ *
+ * @example
+ * {
+ * 'stroke': '#ff0000', // basic
+ * 'stroke-width': 2, // hyphenated
+ * 'rotation': 45 // custom
+ * 'd': ['M', 10, 10, 'L', 30, 30, 'z'] // path definition, note format
+ * }
+ *
+ * @interface Highcharts.SVGAttributes
+ *//**
+ * @name Highcharts.SVGAttributes#[key:string]
+ * @type {boolean|number|string|Array|Dictionary|undefined}
+ *//**
+ * @name Highcharts.SVGAttributes#d
+ * @type {string|Highcharts.SVGPathArray|undefined}
+ *//**
+ * @name Highcharts.SVGAttributes#inverted
+ * @type {boolean|undefined}
+ *//**
+ * @name Highcharts.SVGAttributes#matrix
+ * @type {Array|undefined}
+ *//**
+ * @name Highcharts.SVGAttributes#rotation
+ * @type {string|undefined}
+ *//**
+ * @name Highcharts.SVGAttributes#rotationOriginX
+ * @type {number|undefined}
+ *//**
+ * @name Highcharts.SVGAttributes#rotationOriginY
+ * @type {number|undefined}
+ *//**
+ * @name Highcharts.SVGAttributes#scaleX
+ * @type {number|undefined}
+ *//**
+ * @name Highcharts.SVGAttributes#scaleY
+ * @type {number|undefined}
+ *//**
+ * @name Highcharts.SVGAttributes#stroke
+ * @type {Highcharts.ColorString|undefined}
+ *//**
+ * @name Highcharts.SVGAttributes#style
+ * @type {string|Highcharts.CSSObject|undefined}
+ *//**
+ * @name Highcharts.SVGAttributes#translateX
+ * @type {number|undefined}
+ *//**
+ * @name Highcharts.SVGAttributes#translateY
+ * @type {number|undefined}
+ *//**
+ * @name Highcharts.SVGAttributes#zIndex
+ * @type {number|undefined}
+ */
+
+ /**
+ * An SVG DOM element. The type is a reference to the regular SVGElement in the
+ * global scope.
+ *
+ * @typedef {globals.GlobalSVGElement} Highcharts.SVGDOMElement
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGElement
*/
- /* eslint max-len: ["warn", 80, 4] */
+
+ /**
+ * Array of path commands, that will go into the `d` attribute of an SVG
+ * element.
+ *
+ * @typedef {Array} Highcharts.SVGPathArray
+ */
+
+ /**
+ * Possible path commands in a SVG path array.
+ *
+ * @typedef {string} Highcharts.SVGPathCommand
+ * @validvalue ["a","c","h","l","m","q","s","t","v","z","A","C","H","L","M","Q","S","T","V","Z"]
+ */
+
+
/**
* The Highcharts object is the placeholder for all other members, and various
@@ -80,7 +447,7 @@
*
* @example
* var chart = Highcharts.chart('container', { ... });
- *
+ *
* @namespace Highcharts
*/
@@ -94,21 +461,34 @@
* Provide error messages for debugging, with links to online explanation. This
* function can be overridden to provide custom error handling.
*
- * @function #error
- * @memberOf Highcharts
- * @param {Number|String} code - The error code. See [errors.xml]{@link
- * https://github.com/highcharts/highcharts/blob/master/errors/errors.xml}
- * for available codes. If it is a string, the error message is printed
- * directly in the console.
- * @param {Boolean} [stop=false] - Whether to throw an error or just log a
- * warning in the console.
+ * @sample highcharts/chart/highcharts-error/
+ * Custom error handler
*
- * @sample highcharts/chart/highcharts-error/ Custom error handler
+ * @function Highcharts.error
+ *
+ * @param {number|string} code
+ * The error code. See
+ * [errors.xml](https://github.com/highcharts/highcharts/blob/master/errors/errors.xml)
+ * for available codes. If it is a string, the error message is printed
+ * directly in the console.
+ *
+ * @param {boolean} [stop=false]
+ * Whether to throw an error or just log a warning in the console.
+ *
+ * @param {Highcharts.Chart} [chart]
+ * Reference to the chart that causes the error. Used in 'debugger'
+ * module to display errors directly on the chart.
+ * Important note: This argument is undefined for errors that lack
+ * access to the Chart instance.
*/
- H.error = function(code, stop) {
+ H.error = function (code, stop, chart) {
var msg = H.isNumber(code) ?
'Highcharts error #' + code + ': www.highcharts.com/errors/' + code :
code;
+
+ if (chart) {
+ H.fireEvent(chart, 'displayError', { code: code });
+ }
if (stop) {
throw new Error(msg);
}
@@ -123,18 +503,23 @@
* (attribute or style prop) on one element. Animation is always initiated
* through {@link SVGElement#animate}.
*
- * @constructor Fx
- * @memberOf Highcharts
- * @param {HTMLDOMElement|SVGElement} elem - The element to animate.
- * @param {AnimationOptions} options - Animation options.
- * @param {string} prop - The single attribute or CSS property to animate.
- * @private
- *
* @example
* var rect = renderer.rect(0, 0, 10, 10).add();
* rect.animate({ width: 100 });
+ *
+ * @private
+ * @class Highcharts.Fx
+ *
+ * @param {Highcharts.HTMLDOMElement|Highcharts.SVGElement} elem
+ * The element to animate.
+ *
+ * @param {Highcharts.AnimationOptionsObject} options
+ * Animation options.
+ *
+ * @param {string} prop
+ * The single attribute or CSS property to animate.
*/
- H.Fx = function(elem, options, prop) {
+ H.Fx = function (elem, options, prop) {
this.options = options;
this.elem = elem;
this.prop = prop;
@@ -144,10 +529,9 @@
/**
* Set the current step of a path definition on SVGElement.
*
- * @function #dSetter
- * @memberOf Highcharts.Fx
+ * @function Highcharts.Fx#dSetter
*/
- dSetter: function() {
+ dSetter: function () {
var start = this.paths[0],
end = this.paths[1],
ret = [],
@@ -164,11 +548,11 @@
startVal = parseFloat(start[i]);
ret[i] =
isNaN(startVal) ? // a letter instruction like M or L
- end[i] :
- now * (parseFloat(end[i] - startVal)) + startVal;
+ end[i] :
+ now * (parseFloat(end[i] - startVal)) + startVal;
}
- // If animation is finished or length not matching, land on right value
+ // If animation is finished or length not matching, land on right value
} else {
ret = end;
}
@@ -178,10 +562,9 @@
/**
* Update the element with the current animation step.
*
- * @function #update
- * @memberOf Highcharts.Fx
+ * @function Highcharts.Fx#update
*/
- update: function() {
+ update: function () {
var elem = this.elem,
prop = this.prop, // if destroyed, it is null
now = this.now,
@@ -191,13 +574,13 @@
if (this[prop + 'Setter']) {
this[prop + 'Setter']();
- // Other animations on SVGElement
+ // Other animations on SVGElement
} else if (elem.attr) {
if (elem.element) {
elem.attr(prop, now, null, true);
}
- // HTML styles, raw HTML content like container size
+ // HTML styles, raw HTML content like container size
} else {
elem.style[prop] = now + this.unit;
}
@@ -211,38 +594,44 @@
/**
* Run an animation.
*
- * @function #run
- * @memberOf Highcharts.Fx
- * @param {Number} from - The current value, value to start from.
- * @param {Number} to - The end value, value to land on.
- * @param {String} [unit] - The property unit, for example `px`.
- *
+ * @function Highcharts.Fx#run
+ *
+ * @param {number} from
+ * The current value, value to start from.
+ *
+ * @param {number} to
+ * The end value, value to land on.
+ *
+ * @param {string} [unit]
+ * The property unit, for example `px`.
*/
- run: function(from, to, unit) {
+ run: function (from, to, unit) {
var self = this,
options = self.options,
- timer = function(gotoEnd) {
+ timer = function (gotoEnd) {
return timer.stopped ? false : self.step(gotoEnd);
},
requestAnimationFrame =
- win.requestAnimationFrame ||
- function(step) {
- setTimeout(step, 13);
- },
- step = function() {
- H.timers = H.grep(H.timers, function(timer) {
- return timer();
- });
+ win.requestAnimationFrame ||
+ function (step) {
+ setTimeout(step, 13);
+ },
+ step = function () {
+ for (var i = 0; i < H.timers.length; i++) {
+ if (!H.timers[i]()) {
+ H.timers.splice(i--, 1);
+ }
+ }
if (H.timers.length) {
requestAnimationFrame(step);
}
};
- if (from === to) {
+ if (from === to && !this.elem['forceAnimate:' + this.prop]) {
delete options.curAnim[this.prop];
- if (options.complete && H.keys(options.curAnim).length === 0) {
- options.complete();
+ if (options.complete && Object.keys(options.curAnim).length === 0) {
+ options.complete.call(this.elem);
}
} else { // #7166
this.startTime = +new Date();
@@ -264,13 +653,15 @@
/**
* Run a single step in the animation.
*
- * @function #step
- * @memberOf Highcharts.Fx
- * @param {Boolean} [gotoEnd] - Whether to go to the endpoint of the
- * animation after abort.
- * @returns {Boolean} Returns `true` if animation continues.
+ * @function Highcharts.Fx#step
+ *
+ * @param {boolean} [gotoEnd]
+ * Whether to go to the endpoint of the animation after abort.
+ *
+ * @return {boolean}
+ * Returns `true` if animation continues.
*/
- step: function(gotoEnd) {
+ step: function (gotoEnd) {
var t = +new Date(),
ret,
done,
@@ -292,7 +683,7 @@
done = true;
- H.objectEach(curAnim, function(val) {
+ H.objectEach(curAnim, function (val) {
if (val !== true) {
done = false;
}
@@ -315,15 +706,22 @@
/**
* Prepare start and end values so that the path can be animated one to one.
*
- * @function #initPath
- * @memberOf Highcharts.Fx
- * @param {SVGElement} elem - The SVGElement item.
- * @param {String} fromD - Starting path definition.
- * @param {Array} toD - Ending path definition.
- * @returns {Array} An array containing start and end paths in array form
- * so that they can be animated in parallel.
+ * @function Highcharts.Fx#initPath
+ *
+ * @param {Highcharts.SVGElement} elem
+ * The SVGElement item.
+ *
+ * @param {string} fromD
+ * Starting path definition.
+ *
+ * @param {Highcharts.SVGPathArray} toD
+ * Ending path definition.
+ *
+ * @return {Array}
+ * An array containing start and end paths in array form so that
+ * they can be animated in parallel.
*/
- initPath: function(elem, fromD, toD) {
+ initPath: function (elem, fromD, toD) {
fromD = fromD || '';
var shift,
startX = elem.startX,
@@ -346,6 +744,7 @@
function sixify(arr) {
var isOperator,
nextIsOperator;
+
i = arr.length;
while (i--) {
@@ -368,12 +767,13 @@
*/
function insertSlice(arr, subArr, index) {
[].splice.apply(
- arr, [index, 0].concat(subArr)
+ arr,
+ [index, 0].concat(subArr)
);
}
/**
- * If shifting points, prepend a dummy point to the end path.
+ * If shifting points, prepend a dummy point to the end path.
*/
function prepend(arr, other) {
while (arr.length < fullLength) {
@@ -398,10 +798,11 @@
}
/**
- * Copy and append last point until the length matches the end length
+ * Copy and append last point until the length matches the end length.
*/
function append(arr, other) {
var i = (fullLength - arr.length) / numParams;
+
while (i > 0 && i--) {
// Pull out the slice that is going to be appended or inserted.
@@ -409,7 +810,7 @@
// is sliced out. In an area graph, the positionFactor is 2,
// causing the middle two points to be sliced out, since an area
// path starts at left, follows the upper path then turns and
- // follows the bottom back.
+ // follows the bottom back.
slice = arr.slice().splice(
(arr.length / positionFactor) - numParams,
numParams * positionFactor
@@ -447,9 +848,9 @@
if (startX[i] === endX[0]) {
shift = i;
break;
- // Moving right
+ // Moving right
} else if (startX[0] ===
- endX[endX.length - startX.length + i]) {
+ endX[endX.length - startX.length + i]) {
shift = i;
reverse = true;
break;
@@ -462,7 +863,7 @@
if (start.length && H.isNumber(shift)) {
- // The common target length for the start and end array, where both
+ // The common target length for the start and end array, where both
// arrays are padded in opposite ends
fullLength = end.length + shift * positionFactor * numParams;
@@ -476,80 +877,95 @@
}
return [start, end];
- }
- }; // End of Fx prototype
+ },
- /**
- * Handle animation of the color attributes directly.
- */
- H.Fx.prototype.fillSetter =
- H.Fx.prototype.strokeSetter = function() {
+ /**
+ * Handle animation of the color attributes directly.
+ *
+ * @function Highcharts.Fx#fillSetter
+ */
+ fillSetter: function () {
+ H.Fx.prototype.strokeSetter.apply(this, arguments);
+ },
+
+ /**
+ * Handle animation of the color attributes directly.
+ *
+ * @function Highcharts.Fx#strokeSetter
+ */
+ strokeSetter: function () {
this.elem.attr(
this.prop,
H.color(this.start).tweenTo(H.color(this.end), this.pos),
null,
true
);
- };
-
- /**
- * Utility function to extend an object with the members of another.
- *
- * @function #extend
- * @memberOf Highcharts
- * @param {Object} a - The object to be extended.
- * @param {Object} b - The object to add to the first one.
- * @returns {Object} Object a, the original object.
- */
- H.extend = function(a, b) {
- var n;
- if (!a) {
- a = {};
- }
- for (n in b) {
- a[n] = b[n];
}
- return a;
- };
+
+ }; // End of Fx prototype
+
/**
+ * Utility function to deep merge two or more objects and return a third object.
+ * The merge function can also be used with a single object argument to create a
+ * deep copy of an object.
+ *
+ * @function Highcharts.merge
+ *
+ * @param {*} a
+ * The first object to extend. When only this is given, the function
+ * returns a deep copy.
+ *
+ * @param {*} [n]
+ * An object to merge into the previous one.
+ *
+ * @return {*}
+ * The merged object. If the first argument is true, the return is the
+ * same as the second argument.
+ *//**
* Utility function to deep merge two or more objects and return a third object.
* If the first argument is true, the contents of the second object is copied
- * into the first object. The merge function can also be used with a single
+ * into the first object. The merge function can also be used with a single
* object argument to create a deep copy of an object.
*
- * @function #merge
- * @memberOf Highcharts
- * @param {Boolean} [extend] - Whether to extend the left-side object (a) or
- return a whole new object.
- * @param {Object} a - The first object to extend. When only this is given, the
- function returns a deep copy.
- * @param {...Object} [n] - An object to merge into the previous one.
- * @returns {Object} - The merged object. If the first argument is true, the
- * return is the same as the second argument.
- */
- H.merge = function() {
+ * @function Highcharts.merge
+ *
+ * @param {boolean} extend
+ * Whether to extend the left-side object (a) or return a whole new
+ * object.
+ *
+ * @param {*} a
+ * The first object to extend. When only this is given, the function
+ * returns a deep copy.
+ *
+ * @param {*} [n]
+ * An object to merge into the previous one.
+ *
+ * @return {*}
+ * The merged object. If the first argument is true, the return is the
+ * same as the second argument.
+ */
+ H.merge = function () {
var i,
args = arguments,
len,
ret = {},
- doCopy = function(copy, original) {
+ doCopy = function (copy, original) {
// An object is replacing a primitive
if (typeof copy !== 'object') {
copy = {};
}
- H.objectEach(original, function(value, key) {
+ H.objectEach(original, function (value, key) {
// Copy the contents of objects, but not arrays or DOM nodes
- if (
- H.isObject(value, true) &&
+ if (H.isObject(value, true) &&
!H.isClass(value) &&
!H.isDOMElement(value)
) {
copy[key] = doCopy(copy[key] || {}, value);
- // Primitives and arrays are copied over directly
+ // Primitives and arrays are copied over directly
} else {
copy[key] = original[key];
}
@@ -575,75 +991,100 @@
/**
* Shortcut for parseInt
- * @ignore
- * @param {Object} s
- * @param {Number} mag Magnitude
+ *
+ * @private
+ * @function Highcharts.pInt
+ *
+ * @param {*} s
+ *
+ * @param {number} mag
+ * Magnitude
+ *
+ * @return {number}
*/
- H.pInt = function(s, mag) {
+ H.pInt = function (s, mag) {
return parseInt(s, mag || 10);
};
/**
* Utility function to check for string type.
*
- * @function #isString
- * @memberOf Highcharts
- * @param {Object} s - The item to check.
- * @returns {Boolean} - True if the argument is a string.
+ * @function Highcharts.isString
+ *
+ * @param {*} s
+ * The item to check.
+ *
+ * @return {boolean}
+ * True if the argument is a string.
*/
- H.isString = function(s) {
+ H.isString = function (s) {
return typeof s === 'string';
};
/**
* Utility function to check if an item is an array.
*
- * @function #isArray
- * @memberOf Highcharts
- * @param {Object} obj - The item to check.
- * @returns {Boolean} - True if the argument is an array.
+ * @function Highcharts.isArray
+ *
+ * @param {*} obj
+ * The item to check.
+ *
+ * @return {boolean}
+ * True if the argument is an array.
*/
- H.isArray = function(obj) {
+ H.isArray = function (obj) {
var str = Object.prototype.toString.call(obj);
+
return str === '[object Array]' || str === '[object Array Iterator]';
};
/**
* Utility function to check if an item is of type object.
*
- * @function #isObject
- * @memberOf Highcharts
- * @param {Object} obj - The item to check.
- * @param {Boolean} [strict=false] - Also checks that the object is not an
- * array.
- * @returns {Boolean} - True if the argument is an object.
+ * @function Highcharts.isObject
+ *
+ * @param {*} obj
+ * The item to check.
+ *
+ * @param {boolean} [strict=false]
+ * Also checks that the object is not an array.
+ *
+ * @return {boolean}
+ * True if the argument is an object.
*/
- H.isObject = function(obj, strict) {
+ H.isObject = function (obj, strict) {
return !!obj && typeof obj === 'object' && (!strict || !H.isArray(obj));
};
/**
* Utility function to check if an Object is a HTML Element.
*
- * @function #isDOMElement
- * @memberOf Highcharts
- * @param {Object} obj - The item to check.
- * @returns {Boolean} - True if the argument is a HTML Element.
+ * @function Highcharts.isDOMElement
+ *
+ * @param {*} obj
+ * The item to check.
+ *
+ * @return {boolean}
+ * True if the argument is a HTML Element.
*/
- H.isDOMElement = function(obj) {
+ H.isDOMElement = function (obj) {
return H.isObject(obj) && typeof obj.nodeType === 'number';
};
/**
* Utility function to check if an Object is an class.
*
- * @function #isClass
- * @memberOf Highcharts
- * @param {Object} obj - The item to check.
- * @returns {Boolean} - True if the argument is an class.
+ * @function Highcharts.isClass
+ *
+ * @param {*} obj
+ * The item to check.
+ *
+ * @return {boolean}
+ * True if the argument is an class.
*/
- H.isClass = function(obj) {
+ H.isClass = function (obj) {
var c = obj && obj.constructor;
+
return !!(
H.isObject(obj, true) &&
!H.isDOMElement(obj) &&
@@ -652,27 +1093,35 @@
};
/**
- * Utility function to check if an item is of type number.
+ * Utility function to check if an item is a number and it is finite (not NaN,
+ * Infinity or -Infinity).
+ *
+ * @function Highcharts.isNumber
*
- * @function #isNumber
- * @memberOf Highcharts
- * @param {Object} n - The item to check.
- * @returns {Boolean} - True if the item is a number and is not NaN.
+ * @param {*} n
+ * The item to check.
+ *
+ * @return {boolean}
+ * True if the item is a finite number
*/
- H.isNumber = function(n) {
- return typeof n === 'number' && !isNaN(n);
+ H.isNumber = function (n) {
+ return typeof n === 'number' && !isNaN(n) && n < Infinity && n > -Infinity;
};
/**
* Remove the last occurence of an item from an array.
*
- * @function #erase
- * @memberOf Highcharts
- * @param {Array} arr - The array.
- * @param {*} item - The item to remove.
+ * @function Highcharts.erase
+ *
+ * @param {Array} arr
+ * The array.
+ *
+ * @param {*} item
+ * The item to remove.
*/
- H.erase = function(arr, item) {
+ H.erase = function (arr, item) {
var i = arr.length;
+
while (i--) {
if (arr[i] === item) {
arr.splice(i, 1);
@@ -684,13 +1133,15 @@
/**
* Check if an object is null or undefined.
*
- * @function #defined
- * @memberOf Highcharts
- * @param {Object} obj - The object to check.
- * @returns {Boolean} - False if the object is null or undefined, otherwise
- * true.
+ * @function Highcharts.defined
+ *
+ * @param {*} obj
+ * The object to check.
+ *
+ * @return {boolean}
+ * False if the object is null or undefined, otherwise true.
*/
- H.defined = function(obj) {
+ H.defined = function (obj) {
return obj !== undefined && obj !== null;
};
@@ -699,14 +1150,21 @@
* a key and a value, or let the second argument be a collection of keys and
* values. To use as a getter, pass only a string as the second argument.
*
- * @function #attr
- * @memberOf Highcharts
- * @param {Object} elem - The DOM element to receive the attribute(s).
- * @param {String|Object} [prop] - The property or an object of key-value pairs.
- * @param {String} [value] - The value if a single property is set.
- * @returns {*} When used as a getter, return the value.
+ * @function Highcharts.attr
+ *
+ * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} elem
+ * The DOM element to receive the attribute(s).
+ *
+ * @param {string|Highcharts.HTMLAttributes|Highcharts.SVGAttributes} [prop]
+ * The property or an object of key-value pairs.
+ *
+ * @param {string} [value]
+ * The value if a single property is set.
+ *
+ * @return {*}
+ * When used as a getter, return the value.
*/
- H.attr = function(elem, prop, value) {
+ H.attr = function (elem, prop, value) {
var ret;
// if the prop is a string
@@ -715,14 +1173,19 @@
if (H.defined(value)) {
elem.setAttribute(prop, value);
- // get the value
+ // get the value
} else if (elem && elem.getAttribute) {
ret = elem.getAttribute(prop);
+
+ // IE7 and below cannot get class through getAttribute (#7850)
+ if (!ret && prop === 'class') {
+ ret = elem.getAttribute(prop + 'Name');
+ }
}
- // else if prop is defined, it is a hash of key/value pairs
+ // else if prop is defined, it is a hash of key/value pairs
} else if (H.defined(prop) && H.isObject(prop)) {
- H.objectEach(prop, function(val, key) {
+ H.objectEach(prop, function (val, key) {
elem.setAttribute(key, val);
});
}
@@ -732,12 +1195,15 @@
/**
* Check if an element is an array, and if not, make it into an array.
*
- * @function #splat
- * @memberOf Highcharts
- * @param obj {*} - The object to splat.
- * @returns {Array} The produced or original array.
+ * @function Highcharts.splat
+ *
+ * @param {*} obj
+ * The object to splat.
+ *
+ * @return {Array}
+ * The produced or original array.
*/
- H.splat = function(obj) {
+ H.splat = function (obj) {
return H.isArray(obj) ? obj : [obj];
};
@@ -745,35 +1211,88 @@
* Set a timeout if the delay is given, otherwise perform the function
* synchronously.
*
- * @function #syncTimeout
- * @memberOf Highcharts
- * @param {Function} fn - The function callback.
- * @param {Number} delay - Delay in milliseconds.
- * @param {Object} [context] - The context.
- * @returns {Number} An identifier for the timeout that can later be cleared
- * with clearTimeout.
+ * @function Highcharts.syncTimeout
+ *
+ * @param {Function} fn
+ * The function callback.
+ *
+ * @param {number} delay
+ * Delay in milliseconds.
+ *
+ * @param {*} [parameter]
+ * An optional parameter to send to the function callback.
+ *
+ * @return {number}
+ * An identifier for the timeout that can later be cleared with
+ * Highcharts.clearTimeout.
*/
- H.syncTimeout = function(fn, delay, context) {
+ H.syncTimeout = function (fn, delay, context) {
if (delay) {
return setTimeout(fn, delay, context);
}
fn.call(0, context);
};
+ /**
+ * Internal clear timeout. The function checks that the `id` was not removed
+ * (e.g. by `chart.destroy()`). For the details see
+ * [issue #7901](https://github.com/highcharts/highcharts/issues/7901).
+ *
+ * @function Highcharts.clearTimeout
+ *
+ * @param {number} id
+ * Id of a timeout.
+ */
+ H.clearTimeout = function (id) {
+ if (H.defined(id)) {
+ clearTimeout(id);
+ }
+ };
+
+ /**
+ * Utility function to extend an object with the members of another.
+ *
+ * @function Highcharts.extend
+ *
+ * @param {Highcharts.Dictionary<*>} a
+ * The object to be extended.
+ *
+ * @param {Highcharts.Dictionary<*>} b
+ * The object to add to the first one.
+ *
+ * @return {Highcharts.Dictionary<*>}
+ * Object a, the original object.
+ */
+ H.extend = function (a, b) {
+ var n;
+
+ if (!a) {
+ a = {};
+ }
+ for (n in b) {
+ a[n] = b[n];
+ }
+ return a;
+ };
+
/**
* Return the first value that is not null or undefined.
*
- * @function #pick
- * @memberOf Highcharts
- * @param {...*} items - Variable number of arguments to inspect.
- * @returns {*} The value of the first argument that is not null or undefined.
+ * @function Highcharts.pick
+ *
+ * @param {...*} items
+ * Variable number of arguments to inspect.
+ *
+ * @return {*}
+ * The value of the first argument that is not null or undefined.
*/
- H.pick = function() {
+ H.pick = function () {
var args = arguments,
i,
arg,
length = args.length;
+
for (i = 0; i < length; i++) {
arg = args[i];
if (arg !== undefined && arg !== null) {
@@ -782,26 +1301,18 @@
}
};
- /**
- * @typedef {Object} CSSObject - A style object with camel case property names.
- * The properties can be whatever styles are supported on the given SVG or HTML
- * element.
- * @example
- * {
- * fontFamily: 'monospace',
- * fontSize: '1.2em'
- * }
- */
/**
* Set CSS on a given element.
*
- * @function #css
- * @memberOf Highcharts
- * @param {HTMLDOMElement} el - A HTML DOM element.
- * @param {CSSObject} styles - Style object with camel case property names.
- *
+ * @function Highcharts.css
+ *
+ * @param {Highcharts.HTMLDOMElement} el
+ * An HTML DOM element.
+ *
+ * @param {Highcharts.CSSObject} styles
+ * Style object with camel case property names.
*/
- H.css = function(el, styles) {
+ H.css = function (el, styles) {
if (H.isMS && !H.svg) { // #2686
if (styles && styles.opacity !== undefined) {
styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')';
@@ -810,36 +1321,38 @@
H.extend(el.style, styles);
};
- /**
- * A HTML DOM element.
- * @typedef {Object} HTMLDOMElement
- */
-
/**
* Utility function to create an HTML element with attributes and styles.
*
- * @function #createElement
- * @memberOf Highcharts
- * @param {String} tag - The HTML tag.
- * @param {Object} [attribs] - Attributes as an object of key-value pairs.
- * @param {CSSObject} [styles] - Styles as an object of key-value pairs.
- * @param {Object} [parent] - The parent HTML object.
- * @param {Boolean} [nopad=false] - If true, remove all padding, border and
- * margin.
- * @returns {HTMLDOMElement} The created DOM element.
- */
- H.createElement = function(tag, attribs, styles, parent, nopad) {
+ * @function Highcharts.createElement
+ *
+ * @param {string} tag
+ * The HTML tag.
+ *
+ * @param {Highcharts.HTMLAttributes} [attribs]
+ * Attributes as an object of key-value pairs.
+ *
+ * @param {Highcharts.CSSObject} [styles]
+ * Styles as an object of key-value pairs.
+ *
+ * @param {Highcharts.HTMLDOMElement} [parent]
+ * The parent HTML object.
+ *
+ * @param {boolean} [nopad=false]
+ * If true, remove all padding, border and margin.
+ *
+ * @return {Highcharts.HTMLDOMElement}
+ * The created DOM element.
+ */
+ H.createElement = function (tag, attribs, styles, parent, nopad) {
var el = doc.createElement(tag),
css = H.css;
+
if (attribs) {
H.extend(el, attribs);
}
if (nopad) {
- css(el, {
- padding: 0,
- border: 'none',
- margin: 0
- });
+ css(el, { padding: 0, border: 'none', margin: 0 });
}
if (styles) {
css(el, styles);
@@ -853,15 +1366,21 @@
/**
* Extend a prototyped class by new members.
*
- * @function #extendClass
- * @memberOf Highcharts
- * @param {Object} parent - The parent prototype to inherit.
- * @param {Object} members - A collection of prototype members to add or
- * override compared to the parent prototype.
- * @returns {Object} A new prototype.
+ * @function Highcharts.extendClass
+ *
+ * @param {*} parent
+ * The parent prototype to inherit.
+ *
+ * @param {Highcharts.Dictionary<*>} members
+ * A collection of prototype members to add or override compared to the
+ * parent prototype.
+ *
+ * @return {*}
+ * A new prototype.
*/
- H.extendClass = function(parent, members) {
- var object = function() {};
+ H.extendClass = function (parent, members) {
+ var object = function () {};
+
object.prototype = new parent(); // eslint-disable-line new-cap
H.extend(object.prototype, members);
return object;
@@ -870,40 +1389,49 @@
/**
* Left-pad a string to a given length by adding a character repetetively.
*
- * @function #pad
- * @memberOf Highcharts
- * @param {Number} number - The input string or number.
- * @param {Number} length - The desired string length.
- * @param {String} [padder=0] - The character to pad with.
- * @returns {String} The padded string.
+ * @function Highcharts.pad
+ *
+ * @param {number} number
+ * The input string or number.
+ *
+ * @param {number} length
+ * The desired string length.
+ *
+ * @param {string} [padder=0]
+ * The character to pad with.
+ *
+ * @return {string}
+ * The padded string.
*/
- H.pad = function(number, length, padder) {
- return new Array((length || 2) + 1 -
- String(number).length).join(padder || 0) + number;
+ H.pad = function (number, length, padder) {
+ return new Array(
+ (length || 2) +
+ 1 -
+ String(number)
+ .replace('-', '')
+ .length
+ ).join(padder || 0) + number;
};
- /**
- * @typedef {Number|String} RelativeSize - If a number is given, it defines the
- * pixel length. If a percentage string is given, like for example `'50%'`,
- * the setting defines a length relative to a base size, for example the size
- * of a container.
- */
/**
* Return a length based on either the integer value, or a percentage of a base.
*
- * @function #relativeLength
- * @memberOf Highcharts
- * @param {RelativeSize} value
- * A percentage string or a number.
- * @param {number} base
- * The full length that represents 100%.
- * @param {number} [offset=0]
- * A pixel offset to apply for percentage values. Used internally in
- * axis positioning.
+ * @function Highcharts.relativeLength
+ *
+ * @param {Highcharts.RelativeSize} value
+ * A percentage string or a number.
+ *
+ * @param {number} base
+ * The full length that represents 100%.
+ *
+ * @param {number} [offset=0]
+ * A pixel offset to apply for percentage values. Used internally in
+ * axis positioning.
+ *
* @return {number}
* The computed length.
*/
- H.relativeLength = function(value, base, offset) {
+ H.relativeLength = function (value, base, offset) {
return (/%$/).test(value) ?
(base * parseFloat(value) / 100) + (offset || 0) :
parseFloat(value);
@@ -912,24 +1440,30 @@
/**
* Wrap a method with extended functionality, preserving the original function.
*
- * @function #wrap
- * @memberOf Highcharts
- * @param {Object} obj - The context object that the method belongs to. In real
- * cases, this is often a prototype.
- * @param {String} method - The name of the method to extend.
- * @param {Function} func - A wrapper function callback. This function is called
- * with the same arguments as the original function, except that the
- * original function is unshifted and passed as the first argument.
- *
- */
- H.wrap = function(obj, method, func) {
+ * @function Highcharts.wrap
+ *
+ * @param {*} obj
+ * The context object that the method belongs to. In real cases, this is
+ * often a prototype.
+ *
+ * @param {string} method
+ * The name of the method to extend.
+ *
+ * @param {Function} func
+ * A wrapper function callback. This function is called with the same
+ * arguments as the original function, except that the original function
+ * is unshifted and passed as the first argument.
+ */
+ H.wrap = function (obj, method, func) {
var proceed = obj[method];
- obj[method] = function() {
+
+ obj[method] = function () {
var args = Array.prototype.slice.call(arguments),
outerArgs = arguments,
ctx = this,
ret;
- ctx.proceed = function() {
+
+ ctx.proceed = function () {
proceed.apply(ctx, arguments.length ? arguments : outerArgs);
};
args.unshift(proceed);
@@ -939,140 +1473,20 @@
};
};
+
/**
- * Get the time zone offset based on the current timezone information as set in
- * the global options.
+ * Recursively converts all Date properties to timestamps.
*
- * @function #getTZOffset
- * @memberOf Highcharts
- * @param {Number} timestamp - The JavaScript timestamp to inspect.
- * @return {Number} - The timezone offset in minutes compared to UTC.
+ * @param {Object} object - any object to convert properties of
*/
- H.getTZOffset = function(timestamp) {
- var d = H.Date;
- return ((d.hcGetTimezoneOffset && d.hcGetTimezoneOffset(timestamp)) ||
- d.hcTimezoneOffset || 0) * 60000;
- };
-
- /**
- * Formats a JavaScript date timestamp (milliseconds since Jan 1st 1970) into a
- * human readable date string. The format is a subset of the formats for PHP's
- * [strftime]{@link
- * http://www.php.net/manual/en/function.strftime.php} function. Additional
- * formats can be given in the {@link Highcharts.dateFormats} hook.
- *
- * @function #dateFormat
- * @memberOf Highcharts
- * @param {String} format - The desired format where various time
- * representations are prefixed with %.
- * @param {Number} timestamp - The JavaScript timestamp.
- * @param {Boolean} [capitalize=false] - Upper case first letter in the return.
- * @returns {String} The formatted date.
- */
- H.dateFormat = function(format, timestamp, capitalize) {
- if (!H.defined(timestamp) || isNaN(timestamp)) {
- return H.defaultOptions.lang.invalidDate || '';
- }
- format = H.pick(format, '%Y-%m-%d %H:%M:%S');
-
- var D = H.Date,
- date = new D(timestamp - H.getTZOffset(timestamp)),
- // get the basic time values
- hours = date[D.hcGetHours](),
- day = date[D.hcGetDay](),
- dayOfMonth = date[D.hcGetDate](),
- month = date[D.hcGetMonth](),
- fullYear = date[D.hcGetFullYear](),
- lang = H.defaultOptions.lang,
- langWeekdays = lang.weekdays,
- shortWeekdays = lang.shortWeekdays,
- pad = H.pad,
-
- // List all format keys. Custom formats can be added from the outside.
- replacements = H.extend({
-
- // Day
- // Short weekday, like 'Mon'
- 'a': shortWeekdays ?
- shortWeekdays[day] : langWeekdays[day].substr(0, 3),
- // Long weekday, like 'Monday'
- 'A': langWeekdays[day],
- // Two digit day of the month, 01 to 31
- 'd': pad(dayOfMonth),
- // Day of the month, 1 through 31
- 'e': pad(dayOfMonth, 2, ' '),
- 'w': day,
-
- // Week (none implemented)
- // 'W': weekNumber(),
-
- // Month
- // Short month, like 'Jan'
- 'b': lang.shortMonths[month],
- // Long month, like 'January'
- 'B': lang.months[month],
- // Two digit month number, 01 through 12
- 'm': pad(month + 1),
-
- // Year
- // Two digits year, like 09 for 2009
- 'y': fullYear.toString().substr(2, 2),
- // Four digits year, like 2009
- 'Y': fullYear,
-
- // Time
- // Two digits hours in 24h format, 00 through 23
- 'H': pad(hours),
- // Hours in 24h format, 0 through 23
- 'k': hours,
- // Two digits hours in 12h format, 00 through 11
- 'I': pad((hours % 12) || 12),
- // Hours in 12h format, 1 through 12
- 'l': (hours % 12) || 12,
- // Two digits minutes, 00 through 59
- 'M': pad(date[D.hcGetMinutes]()),
- // Upper case AM or PM
- 'p': hours < 12 ? 'AM' : 'PM',
- // Lower case AM or PM
- 'P': hours < 12 ? 'am' : 'pm',
- // Two digits seconds, 00 through 59
- 'S': pad(date.getSeconds()),
- // Milliseconds (naming from Ruby)
- 'L': pad(Math.round(timestamp % 1000), 3)
- },
-
- /**
- * A hook for defining additional date format specifiers. New
- * specifiers are defined as key-value pairs by using the specifier
- * as key, and a function which takes the timestamp as value. This
- * function returns the formatted portion of the date.
- *
- * @type {Object}
- * @name dateFormats
- * @memberOf Highcharts
- * @sample highcharts/global/dateformats/ Adding support for week
- * number
- */
- H.dateFormats
- );
-
-
- // Do the replaces
- H.objectEach(replacements, function(val, key) {
- // Regex would do it in one line, but this is faster
- while (format.indexOf('%' + key) !== -1) {
- format = format.replace(
- '%' + key,
- typeof val === 'function' ? val(timestamp) : val
- );
+ H.datePropsToTimestamps = function (object) {
+ H.objectEach(object, function (val, key) {
+ if (H.isObject(val) && typeof val.getTime === 'function') {
+ object[key] = val.getTime();
+ } else if (H.isObject(val) || H.isArray(val)) {
+ H.datePropsToTimestamps(val);
}
-
});
-
- // Optionally capitalize the string and return
- return capitalize ?
- format.substr(0, 1).toUpperCase() + format.substr(1) :
- format;
};
/**
@@ -1081,13 +1495,22 @@
* @example
* formatSingle('.2f', 5); // => '5.00'.
*
- * @function #formatSingle
- * @memberOf Highcharts
- * @param {String} format The format string.
- * @param {*} val The value.
- * @returns {String} The formatted representation of the value.
+ * @function Highcharts.formatSingle
+ *
+ * @param {string} format
+ * The format string.
+ *
+ * @param {*} val
+ * The value.
+ *
+ * @param {Highcharts.Time} [time]
+ * A `Time` instance that determines the date formatting, for example
+ * for applying time zone corrections to the formatted date.
+ *
+ * @return {string}
+ * The formatted representation of the value.
*/
- H.formatSingle = function(format, val) {
+ H.formatSingle = function (format, val, time) {
var floatRegex = /f$/,
decRegex = /\.([0-9])/,
lang = H.defaultOptions.lang,
@@ -1105,7 +1528,7 @@
);
}
} else {
- val = H.dateFormat(format, val);
+ val = (time || H.time).dateFormat(format, val);
}
return val;
};
@@ -1114,21 +1537,30 @@
* Format a string according to a subset of the rules of Python's String.format
* method.
*
- * @function #format
- * @memberOf Highcharts
- * @param {String} str The string to format.
- * @param {Object} ctx The context, a collection of key-value pairs where each
- * key is replaced by its value.
- * @returns {String} The formatted string.
- *
* @example
* var s = Highcharts.format(
* 'The {color} fox was {len:.2f} feet long',
* { color: 'red', len: Math.PI }
* );
* // => The red fox was 3.14 feet long
+ *
+ * @function Highcharts.format
+ *
+ * @param {string} str
+ * The string to format.
+ *
+ * @param {*} ctx
+ * The context, a collection of key-value pairs where each key is
+ * replaced by its value.
+ *
+ * @param {Highcharts.Time} [time]
+ * A `Time` instance that determines the date formatting, for example
+ * for applying time zone corrections to the formatted date.
+ *
+ * @return {string}
+ * The formatted string.
*/
- H.format = function(str, ctx) {
+ H.format = function (str, ctx, time) {
var splitter = '{',
isInside = false,
segment,
@@ -1163,7 +1595,7 @@
// Format the replacement
if (valueAndFormat.length) {
- val = H.formatSingle(valueAndFormat.join(':'), val);
+ val = H.formatSingle(valueAndFormat.join(':'), val, time);
}
// Push the result and advance the cursor
@@ -1184,33 +1616,54 @@
/**
* Get the magnitude of a number.
*
- * @function #getMagnitude
- * @memberOf Highcharts
- * @param {Number} number The number.
- * @returns {Number} The magnitude, where 1-9 are magnitude 1, 10-99 magnitude 2
- * etc.
+ * @function Highcharts.getMagnitude
+ *
+ * @param {number} number
+ * The number.
+ *
+ * @return {number}
+ * The magnitude, where 1-9 are magnitude 1, 10-99 magnitude 2 etc.
*/
- H.getMagnitude = function(num) {
+ H.getMagnitude = function (num) {
return Math.pow(10, Math.floor(Math.log(num) / Math.LN10));
};
/**
* Take an interval and normalize it to multiples of round numbers.
*
- * @todo Move this function to the Axis prototype. It is here only for
- * historical reasons.
- * @function #normalizeTickInterval
- * @memberOf Highcharts
- * @param {Number} interval - The raw, un-rounded interval.
- * @param {Array} [multiples] - Allowed multiples.
- * @param {Number} [magnitude] - The magnitude of the number.
- * @param {Boolean} [allowDecimals] - Whether to allow decimals.
- * @param {Boolean} [hasTickAmount] - If it has tickAmount, avoid landing
- * on tick intervals lower than original.
- * @returns {Number} The normalized interval.
- */
- H.normalizeTickInterval = function(interval, multiples, magnitude,
- allowDecimals, hasTickAmount) {
+ * @deprecated
+ * @function Highcharts.normalizeTickInterval
+ *
+ * @param {number} interval
+ * The raw, un-rounded interval.
+ *
+ * @param {Array} [multiples]
+ * Allowed multiples.
+ *
+ * @param {number} [magnitude]
+ * The magnitude of the number.
+ *
+ * @param {boolean} [allowDecimals]
+ * Whether to allow decimals.
+ *
+ * @param {boolean} [hasTickAmount]
+ * If it has tickAmount, avoid landing on tick intervals lower than
+ * original.
+ *
+ * @return {number}
+ * The normalized interval.
+ *
+ * @todo
+ * Move this function to the Axis prototype. It is here only for historical
+ * reasons.
+ */
+ H.normalizeTickInterval = function (
+ interval,
+ multiples,
+ magnitude,
+ allowDecimals,
+ hasTickAmount
+ ) {
var normalized,
i,
retInterval = interval;
@@ -1233,7 +1686,7 @@
// the allowDecimals option
if (allowDecimals === false) {
if (magnitude === 1) {
- multiples = H.grep(multiples, function(num) {
+ multiples = multiples.filter(function (num) {
return num % 1 === 0;
});
} else if (magnitude <= 0.1) {
@@ -1246,17 +1699,31 @@
for (i = 0; i < multiples.length; i++) {
retInterval = multiples[i];
// only allow tick amounts smaller than natural
- if ((hasTickAmount && retInterval * magnitude >= interval) ||
- (!hasTickAmount && (normalized <= (multiples[i] +
- (multiples[i + 1] || multiples[i])) / 2))) {
+ if (
+ (
+ hasTickAmount &&
+ retInterval * magnitude >= interval
+ ) ||
+ (
+ !hasTickAmount &&
+ (
+ normalized <=
+ (
+ multiples[i] +
+ (multiples[i + 1] || multiples[i])
+ ) / 2
+ )
+ )
+ ) {
break;
}
}
- // Multiply back to the correct magnitude. Correct floats to appropriate
+ // Multiply back to the correct magnitude. Correct floats to appropriate
// precision (#6085).
retInterval = H.correctFloat(
- retInterval * magnitude, -Math.round(Math.log(0.001) / Math.LN10)
+ retInterval * magnitude,
+ -Math.round(Math.log(0.001) / Math.LN10)
);
return retInterval;
@@ -1267,14 +1734,19 @@
* Sort an object array and keep the order of equal items. The ECMAScript
* standard does not specify the behaviour when items are equal.
*
- * @function #stableSort
- * @memberOf Highcharts
- * @param {Array} arr - The array to sort.
- * @param {Function} sortFunction - The function to sort it with, like with
- * regular Array.prototype.sort.
- *
+ * @function Highcharts.stableSort
+ *
+ * @param {Array} arr
+ * The array to sort.
+ *
+ * @param {Function} sortFunction
+ * The function to sort it with, like with regular Array.prototype.sort.
*/
- H.stableSort = function(arr, sortFunction) {
+ H.stableSort = function (arr, sortFunction) {
+
+ // @todo It seems like Chrome since v70 sorts in a stable way internally,
+ // plus all other browsers do it, so over time we may be able to remove this
+ // function
var length = arr.length,
sortValue,
i;
@@ -1284,7 +1756,7 @@
arr[i].safeI = i; // stable sort index
}
- arr.sort(function(a, b) {
+ arr.sort(function (a, b) {
sortValue = sortFunction(a, b);
return sortValue === 0 ? a.safeI - b.safeI : sortValue;
});
@@ -1300,12 +1772,15 @@
* a maximum call stack size exceeded error in Chrome when trying to apply more
* than 150.000 points. This method is slightly slower, but safe.
*
- * @function #arrayMin
- * @memberOf Highcharts
- * @param {Array} data An array of numbers.
- * @returns {Number} The lowest number.
+ * @function Highcharts.arrayMin
+ *
+ * @param {Array} data
+ * An array of numbers.
+ *
+ * @return {number}
+ * The lowest number.
*/
- H.arrayMin = function(data) {
+ H.arrayMin = function (data) {
var i = data.length,
min = data[0];
@@ -1322,12 +1797,15 @@
* a maximum call stack size exceeded error in Chrome when trying to apply more
* than 150.000 points. This method is slightly slower, but safe.
*
- * @function #arrayMax
- * @memberOf Highcharts
- * @param {Array} data - An array of numbers.
- * @returns {Number} The highest number.
+ * @function Highcharts.arrayMax
+ *
+ * @param {Array} data
+ * An array of numbers.
+ *
+ * @return {number}
+ * The highest number.
*/
- H.arrayMax = function(data) {
+ H.arrayMax = function (data) {
var i = data.length,
max = data[0];
@@ -1344,15 +1822,16 @@
* the given object. It loops all properties and invokes destroy if there is a
* destroy method. The property is then delete.
*
- * @function #destroyObjectProperties
- * @memberOf Highcharts
- * @param {Object} obj - The object to destroy properties on.
- * @param {Object} [except] - Exception, do not destroy this property, only
- * delete it.
- *
+ * @function Highcharts.destroyObjectProperties
+ *
+ * @param {*} obj
+ * The object to destroy properties on.
+ *
+ * @param {*} [except]
+ * Exception, do not destroy this property, only delete it.
*/
- H.destroyObjectProperties = function(obj, except) {
- H.objectEach(obj, function(val, n) {
+ H.destroyObjectProperties = function (obj, except) {
+ H.objectEach(obj, function (val, n) {
// If the object is non-null and destroy is defined
if (val && val !== except && val.destroy) {
// Invoke the destroy
@@ -1368,13 +1847,14 @@
/**
* Discard a HTML element by moving it to the bin and delete.
*
- * @function #discardElement
- * @memberOf Highcharts
- * @param {HTMLDOMElement} element - The HTML node to discard.
- *
+ * @function Highcharts.discardElement
+ *
+ * @param {Highcharts.HTMLDOMElement} element
+ * The HTML node to discard.
*/
- H.discardElement = function(element) {
+ H.discardElement = function (element) {
var garbageBin = H.garbageBin;
+
// create a garbage bin element, not part of the DOM
if (!garbageBin) {
garbageBin = H.createElement('div');
@@ -1390,13 +1870,18 @@
/**
* Fix JS round off float errors.
*
- * @function #correctFloat
- * @memberOf Highcharts
- * @param {Number} num - A float number to fix.
- * @param {Number} [prec=14] - The precision.
- * @returns {Number} The corrected float number.
+ * @function Highcharts.correctFloat
+ *
+ * @param {number} num
+ * A float number to fix.
+ *
+ * @param {number} [prec=14]
+ * The precision.
+ *
+ * @return {number}
+ * The corrected float number.
*/
- H.correctFloat = function(num, prec) {
+ H.correctFloat = function (num, prec) {
return parseFloat(
num.toPrecision(prec || 14)
);
@@ -1406,15 +1891,19 @@
* Set the global animation to either a given value, or fall back to the given
* chart's animation option.
*
- * @function #setAnimation
- * @memberOf Highcharts
- * @param {Boolean|Animation} animation - The animation object.
- * @param {Object} chart - The chart instance.
- *
- * @todo This function always relates to a chart, and sets a property on the
- * renderer, so it should be moved to the SVGRenderer.
+ * @function Highcharts.setAnimation
+ *
+ * @param {boolean|Highcharts.AnimationOptionsObject} animation
+ * The animation object.
+ *
+ * @param {Highcharts.Chart} chart
+ * The chart instance.
+ *
+ * @todo
+ * This function always relates to a chart, and sets a property on the renderer,
+ * so it should be moved to the SVGRenderer.
*/
- H.setAnimation = function(animation, chart) {
+ H.setAnimation = function (animation, chart) {
chart.renderer.globalAnimation = H.pick(
animation,
chart.options.chart.animation,
@@ -1426,22 +1915,25 @@
* Get the animation in object form, where a disabled animation is always
* returned as `{ duration: 0 }`.
*
- * @function #animObject
- * @memberOf Highcharts
- * @param {Boolean|AnimationOptions} animation - An animation setting. Can be an
- * object with duration, complete and easing properties, or a boolean to
- * enable or disable.
- * @returns {AnimationOptions} An object with at least a duration property.
+ * @function Highcharts.animObject
+ *
+ * @param {boolean|Highcharts.AnimationOptionsObject} animation
+ * An animation setting. Can be an object with duration, complete and
+ * easing properties, or a boolean to enable or disable.
+ *
+ * @return {Highcharts.AnimationOptionsObject}
+ * An object with at least a duration property.
*/
- H.animObject = function(animation) {
+ H.animObject = function (animation) {
return H.isObject(animation) ?
- H.merge(animation) : {
- duration: animation ? 500 : 0
- };
+ H.merge(animation) :
+ { duration: animation ? 500 : 0 };
};
/**
* The time unit lookup
+ *
+ * @ignore
*/
H.timeUnits = {
millisecond: 1,
@@ -1457,20 +1949,30 @@
/**
* Format a number and return a string based on input settings.
*
- * @function #numberFormat
- * @memberOf Highcharts
- * @param {Number} number - The input number to format.
- * @param {Number} decimals - The amount of decimals. A value of -1 preserves
- * the amount in the input number.
- * @param {String} [decimalPoint] - The decimal point, defaults to the one given
- * in the lang options, or a dot.
- * @param {String} [thousandsSep] - The thousands separator, defaults to the one
- * given in the lang options, or a space character.
- * @returns {String} The formatted number.
+ * @sample highcharts/members/highcharts-numberformat/
+ * Custom number format
+ *
+ * @function Highcharts.numberFormat
+ *
+ * @param {number} number
+ * The input number to format.
+ *
+ * @param {number} decimals
+ * The amount of decimals. A value of -1 preserves the amount in the
+ * input number.
+ *
+ * @param {string} [decimalPoint]
+ * The decimal point, defaults to the one given in the lang options, or
+ * a dot.
+ *
+ * @param {string} [thousandsSep]
+ * The thousands separator, defaults to the one given in the lang
+ * options, or a space character.
*
- * @sample highcharts/members/highcharts-numberformat/ Custom number format
+ * @return {string}
+ * The formatted number.
*/
- H.numberFormat = function(number, decimals, decimalPoint, thousandsSep) {
+ H.numberFormat = function (number, decimals, decimalPoint, thousandsSep) {
number = +number || 0;
decimals = +decimals;
@@ -1480,13 +1982,36 @@
thousands,
ret,
roundedNumber,
- exponent = number.toString().split('e');
+ exponent = number.toString().split('e'),
+ fractionDigits;
if (decimals === -1) {
// Preserve decimals. Not huge numbers (#3793).
decimals = Math.min(origDec, 20);
} else if (!H.isNumber(decimals)) {
decimals = 2;
+ } else if (decimals && exponent[1] && exponent[1] < 0) {
+ // Expose decimals from exponential notation (#7042)
+ fractionDigits = decimals + +exponent[1];
+ if (fractionDigits >= 0) {
+ // remove too small part of the number while keeping the notation
+ exponent[0] = (+exponent[0]).toExponential(fractionDigits)
+ .split('e')[0];
+ decimals = fractionDigits;
+ } else {
+ // fractionDigits < 0
+ exponent[0] = exponent[0].split('.')[0] || 0;
+
+ if (decimals < 20) {
+ // use number instead of exponential notation (#7405)
+ number = (exponent[0] * Math.pow(10, exponent[1]))
+ .toFixed(decimals);
+ } else {
+ // or zero
+ number = 0;
+ }
+ exponent[1] = 0;
+ }
}
// Add another decimal to avoid rounding errors of float numbers. (#4573)
@@ -1499,7 +2024,7 @@
// A string containing the positive integer component of the number
strinteger = String(H.pInt(roundedNumber));
- // Leftover after grouping into thousands. Can be 0, 1 or 3.
+ // Leftover after grouping into thousands. Can be 0, 1 or 2.
thousands = strinteger.length > 3 ? strinteger.length % 3 : 0;
// Language
@@ -1524,7 +2049,7 @@
ret += decimalPoint + roundedNumber.slice(-decimals);
}
- if (exponent[1]) {
+ if (exponent[1] && +ret !== 0) {
ret += 'e' + exponent[1];
}
@@ -1533,10 +2058,16 @@
/**
* Easing definition
- * @ignore
- * @param {Number} pos Current position, ranging from 0 to 1.
+ *
+ * @private
+ * @function Math.easeInOutSine
+ *
+ * @param {number} pos
+ * Current position, ranging from 0 to 1.
+ *
+ * @return {number}
*/
- Math.easeInOutSine = function(pos) {
+ Math.easeInOutSine = function (pos) {
return -0.5 * (Math.cos(Math.PI * pos) - 1);
};
@@ -1545,26 +2076,54 @@
* properties. For width and height, the dimension of the inner box (excluding
* padding) is returned. Used for fitting the chart within the container.
*
- * @function #getStyle
- * @memberOf Highcharts
- * @param {HTMLDOMElement} el - A HTML element.
- * @param {String} prop - The property name.
- * @param {Boolean} [toInt=true] - Parse to integer.
- * @returns {Number} - The numeric value.
+ * @function Highcharts.getStyle
+ *
+ * @param {Highcharts.HTMLDOMElement} el
+ * An HTML element.
+ *
+ * @param {string} prop
+ * The property name.
+ *
+ * @param {boolean} [toInt=true]
+ * Parse to integer.
+ *
+ * @return {number}
+ * The numeric value.
*/
- H.getStyle = function(el, prop, toInt) {
+ H.getStyle = function (el, prop, toInt) {
var style;
// For width and height, return the actual inner pixel size (#4913)
if (prop === 'width') {
- return Math.min(el.offsetWidth, el.scrollWidth) -
- H.getStyle(el, 'padding-left') -
- H.getStyle(el, 'padding-right');
- } else if (prop === 'height') {
- return Math.min(el.offsetHeight, el.scrollHeight) -
- H.getStyle(el, 'padding-top') -
- H.getStyle(el, 'padding-bottom');
+ return Math.max(
+ 0, // #8377
+ (
+ Math.min(
+ el.offsetWidth,
+ el.scrollWidth,
+ (
+ el.getBoundingClientRect &&
+ // #9871, getBoundingClientRect doesn't handle
+ // transforms, so avoid that
+ H.getStyle(el, 'transform', false) === 'none'
+ ) ?
+ Math.floor(el.getBoundingClientRect().width) : // #6427
+ Infinity
+ ) -
+ H.getStyle(el, 'padding-left') -
+ H.getStyle(el, 'padding-right')
+ )
+ );
+ }
+
+ if (prop === 'height') {
+ return Math.max(
+ 0, // #8377
+ Math.min(el.offsetHeight, el.scrollHeight) -
+ H.getStyle(el, 'padding-top') -
+ H.getStyle(el, 'padding-bottom')
+ );
}
if (!win.getComputedStyle) {
@@ -1586,49 +2145,48 @@
/**
* Search for an item in an array.
*
- * @function #inArray
- * @memberOf Highcharts
- * @param {*} item - The item to search for.
- * @param {arr} arr - The array or node collection to search in.
- * @returns {Number} - The index within the array, or -1 if not found.
- */
- H.inArray = function(item, arr) {
- return (H.indexOfPolyfill || Array.prototype.indexOf).call(arr, item);
- };
-
- /**
- * Filter an array by a callback.
+ * @function Highcharts.inArray
+ *
+ * @deprecated
+ *
+ * @param {*} item
+ * The item to search for.
+ *
+ * @param {Array} arr
+ * The array or node collection to search in.
+ *
+ * @param {number} [fromIndex=0]
+ * The index to start searching from.
*
- * @function #grep
- * @memberOf Highcharts
- * @param {Array} arr - The array to filter.
- * @param {Function} callback - The callback function. The function receives the
- * item as the first argument. Return `true` if the item is to be
- * preserved.
- * @returns {Array} - A new, filtered array.
+ * @return {number}
+ * The index within the array, or -1 if not found.
*/
- H.grep = function(arr, callback) {
- return (H.filterPolyfill || Array.prototype.filter).call(arr, callback);
+ H.inArray = function (item, arr, fromIndex) {
+ return arr.indexOf(item, fromIndex);
};
/**
- * Return the value of the first element in the array that satisfies the
+ * Return the value of the first element in the array that satisfies the
* provided testing function.
*
- * @function #find
- * @memberOf Highcharts
- * @param {Array} arr - The array to test.
- * @param {Function} callback - The callback function. The function receives the
- * item as the first argument. Return `true` if this item satisfies the
- * condition.
- * @returns {Mixed} - The value of the element.
+ * @function Highcharts.find
+ *
+ * @param {Array} arr
+ * The array to test.
+ *
+ * @param {Function} callback
+ * The callback function. The function receives the item as the first
+ * argument. Return `true` if this item satisfies the condition.
+ *
+ * @return {*}
+ * The value of the element.
*/
H.find = Array.prototype.find ?
- function(arr, callback) {
+ function (arr, callback) {
return arr.find(callback);
} :
// Legacy implementation. PhantomJS, IE <= 11 etc. #7223.
- function(arr, fn) {
+ function (arr, fn) {
var i,
length = arr.length;
@@ -1639,76 +2197,37 @@
}
};
- /**
- * Map an array by a callback.
- *
- * @function #map
- * @memberOf Highcharts
- * @param {Array} arr - The array to map.
- * @param {Function} fn - The callback function. Return the new value for the
- * new array.
- * @returns {Array} - A new array item with modified items.
- */
- H.map = function(arr, fn) {
- var results = [],
- i = 0,
- len = arr.length;
-
- for (; i < len; i++) {
- results[i] = fn.call(arr[i], arr[i], i, arr);
- }
-
- return results;
- };
-
/**
* Returns an array of a given object's own properties.
*
- * @function #keys
- * @memberOf highcharts
- * @param {Object} obj - The object of which the properties are to be returned.
- * @returns {Array} - An array of strings that represents all the properties.
- */
- H.keys = function(obj) {
- return (H.keysPolyfill || Object.keys).call(undefined, obj);
- };
-
- /**
- * Reduce an array to a single value.
+ * @function Highcharts.keys
+ * @deprecated
*
- * @function #reduce
- * @memberOf Highcharts
- * @param {Array} arr - The array to reduce.
- * @param {Function} fn - The callback function. Return the reduced value.
- * Receives 4 arguments: Accumulated/reduced value, current value, current
- * array index, and the array.
- * @param {Mixed} initialValue - The initial value of the accumulator.
- * @returns {Mixed} - The reduced value.
- */
- H.reduce = function(arr, func, initialValue) {
- return (H.reducePolyfill || Array.prototype.reduce).call(
- arr,
- func,
- initialValue
- );
- };
+ * @param {*} obj
+ * The object of which the properties are to be returned.
+ *
+ * @return {Array}
+ * An array of strings that represents all the properties.
+ */
+ H.keys = Object.keys;
/**
* Get the element's offset position, corrected for `overflow: auto`.
*
- * @function #offset
- * @memberOf Highcharts
- * @param {HTMLDOMElement} el - The HTML element.
- * @returns {Object} An object containing `left` and `top` properties for the
- * position in the page.
+ * @function Highcharts.offset
+ *
+ * @param {Highcharts.HTMLDOMElement} el
+ * The HTML element.
+ *
+ * @return {Highcharts.OffsetObject}
+ * An object containing `left` and `top` properties for the position in
+ * the page.
*/
- H.offset = function(el) {
+ H.offset = function (el) {
var docElem = doc.documentElement,
- box = el.parentElement ? // IE11 throws Unspecified error in test suite
- el.getBoundingClientRect() : {
- top: 0,
- left: 0
- };
+ box = (el.parentElement || el.parentNode) ?
+ el.getBoundingClientRect() :
+ { top: 0, left: 0 };
return {
top: box.top + (win.pageYOffset || docElem.scrollTop) -
@@ -1721,20 +2240,23 @@
/**
* Stop running animation.
*
- * @todo A possible extension to this would be to stop a single property, when
+ * @function Highcharts.stop
+ *
+ * @param {Highcharts.SVGElement} el
+ * The SVGElement to stop animation on.
+ *
+ * @param {string} [prop]
+ * The property to stop animating. If given, the stop method will stop a
+ * single property from animating, while others continue.
+ *
+ * @todo
+ * A possible extension to this would be to stop a single property, when
* we want to continue animating others. Then assign the prop to the timer
* in the Fx.run method, and check for the prop here. This would be an
* improvement in all cases where we stop the animation from .attr. Instead of
* stopping everything, we can just stop the actual attributes we're setting.
- *
- * @function #stop
- * @memberOf Highcharts
- * @param {SVGElement} el - The SVGElement to stop animation on.
- * @param {string} [prop] - The property to stop animating. If given, the stop
- * method will stop a single property from animating, while others continue.
- *
*/
- H.stop = function(el, prop) {
+ H.stop = function (el, prop) {
var i = H.timers.length;
@@ -1746,72 +2268,178 @@
}
};
- /**
- * Iterate over an array.
- *
- * @function #each
- * @memberOf Highcharts
- * @param {Array} arr - The array to iterate over.
- * @param {Function} fn - The iterator callback. It passes three arguments:
- * * item - The array item.
- * * index - The item's index in the array.
- * * arr - The array that each is being applied to.
- * @param {Object} [ctx] The context.
- */
- H.each = function(arr, fn, ctx) { // modern browsers
- return (H.forEachPolyfill || Array.prototype.forEach).call(arr, fn, ctx);
- };
-
/**
* Iterate over object key pairs in an object.
*
- * @function #objectEach
- * @memberOf Highcharts
- * @param {Object} obj - The object to iterate over.
- * @param {Function} fn - The iterator callback. It passes three arguments:
- * * value - The property value.
- * * key - The property key.
- * * obj - The object that objectEach is being applied to.
- * @param {Object} ctx The context
+ * @function Highcharts.objectEach
+ *
+ * @param {*} obj
+ * The object to iterate over.
+ *
+ * @param {Highcharts.ObjectEachCallbackFunction} fn
+ * The iterator callback. It passes three arguments:
+ * * value - The property value.
+ * * key - The property key.
+ * * obj - The object that objectEach is being applied to.
+ *
+ * @param {*} [ctx]
+ * The context.
*/
- H.objectEach = function(obj, fn, ctx) {
+ H.objectEach = function (obj, fn, ctx) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
- fn.call(ctx, obj[key], key, obj);
+ fn.call(ctx || obj[key], obj[key], key, obj);
}
}
};
+ /**
+ * Iterate over an array.
+ *
+ * @deprecated
+ * @function Highcharts.each
+ *
+ * @param {Array<*>} arr
+ * The array to iterate over.
+ *
+ * @param {Function} fn
+ * The iterator callback. It passes three arguments:
+ * - `item`: The array item.
+ * - `index`: The item's index in the array.
+ * - `arr`: The array that each is being applied to.
+ *
+ * @param {*} [ctx]
+ * The context.
+ */
+
+ /**
+ * Filter an array by a callback.
+ *
+ * @deprecated
+ * @function Highcharts.grep
+ *
+ * @param {Array<*>} arr
+ * The array to filter.
+ *
+ * @param {Function} callback
+ * The callback function. The function receives the item as the first
+ * argument. Return `true` if the item is to be preserved.
+ *
+ * @return {Array<*>}
+ * A new, filtered array.
+ */
+
+ /**
+ * Map an array by a callback.
+ *
+ * @deprecated
+ * @function Highcharts.map
+ *
+ * @param {Array<*>} arr
+ * The array to map.
+ *
+ * @param {Function} fn
+ * The callback function. Return the new value for the new array.
+ *
+ * @return {Array<*>}
+ * A new array item with modified items.
+ */
+
+ /**
+ * Reduce an array to a single value.
+ *
+ * @deprecated
+ * @function Highcharts.reduce
+ *
+ * @param {Array} arr
+ * The array to reduce.
+ *
+ * @param {Function} fn
+ * The callback function. Return the reduced value. Receives 4
+ * arguments: Accumulated/reduced value, current value, current array
+ * index, and the array.
+ *
+ * @param {*} initialValue
+ * The initial value of the accumulator.
+ *
+ * @return {*}
+ * The reduced value.
+ */
+
+ /**
+ * Test whether at least one element in the array passes the test implemented by
+ * the provided function.
+ *
+ * @deprecated
+ * @function Highcharts.some
+ *
+ * @param {Array<*>} arr
+ * The array to test
+ *
+ * @param {Function} fn
+ * The function to run on each item. Return truty to pass the test.
+ * Receives arguments `currentValue`, `index` and `array`.
+ *
+ * @param {*} ctx
+ * The context.
+ *
+ * @return {boolean}
+ */
+ H.objectEach({
+ map: 'map',
+ each: 'forEach',
+ grep: 'filter',
+ reduce: 'reduce',
+ some: 'some'
+ }, function (val, key) {
+ H[key] = function (arr) {
+ return Array.prototype[val].apply(
+ arr,
+ [].slice.call(arguments, 1)
+ );
+ };
+ });
+
/**
* Add an event listener.
*
- * @function #addEvent
- * @memberOf Highcharts
- * @param {Object} el - The element or object to add a listener to. It can be a
+ * @function Highcharts.addEvent
+ *
+ * @param {T} el
+ * The element or object to add a listener to. It can be a
* {@link HTMLDOMElement}, an {@link SVGElement} or any other object.
- * @param {String} type - The event type.
- * @param {Function} fn - The function callback to execute when the event is
- * fired.
- * @returns {Function} A callback function to remove the added event.
+ *
+ * @param {string} type
+ * The event type.
+ *
+ * @param {Highcharts.EventCallbackFunction} fn
+ * The function callback to execute when the event is fired.
+ *
+ * @param {Highcharts.EventOptionsObject} [options]
+ * Options for adding the event.
+ *
+ * @return {Function}
+ * A callback function to remove the added event.
*/
- H.addEvent = function(el, type, fn) {
+ H.addEvent = function (el, type, fn, options) {
var events,
- itemEvents,
addEventListener = el.addEventListener || H.addEventListenerPolyfill;
- // If events are previously set directly on the prototype, pick them up
- // and copy them over to the instance. Otherwise instance handlers would
- // be set on the prototype and apply to multiple charts in the page.
- if (el.hcEvents && !el.hasOwnProperty('hcEvents')) {
- itemEvents = {};
- H.objectEach(el.hcEvents, function(handlers, eventType) {
- itemEvents[eventType] = handlers.slice(0);
- });
- el.hcEvents = itemEvents;
+ // If we're setting events directly on the constructor, use a separate
+ // collection, `protoEvents` to distinguish it from the item events in
+ // `hcEvents`.
+ if (typeof el === 'function' && el.prototype) {
+ events = el.prototype.protoEvents = el.prototype.protoEvents || {};
+ } else {
+ events = el.hcEvents = el.hcEvents || {};
}
- events = el.hcEvents = el.hcEvents || {};
+ // Allow click events added to points, otherwise they will be prevented by
+ // the TouchPointer.pinch function after a pinch zoom operation (#7091).
+ if (H.Point && el instanceof H.Point && el.series && el.series.chart) {
+ el.series.chart.runTrackerClick = true;
+ }
// Handle DOM events
if (addEventListener) {
@@ -1824,8 +2452,16 @@
events[type].push(fn);
+ // Order the calls
+ if (options && H.isNumber(options.order)) {
+ fn.order = options.order;
+ events[type].sort(function (a, b) {
+ return a.order - b.order;
+ });
+ }
+
// Return a function that can be called to remove this event.
- return function() {
+ return function () {
H.removeEvent(el, type, fn);
};
};
@@ -1833,19 +2469,22 @@
/**
* Remove an event that was added with {@link Highcharts#addEvent}.
*
- * @function #removeEvent
- * @memberOf Highcharts
- * @param {Object} el - The element to remove events on.
- * @param {String} [type] - The type of events to remove. If undefined, all
- * events are removed from the element.
- * @param {Function} [fn] - The specific callback to remove. If undefined, all
- * events that match the element and optionally the type are removed.
- *
+ * @function Highcharts.removeEvent
+ *
+ * @param {T} el
+ * The element to remove events on.
+ *
+ * @param {string} [type]
+ * The type of events to remove. If undefined, all events are removed
+ * from the element.
+ *
+ * @param {Highcharts.EventCallbackFunction} [fn]
+ * The specific callback to remove. If undefined, all events that match
+ * the element and optionally the type are removed.
*/
- H.removeEvent = function(el, type, fn) {
+ H.removeEvent = function (el, type, fn) {
var events,
- hcEvents = el.hcEvents,
index;
function removeOneEvent(type, fn) {
@@ -1857,7 +2496,7 @@
}
}
- function removeAllEvents() {
+ function removeAllEvents(eventCollection) {
var types,
len;
@@ -1869,58 +2508,67 @@
types = {};
types[type] = true;
} else {
- types = hcEvents;
+ types = eventCollection;
}
- H.objectEach(types, function(val, n) {
- if (hcEvents[n]) {
- len = hcEvents[n].length;
+ H.objectEach(types, function (val, n) {
+ if (eventCollection[n]) {
+ len = eventCollection[n].length;
while (len--) {
- removeOneEvent(n, hcEvents[n][len]);
+ removeOneEvent(n, eventCollection[n][len]);
}
}
});
}
- if (hcEvents) {
- if (type) {
- events = hcEvents[type] || [];
- if (fn) {
- index = H.inArray(fn, events);
- if (index > -1) {
- events.splice(index, 1);
- hcEvents[type] = events;
- }
- removeOneEvent(type, fn);
+ ['protoEvents', 'hcEvents'].forEach(function (coll) {
+ var eventCollection = el[coll];
+
+ if (eventCollection) {
+ if (type) {
+ events = eventCollection[type] || [];
+ if (fn) {
+ index = events.indexOf(fn);
+ if (index > -1) {
+ events.splice(index, 1);
+ eventCollection[type] = events;
+ }
+ removeOneEvent(type, fn);
+ } else {
+ removeAllEvents(eventCollection);
+ eventCollection[type] = [];
+ }
} else {
- removeAllEvents();
- hcEvents[type] = [];
+ removeAllEvents(eventCollection);
+ el[coll] = {};
}
- } else {
- removeAllEvents();
- el.hcEvents = {};
}
- }
+ });
};
/**
* Fire an event that was registered with {@link Highcharts#addEvent}.
*
- * @function #fireEvent
- * @memberOf Highcharts
- * @param {Object} el - The object to fire the event on. It can be a
- * {@link HTMLDOMElement}, an {@link SVGElement} or any other object.
- * @param {String} type - The type of event.
- * @param {Object} [eventArguments] - Custom event arguments that are passed on
- * as an argument to the event handler.
- * @param {Function} [defaultFunction] - The default function to execute if the
- * other listeners haven't returned false.
- *
- */
- H.fireEvent = function(el, type, eventArguments, defaultFunction) {
+ * @function Highcharts.fireEvent
+ *
+ * @param {*} el
+ * The object to fire the event on. It can be a {@link HTMLDOMElement},
+ * an {@link SVGElement} or any other object.
+ *
+ * @param {string} type
+ * The type of event.
+ *
+ * @param {Highcharts.Dictionary<*>} [eventArguments]
+ * Custom event arguments that are passed on as an argument to the event
+ * handler.
+ *
+ * @param {Function} [defaultFunction]
+ * The default function to execute if the other listeners haven't
+ * returned false.
+ */
+ H.fireEvent = function (el, type, eventArguments, defaultFunction) {
var e,
- hcEvents = el.hcEvents,
events,
len,
i,
@@ -1940,76 +2588,70 @@
el.fireEvent(type, e);
}
- } else if (hcEvents) {
+ } else {
- events = hcEvents[type] || [];
- len = events.length;
+ ['protoEvents', 'hcEvents'].forEach(function (coll) {
+
+ if (el[coll]) {
+ events = el[coll][type] || [];
+ len = events.length;
+
+ if (!eventArguments.target) { // We're running a custom event
+
+ H.extend(eventArguments, {
+ // Attach a simple preventDefault function to skip
+ // default handler if called. The built-in
+ // defaultPrevented property is not overwritable (#5112)
+ preventDefault: function () {
+ eventArguments.defaultPrevented = true;
+ },
+ // Setting target to native events fails with clicking
+ // the zoom-out button in Chrome.
+ target: el,
+ // If the type is not set, we're running a custom event
+ // (#2297). If it is set, we're running a browser event,
+ // and setting it will cause en error in IE8 (#2465).
+ type: type
+ });
+ }
- if (!eventArguments.target) { // We're running a custom event
-
- H.extend(eventArguments, {
- // Attach a simple preventDefault function to skip default
- // handler if called. The built-in defaultPrevented property is
- // not overwritable (#5112)
- preventDefault: function() {
- eventArguments.defaultPrevented = true;
- },
- // Setting target to native events fails with clicking the
- // zoom-out button in Chrome.
- target: el,
- // If the type is not set, we're running a custom event (#2297).
- // If it is set, we're running a browser event, and setting it
- // will cause en error in IE8 (#2465).
- type: type
- });
- }
+ for (i = 0; i < len; i++) {
+ fn = events[i];
- for (i = 0; i < len; i++) {
- fn = events[i];
-
- // If the event handler return false, prevent the default handler
- // from executing
- if (fn && fn.call(el, eventArguments) === false) {
- eventArguments.preventDefault();
+ // If the event handler return false, prevent the default
+ // handler from executing
+ if (fn && fn.call(el, eventArguments) === false) {
+ eventArguments.preventDefault();
+ }
+ }
}
- }
+ });
}
// Run the default if not prevented
if (defaultFunction && !eventArguments.defaultPrevented) {
- defaultFunction(eventArguments);
+ defaultFunction.call(el, eventArguments);
}
};
- /**
- * An animation configuration. Animation configurations can also be defined as
- * booleans, where `false` turns off animation and `true` defaults to a duration
- * of 500ms.
- * @typedef {Object} AnimationOptions
- * @property {Number} duration - The animation duration in milliseconds.
- * @property {String} [easing] - The name of an easing function as defined on
- * the `Math` object.
- * @property {Function} [complete] - A callback function to exectute when the
- * animation finishes.
- * @property {Function} [step] - A callback function to execute on each step of
- * each attribute or CSS property that's being animated. The first argument
- * contains information about the animation and progress.
- */
-
-
/**
* The global animate method, which uses Fx to create individual animators.
*
- * @function #animate
- * @memberOf Highcharts
- * @param {HTMLDOMElement|SVGElement} el - The element to animate.
- * @param {Object} params - An object containing key-value pairs of the
- * properties to animate. Supports numeric as pixel-based CSS properties
- * for HTML objects and attributes for SVGElements.
- * @param {AnimationOptions} [opt] - Animation options.
+ * @function Highcharts.animate
+ *
+ * @param {Highcharts.HTMLDOMElement|Highcharts.SVGElement} el
+ * The element to animate.
+ *
+ * @param {Highcharts.HTMLAttributes|Highcharts.SVGAttributes} params
+ * An object containing key-value pairs of the properties to animate.
+ * Supports numeric as pixel-based CSS properties for HTML objects and
+ * attributes for SVGElements.
+ *
+ * @param {Highcharts.AnimationOptionsObject} [opt]
+ * Animation options.
*/
- H.animate = function(el, params, opt) {
+ H.animate = function (el, params, opt) {
var start,
unit = '',
end,
@@ -2032,7 +2674,7 @@
(Math[opt.easing] || Math.easeInOutSine);
opt.curAnim = H.merge(params);
- H.objectEach(params, function(val, prop) {
+ H.objectEach(params, function (val, prop) {
// Stop current running animation of this property
H.stop(el, prop);
@@ -2070,23 +2712,33 @@
/**
* Factory to create new series prototypes.
*
- * @function #seriesType
- * @memberOf Highcharts
- *
- * @param {String} type - The series type name.
- * @param {String} parent - The parent series type name. Use `line` to inherit
- * from the basic {@link Series} object.
- * @param {Object} options - The additional default options that is merged with
- * the parent's options.
- * @param {Object} props - The properties (functions and primitives) to set on
- * the new prototype.
- * @param {Object} [pointProps] - Members for a series-specific extension of the
- * {@link Point} prototype if needed.
- * @returns {*} - The newly created prototype as extended from {@link Series}
- * or its derivatives.
+ * @function Highcharts.seriesType
+ *
+ * @param {string} type
+ * The series type name.
+ *
+ * @param {string} parent
+ * The parent series type name. Use `line` to inherit from the basic
+ * {@link Series} object.
+ *
+ * @param {*} options
+ * The additional default options that is merged with the parent's
+ * options.
+ *
+ * @param {*} props
+ * The properties (functions and primitives) to set on the new
+ * prototype.
+ *
+ * @param {*} [pointProps]
+ * Members for a series-specific extension of the {@link Point}
+ * prototype if needed.
+ *
+ * @return {Highcharts.Series}
+ * The newly created prototype as extended from {@link Series} or its
+ * derivatives.
*/
// docs: add to API + extending Highcharts
- H.seriesType = function(type, parent, options, props, pointProps) {
+ H.seriesType = function (type, parent, options, props, pointProps) {
var defaultOptions = H.getOptions(),
seriesTypes = H.seriesTypes;
@@ -2098,7 +2750,7 @@
// Create the class
seriesTypes[type] = H.extendClass(seriesTypes[parent] ||
- function() {}, props);
+ function () {}, props);
seriesTypes[type].prototype.type = type;
// Create the point class if needed
@@ -2111,30 +2763,71 @@
};
/**
- * Get a unique key for using in internal element id's and pointers. The key
- * is composed of a random hash specific to this Highcharts instance, and a
+ * Get a unique key for using in internal element id's and pointers. The key is
+ * composed of a random hash specific to this Highcharts instance, and a
* counter.
- * @function #uniqueKey
- * @memberOf Highcharts
- * @return {string} The key.
+ *
* @example
* var id = H.uniqueKey(); // => 'highcharts-x45f6hp-0'
+ *
+ * @function Highcharts.uniqueKey
+ *
+ * @return {string}
+ * A unique key.
*/
- H.uniqueKey = (function() {
+ H.uniqueKey = (function () {
var uniqueKeyHash = Math.random().toString(36).substring(2, 9),
idCounter = 0;
- return function() {
+ return function () {
return 'highcharts-' + uniqueKeyHash + '-' + idCounter++;
};
}());
- /**
- * Register Highcharts as a plugin in jQuery
- */
+ H.isFunction = function (obj) {
+ return typeof obj === 'function';
+ };
+
+ // Register Highcharts as a plugin in jQuery
if (win.jQuery) {
- win.jQuery.fn.highcharts = function() {
+
+ /**
+ * Highcharts-extended JQuery.
+ *
+ * @external JQuery
+ */
+
+ /**
+ * Helper function to return the chart of the current JQuery selector
+ * element.
+ *
+ * @function external:JQuery#highcharts
+ *
+ * @return {Highcharts.Chart}
+ * The chart that is linked to the JQuery selector element.
+ *//**
+ * Factory function to create a chart in the current JQuery selector
+ * element.
+ *
+ * @function external:JQuery#highcharts
+ *
+ * @param {"Chart"|"Map"|"StockChart"|string} [className]
+ * Name of the factory class in the Highcharts namespace.
+ *
+ * @param {Highcharts.Options} [options]
+ * The chart options structure.
+ *
+ * @param {Highcharts.ChartCallbackFunction} [callback]
+ * Function to run when the chart has loaded and and all external
+ * images are loaded. Defining a
+ * [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load)
+ * handler is equivalent.
+ *
+ * @return {JQuery}
+ * The current JQuery selector.
+ */
+ win.jQuery.fn.highcharts = function () {
var args = [].slice.call(arguments);
if (this[0]) { // this[0] is the renderTo div
@@ -2156,31 +2849,40 @@
}
}(Highcharts));
- (function(H) {
+ (function (H) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
- var each = H.each,
- isNumber = H.isNumber,
- map = H.map,
- merge = H.merge,
- pInt = H.pInt;
/**
- * @typedef {string} ColorString
- * A valid color to be parsed and handled by Highcharts. Highcharts internally
+ * A valid color to be parsed and handled by Highcharts. Highcharts internally
* supports hex colors like `#ffffff`, rgb colors like `rgb(255,255,255)` and
* rgba colors like `rgba(255,255,255,1)`. Other colors may be supported by the
* browsers and displayed correctly, but Highcharts is not able to process them
* and apply concepts like opacity and brightening.
+ *
+ * @typedef {string} Highcharts.ColorString
*/
+
+
+
+ var isNumber = H.isNumber,
+ merge = H.merge,
+ pInt = H.pInt;
+
/**
* Handle color operations. The object methods are chainable.
- * @param {String} input The input color in either rbga or hex format
+ *
+ * @private
+ * @class
+ * @name Highcharts.Color
+ *
+ * @param {Highcharts.ColorString} input
+ * The input color in either rbga or hex format
*/
- H.Color = function(input) {
+ H.Color = function (input) {
// Backwards compatibility, allow instanciation without new
if (!(this instanceof H.Color)) {
return new H.Color(input);
@@ -2190,18 +2892,24 @@
};
H.Color.prototype = {
- // Collection of parsers. This can be extended from the outside by pushing parsers
- // to Highcharts.Color.prototype.parsers.
+ // Collection of parsers. This can be extended from the outside by pushing
+ // parsers to Highcharts.Color.prototype.parsers.
parsers: [{
// RGBA color
- regex: /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/,
- parse: function(result) {
- return [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
+ regex: /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/, // eslint-disable-line security/detect-unsafe-regex
+ parse: function (result) {
+ return [
+ pInt(result[1]),
+ pInt(result[2]),
+ pInt(result[3]),
+ parseFloat(result[4], 10)
+ ];
}
}, {
// RGB color
- regex: /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/,
- parse: function(result) {
+ regex:
+ /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/,
+ parse: function (result) {
return [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1];
}
}],
@@ -2209,16 +2917,20 @@
// Collection of named colors. Can be extended from the outside by adding
// colors to Highcharts.Color.prototype.names.
names: {
- none: 'rgba(255,255,255,0)',
white: '#ffffff',
black: '#000000'
},
/**
* Parse the input color to rgba array
- * @param {String} input
+ *
+ * @private
+ * @function Highcharts.Color#init
+ *
+ * @param {Highcharts.ColorString} input
+ * The input color in either rbga or hex format
*/
- init: function(input) {
+ init: function (input) {
var result,
rgba,
i,
@@ -2227,17 +2939,17 @@
this.input = input = this.names[
input && input.toLowerCase ?
- input.toLowerCase() :
- ''
+ input.toLowerCase() :
+ ''
] || input;
// Gradients
if (input && input.stops) {
- this.stops = map(input.stops, function(stop) {
+ this.stops = input.stops.map(function (stop) {
return new H.Color(stop[1]);
});
- // Solid colors
+ // Solid colors
} else {
// Bitmasking as input[0] is not working for legacy IE.
@@ -2256,9 +2968,9 @@
1
];
- // Handle short-form, e.g. #ABC
- // In short form, the value is assumed to be the same
- // for both nibbles for each component. e.g. #ABC = #AABBCC
+ // Handle short-form, e.g. #ABC
+ // In short form, the value is assumed to be the same
+ // for both nibbles for each component. e.g. #ABC = #AABBCC
} else if (len === 4) {
rgba = [
@@ -2286,10 +2998,17 @@
},
/**
- * Return the color a specified format
- * @param {String} format
+ * Return the color in the specified format
+ *
+ * @function Highcharts.Color#get
+ *
+ * @param {string} format
+ * Possible values are 'a', 'rgb', undefined
+ *
+ * @return {Highcharts.ColorString}
+ * This color as a string.
*/
- get: function(format) {
+ get: function (format) {
var input = this.input,
rgba = this.rgba,
ret;
@@ -2297,11 +3016,11 @@
if (this.stops) {
ret = merge(input);
ret.stops = [].concat(ret.stops);
- each(this.stops, function(stop, i) {
+ this.stops.forEach(function (stop, i) {
ret.stops[i] = [ret.stops[i][0], stop.get(format)];
});
- // it's NaN if gradient colors on a column chart
+ // it's NaN if gradient colors on a column chart
} else if (rgba && isNumber(rgba[0])) {
if (format === 'rgb' || (!format && rgba[3] === 1)) {
ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')';
@@ -2317,15 +3036,22 @@
},
/**
- * Brighten the color
- * @param {Number} alpha
+ * Brighten the color instance.
+ *
+ * @function Highcharts.Color#brighten
+ *
+ * @param {number} alpha
+ * The alpha value.
+ *
+ * @return {Highcharts.ColorString}
+ * This color with modifications.
*/
- brighten: function(alpha) {
+ brighten: function (alpha) {
var i,
rgba = this.rgba;
if (this.stops) {
- each(this.stops, function(stop) {
+ this.stops.forEach(function (stop) {
stop.brighten(alpha);
});
@@ -2345,27 +3071,37 @@
},
/**
- * Set the color's opacity to a given alpha value
- * @param {Number} alpha
+ * Set the color's opacity to a given alpha value.
+ *
+ * @function Highcharts.Color#setOpacity
+ *
+ * @param {number} alpha
+ * Opacity between 0 and 1.
+ *
+ * @return {Highcharts.ColorString}
+ * Color with modifications.
*/
- setOpacity: function(alpha) {
+ setOpacity: function (alpha) {
this.rgba[3] = alpha;
return this;
},
- /*
+ /**
* Return an intermediate color between two colors.
*
- * @param {Highcharts.Color} to
- * The color object to tween to.
- * @param {Number} pos
- * The intermediate position, where 0 is the from color (current
- * color item), and 1 is the `to` color.
+ * @function Highcharts.Color#tweenTo
+ *
+ * @param {Highcharts.Color} to
+ * The color object to tween to.
+ *
+ * @param {number} pos
+ * The intermediate position, where 0 is the from color (current
+ * color item), and 1 is the `to` color.
*
- * @return {String}
+ * @return {Highcharts.ColorString}
* The intermediate color in rgba notation.
*/
- tweenTo: function(to, pos) {
+ tweenTo: function (to, pos) {
// Check for has alpha, because rgba colors perform worse due to lack of
// support in WebKit.
var fromRgba = this.rgba,
@@ -2377,7 +3113,7 @@
if (!toRgba.length || !fromRgba || !fromRgba.length) {
ret = to.input || 'none';
- // Interpolate
+ // Interpolate
} else {
hasAlpha = (toRgba[3] !== 1 || fromRgba[3] !== 1);
ret = (hasAlpha ? 'rgba(' : 'rgb(') +
@@ -2388,28 +3124,382 @@
Math.round(toRgba[2] + (fromRgba[2] - toRgba[2]) * (1 - pos)) +
(
hasAlpha ?
- (
- ',' +
- (toRgba[3] + (fromRgba[3] - toRgba[3]) * (1 - pos))
- ) :
- ''
+ (
+ ',' +
+ (toRgba[3] + (fromRgba[3] - toRgba[3]) * (1 - pos))
+ ) :
+ ''
) +
')';
}
return ret;
}
};
- H.color = function(input) {
+
+ /**
+ * Creates a color instance out of a color string.
+ *
+ * @private
+ * @function Highcharts.color
+ *
+ * @param {Highcharts.ColorString} input
+ * The input color in either rbga or hex format
+ */
+ H.color = function (input) {
return new H.Color(input);
};
}(Highcharts));
- (function(H) {
+ (function (H) {
+ /* *
+ *
+ * (c) 2010-2019 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ *
+ * */
+
/**
- * (c) 2010-2017 Torstein Honsi
+ * The horizontal alignment of an element.
*
- * License: www.highcharts.com/license
+ * @typedef {"center"|"left"|"right"} Highcharts.AlignType
+ */
+
+ /**
+ * Options to align the element relative to the chart or another box.
+ *
+ * @interface Highcharts.AlignObject
+ *//**
+ * Horizontal alignment. Can be one of `left`, `center` and `right`.
+ *
+ * @name Highcharts.AlignObject#align
+ * @type {Highcharts.AlignType|undefined}
+ *
+ * @default left
+ *//**
+ * Vertical alignment. Can be one of `top`, `middle` and `bottom`.
+ *
+ * @name Highcharts.AlignObject#verticalAlign
+ * @type {Highcharts.VerticalAlignType|undefined}
+ *
+ * @default top
+ *//**
+ * Horizontal pixel offset from alignment.
+ *
+ * @name Highcharts.AlignObject#x
+ * @type {number|undefined}
+ *
+ * @default 0
+ *//**
+ * Vertical pixel offset from alignment.
+ *
+ * @name Highcharts.AlignObject#y
+ * @type {number|undefined}
+ *
+ * @default 0
+ *//**
+ * Use the `transform` attribute with translateX and translateY custom
+ * attributes to align this elements rather than `x` and `y` attributes.
+ *
+ * @name Highcharts.AlignObject#alignByTranslate
+ * @type {boolean|undefined}
+ *
+ * @default false
+ */
+
+ /**
+ * Bounding box of an element.
+ *
+ * @interface Highcharts.BBoxObject
+ *//**
+ * Height of the bounding box.
+ *
+ * @name Highcharts.BBoxObject#height
+ * @type {number}
+ *//**
+ * Width of the bounding box.
+ *
+ * @name Highcharts.BBoxObject#width
+ * @type {number}
+ *//**
+ * Horizontal position of the bounding box.
+ *
+ * @name Highcharts.BBoxObject#x
+ * @type {number}
+ *//**
+ * Vertical position of the bounding box.
+ *
+ * @name Highcharts.BBoxObject#y
+ * @type {number}
+ */
+
+ /**
+ * A clipping rectangle that can be applied to one or more {@link SVGElement}
+ * instances. It is instanciated with the {@link SVGRenderer#clipRect} function
+ * and applied with the {@link SVGElement#clip} function.
+ *
+ * @example
+ * var circle = renderer.circle(100, 100, 100)
+ * .attr({ fill: 'red' })
+ * .add();
+ * var clipRect = renderer.clipRect(100, 100, 100, 100);
+ *
+ * // Leave only the lower right quarter visible
+ * circle.clip(clipRect);
+ *
+ * @typedef {Highcharts.SVGElement} Highcharts.ClipRectElement
*/
+
+ /**
+ * The font metrics.
+ *
+ * @interface Highcharts.FontMetricsObject
+ *//**
+ * The baseline relative to the top of the box.
+ *
+ * @name Highcharts.FontMetricsObject#b
+ * @type {number}
+ *//**
+ * The line height.
+ *
+ * @name Highcharts.FontMetricsObject#h
+ * @type {number}
+ *//**
+ * The font size.
+ *
+ * @name Highcharts.FontMetricsObject#f
+ * @type {number}
+ */
+
+ /**
+ * Gradient options instead of a solid color.
+ *
+ * @example
+ * // Linear gradient used as a color option
+ * color: {
+ * linearGradient: { x1: 0, x2: 0, y1: 0, y2: 1 },
+ * stops: [
+ * [0, '#003399'], // start
+ * [0.5, '#ffffff'], // middle
+ * [1, '#3366AA'] // end
+ * ]
+ * }
+ * }
+ *
+ * @interface Highcharts.GradientColorObject
+ *//**
+ * Holds an object that defines the start position and the end position relative
+ * to the shape.
+ * @name Highcharts.GradientColorObject#linearGradient
+ * @type {Highcharts.LinearGradientColorObject|undefined}
+ *//**
+ * Holds an object that defines the center position and the radius.
+ * @name Highcharts.GradientColorObject#radialGradient
+ * @type {Highcharts.RadialGradientColorObject|undefined}
+ *//**
+ * The first item in each tuple is the position in the gradient, where 0 is the
+ * start of the gradient and 1 is the end of the gradient. Multiple stops can be
+ * applied. The second item is the color for each stop. This color can also be
+ * given in the rgba format.
+ * @name Highcharts.GradientColorObject#stops
+ * @type {Array>|undefined}
+ */
+
+ /**
+ * Defines the start position and the end position for a gradient relative
+ * to the shape. Start position (x1, y1) and end position (x2, y2) are relative
+ * to the shape, where 0 means top/left and 1 is bottom/right.
+ *
+ * @interface Highcharts.LinearGradientColorObject
+ *//**
+ * Start horizontal position of the gradient. Float ranges 0-1.
+ * @name Highcharts.LinearGradientColorObject#x1
+ * @type {number}
+ *//**
+ * End horizontal position of the gradient. Float ranges 0-1.
+ * @name Highcharts.LinearGradientColorObject#x2
+ * @type {number}
+ *//**
+ * Start vertical position of the gradient. Float ranges 0-1.
+ * @name Highcharts.LinearGradientColorObject#y1
+ * @type {number}
+ *//**
+ * End vertical position of the gradient. Float ranges 0-1.
+ * @name Highcharts.LinearGradientColorObject#y2
+ * @type {number}
+ */
+
+ /**
+ * An object containing `x` and `y` properties for the position of an element.
+ *
+ * @interface Highcharts.PositionObject
+ *//**
+ * X position of the element.
+ * @name Highcharts.PositionObject#x
+ * @type {number}
+ *//**
+ * Y position of the element.
+ * @name Highcharts.PositionObject#y
+ * @type {number}
+ */
+
+ /**
+ * Defines the center position and the radius for a gradient.
+ *
+ * @interface Highcharts.RadialGradientColorObject
+ *//**
+ * Center horizontal position relative to the shape. Float ranges 0-1.
+ * @name Highcharts.RadialGradientColorObject#cx
+ * @type {number}
+ *//**
+ * Center vertical position relative to the shape. Float ranges 0-1.
+ * @name Highcharts.RadialGradientColorObject#cy
+ * @type {number}
+ *//**
+ * Radius relative to the shape. Float ranges 0-1.
+ * @name Highcharts.RadialGradientColorObject#r
+ * @type {number}
+ */
+
+ /**
+ * A rectangle.
+ *
+ * @interface Highcharts.RectangleObject
+ *//**
+ * Height of the rectangle.
+ * @name Highcharts.RectangleObject#height
+ * @type {number}
+ *//**
+ * Width of the rectangle.
+ * @name Highcharts.RectangleObject#width
+ * @type {number}
+ *//**
+ * Horizontal position of the rectangle.
+ * @name Highcharts.RectangleObject#x
+ * @type {number}
+ *//**
+ * Vertical position of the rectangle.
+ * @name Highcharts.RectangleObject#y
+ * @type {number}
+ */
+
+ /**
+ * The shadow options.
+ *
+ * @interface Highcharts.ShadowOptionsObject
+ *//**
+ * The shadow color.
+ * @name Highcharts.ShadowOptionsObject#color
+ * @type {string|undefined}
+ * @default #000000
+ *//**
+ * The horizontal offset from the element.
+ *
+ * @name Highcharts.ShadowOptionsObject#offsetX
+ * @type {number|undefined}
+ * @default 1
+ *//**
+ * The vertical offset from the element.
+ * @name Highcharts.ShadowOptionsObject#offsetY
+ * @type {number|undefined}
+ * @default 1
+ *//**
+ * The shadow opacity.
+ *
+ * @name Highcharts.ShadowOptionsObject#opacity
+ * @type {number|undefined}
+ * @default 0.15
+ *//**
+ * The shadow width or distance from the element.
+ * @name Highcharts.ShadowOptionsObject#width
+ * @type {number|undefined}
+ * @default 3
+ */
+
+ /**
+ * Serialized form of an SVG definition, including children. Some key
+ * property names are reserved: tagName, textContent, and children.
+ *
+ * @interface Highcharts.SVGDefinitionObject
+ *//**
+ * @name Highcharts.SVGDefinitionObject#[key:string]
+ * @type {number|string|Array|undefined}
+ *//**
+ * @name Highcharts.SVGDefinitionObject#children
+ * @type {Array|undefined}
+ *//**
+ * @name Highcharts.SVGDefinitionObject#tagName
+ * @type {string|undefined}
+ *//**
+ * @name Highcharts.SVGDefinitionObject#textContent
+ * @type {string|undefined}
+ */
+
+ /**
+ * An extendable collection of functions for defining symbol paths.
+ *
+ * @typedef Highcharts.SymbolDictionary
+ *
+ * @property {Function|undefined} [key:Highcharts.SymbolKey]
+ */
+
+ /**
+ * Can be one of `arc`, `callout`, `circle`, `diamond`, `square`,
+ * `triangle`, `triangle-down`. Symbols are used internally for point
+ * markers, button and label borders and backgrounds, or custom shapes.
+ * Extendable by adding to {@link SVGRenderer#symbols}.
+ *
+ * @typedef {string} Highcharts.SymbolKey
+ * @validvalue ["arc", "callout", "circle", "diamond", "square", "triangle",
+ * "triangle-down"]
+ */
+
+ /**
+ * Additional options, depending on the actual symbol drawn.
+ *
+ * @interface Highcharts.SymbolOptionsObject
+ *//**
+ * The anchor X position for the `callout` symbol. This is where the chevron
+ * points to.
+ *
+ * @name Highcharts.SymbolOptionsObject#anchorX
+ * @type {number}
+ *//**
+ * The anchor Y position for the `callout` symbol. This is where the chevron
+ * points to.
+ *
+ * @name Highcharts.SymbolOptionsObject#anchorY
+ * @type {number}
+ *//**
+ * The end angle of an `arc` symbol.
+ *
+ * @name Highcharts.SymbolOptionsObject#end
+ * @type {number}
+ *//**
+ * Whether to draw `arc` symbol open or closed.
+ *
+ * @name Highcharts.SymbolOptionsObject#open
+ * @type {boolean}
+ *//**
+ * The radius of an `arc` symbol, or the border radius for the `callout` symbol.
+ *
+ * @name Highcharts.SymbolOptionsObject#r
+ * @type {number}
+ *//**
+ * The start angle of an `arc` symbol.
+ *
+ * @name Highcharts.SymbolOptionsObject#start
+ * @type {number}
+ */
+
+ /**
+ * The vertical alignment of an element.
+ *
+ * @typedef {"bottom"|"middle"|"top"} Highcharts.VerticalAlignType
+ */
+
+
+
var SVGElement,
SVGRenderer,
@@ -2424,12 +3514,9 @@
deg2rad = H.deg2rad,
destroyObjectProperties = H.destroyObjectProperties,
doc = H.doc,
- each = H.each,
extend = H.extend,
erase = H.erase,
- grep = H.grep,
hasTouch = H.hasTouch,
- inArray = H.inArray,
isArray = H.isArray,
isFirefox = H.isFirefox,
isMS = H.isMS,
@@ -2449,9 +3536,6 @@
symbolSizes = H.symbolSizes,
win = H.win;
- /**
- * @typedef {Object} SVGDOMElement - An SVG DOM element.
- */
/**
* The SVGElement prototype is a JavaScript wrapper for SVG elements used in the
* rendering layer of Highcharts. Combined with the {@link
@@ -2460,16 +3544,16 @@
* SVGElement can also wrap HTML labels, when `text` or `label` elements are
* created with the `useHTML` parameter.
*
- * The SVGElement instances are created through factory functions on the
- * {@link Highcharts.SVGRenderer} object, like
- * [rect]{@link Highcharts.SVGRenderer#rect}, [path]{@link
- * Highcharts.SVGRenderer#path}, [text]{@link Highcharts.SVGRenderer#text},
- * [label]{@link Highcharts.SVGRenderer#label}, [g]{@link
- * Highcharts.SVGRenderer#g} and more.
+ * The SVGElement instances are created through factory functions on the {@link
+ * Highcharts.SVGRenderer} object, like {@link Highcharts.SVGRenderer#rect|
+ * rect}, {@link Highcharts.SVGRenderer#path|path}, {@link
+ * Highcharts.SVGRenderer#text|text}, {@link Highcharts.SVGRenderer#label|
+ * label}, {@link Highcharts.SVGRenderer#g|g} and more.
*
- * @class Highcharts.SVGElement
+ * @class
+ * @name Highcharts.SVGElement
*/
- SVGElement = H.SVGElement = function() {
+ SVGElement = H.SVGElement = function () {
return this;
};
extend(SVGElement.prototype, /** @lends Highcharts.SVGElement.prototype */ {
@@ -2482,32 +3566,33 @@
* For labels, these CSS properties are applied to the `text` node directly.
*
* @private
- * @type {Array.}
+ * @name Highcharts.SVGElement#textProps
+ * @type {Array}
*/
textProps: ['direction', 'fontSize', 'fontWeight', 'fontFamily',
'fontStyle', 'color', 'lineHeight', 'width', 'textAlign',
- 'textDecoration', 'textOverflow', 'textOutline'
- ],
+ 'textDecoration', 'textOverflow', 'textOutline', 'cursor'],
/**
- * Initialize the SVG renderer. This function only exists to make the
- * initiation process overridable. It should not be called directly.
+ * Initialize the SVG element. This function only exists to make the
+ * initialization process overridable. It should not be called directly.
+ *
+ * @function Highcharts.SVGElement#init
+ *
+ * @param {Highcharts.SVGRenderer} renderer
+ * The SVGRenderer instance to initialize to.
*
- * @param {SVGRenderer} renderer
- * The SVGRenderer instance to initialize to.
- * @param {String} nodeName
- * The SVG node name.
- *
+ * @param {string} nodeName
+ * The SVG node name.
*/
- init: function(renderer, nodeName) {
+ init: function (renderer, nodeName) {
- /**
+ /**
* The primary DOM node. Each `SVGElement` instance wraps a main DOM
* node, but may also represent more nodes.
*
- * @name element
- * @memberOf SVGElement
- * @type {SVGDOMNode|HTMLDOMNode}
+ * @name Highcharts.SVGElement#element
+ * @type {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement}
*/
this.element = nodeName === 'span' ?
createElement(nodeName) :
@@ -2516,29 +3601,45 @@
/**
* The renderer that the SVGElement belongs to.
*
- * @name renderer
- * @memberOf SVGElement
- * @type {SVGRenderer}
+ * @name Highcharts.SVGElement#renderer
+ * @type {Highcharts.SVGRenderer}
*/
this.renderer = renderer;
+
+ H.fireEvent(this, 'afterInit');
},
/**
* Animate to given attributes or CSS properties.
- *
- * @param {SVGAttributes} params SVG attributes or CSS to animate.
- * @param {AnimationOptions} [options] Animation options.
- * @param {Function} [complete] Function to perform at the end of animation.
*
* @sample highcharts/members/element-on/
* Setting some attributes by animation
- *
- * @returns {SVGElement} Returns the SVGElement for chaining.
+ *
+ * @function Highcharts.SVGElement#animate
+ *
+ * @param {Highcharts.SVGAttributes} params
+ * SVG attributes or CSS to animate.
+ *
+ * @param {Highcharts.AnimationOptionsObject} [options]
+ * Animation options.
+ *
+ * @param {Function} [complete]
+ * Function to perform at the end of animation.
+ *
+ * @return {Highcharts.SVGElement}
+ * Returns the SVGElement for chaining.
*/
- animate: function(params, options, complete) {
+ animate: function (params, options, complete) {
var animOptions = H.animObject(
pick(options, this.renderer.globalAnimation, true)
);
+
+ // When the page is hidden save resources in the background by not
+ // running animation at all (#9749).
+ if (pick(doc.hidden, doc.msHidden, doc.webkitHidden, false)) {
+ animOptions.duration = 0;
+ }
+
if (animOptions.duration !== 0) {
// allows using a callback with the global animation without
// overwriting it
@@ -2548,62 +3649,35 @@
animate(this, params, animOptions);
} else {
this.attr(params, null, complete);
- if (animOptions.step) {
- animOptions.step.call(this);
- }
+
+ // Call the end step synchronously
+ H.objectEach(params, function (val, prop) {
+ if (animOptions.step) {
+ animOptions.step.call(this, val, { prop: prop, pos: 1 });
+ }
+ }, this);
}
return this;
},
- /**
- * @typedef {Object} GradientOptions
- * @property {Object} linearGradient Holds an object that defines the start
- * position and the end position relative to the shape.
- * @property {Number} linearGradient.x1 Start horizontal position of the
- * gradient. Ranges 0-1.
- * @property {Number} linearGradient.x2 End horizontal position of the
- * gradient. Ranges 0-1.
- * @property {Number} linearGradient.y1 Start vertical position of the
- * gradient. Ranges 0-1.
- * @property {Number} linearGradient.y2 End vertical position of the
- * gradient. Ranges 0-1.
- * @property {Object} radialGradient Holds an object that defines the center
- * position and the radius.
- * @property {Number} radialGradient.cx Center horizontal position relative
- * to the shape. Ranges 0-1.
- * @property {Number} radialGradient.cy Center vertical position relative
- * to the shape. Ranges 0-1.
- * @property {Number} radialGradient.r Radius relative to the shape. Ranges
- * 0-1.
- * @property {Array.} stops The first item in each tuple is the
- * position in the gradient, where 0 is the start of the gradient and 1
- * is the end of the gradient. Multiple stops can be applied. The second
- * item is the color for each stop. This color can also be given in the
- * rgba format.
- *
- * @example
- * // Linear gradient used as a color option
- * color: {
- * linearGradient: { x1: 0, x2: 0, y1: 0, y2: 1 },
- * stops: [
- * [0, '#003399'], // start
- * [0.5, '#ffffff'], // middle
- * [1, '#3366AA'] // end
- * ]
- * }
- * }
- */
/**
* Build and apply an SVG gradient out of a common JavaScript configuration
- * object. This function is called from the attribute setters.
+ * object. This function is called from the attribute setters. An event
+ * hook is added for supporting other complex color types.
*
* @private
- * @param {GradientOptions} color The gradient options structure.
- * @param {string} prop The property to apply, can either be `fill` or
- * `stroke`.
- * @param {SVGDOMElement} elem SVG DOM element to apply the gradient on.
+ * @function Highcharts.SVGElement#complexColor
+ *
+ * @param {Highcharts.GradientColorObject} color
+ * The gradient options structure.
+ *
+ * @param {string} prop
+ * The property to apply, can either be `fill` or `stroke`.
+ *
+ * @param {Highcharts.SVGDOMElement} elem
+ * SVG DOM element to apply the gradient on.
*/
- colorGradient: function(color, prop, elem) {
+ complexColor: function (color, prop, elem) {
var renderer = this.renderer,
colorObject,
gradName,
@@ -2619,121 +3693,123 @@
key = [],
value;
- // Apply linear or radial gradients
- if (color.radialGradient) {
- gradName = 'radialGradient';
- } else if (color.linearGradient) {
- gradName = 'linearGradient';
- }
-
- if (gradName) {
- gradAttr = color[gradName];
- gradients = renderer.gradients;
- stops = color.stops;
- radialReference = elem.radialReference;
-
- // Keep < 2.2 kompatibility
- if (isArray(gradAttr)) {
- color[gradName] = gradAttr = {
- x1: gradAttr[0],
- y1: gradAttr[1],
- x2: gradAttr[2],
- y2: gradAttr[3],
- gradientUnits: 'userSpaceOnUse'
- };
- }
-
- // Correct the radial gradient for the radial reference system
- if (
- gradName === 'radialGradient' &&
- radialReference &&
- !defined(gradAttr.gradientUnits)
- ) {
- radAttr = gradAttr; // Save the radial attributes for updating
- gradAttr = merge(
- gradAttr,
- renderer.getRadialAttr(radialReference, radAttr), {
+ H.fireEvent(this.renderer, 'complexColor', {
+ args: arguments
+ }, function () {
+ // Apply linear or radial gradients
+ if (color.radialGradient) {
+ gradName = 'radialGradient';
+ } else if (color.linearGradient) {
+ gradName = 'linearGradient';
+ }
+
+ if (gradName) {
+ gradAttr = color[gradName];
+ gradients = renderer.gradients;
+ stops = color.stops;
+ radialReference = elem.radialReference;
+
+ // Keep < 2.2 kompatibility
+ if (isArray(gradAttr)) {
+ color[gradName] = gradAttr = {
+ x1: gradAttr[0],
+ y1: gradAttr[1],
+ x2: gradAttr[2],
+ y2: gradAttr[3],
gradientUnits: 'userSpaceOnUse'
- }
- );
- }
-
- // Build the unique key to detect whether we need to create a new
- // element (#1282)
- objectEach(gradAttr, function(val, n) {
- if (n !== 'id') {
- key.push(n, val);
+ };
}
- });
- objectEach(stops, function(val) {
- key.push(val);
- });
- key = key.join(',');
-
- // Check if a gradient object with the same config object is created
- // within this renderer
- if (gradients[key]) {
- id = gradients[key].attr('id');
- } else {
+ // Correct the radial gradient for the radial reference system
+ if (
+ gradName === 'radialGradient' &&
+ radialReference &&
+ !defined(gradAttr.gradientUnits)
+ ) {
+ // Save the radial attributes for updating
+ radAttr = gradAttr;
+ gradAttr = merge(
+ gradAttr,
+ renderer.getRadialAttr(radialReference, radAttr),
+ { gradientUnits: 'userSpaceOnUse' }
+ );
+ }
- // Set the id and create the element
- gradAttr.id = id = H.uniqueKey();
- gradients[key] = gradientObject =
- renderer.createElement(gradName)
- .attr(gradAttr)
- .add(renderer.defs);
-
- gradientObject.radAttr = radAttr;
-
- // The gradient needs to keep a list of stops to be able to
- // destroy them
- gradientObject.stops = [];
- each(stops, function(stop) {
- var stopObject;
- if (stop[1].indexOf('rgba') === 0) {
- colorObject = H.color(stop[1]);
- stopColor = colorObject.get('rgb');
- stopOpacity = colorObject.get('a');
- } else {
- stopColor = stop[1];
- stopOpacity = 1;
+ // Build the unique key to detect whether we need to create a
+ // new element (#1282)
+ objectEach(gradAttr, function (val, n) {
+ if (n !== 'id') {
+ key.push(n, val);
}
- stopObject = renderer.createElement('stop').attr({
- offset: stop[0],
- 'stop-color': stopColor,
- 'stop-opacity': stopOpacity
- }).add(gradientObject);
-
- // Add the stop element to the gradient
- gradientObject.stops.push(stopObject);
});
- }
+ objectEach(stops, function (val) {
+ key.push(val);
+ });
+ key = key.join(',');
- // Set the reference to the gradient object
- value = 'url(' + renderer.url + '#' + id + ')';
- elem.setAttribute(prop, value);
- elem.gradient = key;
+ // Check if a gradient object with the same config object is
+ // created within this renderer
+ if (gradients[key]) {
+ id = gradients[key].attr('id');
- // Allow the color to be concatenated into tooltips formatters etc.
- // (#2995)
- color.toString = function() {
- return value;
- };
- }
- },
+ } else {
- /**
- * Apply a text outline through a custom CSS property, by copying the text
- * element and apply stroke to the copy. Used internally. Contrast checks
- * at http://jsfiddle.net/highcharts/43soe9m1/2/ .
- *
- * @private
- * @param {String} textOutline A custom CSS `text-outline` setting, defined
- * by `width color`.
- * @example
- * // Specific color
- * text.css({
+ // Set the id and create the element
+ gradAttr.id = id = H.uniqueKey();
+ gradients[key] = gradientObject =
+ renderer.createElement(gradName)
+ .attr(gradAttr)
+ .add(renderer.defs);
+
+ gradientObject.radAttr = radAttr;
+
+ // The gradient needs to keep a list of stops to be able to
+ // destroy them
+ gradientObject.stops = [];
+ stops.forEach(function (stop) {
+ var stopObject;
+
+ if (stop[1].indexOf('rgba') === 0) {
+ colorObject = H.color(stop[1]);
+ stopColor = colorObject.get('rgb');
+ stopOpacity = colorObject.get('a');
+ } else {
+ stopColor = stop[1];
+ stopOpacity = 1;
+ }
+ stopObject = renderer.createElement('stop').attr({
+ offset: stop[0],
+ 'stop-color': stopColor,
+ 'stop-opacity': stopOpacity
+ }).add(gradientObject);
+
+ // Add the stop element to the gradient
+ gradientObject.stops.push(stopObject);
+ });
+ }
+
+ // Set the reference to the gradient object
+ value = 'url(' + renderer.url + '#' + id + ')';
+ elem.setAttribute(prop, value);
+ elem.gradient = key;
+
+ // Allow the color to be concatenated into tooltips formatters
+ // etc. (#2995)
+ color.toString = function () {
+ return value;
+ };
+ }
+ });
+ },
+
+ /**
+ * Apply a text outline through a custom CSS property, by copying the text
+ * element and apply stroke to the copy. Used internally. Contrast checks at
+ * [example](https://jsfiddle.net/highcharts/43soe9m1/2/).
+ *
+ * @example
+ * // Specific color
+ * text.css({
* textOutline: '1px black'
* });
* // Automatic contrast
@@ -2741,8 +3817,14 @@
* color: '#000000', // black text
* textOutline: '1px contrast' // => white outline
* });
+ *
+ * @private
+ * @function Highcharts.SVGElement#applyTextOutline
+ *
+ * @param {string} textOutline
+ * A custom CSS `text-outline` setting, defined by `width color`.
*/
- applyTextOutline: function(textOutline) {
+ applyTextOutline: function (textOutline) {
var elem = this.element,
tspans,
tspan,
@@ -2778,11 +3860,11 @@
this.ySetter = this.xSetter;
// Since the stroke is applied on center of the actual outline, we
- // need to double it to get the correct stroke-width outside the
+ // need to double it to get the correct stroke-width outside the
// glyphs.
strokeWidth = strokeWidth.replace(
/(^[\d\.]+)(.*?)$/g,
- function(match, digit, unit) {
+ function (match, digit, unit) {
return (2 * digit) + unit;
}
);
@@ -2800,7 +3882,7 @@
// For each of the tspans, create a stroked copy behind it.
firstRealChild = elem.firstChild;
- each(tspans, function(tspan, y) {
+ tspans.forEach(function (tspan, y) {
var clone;
// Let the first line start at the correct X position
@@ -2827,26 +3909,25 @@
}
},
- /**
- *
- * @typedef {Object} SVGAttributes An object of key-value pairs for SVG
- * attributes. Attributes in Highcharts elements for the most parts
- * correspond to SVG, but some are specific to Highcharts, like `zIndex`,
- * `rotation`, `rotationOriginX`, `rotationOriginY`, `translateX`,
- * `translateY`, `scaleX` and `scaleY`. SVG attributes containing a hyphen
- * are _not_ camel-cased, they should be quoted to preserve the hyphen.
- *
- * @example
- * {
- * 'stroke': '#ff0000', // basic
- * 'stroke-width': 2, // hyphenated
- * 'rotation': 45 // custom
- * 'd': ['M', 10, 10, 'L', 30, 30, 'z'] // path definition, note format
- * }
- */
+ // Custom attributes used for symbols, these should be filtered out when
+ // setting SVGElement attributes (#9375).
+ symbolCustomAttribs: [
+ 'x',
+ 'y',
+ 'width',
+ 'height',
+ 'r',
+ 'start',
+ 'end',
+ 'innerR',
+ 'anchorX',
+ 'anchorY',
+ 'rounded'
+ ],
+
/**
* Apply native and custom attributes to the SVG elements.
- *
+ *
* In order to set the rotation center for rotation, set x and y to 0 and
* use `translateX` and `translateY` attributes to position the element
* instead.
@@ -2854,27 +3935,9 @@
* Attributes frequently used in Highcharts are `fill`, `stroke`,
* `stroke-width`.
*
- * @param {SVGAttributes|String} hash - The native and custom SVG
- * attributes.
- * @param {string} [val] - If the type of the first argument is `string`,
- * the second can be a value, which will serve as a single attribute
- * setter. If the first argument is a string and the second is undefined,
- * the function serves as a getter and the current value of the property
- * is returned.
- * @param {Function} [complete] - A callback function to execute after
- * setting the attributes. This makes the function compliant and
- * interchangeable with the {@link SVGElement#animate} function.
- * @param {boolean} [continueAnimation=true] Used internally when `.attr` is
- * called as part of an animation step. Otherwise, calling `.attr` for an
- * attribute will stop animation for that attribute.
- *
- * @returns {SVGElement|string|number} If used as a setter, it returns the
- * current {@link SVGElement} so the calls can be chained. If used as a
- * getter, the current value of the attribute is returned.
- *
* @sample highcharts/members/renderer-rect/
* Setting some attributes
- *
+ *
* @example
* // Set multiple attributes
* element.attr({
@@ -2889,15 +3952,42 @@
*
* // Get an attribute
* element.attr('stroke'); // => 'red'
- *
+ *
+ * @function Highcharts.SVGElement#attr
+ *
+ * @param {string|Highcharts.SVGAttributes} [hash]
+ * The native and custom SVG attributes.
+ *
+ * @param {string} [val]
+ * If the type of the first argument is `string`, the second can be a
+ * value, which will serve as a single attribute setter. If the first
+ * argument is a string and the second is undefined, the function
+ * serves as a getter and the current value of the property is
+ * returned.
+ *
+ * @param {Function} [complete]
+ * A callback function to execute after setting the attributes. This
+ * makes the function compliant and interchangeable with the
+ * {@link SVGElement#animate} function.
+ *
+ * @param {boolean} [continueAnimation=true]
+ * Used internally when `.attr` is called as part of an animation
+ * step. Otherwise, calling `.attr` for an attribute will stop
+ * animation for that attribute.
+ *
+ * @return {number|string|Highcharts.SVGElement}
+ * If used as a setter, it returns the current
+ * {@link Highcharts.SVGElement} so the calls can be chained. If
+ * used as a getter, the current value of the attribute is returned.
*/
- attr: function(hash, val, complete, continueAnimation) {
+ attr: function (hash, val, complete, continueAnimation) {
var key,
element = this.element,
hasSetSymbolSize,
ret = this,
skipAttr,
- setter;
+ setter,
+ symbolCustomAttribs = this.symbolCustomAttribs;
// single key-value pair
if (typeof hash === 'string' && val !== undefined) {
@@ -2914,7 +4004,7 @@
element
);
- // setter
+ // setter
} else {
objectEach(hash, function eachAttribute(val, key) {
@@ -2929,8 +4019,7 @@
// Special handling of symbol attributes
if (
this.symbolName &&
- /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)$/
- .test(key)
+ H.inArray(key, symbolCustomAttribs) !== -1
) {
if (!hasSetSymbolSize) {
this.symbolAttr(hash);
@@ -2947,16 +4036,15 @@
setter = this[key + 'Setter'] || this._defaultSetter;
setter.call(this, val, key, element);
-
// Let the shadow follow the main element
if (
+ !this.styledMode &&
this.shadows &&
/^(width|height|visibility|x|y|d|transform|cx|cy|r)$/
- .test(key)
+ .test(key)
) {
this.updateShadows(key, val, setter);
}
-
}
}, this);
@@ -2965,7 +4053,7 @@
// In accordance with animate, run a complete callback
if (complete) {
- complete();
+ complete.call(this);
}
return ret;
@@ -2978,8 +4066,9 @@
* scale are merged in one "transform" attribute in the SVG node.
*
* @private
+ * @function Highcharts.SVGElement#afterSetters
*/
- afterSetters: function() {
+ afterSetters: function () {
// Update transform. Do this outside the loop to prevent redundant
// updating for batch setting of attributes.
if (this.doTransform) {
@@ -2988,18 +4077,22 @@
}
},
-
/**
* Update the shadow elements with new attributes.
*
* @private
- * @param {String} key - The attribute name.
- * @param {String|Number} value - The value of the attribute.
- * @param {Function} setter - The setter function, inherited from the
- * parent wrapper
- *
+ * @function Highcharts.SVGElement#updateShadows
+ *
+ * @param {string} key
+ * The attribute name.
+ *
+ * @param {string|number} value
+ * The value of the attribute.
+ *
+ * @param {Function} setter
+ * The setter function, inherited from the parent wrapper.
*/
- updateShadows: function(key, value, setter) {
+ updateShadows: function (key, value, setter) {
var shadows = this.shadows,
i = shadows.length;
@@ -3007,31 +4100,37 @@
setter.call(
shadows[i],
key === 'height' ?
- Math.max(value - (shadows[i].cutHeight || 0), 0) :
- key === 'd' ? this.d : value,
+ Math.max(value - (shadows[i].cutHeight || 0), 0) :
+ key === 'd' ? this.d : value,
key,
shadows[i]
);
}
},
-
/**
* Add a class name to an element.
*
- * @param {string} className - The new class name to add.
- * @param {boolean} [replace=false] - When true, the existing class name(s)
- * will be overwritten with the new one. When false, the new one is
- * added.
- * @returns {SVGElement} Return the SVG element for chainability.
+ * @function Highcharts.SVGElement#addClass
+ *
+ * @param {string} className
+ * The new class name to add.
+ *
+ * @param {boolean} [replace=false]
+ * When true, the existing class name(s) will be overwritten with
+ * the new one. When false, the new one is added.
+ *
+ * @return {Highcharts.SVGElement}
+ * Return the SVG element for chainability.
*/
- addClass: function(className, replace) {
+ addClass: function (className, replace) {
var currentClassName = this.attr('class') || '';
+
if (currentClassName.indexOf(className) === -1) {
if (!replace) {
className =
(currentClassName + (currentClassName ? ' ' : '') +
- className).replace(' ', ' ');
+ className).replace(' ', ' ');
}
this.attr('class', className);
}
@@ -3041,24 +4140,30 @@
/**
* Check if an element has the given class name.
- * @param {string} className
- * The class name to check for.
- * @return {Boolean}
+ *
+ * @function Highcharts.SVGElement#hasClass
+ *
+ * @param {string} className
+ * The class name to check for.
+ *
+ * @return {boolean}
* Whether the class name is found.
*/
- hasClass: function(className) {
- return inArray(
- className,
- (this.attr('class') || '').split(' ')
- ) !== -1;
+ hasClass: function (className) {
+ return (this.attr('class') || '').split(' ').indexOf(className) !== -1;
},
/**
* Remove a class name from the element.
- * @param {String|RegExp} className The class name to remove.
- * @return {SVGElement} Returns the SVG element for chainability.
+ *
+ * @function Highcharts.SVGElement#removeClass
+ *
+ * @param {string|RegExp} className
+ * The class name to remove.
+ *
+ * @return {Highcharts.SVGElement} Returns the SVG element for chainability.
*/
- removeClass: function(className) {
+ removeClass: function (className) {
return this.attr(
'class',
(this.attr('class') || '').replace(className, '')
@@ -3069,13 +4174,17 @@
* If one of the symbol size affecting parameters are changed,
* check all the others only once for each call to an element's
* .attr() method
- * @param {Object} hash - The attributes to set.
+ *
* @private
+ * @function Highcharts.SVGElement#symbolAttr
+ *
+ * @param {Highcharts.Dictionary} hash
+ * The attributes to set.
*/
- symbolAttr: function(hash) {
+ symbolAttr: function (hash) {
var wrapper = this;
- each([
+ [
'x',
'y',
'r',
@@ -3086,7 +4195,7 @@
'innerR',
'anchorX',
'anchorY'
- ], function(key) {
+ ].forEach(function (key) {
wrapper[key] = pick(hash[key], wrapper[key]);
});
@@ -3103,40 +4212,43 @@
/**
* Apply a clipping rectangle to this element.
- *
- * @param {ClipRect} [clipRect] - The clipping rectangle. If skipped, the
- * current clip is removed.
- * @returns {SVGElement} Returns the SVG element to allow chaining.
+ *
+ * @function Highcharts.SVGElement#clip
+ *
+ * @param {Highcharts.ClipRectElement} [clipRect]
+ * The clipping rectangle. If skipped, the current clip is removed.
+ *
+ * @return {Highcharts.SVGElement}
+ * Returns the SVG element to allow chaining.
*/
- clip: function(clipRect) {
+ clip: function (clipRect) {
return this.attr(
'clip-path',
clipRect ?
- 'url(' + this.renderer.url + '#' + clipRect.id + ')' :
- 'none'
+ 'url(' + this.renderer.url + '#' + clipRect.id + ')' :
+ 'none'
);
},
/**
* Calculate the coordinates needed for drawing a rectangle crisply and
* return the calculated attributes.
- *
- * @param {Object} rect - A rectangle.
- * @param {number} rect.x - The x position.
- * @param {number} rect.y - The y position.
- * @param {number} rect.width - The width.
- * @param {number} rect.height - The height.
- * @param {number} [strokeWidth] - The stroke width to consider when
- * computing crisp positioning. It can also be set directly on the rect
- * parameter.
*
- * @returns {{x: Number, y: Number, width: Number, height: Number}} The
- * modified rectangle arguments.
+ * @function Highcharts.SVGElement#crisp
+ *
+ * @param {Highcharts.RectangleObject} rect
+ * Rectangle to crisp.
+ *
+ * @param {number} [strokeWidth]
+ * The stroke width to consider when computing crisp positioning. It
+ * can also be set directly on the rect parameter.
+ *
+ * @return {Highcharts.RectangleObject}
+ * The modified rectangle arguments.
*/
- crisp: function(rect, strokeWidth) {
+ crisp: function (rect, strokeWidth) {
var wrapper = this,
- attribs = {},
normalizer;
strokeWidth = strokeWidth || rect.strokeWidth || 0;
@@ -3155,28 +4267,27 @@
if (defined(rect.strokeWidth)) {
rect.strokeWidth = strokeWidth;
}
-
- objectEach(rect, function(val, key) {
- if (wrapper[key] !== val) { // only set attribute if changed
- wrapper[key] = attribs[key] = val;
- }
- });
-
- return attribs;
+ return rect;
},
/**
- * Set styles for the element. In addition to CSS styles supported by
- * native SVG and HTML elements, there are also some custom made for
+ * Set styles for the element. In addition to CSS styles supported by
+ * native SVG and HTML elements, there are also some custom made for
* Highcharts, like `width`, `ellipsis` and `textOverflow` for SVG text
* elements.
- * @param {CSSObject} styles The new CSS styles.
- * @returns {SVGElement} Return the SVG element for chaining.
*
* @sample highcharts/members/renderer-text-on-chart/
* Styled text
+ *
+ * @function Highcharts.SVGElement#css
+ *
+ * @param {Highcharts.CSSObject} styles
+ * The new CSS styles.
+ *
+ * @return {Highcharts.SVGElement}
+ * Return the SVG element for chaining.
*/
- css: function(styles) {
+ css: function (styles) {
var oldStyles = this.styles,
newStyles = {},
elem = this.element,
@@ -3197,7 +4308,7 @@
// Filter out existing styles to increase performance (#2640)
if (oldStyles) {
- objectEach(styles, function(style, n) {
+ objectEach(styles, function (style, n) {
if (style !== oldStyles[n]) {
newStyles[n] = style;
hasNew = true;
@@ -3215,13 +4326,19 @@
}
// Get the text width from style
- textWidth = this.textWidth = (
- styles &&
- styles.width &&
- styles.width !== 'auto' &&
- elem.nodeName.toLowerCase() === 'text' &&
- pInt(styles.width)
- );
+ if (styles) {
+ // Previously set, unset it (#8234)
+ if (styles.width === null || styles.width === 'auto') {
+ delete this.textWidth;
+
+ // Apply new
+ } else if (
+ elem.nodeName.toLowerCase() === 'text' &&
+ styles.width
+ ) {
+ textWidth = this.textWidth = pInt(styles.width);
+ }
+ }
// store object
this.styles = styles;
@@ -3230,23 +4347,23 @@
delete styles.width;
}
- // serialize and set style attribute
- if (isMS && !svg) {
- css(this.element, styles);
- } else {
- hyphenate = function(a, b) {
+ // Serialize and set style attribute
+ if (elem.namespaceURI === this.SVG_NS) { // #7633
+ hyphenate = function (a, b) {
return '-' + b.toLowerCase();
};
- objectEach(styles, function(style, n) {
- if (inArray(n, svgPseudoProps) === -1) {
+ objectEach(styles, function (style, n) {
+ if (svgPseudoProps.indexOf(n) === -1) {
serializedCss +=
- n.replace(/([A-Z])/g, hyphenate) + ':' +
- style + ';';
+ n.replace(/([A-Z])/g, hyphenate) + ':' +
+ style + ';';
}
});
if (serializedCss) {
attr(elem, 'style', serializedCss); // #1881
}
+ } else {
+ css(elem, styles);
}
@@ -3268,46 +4385,112 @@
return this;
},
-
/**
- * Get the current stroke width. In classic mode, the setter registers it
- * directly on the element.
- * @returns {number} The stroke width in pixels.
- * @ignore
+ * Get the computed style. Only in styled mode.
+ *
+ * @example
+ * chart.series[0].points[0].graphic.getStyle('stroke-width'); // => '1px'
+ *
+ * @function Highcharts.SVGElement#getStyle
+ *
+ * @param {string} prop
+ * The property name to check for.
+ *
+ * @return {string}
+ * The current computed value.
*/
- strokeWidth: function() {
- return this['stroke-width'] || 0;
+ getStyle: function (prop) {
+ return win.getComputedStyle(this.element || this, '')
+ .getPropertyValue(prop);
},
+ /**
+ * Get the computed stroke width in pixel values. This is used extensively
+ * when drawing shapes to ensure the shapes are rendered crisp and
+ * positioned correctly relative to each other. Using
+ * `shape-rendering: crispEdges` leaves us less control over positioning,
+ * for example when we want to stack columns next to each other, or position
+ * things pixel-perfectly within the plot box.
+ *
+ * The common pattern when placing a shape is:
+ * - Create the SVGElement and add it to the DOM. In styled mode, it will
+ * now receive a stroke width from the style sheet. In classic mode we
+ * will add the `stroke-width` attribute.
+ * - Read the computed `elem.strokeWidth()`.
+ * - Place it based on the stroke width.
+ *
+ * @function Highcharts.SVGElement#strokeWidth
+ *
+ * @return {number}
+ * The stroke width in pixels. Even if the given stroke widtch (in
+ * CSS or by attributes) is based on `em` or other units, the pixel
+ * size is returned.
+ */
+ strokeWidth: function () {
+
+ // In non-styled mode, read the stroke width as set by .attr
+ if (!this.renderer.styledMode) {
+ return this['stroke-width'] || 0;
+ }
+
+ // In styled mode, read computed stroke width
+ var val = this.getStyle('stroke-width'),
+ ret,
+ dummy;
+
+ // Read pixel values directly
+ if (val.indexOf('px') === val.length - 2) {
+ ret = pInt(val);
+
+ // Other values like em, pt etc need to be measured
+ } else {
+ dummy = doc.createElementNS(SVG_NS, 'rect');
+ attr(dummy, {
+ 'width': val,
+ 'stroke-width': 0
+ });
+ this.element.parentNode.appendChild(dummy);
+ ret = dummy.getBBox().width;
+ dummy.parentNode.removeChild(dummy);
+ }
+ return ret;
+ },
/**
* Add an event listener. This is a simple setter that replaces all other
* events of the same type, opposed to the {@link Highcharts#addEvent}
* function.
- * @param {string} eventType - The event type. If the type is `click`,
- * Highcharts will internally translate it to a `touchstart` event on
- * touch devices, to prevent the browser from waiting for a click event
- * from firing.
- * @param {Function} handler - The handler callback.
- * @returns {SVGElement} The SVGElement for chaining.
*
* @sample highcharts/members/element-on/
* A clickable rectangle
+ *
+ * @function Highcharts.SVGElement#on
+ *
+ * @param {string} eventType
+ * The event type. If the type is `click`, Highcharts will internally
+ * translate it to a `touchstart` event on touch devices, to prevent
+ * the browser from waiting for a click event from firing.
+ *
+ * @param {Function} handler
+ * The handler callback.
+ *
+ * @return {Highcharts.SVGElement}
+ * The SVGElement for chaining.
*/
- on: function(eventType, handler) {
+ on: function (eventType, handler) {
var svgElement = this,
element = svgElement.element;
// touch
if (hasTouch && eventType === 'click') {
- element.ontouchstart = function(e) {
+ element.ontouchstart = function (e) {
svgElement.touchEventFired = Date.now(); // #2269
e.preventDefault();
handler.call(element, e);
};
- element.onclick = function(e) {
+ element.onclick = function (e) {
if (win.navigator.userAgent.indexOf('Android') === -1 ||
- Date.now() - (svgElement.touchEventFired || 0) > 1100) {
+ Date.now() - (svgElement.touchEventFired || 0) > 1100) {
handler.call(element, e);
}
};
@@ -3323,11 +4506,16 @@
* a shape regardless of positioning inside the chart. Used on pie slices
* to make all the slices have the same radial reference point.
*
- * @param {Array} coordinates The center reference. The format is
- * `[centerX, centerY, diameter]` in pixels.
- * @returns {SVGElement} Returns the SVGElement for chaining.
+ * @function Highcharts.SVGElement#setRadialReference
+ *
+ * @param {Array} coordinates
+ * The center reference. The format is `[centerX, centerY, diameter]`
+ * in pixels.
+ *
+ * @return {Highcharts.SVGElement}
+ * Returns the SVGElement for chaining.
*/
- setRadialReference: function(coordinates) {
+ setRadialReference: function (coordinates) {
var existingGradient = this.renderer.gradients[this.element.gradient];
this.element.radialReference = coordinates;
@@ -3348,11 +4536,16 @@
/**
* Move an object and its children by x and y values.
- *
- * @param {number} x - The x value.
- * @param {number} y - The y value.
+ *
+ * @function Highcharts.SVGElement#translate
+ *
+ * @param {number} x
+ * The x value.
+ *
+ * @param {number} y
+ * The y value.
*/
- translate: function(x, y) {
+ translate: function (x, y) {
return this.attr({
translateX: x,
translateY: y
@@ -3360,18 +4553,22 @@
},
/**
- * Invert a group, rotate and flip. This is used internally on inverted
+ * Invert a group, rotate and flip. This is used internally on inverted
* charts, where the points and graphs are drawn as if not inverted, then
* the series group elements are inverted.
*
- * @param {boolean} inverted
- * Whether to invert or not. An inverted shape can be un-inverted by
- * setting it to false.
- * @return {SVGElement}
+ * @function Highcharts.SVGElement#invert
+ *
+ * @param {boolean} inverted
+ * Whether to invert or not. An inverted shape can be un-inverted by
+ * setting it to false.
+ *
+ * @return {Highcharts.SVGElement}
* Return the SVGElement for chaining.
*/
- invert: function(inverted) {
+ invert: function (inverted) {
var wrapper = this;
+
wrapper.inverted = inverted;
wrapper.updateTransform();
return wrapper;
@@ -3381,10 +4578,11 @@
* Update the transform attribute based on internal properties. Deals with
* the custom `translateX`, `translateY`, `rotation`, `scaleX` and `scaleY`
* attributes and updates the SVG `transform` attribute.
+ *
* @private
- *
+ * @function Highcharts.SVGElement#updateTransform
*/
- updateTransform: function() {
+ updateTransform: function () {
var wrapper = this,
translateX = wrapper.translateX || 0,
translateY = wrapper.translateY || 0,
@@ -3442,13 +4640,17 @@
/**
* Bring the element to the front. Alternatively, a new zIndex can be set.
*
- * @returns {SVGElement} Returns the SVGElement for chaining.
- *
* @sample highcharts/members/element-tofront/
* Click an element to bring it to front
+ *
+ * @function Highcharts.SVGElement#toFront
+ *
+ * @return {Highcharts.SVGElement}
+ * Returns the SVGElement for chaining.
*/
- toFront: function() {
+ toFront: function () {
var element = this.element;
+
element.parentNode.appendChild(element);
return this;
},
@@ -3456,27 +4658,26 @@
/**
* Align the element relative to the chart or another box.
- *
- * @param {Object} [alignOptions] The alignment options. The function can be
- * called without this parameter in order to re-align an element after the
- * box has been updated.
- * @param {string} [alignOptions.align=left] Horizontal alignment. Can be
- * one of `left`, `center` and `right`.
- * @param {string} [alignOptions.verticalAlign=top] Vertical alignment. Can
- * be one of `top`, `middle` and `bottom`.
- * @param {number} [alignOptions.x=0] Horizontal pixel offset from
- * alignment.
- * @param {number} [alignOptions.y=0] Vertical pixel offset from alignment.
- * @param {Boolean} [alignByTranslate=false] Use the `transform` attribute
- * with translateX and translateY custom attributes to align this elements
- * rather than `x` and `y` attributes.
- * @param {String|Object} box The box to align to, needs a width and height.
- * When the box is a string, it refers to an object in the Renderer. For
- * example, when box is `spacingBox`, it refers to `Renderer.spacingBox`
- * which holds `width`, `height`, `x` and `y` properties.
- * @returns {SVGElement} Returns the SVGElement for chaining.
- */
- align: function(alignOptions, alignByTranslate, box) {
+ *
+ * @function Highcharts.SVGElement#align
+ *
+ * @param {Highcharts.AlignObject} [alignOptions]
+ * The alignment options. The function can be called without this
+ * parameter in order to re-align an element after the box has been
+ * updated.
+ *
+ * @param {boolean} [alignByTranslate]
+ * Align element by translation.
+ *
+ * @param {string|Highcharts.BBoxObject} [box]
+ * The box to align to, needs a width and height. When the box is a
+ * string, it refers to an object in the Renderer. For example, when
+ * box is `spacingBox`, it refers to `Renderer.spacingBox` which
+ * holds `width`, `height`, `x` and `y` properties.
+ *
+ * @return {Highcharts.SVGElement} Returns the SVGElement for chaining.
+ */
+ align: function (alignOptions, alignByTranslate, box) {
var align,
vAlign,
x,
@@ -3500,7 +4701,7 @@
box = null; // reassign it below
}
- // When called on resize, no arguments are supplied
+ // When called on resize, no arguments are supplied
} else {
alignOptions = this.alignOptions;
alignByTranslate = this.alignByTranslate;
@@ -3554,18 +4755,23 @@
* a single text line of rotation 90 will report a greater height, and a
* width corresponding to the line-height.
*
- * @param {boolean} [reload] Skip the cache and get the updated DOM bouding
- * box.
- * @param {number} [rot] Override the element's rotation. This is internally
- * used on axis labels with a value of 0 to find out what the bounding box
- * would be have been if it were not rotated.
- * @returns {Object} The bounding box with `x`, `y`, `width` and `height`
- * properties.
- *
* @sample highcharts/members/renderer-on-chart/
* Draw a rectangle based on a text's bounding box
+ *
+ * @function Highcharts.SVGElement#getBBox
+ *
+ * @param {boolean} [reload]
+ * Skip the cache and get the updated DOM bouding box.
+ *
+ * @param {number} [rot]
+ * Override the element's rotation. This is internally used on axis
+ * labels with a value of 0 to find out what the bounding box would
+ * be have been if it were not rotated.
+ *
+ * @return {Highcharts.BBoxObject}
+ * The bounding box with `x`, `y`, `width` and `height` properties.
*/
- getBBox: function(reload, rot) {
+ getBBox: function (reload, rot) {
var wrapper = this,
bBox, // = wrapper.bBox,
renderer = wrapper.renderer,
@@ -3580,14 +4786,18 @@
toggleTextShadowShim,
cache = renderer.cache,
cacheKeys = renderer.cacheKeys,
+ isSVG = element.namespaceURI === wrapper.SVG_NS,
cacheKey;
rotation = pick(rot, wrapper.rotation);
rad = rotation * deg2rad;
-
- fontSize = styles && styles.fontSize;
-
+ fontSize = renderer.styledMode ? (
+ element &&
+ SVGElement.prototype.getStyle.call(element, 'font-size')
+ ) : (
+ styles && styles.fontSize
+ );
// Avoid undefined and null (#7316)
if (defined(textStr)) {
@@ -3604,13 +4814,12 @@
// Properties that affect bounding box
cacheKey += [
- '',
- rotation || 0,
- fontSize,
- styles && styles.width,
- styles && styles.textOverflow // #5968
- ]
- .join(',');
+ '',
+ rotation || 0,
+ fontSize,
+ wrapper.textWidth, // #7874, also useHTML
+ styles && styles.textOverflow // #5968
+ ].join(',');
}
@@ -3622,17 +4831,17 @@
if (!bBox) {
// SVG elements
- if (element.namespaceURI === wrapper.SVG_NS || renderer.forExport) {
+ if (isSVG || renderer.forExport) {
try { // Fails in Firefox if the container has display: none.
// When the text shadow shim is used, we need to hide the
// fake shadows to get the correct bounding box (#3872)
- toggleTextShadowShim = this.fakeTS && function(display) {
- each(
+ toggleTextShadowShim = this.fakeTS && function (display) {
+ [].forEach.call(
element.querySelectorAll(
'.highcharts-text-outline'
),
- function(tspan) {
+ function (tspan) {
tspan.style.display = display;
}
);
@@ -3664,14 +4873,11 @@
// other condition is for Opera that returns a width of
// -Infinity on hidden elements.
if (!bBox || bBox.width < 0) {
- bBox = {
- width: 0,
- height: 0
- };
+ bBox = { width: 0, height: 0 };
}
- // VML Renderer or useHTML within SVG
+ // VML Renderer or useHTML within SVG
} else {
bBox = wrapper.htmlGetBBox();
@@ -3686,18 +4892,22 @@
// Workaround for wrong bounding box in IE, Edge and Chrome on
// Windows. With Highcharts' default font, IE and Edge report
- // a box height of 16.899 and Chrome rounds it to 17. If this
+ // a box height of 16.899 and Chrome rounds it to 17. If this
// stands uncorrected, it results in more padding added below
// the text than above when adding a label border or background.
// Also vertical positioning is affected.
- // http://jsfiddle.net/highcharts/em37nvuj/
+ // https://jsfiddle.net/highcharts/em37nvuj/
// (#1101, #1505, #1669, #2568, #6213).
- if (
- styles &&
- styles.fontSize === '11px' &&
- Math.round(height) === 17
- ) {
- bBox.height = height = 14;
+ if (isSVG) {
+ bBox.height = height = (
+ {
+ '11px,17': 14,
+ '13px,20': 16
+ }[
+ styles && styles.fontSize + ',' + Math.round(height)
+ ] ||
+ height
+ );
}
// Adjust for rotated text
@@ -3728,65 +4938,74 @@
},
/**
- * Show the element after it has been hidden.
+ * Show the element after it has been hidden.
*
- * @param {boolean} [inherit=false] Set the visibility attribute to
- * `inherit` rather than `visible`. The difference is that an element with
- * `visibility="visible"` will be visible even if the parent is hidden.
+ * @function Highcharts.SVGElement#show
*
- * @returns {SVGElement} Returns the SVGElement for chaining.
+ * @param {boolean} [inherit=false]
+ * Set the visibility attribute to `inherit` rather than `visible`.
+ * The difference is that an element with `visibility="visible"`
+ * will be visible even if the parent is hidden.
+ *
+ * @return {Highcharts.SVGElement}
+ * Returns the SVGElement for chaining.
*/
- show: function(inherit) {
- return this.attr({
- visibility: inherit ? 'inherit' : 'visible'
- });
+ show: function (inherit) {
+ return this.attr({ visibility: inherit ? 'inherit' : 'visible' });
},
/**
* Hide the element, equivalent to setting the `visibility` attribute to
* `hidden`.
*
- * @returns {SVGElement} Returns the SVGElement for chaining.
+ * @function Highcharts.SVGElement#hide
+ *
+ * @return {Highcharts.SVGElement}
+ * Returns the SVGElement for chaining.
*/
- hide: function() {
- return this.attr({
- visibility: 'hidden'
- });
+ hide: function () {
+ return this.attr({ visibility: 'hidden' });
},
/**
* Fade out an element by animating its opacity down to 0, and hide it on
* complete. Used internally for the tooltip.
- *
- * @param {number} [duration=150] The fade duration in milliseconds.
+ *
+ * @function Highcharts.SVGElement#fadeOut
+ *
+ * @param {number} [duration=150]
+ * The fade duration in milliseconds.
*/
- fadeOut: function(duration) {
+ fadeOut: function (duration) {
var elemWrapper = this;
+
elemWrapper.animate({
opacity: 0
}, {
duration: duration || 150,
- complete: function() {
+ complete: function () {
// #3088, assuming we're only using this for tooltips
- elemWrapper.attr({
- y: -9999
- });
+ elemWrapper.attr({ y: -9999 });
}
});
},
/**
* Add the element to the DOM. All elements must be added this way.
- *
- * @param {SVGElement|SVGDOMElement} [parent] The parent item to add it to.
- * If undefined, the element is added to the {@link
- * Highcharts.SVGRenderer.box}.
*
- * @returns {SVGElement} Returns the SVGElement for chaining.
+ * @sample highcharts/members/renderer-g
+ * Elements added to a group
+ *
+ * @function Highcharts.SVGElement#add
+ *
+ * @param {Highcharts.SVGElement|Highcharts.SVGDOMElement} [parent]
+ * The parent item to add it to. If undefined, the element is added
+ * to the {@link Highcharts.SVGRenderer.box}.
*
- * @sample highcharts/members/renderer-g - Elements added to a group
+ * @return {Highcharts.SVGElement}
+ * Returns the SVGElement for chaining.
*/
- add: function(parent) {
+ add: function (parent) {
var renderer = this.renderer,
element = this.element,
@@ -3830,10 +5049,14 @@
* Removes an element from the DOM.
*
* @private
- * @param {SVGDOMElement|HTMLDOMElement} element The DOM node to remove.
+ * @function Highcharts.SVGElement#safeRemoveChild
+ *
+ * @param {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement} element
+ * The DOM node to remove.
*/
- safeRemoveChild: function(element) {
+ safeRemoveChild: function (element) {
var parentNode = element.parentNode;
+
if (parentNode) {
parentNode.removeChild(element);
}
@@ -3843,45 +5066,48 @@
* Destroy the element and element wrapper and clear up the DOM and event
* hooks.
*
- *
+ * @function Highcharts.SVGElement#destroy
*/
- destroy: function() {
+ destroy: function () {
var wrapper = this,
element = wrapper.element || {},
+ renderer = wrapper.renderer,
parentToClean =
- wrapper.renderer.isSVG &&
- element.nodeName === 'SPAN' &&
- wrapper.parentGroup,
+ renderer.isSVG &&
+ element.nodeName === 'SPAN' &&
+ wrapper.parentGroup,
grandParent,
ownerSVGElement = element.ownerSVGElement,
- i;
+ i,
+ clipPath = wrapper.clipPath;
// remove events
element.onclick = element.onmouseout = element.onmouseover =
element.onmousemove = element.point = null;
stop(wrapper); // stop running animations
- if (wrapper.clipPath && ownerSVGElement) {
+ if (clipPath && ownerSVGElement) {
// Look for existing references to this clipPath and remove them
// before destroying the element (#6196).
- each(
- // The upper case version is for Edge
+ // The upper case version is for Edge
+ [].forEach.call(
ownerSVGElement.querySelectorAll('[clip-path],[CLIP-PATH]'),
- function(el) {
+ function (el) {
+ var clipPathAttr = el.getAttribute('clip-path'),
+ clipPathId = clipPath.element.id;
+
// Include the closing paranthesis in the test to rule out
- // id's from 10 and above (#6550)
- if (el
- .getAttribute('clip-path')
- .match(RegExp(
- // Edge puts quotes inside the url, others not
- '[\("]#' + wrapper.clipPath.element.id + '[\)"]'
- ))
+ // id's from 10 and above (#6550). Edge puts quotes inside
+ // the url, others not.
+ if (
+ clipPathAttr.indexOf('(#' + clipPathId + ')') > -1 ||
+ clipPathAttr.indexOf('("#' + clipPathId + '")') > -1
) {
el.removeAttribute('clip-path');
}
}
);
- wrapper.clipPath = wrapper.clipPath.destroy();
+ wrapper.clipPath = clipPath.destroy();
}
// Destroy stops in case this is a gradient object
@@ -3895,9 +5121,9 @@
// remove element
wrapper.safeRemoveChild(element);
-
- wrapper.destroyShadows();
-
+ if (!renderer.styledMode) {
+ wrapper.destroyShadows();
+ }
// In case of useHTML, clean up empty containers emulating SVG groups
// (#1960, #2393, #2697).
@@ -3914,48 +5140,45 @@
// remove from alignObjects
if (wrapper.alignTo) {
- erase(wrapper.renderer.alignedObjects, wrapper);
+ erase(renderer.alignedObjects, wrapper);
}
- objectEach(wrapper, function(val, key) {
+ objectEach(wrapper, function (val, key) {
delete wrapper[key];
});
return null;
},
-
- /**
- * @typedef {Object} ShadowOptions
- * @property {string} [color=#000000] The shadow color.
- * @property {number} [offsetX=1] The horizontal offset from the element.
- * @property {number} [offsetY=1] The vertical offset from the element.
- * @property {number} [opacity=0.15] The shadow opacity.
- * @property {number} [width=3] The shadow width or distance from the
- * element.
- */
/**
* Add a shadow to the element. Must be called after the element is added to
* the DOM. In styled mode, this method is not used, instead use `defs` and
* filters.
- *
- * @param {boolean|ShadowOptions} shadowOptions The shadow options. If
- * `true`, the default options are applied. If `false`, the current
- * shadow will be removed.
- * @param {SVGElement} [group] The SVG group element where the shadows will
- * be applied. The default is to add it to the same parent as the current
- * element. Internally, this is ised for pie slices, where all the
- * shadows are added to an element behind all the slices.
- * @param {boolean} [cutOff] Used internally for column shadows.
- *
- * @returns {SVGElement} Returns the SVGElement for chaining.
*
* @example
* renderer.rect(10, 100, 100, 100)
* .attr({ fill: 'red' })
* .shadow(true);
+ *
+ * @function Highcharts.SVGElement#shadow
+ *
+ * @param {boolean|Highcharts.ShadowOptionsObject} shadowOptions
+ * The shadow options. If `true`, the default options are applied. If
+ * `false`, the current shadow will be removed.
+ *
+ * @param {Highcharts.SVGElement} [group]
+ * The SVG group element where the shadows will be applied. The
+ * default is to add it to the same parent as the current element.
+ * Internally, this is ised for pie slices, where all the shadows are
+ * added to an element behind all the slices.
+ *
+ * @param {boolean} [cutOff]
+ * Used internally for column shadows.
+ *
+ * @return {Highcharts.SVGElement}
+ * Returns the SVGElement for chaining.
*/
- shadow: function(shadowOptions, group, cutOff) {
+ shadow: function (shadowOptions, group, cutOff) {
var shadows = [],
i,
shadow,
@@ -3982,13 +5205,17 @@
shadow = element.cloneNode(0);
strokeWidth = (shadowWidth * 2) + 1 - (2 * i);
attr(shadow, {
- 'isShadow': 'true',
- 'stroke': shadowOptions.color || '#000000',
+ 'stroke':
+ shadowOptions.color || '#000000',
'stroke-opacity': shadowElementOpacity * i,
'stroke-width': strokeWidth,
'transform': 'translate' + transform,
'fill': 'none'
});
+ shadow.setAttribute(
+ 'class',
+ (shadow.getAttribute('class') || '') + ' highcharts-shadow'
+ );
if (cutOff) {
attr(
shadow,
@@ -4015,18 +5242,26 @@
/**
* Destroy shadows on the element.
+ *
* @private
+ * @function Highcharts.SVGElement#destroyShadows
*/
- destroyShadows: function() {
- each(this.shadows || [], function(shadow) {
+ destroyShadows: function () {
+ (this.shadows || []).forEach(function (shadow) {
this.safeRemoveChild(shadow);
}, this);
this.shadows = undefined;
},
-
-
- xGetter: function(key) {
+ /**
+ * @private
+ * @function Highcharts.SVGElement#xGetter
+ *
+ * @param {string} key
+ *
+ * @return {number|string|null}
+ */
+ xGetter: function (key) {
if (this.element.nodeName === 'circle') {
if (key === 'x') {
key = 'cx';
@@ -4038,15 +5273,22 @@
},
/**
- * Get the current value of an attribute or pseudo attribute, used mainly
- * for animation. Called internally from the {@link
- * Highcharts.SVGRenderer#attr}
- * function.
+ * Get the current value of an attribute or pseudo attribute,
+ * used mainly for animation. Called internally from
+ * the {@link Highcharts.SVGRenderer#attr} function.
*
* @private
+ * @function Highcharts.SVGElement#_defaultGetter
+ *
+ * @param {string} key
+ * Property key.
+ *
+ * @return {number|string|null}
+ * Property value.
*/
- _defaultGetter: function(key) {
+ _defaultGetter: function (key) {
var ret = pick(
+ this[key + 'Value'], // align getter
this[key],
this.element ? this.element.getAttribute(key) : null,
0
@@ -4058,8 +5300,17 @@
return ret;
},
-
- dSetter: function(value, key, element) {
+ /**
+ * @private
+ * @function Highcharts.SVGElement#dSettter
+ *
+ * @param {number|string|Highcharts.SVGPathArray} value
+ *
+ * @param {string} key
+ *
+ * @param {Highcharts.SVGDOMElement} element
+ */
+ dSetter: function (value, key, element) {
if (value && value.join) { // join path
value = value.join(' ');
}
@@ -4077,7 +5328,13 @@
},
- dashstyleSetter: function(value) {
+ /**
+ * @private
+ * @function Highcharts.SVGElement#dashstyleSetter
+ *
+ * @param {string} value
+ */
+ dashstyleSetter: function (value) {
var i,
strokeWidth = this['stroke-width'];
@@ -4109,20 +5366,42 @@
}
},
- alignSetter: function(value) {
- var convert = {
- left: 'start',
- center: 'middle',
- right: 'end'
- };
- this.element.setAttribute('text-anchor', convert[value]);
+ /**
+ * @private
+ * @function Highcharts.SVGElement#alignSetter
+ *
+ * @param {"start"|"middle"|"end"} value
+ */
+ alignSetter: function (value) {
+ var convert = { left: 'start', center: 'middle', right: 'end' };
+ if (convert[value]) {
+ this.alignValue = value;
+ this.element.setAttribute('text-anchor', convert[value]);
+ }
},
- opacitySetter: function(value, key, element) {
+ /**
+ * @private
+ * @function Highcharts.SVGElement#opacitySetter
+ *
+ * @param {string} value
+ *
+ * @param {string} key
+ *
+ * @param {Highcharts.SVGDOMElement} element
+ */
+ opacitySetter: function (value, key, element) {
this[key] = value;
element.setAttribute(key, value);
},
- titleSetter: function(value) {
+ /**
+ * @private
+ * @function Highcharts.SVGElement#titleSetter
+ *
+ * @param {string} value
+ */
+ titleSetter: function (value) {
var titleNode = this.element.getElementsByTagName('title')[0];
+
if (!titleNode) {
titleNode = doc.createElementNS(this.SVG_NS, 'title');
this.element.appendChild(titleNode);
@@ -4136,11 +5415,20 @@
titleNode.appendChild(
doc.createTextNode(
// #3276, #3895
- (String(pick(value), '')).replace(/<[^>]*>/g, '')
+ (String(pick(value), ''))
+ .replace(/<[^>]*>/g, '')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
)
);
},
- textSetter: function(value) {
+ /**
+ * @private
+ * @function Highcharts.SVGElement#textSetter
+ *
+ * @param {string} value
+ */
+ textSetter: function (value) {
if (value !== this.textStr) {
// Delete bBox memo when the text changes
delete this.bBox;
@@ -4151,14 +5439,34 @@
}
}
},
- fillSetter: function(value, key, element) {
+ /**
+ * @private
+ * @function Highcharts.SVGElement#fillSetter
+ *
+ * @param {Highcharts.Color|Highcharts.ColorString} value
+ *
+ * @param {string} key
+ *
+ * @param {Highcharts.SVGDOMElement} element
+ */
+ fillSetter: function (value, key, element) {
if (typeof value === 'string') {
element.setAttribute(key, value);
} else if (value) {
- this.colorGradient(value, key, element);
+ this.complexColor(value, key, element);
}
},
- visibilitySetter: function(value, key, element) {
+ /**
+ * @private
+ * @function Highcharts.SVGElement#visibilitySetter
+ *
+ * @param {string} value
+ *
+ * @param {string} key
+ *
+ * @param {Highcharts.SVGDOMElement} element
+ */
+ visibilitySetter: function (value, key, element) {
// IE9-11 doesn't handle visibilty:inherit well, so we remove the
// attribute instead (#2881, #3909)
if (value === 'inherit') {
@@ -4168,7 +5476,17 @@
}
this[key] = value;
},
- zIndexSetter: function(value, key) {
+ /**
+ * @private
+ * @function Highcharts.SVGElement#zIndexSetter
+ *
+ * @param {string} value
+ *
+ * @param {string} key
+ *
+ * @return {boolean}
+ */
+ zIndexSetter: function (value, key) {
var renderer = this.renderer,
parentGroup = this.parentGroup,
parentWrapper = parentGroup || renderer,
@@ -4185,15 +5503,18 @@
if (defined(value)) {
// So we can read it for other elements in the group
- element.zIndex = value;
+ element.setAttribute('data-z-index', value);
value = +value;
if (this[key] === value) { // Only update when needed (#3865)
run = false;
}
- this[key] = value;
+ } else if (defined(this[key])) {
+ element.removeAttribute('data-z-index');
}
+ this[key] = value;
+
// Insert according to this and other elements' zIndex. Before .add() is
// called, nothing is done. Then on add, or by later calls to
// zIndexSetter, the node is placed on the right place in the DOM.
@@ -4207,14 +5528,15 @@
childNodes = parentNode.childNodes;
for (i = childNodes.length - 1; i >= 0 && !inserted; i--) {
otherElement = childNodes[i];
- otherZIndex = otherElement.zIndex;
+ otherZIndex = otherElement.getAttribute('data-z-index');
undefinedOtherZIndex = !defined(otherZIndex);
if (otherElement !== element) {
if (
// Negative zIndex versus no zIndex:
- // On all levels except the highest. If the parent is ,
- // then we don't want to put items before or
+ // On all levels except the highest. If the parent is
+ // , then we don't want to put items before
+ // or
(value < 0 && undefinedOtherZIndex && !svgParent && !i)
) {
parentNode.insertBefore(element, childNodes[i]);
@@ -4222,8 +5544,12 @@
} else if (
// Insert after the first element with a lower zIndex
pInt(otherZIndex) <= value ||
- // If negative zIndex, add this before first undefined zIndex element
- (undefinedOtherZIndex && (!defined(value) || value >= 0))
+ // If negative zIndex, add this before first undefined
+ // zIndex element
+ (
+ undefinedOtherZIndex &&
+ (!defined(value) || value >= 0)
+ )
) {
parentNode.insertBefore(
element,
@@ -4244,98 +5570,163 @@
}
return inserted;
},
- _defaultSetter: function(value, key, element) {
+ /**
+ * @private
+ * @function Highcharts.SVGElement#_defaultSetter
+ *
+ * @param {string} value
+ *
+ * @param {string} key
+ *
+ * @param {Highcharts.SVGDOMElement} element
+ */
+ _defaultSetter: function (value, key, element) {
element.setAttribute(key, value);
}
});
// Some shared setters and getters
SVGElement.prototype.yGetter =
- SVGElement.prototype.xGetter;
+ SVGElement.prototype.xGetter;
SVGElement.prototype.translateXSetter =
- SVGElement.prototype.translateYSetter =
- SVGElement.prototype.rotationSetter =
- SVGElement.prototype.verticalAlignSetter =
- SVGElement.prototype.rotationOriginXSetter =
- SVGElement.prototype.rotationOriginYSetter =
- SVGElement.prototype.scaleXSetter =
- SVGElement.prototype.scaleYSetter =
- SVGElement.prototype.matrixSetter = function(value, key) {
- this[key] = value;
- this.doTransform = true;
- };
-
+ SVGElement.prototype.translateYSetter =
+ SVGElement.prototype.rotationSetter =
+ SVGElement.prototype.verticalAlignSetter =
+ SVGElement.prototype.rotationOriginXSetter =
+ SVGElement.prototype.rotationOriginYSetter =
+ SVGElement.prototype.scaleXSetter =
+ SVGElement.prototype.scaleYSetter =
+ SVGElement.prototype.matrixSetter = function (value, key) {
+ this[key] = value;
+ this.doTransform = true;
+ };
// 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']) {
- // Use prototype as instance may be overridden
- SVGElement.prototype.fillSetter.call(
- this,
- this.stroke,
- 'stroke',
- element
- );
-
- element.setAttribute('stroke-width', this['stroke-width']);
- this.hasStroke = true;
- } else if (key === 'stroke-width' && value === 0 && this.hasStroke) {
- element.removeAttribute('stroke');
- this.hasStroke = false;
- }
- };
-
-
/**
- * Allows direct access to the Highcharts rendering layer in order to draw
- * primitive shapes like circles, rectangles, paths or text directly on a chart,
- * or independent from any chart. The SVGRenderer represents a wrapper object
+ * @private
+ * @function Highcharts.SVGElement#strokeSetter
+ *
+ * @param {number|string} value
+ *
+ * @param {string} key
+ *
+ * @param {Highcharts.SVGDOMElement} element
+ */
+ 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']) {
+ // Use prototype as instance may be overridden
+ SVGElement.prototype.fillSetter.call(
+ this,
+ this.stroke,
+ 'stroke',
+ element
+ );
+
+ element.setAttribute('stroke-width', this['stroke-width']);
+ this.hasStroke = true;
+ } else if (key === 'stroke-width' && value === 0 && this.hasStroke) {
+ element.removeAttribute('stroke');
+ this.hasStroke = false;
+ }
+ };
+
+ /**
+ * Allows direct access to the Highcharts rendering layer in order to draw
+ * primitive shapes like circles, rectangles, paths or text directly on a chart,
+ * or independent from any chart. The SVGRenderer represents a wrapper object
* for SVG in modern browsers. Through the VMLRenderer, part of the `oldie.js`
* module, it also brings vector graphics to IE <= 8.
*
* An existing chart's renderer can be accessed through {@link Chart.renderer}.
* The renderer can also be used completely decoupled from a chart.
*
- * @param {HTMLDOMElement} container - Where to put the SVG in the web page.
- * @param {number} width - The width of the SVG.
- * @param {number} height - The height of the SVG.
- * @param {boolean} [forExport=false] - Whether the rendered content is intended
- * for export.
- * @param {boolean} [allowHTML=true] - Whether the renderer is allowed to
- * include HTML text, which will be projected on top of the SVG.
+ * @sample highcharts/members/renderer-on-chart
+ * Annotating a chart programmatically.
+ * @sample highcharts/members/renderer-basic
+ * Independent SVG drawing.
*
* @example
* // Use directly without a chart object.
* var renderer = new Highcharts.Renderer(parentNode, 600, 400);
*
- * @sample highcharts/members/renderer-on-chart
- * Annotating a chart programmatically.
- * @sample highcharts/members/renderer-basic
- * Independent SVG drawing.
+ * @class
+ * @name Highcharts.SVGRenderer
*
- * @class Highcharts.SVGRenderer
+ * @param {Highcharts.HTMLDOMElement} container
+ * Where to put the SVG in the web page.
+ *
+ * @param {number} width
+ * The width of the SVG.
+ *
+ * @param {number} height
+ * The height of the SVG.
+ *
+ * @param {boolean} [forExport=false]
+ * Whether the rendered content is intended for export.
+ *
+ * @param {boolean} [allowHTML=true]
+ * Whether the renderer is allowed to include HTML text, which will be
+ * projected on top of the SVG.
*/
- SVGRenderer = H.SVGRenderer = function() {
+ SVGRenderer = H.SVGRenderer = function () {
this.init.apply(this, arguments);
};
extend(SVGRenderer.prototype, /** @lends Highcharts.SVGRenderer.prototype */ {
/**
* A pointer to the renderer's associated Element class. The VMLRenderer
* will have a pointer to VMLElement here.
- * @type {SVGElement}
+ *
+ * @name Highcharts.SVGRenderer#Element
+ * @type {Highcharts.SVGElement}
*/
Element: SVGElement,
+
SVG_NS: SVG_NS,
+
/**
- * Initialize the SVGRenderer. Overridable initiator function that takes
+ * Initialize the SVGRenderer. Overridable initializer function that takes
* the same parameters as the constructor.
- */
- init: function(container, width, height, style, forExport, allowHTML) {
+ *
+ * @function Highcharts.SVGRenderer#init
+ *
+ * @param {Highcharts.HTMLDOMElement} container
+ * Where to put the SVG in the web page.
+ *
+ * @param {number} width
+ * The width of the SVG.
+ *
+ * @param {number} height
+ * The height of the SVG.
+ *
+ * @param {boolean} [forExport=false]
+ * Whether the rendered content is intended for export.
+ *
+ * @param {boolean} [allowHTML=true]
+ * Whether the renderer is allowed to include HTML text, which will
+ * be projected on top of the SVG.
+ *
+ * @param {boolean} [styledMode=false]
+ * Whether the renderer belongs to a chart that is in styled mode.
+ * If it does, it will avoid setting presentational attributes in
+ * some cases, but not when set explicitly through `.attr` and `.css`
+ * etc.
+ *
+ * @return {void}
+ */
+ init: function (
+ container,
+ width,
+ height,
+ style,
+ forExport,
+ allowHTML,
+ styledMode
+ ) {
var renderer = this,
boxWrapper,
element,
@@ -4345,9 +5736,12 @@
.attr({
'version': '1.1',
'class': 'highcharts-root'
- })
+ });
+
+ if (!styledMode) {
+ boxWrapper.css(this.getStyle(style));
+ }
- .css(this.getStyle(style));
element = boxWrapper.element;
container.appendChild(element);
@@ -4363,56 +5757,59 @@
// object properties
renderer.isSVG = true;
- /**
+ /**
* The root `svg` node of the renderer.
- * @name box
- * @memberOf SVGRenderer
- * @type {SVGDOMElement}
+ *
+ * @name Highcharts.SVGRenderer#box
+ * @type {Highcharts.SVGDOMElement}
*/
this.box = element;
- /**
+ /**
* The wrapper for the root `svg` node of the renderer.
*
- * @name boxWrapper
- * @memberOf SVGRenderer
- * @type {SVGElement}
+ * @name Highcharts.SVGRenderer#boxWrapper
+ * @type {Highcharts.SVGElement}
*/
this.boxWrapper = boxWrapper;
renderer.alignedObjects = [];
/**
* Page url used for internal references.
+ *
+ * @private
+ * @name Highcharts.SVGRenderer#url
* @type {string}
*/
// #24, #672, #1070
this.url = (
- (isFirefox || isWebKit) &&
- doc.getElementsByTagName('base').length
- ) ?
+ (isFirefox || isWebKit) &&
+ doc.getElementsByTagName('base').length
+ ) ?
win.location.href
- .replace(/#.*?$/, '') // remove the hash
- .replace(/<[^>]*>/g, '') // wing cut HTML
- // escape parantheses and quotes
- .replace(/([\('\)])/g, '\\$1')
- // replace spaces (needed for Safari only)
- .replace(/ /g, '%20') :
+ .split('#')[0] // remove the hash
+ .replace(/<[^>]*>/g, '') // wing cut HTML
+ // escape parantheses and quotes
+ .replace(/([\('\)])/g, '\\$1')
+ // replace spaces (needed for Safari only)
+ .replace(/ /g, '%20') :
'';
// Add description
desc = this.createElement('desc').add();
desc.element.appendChild(
- doc.createTextNode('Created with Highcharts 6.0.3')
+ doc.createTextNode('Created with Highcharts 7.0.3')
);
/**
* A pointer to the `defs` node of the root SVG.
- * @type {SVGElement}
- * @name defs
- * @memberOf SVGRenderer
+ *
+ * @name Highcharts.SVGRenderer#defs
+ * @type {Highcharts.SVGElement}
*/
renderer.defs = this.createElement('defs').add();
renderer.allowHTML = allowHTML;
renderer.forExport = forExport;
+ renderer.styledMode = styledMode;
renderer.gradients = {}; // Object where gradient SvgElements are stored
renderer.cache = {}; // Cache for numerical bounding boxes
renderer.cacheKeys = [];
@@ -4421,7 +5818,6 @@
renderer.setSize(width, height, false);
-
// Issue 110 workaround:
// In Firefox, if a div is positioned by percentage, its pixel position
// may land between pixels. The container itself doesn't display this,
@@ -4430,12 +5826,10 @@
// for. This doesn't seem to work inside iframes though (like in
// jsFiddle).
var subPixelFix, rect;
+
if (isFirefox && container.getBoundingClientRect) {
- subPixelFix = function() {
- css(container, {
- left: 0,
- top: 0
- });
+ subPixelFix = function () {
+ css(container, { left: 0, top: 0 });
rect = container.getBoundingClientRect();
css(container, {
left: (Math.ceil(rect.left) - rect.left) + 'px',
@@ -4451,15 +5845,80 @@
}
},
+ /**
+ * General method for adding a definition to the SVG `defs` tag. Can be used
+ * for gradients, fills, filters etc. Styled mode only. A hook for adding
+ * general definitions to the SVG's defs tag. Definitions can be referenced
+ * from the CSS by its `id`. Read more in
+ * [gradients, shadows and patterns](https://www.highcharts.com/docs/chart-design-and-style/gradients-shadows-and-patterns).
+ * Styled mode only.
+ *
+ * @function Highcharts.SVGRenderer#definition
+ *
+ * @param {Highcharts.SVGDefinitionObject} def
+ * A serialized form of an SVG definition, including children.
+ *
+ * @return {Highcharts.SVGElement}
+ * The inserted node.
+ */
+ definition: function (def) {
+ var ren = this;
+
+ function recurse(config, parent) {
+ var ret;
+
+ splat(config).forEach(function (item) {
+ var node = ren.createElement(item.tagName),
+ attr = {};
+
+ // Set attributes
+ objectEach(item, function (val, key) {
+ if (
+ key !== 'tagName' &&
+ key !== 'children' &&
+ key !== 'textContent'
+ ) {
+ attr[key] = val;
+ }
+ });
+ node.attr(attr);
+
+ // Add to the tree
+ node.add(parent || ren.defs);
+
+ // Add text content
+ if (item.textContent) {
+ node.element.appendChild(
+ doc.createTextNode(item.textContent)
+ );
+ }
+
+ // Recurse
+ recurse(item.children || [], node);
+
+ ret = node;
+ });
+
+ // Return last node added (on top level it's the only one)
+ return ret;
+ }
+ return recurse(def);
+ },
/**
* Get the global style setting for the renderer.
+ *
* @private
- * @param {CSSObject} style - Style settings.
- * @return {CSSObject} The style settings mixed with defaults.
+ * @function Highcharts.SVGRenderer#getStyle
+ *
+ * @param {Highcharts.CSSObject} style
+ * Style settings.
+ *
+ * @return {Highcharts.CSSObject}
+ * The style settings mixed with defaults.
*/
- getStyle: function(style) {
+ getStyle: function (style) {
this.style = extend({
fontFamily: '"Lucida Grande", "Lucida Sans Unicode", ' +
@@ -4469,34 +5928,43 @@
}, style);
return this.style;
},
+
/**
* Apply the global style on the renderer, mixed with the default styles.
- *
- * @param {CSSObject} style - CSS to apply.
+ *
+ * @function Highcharts.SVGRenderer#setStyle
+ *
+ * @param {Highcharts.CSSObject} style
+ * CSS to apply.
*/
- setStyle: function(style) {
+ setStyle: function (style) {
this.boxWrapper.css(this.getStyle(style));
},
-
/**
* Detect whether the renderer is hidden. This happens when one of the
* parent elements has `display: none`. Used internally to detect when we
* needto render preliminarily in another div to get the text bounding boxes
* right.
*
- * @returns {boolean} True if it is hidden.
+ * @function Highcharts.SVGRenderer#isHidden
+ *
+ * @return {boolean}
+ * True if it is hidden.
*/
- isHidden: function() { // #608
+ isHidden: function () { // #608
return !this.boxWrapper.getBBox().width;
},
/**
* Destroys the renderer and its allocated members.
+ *
+ * @function Highcharts.SVGRenderer#destroy
*/
- destroy: function() {
+ destroy: function () {
var renderer = this,
rendererDefs = renderer.defs;
+
renderer.box = null;
renderer.boxWrapper = renderer.boxWrapper.destroy();
@@ -4521,16 +5989,22 @@
},
/**
- * Create a wrapper for an SVG element. Serves as a factory for
- * {@link SVGElement}, but this function is itself mostly called from
+ * Create a wrapper for an SVG element. Serves as a factory for
+ * {@link SVGElement}, but this function is itself mostly called from
* primitive factories like {@link SVGRenderer#path}, {@link
* SVGRenderer#rect} or {@link SVGRenderer#text}.
- *
- * @param {string} nodeName - The node name, for example `rect`, `g` etc.
- * @returns {SVGElement} The generated SVGElement.
+ *
+ * @function Highcharts.SVGRenderer#createElement
+ *
+ * @param {string} nodeName
+ * The node name, for example `rect`, `g` etc.
+ *
+ * @return {Highcharts.SVGElement}
+ * The generated SVGElement.
*/
- createElement: function(nodeName) {
+ createElement: function (nodeName) {
var wrapper = new this.Element();
+
wrapper.init(this, nodeName);
return wrapper;
},
@@ -4538,7 +6012,9 @@
/**
* Dummy function for plugins, called every time the renderer is updated.
* Prior to Highcharts 5, this was used for the canvg renderer.
- * @function
+ *
+ * @deprecated
+ * @function Highcharts.SVGRenderer#draw
*/
draw: noop,
@@ -4548,8 +6024,15 @@
* function.
*
* @private
+ * @function Highcharts.SVGRenderer#getRadialAttr
+ *
+ * @param {Array} radialReference
+ *
+ * @param {Highcharts.SVGAttributes} gradAttr
+ *
+ * @return {Highcharts.SVGAttributes}
*/
- getRadialAttr: function(radialReference, gradAttr) {
+ getRadialAttr: function (radialReference, gradAttr) {
return {
cx: (radialReference[0] - radialReference[2] / 2) +
gradAttr.cx * radialReference[2],
@@ -4559,45 +6042,109 @@
};
},
- getSpanWidth: function(wrapper, tspan) {
- var renderer = this,
- bBox = wrapper.getBBox(true),
- actualWidth = bBox.width;
-
- // Old IE cannot measure the actualWidth for SVG elements (#2314)
- if (!svg && renderer.forExport) {
- actualWidth = renderer.measureSpanWidth(
- tspan.firstChild.data,
- wrapper.styles
- );
- }
- return actualWidth;
- },
-
- applyEllipsis: function(wrapper, tspan, text, width) {
+ /**
+ * Truncate the text node contents to a given length. Used when the css
+ * width is set. If the `textOverflow` is `ellipsis`, the text is truncated
+ * character by character to the given length. If not, the text is
+ * word-wrapped line by line.
+ *
+ * @private
+ * @function Highcharts.SVGRenderer#truncate
+ *
+ * @param {Highcharts.SVGElement} wrapper
+ *
+ * @param {Highcharts.SVGDOMElement} tspan
+ *
+ * @param {string} text
+ *
+ * @param {Array.} words
+ *
+ * @param {number} width
+ *
+ * @param {Function} getString
+ *
+ * @return {boolean}
+ * True if tspan is too long.
+ */
+ truncate: function (
+ wrapper,
+ tspan,
+ text,
+ words,
+ startAt,
+ width,
+ getString
+ ) {
var renderer = this,
rotation = wrapper.rotation,
- str = text,
- currentIndex,
- minIndex = 0,
- maxIndex = text.length,
- updateTSpan = function(s) {
- tspan.removeChild(tspan.firstChild);
+ str,
+ // Word wrap can not be truncated to shorter than one word, ellipsis
+ // text can be completely blank.
+ minIndex = words ? 1 : 0,
+ maxIndex = (text || words).length,
+ currentIndex = maxIndex,
+ // Cache the lengths to avoid checking the same twice
+ lengths = [],
+ updateTSpan = function (s) {
+ if (tspan.firstChild) {
+ tspan.removeChild(tspan.firstChild);
+ }
if (s) {
tspan.appendChild(doc.createTextNode(s));
}
},
+ getSubStringLength = function (charEnd, concatenatedEnd) {
+ // charEnd is useed when finding the character-by-character
+ // break for ellipsis, concatenatedEnd is used for word-by-word
+ // break for word wrapping.
+ var end = concatenatedEnd || charEnd;
+
+ if (lengths[end] === undefined) {
+ // Modern browsers
+ if (tspan.getSubStringLength) {
+ // Fails with DOM exception on unit-tests/legend/members
+ // of unknown reason. Desired width is 0, text content
+ // is "5" and end is 1.
+ try {
+ lengths[end] = startAt + tspan.getSubStringLength(
+ 0,
+ words ? end + 1 : end
+ );
+
+ } catch (e) {}
+
+ // Legacy
+ } else if (renderer.getSpanWidth) { // #9058 jsdom
+ updateTSpan(getString(text || words, charEnd));
+ lengths[end] = startAt +
+ renderer.getSpanWidth(wrapper, tspan);
+ }
+ }
+ return lengths[end];
+ },
actualWidth,
- wasTooLong;
+ truncated;
+
wrapper.rotation = 0; // discard rotation when computing box
- actualWidth = renderer.getSpanWidth(wrapper, tspan);
- wasTooLong = actualWidth > width;
- if (wasTooLong) {
+ actualWidth = getSubStringLength(tspan.textContent.length);
+ truncated = startAt + actualWidth > width;
+ if (truncated) {
+
+ // Do a binary search for the index where to truncate the text
while (minIndex <= maxIndex) {
currentIndex = Math.ceil((minIndex + maxIndex) / 2);
- str = text.substring(0, currentIndex) + '\u2026';
- updateTSpan(str);
- actualWidth = renderer.getSpanWidth(wrapper, tspan);
+
+ // When checking words for word-wrap, we need to build the
+ // string and measure the subStringLength at the concatenated
+ // word length.
+ if (words) {
+ str = getString(words, currentIndex);
+ }
+ actualWidth = getSubStringLength(
+ currentIndex,
+ str && str.length - 1
+ );
+
if (minIndex === maxIndex) {
// Complete
minIndex = maxIndex + 1;
@@ -4609,45 +6156,65 @@
minIndex = currentIndex;
}
}
- // If max index was 0 it means just ellipsis was also to large.
+ // If max index was 0 it means the shortest possible text was also
+ // too large. For ellipsis that means only the ellipsis, while for
+ // word wrap it means the whole first word.
if (maxIndex === 0) {
- // Remove ellipses.
+ // Remove ellipsis
updateTSpan('');
+
+ // If the new text length is one less than the original, we don't
+ // need the ellipsis
+ } else if (!(text && maxIndex === text.length - 1)) {
+ updateTSpan(str || getString(text || words, currentIndex));
}
}
+
+ // When doing line wrapping, prepare for the next line by removing the
+ // items from this line.
+ if (words) {
+ words.splice(0, currentIndex);
+ }
+
+ wrapper.actualWidth = actualWidth;
wrapper.rotation = rotation; // Apply rotation again.
- return wasTooLong;
+ return truncated;
},
/**
* A collection of characters mapped to HTML entities. When `useHTML` on an
- * element is true, these entities will be rendered correctly by HTML. In
+ * element is true, these entities will be rendered correctly by HTML. In
* the SVG pseudo-HTML, they need to be unescaped back to simple characters,
* so for example `<` will render as `<`.
*
* @example
* // Add support for unescaping quotes
* Highcharts.SVGRenderer.prototype.escapes['"'] = '"';
- *
- * @type {Object}
+ *
+ * @name Highcharts.SVGRenderer#escapes
+ * @type {Highcharts.Dictionary}
*/
escapes: {
'&': '&',
'<': '<',
'>': '>',
"'": ''', // eslint-disable-line quotes
- '"': '"'
+ '"': '"'
},
/**
* Parse a simple HTML string into SVG tspans. Called internally when text
- * is set on an SVGElement. The function supports a subset of HTML tags,
- * CSS text features like `width`, `text-overflow`, `white-space`, and
- * also attributes like `href` and `style`.
+ * is set on an SVGElement. The function supports a subset of HTML tags, CSS
+ * text features like `width`, `text-overflow`, `white-space`, and also
+ * attributes like `href` and `style`.
+ *
* @private
- * @param {SVGElement} wrapper The parent SVGElement.
+ * @function Highcharts.SVGRenderer#buildText
+ *
+ * @param {Highcharts.SVGElement} wrapper
+ * The parent SVGElement.
*/
- buildText: function(wrapper) {
+ buildText: function (wrapper) {
var textNode = wrapper.element,
renderer = this,
forExport = renderer.forExport,
@@ -4655,10 +6222,7 @@
hasMarkup = textStr.indexOf('<') !== -1,
lines,
childNodes = textNode.childNodes,
- clsRegex,
- styleRegex,
- hrefRegex,
- wasTooLong,
+ truncated,
parentX = attr(textNode, 'x'),
textStyles = wrapper.styles,
width = wrapper.textWidth,
@@ -4671,13 +6235,15 @@
isSubsequentLine,
i = childNodes.length,
tempParent = width && !wrapper.added && this.box,
- getLineHeight = function(tspan) {
+ getLineHeight = function (tspan) {
var fontSizeStyle;
- fontSizeStyle = /(px|em)$/.test(tspan && tspan.style.fontSize) ?
- tspan.style.fontSize :
- (fontSize || renderer.style.fontSize || 12);
-
+ if (!renderer.styledMode) {
+ fontSizeStyle =
+ /(px|em)$/.test(tspan && tspan.style.fontSize) ?
+ tspan.style.fontSize :
+ (fontSize || renderer.style.fontSize || 12);
+ }
return textLineHeight ?
pInt(textLineHeight) :
@@ -4687,14 +6253,33 @@
tspan.getAttribute('style') ? tspan : textNode
).h;
},
- unescapeEntities = function(inputStr) {
- objectEach(renderer.escapes, function(value, key) {
- inputStr = inputStr.replace(
- new RegExp(value, 'g'),
- key
- );
+ unescapeEntities = function (inputStr, except) {
+ objectEach(renderer.escapes, function (value, key) {
+ if (!except || except.indexOf(value) === -1) {
+ inputStr = inputStr.toString().replace(
+ new RegExp(value, 'g'), // eslint-disable-line security/detect-non-literal-regexp
+ key
+ );
+ }
});
return inputStr;
+ },
+ parseAttribute = function (s, attr) {
+ var start,
+ delimiter;
+
+ start = s.indexOf('<');
+ s = s.substring(start, s.indexOf('>') - start);
+
+ start = s.indexOf(attr + '=');
+ if (start !== -1) {
+ start = start + attr.length + 1;
+ delimiter = s.charAt(start);
+ if (delimiter === '"' || delimiter === "'") { // eslint-disable-line quotes
+ s = s.substring(start + 1);
+ return s.substring(0, s.indexOf(delimiter));
+ }
+ }
};
// The buildText code is quite heavy, so if we're not changing something
@@ -4720,7 +6305,8 @@
// Skip tspans, add text directly to text node. The forceTSpan is a hook
// used in text outline hack.
- if (!hasMarkup &&
+ if (
+ !hasMarkup &&
!textOutline &&
!ellipsis &&
!width &&
@@ -4728,24 +6314,38 @@
) {
textNode.appendChild(doc.createTextNode(unescapeEntities(textStr)));
- // Complex strings, add more logic
+ // Complex strings, add more logic
} else {
- clsRegex = /<.*class="([^"]+)".*>/;
- styleRegex = /<.*style="([^"]+)".*>/;
- hrefRegex = /<.*href="([^"]+)".*>/;
-
if (tempParent) {
// attach it to the DOM to read offset width
tempParent.appendChild(textNode);
}
if (hasMarkup) {
- lines = textStr
-
- .replace(/<(b|strong)>/g, '')
- .replace(/<(i|em)>/g, '')
+ lines = renderer.styledMode ? (
+ textStr
+ .replace(
+ /<(b|strong)>/g,
+ ''
+ )
+ .replace(
+ /<(i|em)>/g,
+ ''
+ )
+ ) : (
+ textStr
+ .replace(
+ /<(b|strong)>/g,
+ ''
+ )
+ .replace(
+ /<(i|em)>/g,
+ ''
+ )
+ );
+ lines = lines
.replace(//g, ' ')
.split(//g);
@@ -4756,15 +6356,17 @@
// Trim empty lines (#5261)
- lines = grep(lines, function(line) {
+ lines = lines.filter(function (line) {
return line !== '';
});
// build the lines
- each(lines, function buildTextLines(line, lineNo) {
+ lines.forEach(function buildTextLines(line, lineNo) {
var spans,
- spanNo = 0;
+ spanNo = 0,
+ lineLength = 0;
+
line = line
// Trim to prevent useless/costly process on the spaces
// (#5258)
@@ -4773,41 +6375,44 @@
.replace(/<\/span>/g, ' |||');
spans = line.split('|||');
- each(spans, function buildTextSpans(span) {
+ spans.forEach(function buildTextSpans(span) {
if (span !== '' || spans.length === 1) {
var attributes = {},
tspan = doc.createElementNS(
renderer.SVG_NS,
'tspan'
),
- spanCls,
- spanStyle; // #390
- if (clsRegex.test(span)) {
- spanCls = span.match(clsRegex)[1];
- attr(tspan, 'class', spanCls);
+ classAttribute,
+ styleAttribute, // #390
+ hrefAttribute;
+
+ classAttribute = parseAttribute(span, 'class');
+ if (classAttribute) {
+ attr(tspan, 'class', classAttribute);
}
- if (styleRegex.test(span)) {
- spanStyle = span.match(styleRegex)[1].replace(
+
+ styleAttribute = parseAttribute(span, 'style');
+ if (styleAttribute) {
+ styleAttribute = styleAttribute.replace(
/(;| |^)color([ :])/,
'$1fill$2'
);
- attr(tspan, 'style', spanStyle);
+ attr(tspan, 'style', styleAttribute);
}
// Not for export - #1529
- if (hrefRegex.test(span) && !forExport) {
+ hrefAttribute = parseAttribute(span, 'href');
+ if (hrefAttribute && !forExport) {
attr(
tspan,
'onclick',
- 'location.href=\"' +
- span.match(hrefRegex)[1] + '\"'
+ 'location.href=\"' + hrefAttribute + '\"'
);
attr(tspan, 'class', 'highcharts-anchor');
- css(tspan, {
- cursor: 'pointer'
- });
-
+ if (!renderer.styledMode) {
+ css(tspan, { cursor: 'pointer' });
+ }
}
// Strip away unsupported HTML tags (#7126)
@@ -4844,9 +6449,7 @@
// allow getting the right offset height in
// exporting in IE
if (!svg && forExport) {
- css(tspan, {
- display: 'block'
- });
+ css(tspan, { display: 'block' });
}
// Set the line height based on the font size of
@@ -4858,64 +6461,55 @@
);
}
- /*
- if (width) {
- renderer.breakText(wrapper, width);
- }
- */
-
// Check width and apply soft breaks or ellipsis
if (width) {
var words = span.replace(
/([^\^])-/g,
'$1- '
).split(' '), // #1273
- hasWhiteSpace = (
+ hasWhiteSpace = !noWrap && (
spans.length > 1 ||
lineNo ||
- (words.length > 1 && !noWrap)
+ words.length > 1
),
- tooLong,
- rest = [],
- actualWidth,
- dy = getLineHeight(tspan),
- rotation = wrapper.rotation;
+ wrapLineNo = 0,
+ dy = getLineHeight(tspan);
if (ellipsis) {
- wasTooLong = renderer.applyEllipsis(
+ truncated = renderer.truncate(
wrapper,
tspan,
span,
- width
- );
- }
-
- while (!ellipsis &&
- hasWhiteSpace &&
- (words.length || rest.length)
- ) {
- // discard rotation when computing box
- wrapper.rotation = 0;
- actualWidth = renderer.getSpanWidth(
- wrapper,
- tspan
+ undefined,
+ 0,
+ // Target width
+ Math.max(
+ 0,
+ // Substract the font face to make
+ // room for the ellipsis itself
+ width - parseInt(fontSize || 12, 10)
+ ),
+ // Build the text to test for
+ function (text, currentIndex) {
+ return text.substring(
+ 0,
+ currentIndex
+ ) + '\u2026';
+ }
);
- tooLong = actualWidth > width;
+ } else if (hasWhiteSpace) {
- // For ellipsis, do a binary search for the
- // correct string length
- if (wasTooLong === undefined) {
- wasTooLong = tooLong; // First time
- }
+ while (words.length) {
- // Looping down, this is the first word
- // sequence that is not too long, so we can
- // move on to build the next line.
- if (!tooLong || words.length === 1) {
- words = rest;
- rest = [];
+ // For subsequent lines, create tspans
+ // with the same style attributes as the
+ // parent text node.
+ if (
+ words.length &&
+ !noWrap &&
+ wrapLineNo > 0
+ ) {
- if (words.length && !noWrap) {
tspan = doc.createElementNS(
SVG_NS,
'tspan'
@@ -4924,36 +6518,55 @@
dy: dy,
x: parentX
});
- if (spanStyle) { // #390
- attr(tspan, 'style', spanStyle);
+ if (styleAttribute) { // #390
+ attr(
+ tspan,
+ 'style',
+ styleAttribute
+ );
}
+ // Start by appending the full
+ // remaining text
+ tspan.appendChild(
+ doc.createTextNode(
+ words.join(' ')
+ .replace(/- /g, '-')
+ )
+ );
textNode.appendChild(tspan);
}
- // a single word is pressing it out
- if (actualWidth > width) {
- 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, '-')
- )
+ // For each line, truncate the remaining
+ // words into the line length.
+ renderer.truncate(
+ wrapper,
+ tspan,
+ null,
+ words,
+ wrapLineNo === 0 ? lineLength : 0,
+ width,
+ // Build the text to test for
+ function (text, currentIndex) {
+ return words
+ .slice(0, currentIndex)
+ .join(' ')
+ .replace(/- /g, '-');
+ }
);
+
+ lineLength = wrapper.actualWidth;
+ wrapLineNo++;
}
}
- wrapper.rotation = rotation;
}
spanNo++;
}
+
}
+
});
+
// To avoid beginning lines that doesn't add to the textNode
// (#6144)
isSubsequentLine = (
@@ -4962,8 +6575,11 @@
);
});
- if (wasTooLong) {
- wrapper.attr('title', wrapper.textStr);
+ if (ellipsis && truncated) {
+ wrapper.attr(
+ 'title',
+ unescapeEntities(wrapper.textStr, ['<', '>']) // #7179
+ );
}
if (tempParent) {
tempParent.removeChild(textNode);
@@ -4976,85 +6592,65 @@
}
},
-
-
- /*
- breakText: function (wrapper, width) {
- var bBox = wrapper.getBBox(),
- node = wrapper.element,
- textLength = node.textContent.length,
- // try this position first, based on average character width
- pos = Math.round(width * textLength / bBox.width),
- increment = 0,
- finalPos;
-
- if (bBox.width > width) {
- while (finalPos === undefined) {
- textLength = node.getSubStringLength(0, pos);
-
- if (textLength <= width) {
- if (increment === -1) {
- finalPos = pos;
- } else {
- increment = 1;
- }
- } else {
- if (increment === 1) {
- finalPos = pos - 1;
- } else {
- increment = -1;
- }
- }
- pos += increment;
- }
- }
- console.log(
- 'width',
- width,
- 'stringWidth',
- node.getSubStringLength(0, finalPos)
- )
- },
- */
-
/**
* Returns white for dark colors and black for bright colors.
*
- * @param {ColorString} rgba - The color to get the contrast for.
- * @returns {string} The contrast color, either `#000000` or `#FFFFFF`.
+ * @function Highcharts.SVGRenderer#getContrast
+ *
+ * @param {Highcharts.ColorString} rgba
+ * The color to get the contrast for.
+ *
+ * @return {string}
+ * The contrast color, either `#000000` or `#FFFFFF`.
*/
- getContrast: function(rgba) {
+ getContrast: function (rgba) {
rgba = color(rgba).rgba;
// The threshold may be discussed. Here's a proposal for adding
// different weight to the color channels (#6216)
- /*
- rgba[0] *= 1; // red
- rgba[1] *= 1.2; // green
- rgba[2] *= 0.7; // blue
- */
+ rgba[0] *= 1; // red
+ rgba[1] *= 1.2; // green
+ rgba[2] *= 0.5; // blue
- return rgba[0] + rgba[1] + rgba[2] > 2 * 255 ? '#000000' : '#FFFFFF';
+ return rgba[0] + rgba[1] + rgba[2] > 1.8 * 255 ? '#000000' : '#FFFFFF';
},
/**
* Create a button with preset states.
- * @param {string} text - The text or HTML to draw.
- * @param {number} x - The x position of the button's left side.
- * @param {number} y - The y position of the button's top side.
- * @param {Function} callback - The function to execute on button click or
- * touch.
- * @param {SVGAttributes} [normalState] - SVG attributes for the normal
- * state.
- * @param {SVGAttributes} [hoverState] - SVG attributes for the hover state.
- * @param {SVGAttributes} [pressedState] - SVG attributes for the pressed
- * state.
- * @param {SVGAttributes} [disabledState] - SVG attributes for the disabled
- * state.
- * @param {Symbol} [shape=rect] - The shape type.
- * @returns {SVGRenderer} The button element.
- */
- button: function(
+ *
+ * @function Highcharts.SVGRenderer#button
+ *
+ * @param {string} text
+ * The text or HTML to draw.
+ *
+ * @param {number} x
+ * The x position of the button's left side.
+ *
+ * @param {number} y
+ * The y position of the button's top side.
+ *
+ * @param {Function} callback
+ * The function to execute on button click or touch.
+ *
+ * @param {Highcharts.SVGAttributes} [normalState]
+ * SVG attributes for the normal state.
+ *
+ * @param {Highcharts.SVGAttributes} [hoverState]
+ * SVG attributes for the hover state.
+ *
+ * @param {Highcharts.SVGAttributes} [pressedState]
+ * SVG attributes for the pressed state.
+ *
+ * @param {Highcharts.SVGAttributes} [disabledState]
+ * SVG attributes for the disabled state.
+ *
+ * @param {Highcharts.SymbolKey} [shape=rect]
+ * The shape type.
+ *
+ * @return {Highcharts.SVGElement}
+ * The button element.
+ */
+ button: function (
text,
x,
y,
@@ -5076,7 +6672,8 @@
null,
'button'
),
- curState = 0;
+ curState = 0,
+ styledMode = this.styledMode;
// Default, non-stylable attributes
label.attr(merge({
@@ -5084,109 +6681,110 @@
'r': 2
}, normalState));
+ if (!styledMode) {
+ // Presentational
+ var normalStyle,
+ hoverStyle,
+ pressedStyle,
+ disabledStyle;
+
+ // Normal state - prepare the attributes
+ normalState = merge({
+ fill: '#f7f7f7',
+ stroke: '#cccccc',
+ 'stroke-width': 1,
+ style: {
+ color: '#333333',
+ cursor: 'pointer',
+ fontWeight: 'normal'
+ }
+ }, normalState);
+ normalStyle = normalState.style;
+ delete normalState.style;
+
+ // Hover state
+ hoverState = merge(normalState, {
+ fill: '#e6e6e6'
+ }, hoverState);
+ hoverStyle = hoverState.style;
+ delete hoverState.style;
+
+ // Pressed state
+ pressedState = merge(normalState, {
+ fill: '#e6ebf5',
+ style: {
+ color: '#000000',
+ fontWeight: 'bold'
+ }
+ }, pressedState);
+ pressedStyle = pressedState.style;
+ delete pressedState.style;
- // Presentational
- var normalStyle,
- hoverStyle,
- pressedStyle,
- disabledStyle;
-
- // Normal state - prepare the attributes
- normalState = merge({
- fill: '#f7f7f7',
- stroke: '#cccccc',
- 'stroke-width': 1,
- style: {
- color: '#333333',
- cursor: 'pointer',
- fontWeight: 'normal'
- }
- }, normalState);
- normalStyle = normalState.style;
- delete normalState.style;
-
- // Hover state
- hoverState = merge(normalState, {
- fill: '#e6e6e6'
- }, hoverState);
- hoverStyle = hoverState.style;
- delete hoverState.style;
-
- // Pressed state
- pressedState = merge(normalState, {
- fill: '#e6ebf5',
- style: {
- color: '#000000',
- fontWeight: 'bold'
- }
- }, pressedState);
- pressedStyle = pressedState.style;
- delete pressedState.style;
-
- // Disabled state
- disabledState = merge(normalState, {
- style: {
- color: '#cccccc'
- }
- }, disabledState);
- disabledStyle = disabledState.style;
- delete disabledState.style;
-
+ // Disabled state
+ disabledState = merge(normalState, {
+ style: {
+ color: '#cccccc'
+ }
+ }, disabledState);
+ disabledStyle = disabledState.style;
+ delete disabledState.style;
+ }
// Add the events. IE9 and IE10 need mouseover and mouseout to funciton
// (#667).
- addEvent(label.element, isMS ? 'mouseover' : 'mouseenter', function() {
+ addEvent(label.element, isMS ? 'mouseover' : 'mouseenter', function () {
if (curState !== 3) {
label.setState(1);
}
});
- addEvent(label.element, isMS ? 'mouseout' : 'mouseleave', function() {
+ addEvent(label.element, isMS ? 'mouseout' : 'mouseleave', function () {
if (curState !== 3) {
label.setState(curState);
}
});
- label.setState = function(state) {
+ label.setState = function (state) {
// Hover state is temporary, don't record it
if (state !== 1) {
label.state = curState = state;
}
// Update visuals
- label.removeClass(
+ label
+ .removeClass(
/highcharts-button-(normal|hover|pressed|disabled)/
)
.addClass(
- 'highcharts-button-' + ['normal', 'hover', 'pressed', 'disabled'][state || 0]
+ 'highcharts-button-' +
+ ['normal', 'hover', 'pressed', 'disabled'][state || 0]
);
-
- label.attr([
- normalState,
- hoverState,
- pressedState,
- disabledState
- ][state || 0])
- .css([
- normalStyle,
- hoverStyle,
- pressedStyle,
- disabledStyle
- ][state || 0]);
-
+ if (!styledMode) {
+ label
+ .attr([
+ normalState,
+ hoverState,
+ pressedState,
+ disabledState
+ ][state || 0])
+ .css([
+ normalStyle,
+ hoverStyle,
+ pressedStyle,
+ disabledStyle
+ ][state || 0]);
+ }
};
-
// Presentational attributes
- label
- .attr(normalState)
- .css(extend({
- cursor: 'default'
- }, normalStyle));
-
+ if (!styledMode) {
+ label
+ .attr(normalState)
+ .css(extend({ cursor: 'default' }, normalStyle));
+ }
return label
- .on('click', function(e) {
+ .on('click', function (e) {
if (curState !== 3) {
callback.call(label, e);
}
@@ -5195,14 +6793,19 @@
/**
* Make a straight line crisper by not spilling out to neighbour pixels.
- *
- * @param {Array} points - The original points on the format `['M', 0, 0,
- * 'L', 100, 0]`.
- * @param {number} width - The width of the line.
- * @returns {Array} The original points array, but modified to render
- * crisply.
- */
- crispLine: function(points, width) {
+ *
+ * @function Highcharts.SVGRenderer#crispLine
+ *
+ * @param {Highcharts.SVGPathArray} points
+ * The original points on the format `['M', 0, 0, 'L', 100, 0]`.
+ *
+ * @param {number} width
+ * The width of the line.
+ *
+ * @return {Highcharts.SVGPathArray}
+ * The original points array, but modified to render crisply.
+ */
+ crispLine: function (points, width) {
// normalize to a crisp line
if (points[1] === points[4]) {
// Substract due to #1129. Now bottom and left axis gridlines behave
@@ -5218,33 +6821,41 @@
/**
* Draw a path, wraps the SVG `path` element.
- *
- * @param {Array} [path] An SVG path definition in array form.
- *
- * @example
- * var path = renderer.path(['M', 10, 10, 'L', 30, 30, 'z'])
- * .attr({ stroke: '#ff00ff' })
- * .add();
- * @returns {SVGElement} The generated wrapper element.
*
* @sample highcharts/members/renderer-path-on-chart/
* Draw a path in a chart
* @sample highcharts/members/renderer-path/
* Draw a path independent from a chart
*
- */
- /**
+ * @example
+ * var path = renderer.path(['M', 10, 10, 'L', 30, 30, 'z'])
+ * .attr({ stroke: '#ff00ff' })
+ * .add();
+ *
+ * @function Highcharts.SVGRenderer#path
+ *
+ * @param {Highcharts.SVGPathArray} [path]
+ * An SVG path definition in array form.
+ *
+ * @return {Highcharts.SVGElement}
+ * The generated wrapper element.
+ *
+ *//**
* Draw a path, wraps the SVG `path` element.
- *
- * @param {SVGAttributes} [attribs] The initial attributes.
- * @returns {SVGElement} The generated wrapper element.
+ *
+ * @function Highcharts.SVGRenderer#path
+ *
+ * @param {Highcharts.SVGAttributes} [attribs]
+ * The initial attributes.
+ *
+ * @return {Highcharts.SVGElement}
+ * The generated wrapper element.
*/
- path: function(path) {
- var attribs = {
-
+ path: function (path) {
+ var attribs = this.styledMode ? {} : {
fill: 'none'
-
};
+
if (isArray(path)) {
attribs.d = path;
} else if (isObject(path)) { // attributes
@@ -5255,30 +6866,44 @@
/**
* Draw a circle, wraps the SVG `circle` element.
- *
- * @param {number} [x] The center x position.
- * @param {number} [y] The center y position.
- * @param {number} [r] The radius.
- * @returns {SVGElement} The generated wrapper element.
*
- * @sample highcharts/members/renderer-circle/ Drawing a circle
- */
- /**
- * Draw a circle, wraps the SVG `circle` element.
- *
- * @param {SVGAttributes} [attribs] The initial attributes.
- * @returns {SVGElement} The generated wrapper element.
- */
- circle: function(x, y, r) {
- var attribs = isObject(x) ? x : {
- x: x,
- y: y,
- r: r
- },
+ * @sample highcharts/members/renderer-circle/
+ * Drawing a circle
+ *
+ * @function Highcharts.SVGRenderer#circle
+ *
+ * @param {number} [x]
+ * The center x position.
+ *
+ * @param {number} [y]
+ * The center y position.
+ *
+ * @param {number} [r]
+ * The radius.
+ *
+ * @return {Highcharts.SVGElement}
+ * The generated wrapper element.
+ *//**
+ * Draw a circle, wraps the SVG `circle` element.
+ *
+ * @function Highcharts.SVGRenderer#circle
+ *
+ * @param {Highcharts.SVGAttributes} [attribs]
+ * The initial attributes.
+ *
+ * @return {Highcharts.SVGElement}
+ * The generated wrapper element.
+ */
+ circle: function (x, y, r) {
+ var attribs = (
+ isObject(x) ?
+ x :
+ x === undefined ? {} : { x: x, y: y, r: r }
+ ),
wrapper = this.createElement('circle');
// Setting x or y translates to cx and cy
- wrapper.xSetter = wrapper.ySetter = function(value, key, element) {
+ wrapper.xSetter = wrapper.ySetter = function (value, key, element) {
element.setAttribute('c' + key, value);
};
@@ -5287,25 +6912,46 @@
/**
* Draw and return an arc.
- * @param {number} [x=0] Center X position.
- * @param {number} [y=0] Center Y position.
- * @param {number} [r=0] The outer radius of the arc.
- * @param {number} [innerR=0] Inner radius like used in donut charts.
- * @param {number} [start=0] The starting angle of the arc in radians, where
- * 0 is to the right and `-Math.PI/2` is up.
- * @param {number} [end=0] The ending angle of the arc in radians, where 0
- * is to the right and `-Math.PI/2` is up.
- * @returns {SVGElement} The generated wrapper element.
*
* @sample highcharts/members/renderer-arc/
* Drawing an arc
- */
- /**
+ *
+ * @function Highcharts.SVGRenderer#arc
+ *
+ * @param {number} [x=0]
+ * Center X position.
+ *
+ * @param {number} [y=0]
+ * Center Y position.
+ *
+ * @param {number} [r=0]
+ * The outer radius of the arc.
+ *
+ * @param {number} [innerR=0]
+ * Inner radius like used in donut charts.
+ *
+ * @param {number} [start=0]
+ * The starting angle of the arc in radians, where 0 is to the right
+ * and `-Math.PI/2` is up.
+ *
+ * @param {number} [end=0]
+ * The ending angle of the arc in radians, where 0 is to the right
+ * and `-Math.PI/2` is up.
+ *
+ * @return {Highcharts.SVGElement}
+ * The generated wrapper element.
+ *//**
* Draw and return an arc. Overloaded function that takes arguments object.
- * @param {SVGAttributes} attribs Initial SVG attributes.
- * @returns {SVGElement} The generated wrapper element.
+ *
+ * @function Highcharts.SVGRenderer#arc
+ *
+ * @param {Highcharts.SVGAttributes} attribs
+ * Initial SVG attributes.
+ *
+ * @return {Highcharts.SVGElement}
+ * The generated wrapper element.
*/
- arc: function(x, y, r, innerR, start, end) {
+ arc: function (x, y, r, innerR, start, end) {
var arc,
options;
@@ -5334,28 +6980,46 @@
/**
* Draw and return a rectangle.
- * @param {number} [x] Left position.
- * @param {number} [y] Top position.
- * @param {number} [width] Width of the rectangle.
- * @param {number} [height] Height of the rectangle.
- * @param {number} [r] Border corner radius.
- * @param {number} [strokeWidth] A stroke width can be supplied to allow
- * crisp drawing.
- * @returns {SVGElement} The generated wrapper element.
- */
- /**
- * Draw and return a rectangle.
- * @param {SVGAttributes} [attributes]
- * General SVG attributes for the rectangle.
- * @return {SVGElement}
+ *
+ * @function Highcharts.SVGRenderer#rect
+ *
+ * @param {number} [x]
+ * Left position.
+ *
+ * @param {number} [y]
+ * Top position.
+ *
+ * @param {number} [width]
+ * Width of the rectangle.
+ *
+ * @param {number} [height]
+ * Height of the rectangle.
+ *
+ * @param {number} [r]
+ * Border corner radius.
+ *
+ * @param {number} [strokeWidth]
+ * A stroke width can be supplied to allow crisp drawing.
+ *
+ * @return {Highcharts.SVGElement}
* The generated wrapper element.
+ *//**
+ * Draw and return a rectangle.
*
* @sample highcharts/members/renderer-rect-on-chart/
* Draw a rectangle in a chart
* @sample highcharts/members/renderer-rect/
* Draw a rectangle independent from a chart
+ *
+ * @function Highcharts.SVGRenderer#rect
+ *
+ * @param {Highcharts.SVGAttributes} [attributes]
+ * General SVG attributes for the rectangle.
+ *
+ * @return {Highcharts.SVGElement}
+ * The generated wrapper element.
*/
- rect: function(x, y, width, height, r, strokeWidth) {
+ rect: function (x, y, width, height, r, strokeWidth) {
r = isObject(x) ? x.r : r;
@@ -5367,19 +7031,19 @@
height: Math.max(height, 0)
};
-
- if (strokeWidth !== undefined) {
- attribs.strokeWidth = strokeWidth;
- attribs = wrapper.crisp(attribs);
+ if (!this.styledMode) {
+ if (strokeWidth !== undefined) {
+ attribs.strokeWidth = strokeWidth;
+ attribs = wrapper.crisp(attribs);
+ }
+ attribs.fill = 'none';
}
- attribs.fill = 'none';
-
if (r) {
attribs.r = r;
}
- wrapper.rSetter = function(value, key, element) {
+ wrapper.rSetter = function (value, key, element) {
attr(element, {
rx: value,
ry: value
@@ -5392,14 +7056,22 @@
/**
* Resize the {@link SVGRenderer#box} and re-align all aligned child
* elements.
- * @param {number} width
- * The new pixel width.
- * @param {number} height
- * The new pixel height.
- * @param {Boolean|AnimationOptions} [animate=true]
- * Whether and how to animate.
- */
- setSize: function(width, height, animate) {
+ *
+ * @sample highcharts/members/renderer-g/
+ * Show and hide grouped objects
+ *
+ * @function Highcharts.SVGRenderer#setSize
+ *
+ * @param {number} width
+ * The new pixel width.
+ *
+ * @param {number} height
+ * The new pixel height.
+ *
+ * @param {boolean|Highcharts.AnimationOptionsObject} [animate=true]
+ * Whether and how to animate.
+ */
+ setSize: function (width, height, animate) {
var renderer = this,
alignedObjects = renderer.alignedObjects,
i = alignedObjects.length;
@@ -5411,7 +7083,7 @@
width: width,
height: height
}, {
- step: function() {
+ step: function () {
this.attr({
viewBox: '0 0 ' + this.attr('width') + ' ' +
this.attr('height')
@@ -5428,44 +7100,78 @@
/**
* Create and return an svg group element. Child
* {@link Highcharts.SVGElement} objects are added to the group by using the
- * group as the first parameter
- * in {@link Highcharts.SVGElement#add|add()}.
- *
- * @param {string} [name] The group will be given a class name of
- * `highcharts-{name}`. This can be used for styling and scripting.
- * @returns {SVGElement} The generated wrapper element.
+ * group as the first parameter in {@link Highcharts.SVGElement#add|add()}.
*
- * @sample highcharts/members/renderer-g/
- * Show and hide grouped objects
+ * @function Highcharts.SVGRenderer#g
+ *
+ * @param {string} [name]
+ * The group will be given a class name of `highcharts-{name}`. This
+ * can be used for styling and scripting.
+ *
+ * @return {Highcharts.SVGElement}
+ * The generated wrapper element.
*/
- g: function(name) {
+ g: function (name) {
var elem = this.createElement('g');
- return name ? elem.attr({
- 'class': 'highcharts-' + name
- }) : elem;
+
+ return name ? elem.attr({ 'class': 'highcharts-' + name }) : elem;
},
/**
* Display an image.
- * @param {string} src The image source.
- * @param {number} [x] The X position.
- * @param {number} [y] The Y position.
- * @param {number} [width] The image width. If omitted, it defaults to the
- * image file width.
- * @param {number} [height] The image height. If omitted it defaults to the
- * image file height.
- * @returns {SVGElement} The generated wrapper element.
*
* @sample highcharts/members/renderer-image-on-chart/
* Add an image in a chart
* @sample highcharts/members/renderer-image/
* Add an image independent of a chart
+ *
+ * @function Highcharts.SVGRenderer#image
+ *
+ * @param {string} src
+ * The image source.
+ *
+ * @param {number} [x]
+ * The X position.
+ *
+ * @param {number} [y]
+ * The Y position.
+ *
+ * @param {number} [width]
+ * The image width. If omitted, it defaults to the image file width.
+ *
+ * @param {number} [height]
+ * The image height. If omitted it defaults to the image file
+ * height.
+ *
+ * @param {Function} [onload]
+ * Event handler for image load.
+ *
+ * @return {Highcharts.SVGElement}
+ * The generated wrapper element.
*/
- image: function(src, x, y, width, height) {
+ image: function (src, x, y, width, height, onload) {
var attribs = {
preserveAspectRatio: 'none'
},
- elemWrapper;
+ elemWrapper,
+ dummy,
+ setSVGImageSource = function (el, src) {
+ // Set the href in the xlink namespace
+ if (el.setAttributeNS) {
+ el.setAttributeNS(
+ 'http://www.w3.org/1999/xlink', 'href', src
+ );
+ } else {
+ // could be exporting in IE
+ // using href throws "not supported" in ie7 and under,
+ // requries regex shim to fix later
+ el.setAttribute('hc-svg-href', src);
+ }
+ },
+ onDummyLoad = function (e) {
+ setSVGImageSource(elemWrapper.element, src);
+ onload.call(elemWrapper, e);
+ };
// optional properties
if (arguments.length > 1) {
@@ -5479,16 +7185,25 @@
elemWrapper = this.createElement('image').attr(attribs);
- // set the href in the xlink namespace
- if (elemWrapper.element.setAttributeNS) {
- elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',
- 'href', src);
+ // Add load event if supplied
+ if (onload) {
+ // We have to use a dummy HTML image since IE support for SVG image
+ // load events is very buggy. First set a transparent src, wait for
+ // dummy to load, and then add the real src to the SVG image.
+ setSVGImageSource(
+ elemWrapper.element,
+ 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' /* eslint-disable-line */
+ );
+ dummy = new win.Image();
+ addEvent(dummy, 'load', onDummyLoad);
+ dummy.src = src;
+ if (dummy.complete) {
+ onDummyLoad({});
+ }
} 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);
+ setSVGImageSource(elemWrapper.element, src);
}
+
return elemWrapper;
},
@@ -5498,25 +7213,29 @@
* It is used in Highcharts for point makers, which cake a `symbol` option,
* and label and button backgrounds like in the tooltip and stock flags.
*
- * @param {Symbol} symbol - The symbol name.
- * @param {number} x - The X coordinate for the top left position.
- * @param {number} y - The Y coordinate for the top left position.
- * @param {number} width - The pixel width.
- * @param {number} height - The pixel height.
- * @param {Object} [options] - Additional options, depending on the actual
- * symbol drawn.
- * @param {number} [options.anchorX] - The anchor X position for the
- * `callout` symbol. This is where the chevron points to.
- * @param {number} [options.anchorY] - The anchor Y position for the
- * `callout` symbol. This is where the chevron points to.
- * @param {number} [options.end] - The end angle of an `arc` symbol.
- * @param {boolean} [options.open] - Whether to draw `arc` symbol open or
- * closed.
- * @param {number} [options.r] - The radius of an `arc` symbol, or the
- * border radius for the `callout` symbol.
- * @param {number} [options.start] - The start angle of an `arc` symbol.
- */
- symbol: function(symbol, x, y, width, height, options) {
+ * @function Highcharts.SVGRenderer#symbol
+ *
+ * @param {symbol} symbol
+ * The symbol name.
+ *
+ * @param {number} x
+ * The X coordinate for the top left position.
+ *
+ * @param {number} y
+ * The Y coordinate for the top left position.
+ *
+ * @param {number} width
+ * The pixel width.
+ *
+ * @param {number} height
+ * The pixel height.
+ *
+ * @param {Highcharts.SymbolOptionsObject} [options]
+ * Additional options, depending on the actual symbol drawn.
+ *
+ * @return {Highcharts.SVGElement}
+ */
+ symbol: function (symbol, x, y, width, height, options) {
var ren = this,
obj,
@@ -5543,9 +7262,9 @@
if (symbolFn) {
obj = this.path(path);
-
- obj.attr('fill', 'none');
-
+ if (!ren.styledMode) {
+ obj.attr('fill', 'none');
+ }
// expando properties for use in animate and attr
extend(obj, {
@@ -5560,7 +7279,7 @@
}
- // Image symbols
+ // Image symbols
} else if (isImage) {
@@ -5583,7 +7302,7 @@
/**
* Set the size and position
*/
- centerImage = function() {
+ centerImage = function () {
obj.attr({
width: obj.width,
height: obj.height
@@ -5595,11 +7314,12 @@
* and the label size into consideration, and translates the image
* to center within the label.
*/
- each(['width', 'height'], function(key) {
- obj[key + 'Setter'] = function(value, key) {
+ ['width', 'height'].forEach(function (key) {
+ obj[key + 'Setter'] = function (value, key) {
var attribs = {},
imgSize = this['img' + key],
trans = key === 'width' ? 'translateX' : 'translateY';
+
this[key] = value;
if (defined(imgSize)) {
if (this.element) {
@@ -5627,14 +7347,11 @@
} else {
// Initialize image to be 0 size so export will still function
// if there's no cached sizes.
- obj.attr({
- width: 0,
- height: 0
- });
+ obj.attr({ width: 0, height: 0 });
- // Create a dummy JavaScript image to get the width and height.
+ // Create a dummy JavaScript image to get the width and height.
createElement('img', {
- onload: function() {
+ onload: function () {
var chart = charts[ren.chartIndex];
@@ -5650,7 +7367,7 @@
}
// Center the image
- symbolSizes[imageSrc] = { // Cache for next
+ symbolSizes[imageSrc] = { // Cache for next
width: this.width,
height: this.height
};
@@ -5682,19 +7399,14 @@
return obj;
},
- /**
- * @typedef {string} Symbol
- *
- * Can be one of `arc`, `callout`, `circle`, `diamond`, `square`,
- * `triangle`, `triangle-down`. Symbols are used internally for point
- * markers, button and label borders and backgrounds, or custom shapes.
- * Extendable by adding to {@link SVGRenderer#symbols}.
- */
/**
* An extendable collection of functions for defining symbol paths.
+ *
+ * @name Highcharts.SVGRenderer#symbols
+ * @type {Highcharts.SymbolDictionary}
*/
symbols: {
- 'circle': function(x, y, w, h) {
+ 'circle': function (x, y, w, h) {
// Return a full arc
return this.arc(x + w / 2, y + h / 2, w / 2, h / 2, {
start: 0,
@@ -5703,7 +7415,7 @@
});
},
- 'square': function(x, y, w, h) {
+ 'square': function (x, y, w, h) {
return [
'M', x, y,
'L', x + w, y,
@@ -5713,7 +7425,7 @@
];
},
- 'triangle': function(x, y, w, h) {
+ 'triangle': function (x, y, w, h) {
return [
'M', x + w / 2, y,
'L', x + w, y + h,
@@ -5722,7 +7434,7 @@
];
},
- 'triangle-down': function(x, y, w, h) {
+ 'triangle-down': function (x, y, w, h) {
return [
'M', x, y,
'L', x + w, y,
@@ -5730,7 +7442,7 @@
'Z'
];
},
- 'diamond': function(x, y, w, h) {
+ 'diamond': function (x, y, w, h) {
return [
'M', x + w / 2, y,
'L', x + w, y + h / 2,
@@ -5739,14 +7451,14 @@
'Z'
];
},
- 'arc': function(x, y, w, h, options) {
+ 'arc': function (x, y, w, h, options) {
var start = options.start,
rx = options.r || w,
ry = options.r || h || w,
proximity = 0.001,
fullCircle =
- Math.abs(options.end - options.start - 2 * Math.PI) <
- proximity,
+ Math.abs(options.end - options.start - 2 * Math.PI) <
+ proximity,
// Substract a small number to prevent cos and sin of start and
// end from becoming equal on 360 arcs (related: #1561)
end = options.end - proximity,
@@ -5798,7 +7510,7 @@
* Callout shape used for default tooltips, also used for rounded
* rectangles in VML
*/
- callout: function(x, y, w, h, options) {
+ 'callout': function (x, y, w, h, options) {
var arrowLength = 6,
halfDistance = 6,
r = Math.min((options && options.r) || 0, w, h),
@@ -5827,16 +7539,20 @@
anchorY > y + safeDistance &&
anchorY < y + h - safeDistance
) {
- path.splice(13, 3,
+ path.splice(
+ 13,
+ 3,
'L', x + w, anchorY - halfDistance,
x + w + arrowLength, anchorY,
x + w, anchorY + halfDistance,
x + w, y + h - r
);
- // Simple connector
+ // Simple connector
} else {
- path.splice(13, 3,
+ path.splice(
+ 13,
+ 3,
'L', x + w, h / 2,
anchorX, anchorY,
x + w, h / 2,
@@ -5844,7 +7560,7 @@
);
}
- // Anchor on left side
+ // Anchor on left side
} else if (anchorX && anchorX < 0) {
// Chevron
@@ -5852,16 +7568,20 @@
anchorY > y + safeDistance &&
anchorY < y + h - safeDistance
) {
- path.splice(33, 3,
+ path.splice(
+ 33,
+ 3,
'L', x, anchorY + halfDistance,
x - arrowLength, anchorY,
x, anchorY - halfDistance,
x, y + r
);
- // Simple connector
+ // Simple connector
} else {
- path.splice(33, 3,
+ path.splice(
+ 33,
+ 3,
'L', x, h / 2,
anchorX, anchorY,
x, h / 2,
@@ -5875,7 +7595,9 @@
anchorX > x + safeDistance &&
anchorX < x + w - safeDistance
) {
- path.splice(23, 3,
+ path.splice(
+ 23,
+ 3,
'L', anchorX + halfDistance, y + h,
anchorX, y + h + arrowLength,
anchorX - halfDistance, y + h,
@@ -5888,7 +7610,9 @@
anchorX > x + safeDistance &&
anchorX < x + w - safeDistance
) {
- path.splice(3, 3,
+ path.splice(
+ 3,
+ 3,
'L', anchorX - halfDistance, y,
anchorX, y - arrowLength,
anchorX + halfDistance, y,
@@ -5901,10 +7625,9 @@
},
/**
- * @typedef {SVGElement} ClipRect - A clipping rectangle that can be applied
- * to one or more {@link SVGElement} instances. It is instanciated with the
- * {@link SVGRenderer#clipRect} function and applied with the {@link
- * SVGElement#clip} function.
+ * Define a clipping rectangle. The clipping rectangle is later applied
+ * to {@link SVGElement} objects through the {@link SVGElement#clip}
+ * function.
*
* @example
* var circle = renderer.circle(100, 100, 100)
@@ -5914,29 +7637,23 @@
*
* // Leave only the lower right quarter visible
* circle.clip(clipRect);
- */
- /**
- * Define a clipping rectangle. The clipping rectangle is later applied
- * to {@link SVGElement} objects through the {@link SVGElement#clip}
- * function.
- *
- * @param {String} id
+ *
+ * @function Highcharts.SVGRenderer#clipRect
+ *
+ * @param {string} id
+ *
* @param {number} x
+ *
* @param {number} y
+ *
* @param {number} width
- * @param {number} height
- * @returns {ClipRect} A clipping rectangle.
*
- * @example
- * var circle = renderer.circle(100, 100, 100)
- * .attr({ fill: 'red' })
- * .add();
- * var clipRect = renderer.clipRect(100, 100, 100, 100);
+ * @param {number} height
*
- * // Leave only the lower right quarter visible
- * circle.clip(clipRect);
+ * @return {Highcharts.ClipRectElement}
+ * A clipping rectangle.
*/
- clipRect: function(x, y, width, height) {
+ clipRect: function (x, y, width, height) {
var wrapper,
id = H.uniqueKey(),
@@ -5953,24 +7670,11 @@
},
-
-
-
/**
* Draw text. The text can contain a subset of HTML, like spans and anchors
* and some basic text styling of these. For more advanced features like
* border and background, use {@link Highcharts.SVGRenderer#label} instead.
* To update the text after render, run `text.attr({ text: 'New text' })`.
- * @param {String} str
- * The text of (subset) HTML to draw.
- * @param {number} x
- * The x position of the text's lower left corner.
- * @param {number} y
- * The y position of the text's lower left corner.
- * @param {Boolean} [useHTML=false]
- * Use HTML to render the text.
- *
- * @return {SVGElement} The text object.
*
* @sample highcharts/members/renderer-text-on-chart/
* Annotate the chart freely
@@ -5978,8 +7682,25 @@
* Annotate with a border and in response to the data
* @sample highcharts/members/renderer-text/
* Formatted text
+ *
+ * @function Highcharts.SVGRenderer#text
+ *
+ * @param {string} str
+ * The text of (subset) HTML to draw.
+ *
+ * @param {number} x
+ * The x position of the text's lower left corner.
+ *
+ * @param {number} y
+ * The y position of the text's lower left corner.
+ *
+ * @param {boolean} [useHTML=false]
+ * Use HTML to render the text.
+ *
+ * @return {Highcharts.SVGElement}
+ * The text object.
*/
- text: function(str, x, y, useHTML) {
+ text: function (str, x, y, useHTML) {
// declare variables
var renderer = this,
@@ -5994,7 +7715,7 @@
if (y) {
attribs.y = Math.round(y);
}
- if (str || str === 0) {
+ if (defined(str)) {
attribs.text = str;
}
@@ -6002,11 +7723,12 @@
.attr(attribs);
if (!useHTML) {
- wrapper.xSetter = function(value, key, element) {
+ wrapper.xSetter = function (value, key, element) {
var tspans = element.getElementsByTagName('tspan'),
tspan,
parentVal = element.getAttribute(key),
i;
+
for (i = 0; i < tspans.length; i++) {
tspan = tspans[i];
// If the x values are equal, the tspan represents a
@@ -6026,40 +7748,48 @@
* Utility to return the baseline offset and total line height from the font
* size.
*
- * @param {?string} fontSize The current font size to inspect. If not given,
- * the font size will be found from the DOM element.
- * @param {SVGElement|SVGDOMElement} [elem] The element to inspect for a
- * current font size.
- * @returns {Object} An object containing `h`: the line height, `b`: the
- * baseline relative to the top of the box, and `f`: the font size.
+ * @function Highcharts.SVGRenderer#fontMetrics
+ *
+ * @param {string} [fontSize]
+ * The current font size to inspect. If not given, the font size
+ * will be found from the DOM element.
+ *
+ * @param {Highcharts.SVGElement|Highcharts.SVGDOMElement} [elem]
+ * The element to inspect for a current font size.
+ *
+ * @return {Highcharts.FontMetricsObject}
+ * The font metrics.
*/
- fontMetrics: function(fontSize, elem) {
+ fontMetrics: function (fontSize, elem) {
var lineHeight,
baseline;
-
- fontSize = fontSize ||
- // When the elem is a DOM element (#5932)
- (elem && elem.style && elem.style.fontSize) ||
- // Fall back on the renderer style default
- (this.style && this.style.fontSize);
-
-
+ if (
+ (this.styledMode || !/px/.test(fontSize)) &&
+ win.getComputedStyle // old IE doesn't support it
+ ) {
+ fontSize = elem && SVGElement.prototype.getStyle.call(
+ elem,
+ 'font-size'
+ );
+ } else {
+ fontSize = fontSize ||
+ // When the elem is a DOM element (#5932)
+ (elem && elem.style && elem.style.fontSize) ||
+ // Fall back on the renderer style default
+ (this.style && this.style.fontSize);
+ }
// Handle different units
if (/px/.test(fontSize)) {
fontSize = pInt(fontSize);
- } else if (/em/.test(fontSize)) {
- // The em unit depends on parent items
- fontSize = parseFloat(fontSize) *
- (elem ? this.fontMetrics(null, elem.parentNode).f : 16);
} else {
fontSize = 12;
}
// Empirical values found by comparing font size and bounding box
// height. Applies to the default font family.
- // http://jsfiddle.net/highcharts/7xvn7/
+ // https://jsfiddle.net/highcharts/7xvn7/
lineHeight = fontSize < 24 ? fontSize + 3 : Math.round(fontSize * 1.2);
baseline = Math.round(lineHeight * 0.8);
@@ -6074,9 +7804,17 @@
* Correct X and Y positioning of a label for rotation (#1764).
*
* @private
+ * @function Highcharts.SVGRenderer#rotCorr
+ *
+ * @param {number} baseline
+ *
+ * @param {number} rotation
+ *
+ * @param {boolean} alterY
*/
- rotCorr: function(baseline, rotation, alterY) {
+ rotCorr: function (baseline, rotation, alterY) {
var y = baseline;
+
if (rotation && alterY) {
y = Math.max(y * Math.cos(rotation * deg2rad), 4);
}
@@ -6093,38 +7831,50 @@
* background are set through `stroke`, `stroke-width` and `fill` attributes
* using the {@link Highcharts.SVGElement#attr|attr} method. To update the
* text after render, run `label.attr({ text: 'New text' })`.
- *
- * @param {string} str
- * The initial text string or (subset) HTML to render.
- * @param {number} x
- * The x position of the label's left side.
- * @param {number} y
- * The y position of the label's top side or baseline, depending on
- * the `baseline` parameter.
- * @param {String} shape
- * The shape of the label's border/background, if any. Defaults to
- * `rect`. Other possible values are `callout` or other shapes
- * defined in {@link Highcharts.SVGRenderer#symbols}.
- * @param {number} anchorX
- * In case the `shape` has a pointer, like a flag, this is the
- * coordinates it should be pinned to.
- * @param {number} anchorY
- * In case the `shape` has a pointer, like a flag, this is the
- * coordinates it should be pinned to.
- * @param {Boolean} baseline
- * Whether to position the label relative to the text baseline,
- * like {@link Highcharts.SVGRenderer#text|renderer.text}, or to the
- * upper border of the rectangle.
- * @param {String} className
- * Class name for the group.
- *
- * @return {SVGElement}
- * The generated label.
*
* @sample highcharts/members/renderer-label-on-chart/
* A label on the chart
+ *
+ * @function Highcharts.SVGRenderer#label
+ *
+ * @param {string} str
+ * The initial text string or (subset) HTML to render.
+ *
+ * @param {number} x
+ * The x position of the label's left side.
+ *
+ * @param {number} y
+ * The y position of the label's top side or baseline, depending on
+ * the `baseline` parameter.
+ *
+ * @param {string} [shape='rect']
+ * The shape of the label's border/background, if any. Defaults to
+ * `rect`. Other possible values are `callout` or other shapes
+ * defined in {@link Highcharts.SVGRenderer#symbols}.
+ *
+ * @param {number} [anchorX]
+ * In case the `shape` has a pointer, like a flag, this is the
+ * coordinates it should be pinned to.
+ *
+ * @param {number} [anchorY]
+ * In case the `shape` has a pointer, like a flag, this is the
+ * coordinates it should be pinned to.
+ *
+ * @param {boolean} [useHTML=false]
+ * Wether to use HTML to render the label.
+ *
+ * @param {boolean} [baseline=false]
+ * Whether to position the label relative to the text baseline,
+ * like {@link Highcharts.SVGRenderer#text|renderer.text}, or to the
+ * upper border of the rectangle.
+ *
+ * @param {string} [className]
+ * Class name for the group.
+ *
+ * @return {Highcharts.SVGElement}
+ * The generated label.
*/
- label: function(
+ label: function (
str,
x,
y,
@@ -6137,11 +7887,12 @@
) {
var renderer = this,
+ styledMode = renderer.styledMode,
wrapper = renderer.g(className !== 'button' && 'label'),
text = wrapper.text = renderer.text('', 0, 0, useHTML)
- .attr({
- zIndex: 1
- }),
+ .attr({
+ zIndex: 1
+ }),
box,
bBox,
alignFactor = 0,
@@ -6156,8 +7907,12 @@
strokeWidth,
baselineOffset,
hasBGImage = /^url\((.*?)\)$/.test(shape),
- needsBox = hasBGImage,
- getCrispAdjust,
+ needsBox = styledMode || hasBGImage,
+ getCrispAdjust = function () {
+ return styledMode ?
+ box.strokeWidth() % 2 / 2 :
+ (strokeWidth ? parseInt(strokeWidth, 10) : 0) % 2 / 2;
+ },
updateBoxSize,
updateTextPadding,
boxAttr;
@@ -6166,21 +7921,11 @@
wrapper.addClass('highcharts-' + className);
}
-
- needsBox = hasBGImage;
- getCrispAdjust = function() {
- return (strokeWidth || 0) % 2 / 2;
- };
-
-
-
- /**
- * This function runs after the label is added to the DOM (when the
- * bounding box is available), and after the text of the label is
- * updated to detect the new bounding box and reflect it in the border
- * box.
- */
- updateBoxSize = function() {
+ /* This function runs after the label is added to the DOM (when the
+ bounding box is available), and after the text of the label is
+ updated to detect the new bounding box and reflect it in the border
+ box. */
+ updateBoxSize = function () {
var style = text.element.style,
crispAdjust,
attribs = {};
@@ -6190,6 +7935,7 @@
defined(text.textStr) &&
text.getBBox()
); // #3295 && 3514 box failure when string equals 0
+
wrapper.width = (
(width || bBox.width || 0) +
2 * padding +
@@ -6198,9 +7944,11 @@
wrapper.height = (height || bBox.height || 0) + 2 * padding;
// Update the label-scoped y offset
- baselineOffset = padding +
- renderer.fontMetrics(style && style.fontSize, text).b;
-
+ baselineOffset = padding + Math.min(
+ renderer.fontMetrics(style && style.fontSize, text).b,
+ // Math.min because of inline style (#9400)
+ bBox ? bBox.height : Infinity
+ );
if (needsBox) {
@@ -6232,11 +7980,11 @@
}
};
- /**
+ /*
* This function runs after setting text or padding, but only if padding
- * is changed
+ * is changed.
*/
- updateTextPadding = function() {
+ updateTextPadding = function () {
var textX = paddingLeft + padding,
textY;
@@ -6249,16 +7997,19 @@
bBox &&
(textAlign === 'center' || textAlign === 'right')
) {
- textX += {
- center: 0.5,
- right: 1
- }[textAlign] *
+ textX += { center: 0.5, right: 1 }[textAlign] *
(width - bBox.width);
}
// update if anything changed
if (textX !== text.x || textY !== text.y) {
text.attr('x', textX);
+ // #8159 - prevent misplaced data labels in treemap
+ // (useHTML: true)
+ if (text.hasBoxWidthChanged) {
+ bBox = text.getBBox(true);
+ updateBoxSize();
+ }
if (textY !== undefined) {
text.attr('y', textY);
}
@@ -6269,12 +8020,10 @@
text.y = textY;
};
- /**
+ /*
* Set a box attribute, or defer it if the box is not yet created
- * @param {Object} key
- * @param {Object} value
*/
- boxAttr = function(key, value) {
+ boxAttr = function (key, value) {
if (box) {
box.attr(key, value);
} else {
@@ -6282,11 +8031,11 @@
}
};
- /**
+ /*
* After the text element is added, get the desired size of the border
* box and add it before the text in the DOM.
*/
- wrapper.onAdd = function() {
+ wrapper.onAdd = function () {
text.add(wrapper);
wrapper.attr({
// Alignment is available now (#3295, 0 not rendered if given
@@ -6309,22 +8058,22 @@
*/
// only change local variables
- wrapper.widthSetter = function(value) {
+ wrapper.widthSetter = function (value) {
width = H.isNumber(value) ? value : null; // width:auto => null
};
- wrapper.heightSetter = function(value) {
+ wrapper.heightSetter = function (value) {
height = value;
};
- wrapper['text-alignSetter'] = function(value) {
+ wrapper['text-alignSetter'] = function (value) {
textAlign = value;
};
- wrapper.paddingSetter = function(value) {
+ wrapper.paddingSetter = function (value) {
if (defined(value) && value !== padding) {
padding = wrapper.padding = value;
updateTextPadding();
}
};
- wrapper.paddingLeftSetter = function(value) {
+ wrapper.paddingLeftSetter = function (value) {
if (defined(value) && value !== paddingLeft) {
paddingLeft = value;
updateTextPadding();
@@ -6333,25 +8082,19 @@
// change local variable and prevent setting attribute on the group
- wrapper.alignSetter = function(value) {
- value = {
- left: 0,
- center: 0.5,
- right: 1
- }[value];
+ wrapper.alignSetter = function (value) {
+ value = { left: 0, center: 0.5, right: 1 }[value];
if (value !== alignFactor) {
alignFactor = value;
// Bounding box exists, means we're dynamically changing
if (bBox) {
- wrapper.attr({
- x: wrapperX
- }); // #5134
+ wrapper.attr({ x: wrapperX }); // #5134
}
}
};
// apply these to the box and the text alike
- wrapper.textSetter = function(value) {
+ wrapper.textSetter = function (value) {
if (value !== undefined) {
text.textSetter(value);
}
@@ -6360,7 +8103,7 @@
};
// apply these to the box but not to the text
- wrapper['stroke-widthSetter'] = function(value, key) {
+ wrapper['stroke-widthSetter'] = function (value, key) {
if (value) {
needsBox = true;
}
@@ -6368,9 +8111,14 @@
boxAttr(key, value);
};
- wrapper.strokeSetter =
+ if (styledMode) {
+ wrapper.rSetter = function (value, key) {
+ boxAttr(key, value);
+ };
+ } else {
+ wrapper.strokeSetter =
wrapper.fillSetter =
- wrapper.rSetter = function(value, key) {
+ wrapper.rSetter = function (value, key) {
if (key !== 'r') {
if (key === 'fill' && value) {
needsBox = true;
@@ -6380,59 +8128,73 @@
}
boxAttr(key, value);
};
+ }
- wrapper.anchorXSetter = function(value, key) {
+ wrapper.anchorXSetter = function (value, key) {
anchorX = wrapper.anchorX = value;
boxAttr(key, Math.round(value) - getCrispAdjust() - wrapperX);
};
- wrapper.anchorYSetter = function(value, key) {
+ wrapper.anchorYSetter = function (value, key) {
anchorY = wrapper.anchorY = value;
boxAttr(key, value - wrapperY);
};
// rename attributes
- wrapper.xSetter = function(value) {
+ wrapper.xSetter = function (value) {
wrapper.x = value; // for animation getter
if (alignFactor) {
value -= alignFactor * ((width || bBox.width) + 2 * padding);
+
+ // Force animation even when setting to the same value (#7898)
+ wrapper['forceAnimate:x'] = true;
}
wrapperX = Math.round(value);
wrapper.attr('translateX', wrapperX);
};
- wrapper.ySetter = function(value) {
+ wrapper.ySetter = function (value) {
wrapperY = wrapper.y = Math.round(value);
wrapper.attr('translateY', wrapperY);
};
// Redirect certain methods to either the box or the text
var baseCss = wrapper.css;
- return extend(wrapper, {
+ var wrapperExtension = {
/**
* Pick up some properties and apply them to the text instead of the
* wrapper.
- * @ignore
*/
- css: function(styles) {
+ css: function (styles) {
if (styles) {
var textStyles = {};
+
// Create a copy to avoid altering the original object
// (#537)
styles = merge(styles);
- each(wrapper.textProps, function(prop) {
+ wrapper.textProps.forEach(function (prop) {
if (styles[prop] !== undefined) {
textStyles[prop] = styles[prop];
delete styles[prop];
}
});
text.css(textStyles);
+
+ // Update existing text and box
+ if ('width' in textStyles) {
+ updateBoxSize();
+ }
+
+ // Keep updated (#9400)
+ if ('fontSize' in textStyles) {
+ updateBoxSize();
+ updateTextPadding();
+ }
}
return baseCss.call(wrapper, styles);
},
- /**
+ /*
* Return the bounding box of the box, not the group.
- * @ignore
*/
- getBBox: function() {
+ getBBox: function () {
return {
width: bBox.width + 2 * padding,
height: bBox.height + 2 * padding,
@@ -6440,26 +8202,10 @@
y: bBox.y - padding
};
},
-
- /**
- * Apply the shadow to the box.
- * @ignore
- */
- shadow: function(b) {
- if (b) {
- updateBoxSize();
- if (box) {
- box.shadow(b);
- }
- }
- return wrapper;
- },
-
/**
* Destroy and release memory.
- * @ignore
*/
- destroy: function() {
+ destroy: function () {
// Added by button implementation
removeEvent(wrapper.element, 'mouseenter');
@@ -6476,12 +8222,34 @@
// Release local pointers (#1298)
wrapper =
- renderer =
- updateBoxSize =
- updateTextPadding =
- boxAttr = null;
+ renderer =
+ updateBoxSize =
+ updateTextPadding =
+ boxAttr = null;
}
- });
+ };
+
+ if (!styledMode) {
+ /**
+ * Apply the shadow to the box.
+ *
+ * @ignore
+ * @function Highcharts.SVGElement#shadow
+ *
+ * @return {Highcharts.SVGElement}
+ */
+ wrapperExtension.shadow = function (b) {
+ if (b) {
+ updateBoxSize();
+ if (box) {
+ box.shadow(b);
+ }
+ }
+ return wrapper;
+ };
+ }
+
+ return extend(wrapper, wrapperExtension);
}
}); // end SVGRenderer
@@ -6490,18 +8258,19 @@
H.Renderer = SVGRenderer;
}(Highcharts));
- (function(H) {
+ (function (H) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
- /* eslint max-len: ["warn", 80, 4] */
+
+
+
var attr = H.attr,
createElement = H.createElement,
css = H.css,
defined = H.defined,
- each = H.each,
extend = H.extend,
isFirefox = H.isFirefox,
isMS = H.isMS,
@@ -6510,25 +8279,44 @@
pInt = H.pInt,
SVGElement = H.SVGElement,
SVGRenderer = H.SVGRenderer,
- win = H.win,
- wrap = H.wrap;
+ win = H.win;
- // Extend SvgElement for useHTML option
+ // Extend SvgElement for useHTML option.
extend(SVGElement.prototype, /** @lends SVGElement.prototype */ {
+
/**
* Apply CSS to HTML elements. This is used in text within SVG rendering and
* by the VML renderer
+ *
+ * @private
+ * @function Highcharts.SVGElement#htmlCss
+ *
+ * @param {Highcharts.CSSObject} styles
+ *
+ * @return {Highcharts.SVGElement}
*/
- htmlCss: function(styles) {
+ htmlCss: function (styles) {
var wrapper = this,
element = wrapper.element,
- textWidth = styles && element.tagName === 'SPAN' && styles.width;
+ // When setting or unsetting the width style, we need to update
+ // transform (#8809)
+ isSettingWidth = (
+ element.tagName === 'SPAN' &&
+ styles &&
+ 'width' in styles
+ ),
+ textWidth = pick(
+ isSettingWidth && styles.width,
+ undefined
+ ),
+ doTransform;
- if (textWidth) {
+ if (isSettingWidth) {
delete styles.width;
wrapper.textWidth = textWidth;
- wrapper.updateTransform();
+ doTransform = true;
}
+
if (styles && styles.textOverflow === 'ellipsis') {
styles.whiteSpace = 'nowrap';
styles.overflow = 'hidden';
@@ -6536,18 +8324,28 @@
wrapper.styles = extend(wrapper.styles, styles);
css(wrapper.element, styles);
- return wrapper;
- },
+ // Now that all styles are applied, to the transform
+ if (doTransform) {
+ wrapper.htmlUpdateTransform();
+ }
+
+ return wrapper;
+ },
/**
- * VML and useHTML method for calculating the bounding box based on offsets
- * @param {Boolean} refresh Whether to force a fresh value from the DOM or
- * to use the cached value.
+ * VML and useHTML method for calculating the bounding box based on offsets.
+ *
+ * @private
+ * @function Highcharts.SVGElement#htmlGetBBox
+ *
+ * @param {boolean} refresh
+ * Whether to force a fresh value from the DOM or to use the cached
+ * value.
*
- * @return {Object} A hash containing values for x, y, width and height
+ * @return {Highcharts.BBoxObject}
+ * A hash containing values for x, y, width and height.
*/
-
- htmlGetBBox: function() {
+ htmlGetBBox: function () {
var wrapper = this,
element = wrapper.element;
@@ -6561,9 +8359,12 @@
/**
* VML override private method to update elements based on internal
- * properties based on SVG transform
+ * properties based on SVG transform.
+ *
+ * @private
+ * @function Highcharts.SVGElement#htmlUpdateTransform
*/
- htmlUpdateTransform: function() {
+ htmlUpdateTransform: function () {
// aligning non added elements is expensive
if (!this.added) {
this.alignOnAdd = true;
@@ -6578,12 +8379,19 @@
x = wrapper.x || 0,
y = wrapper.y || 0,
align = wrapper.textAlign || 'left',
- alignCorrection = {
- left: 0,
- center: 0.5,
- right: 1
- }[align],
- styles = wrapper.styles;
+ alignCorrection = { left: 0, center: 0.5, right: 1 }[align],
+ styles = wrapper.styles,
+ whiteSpace = styles && styles.whiteSpace;
+
+ function getTextPxLength() {
+ // Reset multiline/ellipsis in order to read width (#4928,
+ // #5417)
+ css(elem, {
+ width: '',
+ whiteSpace: whiteSpace || 'nowrap'
+ });
+ return elem.offsetWidth;
+ }
// apply translate
css(elem, {
@@ -6591,9 +8399,8 @@
marginTop: translateY
});
-
- if (wrapper.shadows) { // used in labels/tooltip
- each(wrapper.shadows, function(shadow) {
+ if (!renderer.styledMode && wrapper.shadows) { // used in labels/tooltip
+ wrapper.shadows.forEach(function (shadow) {
css(shadow, {
marginLeft: translateX + 1,
marginTop: translateY + 1
@@ -6601,10 +8408,9 @@
});
}
-
// apply inversion
if (wrapper.inverted) { // wrapper is a group
- each(elem.childNodes, function(child) {
+ [].forEach.call(elem.childNodes, function (child) {
renderer.invertChild(child, elem);
});
}
@@ -6613,8 +8419,7 @@
var rotation = wrapper.rotation,
baseline,
- textWidth = pInt(wrapper.textWidth),
- whiteSpace = styles && styles.whiteSpace,
+ textWidth = wrapper.textWidth && pInt(wrapper.textWidth),
currentTextTransform = [
rotation,
align,
@@ -6623,14 +8428,46 @@
wrapper.textAlign
].join(',');
+ // Update textWidth. Use the memoized textPxLength if possible, to
+ // avoid the getTextPxLength function using elem.offsetWidth.
+ // Calling offsetWidth affects rendering time as it forces layout
+ // (#7656).
+ if (
+ textWidth !== wrapper.oldTextWidth &&
+ (
+ (textWidth > wrapper.oldTextWidth) ||
+ (wrapper.textPxLength || getTextPxLength()) > textWidth
+ ) && (
+ // Only set the width if the text is able to word-wrap, or
+ // text-overflow is ellipsis (#9537)
+ /[ \-]/.test(elem.textContent || elem.innerText) ||
+ elem.style.textOverflow === 'ellipsis'
+ )
+ ) { // #983, #1254
+ css(elem, {
+ width: textWidth + 'px',
+ display: 'block',
+ whiteSpace: whiteSpace || 'normal' // #3331
+ });
+ wrapper.oldTextWidth = textWidth;
+ wrapper.hasBoxWidthChanged = true; // #8159
+ } else {
+ wrapper.hasBoxWidthChanged = false; // #8159
+ }
+
// Do the calculations and DOM access only if properties changed
if (currentTextTransform !== wrapper.cTT) {
+ baseline = renderer.fontMetrics(elem.style.fontSize, elem).b;
-
- baseline = renderer.fontMetrics(elem.style.fontSize).b;
-
- // Renderer specific handling of span rotation
- if (defined(rotation)) {
+ // Renderer specific handling of span rotation, but only if we
+ // have something to update.
+ if (
+ defined(rotation) &&
+ (
+ (rotation !== (wrapper.oldRotation || 0)) ||
+ (align !== wrapper.oldAlign)
+ )
+ ) {
wrapper.setSpanRotation(
rotation,
alignCorrection,
@@ -6638,28 +8475,13 @@
);
}
- // Reset multiline/ellipsis in order to read width (#4928,
- // #5417)
- css(elem, {
- width: '',
- whiteSpace: whiteSpace || 'nowrap'
- });
-
- // Update textWidth
- if (
- elem.offsetWidth > textWidth &&
- /[ \-]/.test(elem.textContent || elem.innerText)
- ) { // #983, #1254
- css(elem, {
- width: textWidth + 'px',
- display: 'block',
- whiteSpace: whiteSpace || 'normal' // #3331
- });
- }
-
-
wrapper.getSpanCorrection(
- elem.offsetWidth,
+ // Avoid elem.offsetWidth if we can, it affects rendering
+ // time heavily (#7656)
+ (
+ (!defined(rotation) && wrapper.textPxLength) || // #7920
+ elem.offsetWidth
+ ),
baseline,
alignCorrection,
rotation,
@@ -6673,37 +8495,50 @@
top: (y + (wrapper.yCorr || 0)) + 'px'
});
- // Force reflow in webkit to apply the left and top on useHTML
- // element (#1249)
- if (isWebKit) {
- // Assigned to baseline for lint purpose
- baseline = elem.offsetHeight;
- }
-
// record current text transform
wrapper.cTT = currentTextTransform;
+ wrapper.oldRotation = rotation;
+ wrapper.oldAlign = align;
}
},
/**
- * Set the rotation of an individual HTML span
+ * Set the rotation of an individual HTML span.
+ *
+ * @private
+ * @function Highcharts.SVGElement#setSpanRotation
+ *
+ * @param {number} rotation
+ *
+ * @param {number} alignCorrection
+ *
+ * @param {number} baseline
*/
- setSpanRotation: function(rotation, alignCorrection, baseline) {
+ setSpanRotation: function (rotation, alignCorrection, baseline) {
var rotationStyle = {},
cssTransformKey = this.renderer.getTransformKey();
rotationStyle[cssTransformKey] = rotationStyle.transform =
'rotate(' + rotation + 'deg)';
rotationStyle[cssTransformKey + (isFirefox ? 'Origin' : '-origin')] =
- rotationStyle.transformOrigin =
+ rotationStyle.transformOrigin =
(alignCorrection * 100) + '% ' + baseline + 'px';
css(this.element, rotationStyle);
},
/**
* Get the correction in X and Y positioning as the element is rotated.
+ *
+ * @private
+ * @function Highcharts.SVGElement#getSpanCorrection
+ *
+ * @param {number} width
+ *
+ * @param {number} baseline
+ *
+ * @param {number} alignCorrection
*/
- getSpanCorrection: function(width, baseline, alignCorrection) {
+ getSpanCorrection: function (width, baseline, alignCorrection) {
this.xCorr = -width * alignCorrection;
this.yCorr = -baseline;
}
@@ -6712,55 +8547,74 @@
// Extend SvgRenderer for useHTML option.
extend(SVGRenderer.prototype, /** @lends SVGRenderer.prototype */ {
- getTransformKey: function() {
+ /**
+ * @private
+ * @function Highcharts.SVGRenderer#getTransformKey
+ *
+ * @return {string}
+ */
+ getTransformKey: function () {
return isMS && !/Edge/.test(win.navigator.userAgent) ?
'-ms-transform' :
isWebKit ?
- '-webkit-transform' :
- isFirefox ?
- 'MozTransform' :
- win.opera ?
- '-o-transform' :
- '';
+ '-webkit-transform' :
+ isFirefox ?
+ 'MozTransform' :
+ win.opera ?
+ '-o-transform' :
+ '';
},
/**
* Create HTML text node. This is used by the VML renderer as well as the
* SVG renderer through the useHTML option.
*
- * @param {String} str
- * @param {Number} x
- * @param {Number} y
+ * @private
+ * @function Highcharts.SVGRenderer#html
+ *
+ * @param {string} str
+ * The text of (subset) HTML to draw.
+ *
+ * @param {number} x
+ * The x position of the text's lower left corner.
+ *
+ * @param {number} y
+ * The y position of the text's lower left corner.
+ *
+ * @return {Highcharts.HTMLDOMElement}
*/
- html: function(str, x, y) {
+ html: function (str, x, y) {
var wrapper = this.createElement('span'),
element = wrapper.element,
renderer = wrapper.renderer,
isSVG = renderer.isSVG,
- addSetters = function(element, style) {
+ addSetters = function (element, style) {
// These properties are set as attributes on the SVG group, and
// as identical CSS properties on the div. (#3542)
- each(['opacity', 'visibility'], function(prop) {
- wrap(element, prop + 'Setter', function(
- proceed,
+ ['opacity', 'visibility'].forEach(function (prop) {
+ element[prop + 'Setter'] = function (
value,
key,
elem
) {
- proceed.call(this, value, key, elem);
+ SVGElement.prototype[prop + 'Setter']
+ .call(this, value, key, elem);
style[key] = value;
- });
+ };
});
- };
+ element.addedSetters = true;
+ },
+ chart = H.charts[renderer.chartIndex],
+ styledMode = chart && chart.styledMode;
// Text setter
- wrapper.textSetter = function(value) {
+ wrapper.textSetter = function (value) {
if (value !== element.innerHTML) {
delete this.bBox;
}
this.textStr = value;
element.innerHTML = pick(value, '');
- wrapper.htmlUpdateTransform();
+ wrapper.doTransform = true;
};
// Add setters for the element itself (#4938)
@@ -6770,17 +8624,27 @@
// Various setters which rely on update transform
wrapper.xSetter =
- wrapper.ySetter =
- wrapper.alignSetter =
- wrapper.rotationSetter =
- function(value, key) {
- if (key === 'align') {
- // Do not overwrite the SVGElement.align method. Same as VML.
- key = 'textAlign';
- }
- wrapper[key] = value;
- wrapper.htmlUpdateTransform();
- };
+ wrapper.ySetter =
+ wrapper.alignSetter =
+ wrapper.rotationSetter =
+ function (value, key) {
+ if (key === 'align') {
+ // Do not overwrite the SVGElement.align method. Same as VML.
+ key = 'textAlign';
+ }
+ wrapper[key] = value;
+ wrapper.doTransform = true;
+ };
+
+ // Runs at the end of .attr()
+ wrapper.afterSetters = function () {
+ // Update transform. Do this outside the loop to prevent redundant
+ // updating for batch setting of attributes.
+ if (this.doTransform) {
+ this.htmlUpdateTransform();
+ this.doTransform = false;
+ }
+ };
// Set the default attributes
wrapper
@@ -6790,12 +8654,15 @@
y: Math.round(y)
})
.css({
+ position: 'absolute'
+ });
+ if (!styledMode) {
+ wrapper.css({
fontFamily: this.style.fontFamily,
- fontSize: this.style.fontSize,
-
- position: 'absolute'
+ fontSize: this.style.fontSize
});
+ }
// Keep the whiteSpace style outside the wrapper.styles collection
element.style.whiteSpace = 'nowrap';
@@ -6805,7 +8672,7 @@
// This is specific for HTML within SVG
if (isSVG) {
- wrapper.add = function(svgGroupWrapper) {
+ wrapper.add = function (svgGroupWrapper) {
var htmlGroup,
container = renderer.box.parentNode,
@@ -6832,66 +8699,47 @@
// Ensure dynamically updating position when any parent
// is translated
- each(parents.reverse(), function(parentGroup) {
+ parents.reverse().forEach(function (parentGroup) {
var htmlGroupStyle,
cls = attr(parentGroup.element, 'class');
// Common translate setter for X and Y on the HTML
- // group. Using CSS transform instead of left and
- // right prevents flickering in IE and Edge when
- // moving tooltip (#6957).
+ // group. Reverted the fix for #6957 du to
+ // positioning problems and offline export (#7254,
+ // #7280, #7529)
function translateSetter(value, key) {
parentGroup[key] = value;
- // In IE and Edge, use translate because items
- // would flicker below a HTML tooltip (#6957)
- if (isMS) {
- htmlGroupStyle[renderer.getTransformKey()] =
- 'translate(' + (
- parentGroup.x ||
- parentGroup.translateX
- ) + 'px,' + (
- parentGroup.y ||
- parentGroup.translateY
- ) + 'px)';
-
- // Otherwise, use left and top. Using translate
- // doesn't work well with offline export (#7254,
- // #7280)
+ if (key === 'translateX') {
+ htmlGroupStyle.left = value + 'px';
} else {
- if (key === 'translateX') {
- htmlGroupStyle.left = value + 'px';
- } else {
- htmlGroupStyle.top = value + 'px';
- }
+ htmlGroupStyle.top = value + 'px';
}
parentGroup.doTransform = true;
}
if (cls) {
- cls = {
- className: cls
- };
+ cls = { className: cls };
} // else null
// Create a HTML div and append it to the parent div
// to emulate the SVG group structure
htmlGroup =
- parentGroup.div =
- parentGroup.div || createElement('div', cls, {
- position: 'absolute',
- left: (parentGroup.translateX || 0) + 'px',
- top: (parentGroup.translateY || 0) + 'px',
- display: parentGroup.display,
- opacity: parentGroup.opacity, // #5075
- pointerEvents: (
- parentGroup.styles &&
- parentGroup.styles.pointerEvents
- ) // #5595
-
- // the top group is appended to container
- }, htmlGroup || container);
+ parentGroup.div =
+ parentGroup.div || createElement('div', cls, {
+ position: 'absolute',
+ left: (parentGroup.translateX || 0) + 'px',
+ top: (parentGroup.translateY || 0) + 'px',
+ display: parentGroup.display,
+ opacity: parentGroup.opacity, // #5075
+ pointerEvents: (
+ parentGroup.styles &&
+ parentGroup.styles.pointerEvents
+ ) // #5595
+
+ // the top group is appended to container
+ }, htmlGroup || container);
// Shortcut
htmlGroupStyle = htmlGroup.style;
@@ -6899,15 +8747,21 @@
// Set listeners to update the HTML div's position
// whenever the SVG group position is changed.
extend(parentGroup, {
- classSetter: function(value) {
- this.element.setAttribute('class', value);
- htmlGroup.className = value;
- },
- on: function() {
+ // (#7287) Pass htmlGroup to use
+ // the related group
+ classSetter: (function (htmlGroup) {
+ return function (value) {
+ this.element.setAttribute(
+ 'class',
+ value
+ );
+ htmlGroup.className = value;
+ };
+ }(htmlGroup)),
+ on: function () {
if (parents[0].div) { // #6418
- wrapper.on.apply({
- element: parents[0].div
- },
+ wrapper.on.apply(
+ { element: parents[0].div },
arguments
);
}
@@ -6916,7 +8770,9 @@
translateXSetter: translateSetter,
translateYSetter: translateSetter
});
- addSetters(parentGroup, htmlGroupStyle);
+ if (!parentGroup.addedSetters) {
+ addSetters(parentGroup, htmlGroupStyle);
+ }
});
}
@@ -6940,218 +8796,1341 @@
});
}(Highcharts));
- (function(H) {
+ (function (Highcharts) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
- var color = H.color,
- getTZOffset = H.getTZOffset,
- isTouchDevice = H.isTouchDevice,
+
+ /**
+ * Normalized interval.
+ *
+ * @interface Highcharts.NormalizedIntervalObject
+ *//**
+ * The interval in axis values (ms).
+ *
+ * @name Highcharts.NormalizedIntervalObject#unitRange
+ * @type {number}
+ *//**
+ * The count.
+ *
+ * @name Highcharts.NormalizedIntervalObject#count
+ * @type {number}
+ */
+
+ /**
+ * Function of an additional date format specifier.
+ *
+ * @callback Highcharts.TimeFormatCallbackFunction
+ *
+ * @param {number} timestamp
+ * The time to format.
+ *
+ * @return {string}
+ * The formatted portion of the date.
+ */
+
+ /**
+ * Additonal time tick information.
+ *
+ * @interface Highcharts.TimeTicksInfoObject
+ * @augments Highcharts.NormalizedIntervalObject
+ *//**
+ * @name Highcharts.TimeTicksInfoObject#higherRanks
+ * @type {Array}
+ *//**
+ * @name Highcharts.TimeTicksInfoObject#totalRange
+ * @type {number}
+ */
+
+ /**
+ * Time ticks.
+ *
+ * @interface Highcharts.TimeTicksObject
+ * @augments Array
+ *//**
+ * @name Highcharts.TimeTicksObject#info
+ * @type {Highcharts.TimeTicksInfoObject}
+ */
+
+
+
+ var H = Highcharts,
+ defined = H.defined,
+ extend = H.extend,
merge = H.merge,
pick = H.pick,
- svg = H.svg,
+ timeUnits = H.timeUnits,
win = H.win;
- /* ****************************************************************************
- * Handle the options *
- *****************************************************************************/
- /**
- * @optionparent
+ /**
+ * The Time class. Time settings are applied in general for each page using
+ * `Highcharts.setOptions`, or individually for each Chart item through the
+ * [time](https://api.highcharts.com/highcharts/time) options set.
+ *
+ * The Time object is available from {@link Highcharts.Chart#time},
+ * which refers to `Highcharts.time` if no individual time settings are
+ * applied.
+ *
+ * @example
+ * // Apply time settings globally
+ * Highcharts.setOptions({
+ * time: {
+ * timezone: 'Europe/London'
+ * }
+ * });
+ *
+ * // Apply time settings by instance
+ * var chart = Highcharts.chart('container', {
+ * time: {
+ * timezone: 'America/New_York'
+ * },
+ * series: [{
+ * data: [1, 4, 3, 5]
+ * }]
+ * });
+ *
+ * // Use the Time object
+ * console.log(
+ * 'Current time in New York',
+ * chart.time.dateFormat('%Y-%m-%d %H:%M:%S', Date.now())
+ * );
+ *
+ * @class
+ * @name Highcharts.Time
+ *
+ * @param {Highcharts.TimeOptions} options
+ * Time options as defined in [chart.options.time](/highcharts/time).
+ *
+ * @since 6.0.5
*/
- H.defaultOptions = {
+ Highcharts.Time = function (options) {
+ this.update(options, false);
+ };
+ Highcharts.Time.prototype = {
/**
- * An array containing the default colors for the chart's series. When
- * all colors are used, new colors are pulled from the start again.
- *
- * Default colors can also be set on a series or series.type basis,
- * see [column.colors](#plotOptions.column.colors), [pie.colors](#plotOptions.
- * pie.colors).
- *
- * In styled mode, the colors option doesn't exist. Instead, colors
- * are defined in CSS and applied either through series or point class
- * names, or through the [chart.colorCount](#chart.colorCount) option.
- *
- *
- * ### Legacy
- *
- * In Highcharts 3.x, the default colors were:
- *
- * colors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce',
- * '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']
- *
- * In Highcharts 2.x, the default colors were:
- *
- * colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE',
- * '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92']
- *
- * @type {Array}
- * @sample {highcharts} highcharts/chart/colors/ Assign a global color theme
- * @default ["#7cb5ec", "#434348", "#90ed7d", "#f7a35c", "#8085e9",
- * "#f15c80", "#e4d354", "#2b908f", "#f45b5b", "#91e8e1"]
+ * Time options that can apply globally or to individual charts. These
+ * settings affect how `datetime` axes are laid out, how tooltips are
+ * formatted, how series
+ * [pointIntervalUnit](#plotOptions.series.pointIntervalUnit) works and how
+ * the Highstock range selector handles time.
+ *
+ * The common use case is that all charts in the same Highcharts object
+ * share the same time settings, in which case the global settings are set
+ * using `setOptions`.
+ *
+ * ```js
+ * // Apply time settings globally
+ * Highcharts.setOptions({
+ * time: {
+ * timezone: 'Europe/London'
+ * }
+ * });
+ * // Apply time settings by instance
+ * var chart = Highcharts.chart('container', {
+ * time: {
+ * timezone: 'America/New_York'
+ * },
+ * series: [{
+ * data: [1, 4, 3, 5]
+ * }]
+ * });
+ *
+ * // Use the Time object
+ * console.log(
+ * 'Current time in New York',
+ * chart.time.dateFormat('%Y-%m-%d %H:%M:%S', Date.now())
+ * );
+ * ```
+ *
+ * Since v6.0.5, the time options were moved from the `global` obect to the
+ * `time` object, and time options can be set on each individual chart.
+ *
+ * @sample {highcharts|highstock}
+ * highcharts/time/timezone/
+ * Set the timezone globally
+ * @sample {highcharts}
+ * highcharts/time/individual/
+ * Set the timezone per chart instance
+ * @sample {highstock}
+ * stock/time/individual/
+ * Set the timezone per chart instance
+ *
+ * @since 6.0.5
+ * @apioption time
*/
- colors: '#7cb5ec #434348 #90ed7d #f7a35c #8085e9 #f15c80 #e4d354 #2b908f #f45b5b #91e8e1'.split(' '),
+ /**
+ * Whether to use UTC time for axis scaling, tickmark placement and
+ * time display in `Highcharts.dateFormat`. Advantages of using UTC
+ * is that the time displays equally regardless of the user agent's
+ * time zone settings. Local time can be used when the data is loaded
+ * in real time or when correct Daylight Saving Time transitions are
+ * required.
+ *
+ * @sample {highcharts} highcharts/time/useutc-true/
+ * True by default
+ * @sample {highcharts} highcharts/time/useutc-false/
+ * False
+ *
+ * @type {boolean}
+ * @default true
+ * @apioption time.useUTC
+ */
+ /**
+ * A custom `Date` class for advanced date handling. For example,
+ * [JDate](https://github.com/tahajahangir/jdate) can be hooked in to
+ * handle Jalali dates.
+ *
+ * @type {*}
+ * @since 4.0.4
+ * @product highcharts highstock gantt
+ * @apioption time.Date
+ */
/**
- * Styled mode only. Configuration object for adding SVG definitions for
- * reusable elements. See [gradients, shadows and patterns](http://www.
- * highcharts.com/docs/chart-design-and-style/gradients-shadows-and-
- * patterns) for more information and code examples.
- *
- * @type {Object}
- * @since 5.0.0
- * @apioption defs
+ * A callback to return the time zone offset for a given datetime. It
+ * takes the timestamp in terms of milliseconds since January 1 1970,
+ * and returns the timezone offset in minutes. This provides a hook
+ * for drawing time based charts in specific time zones using their
+ * local DST crossover dates, with the help of external libraries.
+ *
+ * @see [global.timezoneOffset](#global.timezoneOffset)
+ *
+ * @sample {highcharts|highstock} highcharts/time/gettimezoneoffset/
+ * Use moment.js to draw Oslo time regardless of browser locale
+ *
+ * @type {Function}
+ * @since 4.1.0
+ * @product highcharts highstock gantt
+ * @apioption time.getTimezoneOffset
*/
/**
- * @ignore
+ * Requires [moment.js](http://momentjs.com/). If the timezone option
+ * is specified, it creates a default
+ * [getTimezoneOffset](#time.getTimezoneOffset) function that looks
+ * up the specified timezone in moment.js. If moment.js is not included,
+ * this throws a Highcharts error in the console, but does not crash the
+ * chart.
+ *
+ * @see [getTimezoneOffset](#time.getTimezoneOffset)
+ *
+ * @sample {highcharts|highstock} highcharts/time/timezone/
+ * Europe/Oslo
+ *
+ * @type {string}
+ * @since 5.0.7
+ * @product highcharts highstock gantt
+ * @apioption time.timezone
*/
- symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
- lang: {
- /**
- * The loading text that appears when the chart is set into the loading
- * state following a call to `chart.showLoading`.
- *
- * @type {String}
- * @default Loading...
- */
- loading: 'Loading...',
+ /**
+ * The timezone offset in minutes. Positive values are west, negative
+ * values are east of UTC, as in the ECMAScript
+ * [getTimezoneOffset](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset)
+ * method. Use this to display UTC based data in a predefined time zone.
+ *
+ * @see [time.getTimezoneOffset](#time.getTimezoneOffset)
+ *
+ * @sample {highcharts|highstock} highcharts/time/timezoneoffset/
+ * Timezone offset
+ *
+ * @type {number}
+ * @default 0
+ * @since 3.0.8
+ * @product highcharts highstock gantt
+ * @apioption time.timezoneOffset
+ */
- /**
- * An array containing the months names. Corresponds to the `%B` format
- * in `Highcharts.dateFormat()`.
- *
- * @type {Array}
- * @default [ "January" , "February" , "March" , "April" , "May" ,
- * "June" , "July" , "August" , "September" , "October" ,
- * "November" , "December"]
- */
- months: [
- 'January', 'February', 'March', 'April', 'May', 'June', 'July',
- 'August', 'September', 'October', 'November', 'December'
- ],
+ defaultOptions: {},
- /**
- * An array containing the months names in abbreviated form. Corresponds
- * to the `%b` format in `Highcharts.dateFormat()`.
- *
- * @type {Array}
- * @default [ "Jan" , "Feb" , "Mar" , "Apr" , "May" , "Jun" ,
- * "Jul" , "Aug" , "Sep" , "Oct" , "Nov" , "Dec"]
- */
- shortMonths: [
- 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
- 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
- ],
+ /**
+ * Update the Time object with current options. It is called internally on
+ * initializing Highcharts, after running `Highcharts.setOptions` and on
+ * `Chart.update`.
+ *
+ * @private
+ * @function Highcharts.Time#update
+ *
+ * @param {Highcharts.TimeOptions} options
+ */
+ update: function (options) {
+ var useUTC = pick(options && options.useUTC, true),
+ time = this;
- /**
- * An array containing the weekday names.
- *
- * @type {Array}
- * @default ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
- * "Friday", "Saturday"]
- */
- weekdays: [
- 'Sunday', 'Monday', 'Tuesday', 'Wednesday',
- 'Thursday', 'Friday', 'Saturday'
- ],
+ this.options = options = merge(true, this.options || {}, options);
- /**
- * Short week days, starting Sunday. If not specified, Highcharts uses
- * the first three letters of the `lang.weekdays` option.
- *
- * @type {Array}
- * @sample highcharts/lang/shortweekdays/
- * Finnish two-letter abbreviations
- * @since 4.2.4
- * @apioption lang.shortWeekdays
- */
+ // Allow using a different Date class
+ this.Date = options.Date || win.Date || Date;
- /**
- * What to show in a date field for invalid dates. Defaults to an empty
- * string.
- *
- * @type {String}
- * @since 4.1.8
- * @product highcharts highstock
- * @apioption lang.invalidDate
- */
+ this.useUTC = useUTC;
+ this.timezoneOffset = useUTC && options.timezoneOffset;
/**
- * The default decimal point used in the `Highcharts.numberFormat`
- * method unless otherwise specified in the function arguments.
- *
- * @type {String}
- * @default .
- * @since 1.2.2
+ * Get the time zone offset based on the current timezone information as
+ * set in the global options.
+ *
+ * @function Highcharts.Time#getTimezoneOffset
+ *
+ * @param {number} timestamp
+ * The JavaScript timestamp to inspect.
+ *
+ * @return {number}
+ * The timezone offset in minutes compared to UTC.
*/
- decimalPoint: '.',
+ this.getTimezoneOffset = this.timezoneOffsetFunction();
- /**
- * [Metric prefixes](http://en.wikipedia.org/wiki/Metric_prefix) used
- * to shorten high numbers in axis labels. Replacing any of the positions
- * with `null` causes the full number to be written. Setting `numericSymbols`
- * to `null` disables shortening altogether.
- *
- * @type {Array}
- * @sample {highcharts} highcharts/lang/numericsymbols/
- * Replacing the symbols with text
- * @sample {highstock} highcharts/lang/numericsymbols/
- * Replacing the symbols with text
- * @default [ "k" , "M" , "G" , "T" , "P" , "E"]
- * @since 2.3.0
+ /*
+ * The time object has options allowing for variable time zones, meaning
+ * the axis ticks or series data needs to consider this.
*/
- numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'],
+ this.variableTimezone = !!(
+ !useUTC ||
+ options.getTimezoneOffset ||
+ options.timezone
+ );
+
+ // UTC time with timezone handling
+ if (this.variableTimezone || this.timezoneOffset) {
+ this.get = function (unit, date) {
+ var realMs = date.getTime(),
+ ms = realMs - time.getTimezoneOffset(date),
+ ret;
+
+ date.setTime(ms); // Temporary adjust to timezone
+ ret = date['getUTC' + unit]();
+ date.setTime(realMs); // Reset
+
+ return ret;
+ };
+ this.set = function (unit, date, value) {
+ var ms, offset, newOffset;
+
+ // For lower order time units, just set it directly using local
+ // time
+ if (
+ unit === 'Milliseconds' ||
+ unit === 'Seconds' ||
+
+ // If we're dealting with minutes, we only need to
+ // consider timezone if we're in Indian time zones with
+ // half-hour offsets (#8768).
+ (
+ unit === 'Minutes' &&
+ date.getTimezoneOffset() % 60 === 0
+ )
+ ) {
+ date['set' + unit](value);
+
+ // Higher order time units need to take the time zone into
+ // account
+ } else {
+
+ // Adjust by timezone
+ offset = time.getTimezoneOffset(date);
+ ms = date.getTime() - offset;
+ date.setTime(ms);
+
+ date['setUTC' + unit](value);
+ newOffset = time.getTimezoneOffset(date);
+
+ ms = date.getTime() + newOffset;
+ date.setTime(ms);
+ }
+
+ };
+
+ // UTC time with no timezone handling
+ } else if (useUTC) {
+ this.get = function (unit, date) {
+ return date['getUTC' + unit]();
+ };
+ this.set = function (unit, date, value) {
+ return date['setUTC' + unit](value);
+ };
+
+ // Local time
+ } else {
+ this.get = function (unit, date) {
+ return date['get' + unit]();
+ };
+ this.set = function (unit, date, value) {
+ return date['set' + unit](value);
+ };
+ }
+
+ },
+
+ /**
+ * Make a time and returns milliseconds. Interprets the inputs as UTC time,
+ * local time or a specific timezone time depending on the current time
+ * settings.
+ *
+ * @function Highcharts.Time#makeTime
+ *
+ * @param {number} year
+ * The year
+ *
+ * @param {number} month
+ * The month. Zero-based, so January is 0.
+ *
+ * @param {number} [date=1]
+ * The day of the month
+ *
+ * @param {number} [hours=0]
+ * The hour of the day, 0-23.
+ *
+ * @param {number} [minutes=0]
+ * The minutes
+ *
+ * @param {number} [seconds=0]
+ * The seconds
+ *
+ * @return {number}
+ * The time in milliseconds since January 1st 1970.
+ */
+ makeTime: function (year, month, date, hours, minutes, seconds) {
+ var d, offset, newOffset;
+
+ if (this.useUTC) {
+ d = this.Date.UTC.apply(0, arguments);
+ offset = this.getTimezoneOffset(d);
+ d += offset;
+ newOffset = this.getTimezoneOffset(d);
+
+ if (offset !== newOffset) {
+ d += newOffset - offset;
+
+ // A special case for transitioning from summer time to winter time.
+ // When the clock is set back, the same time is repeated twice, i.e.
+ // 02:30 am is repeated since the clock is set back from 3 am to
+ // 2 am. We need to make the same time as local Date does.
+ } else if (
+ offset - 36e5 === this.getTimezoneOffset(d - 36e5) &&
+ !H.isSafari
+ ) {
+ d -= 36e5;
+ }
+
+ } else {
+ d = new this.Date(
+ year,
+ month,
+ pick(date, 1),
+ pick(hours, 0),
+ pick(minutes, 0),
+ pick(seconds, 0)
+ ).getTime();
+ }
+ return d;
+ },
+
+ /**
+ * Sets the getTimezoneOffset function. If the `timezone` option is set, a
+ * default getTimezoneOffset function with that timezone is returned. If
+ * a `getTimezoneOffset` option is defined, it is returned. If neither are
+ * specified, the function using the `timezoneOffset` option or 0 offset is
+ * returned.
+ *
+ * @private
+ * @function Highcharts.Time#timezoneOffsetFunction
+ *
+ * @return {Function}
+ * A getTimezoneOffset function
+ */
+ timezoneOffsetFunction: function () {
+ var time = this,
+ options = this.options,
+ moment = win.moment;
+
+ if (!this.useUTC) {
+ return function (timestamp) {
+ return new Date(timestamp).getTimezoneOffset() * 60000;
+ };
+ }
+
+ if (options.timezone) {
+ if (!moment) {
+ // getTimezoneOffset-function stays undefined because it depends
+ // on Moment.js
+ H.error(25);
+
+ } else {
+ return function (timestamp) {
+ return -moment.tz(
+ timestamp,
+ options.timezone
+ ).utcOffset() * 60000;
+ };
+ }
+ }
+
+ // If not timezone is set, look for the getTimezoneOffset callback
+ if (this.useUTC && options.getTimezoneOffset) {
+ return function (timestamp) {
+ return options.getTimezoneOffset(timestamp) * 60000;
+ };
+ }
+
+ // Last, use the `timezoneOffset` option if set
+ return function () {
+ return (time.timezoneOffset || 0) * 60000;
+ };
+ },
+
+ /**
+ * Formats a JavaScript date timestamp (milliseconds since Jan 1st 1970)
+ * into a human readable date string. The format is a subset of the formats
+ * for PHP's [strftime](http://www.php.net/manual/en/function.strftime.php)
+ * function. Additional formats can be given in the
+ * {@link Highcharts.dateFormats} hook.
+ *
+ * @function Highcharts.Time#dateFormat
+ *
+ * @param {string} [format]
+ * The desired format where various time representations are
+ * prefixed with %.
+ *
+ * @param {number} timestamp
+ * The JavaScript timestamp.
+ *
+ * @param {boolean} [capitalize=false]
+ * Upper case first letter in the return.
+ *
+ * @return {string}
+ * The formatted date.
+ */
+ dateFormat: function (format, timestamp, capitalize) {
+ if (!H.defined(timestamp) || isNaN(timestamp)) {
+ return H.defaultOptions.lang.invalidDate || '';
+ }
+ format = H.pick(format, '%Y-%m-%d %H:%M:%S');
+
+ var time = this,
+ date = new this.Date(timestamp),
+ // get the basic time values
+ hours = this.get('Hours', date),
+ day = this.get('Day', date),
+ dayOfMonth = this.get('Date', date),
+ month = this.get('Month', date),
+ fullYear = this.get('FullYear', date),
+ lang = H.defaultOptions.lang,
+ langWeekdays = lang.weekdays,
+ shortWeekdays = lang.shortWeekdays,
+ pad = H.pad,
+
+ // List all format keys. Custom formats can be added from the
+ // outside.
+ replacements = H.extend(
+ {
+
+ // Day
+ // Short weekday, like 'Mon'
+ 'a': shortWeekdays ?
+ shortWeekdays[day] :
+ langWeekdays[day].substr(0, 3),
+ // Long weekday, like 'Monday'
+ 'A': langWeekdays[day],
+ // Two digit day of the month, 01 to 31
+ 'd': pad(dayOfMonth),
+ // Day of the month, 1 through 31
+ 'e': pad(dayOfMonth, 2, ' '),
+ 'w': day,
+
+ // Week (none implemented)
+ // 'W': weekNumber(),
+
+ // Month
+ // Short month, like 'Jan'
+ 'b': lang.shortMonths[month],
+ // Long month, like 'January'
+ 'B': lang.months[month],
+ // Two digit month number, 01 through 12
+ 'm': pad(month + 1),
+ // Month number, 1 through 12 (#8150)
+ 'o': month + 1,
+
+ // Year
+ // Two digits year, like 09 for 2009
+ 'y': fullYear.toString().substr(2, 2),
+ // Four digits year, like 2009
+ 'Y': fullYear,
+
+ // Time
+ // Two digits hours in 24h format, 00 through 23
+ 'H': pad(hours),
+ // Hours in 24h format, 0 through 23
+ 'k': hours,
+ // Two digits hours in 12h format, 00 through 11
+ 'I': pad((hours % 12) || 12),
+ // Hours in 12h format, 1 through 12
+ 'l': (hours % 12) || 12,
+ // Two digits minutes, 00 through 59
+ 'M': pad(time.get('Minutes', date)),
+ // Upper case AM or PM
+ 'p': hours < 12 ? 'AM' : 'PM',
+ // Lower case AM or PM
+ 'P': hours < 12 ? 'am' : 'pm',
+ // Two digits seconds, 00 through 59
+ 'S': pad(date.getSeconds()),
+ // Milliseconds (naming from Ruby)
+ 'L': pad(Math.floor(timestamp % 1000), 3)
+ },
+
+ /**
+ * A hook for defining additional date format specifiers. New
+ * specifiers are defined as key-value pairs by using the
+ * specifier as key, and a function which takes the timestamp as
+ * value. This function returns the formatted portion of the
+ * date.
+ *
+ * @sample highcharts/global/dateformats/
+ * Adding support for week number
+ *
+ * @name Highcharts.dateFormats
+ * @type {Highcharts.Dictionary}
+ */
+ H.dateFormats
+ );
+
+
+ // Do the replaces
+ H.objectEach(replacements, function (val, key) {
+ // Regex would do it in one line, but this is faster
+ while (format.indexOf('%' + key) !== -1) {
+ format = format.replace(
+ '%' + key,
+ typeof val === 'function' ? val.call(time, timestamp) : val
+ );
+ }
+
+ });
+
+ // Optionally capitalize the string and return
+ return capitalize ?
+ format.substr(0, 1).toUpperCase() + format.substr(1) :
+ format;
+ },
+
+ /**
+ * Resolve legacy formats of dateTimeLabelFormats (strings and arrays) into
+ * an object.
+ * @param {String|Array|Object} f General format description
+ * @return {Object} The object definition
+ */
+ resolveDTLFormat: function (f) {
+ if (!H.isObject(f, true)) {
+ f = H.splat(f);
+ return {
+ main: f[0],
+ from: f[1],
+ to: f[2]
+ };
+ }
+ return f;
+ },
+
+ /**
+ * Return an array with time positions distributed on round time values
+ * right and right after min and max. Used in datetime axes as well as for
+ * grouping data on a datetime axis.
+ *
+ * @function Highcharts.Time#getTimeTicks
+ *
+ * @param {Highcharts.NormalizedIntervalObject} normalizedInterval
+ * The interval in axis values (ms) and the count
+ *
+ * @param {number} [min]
+ * The minimum in axis values
+ *
+ * @param {number} [max]
+ * The maximum in axis values
+ *
+ * @param {number} [startOfWeek=1]
+ *
+ * @return {Highcharts.TimeTicksObject}
+ */
+ getTimeTicks: function (
+ normalizedInterval,
+ min,
+ max,
+ startOfWeek
+ ) {
+ var time = this,
+ Date = time.Date,
+ tickPositions = [],
+ i,
+ higherRanks = {},
+ minYear, // used in months and years as a basis for Date.UTC()
+ // When crossing DST, use the max. Resolves #6278.
+ minDate = new Date(min),
+ interval = normalizedInterval.unitRange,
+ count = normalizedInterval.count || 1,
+ variableDayLength,
+ minDay;
+
+ startOfWeek = pick(startOfWeek, 1);
+
+ if (defined(min)) { // #1300
+ time.set(
+ 'Milliseconds',
+ minDate,
+ interval >= timeUnits.second ?
+ 0 : // #3935
+ count * Math.floor(
+ time.get('Milliseconds', minDate) / count
+ )
+ ); // #3652, #3654
+
+ if (interval >= timeUnits.second) { // second
+ time.set(
+ 'Seconds',
+ minDate,
+ interval >= timeUnits.minute ?
+ 0 : // #3935
+ count * Math.floor(time.get('Seconds', minDate) / count)
+ );
+ }
+
+ if (interval >= timeUnits.minute) { // minute
+ time.set(
+ 'Minutes',
+ minDate,
+ interval >= timeUnits.hour ?
+ 0 :
+ count * Math.floor(time.get('Minutes', minDate) / count)
+ );
+ }
+
+ if (interval >= timeUnits.hour) { // hour
+ time.set(
+ 'Hours',
+ minDate,
+ interval >= timeUnits.day ?
+ 0 :
+ count * Math.floor(
+ time.get('Hours', minDate) / count
+ )
+ );
+ }
+
+ if (interval >= timeUnits.day) { // day
+ time.set(
+ 'Date',
+ minDate,
+ interval >= timeUnits.month ?
+ 1 :
+ Math.max(
+ 1,
+ count * Math.floor(
+ time.get('Date', minDate) / count
+ )
+ )
+ );
+ }
+
+ if (interval >= timeUnits.month) { // month
+ time.set(
+ 'Month',
+ minDate,
+ interval >= timeUnits.year ? 0 :
+ count * Math.floor(time.get('Month', minDate) / count)
+ );
+ minYear = time.get('FullYear', minDate);
+ }
+
+ if (interval >= timeUnits.year) { // year
+ minYear -= minYear % count;
+ time.set('FullYear', minDate, minYear);
+ }
+
+ // week is a special case that runs outside the hierarchy
+ if (interval === timeUnits.week) {
+ // get start of current week, independent of count
+ minDay = time.get('Day', minDate);
+ time.set(
+ 'Date',
+ minDate,
+ (
+ time.get('Date', minDate) -
+ minDay + startOfWeek +
+ // We don't want to skip days that are before
+ // startOfWeek (#7051)
+ (minDay < startOfWeek ? -7 : 0)
+ )
+ );
+ }
+
+
+ // Get basics for variable time spans
+ minYear = time.get('FullYear', minDate);
+ var minMonth = time.get('Month', minDate),
+ minDateDate = time.get('Date', minDate),
+ minHours = time.get('Hours', minDate);
+
+ // Redefine min to the floored/rounded minimum time (#7432)
+ min = minDate.getTime();
+
+ // Handle local timezone offset
+ if (time.variableTimezone) {
+
+ // Detect whether we need to take the DST crossover into
+ // consideration. If we're crossing over DST, the day length may
+ // be 23h or 25h and we need to compute the exact clock time for
+ // each tick instead of just adding hours. This comes at a cost,
+ // so first we find out if it is needed (#4951).
+ variableDayLength = (
+ // Long range, assume we're crossing over.
+ max - min > 4 * timeUnits.month ||
+ // Short range, check if min and max are in different time
+ // zones.
+ time.getTimezoneOffset(min) !== time.getTimezoneOffset(max)
+ );
+ }
+
+ // Iterate and add tick positions at appropriate values
+ var t = minDate.getTime();
+
+ i = 1;
+ while (t < max) {
+ tickPositions.push(t);
+
+ // if the interval is years, use Date.UTC to increase years
+ if (interval === timeUnits.year) {
+ t = time.makeTime(minYear + i * count, 0);
+
+ // if the interval is months, use Date.UTC to increase months
+ } else if (interval === timeUnits.month) {
+ t = 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 (
+ variableDayLength &&
+ (interval === timeUnits.day || interval === timeUnits.week)
+ ) {
+ t = time.makeTime(
+ minYear,
+ minMonth,
+ minDateDate +
+ i * count * (interval === timeUnits.day ? 1 : 7)
+ );
+
+ } else if (
+ variableDayLength &&
+ interval === timeUnits.hour &&
+ count > 1
+ ) {
+ // make sure higher ranks are preserved across DST (#6797,
+ // #7621)
+ t = time.makeTime(
+ minYear,
+ minMonth,
+ minDateDate,
+ minHours + i * count
+ );
+
+ // else, the interval is fixed and we use simple addition
+ } else {
+ t += interval * count;
+ }
+
+ i++;
+ }
+
+ // push the last time
+ tickPositions.push(t);
+
+
+ // Handle higher ranks. Mark new days if the time is on midnight
+ // (#950, #1649, #1760, #3349). Use a reasonable dropout threshold
+ // to prevent looping over dense data grouping (#6156).
+ if (interval <= timeUnits.hour && tickPositions.length < 10000) {
+ tickPositions.forEach(function (t) {
+ if (
+ // Speed optimization, no need to run dateFormat unless
+ // we're on a full or half hour
+ t % 1800000 === 0 &&
+ // Check for local or global midnight
+ time.dateFormat('%H%M%S%L', t) === '000000000'
+ ) {
+ higherRanks[t] = 'day';
+ }
+ });
+ }
+ }
+
+
+ // record information on the chosen unit - for dynamic label formatter
+ tickPositions.info = extend(normalizedInterval, {
+ higherRanks: higherRanks,
+ totalRange: interval * count
+ });
+
+ return tickPositions;
+ }
+
+ }; // end of Time
+
+ }(Highcharts));
+ (function (H) {
+ /**
+ * (c) 2010-2019 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+
+ /**
+ * Gets fired when a series is added to the chart after load time, using the
+ * `addSeries` method. Returning `false` prevents the series from being added.
+ *
+ * @callback Highcharts.ChartAddSeriesCallbackFunction
+ *
+ * @param {Highcharts.Chart} this
+ * The chart on which the event occured.
+ *
+ * @param {Highcharts.ChartAddSeriesEventObject} event
+ * The event that occured.
+ */
+
+ /**
+ * Conaints common event information. Through the `options` property you can
+ * access the series options that were passed to the `addSeries` method.
+ *
+ * @interface Highcharts.ChartAddSeriesEventObject
+ *//**
+ * The series options that were passed to the `addSeries` method.
+ * @name Highcharts.ChartAddSeriesEventObject#options
+ * @type {Highcharts.SeriesOptionsType}
+ *//**
+ * Prevents the default behaviour of the event.
+ * @name Highcharts.ChartAddSeriesEventObject#preventDefault
+ * @type {Function}
+ *//**
+ * The event target.
+ * @name Highcharts.ChartAddSeriesEventObject#target
+ * @type {Highcharts.Chart}
+ *//**
+ * The event type.
+ * @name Highcharts.ChartAddSeriesEventObject#type
+ * @type {"drilldown"}
+ */
+
+ /**
+ * Gets fired when clicking on the plot background.
+ *
+ * @callback Highcharts.ChartClickCallbackFunction
+ *
+ * @param {Highcharts.Chart} this
+ * The chart on which the event occured.
+ *
+ * @param {Highcharts.PointerEventObject} event
+ * The event that occured.
+ */
+
+ /**
+ * Contains an axes of the clicked spot.
+ *
+ * @interface Highcharts.ChartClickEventAxisObject
+ *//**
+ * Axis at the clicked spot.
+ * @name Highcharts.ChartClickEventAxisObject#axis
+ * @type {Highcharts.Axis}
+ *//**
+ * Axis value at the clicked spot.
+ * @name Highcharts.ChartClickEventAxisObject#value
+ * @type {number}
+ */
+
+ /**
+ * Contains information about the clicked spot on the chart. Remember the unit
+ * of a datetime axis is milliseconds since 1970-01-01 00:00:00.
+ *
+ * @interface Highcharts.ChartClickEventObject
+ * @extends Highcharts.PointerEventObject
+ *//**
+ * Information about the x-axis on the clicked spot.
+ * @name Highcharts.ChartClickEventObject#xAxis
+ * @type {Array}
+ *//**
+ * Information about the y-axis on the clicked spot.
+ * @name Highcharts.ChartClickEventObject#yAxis
+ * @type {Array}
+ *//**
+ * Information about the z-axis on the clicked spot.
+ * @name Highcharts.ChartClickEventObject#zAxis
+ * @type {Array|undefined}
+ */
+
+ /**
+ * Gets fired when the chart is finished loading.
+ *
+ * @callback Highcharts.ChartLoadCallbackFunction
+ *
+ * @param {Highcharts.Chart} this
+ * The chart on which the event occured.
+ *
+ * @param {global.Event} event
+ * The event that occured.
+ */
+
+ /**
+ * Fires when the chart is redrawn, either after a call to `chart.redraw()` or
+ * after an axis, series or point is modified with the `redraw` option set to
+ * `true`.
+ *
+ * @callback Highcharts.ChartRedrawCallbackFunction
+ *
+ * @param {Highcharts.Chart} this
+ * The chart on which the event occured.
+ *
+ * @param {global.Event} event
+ * The event that occured.
+ */
+
+ /**
+ * Gets fired after initial load of the chart (directly after the `load` event),
+ * and after each redraw (directly after the `redraw` event).
+ *
+ * @callback Highcharts.ChartRenderCallbackFunction
+ *
+ * @param {Highcharts.Chart} this
+ * The chart on which the event occured.
+ *
+ * @param {global.Event} event
+ * The event that occured.
+ */
+
+ /**
+ * Gets fired when an area of the chart has been selected. The default action
+ * for the selection event is to zoom the chart to the selected area. It can be
+ * prevented by calling `event.preventDefault()` or return false.
+ *
+ * @callback Highcharts.ChartSelectionCallbackFunction
+ *
+ * @param {Highcharts.Chart} this
+ * The chart on which the event occured.
+ *
+ * @param {global.ChartSelectionContextObject} event
+ * Event informations
+ *
+ * @return {boolean|undefined}
+ * Return false to prevent the default action, usually zoom.
+ */
+
+ /**
+ * The primary axes are `xAxis[0]` and `yAxis[0]`. Remember the unit of a
+ * datetime axis is milliseconds since 1970-01-01 00:00:00.
+ *
+ * @interface Highcharts.ChartSelectionContextObject
+ * @extends global.Event
+ *//**
+ * Arrays containing the axes of each dimension and each axis' min and max
+ * values.
+ * @name Highcharts.ChartSelectionContextObject#xAxis
+ * @type {Array}
+ *//**
+ * Arrays containing the axes of each dimension and each axis' min and max
+ * values.
+ * @name Highcharts.ChartSelectionContextObject#yAxis
+ * @type {Array}
+ */
+
+ /**
+ * Axis context of the selection.
+ *
+ * @interface Highcharts.ChartSelectionAxisContextObject
+ *//**
+ * The selected Axis.
+ * @name Highcharts.ChartSelectionAxisContextObject#axis
+ * @type {Highcharts.Axis}
+ *//**
+ * The maximum axis value, either automatic or set manually.
+ * @name Highcharts.ChartSelectionAxisContextObject#max
+ * @type {number}
+ *//**
+ * The minimum axis value, either automatic or set manually.
+ * @name Highcharts.ChartSelectionAxisContextObject#min
+ * @type {number}
+ */
+
+ /**
+ * @interface Highcharts.TooltipFormatterContextObject
+ *//**
+ * @name Highcharts.TooltipFormatterContextObject#color
+ * @type {Highcharts.ColorString}
+ *//**
+ * @name Highcharts.TooltipFormatterContextObject#colorIndex
+ * @type {number|undefined}
+ *//**
+ * @name Highcharts.TooltipFormatterContextObject#key
+ * @type {number}
+ *//**
+ * @name Highcharts.TooltipFormatterContextObject#percentage
+ * @type {number|undefined}
+ *//**
+ * @name Highcharts.TooltipFormatterContextObject#point
+ * @type {Highcharts.Point}
+ *//**
+ * @name Highcharts.TooltipFormatterContextObject#series
+ * @type {Highcharts.Series}
+ *//**
+ * @name Highcharts.TooltipFormatterContextObject#total
+ * @type {number|undefined}
+ *//**
+ * @name Highcharts.TooltipFormatterContextObject#x
+ * @type {number}
+ *//**
+ * @name Highcharts.TooltipFormatterContextObject#y
+ * @type {number}
+ */
+
+
+
+ var color = H.color,
+ isTouchDevice = H.isTouchDevice,
+ merge = H.merge,
+ svg = H.svg;
+
+ /* ****************************************************************************
+ * Handle the options *
+ *****************************************************************************/
+ /**
+ * Global default settings.
+ *
+ * @name Highcharts.defaultOptions
+ * @type {Highcharts.Options}
+ *//**
+ * @optionparent
+ */
+ H.defaultOptions = {
+
+ /**
+ * An array containing the default colors for the chart's series. When
+ * all colors are used, new colors are pulled from the start again.
+ *
+ * Default colors can also be set on a series or series.type basis,
+ * see [column.colors](#plotOptions.column.colors),
+ * [pie.colors](#plotOptions.pie.colors).
+ *
+ * In styled mode, the colors option doesn't exist. Instead, colors
+ * are defined in CSS and applied either through series or point class
+ * names, or through the [chart.colorCount](#chart.colorCount) option.
+ *
+ *
+ * ### Legacy
+ *
+ * In Highcharts 3.x, the default colors were:
+ *
+ * colors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce',
+ * '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']
+ *
+ * In Highcharts 2.x, the default colors were:
+ *
+ * colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE',
+ * '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92']
+ *
+ * @sample {highcharts} highcharts/chart/colors/
+ * Assign a global color theme
+ *
+ * @type {Array}
+ * @default ["#7cb5ec", "#434348", "#90ed7d", "#f7a35c", "#8085e9",
+ * "#f15c80", "#e4d354", "#2b908f", "#f45b5b", "#91e8e1"]
+ */
+ colors: '#7cb5ec #434348 #90ed7d #f7a35c #8085e9 #f15c80 #e4d354 #2b908f #f45b5b #91e8e1'.split(' '),
+
+
+ /**
+ * Styled mode only. Configuration object for adding SVG definitions for
+ * reusable elements. See [gradients, shadows and
+ * patterns](https://www.highcharts.com/docs/chart-design-and-style/gradients-shadows-and-patterns)
+ * for more information and code examples.
+ *
+ * @type {*}
+ * @since 5.0.0
+ * @apioption defs
+ */
+
+ /**
+ * @ignore-option
+ */
+ symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
+
+ /**
+ * The language object is global and it can't be set on each chart
+ * initialization. Instead, use `Highcharts.setOptions` to set it before any
+ * chart is initialized.
+ *
+ * Highcharts.setOptions({
+ * lang: {
+ * months: [
+ * 'Janvier', 'Février', 'Mars', 'Avril',
+ * 'Mai', 'Juin', 'Juillet', 'Août',
+ * 'Septembre', 'Octobre', 'Novembre', 'Décembre'
+ * ],
+ * weekdays: [
+ * 'Dimanche', 'Lundi', 'Mardi', 'Mercredi',
+ * 'Jeudi', 'Vendredi', 'Samedi'
+ * ]
+ * }
+ * });
+ */
+ lang: {
+
+ /**
+ * The loading text that appears when the chart is set into the loading
+ * state following a call to `chart.showLoading`.
+ */
+ loading: 'Loading...',
+
+ /**
+ * An array containing the months names. Corresponds to the `%B` format
+ * in `Highcharts.dateFormat()`.
+ *
+ * @type {Array}
+ * @default ["January", "February", "March", "April", "May", "June",
+ * "July", "August", "September", "October", "November",
+ * "December"]
+ */
+ months: [
+ 'January', 'February', 'March', 'April', 'May', 'June', 'July',
+ 'August', 'September', 'October', 'November', 'December'
+ ],
+
+ /**
+ * An array containing the months names in abbreviated form. Corresponds
+ * to the `%b` format in `Highcharts.dateFormat()`.
+ *
+ * @type {Array}
+ * @default ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ * "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+ */
+ shortMonths: [
+ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
+ 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
+ ],
+
+ /**
+ * An array containing the weekday names.
+ *
+ * @type {Array}
+ * @default ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
+ * "Friday", "Saturday"]
+ */
+ weekdays: [
+ 'Sunday', 'Monday', 'Tuesday', 'Wednesday',
+ 'Thursday', 'Friday', 'Saturday'
+ ],
+
+ /**
+ * Short week days, starting Sunday. If not specified, Highcharts uses
+ * the first three letters of the `lang.weekdays` option.
+ *
+ * @sample highcharts/lang/shortweekdays/
+ * Finnish two-letter abbreviations
+ *
+ * @type {Array}
+ * @since 4.2.4
+ * @apioption lang.shortWeekdays
+ */
+
+ /**
+ * What to show in a date field for invalid dates. Defaults to an empty
+ * string.
+ *
+ * @type {string}
+ * @since 4.1.8
+ * @product highcharts highstock
+ * @apioption lang.invalidDate
+ */
+
+ /**
+ * The title appearing on hovering the zoom in button. The text itself
+ * defaults to "+" and can be changed in the button options.
+ *
+ * @type {string}
+ * @default Zoom in
+ * @product highmaps
+ * @apioption lang.zoomIn
+ */
+
+ /**
+ * The title appearing on hovering the zoom out button. The text itself
+ * defaults to "-" and can be changed in the button options.
+ *
+ * @type {string}
+ * @default Zoom out
+ * @product highmaps
+ * @apioption lang.zoomOut
+ */
+
+ /**
+ * The default decimal point used in the `Highcharts.numberFormat`
+ * method unless otherwise specified in the function arguments.
+ *
+ * @since 1.2.2
+ */
+ decimalPoint: '.',
+
+ /**
+ * [Metric prefixes](http://en.wikipedia.org/wiki/Metric_prefix) used
+ * to shorten high numbers in axis labels. Replacing any of the
+ * positions with `null` causes the full number to be written. Setting
+ * `numericSymbols` to `null` disables shortening altogether.
+ *
+ * @sample {highcharts} highcharts/lang/numericsymbols/
+ * Replacing the symbols with text
+ * @sample {highstock} highcharts/lang/numericsymbols/
+ * Replacing the symbols with text
+ *
+ * @type {Array}
+ * @default ["k", "M", "G", "T", "P", "E"]
+ * @since 2.3.0
+ */
+ numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'],
/**
* The magnitude of [numericSymbols](#lang.numericSymbol) replacements.
* Use 10000 for Japanese, Korean and various Chinese locales, which
* use symbols for 10^4, 10^8 and 10^12.
- *
- * @type {Number}
+ *
* @sample highcharts/lang/numericsymbolmagnitude/
* 10000 magnitude for Japanese
- * @default 1000
- * @since 5.0.3
+ *
+ * @type {number}
+ * @default 1000
+ * @since 5.0.3
* @apioption lang.numericSymbolMagnitude
*/
/**
* The text for the label appearing when a chart is zoomed.
- *
- * @type {String}
- * @default Reset zoom
+ *
* @since 1.2.4
*/
resetZoom: 'Reset zoom',
/**
* The tooltip title for the label appearing when a chart is zoomed.
- *
- * @type {String}
- * @default Reset zoom level 1:1
+ *
* @since 1.2.4
*/
resetZoomTitle: 'Reset zoom level 1:1',
/**
* The default thousands separator used in the `Highcharts.numberFormat`
- * method unless otherwise specified in the function arguments. Since
- * Highcharts 4.1 it defaults to a single space character, which is
- * compatible with ISO and works across Anglo-American and continental
- * European languages.
- *
- * The default is a single space.
- *
- * @type {String}
- * @default
- * @since 1.2.2
+ * method unless otherwise specified in the function arguments. Defaults
+ * to a single space character, which is recommended in
+ * [ISO 31-0](https://en.wikipedia.org/wiki/ISO_31-0#Numbers) and works
+ * across Anglo-American and continental European languages.
+ *
+ * @default \u0020
+ * @since 1.2.2
*/
thousandsSep: ' '
},
@@ -7160,7 +10139,7 @@
* Global options that don't apply to each chart. These options, like
* the `lang` options, must be set using the `Highcharts.setOptions`
* method.
- *
+ *
* Highcharts.setOptions({
* global: {
* useUTC: false
@@ -7168,159 +10147,174 @@
* });
*
*/
- global: {
- /**
- * Whether to use UTC time for axis scaling, tickmark placement and
- * time display in `Highcharts.dateFormat`. Advantages of using UTC
- * is that the time displays equally regardless of the user agent's
- * time zone settings. Local time can be used when the data is loaded
- * in real time or when correct Daylight Saving Time transitions are
- * required.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/global/useutc-true/ True by default
- * @sample {highcharts} highcharts/global/useutc-false/ False
- * @default true
- */
- useUTC: true
+ /**
+ * _Canvg rendering for Android 2.x is removed as of Highcharts 5.0\.
+ * Use the [libURL](#exporting.libURL) option to configure exporting._
+ *
+ * The URL to the additional file to lazy load for Android 2.x devices.
+ * These devices don't support SVG, so we download a helper file that
+ * contains [canvg](http://code.google.com/p/canvg/), its dependency
+ * rbcolor, and our own CanVG Renderer class. To avoid hotlinking to
+ * our site, you can install canvas-tools.js on your own server and
+ * change this option accordingly.
+ *
+ * @deprecated
+ *
+ * @type {string}
+ * @default http://code.highcharts.com/{version}/modules/canvas-tools.js
+ * @product highcharts highmaps
+ * @apioption global.canvasToolsURL
+ */
+
+ /**
+ * This option is deprecated since v6.0.5. Instead, use
+ * [time.useUTC](#time.useUTC) that supports individual time settings
+ * per chart.
+ *
+ * @deprecated
+ *
+ * @type {boolean}
+ * @apioption global.useUTC
+ */
+
+ /**
+ * This option is deprecated since v6.0.5. Instead, use
+ * [time.Date](#time.Date) that supports individual time settings
+ * per chart.
+ *
+ * @deprecated
+ *
+ * @type {Function}
+ * @product highcharts highstock
+ * @apioption global.Date
+ */
+
+ /**
+ * This option is deprecated since v6.0.5. Instead, use
+ * [time.getTimezoneOffset](#time.getTimezoneOffset) that supports
+ * individual time settings per chart.
+ *
+ * @deprecated
+ *
+ * @type {Function}
+ * @product highcharts highstock
+ * @apioption global.getTimezoneOffset
+ */
+
+ /**
+ * This option is deprecated since v6.0.5. Instead, use
+ * [time.timezone](#time.timezone) that supports individual time
+ * settings per chart.
+ *
+ * @deprecated
+ *
+ * @type {string}
+ * @product highcharts highstock
+ * @apioption global.timezone
+ */
+
+ /**
+ * This option is deprecated since v6.0.5. Instead, use
+ * [time.timezoneOffset](#time.timezoneOffset) that supports individual
+ * time settings per chart.
+ *
+ * @deprecated
+ *
+ * @type {number}
+ * @product highcharts highstock
+ * @apioption global.timezoneOffset
+ */
+
+ global: {},
- /**
- * A custom `Date` class for advanced date handling. For example,
- * [JDate](https://githubcom/tahajahangir/jdate) can be hooked in to
- * handle Jalali dates.
- *
- * @type {Object}
- * @since 4.0.4
- * @product highcharts highstock
- * @apioption global.Date
- */
+ time: H.Time.prototype.defaultOptions,
+
+ /**
+ * General options for the chart.
+ */
+ chart: {
/**
- * _Canvg rendering for Android 2.x is removed as of Highcharts 5.0\.
- * Use the [libURL](#exporting.libURL) option to configure exporting._
- *
- * The URL to the additional file to lazy load for Android 2.x devices.
- * These devices don't support SVG, so we download a helper file that
- * contains [canvg](http://code.google.com/p/canvg/), its dependency
- * rbcolor, and our own CanVG Renderer class. To avoid hotlinking to
- * our site, you can install canvas-tools.js on your own server and
- * change this option accordingly.
- *
- * @type {String}
- * @deprecated
- * @default http://code.highcharts.com/{version}/modules/canvas-tools.js
- * @product highcharts highmaps
- * @apioption global.canvasToolsURL
- */
-
- /**
- * A callback to return the time zone offset for a given datetime. It
- * takes the timestamp in terms of milliseconds since January 1 1970,
- * and returns the timezone offset in minutes. This provides a hook
- * for drawing time based charts in specific time zones using their
- * local DST crossover dates, with the help of external libraries.
- *
- * @type {Function}
- * @see [global.timezoneOffset](#global.timezoneOffset)
- * @sample {highcharts} highcharts/global/gettimezoneoffset/
- * Use moment.js to draw Oslo time regardless of browser locale
- * @sample {highstock} highcharts/global/gettimezoneoffset/
- * Use moment.js to draw Oslo time regardless of browser locale
- * @since 4.1.0
- * @product highcharts highstock
- * @apioption global.getTimezoneOffset
+ * Default `mapData` for all series. If set to a string, it functions
+ * as an index into the `Highcharts.maps` array. Otherwise it is
+ * interpreted s map data.
+ *
+ * @see [mapData](#series.map.mapData)
+ *
+ * @type {string|Array<*>}
+ * @since 5.0.0
+ * @product highmaps
+ * @apioption chart.map
*/
/**
- * Requires [moment.js](http://momentjs.com/). If the timezone option
- * is specified, it creates a default
- * [getTimezoneOffset](#global.getTimezoneOffset) function that looks
- * up the specified timezone in moment.js. If moment.js is not included,
- * this throws a Highcharts error in the console, but does not crash the
- * chart.
- *
- * @type {String}
- * @see [getTimezoneOffset](#global.getTimezoneOffset)
- * @sample {highcharts} highcharts/global/timezone/ Europe/Oslo
- * @sample {highstock} highcharts/global/timezone/ Europe/Oslo
- * @default undefined
- * @since 5.0.7
- * @product highcharts highstock
- * @apioption global.timezone
- */
-
- /**
- * The timezone offset in minutes. Positive values are west, negative
- * values are east of UTC, as in the ECMAScript [getTimezoneOffset](https://developer.
- * mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset)
- * method. Use this to display UTC based data in a predefined time zone.
- *
- * @type {Number}
- * @see [global.getTimezoneOffset](#global.getTimezoneOffset)
- * @sample {highcharts} highcharts/global/timezoneoffset/
- * Timezone offset
- * @sample {highstock} highcharts/global/timezoneoffset/
- * Timezone offset
- * @default 0
- * @since 3.0.8
- * @product highcharts highstock
- * @apioption global.timezoneOffset
+ * Set lat/lon transformation definitions for the chart. If not defined,
+ * these are extracted from the map data.
+ *
+ * @type {*}
+ * @since 5.0.0
+ * @product highmaps
+ * @apioption chart.mapTransforms
*/
- },
- chart: {
/**
* When using multiple axis, the ticks of two or more opposite axes
* will automatically be aligned by adding ticks to the axis or axes
* with the least ticks, as if `tickAmount` were specified.
- *
+ *
* This can be prevented by setting `alignTicks` to false. If the grid
* lines look messy, it's a good idea to hide them for the secondary
* axis by setting `gridLineWidth` to 0.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/chart/alignticks-true/ True by default
- * @sample {highcharts} highcharts/chart/alignticks-false/ False
+ *
+ * If `startOnTick` or `endOnTick` in an Axis options are set to false,
+ * then the `alignTicks ` will be disabled for the Axis.
+ *
+ * Disabled for logarithmic axes.
+ *
+ * @sample {highcharts} highcharts/chart/alignticks-true/
+ * True by default
+ * @sample {highcharts} highcharts/chart/alignticks-false/
+ * False
* @sample {highstock} stock/chart/alignticks-true/
* True by default
* @sample {highstock} stock/chart/alignticks-false/
* False
- * @default true
- * @product highcharts highstock
+ *
+ * @type {boolean}
+ * @default true
+ * @product highcharts highstock gantt
* @apioption chart.alignTicks
*/
-
/**
* Set the overall animation for all chart updating. Animation can be
* disabled throughout the chart by setting it to false here. It can
* be overridden for each individual API method as a function parameter.
* The only animation not affected by this option is the initial series
- * animation, see [plotOptions.series.animation](#plotOptions.series.
- * animation).
- *
+ * animation, see [plotOptions.series.animation](
+ * #plotOptions.series.animation).
+ *
* The animation can either be set as a boolean or a configuration
* object. If `true`, it will use the 'swing' jQuery easing and a
* duration of 500 ms. If used as a configuration object, the following
* properties are supported:
- *
+ *
*
- *
+ *
* duration
- *
+ *
* The duration of the animation in milliseconds.
- *
+ *
* easing
- *
- * A string reference to an easing function set on the `Math` object.
- * See [the easing demo](http://jsfiddle.net/gh/get/library/pure/
- * highcharts/highcharts/tree/master/samples/highcharts/plotoptions/
- * series-animation-easing/).
- *
+ *
+ * A string reference to an easing function set on the `Math`
+ * object. See [the easing
+ * demo](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-animation-easing/).
+ *
+ *
*
- *
- * @type {Boolean|Object}
+ *
* @sample {highcharts} highcharts/chart/animation-none/
* Updating with no animation
* @sample {highcharts} highcharts/chart/animation-duration/
@@ -7331,59 +10325,62 @@
* Updating with no animation
* @sample {highmaps} maps/chart/animation-duration/
* With a longer duration
- * @default true
+ *
+ * @type {boolean|Highcharts.AnimationOptionsObject}
+ * @default true
* @apioption chart.animation
*/
/**
* A CSS class name to apply to the charts container `div`, allowing
* unique CSS styling for each chart.
- *
- * @type {String}
+ *
+ * @type {string}
* @apioption chart.className
*/
/**
* Event listeners for the chart.
- *
+ *
* @apioption chart.events
*/
/**
- * Fires when a series is added to the chart after load time, using
- * the `addSeries` method. One parameter, `event`, is passed to the
- * function, containing common event information.
- * Through `event.options` you can access the series options that was
- * passed to the `addSeries` method. Returning false prevents the series
- * from being added.
- *
- * @type {Function}
- * @context Chart
- * @sample {highcharts} highcharts/chart/events-addseries/ Alert on add series
- * @sample {highstock} stock/chart/events-addseries/ Alert on add series
- * @since 1.2.0
+ * Fires when a series is added to the chart after load time, using the
+ * `addSeries` method. One parameter, `event`, is passed to the
+ * function, containing common event information. Through
+ * `event.options` you can access the series options that were passed to
+ * the `addSeries` method. Returning false prevents the series from
+ * being added.
+ *
+ * @sample {highcharts} highcharts/chart/events-addseries/
+ * Alert on add series
+ * @sample {highstock} stock/chart/events-addseries/
+ * Alert on add series
+ *
+ * @type {Highcharts.ChartAddSeriesCallbackFunction}
+ * @since 1.2.0
+ * @context Highcharts.Chart
* @apioption chart.events.addSeries
*/
/**
* Fires when clicking on the plot background. One parameter, `event`,
* is passed to the function, containing common event information.
- *
+ *
* Information on the clicked spot can be found through `event.xAxis`
- * and `event.yAxis`, which are arrays containing the axes of each dimension
- * and each axis' value at the clicked spot. The primary axes are `event.
- * xAxis[0]` and `event.yAxis[0]`. Remember the unit of a datetime axis
- * is milliseconds since 1970-01-01 00:00:00.
- *
+ * and `event.yAxis`, which are arrays containing the axes of each
+ * dimension and each axis' value at the clicked spot. The primary axes
+ * are `event.xAxis[0]` and `event.yAxis[0]`. Remember the unit of a
+ * datetime axis is milliseconds since 1970-01-01 00:00:00.
+ *
* click: function(e) {
* console.log(
* Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', e.xAxis[0].value),
* e.yAxis[0].value
* )
* }
- *
- * @type {Function}
- * @context Chart
+ *
* @sample {highcharts} highcharts/chart/events-click/
* Alert coordinates on click
* @sample {highcharts} highcharts/chart/events-container/
@@ -7396,37 +10393,41 @@
* Record coordinates on click
* @sample {highmaps} highcharts/chart/events-container/
* Alternatively, attach event to container
- * @since 1.2.0
+ *
+ * @type {Highcharts.ChartClickCallbackFunction}
+ * @since 1.2.0
+ * @context Highcharts.Chart
* @apioption chart.events.click
*/
/**
* Fires when the chart is finished loading. Since v4.2.2, it also waits
- * for images to be loaded, for example from point markers. One parameter,
- * `event`, is passed to the function, containing common event information.
- *
+ * for images to be loaded, for example from point markers. One
+ * parameter, `event`, is passed to the function, containing common
+ * event information.
+ *
* There is also a second parameter to the chart constructor where a
* callback function can be passed to be executed on chart.load.
- *
- * @type {Function}
- * @context Chart
+ *
* @sample {highcharts} highcharts/chart/events-load/
* Alert on chart load
* @sample {highstock} stock/chart/events-load/
* Alert on chart load
* @sample {highmaps} maps/chart/events-load/
* Add series on chart load
+ *
+ * @type {Highcharts.ChartLoadCallbackFunction}
+ * @context Highcharts.Chart
* @apioption chart.events.load
*/
/**
- * Fires when the chart is redrawn, either after a call to chart.redraw()
- * or after an axis, series or point is modified with the `redraw` option
- * set to true. One parameter, `event`, is passed to the function, containing common event information.
- *
- * @type {Function}
- * @context Chart
+ * Fires when the chart is redrawn, either after a call to
+ * `chart.redraw()` or after an axis, series or point is modified with
+ * the `redraw` option set to `true`. One parameter, `event`, is passed
+ * to the function, containing common event information.
+ *
* @sample {highcharts} highcharts/chart/events-redraw/
* Alert on chart redraw
* @sample {highstock} stock/chart/events-redraw/
@@ -7434,44 +10435,53 @@
* zoomed range
* @sample {highmaps} maps/chart/events-redraw/
* Set subtitle on chart redraw
- * @since 1.2.0
+ *
+ * @type {Highcharts.ChartRedrawCallbackFunction}
+ * @since 1.2.0
+ * @context Highcharts.Chart
* @apioption chart.events.redraw
*/
/**
* Fires after initial load of the chart (directly after the `load`
* event), and after each redraw (directly after the `redraw` event).
- *
- * @type {Function}
- * @context Chart
- * @since 5.0.7
+ *
+ * @type {Highcharts.ChartRenderCallbackFunction}
+ * @since 5.0.7
+ * @context Highcharts.Chart
* @apioption chart.events.render
*/
/**
- * Fires when an area of the chart has been selected. Selection is enabled
- * by setting the chart's zoomType. One parameter, `event`, is passed
- * to the function, containing common event information. The default action for the selection event is to
- * zoom the chart to the selected area. It can be prevented by calling
- * `event.preventDefault()`.
- *
+ * Fires when an area of the chart has been selected. Selection is
+ * enabled by setting the chart's zoomType. One parameter, `event`, is
+ * passed to the function, containing common event information. The
+ * default action for the selection event is to zoom the chart to the
+ * selected area. It can be prevented by calling
+ * `event.preventDefault()` or return false.
+ *
* Information on the selected area can be found through `event.xAxis`
- * and `event.yAxis`, which are arrays containing the axes of each dimension
- * and each axis' min and max values. The primary axes are `event.xAxis[0]`
- * and `event.yAxis[0]`. Remember the unit of a datetime axis is milliseconds
- * since 1970-01-01 00:00:00.
- *
+ * and `event.yAxis`, which are arrays containing the axes of each
+ * dimension and each axis' min and max values. The primary axes are
+ * `event.xAxis[0]` and `event.yAxis[0]`. Remember the unit of a
+ * datetime axis is milliseconds since 1970-01-01 00:00:00.
+ *
* selection: function(event) {
* // log the min and max of the primary, datetime x-axis
* console.log(
- * Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', event.xAxis[0].min),
- * Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', event.xAxis[0].max)
+ * Highcharts.dateFormat(
+ * '%Y-%m-%d %H:%M:%S',
+ * event.xAxis[0].min
+ * ),
+ * Highcharts.dateFormat(
+ * '%Y-%m-%d %H:%M:%S',
+ * event.xAxis[0].max
+ * )
* );
* // log the min and max of the y axis
* console.log(event.yAxis[0].min, event.yAxis[0].max);
* }
- *
- * @type {Function}
+ *
* @sample {highcharts} highcharts/chart/events-selection/
* Report on selection and reset
* @sample {highcharts} highcharts/chart/events-selection-points/
@@ -7479,7 +10489,10 @@
* @sample {highstock} stock/chart/events-selection/
* Report on selection and reset
* @sample {highstock} highcharts/chart/events-selection-points/
- * Select a range of points through a drag selection (Highcharts)
+ * Select a range of points through a drag selection
+ * (Highcharts)
+ *
+ * @type {Highcharts.ChartSelectionCallbackFunction}
* @apioption chart.events.selection
*/
@@ -7488,19 +10501,18 @@
* The numbers in the array designate top, right, bottom and left
* respectively. Use the options `marginTop`, `marginRight`,
* `marginBottom` and `marginLeft` for shorthand setting of one option.
- *
- * By default there is no margin. The actual space is dynamically calculated
- * from the offset of axis labels, axis title, title, subtitle and legend
- * in addition to the `spacingTop`, `spacingRight`, `spacingBottom`
- * and `spacingLeft` options.
- *
- * @type {Array}
+ *
+ * By default there is no margin. The actual space is dynamically
+ * calculated from the offset of axis labels, axis title, title,
+ * subtitle and legend in addition to the `spacingTop`, `spacingRight`,
+ * `spacingBottom` and `spacingLeft` options.
+ *
* @sample {highcharts} highcharts/chart/margins-zero/
* Zero margins
* @sample {highstock} stock/chart/margin-zero/
* Zero margins
*
- * @defaults {all} null
+ * @type {number|Array}
* @apioption chart.margin
*/
@@ -7508,15 +10520,16 @@
* The margin between the bottom outer edge of the chart and the plot
* area. Use this to set a fixed pixel value for the margin as opposed
* to the default dynamic margin. See also `spacingBottom`.
- *
- * @type {Number}
+ *
* @sample {highcharts} highcharts/chart/marginbottom/
* 100px bottom margin
* @sample {highstock} stock/chart/marginbottom/
* 100px bottom margin
* @sample {highmaps} maps/chart/margin/
* 100px margins
- * @since 2.0
+ *
+ * @type {number}
+ * @since 2.0
* @apioption chart.marginBottom
*/
@@ -7524,16 +10537,16 @@
* The margin between the left outer edge of the chart and the plot
* area. Use this to set a fixed pixel value for the margin as opposed
* to the default dynamic margin. See also `spacingLeft`.
- *
- * @type {Number}
+ *
* @sample {highcharts} highcharts/chart/marginleft/
* 150px left margin
* @sample {highstock} stock/chart/marginleft/
* 150px left margin
* @sample {highmaps} maps/chart/margin/
* 100px margins
- * @default null
- * @since 2.0
+ *
+ * @type {number}
+ * @since 2.0
* @apioption chart.marginLeft
*/
@@ -7541,16 +10554,16 @@
* The margin between the right outer edge of the chart and the plot
* area. Use this to set a fixed pixel value for the margin as opposed
* to the default dynamic margin. See also `spacingRight`.
- *
- * @type {Number}
+ *
* @sample {highcharts} highcharts/chart/marginright/
* 100px right margin
* @sample {highstock} stock/chart/marginright/
* 100px right margin
* @sample {highmaps} maps/chart/margin/
* 100px margins
- * @default null
- * @since 2.0
+ *
+ * @type {number}
+ * @since 2.0
* @apioption chart.marginRight
*/
@@ -7558,15 +10571,15 @@
* The margin between the top outer edge of the chart and the plot area.
* Use this to set a fixed pixel value for the margin as opposed to
* the default dynamic margin. See also `spacingTop`.
- *
- * @type {Number}
+ *
* @sample {highcharts} highcharts/chart/margintop/ 100px top margin
* @sample {highstock} stock/chart/margintop/
* 100px top margin
* @sample {highmaps} maps/chart/margin/
* 100px margins
- * @default null
- * @since 2.0
+ *
+ * @type {number}
+ * @since 2.0
* @apioption chart.marginTop
*/
@@ -7574,71 +10587,100 @@
* Allows setting a key to switch between zooming and panning. Can be
* one of `alt`, `ctrl`, `meta` (the command key on Mac and Windows
* key on Windows) or `shift`. The keys are mapped directly to the key
- * properties of the click event argument (`event.altKey`, `event.ctrlKey`,
- * `event.metaKey` and `event.shiftKey`).
- *
- * @validvalue [null, "alt", "ctrl", "meta", "shift"]
- * @type {String}
- * @since 4.0.3
- * @product highcharts
- * @apioption chart.panKey
+ * properties of the click event argument (`event.altKey`,
+ * `event.ctrlKey`, `event.metaKey` and `event.shiftKey`).
+ *
+ * @type {string}
+ * @since 4.0.3
+ * @product highcharts gantt
+ * @validvalue ["alt", "ctrl", "meta", "shift"]
+ * @apioption chart.panKey
*/
/**
* Allow panning in a chart. Best used with [panKey](#chart.panKey)
* to combine zooming and panning.
- *
- * On touch devices, when the [tooltip.followTouchMove](#tooltip.followTouchMove)
- * option is `true` (default), panning requires two fingers. To allow
- * panning with one finger, set `followTouchMove` to `false`.
- *
- * @type {Boolean}
+ *
+ * On touch devices, when the [tooltip.followTouchMove](
+ * #tooltip.followTouchMove) option is `true` (default), panning
+ * requires two fingers. To allow panning with one finger, set
+ * `followTouchMove` to `false`.
+ *
* @sample {highcharts} highcharts/chart/pankey/ Zooming and panning
- * @default {highcharts} false
- * @default {highstock} true
- * @since 4.0.3
- * @product highcharts highstock
+ *
+ * @type {boolean}
+ * @default {highcharts} false
+ * @default {highstock} true
+ * @since 4.0.3
+ * @product highcharts highstock gantt
* @apioption chart.panning
*/
/**
- * Equivalent to [zoomType](#chart.zoomType), but for multitouch gestures
- * only. By default, the `pinchType` is the same as the `zoomType` setting.
- * However, pinching can be enabled separately in some cases, for example
- * in stock charts where a mouse drag pans the chart, while pinching
- * is enabled. When [tooltip.followTouchMove](#tooltip.followTouchMove)
- * is true, pinchType only applies to two-finger touches.
- *
+ * Equivalent to [zoomType](#chart.zoomType), but for multitouch
+ * gestures only. By default, the `pinchType` is the same as the
+ * `zoomType` setting. However, pinching can be enabled separately in
+ * some cases, for example in stock charts where a mouse drag pans the
+ * chart, while pinching is enabled. When [tooltip.followTouchMove](
+ * #tooltip.followTouchMove) is true, pinchType only applies to
+ * two-finger touches.
+ *
+ * @type {string}
+ * @default {highcharts} undefined
+ * @default {highstock} x
+ * @since 3.0
+ * @product highcharts highstock gantt
* @validvalue ["x", "y", "xy"]
- * @type {String}
- * @default {highcharts} null
- * @default {highstock} x
- * @since 3.0
- * @product highcharts highstock
- * @apioption chart.pinchType
+ * @apioption chart.pinchType
+ */
+
+
+ /**
+ * Whether to apply styled mode. When in styled mode, no presentational
+ * attributes or CSS are applied to the chart SVG. Instead, CSS rules
+ * are required to style the chart. The default style sheet is
+ * available from `https://code.highcharts.com/css/highcharts.css`.
+ *
+ * @type {boolean}
+ * @default false
+ * @since 7.0
+ * @apioption chart.styledMode
*/
+ styledMode: false,
/**
* The corner radius of the outer chart border.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/chart/borderradius/ 20px radius
- * @sample {highstock} stock/chart/border/ 10px radius
- * @sample {highmaps} maps/chart/border/ Border options
- * @default 0
+ *
+ * @sample {highcharts} highcharts/chart/borderradius/
+ * 20px radius
+ * @sample {highstock} stock/chart/border/
+ * 10px radius
+ * @sample {highmaps} maps/chart/border/
+ * Border options
+ *
*/
borderRadius: 0,
+ /**
+ * In styled mode, this sets how many colors the class names
+ * should rotate between. With ten colors, series (or points) are
+ * given class names like `highcharts-color-0`, `highcharts-color-0`
+ * [...] `highcharts-color-9`. The equivalent in non-styled mode
+ * is to set colors using the [colors](#colors) setting.
+ *
+ * @since 5.0.0
+ */
+ colorCount: 10,
/**
* Alias of `type`.
- *
- * @validvalue ["line", "spline", "column", "area", "areaspline", "pie"]
- * @type {String}
+ *
+ * @sample {highcharts} highcharts/chart/defaultseriestype/
+ * Bar
+ *
* @deprecated
- * @sample {highcharts} highcharts/chart/defaultseriestype/ Bar
- * @default line
+ *
* @product highcharts
*/
defaultSeriesType: 'line',
@@ -7649,8 +10691,7 @@
* not affect the axes or the other series. For stacks, once one series
* within the stack is hidden, the rest of the stack will close in
* around it even if the axis is not affected.
- *
- * @type {Boolean}
+ *
* @sample {highcharts} highcharts/chart/ignorehiddenseries-true/
* True by default
* @sample {highcharts} highcharts/chart/ignorehiddenseries-false/
@@ -7661,9 +10702,9 @@
* True by default
* @sample {highstock} stock/chart/ignorehiddenseries-false/
* False
- * @default true
- * @since 1.2.0
- * @product highcharts highstock
+ *
+ * @since 1.2.0
+ * @product highcharts highstock gantt
*/
ignoreHiddenSeries: true,
@@ -7678,269 +10719,130 @@
* automatically. Inverting the chart doesn't have an effect if there
* are no cartesian series in the chart, or if the chart is
* [polar](#chart.polar).
- *
- * @type {Boolean}
+ *
* @sample {highcharts} highcharts/chart/inverted/
* Inverted line
* @sample {highstock} stock/navigator/inverted/
* Inverted stock chart
- * @default false
- * @product highcharts highstock
+ *
+ * @type {boolean}
+ * @default false
+ * @product highcharts highstock gantt
* @apioption chart.inverted
*/
/**
* The distance between the outer edge of the chart and the content,
* like title or legend, or axis title and labels if present. The
- * numbers in the array designate top, right, bottom and left respectively.
- * Use the options spacingTop, spacingRight, spacingBottom and spacingLeft
- * options for shorthand setting of one option.
- *
- * @type {Array}
- * @see [chart.margin](#chart.margin)
+ * numbers in the array designate top, right, bottom and left
+ * respectively. Use the options spacingTop, spacingRight, spacingBottom
+ * and spacingLeft options for shorthand setting of one option.
+ *
+ * @type {Array}
+ * @see [chart.margin](#chart.margin)
* @default [10, 10, 15, 10]
- * @since 3.0.6
+ * @since 3.0.6
*/
spacing: [10, 10, 15, 10],
/**
* The button that appears after a selection zoom, allowing the user
* to reset zoom.
- *
*/
resetZoomButton: {
+ /**
+ * What frame the button should be placed related to. Can be either
+ * `plot` or `chart`
+ *
+ * @sample {highcharts} highcharts/chart/resetzoombutton-relativeto/
+ * Relative to the chart
+ * @sample {highstock} highcharts/chart/resetzoombutton-relativeto/
+ * Relative to the chart
+ *
+ * @type {string}
+ * @default plot
+ * @since 2.2
+ * @validvalue ["plot", "chart"]
+ * @apioption chart.resetZoomButton.relativeTo
+ */
+
/**
* A collection of attributes for the button. The object takes SVG
- * attributes like `fill`, `stroke`, `stroke-width` or `r`, the border
- * radius. The theme also supports `style`, a collection of CSS properties
- * for the text. Equivalent attributes for the hover state are given
- * in `theme.states.hover`.
- *
- * @type {Object}
+ * attributes like `fill`, `stroke`, `stroke-width` or `r`, the
+ * border radius. The theme also supports `style`, a collection of
+ * CSS properties for the text. Equivalent attributes for the hover
+ * state are given in `theme.states.hover`.
+ *
* @sample {highcharts} highcharts/chart/resetzoombutton-theme/
* Theming the button
* @sample {highstock} highcharts/chart/resetzoombutton-theme/
* Theming the button
+ *
+ * @type {Highcharts.SVGAttributes}
* @since 2.2
*/
theme: {
/**
- * The Z index for the reset zoom button.
+ * The Z index for the reset zoom button. The default value
+ * places it below the tooltip that has Z index 7.
*/
- zIndex: 20
+ zIndex: 6
},
/**
* The position of the button.
- *
- * @type {Object}
+ *
* @sample {highcharts} highcharts/chart/resetzoombutton-position/
- * Above the plot area
- * @sample {highstock} highcharts/chart/resetzoombutton-position/
- * Above the plot area
- * @sample {highmaps} highcharts/chart/resetzoombutton-position/
- * Above the plot area
- * @since 2.2
- */
- position: {
-
- /**
- * The horizontal alignment of the button.
- *
- * @type {String}
- */
- align: 'right',
-
- /**
- * The horizontal offset of the button.
- *
- * @type {Number}
- */
- x: -10,
-
- /**
- * The vertical alignment of the button.
- *
- * @validvalue ["top", "middle", "bottom"]
- * @type {String}
- * @default top
- * @apioption chart.resetZoomButton.position.verticalAlign
- */
-
- /**
- * The vertical offset of the button.
- *
- * @type {Number}
- */
- y: 10
- }
-
- /**
- * What frame the button should be placed related to. Can be either
- * `plot` or `chart`
- *
- * @validvalue ["plot", "chart"]
- * @type {String}
- * @sample {highcharts} highcharts/chart/resetzoombutton-relativeto/
- * Relative to the chart
- * @sample {highstock} highcharts/chart/resetzoombutton-relativeto/
- * Relative to the chart
- * @default plot
- * @since 2.2
- * @apioption chart.resetZoomButton.relativeTo
- */
- },
-
- /**
- * An explicit width for the chart. By default (when `null`) the width
- * is calculated from the offset width of the containing element.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/chart/width/ 800px wide
- * @sample {highstock} stock/chart/width/ 800px wide
- * @sample {highmaps} maps/chart/size/ Chart with explicit size
- * @default null
- */
- width: null,
-
- /**
- * An explicit height for the chart. If a _number_, the height is
- * given in pixels. If given a _percentage string_ (for example `'56%'`),
- * the height is given as the percentage of the actual chart width.
- * This allows for preserving the aspect ratio across responsive
- * sizes.
- *
- * By default (when `null`) the height is calculated from the offset
- * height of the containing element, or 400 pixels if the containing
- * element's height is 0.
- *
- * @type {Number|String}
- * @sample {highcharts} highcharts/chart/height/
- * 500px height
- * @sample {highstock} stock/chart/height/
- * 300px height
- * @sample {highmaps} maps/chart/size/
- * Chart with explicit size
- * @sample highcharts/chart/height-percent/
- * Highcharts with percentage height
- * @default null
- */
- height: null,
-
-
-
- /**
- * The color of the outer chart border.
- *
- * @type {Color}
- * @see In styled mode, the stroke is set with the `.highcharts-background`
- * class.
- * @sample {highcharts} highcharts/chart/bordercolor/ Brown border
- * @sample {highstock} stock/chart/border/ Brown border
- * @sample {highmaps} maps/chart/border/ Border options
- * @default #335cad
- */
- borderColor: '#335cad',
-
- /**
- * The pixel width of the outer chart border.
- *
- * @type {Number}
- * @see In styled mode, the stroke is set with the `.highcharts-background`
- * class.
- * @sample {highcharts} highcharts/chart/borderwidth/ 5px border
- * @sample {highstock} stock/chart/border/
- * 2px border
- * @sample {highmaps} maps/chart/border/
- * Border options
- * @default 0
- * @apioption chart.borderWidth
- */
-
- /**
- * The background color or gradient for the outer chart area.
- *
- * @type {Color}
- * @see In styled mode, the background is set with the `.highcharts-background` class.
- * @sample {highcharts} highcharts/chart/backgroundcolor-color/ Color
- * @sample {highcharts} highcharts/chart/backgroundcolor-gradient/ Gradient
- * @sample {highstock} stock/chart/backgroundcolor-color/
- * Color
- * @sample {highstock} stock/chart/backgroundcolor-gradient/
- * Gradient
- * @sample {highmaps} maps/chart/backgroundcolor-color/
- * Color
- * @sample {highmaps} maps/chart/backgroundcolor-gradient/
- * Gradient
- * @default #FFFFFF
- */
- backgroundColor: '#ffffff',
-
- /**
- * The background color or gradient for the plot area.
- *
- * @type {Color}
- * @see In styled mode, the plot background is set with the `.highcharts-plot-background` class.
- * @sample {highcharts} highcharts/chart/plotbackgroundcolor-color/
- * Color
- * @sample {highcharts} highcharts/chart/plotbackgroundcolor-gradient/
- * Gradient
- * @sample {highstock} stock/chart/plotbackgroundcolor-color/
- * Color
- * @sample {highstock} stock/chart/plotbackgroundcolor-gradient/
- * Gradient
- * @sample {highmaps} maps/chart/plotbackgroundcolor-color/
- * Color
- * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/
- * Gradient
- * @default null
- * @apioption chart.plotBackgroundColor
- */
+ * Above the plot area
+ * @sample {highstock} highcharts/chart/resetzoombutton-position/
+ * Above the plot area
+ * @sample {highmaps} highcharts/chart/resetzoombutton-position/
+ * Above the plot area
+ *
+ * @type {Highcharts.AlignObject}
+ * @since 2.2
+ */
+ position: {
+ /**
+ * The horizontal alignment of the button.
+ */
+ align: 'right',
- /**
- * The URL for an image to use as the plot background. To set an image
- * as the background for the entire chart, set a CSS background image
- * to the container element. Note that for the image to be applied to
- * exported charts, its URL needs to be accessible by the export server.
- *
- * @type {String}
- * @see In styled mode, a plot background image can be set with the
- * `.highcharts-plot-background` class and a [custom pattern](http://www.
- * highcharts.com/docs/chart-design-and-style/gradients-shadows-and-
- * patterns).
- * @sample {highcharts} highcharts/chart/plotbackgroundimage/ Skies
- * @sample {highstock} stock/chart/plotbackgroundimage/ Skies
- * @default null
- * @apioption chart.plotBackgroundImage
- */
+ /**
+ * The horizontal offset of the button.
+ */
+ x: -10,
- /**
- * The color of the inner chart or plot area border.
- *
- * @type {Color}
- * @see In styled mode, a plot border stroke can be set with the `.
- * highcharts-plot-border` class.
- * @sample {highcharts} highcharts/chart/plotbordercolor/ Blue border
- * @sample {highstock} stock/chart/plotborder/ Blue border
- * @sample {highmaps} maps/chart/plotborder/ Plot border options
- * @default #cccccc
- */
- plotBorderColor: '#cccccc'
+ /**
+ * The vertical alignment of the button.
+ *
+ * @type {Highcharts.VerticalAlignType}
+ * @default top
+ * @apioption chart.resetZoomButton.position.verticalAlign
+ */
+ /**
+ * The vertical offset of the button.
+ */
+ y: 10
+ }
+ },
/**
* The pixel width of the plot area border.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/chart/plotborderwidth/ 1px border
+ *
+ * @sample {highcharts} highcharts/chart/plotborderwidth/
+ * 1px border
* @sample {highstock} stock/chart/plotborder/
* 2px border
* @sample {highmaps} maps/chart/plotborder/
* Plot border options
- * @default 0
+ *
+ * @type {number}
+ * @default 0
* @apioption chart.plotBorderWidth
*/
@@ -7948,36 +10850,49 @@
* Whether to apply a drop shadow to the plot area. Requires that
* plotBackgroundColor be set. The shadow can be an object configuration
* containing `color`, `offsetX`, `offsetY`, `opacity` and `width`.
- *
- * @type {Boolean|Object}
- * @sample {highcharts} highcharts/chart/plotshadow/ Plot shadow
+ *
+ * @sample {highcharts} highcharts/chart/plotshadow/
+ * Plot shadow
* @sample {highstock} stock/chart/plotshadow/
* Plot shadow
* @sample {highmaps} maps/chart/plotborder/
* Plot border options
- * @default false
+ *
+ * @type {boolean|Highcharts.CSSObject}
+ * @default false
* @apioption chart.plotShadow
*/
/**
* When true, cartesian charts like line, spline, area and column are
- * transformed into the polar coordinate system. Requires `highcharts-
- * more.js`.
- *
- * @type {Boolean}
- * @default false
- * @since 2.3.0
- * @product highcharts
+ * transformed into the polar coordinate system. This produces _polar
+ * charts_, also known as _radar charts_. Requires
+ * `highcharts-more.js`.
+ *
+ * @sample {highcharts} highcharts/demo/polar/
+ * Polar chart
+ * @sample {highcharts} highcharts/demo/polar-wind-rose/
+ * Wind rose, stacked polar column chart
+ * @sample {highcharts} highcharts/demo/polar-spider/
+ * Spider web chart
+ * @sample {highcharts} highcharts/parallel-coordinates/polar/
+ * Star plot, multivariate data in a polar chart
+ *
+ * @type {boolean}
+ * @default false
+ * @since 2.3.0
+ * @product highcharts
* @apioption chart.polar
*/
/**
* Whether to reflow the chart to fit the width of the container div
* on resizing the window.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/chart/reflow-true/ True by default
- * @sample {highcharts} highcharts/chart/reflow-false/ False
+ *
+ * @sample {highcharts} highcharts/chart/reflow-true/
+ * True by default
+ * @sample {highcharts} highcharts/chart/reflow-false/
+ * False
* @sample {highstock} stock/chart/reflow-true/
* True by default
* @sample {highstock} stock/chart/reflow-false/
@@ -7986,21 +10901,19 @@
* True by default
* @sample {highmaps} maps/chart/reflow-false/
* False
- * @default true
- * @since 2.1
+ *
+ * @type {boolean}
+ * @default true
+ * @since 2.1
* @apioption chart.reflow
*/
-
-
-
/**
* The HTML element where the chart will be rendered. If it is a string,
* the element by that id is used. The HTML element can also be passed
- * by direct reference, or as the first argument of the chart constructor,
- * in which case the option is not needed.
- *
- * @type {String|Object}
+ * by direct reference, or as the first argument of the chart
+ * constructor, in which case the option is not needed.
+ *
* @sample {highcharts} highcharts/chart/reflow-true/
* String
* @sample {highcharts} highcharts/chart/renderto-object/
@@ -8013,33 +10926,39 @@
* Object reference
* @sample {highstock} stock/chart/renderto-jquery/
* Object reference through jQuery
+ *
+ * @type {string|Highcharts.HTMLDOMElement}
* @apioption chart.renderTo
*/
/**
* The background color of the marker square when selecting (zooming
* in on) an area of the chart.
- *
- * @type {Color}
+ *
* @see In styled mode, the selection marker fill is set with the
- * `.highcharts-selection-marker` class.
- * @default rgba(51,92,173,0.25)
- * @since 2.1.7
+ * `.highcharts-selection-marker` class.
+ *
+ * @type {Highcharts.ColorString}
+ * @default rgba(51,92,173,0.25)
+ * @since 2.1.7
* @apioption chart.selectionMarkerFill
*/
/**
* Whether to apply a drop shadow to the outer chart area. Requires
- * that backgroundColor be set. The shadow can be an object configuration
- * containing `color`, `offsetX`, `offsetY`, `opacity` and `width`.
- *
- * @type {Boolean|Object}
- * @sample {highcharts} highcharts/chart/shadow/ Shadow
+ * that backgroundColor be set. The shadow can be an object
+ * configuration containing `color`, `offsetX`, `offsetY`, `opacity` and
+ * `width`.
+ *
+ * @sample {highcharts} highcharts/chart/shadow/
+ * Shadow
* @sample {highstock} stock/chart/shadow/
* Shadow
* @sample {highmaps} maps/chart/border/
* Chart border and shadow
- * @default false
+ *
+ * @type {boolean|Highcharts.CSSObject}
+ * @default false
* @apioption chart.shadow
*/
@@ -8047,44 +10966,51 @@
* Whether to show the axes initially. This only applies to empty charts
* where series are added dynamically, as axes are automatically added
* to cartesian series.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/chart/showaxes-false/ False by default
- * @sample {highcharts} highcharts/chart/showaxes-true/ True
- * @since 1.2.5
- * @product highcharts
+ *
+ * @sample {highcharts} highcharts/chart/showaxes-false/
+ * False by default
+ * @sample {highcharts} highcharts/chart/showaxes-true/
+ * True
+ *
+ * @type {boolean}
+ * @since 1.2.5
+ * @product highcharts gantt
* @apioption chart.showAxes
*/
/**
* The space between the bottom edge of the chart and the content (plot
- * area, axis title and labels, title, subtitle or legend in top position).
- *
- * @type {Number}
+ * area, axis title and labels, title, subtitle or legend in top
+ * position).
+ *
* @sample {highcharts} highcharts/chart/spacingbottom/
* Spacing bottom set to 100
* @sample {highstock} stock/chart/spacingbottom/
* Spacing bottom set to 100
* @sample {highmaps} maps/chart/spacing/
* Spacing 100 all around
- * @default 15
- * @since 2.1
+ *
+ * @type {number}
+ * @default 15
+ * @since 2.1
* @apioption chart.spacingBottom
*/
/**
* The space between the left edge of the chart and the content (plot
- * area, axis title and labels, title, subtitle or legend in top position).
- *
- * @type {Number}
+ * area, axis title and labels, title, subtitle or legend in top
+ * position).
+ *
* @sample {highcharts} highcharts/chart/spacingleft/
* Spacing left set to 100
* @sample {highstock} stock/chart/spacingleft/
* Spacing left set to 100
* @sample {highmaps} maps/chart/spacing/
* Spacing 100 all around
- * @default 10
- * @since 2.1
+ *
+ * @type {number}
+ * @default 10
+ * @since 2.1
* @apioption chart.spacingLeft
*/
@@ -8092,8 +11018,7 @@
* The space between the right edge of the chart and the content (plot
* area, axis title and labels, title, subtitle or legend in top
* position).
- *
- * @type {Number}
+ *
* @sample {highcharts} highcharts/chart/spacingright-100/
* Spacing set to 100
* @sample {highcharts} highcharts/chart/spacingright-legend/
@@ -8102,8 +11027,10 @@
* Spacing set to 100
* @sample {highmaps} maps/chart/spacing/
* Spacing 100 all around
- * @default 10
- * @since 2.1
+ *
+ * @type {number}
+ * @default 10
+ * @since 2.1
* @apioption chart.spacingRight
*/
@@ -8111,8 +11038,7 @@
* The space between the top edge of the chart and the content (plot
* area, axis title and labels, title, subtitle or legend in top
* position).
- *
- * @type {Number}
+ *
* @sample {highcharts} highcharts/chart/spacingtop-100/
* A top spacing of 100
* @sample {highcharts} highcharts/chart/spacingtop-10/
@@ -8122,8 +11048,10 @@
* A top spacing of 100
* @sample {highmaps} maps/chart/spacing/
* Spacing 100 all around
- * @default 10
- * @since 2.1
+ *
+ * @type {number}
+ * @default 10
+ * @since 2.1
* @apioption chart.spacingTop
*/
@@ -8131,9 +11059,9 @@
* Additional CSS styles to apply inline to the container `div`. Note
* that since the default font styles are applied in the renderer, it
* is ignorant of the individual chart options and must be set globally.
- *
- * @type {CSSObject}
- * @see In styled mode, general chart styles can be set with the `.highcharts-root` class.
+ *
+ * @see In styled mode, general chart styles can be set with the
+ * `.highcharts-root` class.
* @sample {highcharts} highcharts/chart/style-serif-font/
* Using a serif type font
* @sample {highcharts} highcharts/css/em/
@@ -8142,258 +11070,415 @@
* Using a serif type font
* @sample {highmaps} maps/chart/style-serif-font/
* Using a serif type font
- * @default {"fontFamily":"\"Lucida Grande\", \"Lucida Sans Unicode\", Verdana, Arial, Helvetica, sans-serif","fontSize":"12px"}
+ *
+ * @type {Highcharts.CSSObject}
+ * @default {"fontFamily": "\"Lucida Grande\", \"Lucida Sans Unicode\", Verdana, Arial, Helvetica, sans-serif","fontSize":"12px"}
* @apioption chart.style
*/
/**
* The default series type for the chart. Can be any of the chart types
- * listed under [plotOptions](#plotOptions).
- *
- * @validvalue ["line", "spline", "column", "bar", "area", "areaspline", "pie", "arearange", "areasplinerange", "boxplot", "bubble", "columnrange", "errorbar", "funnel", "gauge", "heatmap", "polygon", "pyramid", "scatter", "solidgauge", "treemap", "waterfall"]
- * @type {String}
- * @sample {highcharts} highcharts/chart/type-bar/ Bar
+ * listed under [plotOptions](#plotOptions) and [series](#series) or can
+ * be a series provided by an additional module.
+ *
+ * In TypeScript this option has no effect in sense of typing and
+ * instead the `type` option must always be set in the series.
+ *
+ * @sample {highcharts} highcharts/chart/type-bar/
+ * Bar
* @sample {highstock} stock/chart/type/
* Areaspline
* @sample {highmaps} maps/chart/type-mapline/
* Mapline
- * @default {highcharts} line
- * @default {highstock} line
- * @default {highmaps} map
- * @since 2.1.0
- * @apioption chart.type
+ *
+ * @type {string}
+ * @default {highcharts} line
+ * @default {highstock} line
+ * @default {highmaps} map
+ * @since 2.1.0
+ * @apioption chart.type
*/
/**
* Decides in what dimensions the user can zoom by dragging the mouse.
* Can be one of `x`, `y` or `xy`.
- *
- * @validvalue [null, "x", "y", "xy"]
- * @type {String}
+ *
* @see [panKey](#chart.panKey)
- * @sample {highcharts} highcharts/chart/zoomtype-none/ None by default
- * @sample {highcharts} highcharts/chart/zoomtype-x/ X
- * @sample {highcharts} highcharts/chart/zoomtype-y/ Y
- * @sample {highcharts} highcharts/chart/zoomtype-xy/ Xy
- * @sample {highstock} stock/demo/basic-line/ None by default
- * @sample {highstock} stock/chart/zoomtype-x/ X
- * @sample {highstock} stock/chart/zoomtype-y/ Y
- * @sample {highstock} stock/chart/zoomtype-xy/ Xy
- * @product highcharts highstock
- * @apioption chart.zoomType
+ *
+ * @sample {highcharts} highcharts/chart/zoomtype-none/
+ * None by default
+ * @sample {highcharts} highcharts/chart/zoomtype-x/
+ * X
+ * @sample {highcharts} highcharts/chart/zoomtype-y/
+ * Y
+ * @sample {highcharts} highcharts/chart/zoomtype-xy/
+ * Xy
+ * @sample {highstock} stock/demo/basic-line/
+ * None by default
+ * @sample {highstock} stock/chart/zoomtype-x/
+ * X
+ * @sample {highstock} stock/chart/zoomtype-y/
+ * Y
+ * @sample {highstock} stock/chart/zoomtype-xy/
+ * Xy
+ *
+ * @type {string}
+ * @product highcharts highstock gantt
+ * @validvalue ["x", "y", "xy"]
+ * @apioption chart.zoomType
*/
- },
- /**
- * The chart's main title.
- *
- * @sample {highmaps} maps/title/title/ Title options demonstrated
- */
- title: {
+ /**
+ * An explicit width for the chart. By default (when `null`) the width
+ * is calculated from the offset width of the containing element.
+ *
+ * @sample {highcharts} highcharts/chart/width/
+ * 800px wide
+ * @sample {highstock} stock/chart/width/
+ * 800px wide
+ * @sample {highmaps} maps/chart/size/
+ * Chart with explicit size
+ *
+ * @type {number|null}
+ */
+ width: null,
/**
- * The title of the chart. To disable the title, set the `text` to
- * `null`.
- *
- * @type {String}
- * @sample {highcharts} highcharts/title/text/ Custom title
- * @sample {highstock} stock/chart/title-text/ Custom title
- * @default {highcharts|highmaps} Chart title
- * @default {highstock} null
+ * An explicit height for the chart. If a _number_, the height is
+ * given in pixels. If given a _percentage string_ (for example
+ * `'56%'`), the height is given as the percentage of the actual chart
+ * width. This allows for preserving the aspect ratio across responsive
+ * sizes.
+ *
+ * By default (when `null`) the height is calculated from the offset
+ * height of the containing element, or 400 pixels if the containing
+ * element's height is 0.
+ *
+ * @sample {highcharts} highcharts/chart/height/
+ * 500px height
+ * @sample {highstock} stock/chart/height/
+ * 300px height
+ * @sample {highmaps} maps/chart/size/
+ * Chart with explicit size
+ * @sample highcharts/chart/height-percent/
+ * Highcharts with percentage height
+ *
+ * @type {number|string|null}
*/
- text: 'Chart title',
+ height: null,
/**
- * The horizontal alignment of the title. Can be one of "left", "center"
- * and "right".
- *
- * @validvalue ["left", "center", "right"]
- * @type {String}
- * @sample {highcharts} highcharts/title/align/ Aligned to the plot area (x = 70px = margin left - spacing left)
- * @sample {highstock} stock/chart/title-align/ Aligned to the plot area (x = 50px = margin left - spacing left)
- * @default center
- * @since 2.0
+ * The color of the outer chart border.
+ *
+ * @see In styled mode, the stroke is set with the
+ * `.highcharts-background` class.
+ *
+ * @sample {highcharts} highcharts/chart/bordercolor/
+ * Brown border
+ * @sample {highstock} stock/chart/border/
+ * Brown border
+ * @sample {highmaps} maps/chart/border/
+ * Border options
+ *
+ * @type {Highcharts.ColorString}
*/
- align: 'center',
+ borderColor: '#335cad',
/**
- * The margin between the title and the plot area, or if a subtitle
- * is present, the margin between the subtitle and the plot area.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/title/margin-50/ A chart title margin of 50
- * @sample {highcharts} highcharts/title/margin-subtitle/ The same margin applied with a subtitle
- * @sample {highstock} stock/chart/title-margin/ A chart title margin of 50
- * @default 15
- * @since 2.1
+ * The pixel width of the outer chart border.
+ *
+ * @see In styled mode, the stroke is set with the
+ * `.highcharts-background` class.
+ *
+ * @sample {highcharts} highcharts/chart/borderwidth/
+ * 5px border
+ * @sample {highstock} stock/chart/border/
+ * 2px border
+ * @sample {highmaps} maps/chart/border/
+ * Border options
+ *
+ * @type {number}
+ * @default 0
+ * @apioption chart.borderWidth
*/
- margin: 15,
/**
- * Adjustment made to the title width, normally to reserve space for
- * the exporting burger menu.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/title/widthadjust/ Wider menu, greater padding
- * @sample {highstock} highcharts/title/widthadjust/ Wider menu, greater padding
- * @sample {highmaps} highcharts/title/widthadjust/ Wider menu, greater padding
- * @default -44
- * @since 4.2.5
+ * The background color or gradient for the outer chart area.
+ *
+ * @see In styled mode, the background is set with the
+ * `.highcharts-background` class.
+ *
+ * @sample {highcharts} highcharts/chart/backgroundcolor-color/
+ * Color
+ * @sample {highcharts} highcharts/chart/backgroundcolor-gradient/
+ * Gradient
+ * @sample {highstock} stock/chart/backgroundcolor-color/
+ * Color
+ * @sample {highstock} stock/chart/backgroundcolor-gradient/
+ * Gradient
+ * @sample {highmaps} maps/chart/backgroundcolor-color/
+ * Color
+ * @sample {highmaps} maps/chart/backgroundcolor-gradient/
+ * Gradient
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
*/
- widthAdjust: -44
+ backgroundColor: '#ffffff',
+
+ /**
+ * The background color or gradient for the plot area.
+ *
+ * @see In styled mode, the plot background is set with the
+ * `.highcharts-plot-background` class.
+ *
+ * @sample {highcharts} highcharts/chart/plotbackgroundcolor-color/
+ * Color
+ * @sample {highcharts} highcharts/chart/plotbackgroundcolor-gradient/
+ * Gradient
+ * @sample {highstock} stock/chart/plotbackgroundcolor-color/
+ * Color
+ * @sample {highstock} stock/chart/plotbackgroundcolor-gradient/
+ * Gradient
+ * @sample {highmaps} maps/chart/plotbackgroundcolor-color/
+ * Color
+ * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/
+ * Gradient
+ *
+ * @type {Highcharts.ColorString}
+ * @apioption chart.plotBackgroundColor
+ */
+
+ /**
+ * The URL for an image to use as the plot background. To set an image
+ * as the background for the entire chart, set a CSS background image
+ * to the container element. Note that for the image to be applied to
+ * exported charts, its URL needs to be accessible by the export server.
+ *
+ * @see In styled mode, a plot background image can be set with the
+ * `.highcharts-plot-background` class and a [custom pattern](
+ * https://www.highcharts.com/docs/chart-design-and-style/
+ * gradients-shadows-and-patterns).
+ *
+ * @sample {highcharts} highcharts/chart/plotbackgroundimage/
+ * Skies
+ * @sample {highstock} stock/chart/plotbackgroundimage/
+ * Skies
+ *
+ * @type {string}
+ * @apioption chart.plotBackgroundImage
+ */
+
+ /**
+ * The color of the inner chart or plot area border.
+ *
+ * @see In styled mode, a plot border stroke can be set with the
+ * `.highcharts-plot-border` class.
+ *
+ * @sample {highcharts} highcharts/chart/plotbordercolor/
+ * Blue border
+ * @sample {highstock} stock/chart/plotborder/
+ * Blue border
+ * @sample {highmaps} maps/chart/plotborder/
+ * Plot border options
+ *
+ * @type {Highcharts.ColorString}
+ */
+ plotBorderColor: '#cccccc'
+
+ },
+
+ /**
+ * The chart's main title.
+ *
+ * @sample {highmaps} maps/title/title/
+ * Title options demonstrated
+ */
+ title: {
/**
* When the title is floating, the plot area will not move to make space
* for it.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/chart/zoomtype-none/ False by default
+ *
+ * @sample {highcharts} highcharts/chart/zoomtype-none/
+ * False by default
* @sample {highcharts} highcharts/title/floating/
* True - title on top of the plot area
* @sample {highstock} stock/chart/title-floating/
* True - title on top of the plot area
- * @default false
- * @since 2.1
+ *
+ * @type {boolean}
+ * @default false
+ * @since 2.1
* @apioption title.floating
*/
/**
* CSS styles for the title. Use this for font styling, but use `align`,
* `x` and `y` for text alignment.
- *
- * In styled mode, the title style is given in the `.highcharts-title` class.
- *
- * @type {CSSObject}
- * @sample {highcharts} highcharts/title/style/ Custom color and weight
- * @sample {highstock} stock/chart/title-style/ Custom color and weight
- * @sample highcharts/css/titles/ Styled mode
- * @default {highcharts|highmaps} { "color": "#333333", "fontSize": "18px" }
- * @default {highstock} { "color": "#333333", "fontSize": "16px" }
+ *
+ * In styled mode, the title style is given in the `.highcharts-title`
+ * class.
+ *
+ * @sample {highcharts} highcharts/title/style/
+ * Custom color and weight
+ * @sample {highstock} stock/chart/title-style/
+ * Custom color and weight
+ * @sample highcharts/css/titles/
+ * Styled mode
+ *
+ * @type {Highcharts.CSSObject}
+ * @default {highcharts|highmaps} { "color": "#333333", "fontSize": "18px" }
+ * @default {highstock} { "color": "#333333", "fontSize": "16px" }
* @apioption title.style
*/
/**
- * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
- * and-string-formatting#html) to render the text.
- *
- * @type {Boolean}
- * @default false
+ * Whether to
+ * [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
+ * to render the text.
+ *
+ * @type {boolean}
+ * @default false
* @apioption title.useHTML
*/
/**
- * The vertical alignment of the title. Can be one of `"top"`, `"middle"`
- * and `"bottom"`. When a value is given, the title behaves as if [floating](#title.
- * floating) were `true`.
- *
- * @validvalue ["top", "middle", "bottom"]
- * @type {String}
+ * The vertical alignment of the title. Can be one of `"top"`,
+ * `"middle"` and `"bottom"`. When a value is given, the title behaves
+ * as if [floating](#title.floating) were `true`.
+ *
* @sample {highcharts} highcharts/title/verticalalign/
* Chart title in bottom right corner
* @sample {highstock} stock/chart/title-verticalalign/
* Chart title in bottom right corner
- * @since 2.1
+ *
+ * @type {Highcharts.VerticalAlignType}
+ * @since 2.1
* @apioption title.verticalAlign
*/
/**
- * The x position of the title relative to the alignment within chart.
- * spacingLeft and chart.spacingRight.
- *
- * @type {Number}
+ * The x position of the title relative to the alignment within
+ * `chart.spacingLeft` and `chart.spacingRight`.
+ *
* @sample {highcharts} highcharts/title/align/
- * Aligned to the plot area (x = 70px = margin left - spacing left)
+ * Aligned to the plot area (x = 70px = margin left - spacing
+ * left)
* @sample {highstock} stock/chart/title-align/
- * Aligned to the plot area (x = 50px = margin left - spacing left)
- * @default 0
- * @since 2.0
+ * Aligned to the plot area (x = 50px = margin left - spacing
+ * left)
+ *
+ * @type {number}
+ * @default 0
+ * @since 2.0
* @apioption title.x
*/
/**
- * The y position of the title relative to the alignment within [chart.
- * spacingTop](#chart.spacingTop) and [chart.spacingBottom](#chart.spacingBottom).
- * By default it depends on the font size.
- *
- * @type {Number}
+ * The y position of the title relative to the alignment within
+ * [chart.spacingTop](#chart.spacingTop) and [chart.spacingBottom](
+ * #chart.spacingBottom). By default it depends on the font size.
+ *
* @sample {highcharts} highcharts/title/y/
* Title inside the plot area
* @sample {highstock} stock/chart/title-verticalalign/
* Chart title in bottom right corner
- * @since 2.0
+ *
+ * @type {number}
+ * @since 2.0
* @apioption title.y
*/
- },
-
- /**
- * The chart's subtitle. This can be used both to display a subtitle below
- * the main title, and to display random text anywhere in the chart. The
- * subtitle can be updated after chart initialization through the
- * `Chart.setTitle` method.
- *
- * @sample {highmaps} maps/title/subtitle/ Subtitle options demonstrated
- */
- subtitle: {
-
/**
- * The subtitle of the chart.
- *
- * @type {String}
- * @sample {highcharts} highcharts/subtitle/text/ Custom subtitle
- * @sample {highcharts} highcharts/subtitle/text-formatted/ Formatted and linked text.
- * @sample {highstock} stock/chart/subtitle-text Custom subtitle
- * @sample {highstock} stock/chart/subtitle-text-formatted Formatted and linked text.
+ * The title of the chart. To disable the title, set the `text` to
+ * `undefined`.
+ *
+ * @sample {highcharts} highcharts/title/text/
+ * Custom title
+ * @sample {highstock} stock/chart/title-text/
+ * Custom title
+ *
+ * @default {highcharts|highmaps} Chart title
+ * @default {highstock} undefined
*/
- text: '',
+ text: 'Chart title',
/**
- * The horizontal alignment of the subtitle. Can be one of "left",
- * "center" and "right".
- *
- * @validvalue ["left", "center", "right"]
- * @type {String}
- * @sample {highcharts} highcharts/subtitle/align/ Footnote at right of plot area
- * @sample {highstock} stock/chart/subtitle-footnote Footnote at bottom right of plot area
- * @default center
+ * The horizontal alignment of the title. Can be one of "left", "center"
+ * and "right".
+ *
+ * @sample {highcharts} highcharts/title/align/
+ * Aligned to the plot area (x = 70px = margin left - spacing
+ * left)
+ * @sample {highstock} stock/chart/title-align/
+ * Aligned to the plot area (x = 50px = margin left - spacing
+ * left)
+ *
+ * @type {Highcharts.AlignType}
* @since 2.0
*/
align: 'center',
/**
- * Adjustment made to the subtitle width, normally to reserve space
- * for the exporting burger menu.
- *
- * @type {Number}
- * @see [title.widthAdjust](#title.widthAdjust)
- * @sample {highcharts} highcharts/title/widthadjust/ Wider menu, greater padding
- * @sample {highstock} highcharts/title/widthadjust/ Wider menu, greater padding
- * @sample {highmaps} highcharts/title/widthadjust/ Wider menu, greater padding
- * @default -44
+ * The margin between the title and the plot area, or if a subtitle
+ * is present, the margin between the subtitle and the plot area.
+ *
+ * @sample {highcharts} highcharts/title/margin-50/
+ * A chart title margin of 50
+ * @sample {highcharts} highcharts/title/margin-subtitle/
+ * The same margin applied with a subtitle
+ * @sample {highstock} stock/chart/title-margin/
+ * A chart title margin of 50
+ *
+ * @since 2.1
+ */
+ margin: 15,
+
+ /**
+ * Adjustment made to the title width, normally to reserve space for
+ * the exporting burger menu.
+ *
+ * @sample highcharts/title/widthadjust/
+ * Wider menu, greater padding
+ *
* @since 4.2.5
*/
widthAdjust: -44
+ },
+
+ /**
+ * The chart's subtitle. This can be used both to display a subtitle below
+ * the main title, and to display random text anywhere in the chart. The
+ * subtitle can be updated after chart initialization through the
+ * `Chart.setTitle` method.
+ *
+ * @sample {highmaps} maps/title/subtitle/
+ * Subtitle options demonstrated
+ */
+ subtitle: {
+
/**
* When the subtitle is floating, the plot area will not move to make
* space for it.
- *
- * @type {Boolean}
+ *
* @sample {highcharts} highcharts/subtitle/floating/
* Floating title and subtitle
* @sample {highstock} stock/chart/subtitle-footnote
* Footnote floating at bottom right of plot area
- * @default false
- * @since 2.1
+ *
+ * @type {boolean}
+ * @default false
+ * @since 2.1
* @apioption subtitle.floating
*/
/**
* CSS styles for the title.
- *
- * In styled mode, the subtitle style is given in the `.highcharts-subtitle` class.
- *
- * @type {CSSObject}
+ *
+ * In styled mode, the subtitle style is given in the
+ * `.highcharts-subtitle` class.
+ *
* @sample {highcharts} highcharts/subtitle/style/
* Custom color and weight
* @sample {highcharts} highcharts/css/titles/
@@ -8404,121 +11489,164 @@
* Styled mode
* @sample {highmaps} highcharts/css/titles/
* Styled mode
- * @default { "color": "#666666" }
+ *
+ * @type {Highcharts.CSSObject}
+ * @default {"color": "#666666"}
* @apioption subtitle.style
*/
/**
- * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
- * and-string-formatting#html) to render the text.
- *
- * @type {Boolean}
- * @default false
+ * Whether to
+ * [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
+ * to render the text.
+ *
+ * @type {boolean}
+ * @default false
* @apioption subtitle.useHTML
*/
/**
* The vertical alignment of the title. Can be one of "top", "middle"
* and "bottom". When a value is given, the title behaves as floating.
- *
- * @validvalue ["top", "middle", "bottom"]
- * @type {String}
+ *
* @sample {highcharts} highcharts/subtitle/verticalalign/
* Footnote at the bottom right of plot area
* @sample {highstock} stock/chart/subtitle-footnote
* Footnote at the bottom right of plot area
- * @default
- * @since 2.1
+ *
+ * @type {Highcharts.VerticalAlignType}
+ * @since 2.1
* @apioption subtitle.verticalAlign
*/
/**
- * The x position of the subtitle relative to the alignment within chart.
- * spacingLeft and chart.spacingRight.
- *
- * @type {Number}
+ * The x position of the subtitle relative to the alignment within
+ * `chart.spacingLeft` and `chart.spacingRight`.
+ *
* @sample {highcharts} highcharts/subtitle/align/
* Footnote at right of plot area
* @sample {highstock} stock/chart/subtitle-footnote
* Footnote at the bottom right of plot area
- * @default 0
- * @since 2.0
+ *
+ * @type {number}
+ * @default 0
+ * @since 2.0
* @apioption subtitle.x
*/
/**
- * The y position of the subtitle relative to the alignment within chart.
- * spacingTop and chart.spacingBottom. By default the subtitle is laid
- * out below the title unless the title is floating.
- *
- * @type {Number}
+ * The y position of the subtitle relative to the alignment within
+ * `chart.spacingTop` and `chart.spacingBottom`. By default the subtitle
+ * is laid out below the title unless the title is floating.
+ *
* @sample {highcharts} highcharts/subtitle/verticalalign/
* Footnote at the bottom right of plot area
* @sample {highstock} stock/chart/subtitle-footnote
* Footnote at the bottom right of plot area
- * @default {highcharts} null
- * @default {highstock} null
- * @default {highmaps}
- * @since 2.0
+ *
+ * @type {number}
+ * @since 2.0
* @apioption subtitle.y
*/
+
+ /**
+ * The subtitle of the chart.
+ *
+ * @sample {highcharts|highstock} highcharts/subtitle/text/
+ * Custom subtitle
+ * @sample {highcharts|highstock} highcharts/subtitle/text-formatted/
+ * Formatted and linked text.
+ */
+ text: '',
+
+ /**
+ * The horizontal alignment of the subtitle. Can be one of "left",
+ * "center" and "right".
+ *
+ * @sample {highcharts} highcharts/subtitle/align/
+ * Footnote at right of plot area
+ * @sample {highstock} stock/chart/subtitle-footnote
+ * Footnote at bottom right of plot area
+ *
+ * @type {Highcharts.AlignType}
+ * @since 2.0
+ */
+ align: 'center',
+
+ /**
+ * Adjustment made to the subtitle width, normally to reserve space
+ * for the exporting burger menu.
+ *
+ * @see [title.widthAdjust](#title.widthAdjust)
+ *
+ * @sample highcharts/title/widthadjust/
+ * Wider menu, greater padding
+ *
+ * @since 4.2.5
+ */
+ widthAdjust: -44
},
/**
* The plotOptions is a wrapper object for config objects for each series
* type. The config objects for each series can also be overridden for
* each series item as given in the series array.
- *
- * Configuration options for the series are given in three levels. Options
- * for all series in a chart are given in the [plotOptions.series](#plotOptions.
- * series) object. Then options for all series of a specific type are
- * given in the plotOptions of that type, for example plotOptions.line.
- * Next, options for one single series are given in [the series array](#series).
*
+ * Configuration options for the series are given in three levels. Options
+ * for all series in a chart are given in the [plotOptions.series](
+ * #plotOptions.series) object. Then options for all series of a specific
+ * type are given in the plotOptions of that type, for example
+ * `plotOptions.line`. Next, options for one single series are given in
+ * [the series array](#series).
*/
plotOptions: {},
/**
* HTML labels that can be positioned anywhere in the chart area.
- *
*/
labels: {
/**
- * A HTML label that can be positioned anywhere in the chart area.
- *
- * @type {Array}
+ * An HTML label that can be positioned anywhere in the chart area.
+ *
+ * @type {Array<*>}
* @apioption labels.items
*/
/**
* Inner HTML or text for the label.
- *
- * @type {String}
+ *
+ * @type {string}
* @apioption labels.items.html
*/
/**
* CSS styles for each label. To position the label, use left and top
* like this:
- *
+ *
* style: {
* left: '100px',
* top: '100px'
* }
- *
- * @type {CSSObject}
+ *
+ * @type {Highcharts.CSSObject}
* @apioption labels.items.style
*/
/**
* Shared CSS styles for all labels.
- *
- * @type {CSSObject}
- * @default { "color": "#333333" }
+ *
+ * @type {Highcharts.CSSObject}
+ * @default {"color": "#333333", "position": "absolute"}
*/
style: {
+ /**
+ * @ignore
+ */
position: 'absolute',
+ /**
+ * @ignore
+ */
color: '#333333'
}
},
@@ -8527,11 +11655,10 @@
* The legend is a box containing a symbol and name for each series
* item or point item in the chart. Each series (or points in case
* of pie charts) is represented by a symbol and its name in the legend.
- *
- * It is possible to override the symbol creator function and
- * create [custom legend symbols](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/studies/legend-
- * custom-symbol/).
- *
+ *
+ * It is possible to override the symbol creator function and create
+ * [custom legend symbols](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/studies/legend-custom-symbol/).
+ *
* @productdesc {highmaps}
* A Highmaps legend by default contains one legend item per series, but if
* a `colorAxis` is defined, the axis will be displayed in the legend.
@@ -8541,146 +11668,196 @@
/**
* The background color of the legend.
- *
- * @type {Color}
+ *
* @see In styled mode, the legend background fill can be applied with
- * the `.highcharts-legend-box` class.
- * @sample {highcharts} highcharts/legend/backgroundcolor/ Yellowish background
- * @sample {highstock} stock/legend/align/ Various legend options
- * @sample {highmaps} maps/legend/border-background/ Border and background options
+ * the `.highcharts-legend-box` class.
+ *
+ * @sample {highcharts} highcharts/legend/backgroundcolor/
+ * Yellowish background
+ * @sample {highstock} stock/legend/align/
+ * Various legend options
+ * @sample {highmaps} maps/legend/border-background/
+ * Border and background options
+ *
+ * @type {Highcharts.ColorString}
* @apioption legend.backgroundColor
*/
/**
* The width of the drawn border around the legend.
- *
- * @type {Number}
+ *
* @see In styled mode, the legend border stroke width can be applied
- * with the `.highcharts-legend-box` class.
- * @sample {highcharts} highcharts/legend/borderwidth/ 2px border width
- * @sample {highstock} stock/legend/align/ Various legend options
- * @sample {highmaps} maps/legend/border-background/ Border and background options
- * @default 0
+ * with the `.highcharts-legend-box` class.
+ *
+ * @sample {highcharts} highcharts/legend/borderwidth/
+ * 2px border width
+ * @sample {highstock} stock/legend/align/
+ * Various legend options
+ * @sample {highmaps} maps/legend/border-background/
+ * Border and background options
+ *
+ * @type {number}
+ * @default 0
* @apioption legend.borderWidth
*/
/**
- * Enable or disable the legend.
- *
- * @type {Boolean}
+ * Enable or disable the legend. There is also a series-specific option,
+ * [showInLegend](#plotOptions.series.showInLegend), that can hide the
+ * series from the legend. In some series types this is `false` by
+ * default, so it must set to `true` in order to show the legend for the
+ * series.
+ *
* @sample {highcharts} highcharts/legend/enabled-false/ Legend disabled
* @sample {highstock} stock/legend/align/ Various legend options
* @sample {highmaps} maps/legend/enabled-false/ Legend disabled
+ *
* @default {highstock} false
* @default {highmaps} true
+ * @default {gantt} false
*/
enabled: true,
/**
* The horizontal alignment of the legend box within the chart area.
* Valid values are `left`, `center` and `right`.
- *
+ *
* In the case that the legend is aligned in a corner position, the
* `layout` option will determine whether to place it above/below
* or on the side of the plot area.
- *
- * @validvalue ["left", "center", "right"]
- * @type {String}
+ *
* @sample {highcharts} highcharts/legend/align/
* Legend at the right of the chart
* @sample {highstock} stock/legend/align/
* Various legend options
* @sample {highmaps} maps/legend/alignment/
* Legend alignment
+ *
+ * @type {Highcharts.AlignType}
* @since 2.0
*/
align: 'center',
+ /**
+ * If the [layout](legend.layout) is `horizontal` and the legend items
+ * span over two lines or more, whether to align the items into vertical
+ * columns. Setting this to `false` makes room for more items, but will
+ * look more messy.
+ *
+ * @since 6.1.0
+ */
+ alignColumns: true,
+
/**
* When the legend is floating, the plot area ignores it and is allowed
* to be placed below it.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/legend/floating-false/ False by default
- * @sample {highcharts} highcharts/legend/floating-true/ True
- * @sample {highmaps} maps/legend/alignment/ Floating legend
- * @default false
- * @since 2.1
+ *
+ * @sample {highcharts} highcharts/legend/floating-false/
+ * False by default
+ * @sample {highcharts} highcharts/legend/floating-true/
+ * True
+ * @sample {highmaps} maps/legend/alignment/
+ * Floating legend
+ *
+ * @type {boolean}
+ * @default false
+ * @since 2.1
* @apioption legend.floating
*/
/**
- * The layout of the legend items. Can be one of "horizontal" or "vertical".
- *
- * @validvalue ["horizontal", "vertical"]
- * @type {String}
- * @sample {highcharts} highcharts/legend/layout-horizontal/ Horizontal by default
- * @sample {highcharts} highcharts/legend/layout-vertical/ Vertical
- * @sample {highstock} stock/legend/layout-horizontal/ Horizontal by default
- * @sample {highmaps} maps/legend/padding-itemmargin/ Vertical with data classes
- * @sample {highmaps} maps/legend/layout-vertical/ Vertical with color axis gradient
- * @default horizontal
+ * The layout of the legend items. Can be one of `horizontal` or
+ * `vertical` or `proximate`. When `proximate`, the legend items will be
+ * placed as close as possible to the graphs they're representing,
+ * except in inverted charts or when the legend position doesn't allow
+ * it.
+ *
+ * @sample {highcharts} highcharts/legend/layout-horizontal/
+ * Horizontal by default
+ * @sample {highcharts} highcharts/legend/layout-vertical/
+ * Vertical
+ * @sample highcharts/legend/layout-proximate
+ * Labels proximate to the data
+ * @sample {highstock} stock/legend/layout-horizontal/
+ * Horizontal by default
+ * @sample {highmaps} maps/legend/padding-itemmargin/
+ * Vertical with data classes
+ * @sample {highmaps} maps/legend/layout-vertical/
+ * Vertical with color axis gradient
+ *
+ * @validvalue ["horizontal", "vertical", "proximate"]
*/
layout: 'horizontal',
/**
* In a legend with horizontal layout, the itemDistance defines the
* pixel distance between each item.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/legend/layout-horizontal/ 50px item distance
- * @sample {highstock} highcharts/legend/layout-horizontal/ 50px item distance
- * @default {highcharts} 20
- * @default {highstock} 20
- * @default {highmaps} 8
- * @since 3.0.3
+ *
+ * @sample {highcharts} highcharts/legend/layout-horizontal/
+ * 50px item distance
+ * @sample {highstock} highcharts/legend/layout-horizontal/
+ * 50px item distance
+ *
+ * @type {number}
+ * @default {highcharts} 20
+ * @default {highstock} 20
+ * @default {highmaps} 8
+ * @since 3.0.3
* @apioption legend.itemDistance
*/
/**
* The pixel bottom margin for each legend item.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
- * @sample {highstock} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
- * @sample {highmaps} maps/legend/padding-itemmargin/ Padding and item margins demonstrated
- * @default 0
- * @since 2.2.0
+ *
+ * @sample {highcharts|highstock} highcharts/legend/padding-itemmargin/
+ * Padding and item margins demonstrated
+ * @sample {highmaps} maps/legend/padding-itemmargin/
+ * Padding and item margins demonstrated
+ *
+ * @type {number}
+ * @default 0
+ * @since 2.2.0
* @apioption legend.itemMarginBottom
*/
/**
* The pixel top margin for each legend item.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
- * @sample {highstock} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
- * @sample {highmaps} maps/legend/padding-itemmargin/ Padding and item margins demonstrated
- * @default 0
- * @since 2.2.0
+ *
+ * @sample {highcharts|highstock} highcharts/legend/padding-itemmargin/
+ * Padding and item margins demonstrated
+ * @sample {highmaps} maps/legend/padding-itemmargin/
+ * Padding and item margins demonstrated
+ *
+ * @type {number}
+ * @default 0
+ * @since 2.2.0
* @apioption legend.itemMarginTop
*/
/**
- * The width for each legend item. This is useful in a horizontal layout
- * with many items when you want the items to align vertically. .
- *
- * @type {Number}
- * @sample {highcharts} highcharts/legend/itemwidth-default/ Null by default
- * @sample {highcharts} highcharts/legend/itemwidth-80/ 80 for aligned legend items
- * @default null
- * @since 2.0
+ * The width for each legend item. By default the items are laid out
+ * successively. In a [horizontal layout](legend.layout), if the items
+ * are laid out across two rows or more, they will be vertically aligned
+ * depending on the [legend.alignColumns](legend.alignColumns) option.
+ *
+ * @sample {highcharts} highcharts/legend/itemwidth-default/
+ * Undefined by default
+ * @sample {highcharts} highcharts/legend/itemwidth-80/
+ * 80 for aligned legend items
+ *
+ * @type {number}
+ * @since 2.0
* @apioption legend.itemWidth
*/
/**
- * A [format string](http://www.highcharts.com/docs/chart-concepts/labels-
- * and-string-formatting) for each legend label. Available variables
- * relates to properties on the series, or the point in case of pies.
- *
- * @type {String}
- * @default {name}
- * @since 1.3
+ * A [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
+ * for each legend label. Available variables relates to properties on
+ * the series, or the point in case of pies.
+ *
+ * @type {string}
+ * @default {name}
+ * @since 1.3
* @apioption legend.labelFormat
*/
@@ -8690,28 +11867,35 @@
* of pie charts. By default the series or point name is printed.
*
* @productdesc {highmaps}
- * In Highmaps the context can also be a data class in case
- * of a `colorAxis`.
- *
- * @type {Function}
- * @sample {highcharts} highcharts/legend/labelformatter/ Add text
- * @sample {highmaps} maps/legend/labelformatter/ Data classes with label formatter
- * @context {Series|Point}
- */
- labelFormatter: function() {
+ * In Highmaps the context can also be a data class in case of a
+ * `colorAxis`.
+ *
+ * @sample {highcharts} highcharts/legend/labelformatter/
+ * Add text
+ * @sample {highmaps} maps/legend/labelformatter/
+ * Data classes with label formatter
+ *
+ * @context {Highcharts.Series|Highcharts.Point}
+ */
+ labelFormatter: function () {
return this.name;
},
/**
* Line height for the legend items. Deprecated as of 2.1\. Instead,
* the line height for each item can be set using itemStyle.lineHeight,
- * and the padding between items using itemMarginTop and itemMarginBottom.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/legend/lineheight/ Setting padding
- * @default 16
- * @since 2.0
- * @product highcharts
+ * and the padding between items using `itemMarginTop` and
+ * `itemMarginBottom`.
+ *
+ * @sample {highcharts} highcharts/legend/lineheight/
+ * Setting padding
+ *
+ * @deprecated
+ *
+ * @type {number}
+ * @default 16
+ * @since 2.0
+ * @product highcharts gantt
* @apioption legend.lineHeight
*/
@@ -8719,188 +11903,203 @@
* If the plot area sized is calculated automatically and the legend
* is not floating, the legend margin is the space between the legend
* and the axis labels or plot area.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/legend/margin-default/ 12 pixels by default
- * @sample {highcharts} highcharts/legend/margin-30/ 30 pixels
- * @default 12
- * @since 2.1
+ *
+ * @sample {highcharts} highcharts/legend/margin-default/
+ * 12 pixels by default
+ * @sample {highcharts} highcharts/legend/margin-30/
+ * 30 pixels
+ *
+ * @type {number}
+ * @default 12
+ * @since 2.1
* @apioption legend.margin
*/
/**
- * Maximum pixel height for the legend. When the maximum height is extended,
- * navigation will show.
- *
- * @type {Number}
- * @default undefined
- * @since 2.3.0
+ * Maximum pixel height for the legend. When the maximum height is
+ * extended, navigation will show.
+ *
+ * @type {number}
+ * @since 2.3.0
* @apioption legend.maxHeight
*/
/**
* The color of the drawn border around the legend.
- *
- * @type {Color}
- * @see In styled mode, the legend border stroke can be applied with
- * the `.highcharts-legend-box` class.
- * @sample {highcharts} highcharts/legend/bordercolor/ Brown border
- * @sample {highstock} stock/legend/align/ Various legend options
- * @sample {highmaps} maps/legend/border-background/ Border and background options
- * @default #999999
+ *
+ * @see In styled mode, the legend border stroke can be applied with the
+ * `.highcharts-legend-box` class.
+ *
+ * @sample {highcharts} highcharts/legend/bordercolor/
+ * Brown border
+ * @sample {highstock} stock/legend/align/
+ * Various legend options
+ * @sample {highmaps} maps/legend/border-background/
+ * Border and background options
+ *
+ * @type {Highcharts.ColorString}
*/
borderColor: '#999999',
/**
* The border corner radius of the legend.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/legend/borderradius-default/ Square by default
- * @sample {highcharts} highcharts/legend/borderradius-round/ 5px rounded
- * @sample {highmaps} maps/legend/border-background/ Border and background options
- * @default 0
+ *
+ * @sample {highcharts} highcharts/legend/borderradius-default/
+ * Square by default
+ * @sample {highcharts} highcharts/legend/borderradius-round/
+ * 5px rounded
+ * @sample {highmaps} maps/legend/border-background/
+ * Border and background options
*/
borderRadius: 0,
/**
* Options for the paging or navigation appearing when the legend
* is overflown. Navigation works well on screen, but not in static
- * exported images. One way of working around that is to [increase
- * the chart height in export](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/legend/navigation-
- * enabled-false/).
- *
+ * exported images. One way of working around that is to
+ * [increase the chart height in
+ * export](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/legend/navigation-enabled-false/).
*/
navigation: {
-
- /**
- * The color for the active up or down arrow in the legend page navigation.
- *
- * @type {Color}
- * @see In styled mode, the active arrow be styled with the `.highcharts-legend-nav-active` class.
- * @sample {highcharts} highcharts/legend/navigation/ Legend page navigation demonstrated
- * @sample {highstock} highcharts/legend/navigation/ Legend page navigation demonstrated
- * @default #003399
- * @since 2.2.4
- */
- activeColor: '#003399',
-
/**
- * The color of the inactive up or down arrow in the legend page
- * navigation. .
- *
- * @type {Color}
- * @see In styled mode, the inactive arrow be styled with the
- * `.highcharts-legend-nav-inactive` class.
+ * How to animate the pages when navigating up or down. A value of
+ * `true` applies the default navigation given in the
+ * `chart.animation` option. Additional options can be given as an
+ * object containing values for easing and duration.
+ *
* @sample {highcharts} highcharts/legend/navigation/
* Legend page navigation demonstrated
* @sample {highstock} highcharts/legend/navigation/
* Legend page navigation demonstrated
- * @default {highcharts} #cccccc
- * @default {highstock} #cccccc
- * @default {highmaps} ##cccccc
- * @since 2.2.4
+ *
+ * @type {boolean|Highcharts.AnimationOptionsObject}
+ * @default true
+ * @since 2.2.4
+ * @apioption legend.navigation.animation
*/
- inactiveColor: '#cccccc'
-
/**
- * How to animate the pages when navigating up or down. A value of `true`
- * applies the default navigation given in the chart.animation option.
- * Additional options can be given as an object containing values for
- * easing and duration.
- *
- * @type {Boolean|Object}
+ * The pixel size of the up and down arrows in the legend paging
+ * navigation.
+ *
* @sample {highcharts} highcharts/legend/navigation/
* Legend page navigation demonstrated
* @sample {highstock} highcharts/legend/navigation/
* Legend page navigation demonstrated
- * @default true
- * @since 2.2.4
- * @apioption legend.navigation.animation
+ *
+ * @type {number}
+ * @default 12
+ * @since 2.2.4
+ * @apioption legend.navigation.arrowSize
*/
/**
- * The pixel size of the up and down arrows in the legend paging
- * navigation.
- *
- * @type {Number}
+ * Whether to enable the legend navigation. In most cases, disabling
+ * the navigation results in an unwanted overflow.
+ *
+ * See also the [adapt chart to legend](
+ * https://www.highcharts.com/products/plugin-registry/single/8/Adapt-Chart-To-Legend)
+ * plugin for a solution to extend the chart height to make room for
+ * the legend, optionally in exported charts only.
+ *
+ * @type {boolean}
+ * @default true
+ * @since 4.2.4
+ * @apioption legend.navigation.enabled
+ */
+
+ /**
+ * Text styles for the legend page navigation.
+ *
+ * @see In styled mode, the navigation items are styled with the
+ * `.highcharts-legend-navigation` class.
+ *
* @sample {highcharts} highcharts/legend/navigation/
* Legend page navigation demonstrated
* @sample {highstock} highcharts/legend/navigation/
* Legend page navigation demonstrated
- * @default 12
- * @since 2.2.4
- * @apioption legend.navigation.arrowSize
+ *
+ * @type {Highcharts.CSSObject}
+ * @since 2.2.4
+ * @apioption legend.navigation.style
*/
/**
- * Whether to enable the legend navigation. In most cases, disabling
- * the navigation results in an unwanted overflow.
- *
- * See also the [adapt chart to legend](http://www.highcharts.com/plugin-
- * registry/single/8/Adapt-Chart-To-Legend) plugin for a solution to
- * extend the chart height to make room for the legend, optionally in
- * exported charts only.
- *
- * @type {Boolean}
- * @default true
- * @since 4.2.4
- * @apioption legend.navigation.enabled
+ * The color for the active up or down arrow in the legend page
+ * navigation.
+ *
+ * @see In styled mode, the active arrow be styled with the
+ * `.highcharts-legend-nav-active` class.
+ *
+ * @sample {highcharts} highcharts/legend/navigation/
+ * Legend page navigation demonstrated
+ * @sample {highstock} highcharts/legend/navigation/
+ * Legend page navigation demonstrated
+ *
+ * @type {Highcharts.ColorString}
+ * @since 2.2.4
*/
+ activeColor: '#003399',
/**
- * Text styles for the legend page navigation.
- *
- * @type {CSSObject}
- * @see In styled mode, the navigation items are styled with the
- * `.highcharts-legend-navigation` class.
+ * The color of the inactive up or down arrow in the legend page
+ * navigation. .
+ *
+ * @see In styled mode, the inactive arrow be styled with the
+ * `.highcharts-legend-nav-inactive` class.
+ *
* @sample {highcharts} highcharts/legend/navigation/
* Legend page navigation demonstrated
* @sample {highstock} highcharts/legend/navigation/
* Legend page navigation demonstrated
+ *
+ * @type {Highcharts.ColorString}
* @since 2.2.4
- * @apioption legend.navigation.style
*/
+ inactiveColor: '#cccccc'
},
/**
* The inner padding of the legend box.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/legend/padding-itemmargin/
- * Padding and item margins demonstrated
- * @sample {highstock} highcharts/legend/padding-itemmargin/
+ *
+ * @sample {highcharts|highstock} highcharts/legend/padding-itemmargin/
* Padding and item margins demonstrated
* @sample {highmaps} maps/legend/padding-itemmargin/
* Padding and item margins demonstrated
- * @default 8
- * @since 2.2.0
+ *
+ * @type {number}
+ * @default 8
+ * @since 2.2.0
* @apioption legend.padding
*/
/**
* Whether to reverse the order of the legend items compared to the
* order of the series or points as defined in the configuration object.
- *
- * @type {Boolean}
+ *
* @see [yAxis.reversedStacks](#yAxis.reversedStacks),
* [series.legendIndex](#series.legendIndex)
+ *
* @sample {highcharts} highcharts/legend/reversed/
* Stacked bar with reversed legend
- * @default false
- * @since 1.2.5
+ *
+ * @type {boolean}
+ * @default false
+ * @since 1.2.5
* @apioption legend.reversed
*/
/**
* Whether to show the symbol on the right side of the text rather than
* the left side. This is common in Arabic and Hebraic.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/legend/rtl/ Symbol to the right
- * @default false
- * @since 2.2
+ *
+ * @sample {highcharts} highcharts/legend/rtl/
+ * Symbol to the right
+ *
+ * @type {boolean}
+ * @default false
+ * @since 2.2
* @apioption legend.rtl
*/
@@ -8908,33 +12107,53 @@
* CSS styles for the legend area. In the 1.x versions the position
* of the legend area was determined by CSS. In 2.x, the position is
* determined by properties like `align`, `verticalAlign`, `x` and `y`,
- * but the styles are still parsed for backwards compatibility.
- *
- * @type {CSSObject}
+ * but the styles are still parsed for backwards compatibility.
+ *
* @deprecated
- * @product highcharts highstock
+ *
+ * @type {Highcharts.CSSObject}
+ * @product highcharts highstock
* @apioption legend.style
*/
-
-
/**
* CSS styles for each legend item. Only a subset of CSS is supported,
* notably those options related to text. The default `textOverflow`
- * property makes long texts truncate. Set it to `null` to wrap text
- * instead. A `width` property can be added to control the text width.
- *
- * @type {CSSObject}
- * @see In styled mode, the legend items can be styled with the `.
- * highcharts-legend-item` class.
- * @sample {highcharts} highcharts/legend/itemstyle/ Bold black text
- * @sample {highmaps} maps/legend/itemstyle/ Item text styles
- * @default { "color": "#333333", "cursor": "pointer", "fontSize": "12px", "fontWeight": "bold", "textOverflow": "ellipsis" }
+ * property makes long texts truncate. Set it to `undefined` to wrap
+ * text instead. A `width` property can be added to control the text
+ * width.
+ *
+ * @see In styled mode, the legend items can be styled with the
+ * `.highcharts-legend-item` class.
+ *
+ * @sample {highcharts} highcharts/legend/itemstyle/
+ * Bold black text
+ * @sample {highmaps} maps/legend/itemstyle/
+ * Item text styles
+ *
+ * @type {Highcharts.CSSObject}
+ * @default {"color": "#333333", "cursor": "pointer", "fontSize": "12px", "fontWeight": "bold", "textOverflow": "ellipsis"}
*/
itemStyle: {
+ /**
+ * @ignore
+ */
color: '#333333',
+ /**
+ * @ignore
+ */
+ cursor: 'pointer',
+ /**
+ * @ignore
+ */
fontSize: '12px',
+ /**
+ * @ignore
+ */
fontWeight: 'bold',
+ /**
+ * @ignore
+ */
textOverflow: 'ellipsis'
},
@@ -8942,15 +12161,22 @@
* CSS styles for each legend item in hover mode. Only a subset of
* CSS is supported, notably those options related to text. Properties
* are inherited from `style` unless overridden here.
- *
- * @type {CSSObject}
+ *
* @see In styled mode, the hovered legend items can be styled with
- * the `.highcharts-legend-item:hover` pesudo-class.
- * @sample {highcharts} highcharts/legend/itemhoverstyle/ Red on hover
- * @sample {highmaps} maps/legend/itemstyle/ Item text styles
- * @default { "color": "#000000" }
+ * the `.highcharts-legend-item:hover` pesudo-class.
+ *
+ * @sample {highcharts} highcharts/legend/itemhoverstyle/
+ * Red on hover
+ * @sample {highmaps} maps/legend/itemstyle/
+ * Item text styles
+ *
+ * @type {Highcharts.CSSObject}
+ * @default {"color": "#000000"}
*/
itemHoverStyle: {
+ /**
+ * @ignore
+ */
color: '#000000'
},
@@ -8959,14 +12185,20 @@
* point is hidden. Only a subset of CSS is supported, notably those
* options related to text. Properties are inherited from `style`
* unless overridden here.
- *
- * @type {CSSObject}
+ *
* @see In styled mode, the hidden legend items can be styled with
- * the `.highcharts-legend-item-hidden` class.
- * @sample {highcharts} highcharts/legend/itemhiddenstyle/ Darker gray color
- * @default { "color": "#cccccc" }
+ * the `.highcharts-legend-item-hidden` class.
+ *
+ * @sample {highcharts} highcharts/legend/itemhiddenstyle/
+ * Darker gray color
+ *
+ * @type {Highcharts.CSSObject}
+ * @default {"color": "#cccccc"}
*/
itemHiddenStyle: {
+ /**
+ * @ignore
+ */
color: '#cccccc'
},
@@ -8975,26 +12207,37 @@
* also needs to be applied for this to take effect. The shadow can be
* an object configuration containing `color`, `offsetX`, `offsetY`,
* `opacity` and `width`.
- *
- * @type {Boolean|Object}
+ *
* @sample {highcharts} highcharts/legend/shadow/
* White background and drop shadow
* @sample {highstock} stock/legend/align/
* Various legend options
* @sample {highmaps} maps/legend/border-background/
* Border and background options
- * @default false
+ *
+ * @type {boolean|Highcharts.CSSObject}
*/
shadow: false,
-
/**
* Default styling for the checkbox next to a legend item when
* `showCheckbox` is true.
+ *
+ * @type {Highcharts.CSSObject}
+ * @default {"width": "13px", "height": "13px", "position":"absolute"}
*/
itemCheckboxStyle: {
+ /**
+ * @ignore
+ */
position: 'absolute',
+ /**
+ * @ignore
+ */
width: '13px', // for IE precision
+ /**
+ * @ignore
+ */
height: '13px'
},
// itemWidth: undefined,
@@ -9003,9 +12246,7 @@
* When this is true, the legend symbol width will be the same as
* the symbol height, which in turn defaults to the font size of the
* legend items.
- *
- * @type {Boolean}
- * @default true
+ *
* @since 5.0.0
*/
squareSymbol: true,
@@ -9017,74 +12258,88 @@
* @productdesc {highmaps}
* In Highmaps, when the symbol is the gradient of a vertical color
* axis, the height defaults to 200.
- *
- * @type {Number}
+ *
* @sample {highmaps} maps/legend/layout-vertical-sized/
* Sized vertical gradient
* @sample {highmaps} maps/legend/padding-itemmargin/
* No distance between data classes
- * @since 3.0.8
+ *
+ * @type {number}
+ * @since 3.0.8
* @apioption legend.symbolHeight
*/
/**
* The border radius of the symbol for series types that use a rectangle
* in the legend. Defaults to half the `symbolHeight`.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/legend/symbolradius/ Round symbols
- * @sample {highstock} highcharts/legend/symbolradius/ Round symbols
- * @sample {highmaps} highcharts/legend/symbolradius/ Round symbols
- * @since 3.0.8
+ *
+ * @sample {highcharts} highcharts/legend/symbolradius/
+ * Round symbols
+ * @sample {highstock} highcharts/legend/symbolradius/
+ * Round symbols
+ * @sample {highmaps} highcharts/legend/symbolradius/
+ * Round symbols
+ *
+ * @type {number}
+ * @since 3.0.8
* @apioption legend.symbolRadius
*/
/**
* The pixel width of the legend item symbol. When the `squareSymbol`
* option is set, this defaults to the `symbolHeight`, otherwise 16.
- *
+ *
* @productdesc {highmaps}
* In Highmaps, when the symbol is the gradient of a horizontal color
* axis, the width defaults to 200.
- *
- * @type {Number}
+ *
* @sample {highcharts} highcharts/legend/symbolwidth/
* Greater symbol width and padding
* @sample {highmaps} maps/legend/padding-itemmargin/
* Padding and item margins demonstrated
* @sample {highmaps} maps/legend/layout-vertical-sized/
* Sized vertical gradient
+ *
+ * @type {number}
* @apioption legend.symbolWidth
*/
/**
- * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
- * and-string-formatting#html) to render the legend item texts. Prior
- * to 4.1.7, when using HTML, [legend.navigation](#legend.navigation)
- * was disabled.
- *
- * @type {Boolean}
- * @default false
+ * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
+ * to render the legend item texts.
+ *
+ * Prior to 4.1.7, when using HTML, [legend.navigation](
+ * #legend.navigation) was disabled.
+ *
+ * @type {boolean}
+ * @default false
* @apioption legend.useHTML
*/
/**
- * The width of the legend box.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/legend/width/ Aligned to the plot area
- * @default null
- * @since 2.0
+ * The width of the legend box. If a number is set, it translates to
+ * pixels. Since v7.0.2 it allows setting a percent string of the full
+ * chart width, for example `40%`.
+ *
+ * Defaults to the full chart width from legends below or above the
+ * chart, half the chart width for legends to the left and right.
+ *
+ * @sample {highcharts} highcharts/legend/width/
+ * Aligned to the plot area
+ * @sample {highcharts} highcharts/legend/width-percent/
+ * A percent of the chart width
+ *
+ * @type {number|string}
+ * @since 2.0
* @apioption legend.width
*/
/**
* The pixel padding between the legend item symbol and the legend
* item text.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/legend/symbolpadding/ Greater symbol width and padding
- * @default 5
+ *
+ * @sample {highcharts} highcharts/legend/symbolpadding/
+ * Greater symbol width and padding
*/
symbolPadding: 5,
@@ -9092,30 +12347,36 @@
* The vertical alignment of the legend box. Can be one of `top`,
* `middle` or `bottom`. Vertical position can be further determined
* by the `y` option.
- *
+ *
* In the case that the legend is aligned in a corner position, the
* `layout` option will determine whether to place it above/below
* or on the side of the plot area.
- *
- * @validvalue ["top", "middle", "bottom"]
- * @type {String}
- * @sample {highcharts} highcharts/legend/verticalalign/ Legend 100px from the top of the chart
- * @sample {highstock} stock/legend/align/ Various legend options
- * @sample {highmaps} maps/legend/alignment/ Legend alignment
- * @default bottom
+ *
+ * When the [layout](#legend.layout) option is `proximate`, the
+ * `verticalAlign` option doesn't apply.
+ *
+ * @sample {highcharts} highcharts/legend/verticalalign/
+ * Legend 100px from the top of the chart
+ * @sample {highstock} stock/legend/align/
+ * Various legend options
+ * @sample {highmaps} maps/legend/alignment/
+ * Legend alignment
+ *
+ * @type {Highcharts.VerticalAlignType}
* @since 2.0
*/
verticalAlign: 'bottom',
+
// width: undefined,
/**
* The x offset of the legend relative to its horizontal alignment
* `align` within chart.spacingLeft and chart.spacingRight. Negative
* x moves it to the left, positive x moves it to the right.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/legend/width/ Aligned to the plot area
- * @default 0
+ *
+ * @sample {highcharts} highcharts/legend/width/
+ * Aligned to the plot area
+ *
* @since 2.0
*/
x: 0,
@@ -9124,48 +12385,53 @@
* The vertical offset of the legend relative to it's vertical alignment
* `verticalAlign` within chart.spacingTop and chart.spacingBottom.
* Negative y moves it up, positive y moves it down.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/legend/verticalalign/ Legend 100px from the top of the chart
- * @sample {highstock} stock/legend/align/ Various legend options
- * @sample {highmaps} maps/legend/alignment/ Legend alignment
- * @default 0
+ *
+ * @sample {highcharts} highcharts/legend/verticalalign/
+ * Legend 100px from the top of the chart
+ * @sample {highstock} stock/legend/align/
+ * Various legend options
+ * @sample {highmaps} maps/legend/alignment/
+ * Legend alignment
+ *
* @since 2.0
*/
y: 0,
/**
* A title to be added on top of the legend.
- *
- * @sample {highcharts} highcharts/legend/title/ Legend title
- * @sample {highmaps} maps/legend/alignment/ Legend with title
+ *
+ * @sample {highcharts} highcharts/legend/title/
+ * Legend title
+ * @sample {highmaps} maps/legend/alignment/
+ * Legend with title
+ *
* @since 3.0
*/
title: {
/**
* A text or HTML string for the title.
- *
- * @type {String}
- * @default null
- * @since 3.0
+ *
+ * @type {string}
+ * @since 3.0
* @apioption legend.title.text
*/
-
-
/**
* Generic CSS styles for the legend title.
- *
- * @type {CSSObject}
+ *
* @see In styled mode, the legend title is styled with the
- * `.highcharts-legend-title` class.
- * @default {"fontWeight":"bold"}
- * @since 3.0
+ * `.highcharts-legend-title` class.
+ *
+ * @type {Highcharts.CSSObject}
+ * @default {"fontWeight": "bold"}
+ * @since 3.0
*/
style: {
+ /**
+ * @ignore
+ */
fontWeight: 'bold'
}
-
}
},
@@ -9178,285 +12444,110 @@
* is going on, for example while retrieving new data via an XHR connection.
* The "Loading..." text itself is not part of this configuration
* object, but part of the `lang` object.
- *
*/
loading: {
/**
* The duration in milliseconds of the fade out effect.
- *
- * @type {Number}
- * @sample highcharts/loading/hideduration/ Fade in and out over a second
- * @default 100
- * @since 1.2.0
- * @apioption loading.hideDuration
- */
-
- /**
- * The duration in milliseconds of the fade in effect.
- *
- * @type {Number}
- * @sample highcharts/loading/hideduration/ Fade in and out over a second
- * @default 100
- * @since 1.2.0
- * @apioption loading.showDuration
- */
-
-
- /**
- * CSS styles for the loading label `span`.
- *
- * @type {CSSObject}
- * @see In styled mode, the loading label is styled with the
- * `.highcharts-legend-loading-inner` class.
- * @sample {highcharts|highmaps} highcharts/loading/labelstyle/ Vertically centered
- * @sample {highstock} stock/loading/general/ Label styles
- * @default { "fontWeight": "bold", "position": "relative", "top": "45%" }
- * @since 1.2.0
- */
- labelStyle: {
- fontWeight: 'bold',
- position: 'relative',
- top: '45%'
- },
-
- /**
- * CSS styles for the loading screen that covers the plot area.
- *
- * @type {CSSObject}
- * @see In styled mode, the loading label is styled with the `.highcharts-legend-loading` class.
- * @sample {highcharts|highmaps} highcharts/loading/style/ Gray plot area, white text
- * @sample {highstock} stock/loading/general/ Gray plot area, white text
- * @default { "position": "absolute", "backgroundColor": "#ffffff", "opacity": 0.5, "textAlign": "center" }
- * @since 1.2.0
- */
- style: {
- position: 'absolute',
- backgroundColor: '#ffffff',
- opacity: 0.5,
- textAlign: 'center'
- }
-
- },
-
-
- /**
- * Options for the tooltip that appears when the user hovers over a
- * series or point.
- *
- */
- tooltip: {
-
- /**
- * Enable or disable the tooltip.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/tooltip/enabled/ Disabled
- * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ Disable tooltip and show values on chart instead
- * @default true
- */
- enabled: true,
-
- /**
- * Enable or disable animation of the tooltip. In slow legacy IE browsers
- * the animation is disabled by default.
- *
- * @type {Boolean}
- * @default true
- * @since 2.3.0
- */
- animation: svg,
-
- /**
- * The radius of the rounded border corners.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/tooltip/bordercolor-default/ 5px by default
- * @sample {highcharts} highcharts/tooltip/borderradius-0/ Square borders
- * @sample {highmaps} maps/tooltip/background-border/ Background and border demo
- * @default 3
- */
- borderRadius: 3,
-
- /**
- * For series on a datetime axes, the date format in the tooltip's
- * header will by default be guessed based on the closest data points.
- * This member gives the default string representations used for
- * each unit. For an overview of the replacement codes, see [dateFormat](#Highcharts.
- * dateFormat).
- *
- * Defaults to:
- *
- * {
- * millisecond:"%A, %b %e, %H:%M:%S.%L",
- * second:"%A, %b %e, %H:%M:%S",
- * minute:"%A, %b %e, %H:%M",
- * hour:"%A, %b %e, %H:%M",
- * day:"%A, %b %e, %Y",
- * week:"Week from %A, %b %e, %Y",
- * month:"%B %Y",
- * year:"%Y"
- * }
- *
- * @type {Object}
- * @see [xAxis.dateTimeLabelFormats](#xAxis.dateTimeLabelFormats)
- * @product highcharts highstock
- */
- dateTimeLabelFormats: {
- millisecond: '%A, %b %e, %H:%M:%S.%L',
- second: '%A, %b %e, %H:%M:%S',
- minute: '%A, %b %e, %H:%M',
- hour: '%A, %b %e, %H:%M',
- day: '%A, %b %e, %Y',
- week: 'Week from %A, %b %e, %Y',
- month: '%B %Y',
- year: '%Y'
- },
-
- /**
- * A string to append to the tooltip format.
- *
- * @type {String}
- * @sample {highcharts} highcharts/tooltip/footerformat/ A table for value alignment
- * @sample {highmaps} maps/tooltip/format/ Format demo
- * @default false
- * @since 2.2
- */
- footerFormat: '',
-
- /**
- * Padding inside the tooltip, in pixels.
- *
- * @type {Number}
- * @default 8
- * @since 5.0.0
- */
- padding: 8,
-
- /**
- * Proximity snap for graphs or single points. It defaults to 10 for
- * mouse-powered devices and 25 for touch devices.
- *
- * Note that in most cases the whole plot area captures the mouse
- * movement, and in these cases `tooltip.snap` doesn't make sense.
- * This applies when [stickyTracking](#plotOptions.series.stickyTracking)
- * is `true` (default) and when the tooltip is [shared](#tooltip.shared)
- * or [split](#tooltip.split).
- *
- * @type {Number}
- * @sample {highcharts} highcharts/tooltip/bordercolor-default/ 10 px by default
- * @sample {highcharts} highcharts/tooltip/snap-50/ 50 px on graph
- * @default 10/25
- * @since 1.2.0
- * @product highcharts highstock
- */
- snap: isTouchDevice ? 25 : 10,
-
-
- /**
- * The background color or gradient for the tooltip.
- *
- * In styled mode, the stroke width is set in the `.highcharts-tooltip-box` class.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/tooltip/backgroundcolor-solid/ Yellowish background
- * @sample {highcharts} highcharts/tooltip/backgroundcolor-gradient/ Gradient
- * @sample {highcharts} highcharts/css/tooltip-border-background/ Tooltip in styled mode
- * @sample {highstock} stock/tooltip/general/ Custom tooltip
- * @sample {highstock} highcharts/css/tooltip-border-background/ Tooltip in styled mode
- * @sample {highmaps} maps/tooltip/background-border/ Background and border demo
- * @sample {highmaps} highcharts/css/tooltip-border-background/ Tooltip in styled mode
- * @default rgba(247,247,247,0.85)
- */
- backgroundColor: color('#f7f7f7').setOpacity(0.85).get(),
-
- /**
- * The pixel width of the tooltip border.
- *
- * In styled mode, the stroke width is set in the `.highcharts-tooltip-box` class.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/tooltip/bordercolor-default/ 2px by default
- * @sample {highcharts} highcharts/tooltip/borderwidth/ No border (shadow only)
- * @sample {highcharts} highcharts/css/tooltip-border-background/ Tooltip in styled mode
- * @sample {highstock} stock/tooltip/general/ Custom tooltip
- * @sample {highstock} highcharts/css/tooltip-border-background/ Tooltip in styled mode
- * @sample {highmaps} maps/tooltip/background-border/ Background and border demo
- * @sample {highmaps} highcharts/css/tooltip-border-background/ Tooltip in styled mode
- * @default 1
- */
- borderWidth: 1,
-
- /**
- * The HTML of the tooltip header line. Variables are enclosed by
- * curly brackets. Available variables are `point.key`, `series.name`,
- * `series.color` and other members from the `point` and `series`
- * objects. The `point.key` variable contains the category name, x
- * value or datetime string depending on the type of axis. For datetime
- * axes, the `point.key` date format can be set using tooltip.xDateFormat.
- *
- * @type {String}
- * @sample {highcharts} highcharts/tooltip/footerformat/
- * A HTML table in the tooltip
- * @sample {highstock} highcharts/tooltip/footerformat/
- * A HTML table in the tooltip
- * @sample {highmaps} maps/tooltip/format/ Format demo
+ *
+ * @sample highcharts/loading/hideduration/
+ * Fade in and out over a second
+ *
+ * @type {number}
+ * @default 100
+ * @since 1.2.0
+ * @apioption loading.hideDuration
*/
- headerFormat: '{point.key} ',
/**
- * The HTML of the point's line in the tooltip. Variables are enclosed
- * by curly brackets. Available variables are point.x, point.y, series.
- * name and series.color and other properties on the same form. Furthermore,
- * point.y can be extended by the `tooltip.valuePrefix` and `tooltip.
- * valueSuffix` variables. This can also be overridden for each series,
- * which makes it a good hook for displaying units.
- *
- * In styled mode, the dot is colored by a class name rather
- * than the point color.
- *
- * @type {String}
- * @sample {highcharts} highcharts/tooltip/pointformat/ A different point format with value suffix
- * @sample {highmaps} maps/tooltip/format/ Format demo
- * @default \u25CF {series.name}: {point.y}
- * @since 2.2
+ * The duration in milliseconds of the fade in effect.
+ *
+ * @sample highcharts/loading/hideduration/
+ * Fade in and out over a second
+ *
+ * @type {number}
+ * @default 100
+ * @since 1.2.0
+ * @apioption loading.showDuration
*/
- pointFormat: '\u25CF {series.name}: {point.y} ',
/**
- * Whether to apply a drop shadow to the tooltip.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/tooltip/bordercolor-default/ True by default
- * @sample {highcharts} highcharts/tooltip/shadow/ False
- * @sample {highmaps} maps/tooltip/positioner/ Fixed tooltip position, border and shadow disabled
- * @default true
+ * CSS styles for the loading label `span`.
+ *
+ * @see In styled mode, the loading label is styled with the
+ * `.highcharts-loading-inner` class.
+ *
+ * @sample {highcharts|highmaps} highcharts/loading/labelstyle/
+ * Vertically centered
+ * @sample {highstock} stock/loading/general/
+ * Label styles
+ *
+ * @type {Highcharts.CSSObject}
+ * @default {"fontWeight": "bold", "position": "relative", "top": "45%"}
+ * @since 1.2.0
*/
- shadow: true,
+ labelStyle: {
+ /**
+ * @ignore
+ */
+ fontWeight: 'bold',
+ /**
+ * @ignore
+ */
+ position: 'relative',
+ /**
+ * @ignore
+ */
+ top: '45%'
+ },
/**
- * CSS styles for the tooltip. The tooltip can also be styled through
- * the CSS class `.highcharts-tooltip`.
- *
- * @type {CSSObject}
- * @sample {highcharts} highcharts/tooltip/style/ Greater padding, bold text
- * @default { "color": "#333333", "cursor": "default", "fontSize": "12px", "pointerEvents": "none", "whiteSpace": "nowrap" }
+ * CSS styles for the loading screen that covers the plot area.
+ *
+ * In styled mode, the loading label is styled with the
+ * `.highcharts-loading` class.
+ *
+ * @sample {highcharts|highmaps} highcharts/loading/style/
+ * Gray plot area, white text
+ * @sample {highstock} stock/loading/general/
+ * Gray plot area, white text
+ *
+ * @type {Highcharts.CSSObject}
+ * @default {"position": "absolute", "backgroundColor": "#ffffff", "opacity": 0.5, "textAlign": "center"}
+ * @since 1.2.0
*/
style: {
- color: '#333333',
- cursor: 'default',
- fontSize: '12px',
- pointerEvents: 'none', // #1686 http://caniuse.com/#feat=pointer-events
- whiteSpace: 'nowrap'
+ /**
+ * @ignore
+ */
+ position: 'absolute',
+ /**
+ * @ignore
+ */
+ backgroundColor: '#ffffff',
+ /**
+ * @ignore
+ */
+ opacity: 0.5,
+ /**
+ * @ignore
+ */
+ textAlign: 'center'
}
+ },
+ /**
+ * Options for the tooltip that appears when the user hovers over a
+ * series or point.
+ */
+ tooltip: {
+
/**
- * The color of the tooltip border. When `null`, the border takes the
- * color of the corresponding series or point.
- *
- * @type {Color}
+ * The color of the tooltip border. When `undefined`, the border takes
+ * the color of the corresponding series or point.
+ *
* @sample {highcharts} highcharts/tooltip/bordercolor-default/
* Follow series by default
* @sample {highcharts} highcharts/tooltip/bordercolor-black/
@@ -9465,139 +12556,178 @@
* Styled tooltip
* @sample {highmaps} maps/tooltip/background-border/
* Background and border demo
- * @default null
+ *
+ * @type {Highcharts.ColorString}
* @apioption tooltip.borderColor
*/
/**
* Since 4.1, the crosshair definitions are moved to the Axis object
- * in order for a better separation from the tooltip. See [xAxis.crosshair](#xAxis.
- * crosshair).
- *
- * @type {Mixed}
- * @deprecated
+ * in order for a better separation from the tooltip. See
+ * [xAxis.crosshair](#xAxis.crosshair).
+ *
* @sample {highcharts} highcharts/tooltip/crosshairs-x/
* Enable a crosshair for the x value
- * @default true
+ *
+ * @deprecated
+ *
+ * @type {*}
+ * @default true
* @apioption tooltip.crosshairs
*/
/**
- * Whether the tooltip should follow the mouse as it moves across columns,
- * pie slices and other point types with an extent. By default it behaves
- * this way for scatter, bubble and pie series by override in the `plotOptions`
- * for those series types.
- *
- * For touch moves to behave the same way, [followTouchMove](#tooltip.
- * followTouchMove) must be `true` also.
- *
- * @type {Boolean}
- * @default {highcharts} false
- * @default {highstock} false
- * @default {highmaps} true
- * @since 3.0
+ * Whether the tooltip should follow the mouse as it moves across
+ * columns, pie slices and other point types with an extent. By default
+ * it behaves this way for scatter, bubble and pie series by override
+ * in the `plotOptions` for those series types.
+ *
+ * For touch moves to behave the same way, [followTouchMove](
+ * #tooltip.followTouchMove) must be `true` also.
+ *
+ * @type {boolean}
+ * @default {highcharts} false
+ * @default {highstock} false
+ * @default {highmaps} true
+ * @since 3.0
* @apioption tooltip.followPointer
*/
/**
- * Whether the tooltip should follow the finger as it moves on a touch
+ * Whether the tooltip should update as the finger moves on a touch
* device. If this is `true` and [chart.panning](#chart.panning) is
* set,`followTouchMove` will take over one-finger touches, so the user
* needs to use two fingers for zooming and panning.
- *
- * @type {Boolean}
- * @default {highcharts} true
- * @default {highstock} true
- * @default {highmaps} false
- * @since 3.0.1
+ *
+ * Note the difference to [followPointer](#tooltip.followPointer) that
+ * only defines the _position_ of the tooltip. If `followPointer` is
+ * false in for example a column series, the tooltip will show above or
+ * below the column, but as `followTouchMove` is true, the tooltip will
+ * jump from column to column as the user swipes across the plot area.
+ *
+ * @type {boolean}
+ * @default {highcharts} true
+ * @default {highstock} true
+ * @default {highmaps} false
+ * @since 3.0.1
* @apioption tooltip.followTouchMove
*/
/**
- * Callback function to format the text of the tooltip from scratch. Return
- * `false` to disable tooltip for a specific point on series.
- *
- * A subset of HTML is supported. Unless `useHTML` is true, the HTML of the
- * tooltip is parsed and converted to SVG, therefore this isn't a complete HTML
- * renderer. The following tags are supported: ``, ``, ``, ``,
- * ` `, ``. Spans can be styled with a `style` attribute,
- * but only text-related CSS that is shared with SVG is handled.
- *
- * Since version 2.1 the tooltip can be shared between multiple series
- * through the `shared` option. The available data in the formatter
- * differ a bit depending on whether the tooltip is shared or not. In
- * a shared tooltip, all properties except `x`, which is common for
+ * Callback function to format the text of the tooltip from scratch. In
+ * case of single or [shared](#tooltip.shared) tooltips, a string should
+ * be returned. In case of [split](#tooltip.split) tooltips, it should
+ * return an array where the first item is the header, and subsequent
+ * items are mapped to the points. Return `false` to disable tooltip for
+ * a specific point on series.
+ *
+ * A subset of HTML is supported. Unless `useHTML` is true, the HTML of
+ * the tooltip is parsed and converted to SVG, therefore this isn't a
+ * complete HTML renderer. The following tags are supported: ``,
+ * ``, ``, ``, ` `, ``. Spans can be styled
+ * with a `style` attribute, but only text-related CSS that is shared
+ * with SVG is handled.
+ *
+ * The available data in the formatter differ a bit depending on whether
+ * the tooltip is shared or split, or belongs to a single point. In a
+ * shared/split tooltip, all properties except `x`, which is common for
* all points, are kept in an array, `this.points`.
- *
+ *
* Available data are:
- *
+ *
*
- *
- * this.percentage (not shared) / this.points[i].percentage (shared)
- *
- * Stacked series and pies only. The point's percentage of the total.
+ *
+ * this.percentage (not shared) / this.points[i].percentage (shared)
+ *
+ *
+ * Stacked series and pies only. The point's percentage of the
+ * total.
*
- *
+ *
* this.point (not shared) / this.points[i].point (shared)
- *
- * The point object. The point name, if defined, is available through
- * `this.point.name`.
- *
+ *
+ * The point object. The point name, if defined, is available
+ * through `this.point.name`.
+ *
* this.points
- *
- * In a shared tooltip, this is an array containing all other properties
- * for each point.
- *
+ *
+ * In a shared tooltip, this is an array containing all other
+ * properties for each point.
+ *
* this.series (not shared) / this.points[i].series (shared)
- *
+ *
* The series object. The series name is available through
* `this.series.name`.
- *
+ *
* this.total (not shared) / this.points[i].total (shared)
- *
+ *
* Stacked series only. The total value at this point's x value.
*
- *
+ *
* this.x
- *
+ *
* The x value. This property is the same regardless of the tooltip
* being shared or not.
- *
+ *
* this.y (not shared) / this.points[i].y (shared)
- *
+ *
* The y value.
- *
+ *
*
- *
- * @type {Function}
+ *
* @sample {highcharts} highcharts/tooltip/formatter-simple/
* Simple string formatting
* @sample {highcharts} highcharts/tooltip/formatter-shared/
* Formatting with shared tooltip
+ * @sample {highcharts|highstock} highcharts/tooltip/formatter-split/
+ * Formatting with split tooltip
+ * @sample highcharts/tooltip/formatter-conditional-default/
+ * Extending default formatter
* @sample {highstock} stock/tooltip/formatter/
* Formatting with shared tooltip
* @sample {highmaps} maps/tooltip/formatter/
* String formatting
+ *
+ * @type {Highcharts.FormatterCallbackFunction}
* @apioption tooltip.formatter
*/
/**
* The number of milliseconds to wait until the tooltip is hidden when
* mouse out from a point or chart.
- *
- * @type {Number}
- * @default 500
- * @since 3.0
+ *
+ * @type {number}
+ * @default 500
+ * @since 3.0
* @apioption tooltip.hideDelay
*/
+ /**
+ * Whether to allow the tooltip to render outside the chart's SVG
+ * element box. By default (`false`), the tooltip is rendered within the
+ * chart's SVG element, which results in the tooltip being aligned
+ * inside the chart area. For small charts, this may result in clipping
+ * or overlapping. When `true`, a separate SVG element is created and
+ * overlaid on the page, allowing the tooltip to be aligned inside the
+ * page itself.
+ *
+ * @sample highcharts/tooltip/outside
+ * Small charts with tooltips outside
+ *
+ * @type {boolean}
+ * @default false
+ * @since 6.1.1
+ * @apioption tooltip.outside
+ */
+
/**
* A callback function for formatting the HTML output for a single point
- * in the tooltip. Like the `pointFormat` string, but with more flexibility.
- *
- * @type {Function}
- * @context Point
- * @since 4.1.0
+ * in the tooltip. Like the `pointFormat` string, but with more
+ * flexibility.
+ *
+ * @type {Function}
+ * @since 4.1.0
+ * @context Highcharts.Point
* @apioption tooltip.pointFormatter
*/
@@ -9607,26 +12737,66 @@
* `point`, where point contains values for `plotX` and `plotY` telling
* where the reference point is in the plot area. Add `chart.plotLeft`
* and `chart.plotTop` to get the full coordinates.
- *
+ *
+ * Since v7, when [tooltip.split](#tooltip.split) option is enabled,
+ * positioner is called for each of the boxes separately, including
+ * xAxis header. xAxis header is not a point, instead `point` argument
+ * contains info:
+ * `{ plotX: Number, plotY: Number, isHeader: Boolean }`
+ *
+ *
* The return should be an object containing x and y values, for example
* `{ x: 100, y: 100 }`.
- *
- * @type {Function}
- * @sample {highcharts} highcharts/tooltip/positioner/ A fixed tooltip position
- * @sample {highstock} stock/tooltip/positioner/ A fixed tooltip position on top of the chart
- * @sample {highmaps} maps/tooltip/positioner/ A fixed tooltip position
- * @since 2.2.4
+ *
+ * @sample {highcharts} highcharts/tooltip/positioner/
+ * A fixed tooltip position
+ * @sample {highstock} stock/tooltip/positioner/
+ * A fixed tooltip position on top of the chart
+ * @sample {highmaps} maps/tooltip/positioner/
+ * A fixed tooltip position
+ * @sample {highstock} stock/tooltip/split-positioner/
+ * Split tooltip with fixed positions
+ *
+ * @type {Highcharts.TooltipPositionerCallbackFunction}
+ * @since 2.2.4
* @apioption tooltip.positioner
*/
/**
- * The name of a symbol to use for the border around the tooltip.
- *
+ * The name of a symbol to use for the border around the tooltip. Can
+ * be one of: `"callout"`, `"circle"` or `"square"`. When
+ * [tooltip.split](#tooltip.split) option is enabled, shape is applied
+ * to all boxes except header, which is controlled by
+ * [tooltip.headerShape](#tooltip.headerShape).
+ *
+ * Custom callbacks for symbol path generation can also be added to
+ * `Highcharts.SVGRenderer.prototype.symbols` the same way as for
+ * [series.marker.symbol](plotOptions.line.marker.symbol).
+ *
+ * @type {string}
+ * @default callout
+ * @since 4.0
+ * @validvalue ["callout", "square"]
+ * @apioption tooltip.shape
+ */
+
+ /**
+ * The name of a symbol to use for the border around the tooltip
+ * header. Applies only when [tooltip.split](#tooltip.split) is
+ * enabled.
+ *
+ * Custom callbacks for symbol path generation can also be added to
+ * `Highcharts.SVGRenderer.prototype.symbols` the same way as for
+ * [series.marker.symbol](plotOptions.line.marker.symbol).
+ *
+ * @see [tooltip.shape](#tooltip.shape)
* @type {String}
* @default callout
+ * @sample {highstock} stock/tooltip/split-positioner/
+ * Different shapes for header and split boxes
* @validvalue ["callout", "square"]
- * @since 4.0
- * @apioption tooltip.shape
+ * @since 7.0
+ * @apioption tooltip.headerShape
*/
/**
@@ -9635,107 +12805,372 @@
* data (not pie, scatter, flags etc) will be shown in a single bubble.
* This is recommended for single series charts and for tablet/mobile
* optimized charts.
- *
+ *
* See also [tooltip.split](#tooltip.split), that is better suited for
- * charts with many series, especially line-type series.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/tooltip/shared-false/ False by default
- * @sample {highcharts} highcharts/tooltip/shared-true/ True
- * @sample {highcharts} highcharts/tooltip/shared-x-crosshair/ True with x axis crosshair
- * @sample {highcharts} highcharts/tooltip/shared-true-mixed-types/ True with mixed series types
- * @default false
- * @since 2.1
- * @product highcharts highstock
+ * charts with many series, especially line-type series. The
+ * `tooltip.split` option takes precedence over `tooltip.shared`.
+ *
+ * @sample {highcharts} highcharts/tooltip/shared-false/
+ * False by default
+ * @sample {highcharts} highcharts/tooltip/shared-true/
+ * True
+ * @sample {highcharts} highcharts/tooltip/shared-x-crosshair/
+ * True with x axis crosshair
+ * @sample {highcharts} highcharts/tooltip/shared-true-mixed-types/
+ * True with mixed series types
+ *
+ * @type {boolean}
+ * @default false
+ * @since 2.1
+ * @product highcharts highstock
* @apioption tooltip.shared
*/
/**
- * Split the tooltip into one label per series, with the header close
- * to the axis. This is recommended over [shared](#tooltip.shared) tooltips
- * for charts with multiple line series, generally making them easier
- * to read.
+ * Split the tooltip into one label per series, with the header close
+ * to the axis. This is recommended over [shared](#tooltip.shared)
+ * tooltips for charts with multiple line series, generally making them
+ * easier to read. This option takes precedence over `tooltip.shared`.
+ *
+ * @productdesc {highstock} In Highstock, tooltips are split by default
+ * since v6.0.0. Stock charts typically contain multi-dimension points
+ * and multiple panes, making split tooltips the preferred layout over
+ * the previous `shared` tooltip.
+ *
+ * @sample highcharts/tooltip/split/
+ * Split tooltip
+ * @sample {highcharts|highstock} highcharts/tooltip/formatter-split/
+ * Split tooltip and custom formatter callback
+ *
+ * @type {boolean}
+ * @default {highcharts} false
+ * @default {highstock} true
+ * @since 5.0.0
+ * @product highcharts highstock
+ * @apioption tooltip.split
+ */
+
+ /**
+ * Use HTML to render the contents of the tooltip instead of SVG. Using
+ * HTML allows advanced formatting like tables and images in the
+ * tooltip. It is also recommended for rtl languages as it works around
+ * rtl bugs in early Firefox.
+ *
+ * @sample {highcharts|highstock} highcharts/tooltip/footerformat/
+ * A table for value alignment
+ * @sample {highcharts|highstock} highcharts/tooltip/fullhtml/
+ * Full HTML tooltip
+ * @sample {highmaps} maps/tooltip/usehtml/
+ * Pure HTML tooltip
+ *
+ * @type {boolean}
+ * @default false
+ * @since 2.2
+ * @apioption tooltip.useHTML
+ */
+
+ /**
+ * How many decimals to show in each series' y value. This is
+ * overridable in each series' tooltip options object. The default is to
+ * preserve all decimals.
+ *
+ * @sample {highcharts|highstock} highcharts/tooltip/valuedecimals/
+ * Set decimals, prefix and suffix for the value
+ * @sample {highmaps} maps/tooltip/valuedecimals/
+ * Set decimals, prefix and suffix for the value
+ *
+ * @type {number}
+ * @since 2.2
+ * @apioption tooltip.valueDecimals
+ */
+
+ /**
+ * A string to prepend to each series' y value. Overridable in each
+ * series' tooltip options object.
+ *
+ * @sample {highcharts|highstock} highcharts/tooltip/valuedecimals/
+ * Set decimals, prefix and suffix for the value
+ * @sample {highmaps} maps/tooltip/valuedecimals/
+ * Set decimals, prefix and suffix for the value
+ *
+ * @type {string}
+ * @since 2.2
+ * @apioption tooltip.valuePrefix
+ */
+
+ /**
+ * A string to append to each series' y value. Overridable in each
+ * series' tooltip options object.
+ *
+ * @sample {highcharts|highstock} highcharts/tooltip/valuedecimals/
+ * Set decimals, prefix and suffix for the value
+ * @sample {highmaps} maps/tooltip/valuedecimals/
+ * Set decimals, prefix and suffix for the value
+ *
+ * @type {string}
+ * @since 2.2
+ * @apioption tooltip.valueSuffix
+ */
+
+ /**
+ * The format for the date in the tooltip header if the X axis is a
+ * datetime axis. The default is a best guess based on the smallest
+ * distance between points in the chart.
+ *
+ * @sample {highcharts} highcharts/tooltip/xdateformat/
+ * A different format
+ *
+ * @type {string}
+ * @product highcharts highstock gantt
+ * @apioption tooltip.xDateFormat
+ */
+
+ /**
+ * How many decimals to show for the `point.change` value when the
+ * `series.compare` option is set. This is overridable in each series'
+ * tooltip options object. The default is to preserve all decimals.
+ *
+ * @type {number}
+ * @since 1.0.1
+ * @product highstock
+ * @apioption tooltip.changeDecimals
+ */
+
+ /**
+ * Enable or disable the tooltip.
+ *
+ * @sample {highcharts} highcharts/tooltip/enabled/
+ * Disabled
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/
+ * Disable tooltip and show values on chart instead
+ */
+ enabled: true,
+
+ /**
+ * Enable or disable animation of the tooltip.
+ *
+ * @type {boolean}
+ * @default true
+ * @since 2.3.0
+ */
+ animation: svg,
+
+ /**
+ * The radius of the rounded border corners.
+ *
+ * @sample {highcharts} highcharts/tooltip/bordercolor-default/
+ * 5px by default
+ * @sample {highcharts} highcharts/tooltip/borderradius-0/
+ * Square borders
+ * @sample {highmaps} maps/tooltip/background-border/
+ * Background and border demo
+ */
+ borderRadius: 3,
+
+ /**
+ * For series on a datetime axes, the date format in the tooltip's
+ * header will by default be guessed based on the closest data points.
+ * This member gives the default string representations used for
+ * each unit. For an overview of the replacement codes, see
+ * [dateFormat](/class-reference/Highcharts#dateFormat).
+ *
+ * @see [xAxis.dateTimeLabelFormats](#xAxis.dateTimeLabelFormats)
+ *
+ * @type {Highcharts.Dictionary}
+ * @product highcharts highstock gantt
+ */
+ dateTimeLabelFormats: {
+ millisecond: '%A, %b %e, %H:%M:%S.%L',
+ second: '%A, %b %e, %H:%M:%S',
+ minute: '%A, %b %e, %H:%M',
+ hour: '%A, %b %e, %H:%M',
+ day: '%A, %b %e, %Y',
+ week: 'Week from %A, %b %e, %Y',
+ month: '%B %Y',
+ year: '%Y'
+ },
+
+ /**
+ * A string to append to the tooltip format.
+ *
+ * @sample {highcharts} highcharts/tooltip/footerformat/
+ * A table for value alignment
+ * @sample {highmaps} maps/tooltip/format/
+ * Format demo
+ *
+ * @since 2.2
+ */
+ footerFormat: '',
+
+ /**
+ * Padding inside the tooltip, in pixels.
+ *
+ * @since 5.0.0
+ */
+ padding: 8,
+
+ /**
+ * Proximity snap for graphs or single points. It defaults to 10 for
+ * mouse-powered devices and 25 for touch devices.
*
- * @productdesc {highstock} In Highstock, tooltips are split by default
- * since v6.0.0. Stock charts typically contain multi-dimension points
- * and multiple panes, making split tooltips the preferred layout over
- * the previous `shared` tooltip.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/tooltip/split/ Split tooltip
- * @sample {highstock} highcharts/tooltip/split/ Split tooltip
- * @sample {highmaps} highcharts/tooltip/split/ Split tooltip
- * @default {highcharts} false
- * @default {highstock} true
+ * Note that in most cases the whole plot area captures the mouse
+ * movement, and in these cases `tooltip.snap` doesn't make sense. This
+ * applies when [stickyTracking](#plotOptions.series.stickyTracking)
+ * is `true` (default) and when the tooltip is [shared](#tooltip.shared)
+ * or [split](#tooltip.split).
+ *
+ * @sample {highcharts} highcharts/tooltip/bordercolor-default/
+ * 10 px by default
+ * @sample {highcharts} highcharts/tooltip/snap-50/
+ * 50 px on graph
+ *
+ * @type {number}
+ * @default 10/25
+ * @since 1.2.0
* @product highcharts highstock
- * @since 5.0.0
- * @apioption tooltip.split
*/
+ snap: isTouchDevice ? 25 : 10,
+ /**
+ * The HTML of the tooltip header line. Variables are enclosed by
+ * curly brackets. Available variables are `point.key`, `series.name`,
+ * `series.color` and other members from the `point` and `series`
+ * objects. The `point.key` variable contains the category name, x
+ * value or datetime string depending on the type of axis. For datetime
+ * axes, the `point.key` date format can be set using
+ * `tooltip.xDateFormat`.
+ *
+ * @sample {highcharts} highcharts/tooltip/footerformat/
+ * An HTML table in the tooltip
+ * @sample {highstock} highcharts/tooltip/footerformat/
+ * An HTML table in the tooltip
+ * @sample {highmaps} maps/tooltip/format/
+ * Format demo
+ *
+ * @type {string}
+ * @apioption tooltip.headerFormat
+ */
+ headerFormat: '{point.key} ',
/**
- * Use HTML to render the contents of the tooltip instead of SVG. Using
- * HTML allows advanced formatting like tables and images in the tooltip.
- * It is also recommended for rtl languages as it works around rtl
- * bugs in early Firefox.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/tooltip/footerformat/ A table for value alignment
- * @sample {highcharts} highcharts/tooltip/fullhtml/ Full HTML tooltip
- * @sample {highstock} highcharts/tooltip/footerformat/ A table for value alignment
- * @sample {highstock} highcharts/tooltip/fullhtml/ Full HTML tooltip
- * @sample {highmaps} maps/tooltip/usehtml/ Pure HTML tooltip
- * @default false
- * @since 2.2
- * @apioption tooltip.useHTML
+ * The HTML of the point's line in the tooltip. Variables are enclosed
+ * by curly brackets. Available variables are point.x, point.y, series.
+ * name and series.color and other properties on the same form.
+ * Furthermore, `point.y` can be extended by the `tooltip.valuePrefix`
+ * and `tooltip.valueSuffix` variables. This can also be overridden for
+ * each series, which makes it a good hook for displaying units.
+ *
+ * In styled mode, the dot is colored by a class name rather
+ * than the point color.
+ *
+ * @sample {highcharts} highcharts/tooltip/pointformat/
+ * A different point format with value suffix
+ * @sample {highmaps} maps/tooltip/format/
+ * Format demo
+ *
+ * @type {string}
+ * @default \u25CF {series.name}: {point.y}
+ * @since 2.2
+ * @apioption tooltip.pointFormat
*/
+ pointFormat: '\u25CF {series.name}: {point.y} ',
/**
- * How many decimals to show in each series' y value. This is overridable
- * in each series' tooltip options object. The default is to preserve
- * all decimals.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
- * @sample {highstock} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
- * @sample {highmaps} maps/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
- * @since 2.2
- * @apioption tooltip.valueDecimals
+ * The background color or gradient for the tooltip.
+ *
+ * In styled mode, the stroke width is set in the
+ * `.highcharts-tooltip-box` class.
+ *
+ * @sample {highcharts} highcharts/tooltip/backgroundcolor-solid/
+ * Yellowish background
+ * @sample {highcharts} highcharts/tooltip/backgroundcolor-gradient/
+ * Gradient
+ * @sample {highcharts} highcharts/css/tooltip-border-background/
+ * Tooltip in styled mode
+ * @sample {highstock} stock/tooltip/general/
+ * Custom tooltip
+ * @sample {highstock} highcharts/css/tooltip-border-background/
+ * Tooltip in styled mode
+ * @sample {highmaps} maps/tooltip/background-border/
+ * Background and border demo
+ * @sample {highmaps} highcharts/css/tooltip-border-background/
+ * Tooltip in styled mode
+ *
+ * @type {Highcharts.ColorString}
*/
+ backgroundColor: color('#f7f7f7')
+ .setOpacity(0.85).get(),
/**
- * A string to prepend to each series' y value. Overridable in each
- * series' tooltip options object.
- *
- * @type {String}
- * @sample {highcharts} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
- * @sample {highstock} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
- * @sample {highmaps} maps/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
- * @since 2.2
- * @apioption tooltip.valuePrefix
+ * The pixel width of the tooltip border.
+ *
+ * In styled mode, the stroke width is set in the
+ * `.highcharts-tooltip-box` class.
+ *
+ * @sample {highcharts} highcharts/tooltip/bordercolor-default/
+ * 2px by default
+ * @sample {highcharts} highcharts/tooltip/borderwidth/
+ * No border (shadow only)
+ * @sample {highcharts} highcharts/css/tooltip-border-background/
+ * Tooltip in styled mode
+ * @sample {highstock} stock/tooltip/general/
+ * Custom tooltip
+ * @sample {highstock} highcharts/css/tooltip-border-background/
+ * Tooltip in styled mode
+ * @sample {highmaps} maps/tooltip/background-border/
+ * Background and border demo
+ * @sample {highmaps} highcharts/css/tooltip-border-background/
+ * Tooltip in styled mode
*/
+ borderWidth: 1,
/**
- * A string to append to each series' y value. Overridable in each series'
- * tooltip options object.
- *
- * @type {String}
- * @sample {highcharts} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
- * @sample {highstock} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
- * @sample {highmaps} maps/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
- * @since 2.2
- * @apioption tooltip.valueSuffix
+ * Whether to apply a drop shadow to the tooltip.
+ *
+ * @sample {highcharts} highcharts/tooltip/bordercolor-default/
+ * True by default
+ * @sample {highcharts} highcharts/tooltip/shadow/
+ * False
+ * @sample {highmaps} maps/tooltip/positioner/
+ * Fixed tooltip position, border and shadow disabled
*/
+ shadow: true,
/**
- * The format for the date in the tooltip header if the X axis is a
- * datetime axis. The default is a best guess based on the smallest
- * distance between points in the chart.
- *
- * @type {String}
- * @sample {highcharts} highcharts/tooltip/xdateformat/ A different format
- * @product highcharts highstock
- * @apioption tooltip.xDateFormat
+ * CSS styles for the tooltip. The tooltip can also be styled through
+ * the CSS class `.highcharts-tooltip`.
+ *
+ * Note that the default `pointerEvents` style makes the tooltip ignore
+ * mouse events, so in order to use clickable tooltips, this value must
+ * be set to `auto`.
+ *
+ * @sample {highcharts} highcharts/tooltip/style/
+ * Greater padding, bold text
+ *
+ * @type {Highcharts.CSSObject}
+ * @default {"color": "#333333", "cursor": "default", "fontSize": "12px", "pointerEvents": "none", "whiteSpace": "nowrap"}
*/
+ style: {
+ /**
+ * @ignore
+ */
+ color: '#333333',
+ /**
+ * @ignore
+ */
+ cursor: 'default',
+ /**
+ * @ignore
+ */
+ fontSize: '12px',
+ /**
+ * @ignore
+ */
+ pointerEvents: 'none',
+ /**
+ * @ignore
+ */
+ whiteSpace: 'nowrap'
+ }
},
@@ -9745,216 +13180,182 @@
*/
credits: {
+ /**
+ * Credits for map source to be concatenated with conventional credit
+ * text. By default this is a format string that collects copyright
+ * information from the map if available.
+ *
+ * @see [mapTextFull](#credits.mapTextFull)
+ * @see [text](#credits.text)
+ *
+ * @type {string}
+ * @default \u00a9 {geojson.copyrightShort}
+ * @since 4.2.2
+ * @product highmaps
+ * @apioption credits.mapText
+ */
+
+ /**
+ * Detailed credits for map source to be displayed on hover of credits
+ * text. By default this is a format string that collects copyright
+ * information from the map if available.
+ *
+ * @see [mapText](#credits.mapText)
+ * @see [text](#credits.text)
+ *
+ * @type {string}
+ * @default {geojson.copyright}
+ * @since 4.2.2
+ * @product highmaps
+ * @apioption credits.mapTextFull
+ */
+
/**
* Whether to show the credits text.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/credits/enabled-false/ Credits disabled
- * @sample {highstock} stock/credits/enabled/ Credits disabled
- * @sample {highmaps} maps/credits/enabled-false/ Credits disabled
- * @default true
+ *
+ * @sample {highcharts} highcharts/credits/enabled-false/
+ * Credits disabled
+ * @sample {highstock} stock/credits/enabled/
+ * Credits disabled
+ * @sample {highmaps} maps/credits/enabled-false/
+ * Credits disabled
*/
enabled: true,
/**
* The URL for the credits label.
- *
- * @type {String}
- * @sample {highcharts} highcharts/credits/href/ Custom URL and text
- * @sample {highmaps} maps/credits/customized/ Custom URL and text
- * @default {highcharts} http://www.highcharts.com
- * @default {highstock} "http://www.highcharts.com"
- * @default {highmaps} http://www.highcharts.com
+ *
+ * @sample {highcharts} highcharts/credits/href/
+ * Custom URL and text
+ * @sample {highmaps} maps/credits/customized/
+ * Custom URL and text
*/
- href: 'http://www.highcharts.com',
+ href: 'https://www.highcharts.com?credits',
/**
* Position configuration for the credits label.
- *
- * @type {Object}
- * @sample {highcharts} highcharts/credits/position-left/ Left aligned
- * @sample {highcharts} highcharts/credits/position-left/ Left aligned
- * @sample {highmaps} maps/credits/customized/ Left aligned
- * @sample {highmaps} maps/credits/customized/ Left aligned
+ *
+ * @sample {highcharts} highcharts/credits/position-left/
+ * Left aligned
+ * @sample {highcharts} highcharts/credits/position-left/
+ * Left aligned
+ * @sample {highmaps} maps/credits/customized/
+ * Left aligned
+ * @sample {highmaps} maps/credits/customized/
+ * Left aligned
+ *
+ * @type {Highcharts.AlignObject}
* @since 2.1
*/
position: {
/**
* Horizontal alignment of the credits.
- *
- * @validvalue ["left", "center", "right"]
- * @type {String}
- * @default right
+ *
+ * @type {Highcharts.AlignType}
*/
align: 'right',
/**
* Horizontal pixel offset of the credits.
- *
- * @type {Number}
- * @default -10
*/
x: -10,
/**
* Vertical alignment of the credits.
- *
- * @validvalue ["top", "middle", "bottom"]
- * @type {String}
- * @default bottom
+ *
+ * @type {Highcharts.VerticalAlignType}
*/
verticalAlign: 'bottom',
/**
* Vertical pixel offset of the credits.
- *
- * @type {Number}
- * @default -5
*/
y: -5
- },
+ },
/**
* CSS styles for the credits label.
- *
- * @type {CSSObject}
+ *
* @see In styled mode, credits styles can be set with the
- * `.highcharts-credits` class.
- * @default { "cursor": "pointer", "color": "#999999", "fontSize": "10px" }
+ * `.highcharts-credits` class.
+ *
+ * @type {Highcharts.CSSObject}
+ * @default {"cursor": "pointer", "color": "#999999", "fontSize": "10px"}
*/
style: {
+ /**
+ * @ignore
+ */
cursor: 'pointer',
+ /**
+ * @ignore
+ */
color: '#999999',
+ /**
+ * @ignore
+ */
fontSize: '9px'
},
-
/**
* The text for the credits label.
*
* @productdesc {highmaps}
- * If a map is loaded as GeoJSON, the text defaults to `Highcharts @
- * {map-credits}`. Otherwise, it defaults to `Highcharts.com`.
- *
- * @type {String}
- * @sample {highcharts} highcharts/credits/href/ Custom URL and text
- * @sample {highmaps} maps/credits/customized/ Custom URL and text
- * @default {highcharts|highstock} Highcharts.com
+ * If a map is loaded as GeoJSON, the text defaults to
+ * `Highcharts @ {map-credits}`. Otherwise, it defaults to
+ * `Highcharts.com`.
+ *
+ * @sample {highcharts} highcharts/credits/href/
+ * Custom URL and text
+ * @sample {highmaps} maps/credits/customized/
+ * Custom URL and text
*/
text: 'Highcharts.com'
- }
- };
-
-
-
- /**
- * Sets the getTimezoneOffset function. If the timezone option is set, a default
- * getTimezoneOffset function with that timezone is returned. If not, the
- * specified getTimezoneOffset function is returned. If neither are specified,
- * undefined is returned.
- * @return {function} a getTimezoneOffset function or undefined
- */
- function getTimezoneOffsetOption() {
- var globalOptions = H.defaultOptions.global,
- moment = win.moment;
-
- if (globalOptions.timezone) {
- if (!moment) {
- // getTimezoneOffset-function stays undefined because it depends on
- // Moment.js
- H.error(25);
-
- } else {
- return function(timestamp) {
- return -moment.tz(
- timestamp,
- globalOptions.timezone
- ).utcOffset();
- };
- }
- }
-
- // If not timezone is set, look for the getTimezoneOffset callback
- return globalOptions.useUTC && globalOptions.getTimezoneOffset;
- }
-
- /**
- * Set the time methods globally based on the useUTC option. Time method can be
- * either local time or UTC (default). It is called internally on initiating
- * Highcharts and after running `Highcharts.setOptions`.
- *
- * @private
- */
- function setTimeMethods() {
- var globalOptions = H.defaultOptions.global,
- Date,
- useUTC = globalOptions.useUTC,
- GET = useUTC ? 'getUTC' : 'get',
- SET = useUTC ? 'setUTC' : 'set',
- setters = ['Minutes', 'Hours', 'Day', 'Date', 'Month', 'FullYear'],
- getters = setters.concat(['Milliseconds', 'Seconds']),
- n;
-
- H.Date = Date = globalOptions.Date || win.Date; // Allow using a different Date class
- Date.hcTimezoneOffset = useUTC && globalOptions.timezoneOffset;
- Date.hcGetTimezoneOffset = getTimezoneOffsetOption();
- Date.hcMakeTime = function(year, month, date, hours, minutes, seconds) {
- var d;
- if (useUTC) {
- d = Date.UTC.apply(0, arguments);
- d += getTZOffset(d);
- } else {
- d = new Date(
- year,
- month,
- pick(date, 1),
- pick(hours, 0),
- pick(minutes, 0),
- pick(seconds, 0)
- ).getTime();
- }
- return d;
- };
- // Dynamically set setters and getters. Use for loop, H.each is not yet
- // overridden in oldIE.
- for (n = 0; n < setters.length; n++) {
- Date['hcGet' + setters[n]] = GET + setters[n];
- }
- for (n = 0; n < getters.length; n++) {
- Date['hcSet' + getters[n]] = SET + getters[n];
}
- }
+ };
/**
* Merge the default options with custom options and return the new options
* structure. Commonly used for defining reusable templates.
*
- * @function #setOptions
- * @memberOf Highcharts
* @sample highcharts/global/useutc-false Setting a global option
* @sample highcharts/members/setoptions Applying a global theme
- * @param {Object} options The new custom chart options.
- * @returns {Object} Updated options.
+ *
+ * @function Highcharts.setOptions
+ *
+ * @param {Highcharts.Options} options
+ * The new custom chart options.
+ *
+ * @return {Highcharts.Options}
+ * Updated options.
*/
- H.setOptions = function(options) {
+ H.setOptions = function (options) {
// Copy in the default options
H.defaultOptions = merge(true, H.defaultOptions, options);
- // Apply UTC
- setTimeMethods();
+ // Update the time object
+ H.time.update(
+ merge(H.defaultOptions.global, H.defaultOptions.time),
+ false
+ );
return H.defaultOptions;
};
/**
- * Get the updated default options. Until 3.0.7, merely exposing defaultOptions for outside modules
- * wasn't enough because the setOptions method created a new object.
+ * Get the updated default options. Until 3.0.7, merely exposing defaultOptions
+ * for outside modules wasn't enough because the setOptions method created a new
+ * object.
+ *
+ * @function Highcharts.getOptions
+ *
+ * @return {Highcharts.Options}
*/
- H.getOptions = function() {
+ H.getOptions = function () {
return H.defaultOptions;
};
@@ -9962,44 +13363,117 @@
// Series defaults
H.defaultPlotOptions = H.defaultOptions.plotOptions;
- // set the default time methods
- setTimeMethods();
+
+ /**
+ * Global `Time` object with default options. Since v6.0.5, time settings can be
+ * applied individually for each chart. If no individual settings apply, this
+ * `Time` object is shared by all instances.
+ *
+ * @name Highcharts.time
+ * @type {Highcharts.Time}
+ */
+ H.time = new H.Time(merge(H.defaultOptions.global, H.defaultOptions.time));
+
+ /**
+ * Formats a JavaScript date timestamp (milliseconds since Jan 1st 1970) into a
+ * human readable date string. The format is a subset of the formats for PHP's
+ * [strftime](http://www.php.net/manual/en/function.strftime.php) function.
+ * Additional formats can be given in the {@link Highcharts.dateFormats} hook.
+ *
+ * Since v6.0.5, all internal dates are formatted through the
+ * {@link Highcharts.Chart#time} instance to respect chart-level time settings.
+ * The `Highcharts.dateFormat` function only reflects global time settings set
+ * with `setOptions`.
+ *
+ * @function Highcharts.dateFormat
+ *
+ * @param {string} format
+ * The desired format where various time representations are prefixed
+ * with `%`.
+ *
+ * @param {number} timestamp
+ * The JavaScript timestamp.
+ *
+ * @param {boolean} [capitalize=false]
+ * Upper case first letter in the return.
+ *
+ * @return {string}
+ * The formatted date.
+ */
+ H.dateFormat = function (format, timestamp, capitalize) {
+ return H.time.dateFormat(format, timestamp, capitalize);
+ };
+
+
+
}(Highcharts));
- (function(H) {
+ (function (H) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
+
var correctFloat = H.correctFloat,
defined = H.defined,
destroyObjectProperties = H.destroyObjectProperties,
+ fireEvent = H.fireEvent,
isNumber = H.isNumber,
merge = H.merge,
pick = H.pick,
deg2rad = H.deg2rad;
/**
- * The Tick class
+ * The Tick class.
+ *
+ * @private
+ * @class
+ * @name Highcharts.Tick
+ *
+ * @param {Highcharts.Axis} axis
+ *
+ * @param {number} pos The position of the tick on the axis.
+ *
+ * @param {string} [type] The type of tick.
+ *
+ * @param {boolean} [noLabel=false] Wether to disable the label or not. Defaults to
+ * false.
+ *
+ * @param {object} [parameters] Optional parameters for the tick.
+ *
+ * @param {object} [parameters.tickmarkOffset] Set tickmarkOffset for the tick.
+ *
+ * @param {object} [parameters.category] Set category for the tick.
*/
- H.Tick = function(axis, pos, type, noLabel) {
+ H.Tick = function (axis, pos, type, noLabel, parameters) {
this.axis = axis;
this.pos = pos;
this.type = type || '';
this.isNew = true;
this.isNewLabel = true;
+ this.parameters = parameters || {};
+ // Usually undefined, numeric for grid axes
+ this.tickmarkOffset = this.parameters.tickmarkOffset;
+ this.options = this.parameters.options;
if (!type && !noLabel) {
this.addLabel();
}
};
+ /** @lends Highcharts.Tick.prototype */
H.Tick.prototype = {
+
/**
- * Write the tick label
+ * Write the tick label.
+ *
+ * @private
+ * @function Highcharts.Tick#addLabel
*/
- addLabel: function() {
+ addLabel: function () {
var tick = this,
axis = tick.axis,
options = axis.options,
@@ -10007,78 +13481,137 @@
categories = axis.categories,
names = axis.names,
pos = tick.pos,
- labelOptions = options.labels,
+ labelOptions = pick(
+ tick.options && tick.options.labels,
+ options.labels
+ ),
str,
tickPositions = axis.tickPositions,
isFirst = pos === tickPositions[0],
isLast = pos === tickPositions[tickPositions.length - 1],
- value = categories ?
- pick(categories[pos], names[pos], pos) :
- pos,
+ value = this.parameters.category || (
+ categories ?
+ pick(categories[pos], names[pos], pos) :
+ pos
+ ),
label = tick.label,
tickPositionInfo = tickPositions.info,
- dateTimeLabelFormat;
+ dateTimeLabelFormat,
+ dateTimeLabelFormats,
+ i,
+ list;
// Set the datetime label format. If a higher rank is set for this
// position, use that. If not, use the general format.
if (axis.isDatetimeAxis && tickPositionInfo) {
- dateTimeLabelFormat =
+ dateTimeLabelFormats = chart.time.resolveDTLFormat(
options.dateTimeLabelFormats[
- tickPositionInfo.higherRanks[pos] ||
+ (
+ !options.grid &&
+ tickPositionInfo.higherRanks[pos]
+ ) ||
tickPositionInfo.unitName
- ];
+ ]
+ );
+ dateTimeLabelFormat = dateTimeLabelFormats.main;
}
+
// set properties for access in render method
tick.isFirst = isFirst;
tick.isLast = isLast;
- // get the string
- str = axis.labelFormatter.call({
+ // Get the string
+ tick.formatCtx = {
axis: axis,
chart: chart,
isFirst: isFirst,
isLast: isLast,
dateTimeLabelFormat: dateTimeLabelFormat,
+ tickPositionInfo: tickPositionInfo,
value: axis.isLog ? correctFloat(axis.lin2log(value)) : value,
pos: pos
- });
+ };
+ str = axis.labelFormatter.call(tick.formatCtx, this.formatCtx);
+
+ // Set up conditional formatting based on the format list if existing.
+ list = dateTimeLabelFormats && dateTimeLabelFormats.list;
+ if (list) {
+ tick.shortenLabel = function () {
+ for (i = 0; i < list.length; i++) {
+ label.attr({
+ text: axis.labelFormatter.call(H.extend(
+ tick.formatCtx,
+ { dateTimeLabelFormat: list[i] }
+ ))
+ });
+ if (
+ label.getBBox().width <
+ axis.getSlotWidth(tick) - 2 *
+ pick(labelOptions.padding, 5)
+ ) {
+ return;
+ }
+ }
+ label.attr({
+ text: ''
+ });
+ };
+ }
// first call
if (!defined(label)) {
tick.label = label =
defined(str) && labelOptions.enabled ?
- chart.renderer.text(
- str,
- 0,
- 0,
- labelOptions.useHTML
- )
+ chart.renderer
+ .text(
+ str,
+ 0,
+ 0,
+ labelOptions.useHTML
+ )
+ .add(axis.labelGroup) :
+ null;
+
+ // Un-rotated length
+ if (label) {
+ // Without position absolute, IE export sometimes is wrong
+ if (!chart.styledMode) {
+ label.css(merge(labelOptions.style));
+ }
- // without position absolute, IE export sometimes is
- // wrong.
- .css(merge(labelOptions.style))
+ label.textPxLength = label.getBBox().width;
+ }
- .add(axis.labelGroup) :
- null;
- // Un-rotated length
- tick.labelLength = label && label.getBBox().width;
// Base value to detect change for new calls to getBBox
tick.rotation = 0;
- // update
- } else if (label) {
- label.attr({
- text: str
- });
+ // update
+ } else if (label && label.textStr !== str) {
+ // When resetting text, also reset the width if dynamically set
+ // (#8809)
+ if (
+ label.textWidth &&
+ !(labelOptions.style && labelOptions.style.width) &&
+ !label.styles.width
+ ) {
+ label.css({ width: null });
+ }
+
+ label.attr({ text: str });
}
},
/**
* Get the offset height or width of the label
+ *
+ * @private
+ * @function Highcharts.Tick#getLabelSize
+ *
+ * @return {number}
*/
- getLabelSize: function() {
+ getLabelSize: function () {
return this.label ?
this.label.getBBox()[this.axis.horiz ? 'height' : 'width'] :
0;
@@ -10087,26 +13620,34 @@
/**
* Handle the label overflow by adjusting the labels to the left and right
* edge, or hide them if they collide into the neighbour label.
+ *
+ * @private
+ * @function Highcharts.Tick#handleOverflow
+ *
+ * @param {Highcharts.PositionObject} xy
*/
- handleOverflow: function(xy) {
- var axis = this.axis,
+ handleOverflow: function (xy) {
+ var tick = this,
+ axis = this.axis,
+ labelOptions = axis.options.labels,
pxPos = xy.x,
chartWidth = axis.chart.chartWidth,
spacing = axis.chart.spacing,
leftBound = pick(axis.labelLeft, Math.min(axis.pos, spacing[3])),
rightBound = pick(
axis.labelRight,
- Math.max(axis.pos + axis.len, chartWidth - spacing[1])
+ Math.max(
+ !axis.isRadial ? axis.pos + axis.len : 0,
+ chartWidth - spacing[1]
+ )
),
label = this.label,
rotation = this.rotation,
- factor = {
- left: 0,
- center: 0.5,
- right: 1
- }[axis.labelAlign],
+ factor = { left: 0, center: 0.5, right: 1 }[
+ axis.labelAlign || label.attr('align')
+ ],
labelWidth = label.getBBox().width,
- slotWidth = axis.getSlotWidth(),
+ slotWidth = axis.getSlotWidth(tick),
modifiedSlotWidth = slotWidth,
xCorrection = factor,
goRight = 1,
@@ -10117,12 +13658,13 @@
// Check if the label overshoots the chart spacing box. If it does, move
// it. If it now overshoots the slotWidth, add ellipsis.
- if (!rotation) {
+ if (!rotation && pick(labelOptions.overflow, 'justify') === 'justify') {
leftPos = pxPos - factor * labelWidth;
rightPos = pxPos + (1 - factor) * labelWidth;
if (leftPos < leftBound) {
- modifiedSlotWidth = xy.x + modifiedSlotWidth * (1 - factor) - leftBound;
+ modifiedSlotWidth =
+ xy.x + modifiedSlotWidth * (1 - factor) - leftBound;
} else if (rightPos > rightBound) {
modifiedSlotWidth =
rightBound - xy.x + modifiedSlotWidth * factor;
@@ -10153,8 +13695,8 @@
textWidth = modifiedSlotWidth;
}
- // Add ellipsis to prevent rotated labels to be clipped against the edge
- // of the chart
+ // Add ellipsis to prevent rotated labels to be clipped against the edge
+ // of the chart
} else if (rotation < 0 && pxPos - factor * labelWidth < leftBound) {
textWidth = Math.round(
pxPos / Math.cos(rotation * deg2rad) - leftBound
@@ -10166,26 +13708,47 @@
}
if (textWidth) {
- css.width = textWidth;
- if (!(axis.options.labels.style || {}).textOverflow) {
- css.textOverflow = 'ellipsis';
+ if (tick.shortenLabel) {
+ tick.shortenLabel();
+ } else {
+ css.width = Math.floor(textWidth);
+ if (!(labelOptions.style || {}).textOverflow) {
+ css.textOverflow = 'ellipsis';
+ }
+ label.css(css);
+
}
- label.css(css);
}
},
/**
* Get the x and y position for ticks and labels
+ *
+ * @private
+ * @function Highcharts.Tick#getPosition
+ *
+ * @param {boolean} horiz
+ *
+ * @param {number} tickPos
+ *
+ * @param {number} tickmarkOffset
+ *
+ * @param {boolean} [old]
+ *
+ * @return {number}
+ *
+ * @fires Highcharts.Tick#event:afterGetPosition
*/
- getPosition: function(horiz, pos, tickmarkOffset, old) {
+ getPosition: function (horiz, tickPos, tickmarkOffset, old) {
var axis = this.axis,
chart = axis.chart,
- cHeight = (old && chart.oldChartHeight) || chart.chartHeight;
+ cHeight = (old && chart.oldChartHeight) || chart.chartHeight,
+ pos;
- return {
+ pos = {
x: horiz ?
- (
- axis.translate(pos + tickmarkOffset, null, null, old) +
+ H.correctFloat(
+ axis.translate(tickPos + tickmarkOffset, null, null, old) +
axis.transB
) :
(
@@ -10193,15 +13756,15 @@
axis.offset +
(
axis.opposite ?
- (
(
- (old && chart.oldChartWidth) ||
- chart.chartWidth
- ) -
- axis.right -
- axis.left
- ) :
- 0
+ (
+ (old && chart.oldChartWidth) ||
+ chart.chartWidth
+ ) -
+ axis.right -
+ axis.left
+ ) :
+ 0
)
),
@@ -10212,19 +13775,26 @@
axis.offset -
(axis.opposite ? axis.height : 0)
) :
- (
+ H.correctFloat(
cHeight -
- axis.translate(pos + tickmarkOffset, null, null, old) -
+ axis.translate(tickPos + tickmarkOffset, null, null, old) -
axis.transB
)
};
+ fireEvent(this, 'afterGetPosition', { pos: pos });
+
+ return pos;
+
},
/**
* Get the x, y position of the tick label
+ *
+ * @private
+ *
*/
- getLabelPosition: function(
+ getLabelPosition: function (
x,
y,
label,
@@ -10234,16 +13804,24 @@
index,
step
) {
+
var axis = this.axis,
transA = axis.transA,
reversed = axis.reversed,
staggerLines = axis.staggerLines,
- rotCorr = axis.tickRotCorr || {
- x: 0,
- y: 0
- },
+ rotCorr = axis.tickRotCorr || { x: 0, y: 0 },
yOffset = labelOptions.y,
- line;
+
+ // Adjust for label alignment if we use reserveSpace: true (#5286)
+ labelOffsetCorrection = (
+ !horiz && !axis.reserveSpaceDefault ?
+ -axis.labelOffset * (
+ axis.labelAlign === 'center' ? 0.5 : 1
+ ) :
+ 0
+ ),
+ line,
+ pos = {};
if (!defined(yOffset)) {
if (axis.side === 0) {
@@ -10257,8 +13835,15 @@
}
}
- x = x + labelOptions.x + rotCorr.x - (tickmarkOffset && horiz ?
- tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
+ x = x +
+ labelOptions.x +
+ labelOffsetCorrection +
+ rotCorr.x -
+ (
+ tickmarkOffset && horiz ?
+ tickmarkOffset * transA * (reversed ? -1 : 1) :
+ 0
+ );
y = y + yOffset - (tickmarkOffset && !horiz ?
tickmarkOffset * transA * (reversed ? 1 : -1) : 0);
@@ -10271,16 +13856,25 @@
y += line * (axis.labelOffset / staggerLines);
}
- return {
- x: x,
- y: Math.round(y)
- };
+ pos.x = x;
+ pos.y = Math.round(y);
+
+ fireEvent(
+ this,
+ 'afterGetLabelPosition',
+ { pos: pos, tickmarkOffset: tickmarkOffset, index: index }
+ );
+
+ return pos;
},
/**
* Extendible method to return the path of the marker
+ *
+ * @private
+ *
*/
- getMarkPath: function(x, y, tickLength, tickWidth, horiz, renderer) {
+ getMarkPath: function (x, y, tickLength, tickWidth, horiz, renderer) {
return renderer.crispLine([
'M',
x,
@@ -10293,12 +13887,15 @@
/**
* Renders the gridLine.
+ *
+ * @private
+ *
* @param {Boolean} old Whether or not the tick is old
* @param {number} opacity The opacity of the grid line
* @param {number} reverseCrisp Modifier for avoiding overlapping 1 or -1
* @return {undefined}
*/
- renderGridLine: function(old, opacity, reverseCrisp) {
+ renderGridLine: function (old, opacity, reverseCrisp) {
var tick = this,
axis = tick.axis,
options = axis.options,
@@ -10307,29 +13904,26 @@
attribs = {},
pos = tick.pos,
type = tick.type,
- tickmarkOffset = axis.tickmarkOffset,
- renderer = axis.chart.renderer;
-
-
- var gridPrefix = type ? type + 'Grid' : 'grid',
+ tickmarkOffset = pick(tick.tickmarkOffset, axis.tickmarkOffset),
+ renderer = axis.chart.renderer,
+ gridPrefix = type ? type + 'Grid' : 'grid',
gridLineWidth = options[gridPrefix + 'LineWidth'],
gridLineColor = options[gridPrefix + 'LineColor'],
dashStyle = options[gridPrefix + 'LineDashStyle'];
-
if (!gridLine) {
-
- attribs.stroke = gridLineColor;
- attribs['stroke-width'] = gridLineWidth;
- if (dashStyle) {
- attribs.dashstyle = dashStyle;
+ if (!axis.chart.styledMode) {
+ attribs.stroke = gridLineColor;
+ attribs['stroke-width'] = gridLineWidth;
+ if (dashStyle) {
+ attribs.dashstyle = dashStyle;
+ }
}
-
if (!type) {
attribs.zIndex = 1;
}
if (old) {
- attribs.opacity = 0;
+ opacity = 0;
}
tick.gridLine = gridLine = renderer.path()
.attr(attribs)
@@ -10337,18 +13931,21 @@
'highcharts-' + (type ? type + '-' : '') + 'grid-line'
)
.add(axis.gridGroup);
+
}
- // If the parameter 'old' is set, the current call will be followed
- // by another call, therefore do not do any animations this time
- if (!old && gridLine) {
+ if (gridLine) {
gridLinePath = axis.getPlotLinePath(
pos + tickmarkOffset,
gridLine.strokeWidth() * reverseCrisp,
- old, true
+ old,
+ 'pass'
);
+
+ // If the parameter 'old' is set, the current call will be followed
+ // by another call, therefore do not do any animations this time
if (gridLinePath) {
- gridLine[tick.isNew ? 'attr' : 'animate']({
+ gridLine[old || tick.isNew ? 'attr' : 'animate']({
d: gridLinePath,
opacity: opacity
});
@@ -10358,6 +13955,9 @@
/**
* Renders the tick mark.
+ *
+ * @private
+ *
* @param {Object} xy The position vector of the mark
* @param {number} xy.x The x position of the mark
* @param {number} xy.y The y position of the mark
@@ -10365,7 +13965,7 @@
* @param {number} reverseCrisp Modifier for avoiding overlapping 1 or -1
* @return {undefined}
*/
- renderMark: function(xy, opacity, reverseCrisp) {
+ renderMark: function (xy, opacity, reverseCrisp) {
var tick = this,
axis = tick.axis,
options = axis.options,
@@ -10376,15 +13976,13 @@
mark = tick.mark,
isNewMark = !mark,
x = xy.x,
- y = xy.y;
-
-
- var tickWidth = pick(
- options[tickPrefix + 'Width'], !type && axis.isXAxis ? 1 : 0
+ y = xy.y,
+ tickWidth = pick(
+ options[tickPrefix + 'Width'],
+ !type && axis.isXAxis ? 1 : 0
), // X axis defaults to 1
tickColor = options[tickPrefix + 'Color'];
-
if (tickSize) {
// negate the length
@@ -10398,12 +13996,12 @@
.addClass('highcharts-' + (type ? type + '-' : '') + 'tick')
.add(axis.axisGroup);
-
- mark.attr({
- stroke: tickColor,
- 'stroke-width': tickWidth
- });
-
+ if (!axis.chart.styledMode) {
+ mark.attr({
+ stroke: tickColor,
+ 'stroke-width': tickWidth
+ });
+ }
}
mark[isNewMark ? 'attr' : 'animate']({
d: tick.getMarkPath(
@@ -10412,7 +14010,8 @@
tickSize[0],
mark.strokeWidth() * reverseCrisp,
axis.horiz,
- renderer),
+ renderer
+ ),
opacity: opacity
});
@@ -10423,6 +14022,9 @@
* Renders the tick label.
* Note: The label should already be created in init(), so it should only
* have to be moved into place.
+ *
+ * @private
+ *
* @param {Object} xy The position vector of the label
* @param {number} xy.x The x position of the label
* @param {number} xy.y The y position of the label
@@ -10431,7 +14033,7 @@
* @param {number} index The index of the tick
* @return {undefined}
*/
- renderLabel: function(xy, old, opacity, index) {
+ renderLabel: function (xy, old, opacity, index) {
var tick = this,
axis = tick.axis,
horiz = axis.horiz,
@@ -10439,10 +14041,11 @@
label = tick.label,
labelOptions = options.labels,
step = labelOptions.step,
- tickmarkOffset = axis.tickmarkOffset,
+ tickmarkOffset = pick(tick.tickmarkOffset, axis.tickmarkOffset),
show = true,
x = xy.x,
y = xy.y;
+
if (label && isNumber(x)) {
label.xy = xy = tick.getLabelPosition(
x,
@@ -10472,9 +14075,14 @@
) {
show = false;
- // Handle label overflow and show or hide accordingly
- } else if (horiz && !axis.isRadial && !labelOptions.step &&
- !labelOptions.rotation && !old && opacity !== 0) {
+ // Handle label overflow and show or hide accordingly
+ } else if (
+ horiz &&
+ !labelOptions.step &&
+ !labelOptions.rotation &&
+ !old &&
+ opacity !== 0
+ ) {
tick.handleOverflow(xy);
}
@@ -10499,16 +14107,18 @@
/**
* Put everything in place
*
+ * @private
+ *
* @param index {Number}
* @param old {Boolean} Use old coordinates to prepare an animation into new
* position
*/
- render: function(index, old, opacity) {
+ render: function (index, old, opacity) {
var tick = this,
axis = tick.axis,
horiz = axis.horiz,
pos = tick.pos,
- tickmarkOffset = axis.tickmarkOffset,
+ tickmarkOffset = pick(tick.tickmarkOffset, axis.tickmarkOffset),
xy = tick.getPosition(horiz, pos, tickmarkOffset, old),
x = xy.x,
y = xy.y,
@@ -10528,24 +14138,182 @@
this.renderLabel(xy, old, opacity, index);
tick.isNew = false;
+
+ H.fireEvent(this, 'afterRender');
},
/**
* Destructor for the tick prototype
+ *
+ * @private
+ * @function Highcharts.Tick#destroy
*/
- destroy: function() {
+ destroy: function () {
destroyObjectProperties(this, this.axis);
}
};
}(Highcharts));
- var Axis = (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ var Axis = (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+ /**
+ * @callback Highcharts.AxisEventCallbackFunction
+ *
+ * @param {Highcharts.Axis} this
+ */
+
+ /**
+ * Options for crosshairs on axes.
+ *
+ * @typedef {Highcharts.XAxisCrosshairOptions|Highcharts.YAxisCrosshairOptions} Highcharts.AxisCrosshairOptions
+ */
+
+ /**
+ * @interface Highcharts.AxisLabelsFormatterContextObject
+ *//**
+ * @name Highcharts.AxisLabelsFormatterContextObject#axis
+ * @type {Highcharts.Axis}
+ *//**
+ * @name Highcharts.AxisLabelsFormatterContextObject#chart
+ * @type {Highcharts.Chart}
+ *//**
+ * @name Highcharts.AxisLabelsFormatterContextObject#isFirst
+ * @type {boolean}
+ *//**
+ * @name Highcharts.AxisLabelsFormatterContextObject#isLast
+ * @type {boolean}
+ *//**
+ * @name Highcharts.AxisLabelsFormatterContextObject#value
+ * @type {number}
+ */
+
+ /**
+ * Options for axes.
+ *
+ * @typedef {Highcharts.XAxisOptions|Highcharts.YAxisOptions|Highcharts.ZAxisOptions} Highcharts.AxisOptions
+ */
+
+ /**
+ * The returned object literal from the {@link Highcharts.Axis#getExtremes}
+ * function.
+ *
+ * @interface Highcharts.ExtremesObject
+ *//**
+ * The maximum value of the axis' associated series.
+ * @name Highcharts.ExtremesObject#dataMax
+ * @type {number}
+ *//**
+ * The minimum value of the axis' associated series.
+ * @name Highcharts.ExtremesObject#dataMin
+ * @type {number}
+ *//**
+ * The maximum axis value, either automatic or set manually. If the `max` option
+ * is not set, `maxPadding` is 0 and `endOnTick` is false, this value will be
+ * the same as `dataMax`.
+ * @name Highcharts.ExtremesObject#max
+ * @type {number}
+ *//**
+ * The minimum axis value, either automatic or set manually. If the `min` option
+ * is not set, `minPadding` is 0 and `startOnTick` is false, this value will be
+ * the same as `dataMin`.
+ * @name Highcharts.ExtremesObject#min
+ * @type {number}
+ *//**
+ * The user defined maximum, either from the `max` option or from a zoom or
+ * `setExtremes` action.
+ * @name Highcharts.ExtremesObject#userMax
+ * @type {number}
+ *//**
+ * The user defined minimum, either from the `min` option or from a zoom or
+ * `setExtremes` action.
+ * @name Highcharts.ExtremesObject#userMin
+ * @type {number}
+ */
+
+ /**
+ * @callback Highcharts.AxisPointBreakEventCallbackFunction
+ *
+ * @param {Highcharts.Axis} this
+ *
+ * @param {Highcharts.AxisPointBreakEventObject} event
+ */
+
+ /**
+ * @interface Highcharts.AxisPointBreakEventObject
+ *//**
+ * @name Highcharts.AxisPointBreakEventObject#brk
+ * @type {Highcharts.Dictionary}
+ *//**
+ * @name Highcharts.AxisPointBreakEventObject#point
+ * @type {Highcharts.Point}
+ *//**
+ * @name Highcharts.AxisPointBreakEventObject#preventDefault
+ * @type {Function}
+ *//**
+ * @name Highcharts.AxisPointBreakEventObject#target
+ * @type {Highcharts.SVGElement}
+ *//**
+ * @name Highcharts.AxisPointBreakEventObject#type
+ * @type {"pointBreak"|"pointInBreak"}
+ */
+
+ /**
+ * @callback Highcharts.AxisSetExtremesEventCallbackFunction
+ *
+ * @param {Highcharts.Axis} this
+ *
+ * @param {Highcharts.AxisSetExtremesEventObject} event
+ */
+
+ /**
+ * @interface Highcharts.AxisSetExtremesEventObject
+ *//**
+ * @name Highcharts.AxisSetExtremesEventObject#dataMax
+ * @type {number}
+ *//**
+ * @name Highcharts.AxisSetExtremesEventObject#dataMin
+ * @type {number}
+ *//**
+ * @name Highcharts.AxisSetExtremesEventObject#max
+ * @type {number}
+ *//**
+ * @name Highcharts.AxisSetExtremesEventObject#min
+ * @type {number}
+ *//**
+ * @name Highcharts.AxisSetExtremesEventObject#preventDefault
+ * @type {Function}
+ *//**
+ * @name Highcharts.AxisSetExtremesEventObject#target
+ * @type {Highcharts.SVGElement}
+ *//**
+ * @name Highcharts.AxisSetExtremesEventObject#trigger
+ * @type {string}
+ *//**
+ * @name Highcharts.AxisSetExtremesEventObject#type
+ * @type {"setExtremes"}
+ *//**
+ * @name Highcharts.AxisSetExtremesEventObject#userMax
+ * @type {number}
+ *//**
+ * @name Highcharts.AxisSetExtremesEventObject#userMin
+ * @type {number}
+ */
+
+ /**
+ * @callback Highcharts.AxisTickPositionerCallbackFunction
+ *
+ * @param {Highcharts.Axis} this
+ *
+ * @return {Array}
+ */
+
+
+
var addEvent = H.addEvent,
animObject = H.animObject,
arrayMax = H.arrayMax,
@@ -10556,13 +14324,10 @@
defined = H.defined,
deg2rad = H.deg2rad,
destroyObjectProperties = H.destroyObjectProperties,
- each = H.each,
extend = H.extend,
fireEvent = H.fireEvent,
format = H.format,
getMagnitude = H.getMagnitude,
- grep = H.grep,
- inArray = H.inArray,
isArray = H.isArray,
isNumber = H.isNumber,
isString = H.isString,
@@ -10581,82 +14346,106 @@
*
* A chart can have from 0 axes (pie chart) to multiples. In a normal, single
* series cartesian chart, there is one X axis and one Y axis.
- *
+ *
* The X axis or axes are referenced by {@link Highcharts.Chart.xAxis}, which is
* an array of Axis objects. If there is only one axis, it can be referenced
* through `chart.xAxis[0]`, and multiple axes have increasing indices. The same
* pattern goes for Y axes.
- *
+ *
* If you need to get the axes from a series object, use the `series.xAxis` and
* `series.yAxis` properties. These are not arrays, as one series can only be
* associated to one X and one Y axis.
- *
+ *
* A third way to reference the axis programmatically is by `id`. Add an `id` in
* the axis configuration options, and get the axis by
* {@link Highcharts.Chart#get}.
- *
+ *
* Configuration options for the axes are given in options.xAxis and
* options.yAxis.
- *
- * @class Highcharts.Axis
- * @memberOf Highcharts
- * @param {Highcharts.Chart} chart - The Chart instance to apply the axis on.
- * @param {Object} options - Axis options
+ *
+ * @class
+ * @name Highcharts.Axis
+ *
+ * @param {Highcharts.Chart} chart
+ * The Chart instance to apply the axis on.
+ *
+ * @param {Highcharts.AxisOptions} options
+ * Axis options.
*/
- var Axis = function() {
+ var Axis = function () {
this.init.apply(this, arguments);
};
- H.extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ {
+ H.extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */{
/**
* The X axis or category axis. Normally this is the horizontal axis,
* though if the chart is inverted this is the vertical axis. In case of
* multiple axes, the xAxis node is an array of configuration objects.
- *
- * See [the Axis object](#Axis) for programmatic access to the axis.
+ *
+ * See the [Axis class](/class-reference/Highcharts.Axis) for programmatic
+ * access to the axis.
*
* @productdesc {highmaps}
* In Highmaps, the axis is hidden, but it is used behind the scenes to
* control features like zooming and panning. Zooming is in effect the same
* as setting the extremes of one of the exes.
- *
+ *
+ * @type {*|Array<*>}
* @optionparent xAxis
*/
defaultOptions: {
+
+ /**
+ * When using multiple axis, the ticks of two or more opposite axes
+ * will automatically be aligned by adding ticks to the axis or axes
+ * with the least ticks, as if `tickAmount` were specified.
+ *
+ * This can be prevented by setting `alignTicks` to false. If the grid
+ * lines look messy, it's a good idea to hide them for the secondary
+ * axis by setting `gridLineWidth` to 0.
+ *
+ * If `startOnTick` or `endOnTick` in an Axis options are set to false,
+ * then the `alignTicks ` will be disabled for the Axis.
+ *
+ * Disabled for logarithmic axes.
+ *
+ * @type {boolean}
+ * @default true
+ * @product highcharts highstock gantt
+ * @apioption xAxis.alignTicks
+ */
+
/**
* Whether to allow decimals in this axis' ticks. When counting
* integers, like persons or hits on a web page, decimals should
* be avoided in the labels.
*
- * @type {Boolean}
- * @see [minTickInterval](#xAxis.minTickInterval)
- * @sample {highcharts|highstock}
- * highcharts/yaxis/allowdecimals-true/
- * True by default
- * @sample {highcharts|highstock}
- * highcharts/yaxis/allowdecimals-false/
- * False
+ * @see [minTickInterval](#xAxis.minTickInterval)
+ *
+ * @sample {highcharts|highstock} highcharts/yaxis/allowdecimals-true/
+ * True by default
+ * @sample {highcharts|highstock} highcharts/yaxis/allowdecimals-false/
+ * False
+ *
+ * @type {boolean}
* @default true
* @since 2.0
* @apioption xAxis.allowDecimals
*/
- // allowDecimals: null,
-
/**
* When using an alternate grid color, a band is painted across the
* plot area between every other grid line.
*
- * @type {Color}
- * @sample {highcharts} highcharts/yaxis/alternategridcolor/
- * Alternate grid color on the Y axis
- * @sample {highstock} stock/xaxis/alternategridcolor/
- * Alternate grid color on the Y axis
- * @default null
+ * @sample {highcharts} highcharts/yaxis/alternategridcolor/
+ * Alternate grid color on the Y axis
+ * @sample {highstock} stock/xaxis/alternategridcolor/
+ * Alternate grid color on the Y axis
+ *
+ * @type {Highcharts.ColorString}
* @apioption xAxis.alternateGridColor
*/
- // alternateGridColor: null,
/**
* An array defining breaks in the axis, the sections defined will be
@@ -10665,18 +14454,16 @@
* @productdesc {highcharts}
* Requires that the broken-axis.js module is loaded.
*
- * @type {Array}
- * @sample {highcharts}
- * highcharts/axisbreak/break-simple/
- * Simple break
- * @sample {highcharts|highstock}
- * highcharts/axisbreak/break-visualized/
- * Advanced with callback
- * @sample {highstock}
- * stock/demo/intraday-breaks/
- * Break on nights and weekends
+ * @sample {highcharts} highcharts/axisbreak/break-simple/
+ * Simple break
+ * @sample {highcharts|highstock} highcharts/axisbreak/break-visualized/
+ * Advanced with callback
+ * @sample {highstock} stock/demo/intraday-breaks/
+ * Break on nights and weekends
+ *
+ * @type {Array<*>}
* @since 4.1.0
- * @product highcharts highstock
+ * @product highcharts highstock gantt
* @apioption xAxis.breaks
*/
@@ -10686,19 +14473,19 @@
* so for instance on a `datetime` axis, a break size of 3600000 would
* indicate the equivalent of an hour.
*
- * @type {Number}
+ * @type {number}
* @default 0
* @since 4.1.0
- * @product highcharts highstock
+ * @product highcharts highstock gantt
* @apioption xAxis.breaks.breakSize
*/
/**
* The point where the break starts.
*
- * @type {Number}
+ * @type {number}
* @since 4.1.0
- * @product highcharts highstock
+ * @product highcharts highstock gantt
* @apioption xAxis.breaks.from
*/
@@ -10706,19 +14493,19 @@
* Defines an interval after which the break appears again. By default
* the breaks do not repeat.
*
- * @type {Number}
+ * @type {number}
* @default 0
* @since 4.1.0
- * @product highcharts highstock
+ * @product highcharts highstock gantt
* @apioption xAxis.breaks.repeat
*/
/**
* The point where the break ends.
*
- * @type {Number}
+ * @type {number}
* @since 4.1.0
- * @product highcharts highstock
+ * @product highcharts highstock gantt
* @apioption xAxis.breaks.to
*/
@@ -10733,27 +14520,28 @@
*
* categories: ['Apples', 'Bananas', 'Oranges']
*
- * @type {Array}
- * @sample {highcharts} highcharts/chart/reflow-true/
- * With
- * @sample {highcharts} highcharts/xaxis/categories/
- * Without
- * @product highcharts
- * @default null
+ * @sample {highcharts} highcharts/demo/line-labels/
+ * With
+ * @sample {highcharts} highcharts/xaxis/categories/
+ * Without
+ *
+ * @type {Array}
+ * @product highcharts gantt
* @apioption xAxis.categories
*/
- // categories: [],
/**
* The highest allowed value for automatically computed axis extremes.
*
- * @type {Number}
- * @see [floor](#xAxis.floor)
- * @sample {highcharts|highstock} highcharts/yaxis/floor-ceiling/
- * Floor and ceiling
- * @since 4.0
- * @product highcharts highstock
- * @apioption xAxis.ceiling
+ * @see [floor](#xAxis.floor)
+ *
+ * @sample {highcharts|highstock} highcharts/yaxis/floor-ceiling/
+ * Floor and ceiling
+ *
+ * @type {number}
+ * @since 4.0
+ * @product highcharts highstock gantt
+ * @apioption xAxis.ceiling
*/
/**
@@ -10761,10 +14549,10 @@
* Highcharts styled mode. The class name is applied to group elements
* for the grid, axis elements and labels.
*
- * @type {String}
- * @sample {highcharts|highstock|highmaps}
- * highcharts/css/axis/
- * Multiple axes with separate styling
+ * @sample {highcharts|highstock|highmaps} highcharts/css/axis/
+ * Multiple axes with separate styling
+ *
+ * @type {string}
* @since 5.0.0
* @apioption xAxis.className
*/
@@ -10778,72 +14566,185 @@
* `.highcharts-xaxis-category` classes.
*
* @productdesc {highstock}
- * In Highstock, bu default, the crosshair is enabled on the X axis and
+ * In Highstock, by default, the crosshair is enabled on the X axis and
* disabled on the Y axis.
*
- * @type {Boolean|Object}
- * @sample {highcharts} highcharts/xaxis/crosshair-both/
- * Crosshair on both axes
- * @sample {highstock} stock/xaxis/crosshairs-xy/
- * Crosshair on both axes
- * @sample {highmaps} highcharts/xaxis/crosshair-both/
- * Crosshair on both axes
- * @default false
- * @since 4.1
- * @apioption xAxis.crosshair
+ * @sample {highcharts} highcharts/xaxis/crosshair-both/
+ * Crosshair on both axes
+ * @sample {highstock} stock/xaxis/crosshairs-xy/
+ * Crosshair on both axes
+ * @sample {highmaps} highcharts/xaxis/crosshair-both/
+ * Crosshair on both axes
+ *
+ * @type {boolean|*}
+ * @default false
+ * @since 4.1
+ * @apioption xAxis.crosshair
+ */
+
+ /**
+ * A class name for the crosshair, especially as a hook for styling.
+ *
+ * @type {string}
+ * @since 5.0.0
+ * @apioption xAxis.crosshair.className
+ */
+
+ /**
+ * The color of the crosshair. Defaults to `#cccccc` for numeric and
+ * datetime axes, and `rgba(204,214,235,0.25)` for category axes, where
+ * the crosshair by default highlights the whole category.
+ *
+ * @sample {highcharts|highstock|highmaps} highcharts/xaxis/crosshair-customized/
+ * Customized crosshairs
+ *
+ * @type {Highcharts.ColorString}
+ * @default #cccccc
+ * @since 4.1
+ * @apioption xAxis.crosshair.color
+ */
+
+ /**
+ * The dash style for the crosshair. See
+ * [series.dashStyle](#plotOptions.series.dashStyle)
+ * for possible values.
+ *
+ * @sample {highcharts|highmaps} highcharts/xaxis/crosshair-dotted/
+ * Dotted crosshair
+ * @sample {highstock} stock/xaxis/crosshair-dashed/
+ * Dashed X axis crosshair
+ *
+ * @type {Highcharts.DashStyleType}
+ * @default Solid
+ * @since 4.1
+ * @apioption xAxis.crosshair.dashStyle
+ */
+
+ /**
+ * A label on the axis next to the crosshair.
+ *
+ * In styled mode, the label is styled with the
+ * `.highcharts-crosshair-label` class.
+ *
+ * @sample {highstock} stock/xaxis/crosshair-label/
+ * Crosshair labels
+ * @sample {highstock} highcharts/css/crosshair-label/
+ * Style mode
+ *
+ * @since 2.1
+ * @product highstock
+ * @apioption xAxis.crosshair.label
+ */
+
+ /**
+ * Alignment of the label compared to the axis. Defaults to `left` for
+ * right-side axes, `right` for left-side axes and `center` for
+ * horizontal axes.
+ *
+ * @type {string}
+ * @since 2.1
+ * @product highstock
+ * @apioption xAxis.crosshair.label.align
+ */
+
+ /**
+ * The background color for the label. Defaults to the related series
+ * color, or `#666666` if that is not available.
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @since 2.1
+ * @product highstock
+ * @apioption xAxis.crosshair.label.backgroundColor
+ */
+
+ /**
+ * The border color for the crosshair label
+ *
+ * @type {Highcharts.ColorString}
+ * @since 2.1
+ * @product highstock
+ * @apioption xAxis.crosshair.label.borderColor
+ */
+
+ /**
+ * The border corner radius of the crosshair label.
+ *
+ * @type {number}
+ * @default 3
+ * @since 2.1.10
+ * @product highstock
+ * @apioption xAxis.crosshair.label.borderRadius
+ */
+
+ /**
+ * The border width for the crosshair label.
+ *
+ * @type {number}
+ * @default 0
+ * @since 2.1
+ * @product highstock
+ * @apioption xAxis.crosshair.label.borderWidth
+ */
+
+ /**
+ * A format string for the crosshair label. Defaults to `{value}` for
+ * numeric axes and `{value:%b %d, %Y}` for datetime axes.
+ *
+ * @type {string}
+ * @since 2.1
+ * @product highstock
+ * @apioption xAxis.crosshair.label.format
+ */
+
+ /**
+ * Formatter function for the label text.
+ *
+ * @type {Highcharts.FormatterCallbackFunction}
+ * @since 2.1
+ * @product highstock
+ * @apioption xAxis.crosshair.label.formatter
*/
/**
- * A class name for the crosshair, especially as a hook for styling.
+ * Padding inside the crosshair label.
*
- * @type {String}
- * @since 5.0.0
- * @apioption xAxis.crosshair.className
+ * @type {number}
+ * @default 8
+ * @since 2.1
+ * @product highstock
+ * @apioption xAxis.crosshair.label.padding
*/
/**
- * The color of the crosshair. Defaults to `#cccccc` for numeric and
- * datetime axes, and `rgba(204,214,235,0.25)` for category axes, where
- * the crosshair by default highlights the whole category.
+ * The shape to use for the label box.
*
- * @type {Color}
- * @sample {highcharts|highstock|highmaps}
- * highcharts/xaxis/crosshair-customized/
- * Customized crosshairs
- * @default #cccccc
- * @since 4.1
- * @apioption xAxis.crosshair.color
+ * @type {string}
+ * @default callout
+ * @since 2.1
+ * @product highstock
+ * @apioption xAxis.crosshair.label.shape
*/
/**
- * The dash style for the crosshair. See
- * [series.dashStyle](#plotOptions.series.dashStyle)
- * for possible values.
+ * Text styles for the crosshair label.
*
- * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
- * "ShortDashDotDot", "Dot", "Dash" ,"LongDash",
- * "DashDot", "LongDashDot", "LongDashDotDot"]
- * @type {String}
- * @sample {highcharts|highmaps} highcharts/xaxis/crosshair-dotted/
- * Dotted crosshair
- * @sample {highstock} stock/xaxis/crosshair-dashed/
- * Dashed X axis crosshair
- * @default Solid
- * @since 4.1
- * @apioption xAxis.crosshair.dashStyle
+ * @type {Highcharts.CSSObject}
+ * @default {"color": "white", "fontWeight": "normal", "fontSize": "11px", "textAlign": "center"}
+ * @since 2.1
+ * @product highstock
+ * @apioption xAxis.crosshair.label.style
*/
/**
* Whether the crosshair should snap to the point or follow the pointer
* independent of points.
*
- * @type {Boolean}
- * @sample {highcharts|highstock}
- * highcharts/xaxis/crosshair-snap-false/
- * True by default
- * @sample {highmaps}
- * maps/demo/latlon-advanced/
- * Snap is false
+ * @sample {highcharts|highstock} highcharts/xaxis/crosshair-snap-false/
+ * True by default
+ * @sample {highmaps} maps/demo/latlon-advanced/
+ * Snap is false
+ *
+ * @type {boolean}
* @default true
* @since 4.1
* @apioption xAxis.crosshair.snap
@@ -10853,13 +14754,14 @@
* The pixel width of the crosshair. Defaults to 1 for numeric or
* datetime axes, and for one category width for category axes.
*
- * @type {Number}
- * @sample {highcharts} highcharts/xaxis/crosshair-customized/
- * Customized crosshairs
- * @sample {highstock} highcharts/xaxis/crosshair-customized/
- * Customized crosshairs
- * @sample {highmaps} highcharts/xaxis/crosshair-customized/
- * Customized crosshairs
+ * @sample {highcharts} highcharts/xaxis/crosshair-customized/
+ * Customized crosshairs
+ * @sample {highstock} highcharts/xaxis/crosshair-customized/
+ * Customized crosshairs
+ * @sample {highmaps} highcharts/xaxis/crosshair-customized/
+ * Customized crosshairs
+ *
+ * @type {number}
* @default 1
* @since 4.1
* @apioption xAxis.crosshair.width
@@ -10869,7 +14771,7 @@
* The Z index of the crosshair. Higher Z indices allow drawing the
* crosshair on top of the series or behind the grid lines.
*
- * @type {Number}
+ * @type {number}
* @default 2
* @since 4.1
* @apioption xAxis.crosshair.zIndex
@@ -10882,8 +14784,8 @@
* different units may be used, for example the `day` unit can be used
* on midnight and `hour` unit be used for intermediate values on the
* same axis. For an overview of the replacement codes, see
- * [dateFormat](#Highcharts.dateFormat). Defaults to:
- *
+ * [dateFormat](/class-reference/Highcharts#dateFormat). Defaults to:
+ *
* {
* millisecond: '%H:%M:%S.%L',
* second: '%H:%M:%S',
@@ -10894,23 +14796,43 @@
* month: '%b \'%y',
* year: '%Y'
* }
- *
- * @type {Object}
- * @sample {highcharts} highcharts/xaxis/datetimelabelformats/
- * Different day format on X axis
- * @sample {highstock} stock/xaxis/datetimelabelformats/
- * More information in x axis labels
- * @product highcharts highstock
+ *
+ * @sample {highcharts} highcharts/xaxis/datetimelabelformats/
+ * Different day format on X axis
+ * @sample {highstock} stock/xaxis/datetimelabelformats/
+ * More information in x axis labels
+ *
+ * @product highcharts highstock gantt
*/
dateTimeLabelFormats: {
- millisecond: '%H:%M:%S.%L',
- second: '%H:%M:%S',
- minute: '%H:%M',
- hour: '%H:%M',
- day: '%e. %b',
- week: '%e. %b',
- month: '%b \'%y',
- year: '%Y'
+ millisecond: {
+ main: '%H:%M:%S.%L',
+ range: false
+ },
+ second: {
+ main: '%H:%M:%S',
+ range: false
+ },
+ minute: {
+ main: '%H:%M',
+ range: false
+ },
+ hour: {
+ main: '%H:%M',
+ range: false
+ },
+ day: {
+ main: '%e. %b'
+ },
+ week: {
+ main: '%e. %b'
+ },
+ month: {
+ main: '%b \'%y'
+ },
+ year: {
+ main: '%Y'
+ }
},
/**
@@ -10918,8 +14840,7 @@
*
* Description of the axis to screen reader users.
*
- * @type {String}
- * @default undefined
+ * @type {string}
* @since 5.0.0
* @apioption xAxis.description
*/
@@ -10931,7 +14852,7 @@
* @productdesc {highstock}
* In Highstock, `endOnTick` is always false when the navigator is
* enabled, to prevent jumpy scrolling.
- *
+ *
* @sample {highcharts} highcharts/chart/reflow-true/
* True by default
* @sample {highcharts} highcharts/yaxis/endontick/
@@ -10940,25 +14861,29 @@
* True by default
* @sample {highstock} stock/xaxis/endontick/
* False
- * @since 1.2.0
+ *
+ * @since 1.2.0
*/
endOnTick: false,
/**
* Event handlers for the axis.
*
+ * @type {*}
* @apioption xAxis.events
*/
/**
* An event fired after the breaks have rendered.
*
- * @type {Function}
- * @see [breaks](#xAxis.breaks)
- * @sample {highcharts} highcharts/axisbreak/break-event/
- * AfterBreak Event
+ * @see [breaks](#xAxis.breaks)
+ *
+ * @sample {highcharts} highcharts/axisbreak/break-event/
+ * AfterBreak Event
+ *
+ * @type {Highcharts.AxisEventCallbackFunction}
* @since 4.1.0
- * @product highcharts
+ * @product highcharts gantt
* @apioption xAxis.events.afterBreaks
*/
@@ -10966,42 +14891,43 @@
* As opposed to the `setExtremes` event, this event fires after the
* final min and max values are computed and corrected for `minRange`.
*
- *
* Fires when the minimum and maximum is set for the axis, either by
* calling the `.setExtremes()` method or by selecting an area in the
* chart. One parameter, `event`, is passed to the function, containing
* common event information.
*
- * The new user set minimum and maximum values can be found by `event.
- * min` and `event.max`. These reflect the axis minimum and maximum
- * in axis values. The actual data extremes are found in `event.dataMin`
- * and `event.dataMax`.
+ * The new user set minimum and maximum values can be found by
+ * `event.min` and `event.max`. These reflect the axis minimum and
+ * maximum in axis values. The actual data extremes are found in
+ * `event.dataMin` and `event.dataMax`.
*
- * @type {Function}
- * @context Axis
+ * @type {Highcharts.AxisSetExtremesEventCallbackFunction}
* @since 2.3
+ * @context Axis
* @apioption xAxis.events.afterSetExtremes
*/
/**
* An event fired when a break from this axis occurs on a point.
*
- * @type {Function}
- * @see [breaks](#xAxis.breaks)
- * @context Axis
- * @sample {highcharts} highcharts/axisbreak/break-visualized/
- * Visualization of a Break
+ * @see [breaks](#xAxis.breaks)
+ *
+ * @sample {highcharts} highcharts/axisbreak/break-visualized/
+ * Visualization of a Break
+ *
+ * @type {Highcharts.AxisPointBreakEventCallbackFunction}
* @since 4.1.0
- * @product highcharts
+ * @product highcharts gantt
+ * @context Axis
* @apioption xAxis.events.pointBreak
*/
/**
* An event fired when a point falls inside a break from this axis.
*
- * @type {Function}
+ * @type {Highcharts.AxisPointBreakEventCallbackFunction}
+ * @product highcharts highstock gantt
* @context Axis
- * @product highcharts highstock
* @apioption xAxis.events.pointInBreak
*/
@@ -11011,62 +14937,61 @@
* chart. One parameter, `event`, is passed to the function,
* containing common event information.
*
- * The new user set minimum and maximum values can be found by `event.
- * min` and `event.max`. These reflect the axis minimum and maximum
- * in data values. When an axis is zoomed all the way out from the
- * "Reset zoom" button, `event.min` and `event.max` are null, and
+ * The new user set minimum and maximum values can be found by
+ * `event.min` and `event.max`. These reflect the axis minimum and
+ * maximum in data values. When an axis is zoomed all the way out from
+ * the "Reset zoom" button, `event.min` and `event.max` are null, and
* the new extremes are set based on `this.dataMin` and `this.dataMax`.
*
- * @type {Function}
- * @context Axis
- * @sample {highstock} stock/xaxis/events-setextremes/
- * Log new extremes on x axis
+ * @sample {highstock} stock/xaxis/events-setextremes/
+ * Log new extremes on x axis
+ *
+ * @type {Highcharts.AxisSetExtremesEventCallbackFunction}
* @since 1.2.0
+ * @context Axis
* @apioption xAxis.events.setExtremes
*/
/**
* The lowest allowed value for automatically computed axis extremes.
*
- * @type {Number}
- * @see [ceiling](#yAxis.ceiling)
- * @sample {highcharts} highcharts/yaxis/floor-ceiling/
- * Floor and ceiling
- * @sample {highstock} stock/demo/lazy-loading/
- * Prevent negative stock price on Y axis
- * @default null
+ * @see [ceiling](#yAxis.ceiling)
+ *
+ * @sample {highcharts} highcharts/yaxis/floor-ceiling/
+ * Floor and ceiling
+ * @sample {highstock} stock/demo/lazy-loading/
+ * Prevent negative stock price on Y axis
+ *
+ * @type {number}
* @since 4.0
- * @product highcharts highstock
+ * @product highcharts highstock gantt
* @apioption xAxis.floor
*/
/**
* The dash or dot style of the grid lines. For possible values, see
- * [this demonstration](http://jsfiddle.net/gh/get/library/pure/
- *highcharts/highcharts/tree/master/samples/highcharts/plotoptions/
- *series-dashstyle-all/).
+ * [this demonstration](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/).
+ *
+ * @sample {highcharts} highcharts/yaxis/gridlinedashstyle/
+ * Long dashes
+ * @sample {highstock} stock/xaxis/gridlinedashstyle/
+ * Long dashes
*
- * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
- * "ShortDashDotDot", "Dot", "Dash" ,"LongDash",
- * "DashDot", "LongDashDot", "LongDashDotDot"]
- * @type {String}
- * @sample {highcharts} highcharts/yaxis/gridlinedashstyle/
- * Long dashes
- * @sample {highstock} stock/xaxis/gridlinedashstyle/
- * Long dashes
- * @default Solid
- * @since 1.2
- * @apioption xAxis.gridLineDashStyle
+ * @type {Highcharts.DashStyleType}
+ * @default Solid
+ * @since 1.2
+ * @apioption xAxis.gridLineDashStyle
*/
/**
* The Z index of the grid lines.
*
- * @type {Number}
- * @sample {highcharts|highstock} highcharts/xaxis/gridzindex/
- * A Z index of 4 renders the grid above the graph
+ * @sample {highcharts|highstock} highcharts/xaxis/gridzindex/
+ * A Z index of 4 renders the grid above the graph
+ *
+ * @type {number}
* @default 1
- * @product highcharts highstock
+ * @product highcharts highstock gantt
* @apioption xAxis.gridZIndex
*/
@@ -11074,12 +14999,12 @@
* An id for the axis. This can be used after render time to get
* a pointer to the axis object through `chart.get()`.
*
- * @type {String}
- * @sample {highcharts} highcharts/xaxis/id/
- * Get the object
- * @sample {highstock} stock/xaxis/id/
- * Get the object
- * @default null
+ * @sample {highcharts} highcharts/xaxis/id/
+ * Get the object
+ * @sample {highstock} stock/xaxis/id/
+ * Get the object
+ *
+ * @type {string}
* @since 1.2.0
* @apioption xAxis.id
*/
@@ -11093,6 +15018,7 @@
* and can be enabled on X and Y axes too.
*/
labels: {
+
/**
* What part of the string the given position is anchored to.
* If `left`, the left side of the string is at the axis position.
@@ -11100,15 +15026,19 @@
* an intelligent guess based on which side of the chart the axis
* is on and the rotation of the label.
*
+ * @see [reserveSpace](#xAxis.labels.reserveSpace)
+ *
+ * @sample {highcharts} highcharts/xaxis/labels-align-left/
+ * Left
+ * @sample {highcharts} highcharts/xaxis/labels-align-right/
+ * Right
+ * @sample {highcharts} highcharts/xaxis/labels-reservespace-true/
+ * Left-aligned labels on a vertical category axis
+ *
+ * @type {string}
* @validvalue ["left", "center", "right"]
- * @type {String}
- * @sample {highcharts} highcharts/xaxis/labels-align-left/
- * Left
- * @sample {highcharts} highcharts/xaxis/labels-align-right/
- * Right
* @apioption xAxis.labels.align
*/
- // align: 'center',
/**
* For horizontal axes, the allowed degrees of label rotation
@@ -11119,16 +15049,15 @@
* Set it to `false` to disable rotation, which will
* cause the labels to word-wrap if possible.
*
- * @type {Array}
- * @sample {highcharts|highstock}
- * highcharts/xaxis/labels-autorotation-default/
- * Default auto rotation of 0 or -45
- * @sample {highcharts|highstock}
- * highcharts/xaxis/labels-autorotation-0-90/
- * Custom graded auto rotation
+ * @sample {highcharts|highstock} highcharts/xaxis/labels-autorotation-default/
+ * Default auto rotation of 0 or -45
+ * @sample {highcharts|highstock} highcharts/xaxis/labels-autorotation-0-90/
+ * Custom graded auto rotation
+ *
+ * @type {Array}
* @default [-45]
* @since 4.1.0
- * @product highcharts highstock
+ * @product highcharts highstock gantt
* @apioption xAxis.labels.autoRotation
*/
@@ -11139,13 +15068,13 @@
* short words that don't extend the available horizontal space for
* each label.
*
- * @type {Number}
- * @sample {highcharts}
- * highcharts/xaxis/labels-autorotationlimit/
- * Lower limit
+ * @sample {highcharts} highcharts/xaxis/labels-autorotationlimit/
+ * Lower limit
+ *
+ * @type {number}
* @default 80
* @since 4.1.5
- * @product highcharts
+ * @product highcharts gantt
* @apioption xAxis.labels.autoRotationLimit
*/
@@ -11153,31 +15082,33 @@
* Polar charts only. The label's pixel distance from the perimeter
* of the plot area.
*
- * @type {Number}
+ * @type {number}
* @default 15
- * @product highcharts
+ * @product highcharts gantt
* @apioption xAxis.labels.distance
*/
/**
* Enable or disable the axis labels.
- *
- * @sample {highcharts} highcharts/xaxis/labels-enabled/
- * X axis labels disabled
- * @sample {highstock} stock/xaxis/labels-enabled/
- * X axis labels disabled
- * @default {highcharts|highstock} true
+ *
+ * @sample {highcharts} highcharts/xaxis/labels-enabled/
+ * X axis labels disabled
+ * @sample {highstock} stock/xaxis/labels-enabled/
+ * X axis labels disabled
+ *
+ * @default {highcharts|highstock|gantt} true
* @default {highmaps} false
*/
enabled: true,
/**
- * A [format string](http://www.highcharts.com/docs/chart-
- * concepts/labels-and-string-formatting) for the axis label.
+ * A [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
+ * for the axis label.
*
- * @type {String}
- * @sample {highcharts|highstock} highcharts/yaxis/labels-format/
- * Add units to Y axis label
+ * @sample {highcharts|highstock} highcharts/yaxis/labels-format/
+ * Add units to Y axis label
+ *
+ * @type {string}
* @default {value}
* @since 3.0
* @apioption xAxis.labels.format
@@ -11196,31 +15127,53 @@
* return this.value;
* }
*
- * @type {Function}
- * @sample {highcharts}
- * highcharts/xaxis/labels-formatter-linked/
- * Linked category names
- * @sample {highcharts}
- * highcharts/xaxis/labels-formatter-extended/
- * Modified numeric labels
- * @sample {highstock}
- * stock/xaxis/labels-formatter/
- * Added units on Y axis
+ * @sample {highcharts} highcharts/xaxis/labels-formatter-linked/
+ * Linked category names
+ * @sample {highcharts} highcharts/xaxis/labels-formatter-extended/
+ * Modified numeric labels
+ * @sample {highstock} stock/xaxis/labels-formatter/
+ * Added units on Y axis
+ *
+ * @type {Highcharts.FormatterCallbackFunction}
* @apioption xAxis.labels.formatter
*/
/**
- * How to handle overflowing labels on horizontal axis. Can be
- * undefined, `false` or `"justify"`. By default it aligns inside
- * the chart area. If "justify", labels will not render outside
- * the plot area. If `false`, it will not be aligned at all.
- * If there is room to move it, it will be aligned to the edge,
- * else it will be removed.
+ * The number of pixels to indent the labels per level in a treegrid
+ * axis.
+ *
+ * @sample gantt/treegrid-axis/demo
+ * Indentation 10px by default.
+ * @sample gantt/treegrid-axis/indentation-0px
+ * Indentation set to 0px.
+ *
+ * @product gantt
+ */
+ indentation: 10,
+
+ /**
+ * Horizontal axis only. When `staggerLines` is not set,
+ * `maxStaggerLines` defines how many lines the axis is allowed to
+ * add to automatically avoid overlapping X labels. Set to `1` to
+ * disable overlap detection.
*
* @deprecated
- * @validvalue [null, "justify"]
- * @type {String}
+ * @type {number}
+ * @default 5
+ * @since 1.3.3
+ * @apioption xAxis.labels.maxStaggerLines
+ */
+
+ /**
+ * How to handle overflowing labels on horizontal axis. If set to
+ * `"allow"`, it will not be aligned at all. By default it
+ * `"justify"` labels inside the chart area. If there is room to
+ * move it, it will be aligned to the edge, else it will be removed.
+ *
+ * @type {string}
+ * @default justify
* @since 2.2.5
+ * @validvalue ["allow", "justify"]
* @apioption xAxis.labels.overflow
*/
@@ -11228,47 +15181,58 @@
* The pixel padding for axis labels, to ensure white space between
* them.
*
- * @type {Number}
+ * @type {number}
* @default 5
- * @product highcharts
+ * @product highcharts gantt
* @apioption xAxis.labels.padding
*/
/**
- * Whether to reserve space for the labels. This can be turned off
- * when for example the labels are rendered inside the plot area
- * instead of outside.
+ * Whether to reserve space for the labels. By default, space is
+ * reserved for the labels in these cases:
*
- * @type {Boolean}
- * @sample {highcharts} highcharts/xaxis/labels-reservespace/
- * No reserved space, labels inside plot
- * @default true
+ * * On all horizontal axes.
+ * * On vertical axes if `label.align` is `right` on a left-side
+ * axis or `left` on a right-side axis.
+ * * On vertical axes if `label.align` is `center`.
+ *
+ * This can be turned off when for example the labels are rendered
+ * inside the plot area instead of outside.
+ *
+ * @see [labels.align](#xAxis.labels.align)
+ *
+ * @sample {highcharts} highcharts/xaxis/labels-reservespace/
+ * No reserved space, labels inside plot
+ * @sample {highcharts} highcharts/xaxis/labels-reservespace-true/
+ * Left-aligned labels on a vertical category axis
+ *
+ * @type {boolean}
* @since 4.1.10
- * @product highcharts
+ * @product highcharts gantt
* @apioption xAxis.labels.reserveSpace
*/
/**
* Rotation of the labels in degrees.
*
- * @type {Number}
- * @sample {highcharts} highcharts/xaxis/labels-rotation/
- * X axis labels rotated 90°
+ * @sample {highcharts} highcharts/xaxis/labels-rotation/
+ * X axis labels rotated 90°
+ *
+ * @type {number}
* @default 0
* @apioption xAxis.labels.rotation
*/
- // rotation: 0,
/**
* Horizontal axes only. The number of lines to spread the labels
* over to make room or tighter labels.
*
- * @type {Number}
- * @sample {highcharts} highcharts/xaxis/labels-staggerlines/
- * Show labels over two lines
- * @sample {highstock} stock/xaxis/labels-staggerlines/
- * Show labels over two lines
- * @default null
+ * @sample {highcharts} highcharts/xaxis/labels-staggerlines/
+ * Show labels over two lines
+ * @sample {highstock} stock/xaxis/labels-staggerlines/
+ * Show labels over two lines
+ *
+ * @type {number}
* @since 2.1
* @apioption xAxis.labels.staggerLines
*/
@@ -11283,47 +15247,25 @@
* chosen the wrong axis type.
*
* Read more at
- * [Axis docs](http://www.highcharts.com/docs/chart-concepts/axes)
+ * [Axis docs](https://www.highcharts.com/docs/chart-concepts/axes)
* => What axis should I use?
*
- * @type {Number}
- * @sample {highcharts} highcharts/xaxis/labels-step/
- * Showing only every other axis label on a categorized
- * x axis
- * @sample {highcharts} highcharts/xaxis/labels-step-auto/
- * Auto steps on a category axis
- * @default null
+ * @sample {highcharts} highcharts/xaxis/labels-step/
+ * Showing only every other axis label on a categorized
+ * x-axis
+ * @sample {highcharts} highcharts/xaxis/labels-step-auto/
+ * Auto steps on a category axis
+ *
+ * @type {number}
* @since 2.1
* @apioption xAxis.labels.step
*/
- // step: null,
-
-
-
- /**
- * CSS styles for the label. Use `whiteSpace: 'nowrap'` to prevent
- * wrapping of category labels. Use `textOverflow: 'none'` to
- * prevent ellipsis (dots).
- *
- * In styled mode, the labels are styled with the
- * `.highcharts-axis-labels` class.
- *
- * @type {CSSObject}
- * @sample {highcharts} highcharts/xaxis/labels-style/
- * Red X axis labels
- */
- style: {
- color: '#666666',
- cursor: 'default',
- fontSize: '11px'
- },
-
/**
- * Whether to [use HTML](http://www.highcharts.com/docs/chart-
- * concepts/labels-and-string-formatting#html) to render the labels.
+ * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
+ * to render the labels.
*
- * @type {Boolean}
+ * @type {boolean}
* @default false
* @apioption xAxis.labels.useHTML
*/
@@ -11331,31 +15273,54 @@
/**
* The x position offset of the label relative to the tick position
* on the axis.
- *
+ *
* @sample {highcharts} highcharts/xaxis/labels-x/
* Y axis labels placed on grid lines
*/
- x: 0
+ x: 0,
/**
* The y position offset of the label relative to the tick position
* on the axis. The default makes it adapt to the font size on
* bottom axis.
*
- * @type {Number}
- * @sample {highcharts} highcharts/xaxis/labels-x/
- * Y axis labels placed on grid lines
- * @default null
+ * @sample {highcharts} highcharts/xaxis/labels-x/
+ * Y axis labels placed on grid lines
+ *
+ * @type {number}
* @apioption xAxis.labels.y
*/
/**
* The Z index for the axis labels.
*
- * @type {Number}
- * @default 7
+ * @type {number}
+ * @default 7
* @apioption xAxis.labels.zIndex
*/
+
+ /**
+ * CSS styles for the label. Use `whiteSpace: 'nowrap'` to prevent
+ * wrapping of category labels. Use `textOverflow: 'none'` to
+ * prevent ellipsis (dots).
+ *
+ * In styled mode, the labels are styled with the
+ * `.highcharts-axis-labels` class.
+ *
+ * @sample {highcharts} highcharts/xaxis/labels-style/
+ * Red X axis labels
+ *
+ * @type {Highcharts.CSSObject}
+ * @default {"color": "#666666", "cursor": "default", "fontSize": "11px"}
+ */
+ style: {
+ /** @ignore-option */
+ color: '#666666',
+ /** @ignore-option */
+ cursor: 'default',
+ /** @ignore-option */
+ fontSize: '11px'
+ }
},
/**
@@ -11365,14 +15330,14 @@
* It can be used to show additional info, or to ease reading the
* chart by duplicating the scales.
*
- * @type {Number}
- * @sample {highcharts} highcharts/xaxis/linkedto/
- * Different string formats of the same date
- * @sample {highcharts} highcharts/yaxis/linkedto/
- * Y values on both sides
- * @default null
+ * @sample {highcharts} highcharts/xaxis/linkedto/
+ * Different string formats of the same date
+ * @sample {highcharts} highcharts/yaxis/linkedto/
+ * Y values on both sides
+ *
+ * @type {number}
* @since 2.0.2
- * @product highcharts highstock
+ * @product highcharts highstock gantt
* @apioption xAxis.linkedTo
*/
@@ -11380,23 +15345,24 @@
* The maximum value of the axis. If `null`, the max value is
* automatically calculated.
*
- * If the `endOnTick` option is true, the `max` value might
- * be rounded up.
+ * If the [endOnTick](#yAxis.endOnTick) option is true, the `max` value
+ * might be rounded up.
*
* If a [tickAmount](#yAxis.tickAmount) is set, the axis may be extended
* beyond the set max in order to reach the given number of ticks. The
* same may happen in a chart with multiple axes, determined by [chart.
* alignTicks](#chart), where a `tickAmount` is applied internally.
*
- * @type {Number}
- * @sample {highcharts} highcharts/yaxis/max-200/
- * Y axis max of 200
- * @sample {highcharts} highcharts/yaxis/max-logarithmic/
- * Y axis max on logarithmic axis
- * @sample {highstock} stock/xaxis/min-max/
- * Fixed min and max on X axis
- * @sample {highmaps} maps/axis/min-max/
- * Pre-zoomed to a specific area
+ * @sample {highcharts} highcharts/yaxis/max-200/
+ * Y axis max of 200
+ * @sample {highcharts} highcharts/yaxis/max-logarithmic/
+ * Y axis max on logarithmic axis
+ * @sample {highstock} stock/xaxis/min-max/
+ * Fixed min and max on X axis
+ * @sample {highmaps} maps/axis/min-max/
+ * Pre-zoomed to a specific area
+ *
+ * @type {number}
* @apioption xAxis.max
*/
@@ -11406,16 +15372,17 @@
* when you don't want the highest data value to appear on the edge
* of the plot area. When the axis' `max` option is set or a max extreme
* is set using `axis.setExtremes()`, the maxPadding will be ignored.
- *
- * @sample {highcharts} highcharts/yaxis/maxpadding/
- * Max padding of 0.25 on y axis
- * @sample {highstock} stock/xaxis/minpadding-maxpadding/
- * Greater min- and maxPadding
- * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/
- * Add some padding
- * @default {highcharts} 0.01
- * @default {highstock|highmaps} 0
- * @since 1.2.0
+ *
+ * @sample {highcharts} highcharts/yaxis/maxpadding/
+ * Max padding of 0.25 on y axis
+ * @sample {highstock} stock/xaxis/minpadding-maxpadding/
+ * Greater min- and maxPadding
+ * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/
+ * Add some padding
+ *
+ * @default {highcharts} 0.01
+ * @default {highstock|highmaps} 0
+ * @since 1.2.0
*/
maxPadding: 0.01,
@@ -11423,17 +15390,17 @@
* Deprecated. Use `minRange` instead.
*
* @deprecated
- * @type {Number}
- * @product highcharts highstock
- * @apioption xAxis.maxZoom
+ * @type {number}
+ * @product highcharts highstock
+ * @apioption xAxis.maxZoom
*/
/**
- * The minimum value of the axis. If `null` the min value is
+ * The minimum value of the axis. If `null` the min value is
* automatically calculated.
*
- * If the `startOnTick` option is true (default), the `min` value might
- * be rounded down.
+ * If the [startOnTick](#yAxis.startOnTick) option is true (default),
+ * the `min` value might be rounded down.
*
* The automatically calculated minimum value is also affected by
* [floor](#yAxis.floor), [softMin](#yAxis.softMin),
@@ -11441,70 +15408,68 @@
* as well as [series.threshold](#plotOptions.series.threshold)
* and [series.softThreshold](#plotOptions.series.softThreshold).
*
- * @type {Number}
- * @sample {highcharts} highcharts/yaxis/min-startontick-false/
- * -50 with startOnTick to false
- * @sample {highcharts} highcharts/yaxis/min-startontick-true/
- * -50 with startOnTick true by default
- * @sample {highstock} stock/xaxis/min-max/
- * Set min and max on X axis
- * @sample {highmaps} maps/axis/min-max/
- * Pre-zoomed to a specific area
+ * @sample {highcharts} highcharts/yaxis/min-startontick-false/
+ * -50 with startOnTick to false
+ * @sample {highcharts} highcharts/yaxis/min-startontick-true/
+ * -50 with startOnTick true by default
+ * @sample {highstock} stock/xaxis/min-max/
+ * Set min and max on X axis
+ * @sample {highmaps} maps/axis/min-max/
+ * Pre-zoomed to a specific area
+ *
+ * @type {number}
* @apioption xAxis.min
*/
/**
* The dash or dot style of the minor grid lines. For possible values,
- * see [this demonstration](http://jsfiddle.net/gh/get/library/pure/
- * highcharts/highcharts/tree/master/samples/highcharts/plotoptions/
- * series-dashstyle-all/).
+ * see [this demonstration](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/).
+ *
+ * @sample {highcharts} highcharts/yaxis/minorgridlinedashstyle/
+ * Long dashes on minor grid lines
+ * @sample {highstock} stock/xaxis/minorgridlinedashstyle/
+ * Long dashes on minor grid lines
*
- * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
- * "ShortDashDotDot", "Dot", "Dash" ,"LongDash",
- * "DashDot", "LongDashDot", "LongDashDotDot"]
- * @type {String}
- * @sample {highcharts} highcharts/yaxis/minorgridlinedashstyle/
- * Long dashes on minor grid lines
- * @sample {highstock} stock/xaxis/minorgridlinedashstyle/
- * Long dashes on minor grid lines
- * @default Solid
- * @since 1.2
- * @apioption xAxis.minorGridLineDashStyle
+ * @type {Highcharts.DashStyleType}
+ * @default Solid
+ * @since 1.2
+ * @apioption xAxis.minorGridLineDashStyle
*/
/**
- * Specific tick interval in axis units for the minor ticks.
- * On a linear axis, if `"auto"`, the minor tick interval is
- * calculated as a fifth of the tickInterval. If `null`, minor
- * ticks are not shown.
+ * Specific tick interval in axis units for the minor ticks. On a linear
+ * axis, if `"auto"`, the minor tick interval is calculated as a fifth
+ * of the tickInterval. If `null` or `undefined`, minor ticks are not
+ * shown.
*
* On logarithmic axes, the unit is the power of the value. For example,
- * setting the minorTickInterval to 1 puts one tick on each of 0.1,
- * 1, 10, 100 etc. Setting the minorTickInterval to 0.1 produces 9
- * ticks between 1 and 10, 10 and 100 etc.
+ * setting the minorTickInterval to 1 puts one tick on each of 0.1, 1,
+ * 10, 100 etc. Setting the minorTickInterval to 0.1 produces 9 ticks
+ * between 1 and 10, 10 and 100 etc.
*
* If user settings dictate minor ticks to become too dense, they don't
* make sense, and will be ignored to prevent performance problems.
*
- * @type {Number|String}
- * @sample {highcharts} highcharts/yaxis/minortickinterval-null/
- * Null by default
- * @sample {highcharts} highcharts/yaxis/minortickinterval-5/
- * 5 units
- * @sample {highcharts} highcharts/yaxis/minortickinterval-log-auto/
- * "auto"
- * @sample {highcharts} highcharts/yaxis/minortickinterval-log/
- * 0.1
- * @sample {highstock} stock/demo/basic-line/
- * Null by default
- * @sample {highstock} stock/xaxis/minortickinterval-auto/
- * "auto"
+ * @sample {highcharts} highcharts/yaxis/minortickinterval-null/
+ * Null by default
+ * @sample {highcharts} highcharts/yaxis/minortickinterval-5/
+ * 5 units
+ * @sample {highcharts} highcharts/yaxis/minortickinterval-log-auto/
+ * "auto"
+ * @sample {highcharts} highcharts/yaxis/minortickinterval-log/
+ * 0.1
+ * @sample {highstock} stock/demo/basic-line/
+ * Null by default
+ * @sample {highstock} stock/xaxis/minortickinterval-auto/
+ * "auto"
+ *
+ * @type {number|string|null}
* @apioption xAxis.minorTickInterval
*/
/**
* The pixel length of the minor tick marks.
- *
+ *
* @sample {highcharts} highcharts/yaxis/minorticklength/
* 10px on Y axis
* @sample {highstock} stock/xaxis/minorticks/
@@ -11515,14 +15480,15 @@
/**
* The position of the minor tick marks relative to the axis line.
* Can be one of `inside` and `outside`.
- *
+ *
+ * @sample {highcharts} highcharts/yaxis/minortickposition-outside/
+ * Outside by default
+ * @sample {highcharts} highcharts/yaxis/minortickposition-inside/
+ * Inside
+ * @sample {highstock} stock/xaxis/minorticks/
+ * Inside
+ *
* @validvalue ["inside", "outside"]
- * @sample {highcharts} highcharts/yaxis/minortickposition-outside/
- * Outside by default
- * @sample {highcharts} highcharts/yaxis/minortickposition-inside/
- * Inside
- * @sample {highstock} stock/xaxis/minorticks/
- * Inside
*/
minorTickPosition: 'outside',
@@ -11542,22 +15508,24 @@
* On axes using [categories](#xAxis.categories), minor ticks are not
* supported.
*
- * @type {Boolean}
+ * @sample {highcharts} highcharts/yaxis/minorticks-true/
+ * Enabled on linear Y axis
+ *
+ * @type {boolean}
* @default false
* @since 6.0.0
- * @sample {highcharts} highcharts/yaxis/minorticks-true/
- * Enabled on linear Y axis
* @apioption xAxis.minorTicks
*/
/**
* The pixel width of the minor tick mark.
*
- * @type {Number}
- * @sample {highcharts} highcharts/yaxis/minortickwidth/
- * 3px width
- * @sample {highstock} stock/xaxis/minorticks/
- * 1px width
+ * @sample {highcharts} highcharts/yaxis/minortickwidth/
+ * 3px width
+ * @sample {highstock} stock/xaxis/minorticks/
+ * 1px width
+ *
+ * @type {number}
* @default 0
* @apioption xAxis.minorTickWidth
*/
@@ -11568,16 +15536,18 @@
* when you don't want the lowest data value to appear on the edge
* of the plot area. When the axis' `min` option is set or a min extreme
* is set using `axis.setExtremes()`, the minPadding will be ignored.
- *
- * @sample {highcharts} highcharts/yaxis/minpadding/
- * Min padding of 0.2
- * @sample {highstock} stock/xaxis/minpadding-maxpadding/
- * Greater min- and maxPadding
- * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/
- * Add some padding
- * @default {highcharts} 0.01
- * @default {highstock|highmaps} 0
- * @since 1.2.0
+ *
+ * @sample {highcharts} highcharts/yaxis/minpadding/
+ * Min padding of 0.2
+ * @sample {highstock} stock/xaxis/minpadding-maxpadding/
+ * Greater min- and maxPadding
+ * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/
+ * Add some padding
+ *
+ * @default {highcharts} 0.01
+ * @default {highstock|highmaps} 0
+ * @since 1.2.0
+ * @product highcharts highstock gantt
*/
minPadding: 0.01,
@@ -11598,13 +15568,14 @@
* `endOnTick` settings also affect how the extremes of the axis
* are computed.
*
- * @type {Number}
- * @sample {highcharts} highcharts/xaxis/minrange/
- * Minimum range of 5
- * @sample {highstock} stock/xaxis/minrange/
- * Max zoom of 6 months overrides user selections
- * @sample {highmaps} maps/axis/minrange/
- * Minimum range of 1000
+ * @sample {highcharts} highcharts/xaxis/minrange/
+ * Minimum range of 5
+ * @sample {highstock} stock/xaxis/minrange/
+ * Max zoom of 6 months overrides user selections
+ * @sample {highmaps} maps/axis/minrange/
+ * Minimum range of 1000
+ *
+ * @type {number}
* @apioption xAxis.minRange
*/
@@ -11614,7 +15585,7 @@
* the axis from showing hours. Defaults to the closest distance between
* two points on the axis.
*
- * @type {Number}
+ * @type {number}
* @since 2.3.0
* @apioption xAxis.minTickInterval
*/
@@ -11627,13 +15598,14 @@
* axes the offset is dynamically adjusted to avoid collision, this
* can be overridden by setting offset explicitly.
*
- * @type {Number}
- * @sample {highcharts} highcharts/yaxis/offset/
- * Y axis offset of 70
- * @sample {highcharts} highcharts/yaxis/offset-centered/
- * Axes positioned in the center of the plot
- * @sample {highstock} stock/xaxis/offset/
- * Y axis offset by 70 px
+ * @sample {highcharts} highcharts/yaxis/offset/
+ * Y axis offset of 70
+ * @sample {highcharts} highcharts/yaxis/offset-centered/
+ * Axes positioned in the center of the plot
+ * @sample {highstock} stock/xaxis/offset/
+ * Y axis offset by 70 px
+ *
+ * @type {number}
* @default 0
* @apioption xAxis.offset
*/
@@ -11644,57 +15616,220 @@
* horizontal, so the opposite sides will be right and top respectively.
* This is typically used with dual or multiple axes.
*
- * @type {Boolean}
- * @sample {highcharts} highcharts/yaxis/opposite/
- * Secondary Y axis opposite
- * @sample {highstock} stock/xaxis/opposite/
- * Y axis on left side
+ * @sample {highcharts} highcharts/yaxis/opposite/
+ * Secondary Y axis opposite
+ * @sample {highstock} stock/xaxis/opposite/
+ * Y axis on left side
+ *
+ * @type {boolean}
* @default false
* @apioption xAxis.opposite
*/
+ /**
+ * In an ordinal axis, the points are equally spaced in the chart
+ * regardless of the actual time or x distance between them. This means
+ * that missing data periods (e.g. nights or weekends for a stock chart)
+ * will not take up space in the chart.
+ * Having `ordinal: false` will show any gaps created by the `gapSize`
+ * setting proportionate to their duration.
+ *
+ * In stock charts the X axis is ordinal by default, unless
+ * the boost module is used and at least one of the series' data length
+ * exceeds the [boostThreshold](#series.line.boostThreshold).
+ *
+ * @sample {highstock} stock/xaxis/ordinal-true/
+ * True by default
+ * @sample {highstock} stock/xaxis/ordinal-false/
+ * False
+ *
+ * @type {boolean}
+ * @default true
+ * @since 1.1
+ * @product highstock
+ * @apioption xAxis.ordinal
+ */
+
+ /**
+ * Additional range on the right side of the xAxis. Works similar to
+ * `xAxis.maxPadding`, but value is set in milliseconds. Can be set for
+ * both main `xAxis` and the navigator's `xAxis`.
+ *
+ * @sample {highstock} stock/xaxis/overscroll/
+ * One minute overscroll with live data
+ *
+ * @type {number}
+ * @default 0
+ * @since 6.0.0
+ * @product highstock
+ * @apioption xAxis.overscroll
+ */
+
+ /**
+ * Refers to the index in the [panes](#panes) array. Used for circular
+ * gauges and polar charts. When the option is not set then first pane
+ * will be used.
+ *
+ * @sample highcharts/demo/gauge-vu-meter
+ * Two gauges with different center
+ *
+ * @type {number}
+ * @product highcharts
+ * @apioption xAxis.pane
+ */
+
+
+ /**
+ * The zoomed range to display when only defining one or none of `min`
+ * or `max`. For example, to show the latest month, a range of one month
+ * can be set.
+ *
+ * @sample {highstock} stock/xaxis/range/
+ * Setting a zoomed range when the rangeSelector is disabled
+ *
+ * @type {number}
+ * @product highstock
+ * @apioption xAxis.range
+ */
+
/**
* Whether to reverse the axis so that the highest number is closest
* to the origin. If the chart is inverted, the x axis is reversed by
* default.
*
- * @type {Boolean}
- * @sample {highcharts} highcharts/yaxis/reversed/
- * Reversed Y axis
- * @sample {highstock} stock/xaxis/reversed/
- * Reversed Y axis
+ * @sample {highcharts} highcharts/yaxis/reversed/
+ * Reversed Y axis
+ * @sample {highstock} stock/xaxis/reversed/
+ * Reversed Y axis
+ *
+ * @type {boolean}
* @default false
* @apioption xAxis.reversed
*/
// reversed: false,
+ /**
+ * This option determines how stacks should be ordered within a group.
+ * For example reversed xAxis also reverses stacks, so first series
+ * comes last in a group. To keep order like for non-reversed xAxis
+ * enable this option.
+ *
+ * @sample {highcharts} highcharts/xaxis/reversedstacks/
+ * Reversed stacks comparison
+ * @sample {highstock} highcharts/xaxis/reversedstacks/
+ * Reversed stacks comparison
+ *
+ * @type {boolean}
+ * @default false
+ * @since 6.1.1
+ * @product highcharts highstock
+ * @apioption xAxis.reversedStacks
+ */
+
+ /**
+ * An optional scrollbar to display on the X axis in response to
+ * limiting the minimum and maximum of the axis values.
+ *
+ * In styled mode, all the presentational options for the scrollbar are
+ * replaced by the classes `.highcharts-scrollbar-thumb`,
+ * `.highcharts-scrollbar-arrow`, `.highcharts-scrollbar-button`,
+ * `.highcharts-scrollbar-rifles` and `.highcharts-scrollbar-track`.
+ *
+ * @sample {highstock} stock/yaxis/heatmap-scrollbars/
+ * Heatmap with both scrollbars
+ *
+ * @extends scrollbar
+ * @since 4.2.6
+ * @product highstock
+ * @apioption xAxis.scrollbar
+ */
+
+ /**
+ * Whether to show the axis line and title when the axis has no data.
+ *
+ * @sample {highcharts} highcharts/yaxis/showempty/
+ * When clicking the legend to hide series, one axis preserves
+ * line and title, the other doesn't
+ * @sample {highstock} highcharts/yaxis/showempty/
+ * When clicking the legend to hide series, one axis preserves
+ * line and title, the other doesn't
+ *
+ * @type {boolean}
+ * @default true
+ * @since 1.1
+ * @apioption xAxis.showEmpty
+ */
+
+ /**
+ * Whether to show the first tick label.
+ *
+ * @sample {highcharts} highcharts/xaxis/showfirstlabel-false/
+ * Set to false on X axis
+ * @sample {highstock} stock/xaxis/showfirstlabel/
+ * Labels below plot lines on Y axis
+ *
+ * @type {boolean}
+ * @default true
+ * @apioption xAxis.showFirstLabel
+ */
+
/**
* Whether to show the last tick label. Defaults to `true` on cartesian
* charts, and `false` on polar charts.
*
- * @type {Boolean}
- * @sample {highcharts} highcharts/xaxis/showlastlabel-true/
- * Set to true on X axis
- * @sample {highstock} stock/xaxis/showfirstlabel/
- * Labels below plot lines on Y axis
+ * @sample {highcharts} highcharts/xaxis/showlastlabel-true/
+ * Set to true on X axis
+ * @sample {highstock} stock/xaxis/showfirstlabel/
+ * Labels below plot lines on Y axis
+ *
+ * @type {boolean}
* @default true
- * @product highcharts highstock
+ * @product highcharts highstock gantt
* @apioption xAxis.showLastLabel
*/
+ /**
+ * A soft maximum for the axis. If the series data maximum is less than
+ * this, the axis will stay at this maximum, but if the series data
+ * maximum is higher, the axis will flex to show all data.
+ *
+ * @sample highcharts/yaxis/softmin-softmax/
+ * Soft min and max
+ *
+ * @type {number}
+ * @since 5.0.1
+ * @product highcharts highstock gantt
+ * @apioption xAxis.softMax
+ */
+
+ /**
+ * A soft minimum for the axis. If the series data minimum is greater
+ * than this, the axis will stay at this minimum, but if the series
+ * data minimum is lower, the axis will flex to show all data.
+ *
+ * @sample highcharts/yaxis/softmin-softmax/
+ * Soft min and max
+ *
+ * @type {number}
+ * @since 5.0.1
+ * @product highcharts highstock gantt
+ * @apioption xAxis.softMin
+ */
+
/**
* For datetime axes, this decides where to put the tick between weeks.
* 0 = Sunday, 1 = Monday.
- *
- * @sample {highcharts} highcharts/xaxis/startofweek-monday/
- * Monday by default
- * @sample {highcharts} highcharts/xaxis/startofweek-sunday/
- * Sunday
- * @sample {highstock} stock/xaxis/startofweek-1
- * Monday by default
- * @sample {highstock} stock/xaxis/startofweek-0
- * Sunday
- * @product highcharts highstock
+ *
+ * @sample {highcharts} highcharts/xaxis/startofweek-monday/
+ * Monday by default
+ * @sample {highcharts} highcharts/xaxis/startofweek-sunday/
+ * Sunday
+ * @sample {highstock} stock/xaxis/startofweek-1
+ * Monday by default
+ * @sample {highstock} stock/xaxis/startofweek-0
+ * Sunday
+ *
+ * @product highcharts highstock gantt
*/
startOfWeek: 1,
@@ -11705,20 +15840,76 @@
* @productdesc {highstock}
* In Highstock, `startOnTick` is always false when the navigator is
* enabled, to prevent jumpy scrolling.
- *
- * @sample {highcharts} highcharts/xaxis/startontick-false/
- * False by default
- * @sample {highcharts} highcharts/xaxis/startontick-true/
- * True
- * @sample {highstock} stock/xaxis/endontick/
- * False for Y axis
- * @since 1.2.0
+ *
+ * @sample {highcharts} highcharts/xaxis/startontick-false/
+ * False by default
+ * @sample {highcharts} highcharts/xaxis/startontick-true/
+ * True
+ * @sample {highstock} stock/xaxis/endontick/
+ * False for Y axis
+ *
+ * @since 1.2.0
+ */
+ startOnTick: false,
+
+
+ /**
+ * The amount of ticks to draw on the axis. This opens up for aligning
+ * the ticks of multiple charts or panes within a chart. This option
+ * overrides the `tickPixelInterval` option.
+ *
+ * This option only has an effect on linear axes. Datetime, logarithmic
+ * or category axes are not affected.
+ *
+ * @sample {highcharts} highcharts/yaxis/tickamount/
+ * 8 ticks on Y axis
+ * @sample {highstock} highcharts/yaxis/tickamount/
+ * 8 ticks on Y axis
+ *
+ * @type {number}
+ * @since 4.1.0
+ * @product highcharts highstock gantt
+ * @apioption xAxis.tickAmount
+ */
+
+ /**
+ * The interval of the tick marks in axis units. When `undefined`, the
+ * tick interval is computed to approximately follow the
+ * [tickPixelInterval](#xAxis.tickPixelInterval) on linear and datetime
+ * axes. On categorized axes, a `undefined` tickInterval will default to
+ * 1, one category. Note that datetime axes are based on milliseconds,
+ * so for example an interval of one day is expressed as
+ * `24 * 3600 * 1000`.
+ *
+ * On logarithmic axes, the tickInterval is based on powers, so a
+ * tickInterval of 1 means one tick on each of 0.1, 1, 10, 100 etc. A
+ * tickInterval of 2 means a tick of 0.1, 10, 1000 etc. A tickInterval
+ * of 0.2 puts a tick on 0.1, 0.2, 0.4, 0.6, 0.8, 1, 2, 4, 6, 8, 10, 20,
+ * 40 etc.
+ *
+ *
+ * If the tickInterval is too dense for labels to be drawn, Highcharts
+ * may remove ticks.
+ *
+ * If the chart has multiple axes, the [alignTicks](#chart.alignTicks)
+ * option may interfere with the `tickInterval` setting.
+ *
+ * @see [tickPixelInterval](#xAxis.tickPixelInterval)
+ * @see [tickPositions](#xAxis.tickPositions)
+ * @see [tickPositioner](#xAxis.tickPositioner)
+ *
+ * @sample {highcharts} highcharts/xaxis/tickinterval-5/
+ * Tick interval of 5 on a linear axis
+ * @sample {highstock} stock/xaxis/tickinterval/
+ * Tick interval of 0.01 on Y axis
+ *
+ * @type {number}
+ * @apioption xAxis.tickInterval
*/
- startOnTick: false,
/**
* The pixel length of the main tick marks.
- *
+ *
* @sample {highcharts} highcharts/xaxis/ticklength/
* 20 px tick length on the X axis
* @sample {highstock} stock/xaxis/ticks/
@@ -11726,32 +15917,18 @@
*/
tickLength: 10,
- /**
- * For categorized axes only. If `on` the tick mark is placed in the
- * center of the category, if `between` the tick mark is placed between
- * categories. The default is `between` if the `tickInterval` is 1,
- * else `on`.
- *
- * @validvalue [null, "on", "between"]
- * @sample {highcharts} highcharts/xaxis/tickmarkplacement-between/
- * "between" by default
- * @sample {highcharts} highcharts/xaxis/tickmarkplacement-on/
- * "on"
- * @product highcharts
- */
- tickmarkPlacement: 'between',
-
/**
* If tickInterval is `null` this option sets the approximate pixel
* interval of the tick marks. Not applicable to categorized axis.
- *
- * The tick interval is also influenced by the [minTickInterval](#xAxis.
- * minTickInterval) option, that, by default prevents ticks from being
- * denser than the data points.
- *
- * @see [tickInterval](#xAxis.tickInterval),
- * [tickPositioner](#xAxis.tickPositioner),
- * [tickPositions](#xAxis.tickPositions).
+ *
+ * The tick interval is also influenced by the [minTickInterval](
+ * #xAxis.minTickInterval) option, that, by default prevents ticks from
+ * being denser than the data points.
+ *
+ * @see [tickInterval](#xAxis.tickInterval)
+ * @see [tickPositioner](#xAxis.tickPositioner)
+ * @see [tickPositions](#xAxis.tickPositions)
+ *
* @sample {highcharts} highcharts/xaxis/tickpixelinterval-50/
* 50 px on X axis
* @sample {highstock} stock/xaxis/tickpixelinterval/
@@ -11759,20 +15936,93 @@
*/
tickPixelInterval: 100,
+ /**
+ * For categorized axes only. If `on` the tick mark is placed in the
+ * center of the category, if `between` the tick mark is placed between
+ * categories. The default is `between` if the `tickInterval` is 1, else
+ * `on`.
+ *
+ * @sample {highcharts} highcharts/xaxis/tickmarkplacement-between/
+ * "between" by default
+ * @sample {highcharts} highcharts/xaxis/tickmarkplacement-on/
+ * "on"
+ *
+ * @product highcharts gantt
+ * @validvalue ["on", "between"]
+ */
+ tickmarkPlacement: 'between',
+
/**
* The position of the major tick marks relative to the axis line.
* Can be one of `inside` and `outside`.
- *
+ *
+ * @sample {highcharts} highcharts/xaxis/tickposition-outside/
+ * "outside" by default
+ * @sample {highcharts} highcharts/xaxis/tickposition-inside/
+ * "inside"
+ * @sample {highstock} stock/xaxis/ticks/
+ * Formatted ticks on X axis
+ *
* @validvalue ["inside", "outside"]
- * @sample {highcharts} highcharts/xaxis/tickposition-outside/
- * "outside" by default
- * @sample {highcharts} highcharts/xaxis/tickposition-inside/
- * "inside"
- * @sample {highstock} stock/xaxis/ticks/
- * Formatted ticks on X axis
*/
tickPosition: 'outside',
+ /**
+ * A callback function returning array defining where the ticks are
+ * laid out on the axis. This overrides the default behaviour of
+ * [tickPixelInterval](#xAxis.tickPixelInterval) and [tickInterval](
+ * #xAxis.tickInterval). The automatic tick positions are accessible
+ * through `this.tickPositions` and can be modified by the callback.
+ *
+ * @see [tickPositions](#xAxis.tickPositions)
+ *
+ * @sample {highcharts} highcharts/xaxis/tickpositions-tickpositioner/
+ * Demo of tickPositions and tickPositioner
+ * @sample {highstock} highcharts/xaxis/tickpositions-tickpositioner/
+ * Demo of tickPositions and tickPositioner
+ *
+ * @type {Highcharts.AxisTickPositionerCallbackFunction}
+ * @apioption xAxis.tickPositioner
+ */
+
+ /**
+ * An array defining where the ticks are laid out on the axis. This
+ * overrides the default behaviour of [tickPixelInterval](
+ * #xAxis.tickPixelInterval) and [tickInterval](#xAxis.tickInterval).
+ *
+ * @see [tickPositioner](#xAxis.tickPositioner)
+ *
+ * @sample {highcharts} highcharts/xaxis/tickpositions-tickpositioner/
+ * Demo of tickPositions and tickPositioner
+ * @sample {highstock} highcharts/xaxis/tickpositions-tickpositioner/
+ * Demo of tickPositions and tickPositioner
+ *
+ * @type {Array}
+ * @apioption xAxis.tickPositions
+ */
+
+ /**
+ * The pixel width of the major tick marks.
+ *
+ * In styled mode, the stroke width is given in the `.highcharts-tick`
+ * class.
+ *
+ * @sample {highcharts} highcharts/xaxis/tickwidth/
+ * 10 px width
+ * @sample {highcharts} highcharts/css/axis-grid/
+ * Styled mode
+ * @sample {highstock} stock/xaxis/ticks/
+ * Formatted ticks on X axis
+ * @sample {highstock} highcharts/css/axis-grid/
+ * Styled mode
+ *
+ * @type {number}
+ * @default {highcharts} 1
+ * @default {highstock} 1
+ * @default {highmaps} 0
+ * @apioption xAxis.tickWidth
+ */
+
/**
* The axis title, showing next to the axis line.
*
@@ -11783,98 +16033,317 @@
*/
title: {
+ /**
+ * Deprecated. Set the `text` to `null` to disable the title.
+ *
+ * @deprecated
+ * @type {string}
+ * @default middle
+ * @product highcharts
+ * @apioption xAxis.title.enabled
+ */
+
+ /**
+ * The pixel distance between the axis labels or line and the title.
+ * Defaults to 0 for horizontal axes, 10 for vertical
+ *
+ * @sample {highcharts} highcharts/xaxis/title-margin/
+ * Y axis title margin of 60
+ *
+ * @type {number}
+ * @apioption xAxis.title.margin
+ */
+
+ /**
+ * The distance of the axis title from the axis line. By default,
+ * this distance is computed from the offset width of the labels,
+ * the labels' distance from the axis and the title's margin.
+ * However when the offset option is set, it overrides all this.
+ *
+ * @sample {highcharts} highcharts/yaxis/title-offset/
+ * Place the axis title on top of the axis
+ * @sample {highstock} highcharts/yaxis/title-offset/
+ * Place the axis title on top of the Y axis
+ *
+ * @type {number}
+ * @since 2.2.0
+ * @apioption xAxis.title.offset
+ */
+
+ /**
+ * Whether to reserve space for the title when laying out the axis.
+ *
+ * @type {boolean}
+ * @default true
+ * @since 5.0.11
+ * @product highcharts highstock gantt
+ * @apioption xAxis.title.reserveSpace
+ */
+
+ /**
+ * The rotation of the text in degrees. 0 is horizontal, 270 is
+ * vertical reading from bottom to top.
+ *
+ * @sample {highcharts} highcharts/yaxis/title-offset/
+ * Horizontal
+ *
+ * @type {number}
+ * @default 0
+ * @apioption xAxis.title.rotation
+ */
+
+ /**
+ * The actual text of the axis title. It can contain basic HTML text
+ * markup like , and spans with style.
+ *
+ * @sample {highcharts} highcharts/xaxis/title-text/
+ * Custom HTML
+ * @sample {highstock} stock/xaxis/title-text/
+ * Titles for both axes
+ *
+ * @type {string|null}
+ * @apioption xAxis.title.text
+ */
+
+ /**
+ * Alignment of the text, can be `"left"`, `"right"` or `"center"`.
+ * Default alignment depends on the
+ * [title.align](xAxis.title.align):
+ *
+ * Horizontal axes:
+ * - for `align` = `"low"`, `textAlign` is set to `left`
+ * - for `align` = `"middle"`, `textAlign` is set to `center`
+ * - for `align` = `"high"`, `textAlign` is set to `right`
+ *
+ * Vertical axes:
+ * - for `align` = `"low"` and `opposite` = `true`, `textAlign` is
+ * set to `right`
+ * - for `align` = `"low"` and `opposite` = `false`, `textAlign` is
+ * set to `left`
+ * - for `align` = `"middle"`, `textAlign` is set to `center`
+ * - for `align` = `"high"` and `opposite` = `true` `textAlign` is
+ * set to `left`
+ * - for `align` = `"high"` and `opposite` = `false` `textAlign` is
+ * set to `right`
+ *
+ * @type {string}
+ * @apioption xAxis.title.textAlign
+ */
+
+ /**
+ * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
+ * to render the axis title.
+ *
+ * @type {boolean}
+ * @default false
+ * @product highcharts highstock gantt
+ * @apioption xAxis.title.useHTML
+ */
+
+ /**
+ * Horizontal pixel offset of the title position.
+ *
+ * @type {number}
+ * @default 0
+ * @since 4.1.6
+ * @product highcharts highstock gantt
+ * @apioption xAxis.title.x
+ */
+
+ /**
+ * Vertical pixel offset of the title position.
+ *
+ * @type {number}
+ * @product highcharts highstock gantt
+ * @apioption xAxis.title.y
+ */
+
/**
* Alignment of the title relative to the axis values. Possible
* values are "low", "middle" or "high".
- *
+ *
+ * @sample {highcharts} highcharts/xaxis/title-align-low/
+ * "low"
+ * @sample {highcharts} highcharts/xaxis/title-align-center/
+ * "middle" by default
+ * @sample {highcharts} highcharts/xaxis/title-align-high/
+ * "high"
+ * @sample {highcharts} highcharts/yaxis/title-offset/
+ * Place the Y axis title on top of the axis
+ * @sample {highstock} stock/xaxis/title-align/
+ * Aligned to "high" value
+ *
* @validvalue ["low", "middle", "high"]
- * @sample {highcharts} highcharts/xaxis/title-align-low/
- * "low"
- * @sample {highcharts} highcharts/xaxis/title-align-center/
- * "middle" by default
- * @sample {highcharts} highcharts/xaxis/title-align-high/
- * "high"
- * @sample {highcharts} highcharts/yaxis/title-offset/
- * Place the Y axis title on top of the axis
- * @sample {highstock} stock/xaxis/title-align/
- * Aligned to "high" value
*/
align: 'middle',
-
-
/**
* CSS styles for the title. If the title text is longer than the
* axis length, it will wrap to multiple lines by default. This can
- * be customized by setting `textOverflow: 'ellipsis'`, by
- * setting a specific `width` or by setting `wordSpace: 'nowrap'`.
- *
+ * be customized by setting `textOverflow: 'ellipsis'`, by
+ * setting a specific `width` or by setting `whiteSpace: 'nowrap'`.
+ *
* In styled mode, the stroke width is given in the
* `.highcharts-axis-title` class.
- *
- * @type {CSSObject}
- * @sample {highcharts} highcharts/xaxis/title-style/
- * Red
- * @sample {highcharts} highcharts/css/axis/
- * Styled mode
- * @default { "color": "#666666" }
+ *
+ * @sample {highcharts} highcharts/xaxis/title-style/
+ * Red
+ * @sample {highcharts} highcharts/css/axis/
+ * Styled mode
+ *
+ * @type {Highcharts.CSSObject}
+ * @default {"color": "#666666"}
*/
style: {
+ /** @ignore-option */
color: '#666666'
}
-
},
/**
* The type of axis. Can be one of `linear`, `logarithmic`, `datetime`
* or `category`. In a datetime axis, the numbers are given in
* milliseconds, and tick marks are placed on appropriate values like
- * full hours or days. In a category axis, the
+ * full hours or days. In a category axis, the
* [point names](#series.line.data.name) of the chart's series are used
* for categories, if not a [categories](#xAxis.categories) array is
* defined.
- *
+ *
+ * @sample {highcharts} highcharts/xaxis/type-linear/
+ * Linear
+ * @sample {highcharts} highcharts/yaxis/type-log/
+ * Logarithmic
+ * @sample {highcharts} highcharts/yaxis/type-log-minorgrid/
+ * Logarithmic with minor grid lines
+ * @sample {highcharts} highcharts/xaxis/type-log-both/
+ * Logarithmic on two axes
+ * @sample {highcharts} highcharts/yaxis/type-log-negative/
+ * Logarithmic with extension to emulate negative values
+ *
+ * @product highcharts gantt
* @validvalue ["linear", "logarithmic", "datetime", "category"]
- * @sample {highcharts} highcharts/xaxis/type-linear/
- * Linear
- * @sample {highcharts} highcharts/yaxis/type-log/
- * Logarithmic
- * @sample {highcharts} highcharts/yaxis/type-log-minorgrid/
- * Logarithmic with minor grid lines
- * @sample {highcharts} highcharts/xaxis/type-log-both/
- * Logarithmic on two axes
- * @sample {highcharts} highcharts/yaxis/type-log-negative/
- * Logarithmic with extension to emulate negative values
- * @product highcharts
*/
type: 'linear',
+ /**
+ * The type of axis. Can be one of `linear`, `logarithmic`, `datetime`,
+ * `category` or `treegrid`. Defaults to `treegrid` for Gantt charts,
+ * `linear` for other chart types.
+ *
+ * In a datetime axis, the numbers are given in milliseconds, and tick
+ * marks are placed on appropriate values, like full hours or days. In a
+ * category or treegrid axis, the [point names](#series.line.data.name)
+ * of the chart's series are used for categories, if a
+ * [categories](#xAxis.categories) array is not defined.
+ *
+ * @sample {highcharts} highcharts/yaxis/type-log-minorgrid/
+ * Logarithmic with minor grid lines
+ * @sample {highcharts} highcharts/yaxis/type-log-negative/
+ * Logarithmic with extension to emulate negative values
+ * @sample {gantt} gantt/treegrid-axis/demo
+ * Treegrid axis
+ *
+ * @default {highcharts} linear
+ * @default {gantt} treegrid
+ * @product highcharts gantt
+ * @validvalue ["linear", "logarithmic", "datetime", "category",
+ * "treegrid"]
+ * @apioption yAxis.type
+ */
+
+ /**
+ * Applies only when the axis `type` is `category`. When `uniqueNames`
+ * is true, points are placed on the X axis according to their names.
+ * If the same point name is repeated in the same or another series,
+ * the point is placed on the same X position as other points of the
+ * same name. When `uniqueNames` is false, the points are laid out in
+ * increasing X positions regardless of their names, and the X axis
+ * category will take the name of the last point in each position.
+ *
+ * @sample {highcharts} highcharts/xaxis/uniquenames-true/
+ * True by default
+ * @sample {highcharts} highcharts/xaxis/uniquenames-false/
+ * False
+ *
+ * @type {boolean}
+ * @default true
+ * @since 4.2.7
+ * @product highcharts gantt
+ * @apioption xAxis.uniqueNames
+ */
+
+ /**
+ * Datetime axis only. An array determining what time intervals the
+ * ticks are allowed to fall on. Each array item is an array where the
+ * first value is the time unit and the second value another array of
+ * allowed multiples. Defaults to:
+ *
+ * units: [[
+ * 'millisecond', // unit name
+ * [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
+ * ], [
+ * 'second',
+ * [1, 2, 5, 10, 15, 30]
+ * ], [
+ * 'minute',
+ * [1, 2, 5, 10, 15, 30]
+ * ], [
+ * 'hour',
+ * [1, 2, 3, 4, 6, 8, 12]
+ * ], [
+ * 'day',
+ * [1]
+ * ], [
+ * 'week',
+ * [1]
+ * ], [
+ * 'month',
+ * [1, 3, 6]
+ * ], [
+ * 'year',
+ * null
+ * ]]
+ *
+ * @type {Array|null)>>}
+ * @product highcharts highstock gantt
+ * @apioption xAxis.units
+ */
+ /**
+ * Whether axis, including axis title, line, ticks and labels, should
+ * be visible.
+ *
+ * @type {boolean}
+ * @default true
+ * @since 4.1.9
+ * @product highcharts highstock gantt
+ * @apioption xAxis.visible
+ */
/**
* Color of the minor, secondary grid lines.
- *
+ *
* In styled mode, the stroke width is given in the
* `.highcharts-minor-grid-line` class.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/yaxis/minorgridlinecolor/
- * Bright grey lines from Y axis
- * @sample {highcharts|highstock} highcharts/css/axis-grid/
- * Styled mode
- * @sample {highstock} stock/xaxis/minorgridlinecolor/
- * Bright grey lines from Y axis
+ *
+ * @sample {highcharts} highcharts/yaxis/minorgridlinecolor/
+ * Bright grey lines from Y axis
+ * @sample {highcharts|highstock} highcharts/css/axis-grid/
+ * Styled mode
+ * @sample {highstock} stock/xaxis/minorgridlinecolor/
+ * Bright grey lines from Y axis
+ *
+ * @type {Highcharts.ColorString}
* @default #f2f2f2
*/
minorGridLineColor: '#f2f2f2',
- // minorGridLineDashStyle: null,
/**
* Width of the minor, secondary grid lines.
- *
+ *
* In styled mode, the stroke width is given in the
* `.highcharts-grid-line` class.
- *
+ *
* @sample {highcharts} highcharts/yaxis/minorgridlinewidth/
* 2px lines from Y axis
* @sample {highcharts|highstock} highcharts/css/axis-grid/
@@ -11886,49 +16355,52 @@
/**
* Color for the minor tick marks.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/yaxis/minortickcolor/
- * Black tick marks on Y axis
- * @sample {highstock} stock/xaxis/minorticks/
- * Black tick marks on Y axis
+ *
+ * @sample {highcharts} highcharts/yaxis/minortickcolor/
+ * Black tick marks on Y axis
+ * @sample {highstock} stock/xaxis/minorticks/
+ * Black tick marks on Y axis
+ *
+ * @type {Highcharts.ColorString}
* @default #999999
*/
minorTickColor: '#999999',
/**
* The color of the line marking the axis itself.
- *
+ *
* In styled mode, the line stroke is given in the
* `.highcharts-axis-line` or `.highcharts-xaxis-line` class.
- *
+ *
* @productdesc {highmaps}
* In Highmaps, the axis line is hidden by default, because the axis is
* not visible by default.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/yaxis/linecolor/
- * A red line on Y axis
- * @sample {highcharts|highstock} highcharts/css/axis/
- * Axes in styled mode
- * @sample {highstock} stock/xaxis/linecolor/
- * A red line on X axis
+ *
+ * @sample {highcharts} highcharts/yaxis/linecolor/
+ * A red line on Y axis
+ * @sample {highcharts|highstock} highcharts/css/axis/
+ * Axes in styled mode
+ * @sample {highstock} stock/xaxis/linecolor/
+ * A red line on X axis
+ *
+ * @type {Highcharts.ColorString}
* @default #ccd6eb
*/
lineColor: '#ccd6eb',
/**
* The width of the line marking the axis itself.
- *
+ *
* In styled mode, the stroke width is given in the
* `.highcharts-axis-line` or `.highcharts-xaxis-line` class.
- *
- * @sample {highcharts} highcharts/yaxis/linecolor/
- * A 1px line on Y axis
- * @sample {highcharts|highstock} highcharts/css/axis/
- * Axes in styled mode
- * @sample {highstock} stock/xaxis/linewidth/
- * A 2px line on X axis
+ *
+ * @sample {highcharts} highcharts/yaxis/linecolor/
+ * A 1px line on Y axis
+ * @sample {highcharts|highstock} highcharts/css/axis/
+ * Axes in styled mode
+ * @sample {highstock} stock/xaxis/linewidth/
+ * A 2px line on X axis
+ *
* @default {highcharts|highstock} 1
* @default {highmaps} 0
*/
@@ -11936,25 +16408,26 @@
/**
* Color of the grid lines extending the ticks across the plot area.
- *
+ *
* In styled mode, the stroke is given in the `.highcharts-grid-line`
* class.
*
* @productdesc {highmaps}
* In Highmaps, the grid lines are hidden by default.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/yaxis/gridlinecolor/
- * Green lines
- * @sample {highcharts|highstock} highcharts/css/axis-grid/
- * Styled mode
- * @sample {highstock} stock/xaxis/gridlinecolor/
- * Green lines
+ *
+ * @sample {highcharts} highcharts/yaxis/gridlinecolor/
+ * Green lines
+ * @sample {highcharts|highstock} highcharts/css/axis-grid/
+ * Styled mode
+ * @sample {highstock} stock/xaxis/gridlinecolor/
+ * Green lines
+ *
+ * @type {Highcharts.ColorString}
* @default #e6e6e6
*/
gridLineColor: '#e6e6e6',
- // gridLineDashStyle: 'solid',
+ // gridLineDashStyle: 'solid',
/**
* The width of the grid lines extending the ticks across the plot area.
@@ -11962,13 +16435,14 @@
* In styled mode, the stroke width is given in the
* `.highcharts-grid-line` class.
*
- * @type {Number}
- * @sample {highcharts} highcharts/yaxis/gridlinewidth/
- * 2px lines
- * @sample {highcharts|highstock} highcharts/css/axis-grid/
- * Styled mode
- * @sample {highstock} stock/xaxis/gridlinewidth/
- * 2px lines
+ * @sample {highcharts} highcharts/yaxis/gridlinewidth/
+ * 2px lines
+ * @sample {highcharts|highstock} highcharts/css/axis-grid/
+ * Styled mode
+ * @sample {highstock} stock/xaxis/gridlinewidth/
+ * 2px lines
+ *
+ * @type {number}
* @default 0
* @apioption xAxis.gridLineWidth
*/
@@ -11976,37 +16450,210 @@
/**
* Color for the main tick marks.
- *
+ *
* In styled mode, the stroke is given in the `.highcharts-tick`
* class.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/xaxis/tickcolor/
- * Red ticks on X axis
- * @sample {highcharts|highstock} highcharts/css/axis-grid/
- * Styled mode
- * @sample {highstock} stock/xaxis/ticks/
- * Formatted ticks on X axis
+ *
+ * @sample {highcharts} highcharts/xaxis/tickcolor/
+ * Red ticks on X axis
+ * @sample {highcharts|highstock} highcharts/css/axis-grid/
+ * Styled mode
+ * @sample {highstock} stock/xaxis/ticks/
+ * Formatted ticks on X axis
+ *
+ * @type {Highcharts.ColorString}
* @default #ccd6eb
*/
- tickColor: '#ccd6eb'
- // tickWidth: 1
+ tickColor: '#ccd6eb'
+
+ // tickWidth: 1
+ },
+
+ /**
+ * The Y axis or value axis. Normally this is the vertical axis,
+ * though if the chart is inverted this is the horizontal axis.
+ * In case of multiple axes, the yAxis node is an array of
+ * configuration objects.
+ *
+ * See [the Axis object](/class-reference/Highcharts.Axis) for programmatic
+ * access to the axis.
+ *
+ * @type {*|Array<*>}
+ * @extends xAxis
+ * @excluding ordinal,overscroll,currentDateIndicator
+ * @optionparent yAxis
+ */
+ defaultYAxisOptions: {
+
+ /**
+ * In a polar chart, this is the angle of the Y axis in degrees, where
+ * 0 is up and 90 is right. The angle determines the position of the
+ * axis line and the labels, though the coordinate system is unaffected.
+ *
+ * @sample {highcharts} highcharts/yaxis/angle/
+ * Dual axis polar chart
+ *
+ * @type {number}
+ * @default 0
+ * @since 4.2.7
+ * @product highcharts
+ * @apioption yAxis.angle
+ */
+
+ /**
+ * Polar charts only. Whether the grid lines should draw as a polygon
+ * with straight lines between categories, or as circles. Can be either
+ * `circle` or `polygon`.
+ *
+ * @sample {highcharts} highcharts/demo/polar-spider/
+ * Polygon grid lines
+ * @sample {highcharts} highcharts/yaxis/gridlineinterpolation/
+ * Circle and polygon
+ *
+ * @type {string}
+ * @product highcharts
+ * @validvalue ["circle", "polygon"]
+ * @apioption yAxis.gridLineInterpolation
+ */
+
+ /**
+ * The height of the Y axis. If it's a number, it is interpreted as
+ * pixels.
+ *
+ * Since Highstock 2: If it's a percentage string, it is interpreted
+ * as percentages of the total plot height.
+ *
+ * @see [yAxis.top](#yAxis.top)
+ *
+ * @sample {highstock} stock/demo/candlestick-and-volume/
+ * Percentage height panes
+ *
+ * @type {number|string}
+ * @product highstock
+ * @apioption yAxis.height
+ */
+
+ /**
+ * Solid gauge only. Unless [stops](#yAxis.stops) are set, the color
+ * to represent the maximum value of the Y axis.
+ *
+ * @sample {highcharts} highcharts/yaxis/mincolor-maxcolor/
+ * Min and max colors
+ *
+ * @type {Highcharts.ColorString}
+ * @default #003399
+ * @since 4.0
+ * @product highcharts
+ * @apioption yAxis.maxColor
+ */
+
+ /**
+ * Solid gauge only. Unless [stops](#yAxis.stops) are set, the color
+ * to represent the minimum value of the Y axis.
+ *
+ * @sample {highcharts} highcharts/yaxis/mincolor-maxcolor/
+ * Min and max color
+ *
+ * @type {Highcharts.ColorString}
+ * @default #e6ebf5
+ * @since 4.0
+ * @product highcharts
+ * @apioption yAxis.minColor
+ */
+
+ /**
+ * Whether to reverse the axis so that the highest number is closest
+ * to the origin.
+ *
+ * @sample {highcharts} highcharts/yaxis/reversed/
+ * Reversed Y axis
+ * @sample {highstock} stock/xaxis/reversed/
+ * Reversed Y axis
+ *
+ * @type {boolean}
+ * @default {highcharts} false
+ * @default {highstock} false
+ * @default {highmaps} true
+ * @default {gantt} true
+ * @apioption yAxis.reversed
+ */
+
+ /**
+ * If `true`, the first series in a stack will be drawn on top in a
+ * positive, non-reversed Y axis. If `false`, the first series is in
+ * the base of the stack.
+ *
+ * @sample {highcharts} highcharts/yaxis/reversedstacks-false/
+ * Non-reversed stacks
+ * @sample {highstock} highcharts/yaxis/reversedstacks-false/
+ * Non-reversed stacks
+ *
+ * @type {boolean}
+ * @default true
+ * @since 3.0.10
+ * @product highcharts highstock
+ * @apioption yAxis.reversedStacks
+ */
+
+ /**
+ * Solid gauge series only. Color stops for the solid gauge. Use this
+ * in cases where a linear gradient between a `minColor` and `maxColor`
+ * is not sufficient. The stops is an array of tuples, where the first
+ * item is a float between 0 and 1 assigning the relative position in
+ * the gradient, and the second item is the color.
+ *
+ * For solid gauges, the Y axis also inherits the concept of
+ * [data classes](http://api.highcharts.com/highmaps#colorAxis.dataClasses)
+ * from the Highmaps color axis.
+ *
+ * @see [minColor](#yAxis.minColor)
+ * @see [maxColor](#yAxis.maxColor)
+ *
+ * @sample {highcharts} highcharts/demo/gauge-solid/
+ * True by default
+ *
+ * @type {Array>}
+ * @since 4.0
+ * @product highcharts
+ * @apioption yAxis.stops
+ */
+
+ /**
+ * The pixel width of the major tick marks.
+ *
+ * @sample {highcharts} highcharts/xaxis/tickwidth/ 10 px width
+ * @sample {highstock} stock/xaxis/ticks/ Formatted ticks on X axis
+ *
+ * @type {number}
+ * @default 0
+ * @product highcharts highstock gantt
+ * @apioption yAxis.tickWidth
+ */
+
+ /**
+ * Angular gauges and solid gauges only. The label's pixel distance
+ * from the perimeter of the plot area.
+ *
+ * @type {number}
+ * @default -25
+ * @product highcharts
+ * @apioption yAxis.labels.distance
+ */
- },
+ /**
+ * The y position offset of the label relative to the tick position
+ * on the axis.
+ *
+ * @sample {highcharts} highcharts/xaxis/labels-x/
+ * Y axis labels placed on grid lines
+ *
+ * @type {number}
+ * @default {highcharts} 3
+ * @default {highstock} -2
+ * @default {highmaps} 3
+ * @apioption yAxis.labels.y
+ */
- /**
- * The Y axis or value axis. Normally this is the vertical axis,
- * though if the chart is inverted this is the horizontal axis.
- * In case of multiple axes, the yAxis node is an array of
- * configuration objects.
- *
- * See [the Axis object](#Axis) for programmatic access to the axis.
- *
- * @extends xAxis
- * @excluding ordinal,overscroll
- * @optionparent yAxis
- */
- defaultYAxisOptions: {
/**
* @productdesc {highstock}
* In Highstock, `endOnTick` is always false when the navigator is
@@ -12014,24 +16661,60 @@
*/
endOnTick: true,
+ /**
+ * Padding of the max value relative to the length of the axis. A
+ * padding of 0.05 will make a 100px axis 5px longer. This is useful
+ * when you don't want the highest data value to appear on the edge
+ * of the plot area. When the axis' `max` option is set or a max extreme
+ * is set using `axis.setExtremes()`, the maxPadding will be ignored.
+ *
+ * @sample {highcharts} highcharts/yaxis/maxpadding-02/
+ * Max padding of 0.2
+ * @sample {highstock} stock/xaxis/minpadding-maxpadding/
+ * Greater min- and maxPadding
+ *
+ * @since 1.2.0
+ * @product highcharts highstock gantt
+ */
+ maxPadding: 0.05,
+
+ /**
+ * Padding of the min value relative to the length of the axis. A
+ * padding of 0.05 will make a 100px axis 5px longer. This is useful
+ * when you don't want the lowest data value to appear on the edge
+ * of the plot area. When the axis' `min` option is set or a max extreme
+ * is set using `axis.setExtremes()`, the maxPadding will be ignored.
+ *
+ * @sample {highcharts} highcharts/yaxis/minpadding/
+ * Min padding of 0.2
+ * @sample {highstock} stock/xaxis/minpadding-maxpadding/
+ * Greater min- and maxPadding
+ *
+ * @since 1.2.0
+ * @product highcharts highstock gantt
+ */
+ minPadding: 0.05,
+
/**
* @productdesc {highstock}
* In Highstock 1.x, the Y axis was placed on the left side by default.
*
- * @sample {highcharts} highcharts/yaxis/opposite/
- * Secondary Y axis opposite
- * @sample {highstock} stock/xaxis/opposite/
- * Y axis on left side
+ * @sample {highcharts} highcharts/yaxis/opposite/
+ * Secondary Y axis opposite
+ * @sample {highstock} stock/xaxis/opposite/
+ * Y axis on left side
+ *
+ * @type {boolean}
* @default {highstock} true
* @default {highcharts} false
- * @product highstock highcharts
+ * @product highstock highcharts gantt
* @apioption yAxis.opposite
*/
/**
- * @see [tickInterval](#xAxis.tickInterval),
- * [tickPositioner](#xAxis.tickPositioner),
- * [tickPositions](#xAxis.tickPositions).
+ * @see [tickInterval](#xAxis.tickInterval)
+ * @see [tickPositioner](#xAxis.tickPositioner)
+ * @see [tickPositions](#xAxis.tickPositions)
*/
tickPixelInterval: 72,
@@ -12048,19 +16731,20 @@
*
* Angular gauges and solid gauges defaults to `center`.
*
- * @validvalue ["left", "center", "right"]
- * @type {String}
- * @sample {highcharts} highcharts/yaxis/labels-align-left/
- * Left
+ * @sample {highcharts} highcharts/yaxis/labels-align-left/
+ * Left
+ *
+ * @type {string}
* @default {highcharts|highmaps} right
* @default {highstock} left
+ * @validvalue ["left", "center", "right"]
* @apioption yAxis.labels.align
*/
/**
* The x position offset of the label relative to the tick position
* on the axis. Defaults to -15 for left axis, 15 for right axis.
- *
+ *
* @sample {highcharts} highcharts/xaxis/labels-x/
* Y axis labels placed on grid lines
*/
@@ -12071,90 +16755,312 @@
* @productdesc {highmaps}
* In Highmaps, the axis line is hidden by default, because the axis is
* not visible by default.
- *
+ *
+ * @type {Highcharts.ColorString}
* @apioption yAxis.lineColor
*/
/**
- * @sample {highcharts} highcharts/yaxis/min-startontick-false/
- * -50 with startOnTick to false
- * @sample {highcharts} highcharts/yaxis/min-startontick-true/
- * -50 with startOnTick true by default
- * @sample {highstock} stock/yaxis/min-max/
- * Fixed min and max on Y axis
- * @sample {highmaps} maps/axis/min-max/
- * Pre-zoomed to a specific area
- * @apioption yAxis.min
+ * If there are multiple axes on the same side of the chart, the pixel
+ * margin between the axes. Defaults to 0 on vertical axes, 15 on
+ * horizontal axes.
+ *
+ * @type number
+ * @since 7.0.3
+ * @apioption xAxis.margin
*/
/**
- * @sample {highcharts} highcharts/yaxis/max-200/
- * Y axis max of 200
- * @sample {highcharts} highcharts/yaxis/max-logarithmic/
- * Y axis max on logarithmic axis
- * @sample {highstock} stock/yaxis/min-max/
- * Fixed min and max on Y axis
- * @sample {highmaps} maps/axis/min-max/
- * Pre-zoomed to a specific area
+ * @sample {highcharts} highcharts/yaxis/max-200/
+ * Y axis max of 200
+ * @sample {highcharts} highcharts/yaxis/max-logarithmic/
+ * Y axis max on logarithmic axis
+ * @sample {highstock} stock/yaxis/min-max/
+ * Fixed min and max on Y axis
+ * @sample {highmaps} maps/axis/min-max/
+ * Pre-zoomed to a specific area
+ *
+ * @type {number}
* @apioption yAxis.max
*/
/**
- * Padding of the max value relative to the length of the axis. A
- * padding of 0.05 will make a 100px axis 5px longer. This is useful
- * when you don't want the highest data value to appear on the edge
- * of the plot area. When the axis' `max` option is set or a max extreme
- * is set using `axis.setExtremes()`, the maxPadding will be ignored.
- *
- * @sample {highcharts} highcharts/yaxis/maxpadding-02/
- * Max padding of 0.2
- * @sample {highstock} stock/xaxis/minpadding-maxpadding/
- * Greater min- and maxPadding
- * @since 1.2.0
- * @product highcharts highstock
+ * @sample {highcharts} highcharts/yaxis/min-startontick-false/
+ * -50 with startOnTick to false
+ * @sample {highcharts} highcharts/yaxis/min-startontick-true/
+ * -50 with startOnTick true by default
+ * @sample {highstock} stock/yaxis/min-max/
+ * Fixed min and max on Y axis
+ * @sample {highmaps} maps/axis/min-max/
+ * Pre-zoomed to a specific area
+ *
+ * @type {number}
+ * @apioption yAxis.min
*/
- maxPadding: 0.05,
/**
- * Padding of the min value relative to the length of the axis. A
- * padding of 0.05 will make a 100px axis 5px longer. This is useful
- * when you don't want the lowest data value to appear on the edge
- * of the plot area. When the axis' `min` option is set or a max extreme
- * is set using `axis.setExtremes()`, the maxPadding will be ignored.
- *
- * @sample {highcharts} highcharts/yaxis/minpadding/
- * Min padding of 0.2
- * @sample {highstock} stock/xaxis/minpadding-maxpadding/
- * Greater min- and maxPadding
- * @since 1.2.0
- * @product highcharts highstock
+ * An optional scrollbar to display on the Y axis in response to
+ * limiting the minimum an maximum of the axis values.
+ *
+ * In styled mode, all the presentational options for the scrollbar
+ * are replaced by the classes `.highcharts-scrollbar-thumb`,
+ * `.highcharts-scrollbar-arrow`, `.highcharts-scrollbar-button`,
+ * `.highcharts-scrollbar-rifles` and `.highcharts-scrollbar-track`.
+ *
+ * @sample {highstock} stock/yaxis/scrollbar/
+ * Scrollbar on the Y axis
+ *
+ * @extends scrollbar
+ * @since 4.2.6
+ * @product highstock
+ * @excluding height
+ * @apioption yAxis.scrollbar
+ */
+
+ /**
+ * Enable the scrollbar on the Y axis.
+ *
+ * @sample {highstock} stock/yaxis/scrollbar/
+ * Enabled on Y axis
+ *
+ * @type {boolean}
+ * @default false
+ * @since 4.2.6
+ * @product highstock
+ * @apioption yAxis.scrollbar.enabled
+ */
+
+ /**
+ * Pixel margin between the scrollbar and the axis elements.
+ *
+ * @type {number}
+ * @default 10
+ * @since 4.2.6
+ * @product highstock
+ * @apioption yAxis.scrollbar.margin
+ */
+
+ /**
+ * Whether to show the scrollbar when it is fully zoomed out at max
+ * range. Setting it to `false` on the Y axis makes the scrollbar stay
+ * hidden until the user zooms in, like common in browsers.
+ *
+ * @type {boolean}
+ * @default true
+ * @since 4.2.6
+ * @product highstock
+ * @apioption yAxis.scrollbar.showFull
+ */
+
+ /**
+ * The width of a vertical scrollbar or height of a horizontal
+ * scrollbar. Defaults to 20 on touch devices.
+ *
+ * @type {number}
+ * @default 14
+ * @since 4.2.6
+ * @product highstock
+ * @apioption yAxis.scrollbar.size
+ */
+
+ /**
+ * Z index of the scrollbar elements.
+ *
+ * @type {number}
+ * @default 3
+ * @since 4.2.6
+ * @product highstock
+ * @apioption yAxis.scrollbar.zIndex
+ */
+
+ /**
+ * A soft maximum for the axis. If the series data maximum is less
+ * than this, the axis will stay at this maximum, but if the series
+ * data maximum is higher, the axis will flex to show all data.
+ *
+ * **Note**: The [series.softThreshold](
+ * #plotOptions.series.softThreshold) option takes precedence over this
+ * option.
+ *
+ * @sample highcharts/yaxis/softmin-softmax/
+ * Soft min and max
+ *
+ * @type {number}
+ * @since 5.0.1
+ * @product highcharts highstock gantt
+ * @apioption yAxis.softMax
+ */
+
+ /**
+ * A soft minimum for the axis. If the series data minimum is greater
+ * than this, the axis will stay at this minimum, but if the series
+ * data minimum is lower, the axis will flex to show all data.
+ *
+ * **Note**: The [series.softThreshold](
+ * #plotOptions.series.softThreshold) option takes precedence over this
+ * option.
+ *
+ * @sample highcharts/yaxis/softmin-softmax/
+ * Soft min and max
+ *
+ * @type {number}
+ * @since 5.0.1
+ * @product highcharts highstock gantt
+ * @apioption yAxis.softMin
+ */
+
+ /**
+ * Defines the horizontal alignment of the stack total label. Can be one
+ * of `"left"`, `"center"` or `"right"`. The default value is calculated
+ * at runtime and depends on orientation and whether the stack is
+ * positive or negative.
+ *
+ * @sample {highcharts} highcharts/yaxis/stacklabels-align-left/
+ * Aligned to the left
+ * @sample {highcharts} highcharts/yaxis/stacklabels-align-center/
+ * Aligned in center
+ * @sample {highcharts} highcharts/yaxis/stacklabels-align-right/
+ * Aligned to the right
+ *
+ * @type {Highcharts.AlignType}
+ * @since 2.1.5
+ * @product highcharts
+ * @apioption yAxis.stackLabels.align
+ */
+
+ /**
+ * A [format string](http://docs.highcharts.com/#formatting) for the
+ * data label. Available variables are the same as for `formatter`.
+ *
+ * @type {string}
+ * @default {total}
+ * @since 3.0.2
+ * @product highcharts highstock
+ * @apioption yAxis.stackLabels.format
+ */
+
+ /**
+ * Rotation of the labels in degrees.
+ *
+ * @sample {highcharts} highcharts/yaxis/stacklabels-rotation/
+ * Labels rotated 45°
+ *
+ * @type {number}
+ * @default 0
+ * @since 2.1.5
+ * @product highcharts
+ * @apioption yAxis.stackLabels.rotation
+ */
+
+ /**
+ * The text alignment for the label. While `align` determines where the
+ * texts anchor point is placed with regards to the stack, `textAlign`
+ * determines how the text is aligned against its anchor point. Possible
+ * values are `"left"`, `"center"` and `"right"`. The default value is
+ * calculated at runtime and depends on orientation and whether the
+ * stack is positive or negative.
+ *
+ * @sample {highcharts} highcharts/yaxis/stacklabels-textalign-left/
+ * Label in center position but text-aligned left
+ *
+ * @type {Highcharts.AlignType}
+ * @since 2.1.5
+ * @product highcharts
+ * @apioption yAxis.stackLabels.textAlign
+ */
+
+ /**
+ * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
+ * to render the labels.
+ *
+ * @type {boolean}
+ * @default false
+ * @since 3.0
+ * @product highcharts highstock
+ * @apioption yAxis.stackLabels.useHTML
+ */
+
+ /**
+ * Defines the vertical alignment of the stack total label. Can be one
+ * of `"top"`, `"middle"` or `"bottom"`. The default value is calculated
+ * at runtime and depends on orientation and whether the stack is
+ * positive or negative.
+ *
+ * @sample {highcharts} highcharts/yaxis/stacklabels-verticalalign-top/
+ * Vertically aligned top
+ * @sample {highcharts} highcharts/yaxis/stacklabels-verticalalign-middle/
+ * Vertically aligned middle
+ * @sample {highcharts} highcharts/yaxis/stacklabels-verticalalign-bottom/
+ * Vertically aligned bottom
+ *
+ * @type {Highcharts.VerticalAlignType}
+ * @since 2.1.5
+ * @product highcharts
+ * @apioption yAxis.stackLabels.verticalAlign
+ */
+
+ /**
+ * The x position offset of the label relative to the left of the
+ * stacked bar. The default value is calculated at runtime and depends
+ * on orientation and whether the stack is positive or negative.
+ *
+ * @sample {highcharts} highcharts/yaxis/stacklabels-x/
+ * Stack total labels with x offset
+ *
+ * @type {number}
+ * @since 2.1.5
+ * @product highcharts
+ * @apioption yAxis.stackLabels.x
+ */
+
+ /**
+ * The y position offset of the label relative to the tick position
+ * on the axis. The default value is calculated at runtime and depends
+ * on orientation and whether the stack is positive or negative.
+ *
+ * @sample {highcharts} highcharts/yaxis/stacklabels-y/
+ * Stack total labels with y offset
+ *
+ * @type {number}
+ * @since 2.1.5
+ * @product highcharts
+ * @apioption yAxis.stackLabels.y
*/
- minPadding: 0.05,
/**
* Whether to force the axis to start on a tick. Use this option with
* the `maxPadding` option to control the axis start.
- *
- * @sample {highcharts} highcharts/xaxis/startontick-false/
- * False by default
- * @sample {highcharts} highcharts/xaxis/startontick-true/
- * True
- * @sample {highstock} stock/xaxis/endontick/
- * False for Y axis
+ *
+ * @sample {highcharts} highcharts/xaxis/startontick-false/
+ * False by default
+ * @sample {highcharts} highcharts/xaxis/startontick-true/
+ * True
+ * @sample {highstock} stock/xaxis/endontick/
+ * False for Y axis
+ *
* @since 1.2.0
- * @product highcharts highstock
+ * @product highcharts highstock gantt
*/
startOnTick: true,
- /**
- * @extends xAxis.title
- */
title: {
+ /**
+ * The pixel distance between the axis labels and the title.
+ * Positive values are outside the axis line, negative are inside.
+ *
+ * @sample {highcharts} highcharts/xaxis/title-margin/
+ * Y axis title margin of 60
+ *
+ * @type {number}
+ * @default 40
+ * @apioption yAxis.title.margin
+ */
+
/**
* The rotation of the text in degrees. 0 is horizontal, 270 is
* vertical reading from bottom to top.
- *
+ *
* @sample {highcharts} highcharts/yaxis/title-offset/
* Horizontal
*/
@@ -12164,34 +17070,53 @@
* The actual text of the axis title. Horizontal texts can contain
* HTML, but rotated texts are painted using vector techniques and
* must be clean text. The Y axis title is disabled by setting the
- * `text` option to `null`.
- *
- * @sample {highcharts} highcharts/xaxis/title-text/
- * Custom HTML
+ * `text` option to `undefined`.
+ *
+ * @sample {highcharts} highcharts/xaxis/title-text/
+ * Custom HTML
+ *
+ * @type {string|null}
* @default {highcharts} Values
- * @default {highstock} null
- * @product highcharts highstock
+ * @default {highstock} undefined
+ * @product highcharts highstock gantt
*/
text: 'Values'
},
+ /**
+ * The top position of the Y axis. If it's a number, it is interpreted
+ * as pixel position relative to the chart.
+ *
+ * Since Highstock 2: If it's a percentage string, it is interpreted
+ * as percentages of the plot height, offset from plot area top.
+ *
+ * @see [yAxis.height](#yAxis.height)
+ *
+ * @sample {highstock} stock/demo/candlestick-and-volume/
+ * Percentage height panes
+ *
+ * @type {number|string}
+ * @product highstock
+ * @apioption yAxis.top
+ */
+
/**
* The stack labels show the total value for each bar in a stacked
* column or bar chart. The label will be placed on top of positive
* columns and below negative columns. In case of an inverted column
* chart or a bar chart the label is placed to the right of positive
* bars and to the left of negative bars.
- *
+ *
* @product highcharts
*/
stackLabels: {
/**
* Allow the stack labels to overlap.
- *
- * @sample {highcharts}
- * highcharts/yaxis/stacklabels-allowoverlap-false/
- * Default false
+ *
+ * @sample {highcharts} highcharts/yaxis/stacklabels-allowoverlap-false/
+ * Default false
+ *
* @since 5.0.13
* @product highcharts
*/
@@ -12199,9 +17124,10 @@
/**
* Enable or disable the stack total labels.
- *
- * @sample {highcharts} highcharts/yaxis/stacklabels-enabled/
- * Enabled stack total labels
+ *
+ * @sample {highcharts} highcharts/yaxis/stacklabels-enabled/
+ * Enabled stack total labels
+ *
* @since 2.1.5
* @product highcharts
*/
@@ -12211,52 +17137,69 @@
* Callback JavaScript function to format the label. The value is
* given by `this.total`.
*
- * @default function() { return this.total; }
- *
- * @type {Function}
- * @sample {highcharts} highcharts/yaxis/stacklabels-formatter/
- * Added units to stack total value
+ * @sample {highcharts} highcharts/yaxis/stacklabels-formatter/
+ * Added units to stack total value
+ *
+ * @type {Highcharts.FormatterCallbackFunction}
* @since 2.1.5
* @product highcharts
*/
- formatter: function() {
+ formatter: function () {
return H.numberFormat(this.total, -1);
},
-
/**
* CSS styles for the label.
- *
+ *
* In styled mode, the styles are set in the
* `.highcharts-stack-label` class.
- *
- * @type {CSSObject}
- * @sample {highcharts} highcharts/yaxis/stacklabels-style/
- * Red stack total labels
+ *
+ * @sample {highcharts} highcharts/yaxis/stacklabels-style/
+ * Red stack total labels
+ *
+ * @type {Highcharts.CSSObject}
+ * @default {"color": "#666666", "fontSize": "11px", "fontWeight": "bold", "textOutline": "1px contrast"}
* @since 2.1.5
* @product highcharts
*/
style: {
+ /** @ignore-option */
+ color: '#000000',
+ /** @ignore-option */
fontSize: '11px',
+ /** @ignore-option */
fontWeight: 'bold',
- color: '#000000',
+ /** @ignore-option */
textOutline: '1px contrast'
}
-
},
-
gridLineWidth: 1,
+
lineWidth: 0
- // tickWidth: 0
+ // tickWidth: 0
},
/**
- * These options extend the defaultOptions for left axes.
- *
- * @private
- * @type {Object}
+ * The Z axis or depth axis for 3D plots.
+ *
+ * See the [Axis class](/class-reference/Highcharts.Axis) for programmatic
+ * access to the axis.
+ *
+ * @sample {highcharts} highcharts/3d/scatter-zaxis-categories/
+ * Z-Axis with Categories
+ * @sample {highcharts} highcharts/3d/scatter-zaxis-grid/
+ * Z-Axis with styling
+ *
+ * @type {*|Array<*>}
+ * @extends xAxis
+ * @since 5.0.0
+ * @product highcharts
+ * @excluding breaks, crosshair, lineColor, lineWidth, nameToX, showEmpty
+ * @apioption zAxis
*/
+
+ // This variable extends the defaultOptions for left axes.
defaultLeftAxisOptions: {
labels: {
x: -15
@@ -12266,12 +17209,7 @@
}
},
- /**
- * These options extend the defaultOptions for right axes.
- *
- * @private
- * @type {Object}
- */
+ // This variable extends the defaultOptions for right axes.
defaultRightAxisOptions: {
labels: {
x: 15
@@ -12281,12 +17219,7 @@
}
},
- /**
- * These options extend the defaultOptions for bottom axes.
- *
- * @private
- * @type {Object}
- */
+ // This variable extends the defaultOptions for bottom axes.
defaultBottomAxisOptions: {
labels: {
autoRotation: [-45],
@@ -12294,16 +17227,13 @@
// overflow: undefined,
// staggerLines: null
},
+ margin: 15,
title: {
rotation: 0
}
},
- /**
- * These options extend the defaultOptions for top axes.
- *
- * @private
- * @type {Object}
- */
+
+ // This variable extends the defaultOptions for top axes.
defaultTopAxisOptions: {
labels: {
autoRotation: [-45],
@@ -12311,42 +17241,54 @@
// overflow: undefined
// staggerLines: null
},
+ margin: 15,
title: {
rotation: 0
}
},
/**
- * Overrideable function to initialize the axis.
+ * Overrideable function to initialize the axis.
*
* @see {@link Axis}
+ *
+ * @function Highcharts.Axis#init
+ *
+ * @param {Highcharts.Chart} chart
+ *
+ * @param {Highcharts.Options} userOptions
+ *
+ * @fires Highcharts.Axis#event:afterInit
+ * @fires Highcharts.Axis#event:init
*/
- init: function(chart, userOptions) {
+ init: function (chart, userOptions) {
var isXAxis = userOptions.isX,
axis = this;
-
/**
* The Chart that the axis belongs to.
*
- * @name chart
- * @memberOf Axis
- * @type {Chart}
+ * @name Highcharts.Axis#chart
+ * @type {Highcharts.Chart}
*/
axis.chart = chart;
/**
* Whether the axis is horizontal.
*
- * @name horiz
- * @memberOf Axis
- * @type {Boolean}
+ * @name Highcharts.Axis#horiz
+ * @type {boolean}
*/
axis.horiz = chart.inverted && !axis.isZAxis ? !isXAxis : isXAxis;
- // Flag, isXAxis
+ /**
+ * Whether the axis is the x-axis.
+ *
+ * @name Highcharts.Axis#isXAxis
+ * @type {boolean|undefined}
+ */
axis.isXAxis = isXAxis;
/**
@@ -12354,22 +17296,21 @@
* or `colorAxis`. Corresponds to properties on Chart, for example
* {@link Chart.xAxis}.
*
- * @name coll
- * @memberOf Axis
- * @type {String}
+ * @name Highcharts.Axis#coll
+ * @type {string}
*/
axis.coll = axis.coll || (isXAxis ? 'xAxis' : 'yAxis');
+ fireEvent(this, 'init', { userOptions: userOptions });
axis.opposite = userOptions.opposite; // needed in setOptions
/**
- * The side on which the axis is rendered. 0 is top, 1 is right, 2 is
- * bottom and 3 is left.
+ * The side on which the axis is rendered. 0 is top, 1 is right, 2
+ * is bottom and 3 is left.
*
- * @name side
- * @memberOf Axis
- * @type {Number}
+ * @name Highcharts.Axis#side
+ * @type {number}
*/
axis.side = userOptions.side || (axis.horiz ?
(axis.opposite ? 0 : 2) : // top : bottom
@@ -12383,7 +17324,8 @@
isDatetimeAxis = type === 'datetime';
axis.labelFormatter = options.labels.formatter ||
- axis.defaultLabelFormatter; // can be overwritten by dynamic format
+ // can be overwritten by dynamic format
+ axis.defaultLabelFormatter;
// Flag, stagger lines or not
@@ -12396,9 +17338,8 @@
* Whether the axis is reversed. Based on the `axis.reversed`,
* option, but inverted charts have reversed xAxis by default.
*
- * @name reversed
- * @memberOf Axis
- * @type {Boolean}
+ * @name Highcharts.Axis#reversed
+ * @type {boolean}
*/
axis.reversed = options.reversed;
axis.visible = options.visible !== false;
@@ -12407,7 +17348,11 @@
// Initial categories
axis.hasNames = type === 'category' || options.categories === true;
axis.categories = options.categories || axis.hasNames;
- axis.names = axis.names || []; // Preserve on update (#3830)
+ if (!axis.names) { // Preserve on update (#3830)
+ axis.names = [];
+ axis.names.keys = {};
+ }
+
// Placeholder for plotlines and plotbands groups
axis.plotLinesAndBandsGroups = {};
@@ -12420,10 +17365,26 @@
// Flag, if axis is linked to another axis
axis.isLinked = defined(options.linkedTo);
- // Major ticks
+ /**
+ * List of major ticks mapped by postition on axis.
+ *
+ * @see {@link Highcharts.Tick}
+ *
+ * @private
+ * @name Highcharts.Axis#ticks
+ * @type {Highcharts.Dictionary}
+ */
axis.ticks = {};
axis.labelEdge = [];
- // Minor ticks
+ /**
+ * List of minor ticks mapped by position on the axis.
+ *
+ * @see {@link Highcharts.Tick}
+ *
+ * @private
+ * @name Highcharts.Axis#minorTicks
+ * @type {Highcharts.Dictionary}
+ */
axis.minorTicks = {};
// List of plotLines/Bands
@@ -12450,29 +17411,26 @@
* logarithm of the real value, and the real value can be obtained from
* {@link Axis#getExtremes}.
*
- * @name max
- * @memberOf Axis
- * @type {Number}
+ * @name Highcharts.Axis#max
+ * @type {number|null}
*/
axis.max = null;
+
/**
* The minimum value of the axis. In a logarithmic axis, this is the
* logarithm of the real value, and the real value can be obtained from
* {@link Axis#getExtremes}.
*
- * @name min
- * @memberOf Axis
- * @type {Number}
+ * @name Highcharts.Axis#min
+ * @type {number|null}
*/
axis.min = null;
-
/**
* The processed crosshair options.
*
- * @name crosshair
- * @memberOf Axis
- * @type {AxisCrosshairOptions}
+ * @name Highcharts.Axis#crosshair
+ * @type {boolean|Highcharts.AxisCrosshairOptions}
*/
axis.crosshair = pick(
options.crosshair,
@@ -12483,7 +17441,7 @@
var events = axis.options.events;
// Register. Don't add it again on Axis.update().
- if (inArray(axis, chart.axes) === -1) { //
+ if (chart.axes.indexOf(axis) === -1) { //
if (isXAxis) { // #2713
chart.axes.splice(chart.xAxis.length, 0, axis);
} else {
@@ -12496,9 +17454,8 @@
/**
* All series associated to the axis.
*
- * @name series
- * @memberOf Axis
- * @type {Array.}
+ * @name Highcharts.Axis#series
+ * @type {Array}
*/
axis.series = axis.series || []; // populated by Series
@@ -12513,7 +17470,7 @@
}
// register event listeners
- objectEach(events, function(event, eventType) {
+ objectEach(events, function (event, eventType) {
addEvent(axis, eventType, event);
});
@@ -12523,17 +17480,25 @@
axis.val2lin = axis.log2lin;
axis.lin2val = axis.lin2log;
}
+
+ fireEvent(this, 'afterInit');
},
/**
* Merge and set options.
*
* @private
+ * @function Highcharts.Axis#setOptions
+ *
+ * @param {Highcharts.AxisOptions} userOptions
+ *
+ * @fires Highcharts.Axis#event:afterSetOptions
*/
- setOptions: function(userOptions) {
+ setOptions: function (userOptions) {
this.options = merge(
this.defaultOptions,
- this.coll === 'yAxis' && this.defaultYAxisOptions, [
+ this.coll === 'yAxis' && this.defaultYAxisOptions,
+ [
this.defaultTopAxisOptions,
this.defaultRightAxisOptions,
this.defaultBottomAxisOptions,
@@ -12544,19 +17509,21 @@
userOptions
)
);
+
+ fireEvent(this, 'afterSetOptions', { userOptions: userOptions });
},
/**
* The default label formatter. The context is a special config object for
- * the label. In apps, use the {@link
- * https://api.highcharts.com/highcharts/xAxis.labels.formatter|
- * labels.formatter} instead except when a modification is needed.
- *
+ * the label. In apps, use the
+ * [labels.formatter](https://api.highcharts.com/highcharts/xAxis.labels.formatter)
+ * instead except when a modification is needed.
* @private
*/
- defaultLabelFormatter: function() {
+ defaultLabelFormatter: function () {
var axis = this.axis,
value = this.value,
+ time = axis.chart.time,
categories = axis.categories,
dateTimeLabelFormat = this.dateTimeLabelFormat,
lang = defaultOptions.lang,
@@ -12570,17 +17537,17 @@
// make sure the same symbol is added for all labels on a linear
// axis
numericSymbolDetector = axis.isLog ?
- Math.abs(value) :
- axis.tickInterval;
+ Math.abs(value) :
+ axis.tickInterval;
if (formatOption) {
- ret = format(formatOption, this);
+ ret = format(formatOption, this, time);
} else if (categories) {
ret = value;
} else if (dateTimeLabelFormat) { // datetime axis
- ret = H.dateFormat(dateTimeLabelFormat, value);
+ ret = time.dateFormat(dateTimeLabelFormat, value);
} else if (i && numericSymbolDetector >= 1000) {
// Decide whether we should add a numeric symbol like k (thousands)
@@ -12590,7 +17557,7 @@
while (i-- && ret === undefined) {
multi = Math.pow(numSymMagnitude, i + 1);
if (
- // Only accept a numeric symbol when the distance is more
+ // Only accept a numeric symbol when the distance is more
// than a full unit. So for example if the symbol is k, we
// don't accept numbers like 0.5k.
numericSymbolDetector >= multi &&
@@ -12619,115 +17586,128 @@
/**
* Get the minimum and maximum for the series of each axis. The function
* analyzes the axis series and updates `this.dataMin` and `this.dataMax`.
- *
* @private
+ * @fires Highcharts.Axis#event:afterGetSeriesExtremes
+ * @fires Highcharts.Axis#event:getSeriesExtremes
*/
- getSeriesExtremes: function() {
+ getSeriesExtremes: function () {
var axis = this,
chart = axis.chart;
- axis.hasVisibleSeries = false;
- // Reset properties in case we're redrawing (#3353)
- axis.dataMin = axis.dataMax = axis.threshold = null;
- axis.softThreshold = !axis.isXAxis;
+ fireEvent(this, 'getSeriesExtremes', null, function () {
- if (axis.buildStacks) {
- axis.buildStacks();
- }
+ axis.hasVisibleSeries = false;
+
+ // Reset properties in case we're redrawing (#3353)
+ axis.dataMin = axis.dataMax = axis.threshold = null;
+ axis.softThreshold = !axis.isXAxis;
- // loop through this axis' series
- each(axis.series, function(series) {
+ if (axis.buildStacks) {
+ axis.buildStacks();
+ }
- if (series.visible || !chart.options.chart.ignoreHiddenSeries) {
+ // loop through this axis' series
+ axis.series.forEach(function (series) {
- var seriesOptions = series.options,
- xData,
- threshold = seriesOptions.threshold,
- seriesDataMin,
- seriesDataMax;
+ if (series.visible || !chart.options.chart.ignoreHiddenSeries) {
- axis.hasVisibleSeries = true;
+ var seriesOptions = series.options,
+ xData,
+ threshold = seriesOptions.threshold,
+ seriesDataMin,
+ seriesDataMax;
- // Validate threshold in logarithmic axes
- if (axis.positiveValuesOnly && threshold <= 0) {
- threshold = null;
- }
+ axis.hasVisibleSeries = true;
- // Get dataMin and dataMax for X axes
- if (axis.isXAxis) {
- xData = series.xData;
- if (xData.length) {
- // If xData contains values which is not numbers, then
- // filter them out. To prevent performance hit, we only
- // do this after we have already found seriesDataMin
- // because in most cases all data is valid. #5234.
- seriesDataMin = arrayMin(xData);
- seriesDataMax = arrayMax(xData);
-
- if (!isNumber(seriesDataMin) &&
- !(seriesDataMin instanceof Date) // #5010
- ) {
- xData = grep(xData, isNumber);
- // Do it again with valid data
+ // Validate threshold in logarithmic axes
+ if (axis.positiveValuesOnly && threshold <= 0) {
+ threshold = null;
+ }
+
+ // Get dataMin and dataMax for X axes
+ if (axis.isXAxis) {
+ xData = series.xData;
+ if (xData.length) {
+ // If xData contains values which is not numbers,
+ // then filter them out. To prevent performance hit,
+ // we only do this after we have already found
+ // seriesDataMin because in most cases all data is
+ // valid. #5234.
seriesDataMin = arrayMin(xData);
+ seriesDataMax = arrayMax(xData);
+
+ if (
+ !isNumber(seriesDataMin) &&
+ !(seriesDataMin instanceof Date) // #5010
+ ) {
+ xData = xData.filter(isNumber);
+ // Do it again with valid data
+ seriesDataMin = arrayMin(xData);
+ seriesDataMax = arrayMax(xData);
+ }
+
+ if (xData.length) {
+ axis.dataMin = Math.min(
+ pick(axis.dataMin, xData[0], seriesDataMin),
+ seriesDataMin
+ );
+ axis.dataMax = Math.max(
+ pick(axis.dataMax, xData[0], seriesDataMax),
+ seriesDataMax
+ );
+ }
}
- axis.dataMin = Math.min(
- pick(axis.dataMin, xData[0], seriesDataMin),
- seriesDataMin
- );
- axis.dataMax = Math.max(
- pick(axis.dataMax, xData[0], seriesDataMax),
- seriesDataMax
- );
- }
-
// Get dataMin and dataMax for Y axes, as well as handle
// stacking and processed data
- } else {
+ } else {
- // Get this particular series extremes
- series.getExtremes();
- seriesDataMax = series.dataMax;
- seriesDataMin = series.dataMin;
-
- // Get the dataMin and dataMax so far. If percentage is
- // used, the min and max are always 0 and 100. If
- // seriesDataMin and seriesDataMax is null, then series
- // doesn't have active y data, we continue with nulls
- if (defined(seriesDataMin) && defined(seriesDataMax)) {
- axis.dataMin = Math.min(
- pick(axis.dataMin, seriesDataMin),
- seriesDataMin
- );
- axis.dataMax = Math.max(
- pick(axis.dataMax, seriesDataMax),
- seriesDataMax
- );
- }
+ // Get this particular series extremes
+ series.getExtremes();
+ seriesDataMax = series.dataMax;
+ seriesDataMin = series.dataMin;
+
+ // Get the dataMin and dataMax so far. If percentage is
+ // used, the min and max are always 0 and 100. If
+ // seriesDataMin and seriesDataMax is null, then series
+ // doesn't have active y data, we continue with nulls
+ if (defined(seriesDataMin) && defined(seriesDataMax)) {
+ axis.dataMin = Math.min(
+ pick(axis.dataMin, seriesDataMin),
+ seriesDataMin
+ );
+ axis.dataMax = Math.max(
+ pick(axis.dataMax, seriesDataMax),
+ seriesDataMax
+ );
+ }
- // Adjust to threshold
- if (defined(threshold)) {
- axis.threshold = threshold;
- }
- // If any series has a hard threshold, it takes precedence
- if (!seriesOptions.softThreshold ||
- axis.positiveValuesOnly
- ) {
- axis.softThreshold = false;
+ // Adjust to threshold
+ if (defined(threshold)) {
+ axis.threshold = threshold;
+ }
+ // If any series has a hard threshold, it takes
+ // precedence
+ if (
+ !seriesOptions.softThreshold ||
+ axis.positiveValuesOnly
+ ) {
+ axis.softThreshold = false;
+ }
}
}
- }
+ });
});
+
+ fireEvent(this, 'afterGetSeriesExtremes');
},
/**
* Translate from axis value to pixel position on the chart, or back. Use
* the `toPixels` and `toValue` functions in applications.
- *
* @private
*/
- translate: function(
+ translate: function (
val,
backwards,
cvsCoord,
@@ -12775,7 +17755,7 @@
returnValue = axis.lin2val(returnValue);
}
- // From value to pixels
+ // From value to pixels
} else {
if (doPostTranslate) { // log and ordinal axes
val = axis.val2lin(val);
@@ -12795,15 +17775,20 @@
/**
* Translate a value in terms of axis units into pixels within the chart.
- *
- * @param {Number} value
- * A value in terms of axis units.
- * @param {Boolean} paneCoordinates
- * Whether to return the pixel coordinate relative to the chart or
- * just the axis/pane itself.
- * @return {Number} Pixel position of the value on the chart or axis.
- */
- toPixels: function(value, paneCoordinates) {
+ *
+ * @function Highcharts.Axis#toPixels
+ *
+ * @param {number} value
+ * A value in terms of axis units.
+ *
+ * @param {boolean} paneCoordinates
+ * Whether to return the pixel coordinate relative to the chart or
+ * just the axis/pane itself.
+ *
+ * @return {number}
+ * Pixel position of the value on the chart or axis.
+ */
+ toPixels: function (value, paneCoordinates) {
return this.translate(value, false, !this.horiz, null, true) +
(paneCoordinates ? 0 : this.pos);
},
@@ -12811,17 +17796,24 @@
/**
* Translate a pixel position along the axis to a value in terms of axis
* units.
- * @param {Number} pixel
- * The pixel value coordinate.
- * @param {Boolean} paneCoordiantes
- * Whether the input pixel is relative to the chart or just the
- * axis/pane itself.
- * @return {Number} The axis value.
- */
- toValue: function(pixel, paneCoordinates) {
+ *
+ * @function Highcharts.Axis#toValue
+ *
+ * @param {number} pixel
+ * The pixel value coordinate.
+ *
+ * @param {boolean} paneCoordiantes
+ * Whether the input pixel is relative to the chart or just the
+ * axis/pane itself.
+ *
+ * @return {number}
+ * The axis value.
+ */
+ toValue: function (pixel, paneCoordinates) {
return this.translate(
pixel - (paneCoordinates ? 0 : this.pos),
- true, !this.horiz,
+ true,
+ !this.horiz,
null,
true
);
@@ -12831,24 +17823,32 @@
* Create the path for a plot line that goes from the given value on
* this axis, across the plot to the opposite side. Also used internally for
* grid lines and crosshairs.
- *
- * @param {Number} value
- * Axis value.
- * @param {Number} [lineWidth=1]
- * Used for calculation crisp line coordinates.
- * @param {Boolean} [old=false]
- * Use old coordinates (for resizing and rescaling).
- * @param {Boolean} [force=false]
- * If `false`, the function will return null when it falls outside
- * the axis bounds.
- * @param {Number} [translatedValue]
- * If given, return the plot line path of a pixel position on the
- * axis.
- *
- * @return {Array.}
+ *
+ * @function Highcharts.Axis#getPlotLinePath
+ *
+ * @param {number} value
+ * Axis value.
+ *
+ * @param {number} [lineWidth=1]
+ * Used for calculation crisp line coordinates.
+ *
+ * @param {boolean} [old=false]
+ * Use old coordinates (for resizing and rescaling).
+ *
+ * @param {boolean|string} [force=false]
+ * If `false`, the function will return null when it falls outside
+ * the axis bounds. If `true`, the function will return a path
+ * aligned to the plot area sides if it falls outside. If `pass`, it
+ * will return a path outside.
+ *
+ * @param {number} [translatedValue]
+ * If given, return the plot line path of a pixel position on the
+ * axis.
+ *
+ * @return {Array}
* The SVG path definition for the plot line.
*/
- getPlotLinePath: function(value, lineWidth, old, force, translatedValue) {
+ getPlotLinePath: function (value, lineWidth, old, force, translatedValue) {
var axis = this,
chart = axis.chart,
axisLeft = axis.left,
@@ -12861,12 +17861,13 @@
cWidth = (old && chart.oldChartWidth) || chart.chartWidth,
skip,
transB = axis.transB,
+ evt,
/**
* Check if x is between a and b. If not, either move to a/b
* or skip, depending on the force parameter.
*/
- between = function(x, a, b) {
- if (x < a || x > b) {
+ between = function (x, a, b) {
+ if (force !== 'pass' && x < a || x > b) {
if (force) {
x = Math.min(Math.max(a, x), b);
} else {
@@ -12876,58 +17877,79 @@
return x;
};
- translatedValue = pick(
- translatedValue,
- axis.translate(value, null, null, old)
- );
- x1 = x2 = Math.round(translatedValue + transB);
- y1 = y2 = Math.round(cHeight - translatedValue - transB);
- if (!isNumber(translatedValue)) { // no min or max
- skip = true;
- force = false; // #7175, don't force it when path is invalid
- } else if (axis.horiz) {
- y1 = axisTop;
- y2 = cHeight - axis.bottom;
- x1 = x2 = between(x1, axisLeft, axisLeft + axis.width);
- } else {
- x1 = axisLeft;
- x2 = cWidth - axis.right;
- y1 = y2 = between(y1, axisTop, axisTop + axis.height);
- }
- return skip && !force ?
- null :
- chart.renderer.crispLine(
- ['M', x1, y1, 'L', x2, y2],
- lineWidth || 1
+ evt = {
+ value: value,
+ lineWidth: lineWidth,
+ old: old,
+ force: force,
+ translatedValue: translatedValue
+ };
+ fireEvent(this, 'getPlotLinePath', evt, function (e) {
+
+ translatedValue = pick(
+ translatedValue,
+ axis.translate(value, null, null, old)
);
+ // Keep the translated value within sane bounds, and avoid Infinity
+ // to fail the isNumber test (#7709).
+ translatedValue = Math.min(Math.max(-1e5, translatedValue), 1e5);
+
+
+ x1 = x2 = Math.round(translatedValue + transB);
+ y1 = y2 = Math.round(cHeight - translatedValue - transB);
+ if (!isNumber(translatedValue)) { // no min or max
+ skip = true;
+ force = false; // #7175, don't force it when path is invalid
+ } else if (axis.horiz) {
+ y1 = axisTop;
+ y2 = cHeight - axis.bottom;
+ x1 = x2 = between(x1, axisLeft, axisLeft + axis.width);
+ } else {
+ x1 = axisLeft;
+ x2 = cWidth - axis.right;
+ y1 = y2 = between(y1, axisTop, axisTop + axis.height);
+ }
+ e.path = skip && !force ?
+ null :
+ chart.renderer.crispLine(
+ ['M', x1, y1, 'L', x2, y2],
+ lineWidth || 1
+ );
+ });
+
+ return evt.path;
},
/**
* Internal function to et the tick positions of a linear axis to round
* values like whole tens or every five.
*
- * @param {Number} tickInterval
- * The normalized tick interval
- * @param {Number} min
- * Axis minimum.
- * @param {Number} max
- * Axis maximum.
+ * @function Highcharts.Axis#getLinearTickPositions
+ *
+ * @param {number} tickInterval
+ * The normalized tick interval.
*
- * @return {Array.}
+ * @param {number} min
+ * Axis minimum.
+ *
+ * @param {number} max
+ * Axis maximum.
+ *
+ * @return {Array}
* An array of axis values where ticks should be placed.
*/
- getLinearTickPositions: function(tickInterval, min, max) {
+ getLinearTickPositions: function (tickInterval, min, max) {
var pos,
lastPos,
roundedMin =
- correctFloat(Math.floor(min / tickInterval) * tickInterval),
+ correctFloat(Math.floor(min / tickInterval) * tickInterval),
roundedMax =
- correctFloat(Math.ceil(max / tickInterval) * tickInterval),
+ correctFloat(Math.ceil(max / tickInterval) * tickInterval),
tickPositions = [],
precision;
// When the precision is higher than what we filter out in
- // correctFloat, skip it (#6183).
+ // correctFloat, skip it (#6183).
if (correctFloat(roundedMin + tickInterval) === roundedMin) {
precision = 20;
}
@@ -12967,8 +17989,12 @@
/**
* Resolve the new minorTicks/minorTickInterval options into the legacy
* loosely typed minorTickInterval option.
+ *
+ * @function Highcharts.Axis#getMinorTickInterval
+ *
+ * @return {number|"auto"|null}
*/
- getMinorTickInterval: function() {
+ getMinorTickInterval: function () {
var options = this.options;
if (options.minorTicks === true) {
@@ -12984,10 +18010,12 @@
* Internal function to return the minor tick positions. For logarithmic
* axes, the same logic as for major ticks is reused.
*
- * @return {Array.}
+ * @function Highcharts.Axis#getMinorTickPositions
+ *
+ * @return {Array}
* An array of axis values where ticks should be placed.
*/
- getMinorTickPositions: function() {
+ getMinorTickPositions: function () {
var axis = this,
options = axis.options,
tickPositions = axis.tickPositions,
@@ -13006,7 +18034,7 @@
if (axis.isLog) {
// For each interval in the major ticks, compute the minor ticks
// separately.
- each(this.paddedTicks, function(pos, i, paddedTicks) {
+ this.paddedTicks.forEach(function (pos, i, paddedTicks) {
if (i) {
minorTickPositions.push.apply(
minorTickPositions,
@@ -13034,7 +18062,9 @@
);
} else {
for (
- pos = min + (tickPositions[0] - min) % minorTickInterval; pos <= max; pos += minorTickInterval
+ pos = min + (tickPositions[0] - min) % minorTickInterval;
+ pos <= max;
+ pos += minorTickInterval
) {
// Very, very, tight grid lines (#5771)
if (pos === minorTickPositions[0]) {
@@ -13054,13 +18084,12 @@
/**
* Adjust the min and max for the minimum range. Keep in mind that the
* series data is not yet processed, so we don't have information on data
- * cropping and grouping, or updated axis.pointRange or series.pointRange.
- * The data can't be processed until we have finally established min and
- * max.
- *
+ * cropping and grouping, or updated `axis.pointRange` or
+ * `series.pointRange`. The data can't be processed until we have finally
+ * established min and max.
* @private
*/
- adjustForMinRange: function() {
+ adjustForMinRange: function () {
var axis = this,
options = axis.options,
min = axis.min,
@@ -13087,7 +18116,7 @@
// Find the closest distance between raw data points, as opposed
// to closestPointRange that applies to processed points
// (cropped and grouped)
- each(axis.series, function(series) {
+ axis.series.forEach(function (series) {
xData = series.xData;
loopLength = series.xIncrement ? 1 : xData.length - 1;
for (i = loopLength; i > 0; i--) {
@@ -13147,23 +18176,20 @@
axis.max = max;
},
- /**
- * Find the closestPointRange across all series.
- *
- * @private
- */
- getClosest: function() {
+ // Find the closestPointRange across all series.
+ getClosest: function () {
var ret;
if (this.categories) {
ret = 1;
} else {
- each(this.series, function(series) {
+ this.series.forEach(function (series) {
var seriesClosest = series.closestPointRange,
visible = series.visible ||
- !series.chart.options.chart.ignoreHiddenSeries;
+ !series.chart.options.chart.ignoreHiddenSeries;
- if (!series.noSharedTooltip &&
+ if (
+ !series.noSharedTooltip &&
defined(seriesClosest) &&
visible
) {
@@ -13180,16 +18206,11 @@
* When a point name is given and no x, search for the name in the existing
* categories, or if categories aren't provided, search names or create a
* new category (#2522).
- *
* @private
- *
- * @param {Point}
- * The point to inspect.
- *
- * @return {Number}
- * The X value that the point is given.
+ * @param {Highcharts.Point} point The point to inspect.
+ * @return {number} The X value that the point is given.
*/
- nameToX: function(point) {
+ nameToX: function (point) {
var explicitCategories = isArray(this.categories),
names = explicitCategories ? this.categories : this.names,
nameX = point.options.x,
@@ -13200,9 +18221,14 @@
if (!defined(nameX)) {
nameX = this.options.uniqueNames === false ?
point.series.autoIncrement() :
- inArray(point.name, names);
+ (
+ explicitCategories ?
+ names.indexOf(point.name) :
+ pick(names.keys[point.name], -1)
+
+ );
}
- if (nameX === -1) { // The name is not found in currenct categories
+ if (nameX === -1) { // Not found in currenct categories
if (!explicitCategories) {
x = names.length;
}
@@ -13213,36 +18239,53 @@
// Write the last point's name to the names array
if (x !== undefined) {
this.names[x] = point.name;
+ // Backwards mapping is much faster than array searching (#7725)
+ this.names.keys[point.name] = x;
}
return x;
},
- /**
- * When changes have been done to series data, update the axis.names.
- *
- * @private
- */
- updateNames: function() {
- var axis = this;
+ // When changes have been done to series data, update the axis.names.
+ updateNames: function () {
+ var axis = this,
+ names = this.names,
+ i = names.length;
+
+ if (i > 0) {
+ Object.keys(names.keys).forEach(function (key) {
+ delete names.keys[key];
+ });
+ names.length = 0;
- if (this.names.length > 0) {
- this.names.length = 0;
this.minRange = this.userMinRange; // Reset
- each(this.series || [], function(series) {
+ (this.series || []).forEach(function (series) {
// Reset incrementer (#5928)
series.xIncrement = null;
// When adding a series, points are not yet generated
if (!series.points || series.isDirtyData) {
+ // When we're updating the series with data that is longer
+ // than it was, and cropThreshold is passed, we need to make
+ // sure that the axis.max is increased _before_ running the
+ // premature processData. Otherwise this early iteration of
+ // processData will crop the points to axis.max, and the
+ // names array will be too short (#5857).
+ axis.max = Math.max(axis.max, series.xData.length - 1);
+
series.processData();
series.generatePoints();
}
- each(series.points, function(point, i) {
+ series.data.forEach(function (point, i) { // #9487
var x;
- if (point.options) {
+
+ if (
+ point &&
+ point.options &&
+ point.name !== undefined // #9562
+ ) {
x = axis.nameToX(point);
if (x !== undefined && x !== point.x) {
point.x = x;
@@ -13256,10 +18299,11 @@
/**
* Update translation information.
- *
* @private
+ * @param {boolean} saveOld
+ * @fires Highcharts.Axis#event:afterSetAxisTranslation
*/
- setAxisTranslation: function(saveOld) {
+ setAxisTranslation: function (saveOld) {
var axis = this,
range = axis.max - axis.min,
pointRange = axis.axisPointRange || 0,
@@ -13283,18 +18327,18 @@
minPointOffset = linkedParent.minPointOffset;
pointRangePadding = linkedParent.pointRangePadding;
} else {
- each(axis.series, function(series) {
+ axis.series.forEach(function (series) {
var seriesPointRange = hasCategories ?
- 1 :
- (
- isXAxis ?
- pick(
- series.options.pointRange,
- closestPointRange,
- 0
- ) :
- (axis.axisPointRange || 0)
- ), // #2806
+ 1 :
+ (
+ isXAxis ?
+ pick(
+ series.options.pointRange,
+ closestPointRange,
+ 0
+ ) :
+ (axis.axisPointRange || 0)
+ ), // #2806
pointPlacement = series.options.pointPlacement;
pointRange = Math.max(pointRange, seriesPointRange);
@@ -13307,7 +18351,8 @@
// padding does not apply.
minPointOffset = Math.max(
minPointOffset,
- isString(pointPlacement) ? 0 : seriesPointRange / 2
+ isXAxis && isString(pointPlacement) ?
+ 0 : seriesPointRange / 2
);
// Determine the total padding needed to the length of
@@ -13315,7 +18360,8 @@
// series' pointPlacement is 'on', no padding is added.
pointRangePadding = Math.max(
pointRangePadding,
- pointPlacement === 'on' ? 0 : seriesPointRange
+ isXAxis && pointPlacement === 'on' ?
+ 0 : seriesPointRange
);
}
});
@@ -13347,30 +18393,32 @@
axis.oldTransA = transA;
}
axis.translationSlope = axis.transA = transA =
- axis.options.staticScale ||
+ axis.staticScale ||
axis.len / ((range + pointRangePadding) || 1);
// Translation addend
axis.transB = axis.horiz ? axis.left : axis.bottom;
axis.minPixelPadding = transA * minPointOffset;
+
+ fireEvent(this, 'afterSetAxisTranslation');
},
- minFromRange: function() {
+ minFromRange: function () {
return this.max - this.range;
},
/**
* Set the tick positions to round values and optionally extend the extremes
* to the nearest tick.
- *
* @private
+ * @param {boolean} secondPass
+ * @fires Highcharts.Axis#event:foundExtremes
*/
- setTickInterval: function(secondPass) {
+ setTickInterval: function (secondPass) {
var axis = this,
chart = axis.chart,
options = axis.options,
isLog = axis.isLog,
- log2lin = axis.log2lin,
isDatetimeAxis = axis.isDatetimeAxis,
isXAxis = axis.isXAxis,
isLinked = axis.isLinked,
@@ -13382,7 +18430,7 @@
minTickInterval,
tickPixelIntervalOption = options.tickPixelInterval,
categories = axis.categories,
- threshold = axis.threshold,
+ threshold = isNumber(axis.threshold) ? axis.threshold : null,
softThreshold = axis.softThreshold,
thresholdMin,
thresholdMax,
@@ -13410,10 +18458,10 @@
linkedParentExtremes.dataMax
);
if (options.type !== axis.linkedParent.options.type) {
- H.error(11, 1); // Can't link axes of different type
+ H.error(11, 1, chart); // Can't link axes of different type
}
- // Initial min and max from the extreme data values
+ // Initial min and max from the extreme data values
} else {
// Adjust to hard threshold
@@ -13438,13 +18486,13 @@
!secondPass &&
Math.min(axis.min, pick(axis.dataMin, axis.min)) <= 0
) { // #978
- H.error(10, 1); // Can't plot negative values on log axis
+ H.error(10, 1, chart); // Can't plot negative values on log axis
}
// The correctFloat cures #934, float errors on full tens. But it
// was too aggressive for #4360 because of conversion back to lin,
// therefore use precision 15.
- axis.min = correctFloat(log2lin(axis.min), 15);
- axis.max = correctFloat(log2lin(axis.max), 15);
+ axis.min = correctFloat(axis.log2lin(axis.min), 15);
+ axis.max = correctFloat(axis.log2lin(axis.max), 15);
}
// handle zoomed range
@@ -13470,7 +18518,8 @@
// Pad the values to get clear of the chart's edges. To avoid
// tickInterval taking the padding into account, we do this after
// computing tick interval (#1337).
- if (!categories &&
+ if (
+ !categories &&
!axis.axisPointRange &&
!axis.usePercentage &&
!isLinked &&
@@ -13489,17 +18538,23 @@
}
// Handle options for floor, ceiling, softMin and softMax (#6359)
- if (isNumber(options.softMin)) {
+ if (isNumber(options.softMin) && !isNumber(axis.userMin)) {
axis.min = Math.min(axis.min, options.softMin);
}
- if (isNumber(options.softMax)) {
+ if (isNumber(options.softMax) && !isNumber(axis.userMax)) {
axis.max = Math.max(axis.max, options.softMax);
}
if (isNumber(options.floor)) {
- axis.min = Math.max(axis.min, options.floor);
+ axis.min = Math.min(
+ Math.max(axis.min, options.floor),
+ Number.MAX_VALUE
+ );
}
if (isNumber(options.ceiling)) {
- axis.max = Math.min(axis.max, options.ceiling);
+ axis.max = Math.max(
+ Math.min(axis.max, options.ceiling),
+ pick(axis.userMax, -Number.MAX_VALUE)
+ );
}
@@ -13510,13 +18565,15 @@
// This is prevented by the softThreshold option.
if (softThreshold && defined(axis.dataMin)) {
threshold = threshold || 0;
- if (!defined(hardMin) &&
+ if (
+ !defined(hardMin) &&
axis.min < threshold &&
axis.dataMin >= threshold
) {
axis.min = threshold;
- } else if (!defined(hardMax) &&
+ } else if (
+ !defined(hardMax) &&
axis.max > threshold &&
axis.dataMax <= threshold
) {
@@ -13537,7 +18594,7 @@
isLinked &&
!tickIntervalOption &&
tickPixelIntervalOption ===
- axis.linkedParent.options.tickPixelInterval
+ axis.linkedParent.options.tickPixelInterval
) {
axis.tickInterval = tickIntervalOption =
axis.linkedParent.tickInterval;
@@ -13546,25 +18603,23 @@
axis.tickInterval = pick(
tickIntervalOption,
this.tickAmount ?
- ((axis.max - axis.min) / Math.max(this.tickAmount - 1, 1)) :
- undefined,
+ ((axis.max - axis.min) / Math.max(this.tickAmount - 1, 1)) :
+ undefined,
// For categoried axis, 1 is default, for linear axis use
// tickPix
categories ?
- 1 :
- // don't let it be more than the data range
- (axis.max - axis.min) * tickPixelIntervalOption /
- Math.max(axis.len, tickPixelIntervalOption)
+ 1 :
+ // don't let it be more than the data range
+ (axis.max - axis.min) * tickPixelIntervalOption /
+ Math.max(axis.len, tickPixelIntervalOption)
);
}
- /**
- * Now we're finished detecting min and max, crop and group series data.
- * This is in turn needed in order to find tick positions in
- * ordinal axes.
- */
+ // Now we're finished detecting min and max, crop and group series data.
+ // This is in turn needed in order to find tick positions in ordinal
+ // axes.
if (isXAxis && !secondPass) {
- each(axis.series, function(series) {
+ axis.series.forEach(function (series) {
series.processData(
axis.min !== axis.oldMin || axis.max !== axis.oldMax
);
@@ -13610,13 +18665,15 @@
// in the order of thousands, chances are we are dealing with
// years. Don't allow decimals. #3363.
pick(
- options.allowDecimals, !(
+ options.allowDecimals,
+ !(
axis.tickInterval > 0.5 &&
axis.tickInterval < 5 &&
axis.max > 1000 &&
axis.max < 9999
)
- ), !!this.tickAmount
+ ),
+ !!this.tickAmount
);
}
@@ -13630,8 +18687,12 @@
/**
* Now we have computed the normalized tickInterval, get the tick positions
+ *
+ * @function Highcharts.Axis#setTickPositions
+ *
+ * @fires Highcharts.Axis#event:afterSetTickPositions
*/
- setTickPositions: function() {
+ setTickPositions: function () {
var options = this.options,
tickPositions,
@@ -13653,8 +18714,8 @@
this.minorTickInterval =
minorTickIntervalOption === 'auto' &&
this.tickInterval ?
- this.tickInterval / 5 :
- minorTickIntervalOption;
+ this.tickInterval / 5 :
+ minorTickIntervalOption;
// When there is only one point, or all points have the same value on
// this axis, then min and max are equal and tickPositions.length is 0
@@ -13677,7 +18738,19 @@
tickPositionsOption && tickPositionsOption.slice();
if (!tickPositions) {
- if (this.isDatetimeAxis) {
+ // Too many ticks (#6405). Create a friendly warning and provide two
+ // ticks so at least we can show the data series.
+ if (
+ !this.ordinalPositions &&
+ (
+ (this.max - this.min) / this.tickInterval >
+ Math.max(2 * this.len, 200)
+ )
+ ) {
+ tickPositions = [this.min, this.max];
+ H.error(19, false, this.chart);
+
+ } else if (this.isDatetimeAxis) {
tickPositions = this.getTimeTicks(
this.normalizeTimeTickInterval(
this.tickInterval,
@@ -13719,7 +18792,8 @@
// positions.
if (tickPositioner) {
tickPositioner = tickPositioner.apply(
- this, [this.min, this.max]
+ this,
+ [this.min, this.max]
);
if (tickPositioner) {
this.tickPositions = tickPositions = tickPositioner;
@@ -13743,19 +18817,19 @@
this.adjustTickAmount();
}
}
+
+ fireEvent(this, 'afterSetTickPositions');
},
- /**
- * Handle startOnTick and endOnTick by either adapting to padding min/max or
- * rounded min/max. Also handle single data points.
- *
- * @private
- */
- trimTicks: function(tickPositions, startOnTick, endOnTick) {
+ // Handle startOnTick and endOnTick by either adapting to padding min/max or
+ // rounded min/max. Also handle single data points.
+ trimTicks: function (tickPositions, startOnTick, endOnTick) {
var roundedMin = tickPositions[0],
roundedMax = tickPositions[tickPositions.length - 1],
minPointOffset = this.minPointOffset || 0;
+ fireEvent(this, 'trimTicks');
+
if (!this.isLinked) {
if (startOnTick && roundedMin !== -Infinity) { // #6502
this.min = roundedMin;
@@ -13769,13 +18843,17 @@
this.max = roundedMax;
} else {
while (this.max + minPointOffset <
- tickPositions[tickPositions.length - 1]) {
+ tickPositions[tickPositions.length - 1]) {
tickPositions.pop();
}
}
// If no tick are left, set one tick in the middle (#3195)
- if (tickPositions.length === 0 && defined(roundedMin)) {
+ if (
+ tickPositions.length === 0 &&
+ defined(roundedMin) &&
+ !this.options.tickPositions
+ ) {
tickPositions.push((roundedMax + roundedMin) / 2);
}
}
@@ -13783,12 +18861,10 @@
/**
* Check if there are multiple axes in the same pane.
- *
* @private
- * @return {Boolean}
- * True if there are other axes.
+ * @return {boolean} True if there are other axes.
*/
- alignToOthers: function() {
+ alignToOthers: function () {
var others = {}, // Whether there is another axis to pair with this one
hasOther,
options = this.options;
@@ -13798,11 +18874,15 @@
this.chart.options.chart.alignTicks !== false &&
options.alignTicks !== false &&
+ // Disabled when startOnTick or endOnTick are false (#7604)
+ options.startOnTick !== false &&
+ options.endOnTick !== false &&
+
// Don't try to align ticks on a log axis, they are not evenly
// spaced (#6021)
!this.isLog
) {
- each(this.chart[this.coll], function(axis) {
+ this.chart[this.coll].forEach(function (axis) {
var otherOptions = axis.options,
horiz = axis.horiz,
key = [
@@ -13828,15 +18908,15 @@
/**
* Find the max ticks of either the x and y axis collection, and record it
* in `this.tickAmount`.
- *
* @private
*/
- getTickAmount: function() {
+ getTickAmount: function () {
var options = this.options,
tickAmount = options.tickAmount,
tickPixelInterval = options.tickPixelInterval;
- if (!defined(options.tickInterval) &&
+ if (
+ !defined(options.tickInterval) &&
this.len < tickPixelInterval &&
!this.isRadial &&
!this.isLog &&
@@ -13866,58 +18946,95 @@
/**
* When using multiple axes, adjust the number of ticks to match the highest
* number of ticks in that group.
- *
* @private
*/
- adjustTickAmount: function() {
- var tickInterval = this.tickInterval,
- tickPositions = this.tickPositions,
- tickAmount = this.tickAmount,
- finalTickAmt = this.finalTickAmt,
+ adjustTickAmount: function () {
+ var axis = this,
+ axisOptions = axis.options,
+ tickInterval = axis.tickInterval,
+ tickPositions = axis.tickPositions,
+ tickAmount = axis.tickAmount,
+ finalTickAmt = axis.finalTickAmt,
currentTickAmount = tickPositions && tickPositions.length,
- i,
- len;
+ threshold = pick(axis.threshold, axis.softThreshold ? 0 : null),
+ min,
+ len,
+ i;
- if (currentTickAmount < tickAmount) {
- while (tickPositions.length < tickAmount) {
- tickPositions.push(correctFloat(
- tickPositions[tickPositions.length - 1] + tickInterval
- ));
- }
- this.transA *= (currentTickAmount - 1) / (tickAmount - 1);
- this.max = tickPositions[tickPositions.length - 1];
+ if (axis.hasData()) {
+ if (currentTickAmount < tickAmount) {
+ min = axis.min;
+
+ while (tickPositions.length < tickAmount) {
+
+ // Extend evenly for both sides unless we're on the
+ // threshold (#3965)
+ if (
+ tickPositions.length % 2 ||
+ min === threshold
+ ) {
+ // to the end
+ tickPositions.push(correctFloat(
+ tickPositions[tickPositions.length - 1] +
+ tickInterval
+ ));
+ } else {
+ // to the start
+ tickPositions.unshift(correctFloat(
+ tickPositions[0] - tickInterval
+ ));
+ }
+ }
+ axis.transA *= (currentTickAmount - 1) / (tickAmount - 1);
+
+ // Do not crop when ticks are not extremes (#9841)
+ axis.min = axisOptions.startOnTick ?
+ tickPositions[0] :
+ Math.min(axis.min, tickPositions[0]);
+ axis.max = axisOptions.endOnTick ?
+ tickPositions[tickPositions.length - 1] :
+ Math.max(axis.max, tickPositions[tickPositions.length - 1]);
// We have too many ticks, run second pass to try to reduce ticks
- } else if (currentTickAmount > tickAmount) {
- this.tickInterval *= 2;
- this.setTickPositions();
- }
+ } else if (currentTickAmount > tickAmount) {
+ axis.tickInterval *= 2;
+ axis.setTickPositions();
+ }
- // The finalTickAmt property is set in getTickAmount
- if (defined(finalTickAmt)) {
- i = len = tickPositions.length;
- while (i--) {
- if (
- // Remove every other tick
- (finalTickAmt === 3 && i % 2 === 1) ||
- // Remove all but first and last
- (finalTickAmt <= 2 && i > 0 && i < len - 1)
- ) {
- tickPositions.splice(i, 1);
+ // The finalTickAmt property is set in getTickAmount
+ if (defined(finalTickAmt)) {
+ i = len = tickPositions.length;
+ while (i--) {
+ if (
+ // Remove every other tick
+ (finalTickAmt === 3 && i % 2 === 1) ||
+ // Remove all but first and last
+ (finalTickAmt <= 2 && i > 0 && i < len - 1)
+ ) {
+ tickPositions.splice(i, 1);
+ }
}
+ axis.finalTickAmt = undefined;
}
- this.finalTickAmt = undefined;
}
},
/**
* Set the scale based on data min and max, user set min and max or options.
- *
* @private
+ * @fires Highcharts.Axis#event:afterSetScale
*/
- setScale: function() {
+ setScale: function () {
var axis = this,
- isDirtyData,
+ isDirtyData = axis.series.some(function (series) {
+ return (
+ series.isDirtyData ||
+ series.isDirty ||
+ // When x axis is dirty, we need new data extremes for y as
+ // well
+ series.xAxis.isDirty
+ );
+ }),
isDirtyAxisLength;
axis.oldMin = axis.min;
@@ -13928,18 +19045,6 @@
axis.setAxisSize();
isDirtyAxisLength = axis.len !== axis.oldAxisLength;
- // is there new data?
- each(axis.series, function(series) {
- if (
- series.isDirtyData ||
- series.isDirty ||
- // When x axis is dirty, we need new data extremes for y as well
- series.xAxis.isDirty
- ) {
- isDirtyData = true;
- }
- });
-
// do we really need to go through all this?
if (
isDirtyAxisLength ||
@@ -13979,6 +19084,8 @@
} else if (axis.cleanStacks) {
axis.cleanStacks();
}
+
+ fireEvent(this, 'afterSetScale');
},
/**
@@ -13988,18 +19095,6 @@
* options can be set to false before calling setExtremes. Also, setExtremes
* will not allow a range lower than the `minRange` option, which by default
* is the range of five points.
- *
- * @param {Number} [newMin]
- * The new minimum value.
- * @param {Number} [newMax]
- * The new maximum value.
- * @param {Boolean} [redraw=true]
- * Whether to redraw the chart or wait for an explicit call to
- * {@link Highcharts.Chart#redraw}
- * @param {AnimationOptions} [animation=true]
- * Enable or modify animations.
- * @param {Object} [eventArguments]
- * Arguments to be accessed in event handler.
*
* @sample highcharts/members/axis-setextremes/
* Set extremes from a button
@@ -14011,14 +19106,34 @@
* Set extremes in Highstock
* @sample maps/members/axis-setextremes/
* Set extremes in Highmaps
+ *
+ * @function Highcharts.Axis#setExtremes
+ *
+ * @param {number} [newMin]
+ * The new minimum value.
+ *
+ * @param {number} [newMax]
+ * The new maximum value.
+ *
+ * @param {boolean} [redraw=true]
+ * Whether to redraw the chart or wait for an explicit call to
+ * {@link Highcharts.Chart#redraw}
+ *
+ * @param {boolean|Highcharts.AnimationOptionsObject} [animation=true]
+ * Enable or modify animations.
+ *
+ * @param {*} [eventArguments]
+ * Arguments to be accessed in event handler.
+ *
+ * @fires Highcharts.Axis#event:setExtremes
*/
- setExtremes: function(newMin, newMax, redraw, animation, eventArguments) {
+ setExtremes: function (newMin, newMax, redraw, animation, eventArguments) {
var axis = this,
chart = axis.chart;
redraw = pick(redraw, true); // defaults to true
- each(axis.series, function(serie) {
+ axis.series.forEach(function (serie) {
delete serie.kdTree;
});
@@ -14029,7 +19144,7 @@
});
// Fire the event
- fireEvent(axis, 'setExtremes', eventArguments, function() {
+ fireEvent(axis, 'setExtremes', eventArguments, function () {
axis.userMin = newMin;
axis.userMax = newMax;
@@ -14041,67 +19156,68 @@
});
},
- /**
- * Overridable method for zooming chart. Pulled out in a separate method to
- * allow overriding in stock charts.
- *
- * @private
- */
- zoom: function(newMin, newMax) {
+ // Overridable method for zooming chart. Pulled out in a separate method to
+ // allow overriding in stock charts.
+ zoom: function (newMin, newMax) {
var dataMin = this.dataMin,
dataMax = this.dataMax,
options = this.options,
min = Math.min(dataMin, pick(options.min, dataMin)),
- max = Math.max(dataMax, pick(options.max, dataMax));
-
- if (newMin !== this.min || newMax !== this.max) { // #5790
-
- // Prevent pinch zooming out of range. Check for defined is for
- // #1946. #1734.
- if (!this.allowZoomOutside) {
- // #6014, sometimes newMax will be smaller than min (or newMin
- // will be larger than max).
- if (defined(dataMin)) {
- if (newMin < min) {
- newMin = min;
- }
- if (newMin > max) {
- newMin = max;
- }
- }
- if (defined(dataMax)) {
- if (newMax < min) {
- newMax = min;
+ max = Math.max(dataMax, pick(options.max, dataMax)),
+ evt = { newMin: newMin, newMax: newMax };
+
+ fireEvent(this, 'zoom', evt, function (e) {
+
+ // Use e.newMin and e.newMax - event handlers may have altered them
+ var newMin = e.newMin,
+ newMax = e.newMax;
+
+ if (newMin !== this.min || newMax !== this.max) { // #5790
+
+ // Prevent pinch zooming out of range. Check for defined is for
+ // #1946. #1734.
+ if (!this.allowZoomOutside) {
+ // #6014, sometimes newMax will be smaller than min (or
+ // newMin will be larger than max).
+ if (defined(dataMin)) {
+ if (newMin < min) {
+ newMin = min;
+ }
+ if (newMin > max) {
+ newMin = max;
+ }
}
- if (newMax > max) {
- newMax = max;
+ if (defined(dataMax)) {
+ if (newMax < min) {
+ newMax = min;
+ }
+ if (newMax > max) {
+ newMax = max;
+ }
}
}
- }
- // In full view, displaying the reset zoom button is not required
- this.displayBtn = newMin !== undefined || newMax !== undefined;
+ // In full view, displaying the reset zoom button is not
+ // required
+ this.displayBtn = newMin !== undefined || newMax !== undefined;
- // Do it
- this.setExtremes(
- newMin,
- newMax,
- false,
- undefined, {
- trigger: 'zoom'
- }
- );
- }
+ // Do it
+ this.setExtremes(
+ newMin,
+ newMax,
+ false,
+ undefined,
+ { trigger: 'zoom' }
+ );
+ }
+ e.zoomed = true;
+ });
- return true;
+ return evt.zoomed;
},
- /**
- * Update the axis metrics.
- *
- * @private
- */
- setAxisSize: function() {
+ // Update the axis metrics.
+ setAxisSize: function () {
var chart = this.chart,
options = this.options,
// [top, right, bottom, left]
@@ -14144,44 +19260,26 @@
this.pos = horiz ? left : top; // distance from SVG origin
},
- /**
- * The returned object literal from the {@link Highcharts.Axis#getExtremes}
- * function.
- *
- * @typedef {Object} Extremes
- * @property {Number} dataMax
- * The maximum value of the axis' associated series.
- * @property {Number} dataMin
- * The minimum value of the axis' associated series.
- * @property {Number} max
- * The maximum axis value, either automatic or set manually. If
- * the `max` option is not set, `maxPadding` is 0 and `endOnTick`
- * is false, this value will be the same as `dataMax`.
- * @property {Number} min
- * The minimum axis value, either automatic or set manually. If
- * the `min` option is not set, `minPadding` is 0 and
- * `startOnTick` is false, this value will be the same
- * as `dataMin`.
- */
/**
* Get the current extremes for the axis.
*
- * @returns {Extremes}
+ * @sample highcharts/members/axis-getextremes/
+ * Report extremes by click on a button
+ * @sample maps/members/axis-getextremes/
+ * Get extremes in Highmaps
+ *
+ * @function Highcharts.Axis#getExtremes
+ *
+ * @returns {Highcharts.ExtremesObject}
* An object containing extremes information.
- *
- * @sample highcharts/members/axis-getextremes/
- * Report extremes by click on a button
- * @sample maps/members/axis-getextremes/
- * Get extremes in Highmaps
*/
- getExtremes: function() {
+ getExtremes: function () {
var axis = this,
- isLog = axis.isLog,
- lin2log = axis.lin2log;
+ isLog = axis.isLog;
return {
- min: isLog ? correctFloat(lin2log(axis.min)) : axis.min,
- max: isLog ? correctFloat(lin2log(axis.max)) : axis.max,
+ min: isLog ? correctFloat(axis.lin2log(axis.min)) : axis.min,
+ max: isLog ? correctFloat(axis.lin2log(axis.max)) : axis.max,
dataMin: axis.dataMin,
dataMax: axis.dataMax,
userMin: axis.userMin,
@@ -14193,22 +19291,25 @@
* Get the zero plane either based on zero or on the min or max value.
* Used in bar and area plots.
*
- * @param {Number} threshold
- * The threshold in axis values.
+ * @function Highcharts.Axis#getThreshold
+ *
+ * @param {number} threshold
+ * The threshold in axis values.
*
- * @return {Number}
+ * @return {number}
* The translated threshold position in terms of pixels, and
* corrected to stay within the axis bounds.
*/
- getThreshold: function(threshold) {
+ getThreshold: function (threshold) {
var axis = this,
isLog = axis.isLog,
- lin2log = axis.lin2log,
- realMin = isLog ? lin2log(axis.min) : axis.min,
- realMax = isLog ? lin2log(axis.max) : axis.max;
+ realMin = isLog ? axis.lin2log(axis.min) : axis.min,
+ realMax = isLog ? axis.lin2log(axis.max) : axis.max;
- if (threshold === null) {
+ if (threshold === null || threshold === -Infinity) {
threshold = realMin;
+ } else if (threshold === Infinity) {
+ threshold = realMax;
} else if (realMin > threshold) {
threshold = realMin;
} else if (realMax < threshold) {
@@ -14221,75 +19322,72 @@
/**
* Compute auto alignment for the axis label based on which side the axis is
* on and the given rotation for the label.
- *
- * @param {Number} rotation
- * The rotation in degrees as set by either the `rotation` or
- * `autoRotation` options.
* @private
+ * @param {number} rotation The rotation in degrees as set by either the
+ * `rotation` or `autoRotation` options.
+ * @return {string} Can be `center`, `left` or `right`.
*/
- autoLabelAlign: function(rotation) {
- var ret,
- angle = (pick(rotation, 0) - (this.side * 90) + 720) % 360;
+ autoLabelAlign: function (rotation) {
+ var angle = (pick(rotation, 0) - (this.side * 90) + 720) % 360,
+ evt = { align: 'center' };
- if (angle > 15 && angle < 165) {
- ret = 'right';
- } else if (angle > 195 && angle < 345) {
- ret = 'left';
- } else {
- ret = 'center';
- }
- return ret;
+ fireEvent(this, 'autoLabelAlign', evt, function (e) {
+
+ if (angle > 15 && angle < 165) {
+ e.align = 'right';
+ } else if (angle > 195 && angle < 345) {
+ e.align = 'left';
+ }
+ });
+
+ return evt.align;
},
/**
* Get the tick length and width for the axis based on axis options.
- *
* @private
- *
- * @param {String} prefix
- * 'tick' or 'minorTick'
- * @return {Array.}
- * An array of tickLength and tickWidth
+ * @param {string} prefix 'tick' or 'minorTick'
+ * @return {Array} An array of tickLength and tickWidth
*/
- tickSize: function(prefix) {
+ tickSize: function (prefix) {
var options = this.options,
tickLength = options[prefix + 'Length'],
tickWidth = pick(
options[prefix + 'Width'],
prefix === 'tick' && this.isXAxis ? 1 : 0 // X axis default 1
- );
+ ),
+ e,
+ tickSize;
if (tickWidth && tickLength) {
// Negate the length
if (options[prefix + 'Position'] === 'inside') {
tickLength = -tickLength;
}
- return [tickLength, tickWidth];
+ tickSize = [tickLength, tickWidth];
}
+ e = { tickSize: tickSize };
+ fireEvent(this, 'afterTickSize', e);
+
+ return e.tickSize;
+
},
- /**
- * Return the size of the labels.
- *
- * @private
- */
- labelMetrics: function() {
+ // Return the size of the labels.
+ labelMetrics: function () {
var index = this.tickPositions && this.tickPositions[0] || 0;
+
return this.chart.renderer.fontMetrics(
this.options.labels.style && this.options.labels.style.fontSize,
this.ticks[index] && this.ticks[index].label
);
},
- /**
- * Prevent the ticks from getting so close we can't draw the labels. On a
- * horizontal axis, this is handled by rotating the labels, removing ticks
- * and adding ellipsis. On a vertical axis remove ticks and add ellipsis.
- *
- * @private
- */
- unsquish: function() {
+ // Prevent the ticks from getting so close we can't draw the labels. On a
+ // horizontal axis, this is handled by rotating the labels, removing ticks
+ // and adding ellipsis. On a vertical axis remove ticks and add ellipsis.
+ unsquish: function () {
var labelOptions = this.options.labels,
horiz = this.horiz,
tickInterval = this.tickInterval,
@@ -14303,30 +19401,43 @@
step,
bestScore = Number.MAX_VALUE,
autoRotation,
+ range = this.max - this.min,
// Return the multiple of tickInterval that is needed to avoid
// collision
- getStep = function(spaceNeeded) {
+ getStep = function (spaceNeeded) {
var step = spaceNeeded / (slotSize || 1);
+
step = step > 1 ? Math.ceil(step) : 1;
- return step * tickInterval;
+
+ // Guard for very small or negative angles (#9835)
+ if (
+ step * tickInterval > range &&
+ spaceNeeded !== Infinity &&
+ slotSize !== Infinity
+ ) {
+ step = Math.ceil(range / tickInterval);
+ }
+
+ return correctFloat(step * tickInterval);
};
if (horiz) {
autoRotation = !labelOptions.staggerLines &&
!labelOptions.step &&
( // #3971
- defined(rotationOption) ? [rotationOption] :
- slotSize < pick(labelOptions.autoRotationLimit, 80) &&
- labelOptions.autoRotation
+ defined(rotationOption) ?
+ [rotationOption] :
+ slotSize < pick(labelOptions.autoRotationLimit, 80) &&
+ labelOptions.autoRotation
);
if (autoRotation) {
- // Loop over the given autoRotation options, and determine
+ // Loop over the given autoRotation options, and determine
// which gives the best score. The best score is that with
// the lowest number of steps and a rotation closest
// to horizontal.
- each(autoRotation, function(rot) {
+ autoRotation.forEach(function (rot) {
var score;
if (
@@ -14363,12 +19474,13 @@
* Get the general slot width for labels/categories on this axis. This may
* change between the pre-render (from Axis.getOffset) and the final tick
* rendering and placement.
- *
* @private
- * @return {Number}
- * The pixel width allocated to each axis label.
+ * @param {Highcharts.Tick} [tick] Optionally, calculate the slot width
+ * basing on tick label. It is used in highcharts-3d module, where the slots
+ * has different widths depending on perspective angles.
+ * @return {number} The pixel width allocated to each axis label.
*/
- getSlotWidth: function() {
+ getSlotWidth: function (tick) {
// #5086, #1580, #1931
var chart = this.chart,
horiz = this.horiz,
@@ -14380,37 +19492,39 @@
marginLeft = chart.margin[3];
return (
+ tick &&
+ tick.slotWidth // Used by grid axis
+ ) || (
horiz &&
(labelOptions.step || 0) < 2 &&
!labelOptions.rotation && // #4415
((this.staggerLines || 1) * this.len) / slotCount
- ) || (!horiz && (
- // #7028
- (
- labelOptions.style &&
- parseInt(labelOptions.style.width, 10)
- ) ||
- (
- marginLeft &&
- (marginLeft - chart.spacing[3])
- ) ||
- chart.chartWidth * 0.33
- ));
+ ) || (
+ !horiz && (
+ // #7028
+ (
+ labelOptions.style &&
+ parseInt(labelOptions.style.width, 10)
+ ) ||
+ (
+ marginLeft &&
+ (marginLeft - chart.spacing[3])
+ ) ||
+ chart.chartWidth * 0.33
+ )
+ );
},
- /**
- * Render the axis labels and determine whether ellipsis or rotation need
- * to be applied.
- *
- * @private
- */
- renderUnsquish: function() {
+ // Render the axis labels and determine whether ellipsis or rotation need to
+ // be applied.
+ renderUnsquish: function () {
var chart = this.chart,
renderer = chart.renderer,
tickPositions = this.tickPositions,
ticks = this.ticks,
labelOptions = this.options.labels,
+ labelStyleOptions = (labelOptions && labelOptions.style || {}),
horiz = this.horiz,
slotWidth = this.getSlotWidth(),
innerWidth = Math.max(
@@ -14420,8 +19534,9 @@
attr = {},
labelMetrics = this.labelMetrics(),
textOverflowOption = labelOptions.style &&
- labelOptions.style.textOverflow,
- css,
+ labelOptions.style.textOverflow,
+ commonWidth,
+ commonTextOverflow,
maxLabelLength = 0,
label,
i,
@@ -14433,10 +19548,14 @@
}
// Get the longest label length
- each(tickPositions, function(tick) {
+ tickPositions.forEach(function (tick) {
tick = ticks[tick];
- if (tick && tick.labelLength > maxLabelLength) {
- maxLabelLength = tick.labelLength;
+ if (
+ tick &&
+ tick.label &&
+ tick.label.textPxLength > maxLabelLength
+ ) {
+ maxLabelLength = tick.label.textPxLength;
}
});
this.maxLabelLength = maxLabelLength;
@@ -14456,15 +19575,13 @@
this.labelRotation = 0;
}
- // Handle word-wrap or ellipsis on vertical axis
+ // Handle word-wrap or ellipsis on vertical axis
} else if (slotWidth) {
// For word-wrap or ellipsis
- css = {
- width: innerWidth + 'px'
- };
+ commonWidth = innerWidth;
if (!textOverflowOption) {
- css.textOverflow = 'clip';
+ commonTextOverflow = 'clip';
// On vertical axis, only allow word wrap if there is room
// for more lines.
@@ -14479,16 +19596,12 @@
label.styles &&
label.styles.textOverflow === 'ellipsis'
) {
- label.css({
- textOverflow: 'clip'
- });
+ label.css({ textOverflow: 'clip' });
- // Set the correct width in order to read
- // the bounding box height (#4678, #5034)
- } else if (ticks[pos].labelLength > slotWidth) {
- label.css({
- width: slotWidth + 'px'
- });
+ // Set the correct width in order to read
+ // the bounding box height (#4678, #5034)
+ } else if (label.textPxLength > slotWidth) {
+ label.css({ width: slotWidth + 'px' });
}
if (
@@ -14497,9 +19610,7 @@
(labelMetrics.h - labelMetrics.f)
)
) {
- label.specCss = {
- textOverflow: 'ellipsis'
- };
+ label.specificTextOverflow = 'ellipsis';
}
}
}
@@ -14509,15 +19620,13 @@
// Add ellipsis if the label length is significantly longer than ideal
if (attr.rotation) {
- css = {
- width: (
- maxLabelLength > chart.chartHeight * 0.5 ?
+ commonWidth = (
+ maxLabelLength > chart.chartHeight * 0.5 ?
chart.chartHeight * 0.33 :
- chart.chartHeight
- ) + 'px'
- };
+ maxLabelLength
+ );
if (!textOverflowOption) {
- css.textOverflow = 'ellipsis';
+ commonTextOverflow = 'ellipsis';
}
}
@@ -14529,20 +19638,54 @@
}
// Apply general and specific CSS
- each(tickPositions, function(pos) {
+ tickPositions.forEach(function (pos) {
var tick = ticks[pos],
- label = tick && tick.label;
+ label = tick && tick.label,
+ widthOption = labelStyleOptions.width,
+ css = {};
+
if (label) {
// This needs to go before the CSS in old IE (#4502)
label.attr(attr);
- if (css) {
- label.css(merge(css, label.specCss));
+ if (tick.shortenLabel) {
+ tick.shortenLabel();
+ } else if (
+ commonWidth &&
+ !widthOption &&
+ // Setting width in this case messes with the bounding box
+ // (#7975)
+ labelStyleOptions.whiteSpace !== 'nowrap' &&
+ (
+ // Speed optimizing, #7656
+ commonWidth < label.textPxLength ||
+ // Resetting CSS, #4928
+ label.element.tagName === 'SPAN'
+ )
+ ) {
+ css.width = commonWidth;
+ if (!textOverflowOption) {
+ css.textOverflow = (
+ label.specificTextOverflow ||
+ commonTextOverflow
+ );
+ }
+ label.css(css);
+
+ // Reset previously shortened label (#8210)
+ } else if (
+ label.styles &&
+ label.styles.width &&
+ !css.width &&
+ !widthOption
+ ) {
+ label.css({ width: null });
}
- delete label.specCss;
+
+ delete label.specificTextOverflow;
tick.rotation = attr.rotation;
}
- });
+ }, this);
// Note: Why is this not part of getLabelPosition?
this.tickRotCorr = renderer.rotCorr(
@@ -14555,12 +19698,14 @@
/**
* Return true if the axis has associated data.
*
- * @return {Boolean}
+ * @function Highcharts.Axis#hasData
+ *
+ * @return {boolean}
* True if the axis has associated visible series and those series
* have either valid data points or explicit `min` and `max`
* settings.
*/
- hasData: function() {
+ hasData: function () {
return (
this.hasVisibleSeries ||
(
@@ -14574,16 +19719,21 @@
/**
* Adds the title defined in axis.options.title.
- * @param {Boolean} display - whether or not to display the title
+ *
+ * @function Highcharts.Axis#addTitle
+ *
+ * @param {boolean} display
+ * Whether or not to display the title.
*/
- addTitle: function(display) {
+ addTitle: function (display) {
var axis = this,
renderer = axis.chart.renderer,
horiz = axis.horiz,
opposite = axis.opposite,
options = axis.options,
axisTitleOptions = options.title,
- textAlign;
+ textAlign,
+ styledMode = axis.chart.styledMode;
if (!axis.axisTitle) {
textAlign = axisTitleOptions.textAlign;
@@ -14599,50 +19749,45 @@
})[axisTitleOptions.align];
}
axis.axisTitle = renderer.text(
- axisTitleOptions.text,
- 0,
- 0,
- axisTitleOptions.useHTML
- )
+ axisTitleOptions.text,
+ 0,
+ 0,
+ axisTitleOptions.useHTML
+ )
.attr({
zIndex: 7,
rotation: axisTitleOptions.rotation || 0,
align: textAlign
})
- .addClass('highcharts-axis-title')
+ .addClass('highcharts-axis-title');
- .css(axisTitleOptions.style)
+ // #7814, don't mutate style option
+ if (!styledMode) {
+ axis.axisTitle.css(merge(axisTitleOptions.style));
+ }
- .add(axis.axisGroup);
+ axis.axisTitle.add(axis.axisGroup);
axis.axisTitle.isNew = true;
}
// Max width defaults to the length of the axis
-
- if (!axisTitleOptions.style.width && !axis.isRadial) {
-
+ if (!styledMode && !axisTitleOptions.style.width && !axis.isRadial) {
axis.axisTitle.css({
width: axis.len
});
-
}
-
-
// hide or show the title depending on whether showEmpty is set
axis.axisTitle[display ? 'show' : 'hide'](true);
},
/**
* Generates a tick for initial positioning.
- *
* @private
- * @param {number} pos
- * The tick position in axis values.
- * @param {number} i
- * The index of the tick in {@link Axis.tickPositions}.
+ * @param {number} pos The tick position in axis values.
+ * @param {number} i The index of the tick in {@link Axis.tickPositions}.
*/
- generateTick: function(pos) {
+ generateTick: function (pos) {
var ticks = this.ticks;
if (!ticks[pos]) {
@@ -14653,11 +19798,11 @@
},
/**
- * Render the tick labels to a preliminary position to get their sizes.
- *
+ * Render the tick labels to a preliminary position to get their sizes
* @private
+ * @fires Highcharts.Axis#event:afterGetOffset
*/
- getOffset: function() {
+ getOffset: function () {
var axis = this,
chart = axis.chart,
renderer = chart.renderer,
@@ -14667,7 +19812,7 @@
horiz = axis.horiz,
side = axis.side,
invertedSide = chart.inverted &&
- !axis.isZAxis ? [1, 0, 3, 2][side] : side,
+ !axis.isZAxis ? [1, 0, 3, 2][side] : side,
hasData,
showAxis,
titleOffset = 0,
@@ -14684,7 +19829,7 @@
className = options.className,
axisParent = axis.axisParent, // Used in color axis
lineHeightCorrection,
- tickSize = this.tickSize('tick');
+ tickSize;
// For reuse in Axis.render
hasData = axis.hasData();
@@ -14696,27 +19841,21 @@
// Create the axisGroup and gridGroup elements on first iteration
if (!axis.axisGroup) {
axis.gridGroup = renderer.g('grid')
- .attr({
- zIndex: options.gridZIndex || 1
- })
+ .attr({ zIndex: options.gridZIndex || 1 })
.addClass(
'highcharts-' + this.coll.toLowerCase() + '-grid ' +
(className || '')
)
.add(axisParent);
axis.axisGroup = renderer.g('axis')
- .attr({
- zIndex: options.zIndex || 2
- })
+ .attr({ zIndex: options.zIndex || 2 })
.addClass(
'highcharts-' + this.coll.toLowerCase() + ' ' +
(className || '')
)
.add(axisParent);
axis.labelGroup = renderer.g('axis-labels')
- .attr({
- zIndex: labelOptions.zIndex || 7
- })
+ .attr({ zIndex: labelOptions.zIndex || 7 })
.addClass(
'highcharts-' + axis.coll.toLowerCase() + '-labels ' +
(className || '')
@@ -14727,7 +19866,7 @@
if (hasData || axis.isLinked) {
// Generate ticks
- each(tickPositions, function(pos, i) {
+ tickPositions.forEach(function (pos, i) {
// i is not used here, but may be used in overrides
axis.generateTick(pos, i);
});
@@ -14737,18 +19876,17 @@
// Left side must be align: right and right side must
// have align: left for labels
- if (
- labelOptions.reserveSpace !== false &&
- (
- side === 0 ||
- side === 2 || {
- 1: 'left',
- 3: 'right'
- }[side] === axis.labelAlign ||
- axis.labelAlign === 'center'
- )
- ) {
- each(tickPositions, function(pos) {
+ axis.reserveSpaceDefault = (
+ side === 0 ||
+ side === 2 ||
+ { 1: 'left', 3: 'right' }[side] === axis.labelAlign
+ );
+ if (pick(
+ labelOptions.reserveSpace,
+ axis.labelAlign === 'center' ? true : null,
+ axis.reserveSpaceDefault
+ )) {
+ tickPositions.forEach(function (pos) {
// get the highest offset
labelOffset = Math.max(
ticks[pos].getLabelSize(),
@@ -14759,11 +19897,11 @@
if (axis.staggerLines) {
labelOffset *= axis.staggerLines;
- axis.labelOffset = labelOffset * (axis.opposite ? -1 : 1);
}
+ axis.labelOffset = labelOffset * (axis.opposite ? -1 : 1);
} else { // doesn't have data
- objectEach(ticks, function(tick, n) {
+ objectEach(ticks, function (tick, n) {
tick.destroy();
delete ticks[n];
});
@@ -14790,12 +19928,12 @@
axis.renderLine();
// handle automatic or user set offset
- axis.offset = directionFactor * pick(options.offset, axisOffset[side]);
+ axis.offset = directionFactor * pick(
+ options.offset,
+ axisOffset[side] ? axisOffset[side] + (options.margin || 0) : 0
+ );
- axis.tickRotCorr = axis.tickRotCorr || {
- x: 0,
- y: 0
- }; // polar
+ axis.tickRotCorr = axis.tickRotCorr || { x: 0, y: 0 }; // polar
if (side === 0) {
lineHeightCorrection = -axis.labelMetrics().h;
} else if (side === 2) {
@@ -14810,23 +19948,34 @@
labelOffsetPadded -= lineHeightCorrection;
labelOffsetPadded += directionFactor * (
horiz ?
- pick(
- labelOptions.y,
- axis.tickRotCorr.y + directionFactor * 8
- ) :
- labelOptions.x
+ pick(
+ labelOptions.y,
+ axis.tickRotCorr.y + directionFactor * 8
+ ) :
+ labelOptions.x
);
}
axis.axisTitleMargin = pick(titleOffsetOption, labelOffsetPadded);
+ if (axis.getMaxLabelDimensions) {
+ axis.maxLabelDimensions = axis.getMaxLabelDimensions(
+ ticks,
+ tickPositions
+ );
+ }
+
+ // Due to GridAxis.tickSize, tickSize should be calculated after ticks
+ // has rendered.
+ tickSize = this.tickSize('tick');
+
axisOffset[side] = Math.max(
axisOffset[side],
axis.axisTitleMargin + titleOffset + directionFactor * axis.offset,
labelOffsetPadded, // #3027
hasData && tickPositions.length && tickSize ?
- tickSize[0] + directionFactor * axis.offset :
- 0 // #4866
+ tickSize[0] + directionFactor * axis.offset :
+ 0 // #4866
);
// Decide the clipping needed to keep the graph inside
@@ -14835,25 +19984,30 @@
0 :
Math.floor(axis.axisLine.strokeWidth() / 2) * 2; // #4308, #4371
clipOffset[invertedSide] = Math.max(clipOffset[invertedSide], clip);
+
+ fireEvent(this, 'afterGetOffset');
},
/**
* Internal function to get the path for the axis line. Extended for polar
* charts.
*
- * @param {Number} lineWidth
- * The line width in pixels.
- * @return {Array}
+ * @function Highcharts.Axis#getLinePath
+ *
+ * @param {number} lineWidth
+ * The line width in pixels.
+ *
+ * @return {Highcharts.SVGPathArray}
* The SVG path definition in array form.
*/
- getLinePath: function(lineWidth) {
+ getLinePath: function (lineWidth) {
var chart = this.chart,
opposite = this.opposite,
offset = this.offset,
horiz = this.horiz,
lineLeft = this.left + (opposite ? this.width : 0) + offset,
lineTop = chart.chartHeight - this.bottom -
- (opposite ? this.height : 0) + offset;
+ (opposite ? this.height : 0) + offset;
if (opposite) {
lineWidth *= -1; // crispify the other way - #1480, #1687
@@ -14863,50 +20017,49 @@
.crispLine([
'M',
horiz ?
- this.left :
- lineLeft,
+ this.left :
+ lineLeft,
horiz ?
- lineTop :
- this.top,
+ lineTop :
+ this.top,
'L',
horiz ?
- chart.chartWidth - this.right :
- lineLeft,
+ chart.chartWidth - this.right :
+ lineLeft,
horiz ?
- lineTop :
- chart.chartHeight - this.bottom
+ lineTop :
+ chart.chartHeight - this.bottom
], lineWidth);
},
/**
* Render the axis line. Called internally when rendering and redrawing the
* axis.
+ *
+ * @function Highcharts.Axis#renderLine
*/
- renderLine: function() {
+ renderLine: function () {
if (!this.axisLine) {
this.axisLine = this.chart.renderer.path()
.addClass('highcharts-axis-line')
.add(this.axisGroup);
-
- this.axisLine.attr({
- stroke: this.options.lineColor,
- 'stroke-width': this.options.lineWidth,
- zIndex: 7
- });
-
+ if (!this.chart.styledMode) {
+ this.axisLine.attr({
+ stroke: this.options.lineColor,
+ 'stroke-width': this.options.lineWidth,
+ zIndex: 7
+ });
+ }
}
},
/**
* Position the axis title.
- *
* @private
- *
- * @return {Object}
- * X and Y positions for the title.
+ * @return {Highcharts.PositionObject} X and Y positions for the title.
*/
- getTitlePosition: function() {
+ getTitlePosition: function () {
// compute anchor points for each of the title align options
var horiz = this.horiz,
axisLeft = this.left,
@@ -14924,7 +20077,7 @@
axisTitle
),
// The part of a multiline text that is below the baseline of the
- // first line. Subtract 1 to preserve pixel-perfectness from the
+ // first line. Subtract 1 to preserve pixel-perfectness from the
// old behaviour (v5.0.12), where only one line was allowed.
textHeightOvershoot = Math.max(
axisTitle.getBBox(null, 0).height - fontMetrics.h - 1,
@@ -14940,31 +20093,43 @@
// the position in the perpendicular direction of the axis
offAxis = (horiz ? axisTop + this.height : axisLeft) +
- (horiz ? 1 : -1) * // horizontal axis reverses the margin
- (opposite ? -1 : 1) * // so does opposite axes
- this.axisTitleMargin + [-textHeightOvershoot, // top
- textHeightOvershoot, // right
- fontMetrics.f, // bottom
- -textHeightOvershoot // left
- ][this.side];
+ (horiz ? 1 : -1) * // horizontal axis reverses the margin
+ (opposite ? -1 : 1) * // so does opposite axes
+ this.axisTitleMargin +
+ [
+ -textHeightOvershoot, // top
+ textHeightOvershoot, // right
+ fontMetrics.f, // bottom
+ -textHeightOvershoot // left
+ ][this.side],
+ titlePosition = {
+ x: horiz ?
+ alongAxis + xOption :
+ offAxis + (opposite ? this.width : 0) + offset + xOption,
+ y: horiz ?
+ offAxis + yOption - (opposite ? this.height : 0) + offset :
+ alongAxis + yOption
+ };
+ fireEvent(
+ this,
+ 'afterGetTitlePosition',
+ { titlePosition: titlePosition }
+ );
- return {
- x: horiz ?
- alongAxis + xOption : offAxis + (opposite ? this.width : 0) + offset + xOption,
- y: horiz ?
- offAxis + yOption - (opposite ? this.height : 0) + offset : alongAxis + yOption
- };
+ return titlePosition;
},
/**
- * Render a minor tick into the given position. If a minor tick already
+ * Render a minor tick into the given position. If a minor tick already
* exists in this position, move it.
- *
- * @param {number} pos
- * The position in axis values.
+ *
+ * @function Highcharts.Axis#renderMinorTick
+ *
+ * @param {number} pos
+ * The position in axis values.
*/
- renderMinorTick: function(pos) {
+ renderMinorTick: function (pos) {
var slideInTicks = this.chart.hasRendered && isNumber(this.oldMin),
minorTicks = this.minorTicks;
@@ -14983,13 +20148,16 @@
/**
* Render a major tick into the given position. If a tick already exists
* in this position, move it.
- *
- * @param {number} pos
- * The position in axis values.
- * @param {number} i
- * The tick index.
+ *
+ * @function Highcharts.Axis#renderTick
+ *
+ * @param {number} pos
+ * The position in axis values.
+ *
+ * @param {number} i
+ * The tick index.
*/
- renderTick: function(pos, i) {
+ renderTick: function (pos, i) {
var isLinked = this.isLinked,
ticks = this.ticks,
slideInTicks = this.chart.hasRendered && isNumber(this.oldMin);
@@ -15000,10 +20168,13 @@
if (!ticks[pos]) {
ticks[pos] = new Tick(this, pos);
}
-
+ // NOTE this seems like overkill. Could be handled in tick.render by
+ // setting old position in attr, then set new position in animate.
// render new ticks in old position
if (slideInTicks && ticks[pos].isNew) {
- ticks[pos].render(i, true, 0.1);
+ // Start with negative opacity so that it is visible from
+ // halfway into the animation
+ ticks[pos].render(i, true, -1);
}
ticks[pos].render(i);
@@ -15012,16 +20183,15 @@
/**
* Render the axis.
- *
* @private
+ * @fires Highcharts.Axis#event:afterRender
*/
- render: function() {
+ render: function () {
var axis = this,
chart = axis.chart,
renderer = chart.renderer,
options = axis.options,
isLog = axis.isLog,
- lin2log = axis.lin2log,
isLinked = axis.isLinked,
tickPositions = axis.tickPositions,
axisTitle = axis.axisTitle,
@@ -15042,8 +20212,8 @@
axis.overlap = false;
// Mark all elements inActive before we go over and mark the active ones
- each([ticks, minorTicks, alternateBands], function(coll) {
- objectEach(coll, function(tick) {
+ [ticks, minorTicks, alternateBands].forEach(function (coll) {
+ objectEach(coll, function (tick) {
tick.isActive = false;
});
});
@@ -15053,7 +20223,7 @@
// minor ticks
if (axis.minorTickInterval && !axis.categories) {
- each(axis.getMinorTickPositions(), function(pos) {
+ axis.getMinorTickPositions().forEach(function (pos) {
axis.renderMinorTick(pos);
});
}
@@ -15061,7 +20231,7 @@
// Major ticks. Pull out the first item and render it last so that
// we can get the position of the neighbour label. #808.
if (tickPositions.length) { // #1300
- each(tickPositions, function(pos, i) {
+ tickPositions.forEach(function (pos, i) {
axis.renderTick(pos, i);
});
// In a categorized axis, the tick marks are displayed
@@ -15078,7 +20248,7 @@
// alternate grid color
if (alternateGridColor) {
- each(tickPositions, function(pos, i) {
+ tickPositions.forEach(function (pos, i) {
to = tickPositions[i + 1] !== undefined ?
tickPositions[i + 1] + tickmarkOffset :
axis.max - tickmarkOffset;
@@ -15088,8 +20258,8 @@
pos < axis.max &&
to <= axis.max + (
chart.polar ?
- -tickmarkOffset :
- tickmarkOffset
+ -tickmarkOffset :
+ tickmarkOffset
)
) { // #2248, #4660
if (!alternateBands[pos]) {
@@ -15097,8 +20267,8 @@
}
from = pos + tickmarkOffset; // #949
alternateBands[pos].options = {
- from: isLog ? lin2log(from) : from,
- to: isLog ? lin2log(to) : to,
+ from: isLog ? axis.lin2log(from) : from,
+ to: isLog ? axis.lin2log(to) : to,
color: alternateGridColor
};
alternateBands[pos].render();
@@ -15109,9 +20279,10 @@
// custom plot lines and bands
if (!axis._addedPlotLB) { // only first time
- each(
- (options.plotLines || []).concat(options.plotBands || []),
- function(plotLineOptions) {
+ (
+ (options.plotLines || []).concat(options.plotBands || [])
+ ).forEach(
+ function (plotLineOptions) {
axis.addPlotBandOrLine(plotLineOptions);
}
);
@@ -15121,11 +20292,11 @@
} // end if hasData
// Remove inactive ticks
- each([ticks, minorTicks, alternateBands], function(coll) {
+ [ticks, minorTicks, alternateBands].forEach(function (coll) {
var i,
forDestruction = [],
delay = animation.duration,
- destroyInactiveItems = function() {
+ destroyInactiveItems = function () {
i = forDestruction.length;
while (i--) {
// When resizing rapidly, the same items
@@ -15142,7 +20313,7 @@
};
- objectEach(coll, function(tick, pos) {
+ objectEach(coll, function (tick, pos) {
if (!tick.isActive) {
// Render to zero opacity
tick.render(pos, false, 0);
@@ -15155,10 +20326,10 @@
syncTimeout(
destroyInactiveItems,
coll === alternateBands ||
- !chart.hasRendered ||
- !delay ?
- 0 :
- delay
+ !chart.hasRendered ||
+ !delay ?
+ 0 :
+ delay
);
});
@@ -15175,6 +20346,7 @@
if (axisTitle && showAxis) {
var titleXy = axis.getTitlePosition();
+
if (isNumber(titleXy.y)) {
axisTitle[axisTitle.isNew ? 'attr' : 'animate'](titleXy);
axisTitle.isNew = false;
@@ -15191,28 +20363,26 @@
// End stacked totals
axis.isDirty = false;
+
+ fireEvent(this, 'afterRender');
},
- /**
- * Redraw the axis to reflect changes in the data or axis extremes. Called
- * internally from {@link Chart#redraw}.
- *
- * @private
- */
- redraw: function() {
+ // Redraw the axis to reflect changes in the data or axis extremes. Called
+ // internally from Highcharts.Chart#redraw.
+ redraw: function () {
if (this.visible) {
// render the axis
this.render();
// move plot lines and bands
- each(this.plotLinesAndBands, function(plotLine) {
+ this.plotLinesAndBands.forEach(function (plotLine) {
plotLine.render();
});
}
// mark associated series as dirty and ready for redraw
- each(this.series, function(series) {
+ this.series.forEach(function (series) {
series.isDirty = true;
});
@@ -15225,34 +20395,34 @@
/**
* Destroys an Axis instance. See {@link Axis#remove} for the API endpoint
* to fully remove the axis.
- *
* @private
- * @param {Boolean} keepEvents
- * Whether to preserve events, used internally in Axis.update.
+ * @param {boolean} keepEvents Whether to preserve events, used internally
+ * in Axis.update.
*/
- destroy: function(keepEvents) {
+ destroy: function (keepEvents) {
var axis = this,
stacks = axis.stacks,
plotLinesAndBands = axis.plotLinesAndBands,
plotGroup,
i;
+ fireEvent(this, 'destroy', { keepEvents: keepEvents });
+
// Remove the events
if (!keepEvents) {
removeEvent(axis);
}
// Destroy each stack total
- objectEach(stacks, function(stack, stackKey) {
+ objectEach(stacks, function (stack, stackKey) {
destroyObjectProperties(stack);
stacks[stackKey] = null;
});
// Destroy collections
- each(
- [axis.ticks, axis.minorTicks, axis.alternateBands],
- function(coll) {
+ [axis.ticks, axis.minorTicks, axis.alternateBands].forEach(
+ function (coll) {
destroyObjectProperties(coll);
}
);
@@ -15263,12 +20433,10 @@
}
}
- // Destroy local variables
- each(
- ['stackTotalGroup', 'axisLine', 'axisTitle', 'axisGroup',
- 'gridGroup', 'labelGroup', 'cross'
- ],
- function(prop) {
+ // Destroy elements
+ ['stackTotalGroup', 'axisLine', 'axisTitle', 'axisGroup',
+ 'gridGroup', 'labelGroup', 'cross', 'scrollbar'].forEach(
+ function (prop) {
if (axis[prop]) {
axis[prop] = axis[prop].destroy();
}
@@ -15282,8 +20450,8 @@
}
// Delete all properties and fall back to the prototype.
- objectEach(axis, function(val, key) {
- if (inArray(key, axis.keepProps) === -1) {
+ objectEach(axis, function (val, key) {
+ if (axis.keepProps.indexOf(key) === -1) {
delete axis[key];
}
});
@@ -15292,13 +20460,19 @@
/**
* Internal function to draw a crosshair.
*
- * @param {PointerEvent} [e]
- * The event arguments from the modified pointer event, extended
- * with `chartX` and `chartY`
- * @param {Point} [point]
- * The Point object if the crosshair snaps to points.
+ * @function Highcharts.Axis#drawCrosshair
+ *
+ * @param {Highcharts.PointerEventObject} [e]
+ * The event arguments from the modified pointer event, extended with
+ * `chartX` and `chartY`
+ *
+ * @param {Highcharts.Point} [point]
+ * The Point object if the crosshair snaps to points.
+ *
+ * @fires Highcharts.Axis#event:afterDrawCrosshair
+ * @fires Highcharts.Axis#event:drawCrosshair
*/
- drawCrosshair: function(e, point) {
+ drawCrosshair: function (e, point) {
var path,
options = this.crosshair,
@@ -15307,6 +20481,8 @@
categorized,
graphic = this.cross;
+ fireEvent(this, 'drawCrosshair', { e: e, point: point });
+
// Use last available event when updating non-snapped crosshairs without
// mouse interaction (#5287)
if (!e) {
@@ -15327,12 +20503,15 @@
pos = e &&
(
this.horiz ?
- e.chartX - this.pos :
- this.len - e.chartY + this.pos
+ e.chartX - this.pos :
+ this.len - e.chartY + this.pos
);
} else if (defined(point)) {
// #3834
- pos = this.isXAxis ? point.plotX : this.len - point.plotY;
+ pos = pick(
+ point.crosshairPos, // 3D axis extension
+ this.isXAxis ? point.plotX : this.len - point.plotY
+ );
}
if (defined(pos)) {
@@ -15370,27 +20549,27 @@
})
.add();
-
// Presentational attributes
- graphic.attr({
- 'stroke': options.color ||
- (
- categorized ?
- color('#ccd6eb')
- .setOpacity(0.25).get() :
- '#cccccc'
- ),
- 'stroke-width': pick(options.width, 1)
- }).css({
- 'pointer-events': 'none'
- });
- if (options.dashStyle) {
+ if (!this.chart.styledMode) {
graphic.attr({
- dashstyle: options.dashStyle
+ 'stroke': options.color ||
+ (
+ categorized ?
+ color('#ccd6eb')
+ .setOpacity(0.25).get() :
+ '#cccccc'
+ ),
+ 'stroke-width': pick(options.width, 1)
+ }).css({
+ 'pointer-events': 'none'
});
+ if (options.dashStyle) {
+ graphic.attr({
+ dashstyle: options.dashStyle
+ });
+ }
}
-
}
graphic.show().attr({
@@ -15404,206 +20583,65 @@
}
this.cross.e = e;
}
+
+ fireEvent(this, 'afterDrawCrosshair', { e: e, point: point });
},
/**
- * Hide the crosshair if visible.
+ * Hide the crosshair if visible.
+ *
+ * @function Highcharts.Axis#hideCrosshair
*/
- hideCrosshair: function() {
+ hideCrosshair: function () {
if (this.cross) {
this.cross.hide();
}
+ fireEvent(this, 'afterHideCrosshair');
}
}); // end Axis
H.Axis = Axis;
+
return Axis;
}(Highcharts));
- (function(H) {
+ (function (H) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
+
var Axis = H.Axis,
- Date = H.Date,
- dateFormat = H.dateFormat,
- defaultOptions = H.defaultOptions,
- defined = H.defined,
- each = H.each,
- extend = H.extend,
getMagnitude = H.getMagnitude,
- getTZOffset = H.getTZOffset,
normalizeTickInterval = H.normalizeTickInterval,
- pick = H.pick,
timeUnits = H.timeUnits;
+
/**
* Set the tick positions to a time unit that makes sense, for example
* on the first of each month or on every Monday. Return an array
* with the time positions. Used in datetime axes as well as for grouping
* data on a datetime axis.
*
- * @param {Object} normalizedInterval The interval in axis values (ms) and the count
- * @param {Number} min The minimum in axis values
- * @param {Number} max The maximum in axis values
- * @param {Number} startOfWeek
+ * @private
+ * @function Highcharts.Axis#getTimeTicks
+ *
+ * @param {*} normalizedInterval
+ * The interval in axis values (ms) and thecount
+ *
+ * @param {number} min
+ * The minimum in axis values
+ *
+ * @param {number} max
+ * The maximum in axis values
+ *
+ * @param {number} startOfWeek
+ *
+ * @return {number}
*/
- Axis.prototype.getTimeTicks = function(normalizedInterval, min, max, startOfWeek) {
- var tickPositions = [],
- i,
- higherRanks = {},
- useUTC = defaultOptions.global.useUTC,
- minYear, // used in months and years as a basis for Date.UTC()
- // When crossing DST, use the max. Resolves #6278.
- minDate = new Date(min - Math.max(getTZOffset(min), getTZOffset(max))),
- makeTime = Date.hcMakeTime,
- interval = normalizedInterval.unitRange,
- count = normalizedInterval.count,
- baseOffset, // #6797
- variableDayLength;
-
- if (defined(min)) { // #1300
- minDate[Date.hcSetMilliseconds](interval >= timeUnits.second ? 0 : // #3935
- count * Math.floor(minDate.getMilliseconds() / count)); // #3652, #3654
-
- if (interval >= timeUnits.second) { // second
- minDate[Date.hcSetSeconds](interval >= timeUnits.minute ? 0 : // #3935
- count * Math.floor(minDate.getSeconds() / count));
- }
-
- if (interval >= timeUnits.minute) { // minute
- minDate[Date.hcSetMinutes](interval >= timeUnits.hour ? 0 :
- count * Math.floor(minDate[Date.hcGetMinutes]() / count));
- }
-
- if (interval >= timeUnits.hour) { // hour
- minDate[Date.hcSetHours](interval >= timeUnits.day ? 0 :
- count * Math.floor(minDate[Date.hcGetHours]() / count));
- }
-
- if (interval >= timeUnits.day) { // day
- minDate[Date.hcSetDate](interval >= timeUnits.month ? 1 :
- count * Math.floor(minDate[Date.hcGetDate]() / count));
- }
-
- if (interval >= timeUnits.month) { // month
- minDate[Date.hcSetMonth](interval >= timeUnits.year ? 0 :
- count * Math.floor(minDate[Date.hcGetMonth]() / count));
- minYear = minDate[Date.hcGetFullYear]();
- }
-
- if (interval >= timeUnits.year) { // year
- minYear -= minYear % count;
- minDate[Date.hcSetFullYear](minYear);
- }
-
- // week is a special case that runs outside the hierarchy
- if (interval === timeUnits.week) {
- // get start of current week, independent of count
- minDate[Date.hcSetDate](minDate[Date.hcGetDate]() - minDate[Date.hcGetDay]() +
- pick(startOfWeek, 1));
- }
-
-
- // Get basics for variable time spans
- minYear = minDate[Date.hcGetFullYear]();
- var minMonth = minDate[Date.hcGetMonth](),
- minDateDate = minDate[Date.hcGetDate](),
- minHours = minDate[Date.hcGetHours]();
-
-
- // Handle local timezone offset
- if (Date.hcTimezoneOffset || Date.hcGetTimezoneOffset) {
-
- // Detect whether we need to take the DST crossover into
- // consideration. If we're crossing over DST, the day length may be
- // 23h or 25h and we need to compute the exact clock time for each
- // tick instead of just adding hours. This comes at a cost, so first
- // we found out if it is needed. #4951.
- variableDayLength =
- (!useUTC || !!Date.hcGetTimezoneOffset) &&
- (
- // Long range, assume we're crossing over.
- max - min > 4 * timeUnits.month ||
- // Short range, check if min and max are in different time
- // zones.
- getTZOffset(min) !== getTZOffset(max)
- );
-
- // Adjust minDate to the offset date
- minDate = minDate.getTime();
- baseOffset = getTZOffset(minDate);
- minDate = new Date(minDate + baseOffset);
- }
-
-
- // Iterate and add tick positions at appropriate values
- var time = minDate.getTime();
- i = 1;
- while (time < max) {
- tickPositions.push(time);
-
- // if the interval is years, use Date.UTC to increase years
- 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) {
- 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 (
- variableDayLength &&
- (interval === timeUnits.day || interval === timeUnits.week)
- ) {
- time = makeTime(minYear, minMonth, minDateDate +
- i * count * (interval === timeUnits.day ? 1 : 7));
-
- } else if (variableDayLength && interval === timeUnits.hour) {
- // corrected by the start date time zone offset (baseOffset)
- // to hide duplicated label (#6797)
- time = makeTime(minYear, minMonth, minDateDate, minHours +
- i * count, 0, 0, baseOffset) - baseOffset;
-
- // else, the interval is fixed and we use simple addition
- } else {
- time += interval * count;
- }
-
- i++;
- }
-
- // push the last time
- tickPositions.push(time);
-
-
- // Handle higher ranks. Mark new days if the time is on midnight
- // (#950, #1649, #1760, #3349). Use a reasonable dropout threshold to
- // prevent looping over dense data grouping (#6156).
- if (interval <= timeUnits.hour && tickPositions.length < 10000) {
- each(tickPositions, function(time) {
- if (
- // Speed optimization, no need to run dateFormat unless
- // we're on a full or half hour
- time % 1800000 === 0 &&
- // Check for local or global midnight
- dateFormat('%H%M%S%L', time) === '000000000'
- ) {
- higherRanks[time] = 'day';
- }
- });
- }
- }
-
-
- // record information on the chosen unit - for dynamic label formatter
- tickPositions.info = extend(normalizedInterval, {
- higherRanks: higherRanks,
- totalRange: interval * count
- });
-
- return tickPositions;
+ Axis.prototype.getTimeTicks = function () {
+ return this.chart.time.getTimeTicks.apply(this.chart.time, arguments);
};
/**
@@ -15613,36 +20651,45 @@
* of segments in stock charts, the normalizing logic was extracted in order to
* prevent it for running over again for each segment having the same interval.
* #662, #697.
+ *
+ * @private
+ * @function Highcharts.Axis#normalizeTimeTickInterval
+ *
+ * @param {number} tickInterval
+ *
+ * @param {Array>} [unitsOption]
+ *
+ * @return {*}
*/
- Axis.prototype.normalizeTimeTickInterval = function(tickInterval, unitsOption) {
- var units = unitsOption || [
- [
- 'millisecond', // unit name
- [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
- ],
- [
- 'second', [1, 2, 5, 10, 15, 30]
- ],
- [
- 'minute', [1, 2, 5, 10, 15, 30]
- ],
- [
- 'hour', [1, 2, 3, 4, 6, 8, 12]
- ],
- [
- 'day', [1, 2]
- ],
- [
- 'week', [1, 2]
- ],
- [
- 'month', [1, 2, 3, 4, 6]
- ],
- [
- 'year',
- null
- ]
- ],
+ Axis.prototype.normalizeTimeTickInterval = function (
+ tickInterval,
+ unitsOption
+ ) {
+ var units = unitsOption || [[
+ 'millisecond', // unit name
+ [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
+ ], [
+ 'second',
+ [1, 2, 5, 10, 15, 30]
+ ], [
+ 'minute',
+ [1, 2, 5, 10, 15, 30]
+ ], [
+ 'hour',
+ [1, 2, 3, 4, 6, 8, 12]
+ ], [
+ 'day',
+ [1, 2]
+ ], [
+ 'week',
+ [1, 2]
+ ], [
+ 'month',
+ [1, 2, 3, 4, 6]
+ ], [
+ 'year',
+ null
+ ]],
unit = units[units.length - 1], // default unit is years
interval = timeUnits[unit[0]],
multiples = unit[1],
@@ -15657,9 +20704,10 @@
if (units[i + 1]) {
- // lessThan is in the middle between the highest multiple and the next unit.
+ // lessThan is in the middle between the highest multiple and the
+ // next unit.
var lessThan = (interval * multiples[multiples.length - 1] +
- timeUnits[units[i + 1][0]]) / 2;
+ timeUnits[units[i + 1][0]]) / 2;
// break and keep the current unit
if (tickInterval <= lessThan) {
@@ -15677,7 +20725,9 @@
count = normalizeTickInterval(
tickInterval / interval,
multiples,
- unit[0] === 'year' ? Math.max(getMagnitude(tickInterval / interval), 1) : 1 // #1913, #2360
+ unit[0] === 'year' ?
+ Math.max(getMagnitude(tickInterval / interval), 1) : // #1913, #2360
+ 1
);
return {
@@ -15688,30 +20738,44 @@
};
}(Highcharts));
- (function(H) {
+ (function (H) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
+
var Axis = H.Axis,
getMagnitude = H.getMagnitude,
- map = H.map,
normalizeTickInterval = H.normalizeTickInterval,
pick = H.pick;
- /**
+
+ /*
* Methods defined on the Axis prototype
*/
/**
- * Set the tick positions of a logarithmic axis
+ * Set the tick positions of a logarithmic axis.
+ *
+ * @private
+ * @function Highcharts.Axis#getLogTickPositions
+ *
+ * @param {number} interval
+ *
+ * @param {number} min
+ *
+ * @param {number} max
+ *
+ * @param {number} minor
+ *
+ * @return {Array}
*/
- Axis.prototype.getLogTickPositions = function(interval, min, max, minor) {
+ Axis.prototype.getLogTickPositions = function (interval, min, max, minor) {
var axis = this,
options = axis.options,
axisLength = axis.len,
- lin2log = axis.lin2log,
- log2lin = axis.log2lin,
// Since we use this method for both major and minor ticks,
// use a local variable and return the result
positions = [];
@@ -15726,8 +20790,8 @@
interval = Math.round(interval);
positions = axis.getLinearTickPositions(interval, min, max);
- // Second case: We need intermediary ticks. For example
- // 1, 2, 4, 6, 8, 10, 20, 40 etc.
+ // Second case: We need intermediary ticks. For example
+ // 1, 2, 4, 6, 8, 10, 20, 40 etc.
} else if (interval >= 0.08) {
var roundedMin = Math.floor(min),
intermediate,
@@ -15740,7 +20804,9 @@
if (interval > 0.3) {
intermediate = [1, 2, 4];
- } else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc
+
+ // 0.2 equals five minor ticks per 1, 10, 100 etc
+ } else if (interval > 0.15) {
intermediate = [1, 2, 4, 6, 8];
} else { // 0.1 equals ten minor ticks per 1, 10, 100 etc
intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
@@ -15749,8 +20815,13 @@
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) && lastPos !== undefined) { // #1670, lastPos is #3113
+ pos = axis.log2lin(axis.lin2log(i) * intermediate[j]);
+ // #1670, lastPos is #3113
+ if (
+ pos > min &&
+ (!minor || lastPos <= max) &&
+ lastPos !== undefined
+ ) {
positions.push(lastPos);
}
@@ -15761,23 +20832,29 @@
}
}
- // Third case: We are so deep in between whole logarithmic values that
- // we might as well handle the tick positions like a linear axis. For
- // example 1.01, 1.02, 1.03, 1.04.
+ // Third case: We are so deep in between whole logarithmic values that
+ // we might as well handle the tick positions like a linear axis. For
+ // example 1.01, 1.02, 1.03, 1.04.
} else {
- var realMin = lin2log(min),
- realMax = lin2log(max),
+ var realMin = axis.lin2log(min),
+ realMax = axis.lin2log(max),
tickIntervalOption = minor ?
- this.getMinorTickInterval() :
- options.tickInterval,
- filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption,
- tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1),
- totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength;
+ this.getMinorTickInterval() :
+ options.tickInterval,
+ filteredTickIntervalOption = tickIntervalOption === 'auto' ?
+ null :
+ tickIntervalOption,
+ tickPixelIntervalOption =
+ options.tickPixelInterval / (minor ? 5 : 1),
+ totalPixelLength = minor ?
+ axisLength / axis.tickPositions.length :
+ axisLength;
interval = pick(
filteredTickIntervalOption,
axis._minorAutoInterval,
- (realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1)
+ (realMax - realMin) *
+ tickPixelIntervalOption / (totalPixelLength || 1)
);
interval = normalizeTickInterval(
@@ -15786,11 +20863,11 @@
getMagnitude(interval)
);
- positions = map(axis.getLinearTickPositions(
+ positions = axis.getLinearTickPositions(
interval,
realMin,
realMax
- ), log2lin);
+ ).map(axis.log2lin);
if (!minor) {
axis._minorAutoInterval = interval / 5;
@@ -15804,34 +20881,83 @@
return positions;
};
- Axis.prototype.log2lin = function(num) {
+ /**
+ * @private
+ * @function Highcharts.Axis#log2lin
+ *
+ * @param {number} num
+ *
+ * @return {number}
+ */
+ Axis.prototype.log2lin = function (num) {
return Math.log(num) / Math.LN10;
};
- Axis.prototype.lin2log = function(num) {
+ /**
+ * @private
+ * @function Highcharts.Axis#lin2log
+ *
+ * @param {number} num
+ *
+ * @return {number}
+ */
+ Axis.prototype.lin2log = function (num) {
return Math.pow(10, num);
};
}(Highcharts));
- (function(H, Axis) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H, Axis) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+ /**
+ * Options for plot bands on axes.
+ *
+ * @typedef {Highcharts.XAxisPlotBandsOptions|Highcharts.YAxisPlotBandsOptions|Highcharts.ZAxisPlotBandsOptions} Highcharts.AxisPlotBandsOptions
+ */
+
+ /**
+ * Options for plot band labels on axes.
+ *
+ * @typedef {Highcharts.XAxisPlotBandsLabelOptions|Highcharts.YAxisPlotBandsLabelOptions|Highcharts.ZAxisPlotBandsLabelOptions} Highcharts.AxisPlotBandsLabelOptions
+ */
+
+ /**
+ * Options for plot lines on axes.
+ *
+ * @typedef {Highcharts.XAxisPlotLinesOptions|Highcharts.YAxisPlotLinesOptions|Highcharts.ZAxisPlotLinesOptions} Highcharts.AxisPlotLinesOptions
+ */
+
+ /**
+ * Options for plot line labels on axes.
+ *
+ * @typedef {Highcharts.XAxisPlotLinesLabelOptions|Highcharts.YAxisPlotLinesLabelOptions|Highcharts.ZAxisPlotLinesLabelOptions} Highcharts.AxisPlotLinesLabelOptions
+ */
+
+
+
var arrayMax = H.arrayMax,
arrayMin = H.arrayMin,
defined = H.defined,
destroyObjectProperties = H.destroyObjectProperties,
- each = H.each,
erase = H.erase,
merge = H.merge,
pick = H.pick;
- /*
+
+ /**
* The object wrapper for plot lines and plot bands
- * @param {Object} options
+ *
+ * @class
+ * @name Highcharts.PlotLineOrBand
+ *
+ * @param {Highcharts.Axis} axis
+ *
+ * @param {Highcharts.AxisPlotLinesOptions|Highcharts.AxisPlotBandsOptions} options
*/
- H.PlotLineOrBand = function(axis, options) {
+ H.PlotLineOrBand = function (axis, options) {
this.axis = axis;
if (options) {
@@ -15845,8 +20971,16 @@
/**
* Render the plot line or plot band. If it is already existing,
* move it.
+ *
+ * @private
+ * @function Highcharts.PlotLineOrBand#render
+ *
+ * @return {Highcharts.PlotLineOrBand|undefined}
*/
- render: function() {
+ render: function () {
+
+ H.fireEvent(this, 'render');
+
var plotLine = this,
axis = plotLine.axis,
horiz = axis.horiz,
@@ -15871,38 +21005,35 @@
groupAttribs = {},
renderer = axis.chart.renderer,
groupName = isBand ? 'bands' : 'lines',
- group,
- log2lin = axis.log2lin;
+ group;
// logarithmic conversion
if (axis.isLog) {
- from = log2lin(from);
- to = log2lin(to);
- value = log2lin(value);
+ from = axis.log2lin(from);
+ to = axis.log2lin(to);
+ value = axis.log2lin(value);
}
-
// Set the presentational attributes
- if (isLine) {
- attribs = {
- stroke: color,
- 'stroke-width': options.width
- };
- if (options.dashStyle) {
- attribs.dashstyle = options.dashStyle;
- }
+ if (!axis.chart.styledMode) {
+ if (isLine) {
+ attribs.stroke = color;
+ attribs['stroke-width'] = options.width;
+ if (options.dashStyle) {
+ attribs.dashstyle = options.dashStyle;
+ }
- } else if (isBand) { // plot band
- if (color) {
- attribs.fill = color;
- }
- if (options.borderWidth) {
- attribs.stroke = options.borderColor;
- attribs['stroke-width'] = options.borderWidth;
+ } else if (isBand) { // plot band
+ if (color) {
+ attribs.fill = color;
+ }
+ if (options.borderWidth) {
+ attribs.stroke = options.borderColor;
+ attribs['stroke-width'] = options.borderWidth;
+ }
}
}
-
// Grouping and zIndex
groupAttribs.zIndex = zIndex;
groupName += '-' + zIndex;
@@ -15911,15 +21042,21 @@
if (!group) {
axis.plotLinesAndBandsGroups[groupName] = group =
renderer.g('plot-' + groupName)
- .attr(groupAttribs).add();
+ .attr(groupAttribs).add();
}
// Create the path
if (isNew) {
+ /**
+ * SVG element of the plot line or band.
+ *
+ * @name Highcharts.PlotLineOrBand#svgElement
+ * @type {Highcharts.SVGElement}
+ */
plotLine.svgElem = svgElem =
renderer
- .path()
- .attr(attribs).add(group);
+ .path()
+ .attr(attribs).add(group);
}
@@ -15935,14 +21072,12 @@
// common for lines and bands
if (isNew && path && path.length) {
- svgElem.attr({
- d: path
- });
+ svgElem.attr({ d: path });
// events
if (events) {
- H.objectEach(events, function(event, eventType) {
- svgElem.on(eventType, function(e) {
+ H.objectEach(events, function (event, eventType) {
+ svgElem.on(eventType, function (e) {
events[eventType].apply(plotLine, [e]);
});
});
@@ -15950,9 +21085,7 @@
} else if (svgElem) {
if (path) {
svgElem.show();
- svgElem.animate({
- d: path
- });
+ svgElem.animate({ d: path });
} else {
svgElem.hide();
if (label) {
@@ -15969,7 +21102,7 @@
path.length &&
axis.width > 0 &&
axis.height > 0 &&
- !path.flat
+ !path.isFlat
) {
// apply defaults
optionsLabel = merge({
@@ -15992,8 +21125,19 @@
/**
* Render and align label for plot line or band.
+ *
+ * @private
+ * @function Highcharts.PlotLineOrBand#renderLabel
+ *
+ * @param {Highcharts.AxisPlotLinesLabelOptions|Highcharts.AxisPlotBandsLabelOptions} optionsLabel
+ *
+ * @param {Highcharts.SVGPathArray} path
+ *
+ * @param {boolean} [isBand]
+ *
+ * @param {number} [zIndex]
*/
- renderLabel: function(optionsLabel, path, isBand, zIndex) {
+ renderLabel: function (optionsLabel, path, isBand, zIndex) {
var plotLine = this,
label = plotLine.label,
renderer = plotLine.axis.chart.renderer,
@@ -16014,24 +21158,32 @@
attribs.zIndex = zIndex;
+ /**
+ * SVG element of the label.
+ *
+ * @name Highcharts.PlotLineOrBand#label
+ * @type {Highcharts.SVGElement}
+ */
plotLine.label = label = renderer.text(
- optionsLabel.text,
- 0,
- 0,
- optionsLabel.useHTML
- )
+ optionsLabel.text,
+ 0,
+ 0,
+ optionsLabel.useHTML
+ )
.attr(attribs)
.add();
-
- label.css(optionsLabel.style);
-
+ if (!this.axis.chart.styledMode) {
+ label.css(optionsLabel.style);
+ }
}
// get the bounding box and align the label
// #3000 changed to better handle choice between plotband or plotline
- xBounds = path.xBounds || [path[1], path[4], (isBand ? path[6] : path[1])];
- yBounds = path.yBounds || [path[2], path[5], (isBand ? path[7] : path[2])];
+ xBounds = path.xBounds ||
+ [path[1], path[4], (isBand ? path[6] : path[1])];
+ yBounds = path.yBounds ||
+ [path[2], path[5], (isBand ? path[7] : path[2])];
x = arrayMin(xBounds);
y = arrayMin(yBounds);
@@ -16046,53 +21198,595 @@
},
/**
- * Remove the plot line or band
+ * Remove the plot line or band.
+ *
+ * @function Highcharts.PlotLineOrBand#destroy
+ */
+ destroy: function () {
+ // remove it from the lookup
+ erase(this.axis.plotLinesAndBands, this);
+
+ delete this.axis;
+ destroyObjectProperties(this);
+ }
+ };
+
+ // Object with members for extending the Axis prototype
+ H.extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ {
+
+ /**
+ * An array of colored bands stretching across the plot area marking an
+ * interval on the axis.
+ *
+ * In styled mode, the plot bands are styled by the `.highcharts-plot-band`
+ * class in addition to the `className` option.
+ *
+ * @productdesc {highcharts}
+ * In a gauge, a plot band on the Y axis (value axis) will stretch along the
+ * perimeter of the gauge.
+ *
+ * @type {Array<*>}
+ * @product highcharts highstock gantt
+ * @apioption xAxis.plotBands
+ */
+
+ /**
+ * Border color for the plot band. Also requires `borderWidth` to be set.
+ *
+ * @type {Highcharts.ColorString}
+ * @apioption xAxis.plotBands.borderColor
+ */
+
+ /**
+ * Border width for the plot band. Also requires `borderColor` to be set.
+ *
+ * @type {number}
+ * @default 0
+ * @apioption xAxis.plotBands.borderWidth
+ */
+
+ /**
+ * A custom class name, in addition to the default `highcharts-plot-band`,
+ * to apply to each individual band.
+ *
+ * @type {string}
+ * @since 5.0.0
+ * @apioption xAxis.plotBands.className
+ */
+
+ /**
+ * The color of the plot band.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotbands-color/
+ * Color band
+ * @sample {highstock} stock/xaxis/plotbands/
+ * Plot band on Y axis
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @apioption xAxis.plotBands.color
+ */
+
+ /**
+ * An object defining mouse events for the plot band. Supported properties
+ * are `click`, `mouseover`, `mouseout`, `mousemove`.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotbands-events/
+ * Mouse events demonstrated
+ *
+ * @since 1.2
+ * @context PlotLineOrBand
+ * @apioption xAxis.plotBands.events
+ */
+
+ /**
+ * The start position of the plot band in axis units.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotbands-color/
+ * Datetime axis
+ * @sample {highcharts} highcharts/xaxis/plotbands-from/
+ * Categorized axis
+ * @sample {highstock} stock/xaxis/plotbands/
+ * Plot band on Y axis
+ *
+ * @type {number}
+ * @apioption xAxis.plotBands.from
+ */
+
+ /**
+ * An id used for identifying the plot band in Axis.removePlotBand.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotbands-id/
+ * Remove plot band by id
+ * @sample {highstock} highcharts/xaxis/plotbands-id/
+ * Remove plot band by id
+ *
+ * @type {string}
+ * @apioption xAxis.plotBands.id
+ */
+
+ /**
+ * The end position of the plot band in axis units.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotbands-color/
+ * Datetime axis
+ * @sample {highcharts} highcharts/xaxis/plotbands-from/
+ * Categorized axis
+ * @sample {highstock} stock/xaxis/plotbands/
+ * Plot band on Y axis
+ *
+ * @type {number}
+ * @apioption xAxis.plotBands.to
+ */
+
+ /**
+ * The z index of the plot band within the chart, relative to other
+ * elements. Using the same z index as another element may give
+ * unpredictable results, as the last rendered element will be on top.
+ * Values from 0 to 20 make sense.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotbands-color/
+ * Behind plot lines by default
+ * @sample {highcharts} highcharts/xaxis/plotbands-zindex/
+ * Above plot lines
+ * @sample {highcharts} highcharts/xaxis/plotbands-zindex-above-series/
+ * Above plot lines and series
+ *
+ * @type {number}
+ * @since 1.2
+ * @apioption xAxis.plotBands.zIndex
+ */
+
+ /**
+ * Text labels for the plot bands
+ *
+ * @product highcharts highstock gantt
+ * @apioption xAxis.plotBands.label
+ */
+
+ /**
+ * Horizontal alignment of the label. Can be one of "left", "center" or
+ * "right".
+ *
+ * @sample {highcharts} highcharts/xaxis/plotbands-label-align/
+ * Aligned to the right
+ * @sample {highstock} stock/xaxis/plotbands-label/
+ * Plot band with labels
+ *
+ * @type {Highcharts.AlignType}
+ * @default center
+ * @since 2.1
+ * @apioption xAxis.plotBands.label.align
+ */
+
+ /**
+ * Rotation of the text label in degrees .
+ *
+ * @sample {highcharts} highcharts/xaxis/plotbands-label-rotation/
+ * Vertical text
+ *
+ * @type {number}
+ * @default 0
+ * @since 2.1
+ * @apioption xAxis.plotBands.label.rotation
+ */
+
+ /**
+ * CSS styles for the text label.
+ *
+ * In styled mode, the labels are styled by the
+ * `.highcharts-plot-band-label` class.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotbands-label-style/
+ * Blue and bold label
+ *
+ * @type {Highcharts.CSSObject}
+ * @since 2.1
+ * @apioption xAxis.plotBands.label.style
+ */
+
+ /**
+ * The string text itself. A subset of HTML is supported.
+ *
+ * @type {string}
+ * @since 2.1
+ * @apioption xAxis.plotBands.label.text
+ */
+
+ /**
+ * The text alignment for the label. While `align` determines where the
+ * texts anchor point is placed within the plot band, `textAlign` determines
+ * how the text is aligned against its anchor point. Possible values are
+ * "left", "center" and "right". Defaults to the same as the `align` option.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotbands-label-rotation/
+ * Vertical text in center position but text-aligned left
+ *
+ * @type {Highcharts.AlignType}
+ * @since 2.1
+ * @apioption xAxis.plotBands.label.textAlign
+ */
+
+ /**
+ * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
+ * to render the labels.
+ *
+ * @type {boolean}
+ * @default false
+ * @since 3.0.3
+ * @apioption xAxis.plotBands.label.useHTML
+ */
+
+ /**
+ * Vertical alignment of the label relative to the plot band. Can be one of
+ * "top", "middle" or "bottom".
+ *
+ * @sample {highcharts} highcharts/xaxis/plotbands-label-verticalalign/
+ * Vertically centered label
+ * @sample {highstock} stock/xaxis/plotbands-label/
+ * Plot band with labels
+ *
+ * @type {Highcharts.VerticalAlignType}
+ * @default top
+ * @since 2.1
+ * @apioption xAxis.plotBands.label.verticalAlign
+ */
+
+ /**
+ * Horizontal position relative the alignment. Default varies by
+ * orientation.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotbands-label-align/
+ * Aligned 10px from the right edge
+ * @sample {highstock} stock/xaxis/plotbands-label/
+ * Plot band with labels
+ *
+ * @type {number}
+ * @since 2.1
+ * @apioption xAxis.plotBands.label.x
+ */
+
+ /**
+ * Vertical position of the text baseline relative to the alignment. Default
+ * varies by orientation.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotbands-label-y/
+ * Label on x axis
+ * @sample {highstock} stock/xaxis/plotbands-label/
+ * Plot band with labels
+ *
+ * @type {number}
+ * @since 2.1
+ * @apioption xAxis.plotBands.label.y
+ */
+
+ /**
+ * An array of lines stretching across the plot area, marking a specific
+ * value on one of the axes.
+ *
+ * In styled mode, the plot lines are styled by the
+ * `.highcharts-plot-line` class in addition to the `className` option.
+ *
+ * @type {Array<*>}
+ * @product highcharts highstock gantt
+ * @apioption xAxis.plotLines
+ */
+
+ /**
+ * A custom class name, in addition to the default `highcharts-plot-line`,
+ * to apply to each individual line.
+ *
+ * @type {string}
+ * @since 5.0.0
+ * @apioption xAxis.plotLines.className
+ */
+
+ /**
+ * The color of the line.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotlines-color/
+ * A red line from X axis
+ * @sample {highstock} stock/xaxis/plotlines/
+ * Plot line on Y axis
+ *
+ * @type {Highcharts.ColorString}
+ * @apioption xAxis.plotLines.color
+ */
+
+ /**
+ * The dashing or dot style for the plot line. For possible values see
+ * [this overview](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/).
+ *
+ * @sample {highcharts} highcharts/xaxis/plotlines-dashstyle/
+ * Dash and dot pattern
+ * @sample {highstock} stock/xaxis/plotlines/
+ * Plot line on Y axis
+ *
+ * @type {Highcharts.DashStyleType}
+ * @default Solid
+ * @since 1.2
+ * @apioption xAxis.plotLines.dashStyle
+ */
+
+ /**
+ * An object defining mouse events for the plot line. Supported
+ * properties are `click`, `mouseover`, `mouseout`, `mousemove`.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotlines-events/
+ * Mouse events demonstrated
+ *
+ * @type {*}
+ * @since 1.2
+ * @context PlotLineOrBand
+ * @apioption xAxis.plotLines.events
+ */
+
+ /**
+ * An id used for identifying the plot line in Axis.removePlotLine.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotlines-id/
+ * Remove plot line by id
+ *
+ * @type {string}
+ * @apioption xAxis.plotLines.id
+ */
+
+ /**
+ * The position of the line in axis units.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotlines-color/
+ * Between two categories on X axis
+ * @sample {highstock} stock/xaxis/plotlines/
+ * Plot line on Y axis
+ *
+ * @type {number}
+ * @apioption xAxis.plotLines.value
+ */
+
+ /**
+ * The width or thickness of the plot line.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotlines-color/
+ * 2px wide line from X axis
+ * @sample {highstock} stock/xaxis/plotlines/
+ * Plot line on Y axis
+ *
+ * @type {number}
+ * @apioption xAxis.plotLines.width
+ */
+
+ /**
+ * The z index of the plot line within the chart.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotlines-zindex-behind/
+ * Behind plot lines by default
+ * @sample {highcharts} highcharts/xaxis/plotlines-zindex-above/
+ * Above plot lines
+ * @sample {highcharts} highcharts/xaxis/plotlines-zindex-above-all/
+ * Above plot lines and series
+ *
+ * @type {number}
+ * @since 1.2
+ * @apioption xAxis.plotLines.zIndex
+ */
+
+ /**
+ * Text labels for the plot bands
+ *
+ * @apioption xAxis.plotLines.label
+ */
+
+ /**
+ * Horizontal alignment of the label. Can be one of "left", "center" or
+ * "right".
+ *
+ * @sample {highcharts} highcharts/xaxis/plotlines-label-align-right/
+ * Aligned to the right
+ * @sample {highstock} stock/xaxis/plotlines/
+ * Plot line on Y axis
+ *
+ * @type {Highcharts.AlignType}
+ * @default left
+ * @since 2.1
+ * @apioption xAxis.plotLines.label.align
+ */
+
+ /**
+ * Rotation of the text label in degrees. Defaults to 0 for horizontal plot
+ * lines and 90 for vertical lines.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotlines-label-verticalalign-middle/
+ * Slanted text
+ *
+ * @type {number}
+ * @since 2.1
+ * @apioption xAxis.plotLines.label.rotation
+ */
+
+ /**
+ * CSS styles for the text label.
+ *
+ * In styled mode, the labels are styled by the
+ * `.highcharts-plot-line-label` class.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotlines-label-style/
+ * Blue and bold label
+ *
+ * @type {Highcharts.CSSObject}
+ * @since 2.1
+ * @apioption xAxis.plotLines.label.style
+ */
+
+ /**
+ * The text itself. A subset of HTML is supported.
+ *
+ * @type {string}
+ * @since 2.1
+ * @apioption xAxis.plotLines.label.text
+ */
+
+ /**
+ * The text alignment for the label. While `align` determines where the
+ * texts anchor point is placed within the plot band, `textAlign` determines
+ * how the text is aligned against its anchor point. Possible values are
+ * "left", "center" and "right". Defaults to the same as the `align` option.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotlines-label-textalign/
+ * Text label in bottom position
+ *
+ * @type {Highcharts.AlignType}
+ * @since 2.1
+ * @apioption xAxis.plotLines.label.textAlign
+ */
+
+ /**
+ * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
+ * to render the labels.
+ *
+ * @type {boolean}
+ * @default false
+ * @since 3.0.3
+ * @apioption xAxis.plotLines.label.useHTML
+ */
+
+ /**
+ * Vertical alignment of the label relative to the plot line. Can be
+ * one of "top", "middle" or "bottom".
+ *
+ * @sample {highcharts} highcharts/xaxis/plotlines-label-verticalalign-middle/
+ * Vertically centered label
+ *
+ * @type {Highcharts.VerticalAlignType}
+ * @default {highcharts} top
+ * @default {highstock} top
+ * @since 2.1
+ * @validvalue ["top", "middle", "bottom"]
+ * @apioption xAxis.plotLines.label.verticalAlign
+ */
+
+ /**
+ * Horizontal position relative the alignment. Default varies by
+ * orientation.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotlines-label-align-right/
+ * Aligned 10px from the right edge
+ * @sample {highstock} stock/xaxis/plotlines/
+ * Plot line on Y axis
+ *
+ * @type {number}
+ * @since 2.1
+ * @apioption xAxis.plotLines.label.x
+ */
+
+ /**
+ * Vertical position of the text baseline relative to the alignment. Default
+ * varies by orientation.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotlines-label-y/
+ * Label below the plot line
+ * @sample {highstock} stock/xaxis/plotlines/
+ * Plot line on Y axis
+ *
+ * @type {number}
+ * @since 2.1
+ * @apioption xAxis.plotLines.label.y
+ */
+
+ /**
+ * An array of objects defining plot bands on the Y axis.
+ *
+ * @type {Array<*>}
+ * @extends xAxis.plotBands
+ * @apioption yAxis.plotBands
+ */
+
+ /**
+ * In a gauge chart, this option determines the inner radius of the
+ * plot band that stretches along the perimeter. It can be given as
+ * a percentage string, like `"100%"`, or as a pixel number, like `100`.
+ * By default, the inner radius is controlled by the [thickness](
+ * #yAxis.plotBands.thickness) option.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotbands-gauge
+ * Gauge plot band
+ *
+ * @type {number|string}
+ * @since 2.3
+ * @product highcharts
+ * @apioption yAxis.plotBands.innerRadius
+ */
+
+ /**
+ * In a gauge chart, this option determines the outer radius of the
+ * plot band that stretches along the perimeter. It can be given as
+ * a percentage string, like `"100%"`, or as a pixel number, like `100`.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotbands-gauge
+ * Gauge plot band
+ *
+ * @type {number|string}
+ * @default 100%
+ * @since 2.3
+ * @product highcharts
+ * @apioption yAxis.plotBands.outerRadius
*/
- destroy: function() {
- // remove it from the lookup
- erase(this.axis.plotLinesAndBands, this);
-
- delete this.axis;
- destroyObjectProperties(this);
- }
- };
- /**
- * Object with members for extending the Axis prototype
- * @todo Extend directly instead of adding object to Highcharts first
- */
+ /**
+ * In a gauge chart, this option sets the width of the plot band
+ * stretching along the perimeter. It can be given as a percentage
+ * string, like `"10%"`, or as a pixel number, like `10`. The default
+ * value 10 is the same as the default [tickLength](#yAxis.tickLength),
+ * thus making the plot band act as a background for the tick markers.
+ *
+ * @sample {highcharts} highcharts/xaxis/plotbands-gauge
+ * Gauge plot band
+ *
+ * @type {number|string}
+ * @default 10
+ * @since 2.3
+ * @product highcharts
+ * @apioption yAxis.plotBands.thickness
+ */
- H.extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ {
+ /**
+ * An array of objects representing plot lines on the X axis
+ *
+ * @type {Array<*>}
+ * @extends xAxis.plotLines
+ * @apioption yAxis.plotLines
+ */
/**
* Internal function to create the SVG path definition for a plot band.
*
- * @param {Number} from
- * The axis value to start from.
- * @param {Number} to
- * The axis value to end on.
+ * @function Highcharts.Axis#getPlotBandPath
*
- * @return {Array.}
+ * @param {number} from
+ * The axis value to start from.
+ *
+ * @param {number} to
+ * The axis value to end on.
+ *
+ * @return {Highcharts.SVGPathArray}
* The SVG path definition in array form.
*/
- getPlotBandPath: function(from, to) {
+ getPlotBandPath: function (from, to) {
var toPath = this.getPlotLinePath(to, null, null, true),
path = this.getPlotLinePath(from, null, null, true),
result = [],
i,
- // #4964 check if chart is inverted or plotband is on yAxis
+ // #4964 check if chart is inverted or plotband is on yAxis
horiz = this.horiz,
plus = 1,
- flat,
+ isFlat,
outside =
- (from < this.min && to < this.min) ||
- (from > this.max && to > this.max);
+ (from < this.min && to < this.min) ||
+ (from > this.max && to > this.max);
if (path && toPath) {
// Flat paths don't need labels (#3836)
if (outside) {
- flat = path.toString() === toPath.toString();
+ isFlat = path.toString() === toPath.toString();
plus = 0;
}
@@ -16121,7 +21815,7 @@
toPath[i + 2],
'z'
);
- result.flat = flat;
+ result.isFlat = isFlat;
}
} else { // outside the axis area
@@ -16134,32 +21828,38 @@
/**
* Add a plot band after render time.
*
- * @param {AxisPlotBandsOptions} options
- * A configuration object for the plot band, as defined in {@link
- * https://api.highcharts.com/highcharts/xAxis.plotBands|
- * xAxis.plotBands}.
- * @return {Object}
- * The added plot band.
* @sample highcharts/members/axis-addplotband/
* Toggle the plot band from a button
+ *
+ * @function Highcharts.Axis#addPlotBand
+ *
+ * @param {Highcharts.AxisPlotBandsOptions} options
+ * A configuration object for the plot band, as defined in
+ * [xAxis.plotBands](https://api.highcharts.com/highcharts/xAxis.plotBands).
+ *
+ * @return {Highcharts.PlotLineOrBand|undefined}
+ * The added plot band.
*/
- addPlotBand: function(options) {
+ addPlotBand: function (options) {
return this.addPlotBandOrLine(options, 'plotBands');
},
/**
* Add a plot line after render time.
- *
- * @param {AxisPlotLinesOptions} options
- * A configuration object for the plot line, as defined in {@link
- * https://api.highcharts.com/highcharts/xAxis.plotLines|
- * xAxis.plotLines}.
- * @return {Object}
- * The added plot line.
+ *
* @sample highcharts/members/axis-addplotline/
* Toggle the plot line from a button
+ *
+ * @function Highcharts.Axis#addPlotLine
+ *
+ * @param {Highcharts.AxisPlotLinesOptions} options
+ * A configuration object for the plot line, as defined in
+ * [xAxis.plotLines](https://api.highcharts.com/highcharts/xAxis.plotLines).
+ *
+ * @return {Highcharts.PlotLineOrBand|undefined}
+ * The added plot line.
*/
- addPlotLine: function(options) {
+ addPlotLine: function (options) {
return this.addPlotBandOrLine(options, 'plotLines');
},
@@ -16168,10 +21868,16 @@
* and addPlotLine internally.
*
* @private
- * @param options {AxisPlotLinesOptions|AxisPlotBandsOptions}
- * The plotBand or plotLine configuration object.
+ * @function Highcharts.Axis#addPlotBandOrLine
+ *
+ * @param {Highcharts.AxisPlotLinesOptions|Highcharts.AxisPlotBandsOptions} options
+ * The plotBand or plotLine configuration object.
+ *
+ * @param {"plotBands"|"plotLines"} [coll]
+ *
+ * @return {Highcharts.PlotLineOrBand|undefined}
*/
- addPlotBandOrLine: function(options, coll) {
+ addPlotBandOrLine: function (options, coll) {
var obj = new H.PlotLineOrBand(this, options).render(),
userOptions = this.userOptions;
@@ -16192,24 +21898,27 @@
* from `removePlotBand` and `removePlotLine`.
*
* @private
- * @param {String} id
+ * @function Highcharts.Axis#removePlotBandOrLine
+ *
+ * @param {string} id
*/
- removePlotBandOrLine: function(id) {
+ removePlotBandOrLine: function (id) {
var plotLinesAndBands = this.plotLinesAndBands,
options = this.options,
userOptions = this.userOptions,
i = plotLinesAndBands.length;
+
while (i--) {
if (plotLinesAndBands[i].id === id) {
plotLinesAndBands[i].destroy();
}
}
- each([
+ ([
options.plotLines || [],
userOptions.plotLines || [],
options.plotBands || [],
userOptions.plotBands || []
- ], function(arr) {
+ ]).forEach(function (arr) {
i = arr.length;
while (i--) {
if (arr[i].id === id) {
@@ -16221,98 +21930,233 @@
/**
* Remove a plot band by its id.
- *
- * @param {String} id
- * The plot band's `id` as given in the original configuration
- * object or in the `addPlotBand` option.
+ *
* @sample highcharts/members/axis-removeplotband/
* Remove plot band by id
* @sample highcharts/members/axis-addplotband/
* Toggle the plot band from a button
+ *
+ * @function Highcharts.Axis#removePlotBand
+ *
+ * @param {string} id
+ * The plot band's `id` as given in the original configuration
+ * object or in the `addPlotBand` option.
*/
- removePlotBand: function(id) {
+ removePlotBand: function (id) {
this.removePlotBandOrLine(id);
},
/**
* Remove a plot line by its id.
- * @param {String} id
- * The plot line's `id` as given in the original configuration
- * object or in the `addPlotLine` option.
+ *
* @sample highcharts/xaxis/plotlines-id/
* Remove plot line by id
* @sample highcharts/members/axis-addplotline/
* Toggle the plot line from a button
+ *
+ * @function Highcharts.Axis#removePlotLine
+ *
+ * @param {string} id
+ * The plot line's `id` as given in the original configuration
+ * object or in the `addPlotLine` option.
*/
- removePlotLine: function(id) {
+ removePlotLine: function (id) {
this.removePlotBandOrLine(id);
}
});
}(Highcharts, Axis));
- (function(H) {
+ (function (H) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
- var dateFormat = H.dateFormat,
- each = H.each,
+
+ /**
+ * A callback function to place the tooltip in a specific position.
+ *
+ * @callback Highcharts.TooltipPositionerCallbackFunction
+ *
+ * @param {number} labelWidth
+ * Width of the tooltip.
+ *
+ * @param {number} labelHeight
+ * Height of the tooltip.
+ *
+ * @param {Highcharts.TooltipPositionerPointObject} point
+ * Point information for positioning a tooltip.
+ *
+ * @return {Highcharts.PositionObject}
+ * New position for the tooltip.
+ */
+
+ /**
+ * Point information for positioning a tooltip.
+ *
+ * @interface Highcharts.TooltipPositionerPointObject
+ *//**
+ * If `tooltip.split` option is enabled and positioner is called for each of the
+ * boxes separately, this property indicates the call on the xAxis header, which
+ * is not a point itself.
+ * @name Highcharts.TooltipPositionerPointObject#isHeader
+ * @type {boolean}
+ *//**
+ * @name Highcharts.TooltipPositionerPointObject#negative
+ * @type {boolean}
+ *//**
+ * The reference point relative to the plot area. Add chart.plotLeft to get the
+ * full coordinates.
+ * @name Highcharts.TooltipPositionerPointObject#plotX
+ * @type {number}
+ *//**
+ * The reference point relative to the plot area. Add chart.plotTop to get the
+ * full coordinates.
+ * @name Highcharts.TooltipPositionerPointObject#plotY
+ * @type {number}
+ */
+
+
+
+ var doc = H.doc,
extend = H.extend,
format = H.format,
isNumber = H.isNumber,
- map = H.map,
merge = H.merge,
pick = H.pick,
splat = H.splat,
syncTimeout = H.syncTimeout,
timeUnits = H.timeUnits;
+
/**
- * The tooltip object
- * @param {Object} chart The chart instance
- * @param {Object} options Tooltip options
+ * Tooltip of a chart.
+ *
+ * @class
+ * @name Highcharts.Tooltip
+ *
+ * @param {Highcharts.Chart} chart
+ * The chart instance.
+ *
+ * @param {Highcharts.TooltipOptions} options
+ * Tooltip options.
*/
- H.Tooltip = function() {
+ H.Tooltip = function () {
this.init.apply(this, arguments);
};
H.Tooltip.prototype = {
- init: function(chart, options) {
+ /**
+ * @private
+ * @function Highcharts.Tooltip#init
+ *
+ * @param {Highcharts.Chart} chart
+ * The chart instance.
+ *
+ * @param {Highcharts.TooltipOptions} options
+ * Tooltip options.
+ */
+ init: function (chart, options) {
- // Save the chart and options
+ /**
+ * Chart of the tooltip.
+ *
+ * @readonly
+ * @name Highcharts.Tooltip#chart
+ * @type {Highcharts.Chart}
+ */
this.chart = chart;
+
+ /**
+ * Used tooltip options.
+ *
+ * @readonly
+ * @name Highcharts.Tooltip#options
+ * @type {Highcharts.TooltipOptions}
+ */
this.options = options;
- // List of crosshairs
+ /**
+ * List of crosshairs.
+ *
+ * @private
+ * @readonly
+ * @name Highcharts.Tooltip#crosshairs
+ * @type {Array<*>}
+ */
this.crosshairs = [];
- // Current values of x and y when animating
- this.now = {
- x: 0,
- y: 0
- };
+ /**
+ * Current values of x and y when animating.
+ *
+ * @private
+ * @readonly
+ * @name Highcharts.Tooltip#now
+ * @type {*}
+ */
+ this.now = { x: 0, y: 0 };
- // The tooltip is initially hidden
+ /**
+ * Tooltips are initially hidden.
+ *
+ * @readonly
+ * @name Highcharts.Tooltip#isHidden
+ * @type {boolean}
+ */
this.isHidden = true;
-
-
- // Public property for getting the shared state.
+ /**
+ * True, if the tooltip is splitted into one label per series, with the
+ * header close to the axis.
+ *
+ * @readonly
+ * @name Highcharts.Tooltip#split
+ * @type {boolean}
+ */
this.split = options.split && !chart.inverted;
+
+ /**
+ * When the tooltip is shared, the entire plot area will capture mouse
+ * movement or touch events.
+ *
+ * @readonly
+ * @name Highcharts.Tooltip#shared
+ * @type {boolean}
+ */
this.shared = options.shared || this.split;
+ /**
+ * Whether to allow the tooltip to render outside the chart's SVG
+ * element box. By default (false), the tooltip is rendered within the
+ * chart's SVG element, which results in the tooltip being aligned
+ * inside the chart area.
+ *
+ * @readonly
+ * @name Highcharts.Tooltip#outside
+ * @type {boolean}
+ *
+ * @todo
+ * Split tooltip does not support outside in the first iteration. Should
+ * not be too complicated to implement.
+ */
+ this.outside = options.outside && !this.split;
+
},
/**
* Destroy the single tooltips in a split tooltip.
* If the tooltip is active then it is not destroyed, unless forced to.
- * @param {boolean} force Force destroy all tooltips.
- * @return {undefined}
+ *
+ * @private
+ * @function Highcharts.Tooltip#cleanSplit
+ *
+ * @param {boolean} force
+ * Force destroy all tooltips.
*/
- cleanSplit: function(force) {
- each(this.chart.series, function(series) {
+ cleanSplit: function (force) {
+ this.chart.series.forEach(function (series) {
var tt = series && series.tt;
+
if (tt) {
if (!tt.isActive || force) {
series.tt = tt.destroy();
@@ -16323,24 +22167,95 @@
});
},
+ /**
+ * In styled mode, apply the default filter for the tooltip drop-shadow. It
+ * needs to have an id specific to the chart, otherwise there will be issues
+ * when one tooltip adopts the filter of a different chart, specifically one
+ * where the container is hidden.
+ *
+ * @private
+ * @function Highcharts.Tooltip#applyFilter
+ */
+ applyFilter: function () {
+
+ var chart = this.chart;
+ chart.renderer.definition({
+ tagName: 'filter',
+ id: 'drop-shadow-' + chart.index,
+ opacity: 0.5,
+ children: [{
+ tagName: 'feGaussianBlur',
+ 'in': 'SourceAlpha',
+ stdDeviation: 1
+ }, {
+ tagName: 'feOffset',
+ dx: 1,
+ dy: 1
+ }, {
+ tagName: 'feComponentTransfer',
+ children: [{
+ tagName: 'feFuncA',
+ type: 'linear',
+ slope: 0.3
+ }]
+ }, {
+ tagName: 'feMerge',
+ children: [{
+ tagName: 'feMergeNode'
+ }, {
+ tagName: 'feMergeNode',
+ 'in': 'SourceGraphic'
+ }]
+ }]
+ });
+ chart.renderer.definition({
+ tagName: 'style',
+ textContent: '.highcharts-tooltip-' + chart.index + '{' +
+ 'filter:url(#drop-shadow-' + chart.index + ')' +
+ '}'
+ });
+ },
/**
- * Create the Tooltip label element if it doesn't exist, then return the
- * label.
+ * Creates the Tooltip label element if it does not exist, then returns it.
+ *
+ * @function Highcharts.Tooltip#getLabel
+ *
+ * @return {Highcharts.SVGElement}
*/
- getLabel: function() {
+ getLabel: function () {
- var renderer = this.chart.renderer,
- options = this.options;
+ var tooltip = this,
+ renderer = this.chart.renderer,
+ styledMode = this.chart.styledMode,
+ options = this.options,
+ container,
+ set;
if (!this.label) {
+
+ if (this.outside) {
+ this.container = container = H.doc.createElement('div');
+ container.className = 'highcharts-tooltip-container';
+ H.css(container, {
+ position: 'absolute',
+ top: '1px',
+ pointerEvents: options.style && options.style.pointerEvents
+ });
+ H.doc.body.appendChild(container);
+
+ this.renderer = renderer = new H.Renderer(container, 0, 0);
+ }
+
+
// Create the label
if (this.split) {
this.label = renderer.g('tooltip');
} else {
- this.label = renderer.label(
+ this.label = renderer
+ .label(
'',
0,
0,
@@ -16356,19 +22271,38 @@
r: options.borderRadius
});
-
- this.label
- .attr({
- 'fill': options.backgroundColor,
- 'stroke-width': options.borderWidth
- })
- // #2301, #2657
- .css(options.style)
- .shadow(options.shadow);
-
+ if (!styledMode) {
+ this.label
+ .attr({
+ 'fill': options.backgroundColor,
+ 'stroke-width': options.borderWidth
+ })
+ // #2301, #2657
+ .css(options.style)
+ .shadow(options.shadow);
+ }
}
+ if (styledMode) {
+ // Apply the drop-shadow filter
+ this.applyFilter();
+ this.label.addClass('highcharts-tooltip-' + this.chart.index);
+ }
+ if (this.outside) {
+ set = {
+ x: this.label.xSetter,
+ y: this.label.ySetter
+ };
+ this.label.xSetter = function (value, key) {
+ set[key].call(this.label, tooltip.distance);
+ container.style.left = value + 'px';
+ };
+ this.label.ySetter = function (value, key) {
+ set[key].call(this.label, tooltip.distance);
+ container.style.top = value + 'px';
+ };
+ }
this.label
.attr({
@@ -16379,7 +22313,15 @@
return this.label;
},
- update: function(options) {
+ /**
+ * Updates the tooltip with the provided tooltip options.
+ *
+ * @function Highcharts.Tooltip#update
+ *
+ * @param {Highcharts.TooltipOptions} options
+ * The tooltip options to update.
+ */
+ update: function (options) {
this.destroy();
// Update user options (#6218)
merge(true, this.chart.options.tooltip.userOptions, options);
@@ -16387,9 +22329,11 @@
},
/**
- * Destroy the tooltip and its elements.
+ * Removes and destroys the tooltip and its elements.
+ *
+ * @function Highcharts.Tooltip#destroy
*/
- destroy: function() {
+ destroy: function () {
// Destroy and clear local variables
if (this.label) {
this.label = this.label.destroy();
@@ -16398,25 +22342,35 @@
this.cleanSplit(this.chart, true);
this.tt = this.tt.destroy();
}
- clearTimeout(this.hideTimer);
- clearTimeout(this.tooltipTimeout);
+ if (this.renderer) {
+ this.renderer = this.renderer.destroy();
+ H.discardElement(this.container);
+ }
+ H.clearTimeout(this.hideTimer);
+ H.clearTimeout(this.tooltipTimeout);
},
/**
- * Provide a soft movement for the tooltip
+ * Moves the tooltip with a soft animation to a new position.
*
- * @param {Number} x
- * @param {Number} y
- * @private
+ * @function Highcharts.Tooltip#move
+ *
+ * @param {number} x
+ *
+ * @param {number} y
+ *
+ * @param {number} anchorX
+ *
+ * @param {number} anchorY
*/
- move: function(x, y, anchorX, anchorY) {
+ move: function (x, y, anchorX, anchorY) {
var tooltip = this,
now = tooltip.now,
animate = tooltip.options.animation !== false &&
- !tooltip.isHidden &&
- // When we get close to the target position, abort animation and
- // land on the right place (#3056)
- (Math.abs(x - now.x) > 1 || Math.abs(y - now.y) > 1),
+ !tooltip.isHidden &&
+ // When we get close to the target position, abort animation and
+ // land on the right place (#3056)
+ (Math.abs(x - now.x) > 1 || Math.abs(y - now.y) > 1),
skipAnchor = tooltip.followPointer || tooltip.len > 1;
// Get intermediate values for animation
@@ -16424,9 +22378,11 @@
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,
+ undefined :
+ animate ? (2 * now.anchorX + anchorX) / 3 : anchorX,
anchorY: skipAnchor ?
- undefined : animate ? (now.anchorY + anchorY) / 2 : anchorY
+ undefined :
+ animate ? (now.anchorY + anchorY) / 2 : anchorY
});
// Move to the intermediate value
@@ -16437,10 +22393,10 @@
if (animate) {
// Never allow two timeouts
- clearTimeout(this.tooltipTimeout);
+ H.clearTimeout(this.tooltipTimeout);
// Set the fixed interval ticking for the smooth tooltip
- this.tooltipTimeout = setTimeout(function() {
+ 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) {
@@ -16452,15 +22408,23 @@
},
/**
- * Hide the tooltip
+ * Hides the tooltip with a fade out animation.
+ *
+ * @function Highcharts.Tooltip#hide
+ *
+ * @param {number} [delay]
+ * The fade out in milliseconds. If no value is provided the value
+ * of the tooltip.hideDelay option is used. A value of 0 disables
+ * the fade out animation.
*/
- hide: function(delay) {
+ hide: function (delay) {
var tooltip = this;
+
// disallow duplicate timers (#1728, #1766)
- clearTimeout(this.hideTimer);
+ H.clearTimeout(this.hideTimer);
delay = pick(delay, this.options.hideDelay, 500);
if (!this.isHidden) {
- this.hideTimer = syncTimeout(function() {
+ this.hideTimer = syncTimeout(function () {
tooltip.getLabel()[delay ? 'fadeOut' : 'hide']();
tooltip.isHidden = true;
}, delay);
@@ -16470,10 +22434,18 @@
/**
* Extendable method to get the anchor position of the tooltip
* from a point or set of points
+ *
+ * @private
+ * @function Highcharts.Tooltip#getAnchor
+ *
+ * @param {Array} points
+ *
+ * @param {global.Event} [mouseEvent]
*/
- getAnchor: function(points, mouseEvent) {
+ getAnchor: function (points, mouseEvent) {
var ret,
chart = this.chart,
+ pointer = chart.pointer,
inverted = chart.inverted,
plotTop = chart.plotTop,
plotLeft = chart.plotLeft,
@@ -16484,22 +22456,21 @@
points = splat(points);
- // Pie uses a special tooltipPos
- ret = points[0].tooltipPos;
-
// When tooltip follows mouse, relate the position to the mouse
if (this.followPointer && mouseEvent) {
if (mouseEvent.chartX === undefined) {
- mouseEvent = chart.pointer.normalize(mouseEvent);
+ mouseEvent = pointer.normalize(mouseEvent);
}
ret = [
mouseEvent.chartX - chart.plotLeft,
mouseEvent.chartY - plotTop
];
- }
+ // Pie uses a special tooltipPos
+ } else if (points[0].tooltipPos) {
+ ret = points[0].tooltipPos;
// When shared, use the average position
- if (!ret) {
- each(points, function(point) {
+ } else {
+ points.forEach(function (point) {
yAxis = point.series.yAxis;
xAxis = point.series.xAxis;
plotX += point.plotX +
@@ -16507,8 +22478,8 @@
plotY +=
(
point.plotLow ?
- (point.plotLow + point.plotHigh) / 2 :
- point.plotY
+ (point.plotLow + point.plotHigh) / 2 :
+ point.plotY
) +
(!inverted && yAxis ? yAxis.top - plotTop : 0); // #1151
});
@@ -16519,20 +22490,31 @@
ret = [
inverted ? chart.plotWidth - plotY : plotX,
this.shared && !inverted && points.length > 1 && mouseEvent ?
- // place shared tooltip next to the mouse (#424)
- mouseEvent.chartY - plotTop :
- inverted ? chart.plotHeight - plotX : plotY
+ // place shared tooltip next to the mouse (#424)
+ mouseEvent.chartY - plotTop :
+ inverted ? chart.plotHeight - plotX : plotY
];
}
- return map(ret, Math.round);
+ return ret.map(Math.round);
},
/**
* Place the tooltip in a chart without spilling over
* and not covering the point it self.
+ *
+ * @private
+ * @function Highcharts.Tooltip#getPosition
+ *
+ * @param {number} boxWidth
+ *
+ * @param {number} boxHeight
+ *
+ * @param {Highcharts.Point} point
+ *
+ * @return {*}
*/
- getPosition: function(boxWidth, boxHeight, point) {
+ getPosition: function (boxWidth, boxHeight, point) {
var chart = this.chart,
distance = this.distance,
@@ -16540,25 +22522,53 @@
// Don't use h if chart isn't inverted (#7242)
h = (chart.inverted && point.h) || 0, // #4117
swapped,
- first = ['y', chart.chartHeight, boxHeight,
- point.plotY + chart.plotTop, chart.plotTop,
- chart.plotTop + chart.plotHeight
+ outside = this.outside,
+ outerWidth = outside ?
+ // substract distance to prevent scrollbars
+ doc.documentElement.clientWidth - 2 * distance :
+ chart.chartWidth,
+ outerHeight = outside ?
+ Math.max(
+ doc.body.scrollHeight,
+ doc.documentElement.scrollHeight,
+ doc.body.offsetHeight,
+ doc.documentElement.offsetHeight,
+ doc.documentElement.clientHeight
+ ) :
+ chart.chartHeight,
+ chartPosition = chart.pointer.chartPosition,
+ first = [
+ 'y',
+ outerHeight,
+ boxHeight,
+ (outside ? chartPosition.top - distance : 0) +
+ point.plotY + chart.plotTop,
+ outside ? 0 : chart.plotTop,
+ outside ? outerHeight : chart.plotTop + chart.plotHeight
],
- second = ['x', chart.chartWidth, boxWidth,
- point.plotX + chart.plotLeft, chart.plotLeft,
- chart.plotLeft + chart.plotWidth
+ second = [
+ 'x',
+ outerWidth,
+ boxWidth,
+ (outside ? chartPosition.left - distance : 0) +
+ point.plotX + chart.plotLeft,
+ outside ? 0 : chart.plotLeft,
+ outside ? outerWidth : chart.plotLeft + chart.plotWidth
],
// The far side is right or bottom
preferFarSide = !this.followPointer && pick(
- point.ttBelow, !chart.inverted === !!point.negative
+ point.ttBelow,
+ !chart.inverted === !!point.negative
), // #4984
- /**
+ /*
* Handle the preferred dimension. When the preferred dimension is
* tooltip on top or bottom of the point, it will look for space
* there.
+ *
+ * @private
*/
- firstDimension = function(
+ firstDimension = function (
dim,
outerSize,
innerSize,
@@ -16584,47 +22594,52 @@
ret[dim] = Math.max(
min,
alignedRight + h + innerSize > outerSize ?
- alignedRight :
- alignedRight + h
+ alignedRight :
+ alignedRight + h
);
} else {
return false;
}
},
- /**
+
+ /*
* Handle the secondary dimension. If the preferred dimension is
* tooltip on top or bottom of the point, the second dimension is to
* align the tooltip above the point, trying to align center but
* allowing left or right align within the chart box.
+ *
+ * @private
*/
- secondDimension = function(dim, outerSize, innerSize, point) {
+ secondDimension = function (dim, outerSize, innerSize, point) {
var retVal;
// Too close to the edge, return false and swap dimensions
if (point < distance || point > outerSize - distance) {
retVal = false;
- // Align left/top
+ // Align left/top
} else if (point < innerSize / 2) {
ret[dim] = 1;
- // Align right/bottom
+ // Align right/bottom
} else if (point > outerSize - innerSize / 2) {
ret[dim] = outerSize - innerSize - 2;
- // Align center
+ // Align center
} else {
ret[dim] = point - innerSize / 2;
}
return retVal;
},
- /**
+
+ /*
* Swap the dimensions
*/
- swap = function(count) {
+ swap = function (count) {
var temp = first;
+
first = second;
second = temp;
swapped = count;
},
- run = function() {
+ run = function () {
if (firstDimension.apply(0, first) !== false) {
if (
secondDimension.apply(0, second) === false &&
@@ -16655,9 +22670,14 @@
* In case no user defined formatter is given, this will be used. Note that
* the context here is an object holding point, series, x, y etc.
*
- * @returns {String|Array}
+ * @private
+ * @function Highcharts.Tooltip#defaultFormatter
+ *
+ * @param {Highcharts.Tooltip} tooltip
+ *
+ * @return {Array}
*/
- defaultFormatter: function(tooltip) {
+ defaultFormatter: function (tooltip) {
var items = this.points || splat(this),
s;
@@ -16675,9 +22695,17 @@
/**
* Refresh the tooltip's text and position.
- * @param {Object|Array} pointOrPoints Rither a point or an array of points
+ *
+ * @function Highcharts.Tooltip#refresh
+ *
+ * @param {Highcharts.Point|Array} pointOrPoints
+ * Either a point or an array of points.
+ *
+ * @param {global.Event} [mouseEvent]
+ * Mouse event, that is responsible for the refresh and should be
+ * used for the tooltip update.
*/
- refresh: function(pointOrPoints, mouseEvent) {
+ refresh: function (pointOrPoints, mouseEvent) {
var tooltip = this,
label,
options = tooltip.options,
@@ -16690,13 +22718,14 @@
pointConfig = [],
formatter = options.formatter || tooltip.defaultFormatter,
shared = tooltip.shared,
- currentSeries;
+ currentSeries,
+ styledMode = this.chart.styledMode;
if (!options.enabled) {
return;
}
- clearTimeout(this.hideTimer);
+ H.clearTimeout(this.hideTimer);
// get the reference point coordinates (pie charts use tooltipPos)
tooltip.followPointer = splat(point)[0].series.tooltipOptions
@@ -16707,7 +22736,7 @@
// shared tooltip, array is sent over
if (shared && !(point.series && point.series.noSharedTooltip)) {
- each(point, function(item) {
+ point.forEach(function (item) {
item.setState('hover');
pointConfig.push(item.getLabelConfig());
@@ -16720,7 +22749,7 @@
textConfig.points = pointConfig;
point = point[0];
- // single point tooltip
+ // single point tooltip
} else {
textConfig = point.getLabelConfig();
}
@@ -16751,16 +22780,12 @@
} else {
// Prevent the tooltip from flowing over the chart box (#6659)
-
- if (!options.style.width) {
-
+ if (!options.style.width || styledMode) {
label.css({
width: this.chart.spacingBox.width
});
-
}
-
label.attr({
text: text && text.join ? text.join('') : text
});
@@ -16772,16 +22797,16 @@
pick(point.colorIndex, currentSeries.colorIndex)
);
-
- label.attr({
- stroke: (
- options.borderColor ||
- point.color ||
- currentSeries.color ||
- '#666666'
- )
- });
-
+ if (!styledMode) {
+ label.attr({
+ stroke: (
+ options.borderColor ||
+ point.color ||
+ currentSeries.color ||
+ '#666666'
+ )
+ });
+ }
tooltip.updatePosition({
plotX: x,
@@ -16798,10 +22823,17 @@
/**
* Render the split tooltip. Loops over each point's text and adds
- * a label next to the point, then uses the distribute function to
+ * a label next to the point, then uses the distribute function to
* find best non-overlapping positions.
+ *
+ * @private
+ * @function Highcharts.Tooltip#renderSplit
+ *
+ * @param {Array} labels
+ *
+ * @param {Array} points
*/
- renderSplit: function(labels, points) {
+ renderSplit: function (labels, points) {
var tooltip = this,
boxes = [],
chart = this.chart,
@@ -16809,21 +22841,24 @@
rightAligned = true,
options = this.options,
headerHeight = 0,
- tooltipLabel = this.getLabel();
+ headerTop,
+ tooltipLabel = this.getLabel(),
+ distributionBoxTop = chart.plotTop;
// Graceful degradation for legacy formatters
if (H.isString(labels)) {
labels = [false, labels];
}
// Create the individual labels for header and points, ignore footer
- each(labels.slice(0, points.length + 1), function(str, i) {
- if (str !== false) {
+ labels.slice(0, points.length + 1).forEach(function (str, i) {
+ if (str !== false && str !== '') {
var point = points[i - 1] ||
- // Item 0 is the header. Instead of this, we could also
- // use the crosshair label
{
+ // Item 0 is the header. Instead of this, we could also
+ // use the crosshair label
isHeader: true,
- plotX: points[0].plotX
+ plotX: points[0].plotX,
+ plotY: chart.plotHeight
},
owner = point.series || tooltip,
tt = owner.tt,
@@ -16836,34 +22871,43 @@
target,
x,
bBox,
- boxWidth;
+ boxWidth,
+ attribs;
// Store the tooltip referance on the series
if (!tt) {
- owner.tt = tt = ren.label(
+
+ attribs = {
+ padding: options.padding,
+ r: options.borderRadius
+ };
+
+ if (!chart.styledMode) {
+ attribs.fill = options.backgroundColor;
+ attribs.stroke = (
+ options.borderColor ||
+ point.color ||
+ series.color ||
+ '#333333'
+ );
+ attribs['stroke-width'] = options.borderWidth;
+ }
+
+ owner.tt = tt = ren
+ .label(
null,
null,
null,
- 'callout',
+ (
+ point.isHeader ? options.headerShape :
+ options.shape
+ ) || 'callout',
null,
null,
options.useHTML
)
.addClass('highcharts-tooltip-box ' + colorClass)
- .attr({
- 'padding': options.padding,
- 'r': options.borderRadius,
-
- 'fill': options.backgroundColor,
- 'stroke': (
- options.borderColor ||
- point.color ||
- series.color ||
- '#333333'
- ),
- 'stroke-width': options.borderWidth
-
- })
+ .attr(attribs)
.add(tooltipLabel);
}
@@ -16871,10 +22915,10 @@
tt.attr({
text: str
});
-
- tt.css(options.style)
- .shadow(options.shadow);
-
+ if (!chart.styledMode) {
+ tt.css(options.style)
+ .shadow(options.shadow);
+ }
// Get X position now, so we can move all to the other side in
// case of overflow
@@ -16882,12 +22926,23 @@
boxWidth = bBox.width + tt.strokeWidth();
if (point.isHeader) {
headerHeight = bBox.height;
+ if (chart.xAxis[0].opposite) {
+ headerTop = true;
+ distributionBoxTop -= headerHeight;
+ }
x = Math.max(
0, // No left overflow
Math.min(
point.plotX + chart.plotLeft - boxWidth / 2,
// No right overflow (#5794)
- chart.chartWidth - boxWidth
+ chart.chartWidth +
+ (
+ // Scrollable plot area
+ chart.scrollablePixels ?
+ chart.scrollablePixels - chart.marginRight :
+ 0
+ ) -
+ boxWidth
)
);
} else {
@@ -16904,10 +22959,15 @@
// Prepare for distribution
target = (point.series && point.series.yAxis &&
point.series.yAxis.pos) + (point.plotY || 0);
- target -= chart.plotTop;
+ target -= distributionBoxTop;
+
+ if (point.isHeader) {
+ target = headerTop ?
+ -headerHeight :
+ chart.plotHeight + headerHeight;
+ }
boxes.push({
- target: point.isHeader ?
- chart.plotHeight + headerHeight : target,
+ target: target,
rank: point.isHeader ? 1 : 0,
size: owner.tt.getBBox().height + 1,
point: point,
@@ -16920,31 +22980,54 @@
// Clean previous run (for missing points)
this.cleanSplit();
+ if (options.positioner) {
+ boxes.forEach(function (box) {
+ var boxPosition = options.positioner.call(
+ tooltip,
+ box.tt.getBBox().width,
+ box.size,
+ box.point
+ );
+
+ box.x = boxPosition.x;
+ box.align = 0; // 0-align to the top, 1-align to the bottom
+ box.target = boxPosition.y;
+ box.rank = pick(boxPosition.rank, box.rank);
+ });
+ }
+
// Distribute and put in place
H.distribute(boxes, chart.plotHeight + headerHeight);
- each(boxes, function(box) {
+ boxes.forEach(function (box) {
var point = box.point,
series = point.series;
// Put the label in place
box.tt.attr({
visibility: box.pos === undefined ? 'hidden' : 'inherit',
- x: (rightAligned || point.isHeader ?
+ x: (rightAligned || point.isHeader || options.positioner ?
box.x :
- point.plotX + chart.plotLeft + pick(options.distance, 16)),
- y: box.pos + chart.plotTop,
+ point.plotX + chart.plotLeft + tooltip.distance),
+ y: box.pos + distributionBoxTop,
anchorX: point.isHeader ?
- point.plotX + chart.plotLeft : point.plotX + series.xAxis.pos,
+ point.plotX + chart.plotLeft :
+ point.plotX + series.xAxis.pos,
anchorY: point.isHeader ?
- box.pos + chart.plotTop - 15 : point.plotY + series.yAxis.pos
+ chart.plotTop + chart.plotHeight / 2 :
+ point.plotY + series.yAxis.pos
});
});
},
/**
* Find the new position and perform the move
+ *
+ * @private
+ * @function Highcharts.Tooltip#updatePosition
+ *
+ * @param {Highcharts.Point} point
*/
- updatePosition: function(point) {
+ updatePosition: function (point) {
var chart = this.chart,
label = this.getLabel(),
pos = (this.options.positioner || this.getPosition).call(
@@ -16952,28 +23035,57 @@
label.width,
label.height,
point
+ ),
+ anchorX = point.plotX + chart.plotLeft,
+ anchorY = point.plotY + chart.plotTop,
+ pad;
+
+ // Set the renderer size dynamically to prevent document size to change
+ if (this.outside) {
+ pad = (this.options.borderWidth || 0) + 2 * this.distance;
+ this.renderer.setSize(
+ label.width + pad,
+ label.height + pad,
+ false
);
+ anchorX += chart.pointer.chartPosition.left - pos.x;
+ anchorY += chart.pointer.chartPosition.top - pos.y;
+ }
// do the move
this.move(
Math.round(pos.x),
- Math.round(pos.y || 0), // can be undefined (#3977)
- point.plotX + chart.plotLeft,
- point.plotY + chart.plotTop
+ Math.round(pos.y || 0), // can be undefined (#3977)
+ anchorX,
+ anchorY
);
},
/**
* Get the optimal date format for a point, based on a range.
- * @param {number} range - The time range
- * @param {number|Date} date - The date of the point in question
- * @param {number} startOfWeek - An integer representing the first day of
- * the week, where 0 is Sunday
- * @param {Object} dateTimeLabelFormats - A map of time units to formats
- * @return {string} - the optimal date format for a point
- */
- getDateFormat: function(range, date, startOfWeek, dateTimeLabelFormats) {
- var dateStr = dateFormat('%m-%d %H:%M:%S.%L', date),
+ *
+ * @private
+ * @function Highcharts.Tooltip#getDateFormat
+ *
+ * @param {number} range
+ * The time range
+ *
+ * @param {number|Date} date
+ * The date of the point in question
+ *
+ * @param {number} startOfWeek
+ * An integer representing the first day of the week, where 0 is
+ * Sunday.
+ *
+ * @param {Highcharts.Dictionary} dateTimeLabelFormats
+ * A map of time units to formats.
+ *
+ * @return {string}
+ * The optimal date format for a point.
+ */
+ getDateFormat: function (range, date, startOfWeek, dateTimeLabelFormats) {
+ var time = this.chart.time,
+ dateStr = time.dateFormat('%m-%d %H:%M:%S.%L', date),
format,
n,
blank = '01-01 00:00:00.000',
@@ -16985,13 +23097,14 @@
day: 3
},
lastN = 'millisecond'; // for sub-millisecond data, #4223
+
for (n in timeUnits) {
// If the range is exactly one week and we're looking at a
// Sunday/Monday, go for the week format
if (
range === timeUnits.week &&
- +dateFormat('%w', date) === startOfWeek &&
+ +time.dateFormat('%w', date) === startOfWeek &&
dateStr.substr(6) === blank.substr(6)
) {
n = 'week';
@@ -17021,7 +23134,7 @@
}
if (n) {
- format = dateTimeLabelFormats[n];
+ format = time.resolveDTLFormat(dateTimeLabelFormats[n]).main;
}
return format;
@@ -17029,8 +23142,19 @@
/**
* Get the best X date format based on the closest point range on the axis.
+ *
+ * @private
+ * @function Highcharts.Tooltip#getXDateFormat
+ *
+ * @param {Highcharts.Point} point
+ *
+ * @param {Highcharts.TooltipOptions} options
+ *
+ * @param {Highcharts.Axis} xAxis
+ *
+ * @return {string}
*/
- getXDateFormat: function(point, options, xAxis) {
+ getXDateFormat: function (point, options, xAxis) {
var xDateFormat,
dateTimeLabelFormats = options.dateTimeLabelFormats,
closestPointRange = xAxis && xAxis.closestPointRange;
@@ -17052,8 +23176,17 @@
/**
* Format the footer/header of the tooltip
* #3397: abstraction to enable formatting of footer and header
+ *
+ * @private
+ * @function Highcharts.Tooltip#tooltipFooterHeaderFormatter
+ *
+ * @param {*} labelConfig
+ *
+ * @param {boolean} isFooter
+ *
+ * @return {string}
*/
- tooltipFooterHeaderFormatter: function(labelConfig, isFooter) {
+ tooltipFooterHeaderFormatter: function (labelConfig, isFooter) {
var footOrHead = isFooter ? 'footer' : 'header',
series = labelConfig.series,
tooltipOptions = series.tooltipOptions,
@@ -17064,45 +23197,64 @@
xAxis.options.type === 'datetime' &&
isNumber(labelConfig.key)
),
- formatString = tooltipOptions[footOrHead + 'Format'];
-
- // Guess the best date format based on the closest point distance (#568,
- // #3418)
- if (isDateTime && !xDateFormat) {
- xDateFormat = this.getXDateFormat(
- labelConfig,
- tooltipOptions,
- xAxis
- );
- }
+ formatString = tooltipOptions[footOrHead + 'Format'],
+ evt = { isFooter: isFooter, labelConfig: labelConfig };
+
+ H.fireEvent(this, 'headerFormatter', evt, function (e) {
+
+ // Guess the best date format based on the closest point distance
+ // (#568, #3418)
+ if (isDateTime && !xDateFormat) {
+ xDateFormat = this.getXDateFormat(
+ labelConfig,
+ tooltipOptions,
+ xAxis
+ );
+ }
- // Insert the footer date format if any
- if (isDateTime && xDateFormat) {
- each(
- (labelConfig.point && labelConfig.point.tooltipDateKeys) || ['key'],
- function(key) {
- formatString = formatString.replace(
- '{point.' + key + '}',
- '{point.' + key + ':' + xDateFormat + '}'
- );
- }
- );
- }
+ // Insert the footer date format if any
+ if (isDateTime && xDateFormat) {
+ ((labelConfig.point && labelConfig.point.tooltipDateKeys) ||
+ ['key']).forEach(
+ function (key) {
+ formatString = formatString.replace(
+ '{point.' + key + '}',
+ '{point.' + key + ':' + xDateFormat + '}'
+ );
+ }
+ );
+ }
+
+ // Replace default header style with class name
+ if (series.chart.styledMode) {
+ formatString = this.styledModeFormat(formatString);
+ }
+
+ e.text = format(formatString, {
+ point: labelConfig,
+ series: series
+ }, this.chart.time);
- return format(formatString, {
- point: labelConfig,
- series: series
});
+ return evt.text;
},
/**
* Build the body (lines) of the tooltip by iterating over the items and
* returning one entry for each item, abstracting this functionality allows
* to easily overwrite and extend it.
+ *
+ * @private
+ * @function Highcharts.Tooltip#bodyFormatter
+ *
+ * @param {Array} items
+ *
+ * @return {string}
*/
- bodyFormatter: function(items) {
- return map(items, function(item) {
+ bodyFormatter: function (items) {
+ return items.map(function (item) {
var tooltipOptions = item.series.tooltipOptions;
+
return (
tooltipOptions[
(item.point.formatPrefix || 'point') + 'Formatter'
@@ -17110,20 +23262,127 @@
item.point.tooltipFormatter
).call(
item.point,
- tooltipOptions[(item.point.formatPrefix || 'point') + 'Format']
+ tooltipOptions[
+ (item.point.formatPrefix || 'point') + 'Format'
+ ] || ''
);
});
+ },
+
+ styledModeFormat: function (formatString) {
+ return formatString
+ .replace(
+ 'style="font-size: 10px"',
+ 'class="highcharts-header"'
+ )
+ .replace(
+ /style="color:{(point|series)\.color}"/g,
+ 'class="highcharts-color-{$1.colorIndex}"'
+ );
}
};
}(Highcharts));
- (function(Highcharts) {
+ (function (Highcharts) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+ /**
+ * One position in relation to an axis.
+ *
+ * @interface Highcharts.PointerAxisCoordinateObject
+ *//**
+ * Related axis.
+ *
+ * @name Highcharts.PointerAxisCoordinateObject#axis
+ * @type {Highcharts.Axis}
+ *//**
+ * Axis value.
+ *
+ * @name Highcharts.PointerAxisCoordinateObject#value
+ * @type {number}
+ */
+
+ /**
+ * Positions in terms of axis values.
+ *
+ * @interface Highcharts.PointerAxisCoordinatesObject
+ *//**
+ * Positions on the x-axis.
+ * @name Highcharts.PointerAxisCoordinatesObject#xAxis
+ * @type {Array}
+ *//**
+ * Positions on the y-axis.
+ * @name Highcharts.PointerAxisCoordinatesObject#yAxis
+ * @type {Array}
+ */
+
+ /**
+ * Pointer coordinates.
+ *
+ * @interface Highcharts.PointerCoordinatesObject
+ *//**
+ * @name Highcharts.PointerCoordinatesObject#chartX
+ * @type {number}
+ *//**
+ * @name Highcharts.PointerCoordinatesObject#chartY
+ * @type {number}
+ */
+
+ /**
+ * A native browser mouse or touch event, extended with position information
+ * relative to the {@link Chart.container}.
+ *
+ * @interface Highcharts.PointerEventObject
+ * @extends global.PointerEvent
+ *//**
+ * The X coordinate of the pointer interaction relative to the chart.
+ *
+ * @name Highcharts.PointerEventObject#chartX
+ * @type {number}
+ *//**
+ * The Y coordinate of the pointer interaction relative to the chart.
+ *
+ * @name Highcharts.PointerEventObject#chartY
+ * @type {number}
+ */
+
+ /**
+ * Axis-specific data of a selection.
+ *
+ * @interface Highcharts.SelectDataObject
+ *//**
+ * @name Highcharts.SelectDataObject#axis
+ * @type {Highcharts.Axis}
+ *//**
+ * @name Highcharts.SelectDataObject#min
+ * @type {number}
+ *//**
+ * @name Highcharts.SelectDataObject#max
+ * @type {number}
+ */
+
+ /**
+ * Object for select events.
+ *
+ * @interface Highcharts.SelectEventObject
+ *//**
+ * @name Highcharts.SelectEventObject#originalEvent
+ * @type {global.Event}
+ *//**
+ * @name Highcharts.SelectEventObject#xAxis
+ * @type {Array}
+ *//**
+ * @name Highcharts.SelectEventObject#yAxis
+ * @type {Array}
+ */
+
+
+
var H = Highcharts,
addEvent = H.addEvent,
attr = H.attr,
@@ -17131,10 +23390,10 @@
color = H.color,
css = H.css,
defined = H.defined,
- each = H.each,
extend = H.extend,
find = H.find,
fireEvent = H.fireEvent,
+ isNumber = H.isNumber,
isObject = H.isObject,
offset = H.offset,
pick = H.pick,
@@ -17147,13 +23406,16 @@
* property.
*
* @class
- * @param {Chart} chart
- * The Chart instance.
- * @param {Options} options
- * The root options object. The pointer uses options from the chart and
- * tooltip structures.
+ * @name Highcharts.Pointer
+ *
+ * @param {Highcharts.Chart} chart
+ * The Chart instance.
+ *
+ * @param {Highcharts.Options} options
+ * The root options object. The pointer uses options from the chart and
+ * tooltip structures.
*/
- Highcharts.Pointer = function(chart, options) {
+ Highcharts.Pointer = function (chart, options) {
this.init(chart, options);
};
@@ -17162,20 +23424,35 @@
* Initialize the Pointer.
*
* @private
+ * @function Highcharts.Pointer#init
+ *
+ * @param {Highcharts.Chart} chart
+ * The Chart instance.
+ *
+ * @param {Highcharts.Options} options
+ * The root options object. The pointer uses options from the chart
+ * and tooltip structures.
*/
- init: function(chart, options) {
+ init: function (chart, options) {
// Store references
this.options = options;
this.chart = chart;
// Do we need to handle click on a touch device?
- this.runChartClick = options.chart.events && !!options.chart.events.click;
+ this.runChartClick =
+ options.chart.events && !!options.chart.events.click;
this.pinchDown = [];
this.lastValidTouch = {};
if (Tooltip) {
+ /**
+ * Tooltip object for points of series.
+ *
+ * @name Highcharts.Chart#tooltip
+ * @type {Highcharts.Tooltip}
+ */
chart.tooltip = new Tooltip(chart, options.tooltip);
this.followTouchMove = pick(options.tooltip.followTouchMove, true);
}
@@ -17188,8 +23465,12 @@
* down events.
*
* @private
+ * @function Highcharts.Pointer#zoomOption
+ *
+ * @param {global.Event} e
+ * Event object.
*/
- zoomOption: function(e) {
+ zoomOption: function (e) {
var chart = this.chart,
options = chart.options.chart,
zoomType = options.zoomType || '',
@@ -17209,34 +23490,26 @@
this.hasZoom = zoomX || zoomY;
},
- /**
- * @typedef {Object} PointerEvent
- * A native browser mouse or touch event, extended with position
- * information relative to the {@link Chart.container}.
- * @property {Number} chartX
- * The X coordinate of the pointer interaction relative to the
- * chart.
- * @property {Number} chartY
- * The Y coordinate of the pointer interaction relative to the
- * chart.
- *
- */
/**
* Takes a browser event object and extends it with custom Highcharts
- * properties `chartX` and `chartY` in order to work on the internal
+ * properties `chartX` and `chartY` in order to work on the internal
* coordinate system.
- *
- * @param {Object} e
- * The event object in standard browsers.
*
- * @return {PointerEvent}
+ * @function Highcharts.Pointer#normalize
+ *
+ * @param {global.Event} e
+ * Event object in standard browsers.
+ *
+ * @return {Highcharts.PointerEventObject}
* A browser event with extended properties `chartX` and `chartY`.
*/
- normalize: function(e, chartPosition) {
+ normalize: function (e, chartPosition) {
var ePos;
// iOS (#2757)
- ePos = e.touches ? (e.touches.length ? e.touches.item(0) : e.changedTouches[0]) : e;
+ ePos = e.touches ?
+ (e.touches.length ? e.touches.item(0) : e.changedTouches[0]) :
+ e;
// Get mouse position
if (!chartPosition) {
@@ -17252,74 +23525,87 @@
/**
* Get the click position in terms of axis values.
*
- * @param {PointerEvent} e
- * A pointer event, extended with `chartX` and `chartY`
- * properties.
+ * @function Highcharts.Pointer#getCoordinates
+ *
+ * @param {Highcharts.PointerEventObject} e
+ * Pointer event, extended with `chartX` and `chartY` properties.
+ *
+ * @return {Highcharts.PointerAxisCoordinatesObject}
*/
- getCoordinates: function(e) {
+ getCoordinates: function (e) {
+
var coordinates = {
xAxis: [],
yAxis: []
};
- each(this.chart.axes, function(axis) {
+ this.chart.axes.forEach(function (axis) {
coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({
axis: axis,
value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY'])
});
});
+
return coordinates;
},
+
/**
* Finds the closest point to a set of coordinates, using the k-d-tree
* algorithm.
*
- * @param {Array.} series
- * All the series to search in.
- * @param {boolean} shared
- * Whether it is a shared tooltip or not.
- * @param {object} coordinates
- * Chart coordinates of the pointer.
- * @param {number} coordinates.chartX
- * @param {number} coordinates.chartY
+ * @function Highcharts.Pointer#findNearestKDPoints
*
- * @return {Point|undefined} The point closest to given coordinates.
+ * @param {Array} series
+ * All the series to search in.
+ *
+ * @param {boolean} shared
+ * Whether it is a shared tooltip or not.
+ *
+ * @param {Highcharts.PointerEventObject} e
+ * The pointer event object, containing chart coordinates of the
+ * pointer.
+ *
+ * @return {Point|undefined}
+ * The point closest to given coordinates.
*/
- findNearestKDPoint: function(series, shared, coordinates) {
+ findNearestKDPoint: function (series, shared, e) {
var closest,
- sort = function(p1, p2) {
+ sort = function (p1, p2) {
var isCloserX = p1.distX - p2.distX,
isCloser = p1.dist - p2.dist,
isAbove =
- (p2.series.group && p2.series.group.zIndex) -
- (p1.series.group && p1.series.group.zIndex),
+ (p2.series.group && p2.series.group.zIndex) -
+ (p1.series.group && p1.series.group.zIndex),
result;
// We have two points which are not in the same place on xAxis
// and shared tooltip:
if (isCloserX !== 0 && shared) { // #5721
result = isCloserX;
- // Points are not exactly in the same place on x/yAxis:
+ // Points are not exactly in the same place on x/yAxis:
} else if (isCloser !== 0) {
result = isCloser;
- // The same xAxis and yAxis position, sort by z-index:
+ // The same xAxis and yAxis position, sort by z-index:
} else if (isAbove !== 0) {
result = isAbove;
- // The same zIndex, sort by array index:
+ // The same zIndex, sort by array index:
} else {
result = p1.series.index > p2.series.index ? -1 : 1;
}
return result;
};
- each(series, function(s) {
+
+ series.forEach(function (s) {
var noSharedTooltip = s.noSharedTooltip && shared,
- compareX = (!noSharedTooltip &&
+ compareX = (
+ !noSharedTooltip &&
s.options.findNearestPointBy.indexOf('y') < 0
),
point = s.searchPoint(
- coordinates,
+ e,
compareX
);
+
if (
// Check that we actually found a point on the series.
isObject(point, true) &&
@@ -17331,7 +23617,16 @@
});
return closest;
},
- getPointFromEvent: function(e) {
+
+ /**
+ * @private
+ * @function Highcharts.Pointer#getPointFromEvent
+ *
+ * @param {global.Event} e
+ *
+ * @return {Highcharts.Point|undefined}
+ */
+ getPointFromEvent: function (e) {
var target = e.target,
point;
@@ -17342,11 +23637,22 @@
return point;
},
- getChartCoordinatesFromPoint: function(point, inverted) {
+ /**
+ * @private
+ * @function Highcharts.Pointer#getChartCoordinatesFromPoint
+ *
+ * @param {Highcharts.Point} point
+ *
+ * @param {boolean} inverted
+ *
+ * @return {Highcharts.PointerCoordinatesObject}
+ */
+ getChartCoordinatesFromPoint: function (point, inverted) {
var series = point.series,
xAxis = series.xAxis,
yAxis = series.yAxis,
- plotX = pick(point.clientX, point.plotX);
+ plotX = pick(point.clientX, point.plotX),
+ shapeArgs = point.shapeArgs;
if (xAxis && yAxis) {
return inverted ? {
@@ -17357,47 +23663,58 @@
chartY: point.plotY + yAxis.pos
};
}
+
+ if (shapeArgs && shapeArgs.x && shapeArgs.y) {
+ // E.g. pies do not have axes
+ return {
+ chartX: shapeArgs.x,
+ chartY: shapeArgs.y
+ };
+ }
},
/**
* Calculates what is the current hovered point/points and series.
*
* @private
+ * @function Highcharts.Pointer#getHoverData
*
- * @param {undefined|Point} existingHoverPoint
- * The point currrently beeing hovered.
- * @param {undefined|Series} existingHoverSeries
- * The series currently beeing hovered.
- * @param {Array.} series
- * All the series in the chart.
- * @param {boolean} isDirectTouch
- * Is the pointer directly hovering the point.
- * @param {boolean} shared
- * Whether it is a shared tooltip or not.
- * @param {object} coordinates
- * Chart coordinates of the pointer.
- * @param {number} coordinates.chartX
- * @param {number} coordinates.chartY
- *
- * @return {object}
- * Object containing resulting hover data.
+ * @param {Highcharts.Point|undefined} existingHoverPoint
+ * The point currrently beeing hovered.
+ *
+ * @param {Highcharts.Series|undefined} existingHoverSeries
+ * The series currently beeing hovered.
+ *
+ * @param {Array} series
+ * All the series in the chart.
+ *
+ * @param {boolean} isDirectTouch
+ * Is the pointer directly hovering the point.
+ *
+ * @param {boolean} shared
+ * Whether it is a shared tooltip or not.
+ *
+ * @param {Highcharts.PointerEventObject} e
+ * The triggering event, containing chart coordinates of the pointer.
+ *
+ * @return {*}
+ * Object containing resulting hover data: hoverPoint, hoverSeries,
+ * and hoverPoints.
*/
- getHoverData: function(
+ getHoverData: function (
existingHoverPoint,
existingHoverSeries,
series,
isDirectTouch,
shared,
- coordinates,
- params
+ e
) {
var hoverPoint,
hoverPoints = [],
hoverSeries = existingHoverSeries,
- isBoosting = params && params.isBoosting,
useExisting = !!(isDirectTouch && existingHoverPoint),
notSticky = hoverSeries && !hoverSeries.stickyTracking,
- filter = function(s) {
+ filter = function (s) {
return (
s.visible &&
!(!shared && s.directTouch) && // #3821
@@ -17406,17 +23723,17 @@
},
// Which series to look in for the hover point
searchSeries = notSticky ?
- // Only search on hovered series if it has stickyTracking false
- [hoverSeries] :
- // Filter what series to look in.
- H.grep(series, function(s) {
- return filter(s) && s.stickyTracking;
- });
+ // Only search on hovered series if it has stickyTracking false
+ [hoverSeries] :
+ // Filter what series to look in.
+ series.filter(function (s) {
+ return filter(s) && s.stickyTracking;
+ });
// Use existing hovered point or find the one closest to coordinates.
hoverPoint = useExisting ?
existingHoverPoint :
- this.findNearestKDPoint(searchSeries, shared, coordinates);
+ this.findNearestKDPoint(searchSeries, shared, e);
// Assign hover series
hoverSeries = hoverPoint && hoverPoint.series;
@@ -17425,21 +23742,22 @@
if (hoverPoint) {
// When tooltip is shared, it displays more than one point
if (shared && !hoverSeries.noSharedTooltip) {
- searchSeries = H.grep(series, function(s) {
+ searchSeries = series.filter(function (s) {
return filter(s) && !s.noSharedTooltip;
});
// Get all points with the same x value as the hoverPoint
- each(searchSeries, function(s) {
- var point = find(s.points, function(p) {
+ searchSeries.forEach(function (s) {
+ var point = find(s.points, function (p) {
return p.x === hoverPoint.x && !p.isNull;
});
+
if (isObject(point)) {
/*
- * Boost returns a minimal point. Convert it to a usable
- * point for tooltip and states.
- */
- if (isBoosting) {
+ * Boost returns a minimal point. Convert it to a usable
+ * point for tooltip and states.
+ */
+ if (s.chart.isBoosting) {
point = s.getPoint(point);
}
hoverPoints.push(point);
@@ -17455,26 +23773,37 @@
hoverPoints: hoverPoints
};
},
+
/**
* With line type charts with a single tracker, get the point closest to the
* mouse. Run Point.onMouseOver and display tooltip for the point or points.
*
* @private
+ * @function Highcharts.Pointer#runPointActions
+ *
+ * @param {global.Event} e
+ *
+ * @param {Highcharts.Point} p
+ *
+ * @fires Highcharts.Point#event:mouseOut
+ * @fires Highcharts.Point#event:mouseOver
*/
- runPointActions: function(e, p) {
+ runPointActions: function (e, p) {
var pointer = this,
chart = pointer.chart,
series = chart.series,
tooltip = chart.tooltip && chart.tooltip.options.enabled ?
- chart.tooltip :
- undefined,
+ chart.tooltip :
+ undefined,
shared = tooltip ? tooltip.shared : false,
hoverPoint = p || chart.hoverPoint,
hoverSeries = hoverPoint && hoverPoint.series || chart.hoverSeries,
// onMouseOver or already hovering a series with directTouch
- isDirectTouch = !!p || (
- (hoverSeries && hoverSeries.directTouch) &&
- pointer.isDirectTouch
+ isDirectTouch = e.type !== 'touchmove' && (
+ !!p || (
+ (hoverSeries && hoverSeries.directTouch) &&
+ pointer.isDirectTouch
+ )
),
hoverData = this.getHoverData(
hoverPoint,
@@ -17482,9 +23811,7 @@
series,
isDirectTouch,
shared,
- e, {
- isBoosting: chart.isBoosting
- }
+ e
),
useSharedTooltip,
followPointer,
@@ -17496,7 +23823,11 @@
points = hoverData.hoverPoints;
hoverSeries = hoverData.hoverSeries;
followPointer = hoverSeries && hoverSeries.tooltipOptions.followPointer;
- useSharedTooltip = shared && hoverSeries && !hoverSeries.noSharedTooltip;
+ useSharedTooltip = (
+ shared &&
+ hoverSeries &&
+ !hoverSeries.noSharedTooltip
+ );
// Refresh tooltip for kdpoint if new hover point or tooltip was hidden
// #3926, #4200
@@ -17505,13 +23836,13 @@
// !(hoverSeries && hoverSeries.directTouch) &&
(hoverPoint !== chart.hoverPoint || (tooltip && tooltip.isHidden))
) {
- each(chart.hoverPoints || [], function(p) {
- if (H.inArray(p, points) === -1) {
+ (chart.hoverPoints || []).forEach(function (p) {
+ if (points.indexOf(p) === -1) {
p.setState();
}
});
// Do mouseover on all points (#3919, #3985, #4410, #5622)
- each(points || [], function(p) {
+ (points || []).forEach(function (p) {
p.setState('hover');
});
// set normal state to previous series
@@ -17519,7 +23850,7 @@
hoverSeries.onMouseOver();
}
- // If tracking is on series in stead of on each point,
+ // If tracking is on series in stead of on each point,
// fire mouseOver on hover point. // #4448
if (chart.hoverPoint) {
chart.hoverPoint.firePointEvent('mouseOut');
@@ -17537,13 +23868,10 @@
if (tooltip) {
tooltip.refresh(useSharedTooltip ? points : hoverPoint, e);
}
- // Update positions (regardless of kdpoint or hoverPoint)
+ // Update positions (regardless of kdpoint or hoverPoint)
} else if (followPointer && tooltip && !tooltip.isHidden) {
anchor = tooltip.getAnchor([{}], e);
- tooltip.updatePosition({
- plotX: anchor[0],
- plotY: anchor[1]
- });
+ tooltip.updatePosition({ plotX: anchor[0], plotY: anchor[1] });
}
// Start the event listener to pick up the tooltip and crosshairs
@@ -17551,8 +23879,9 @@
pointer.unDocMouseMove = addEvent(
chart.container.ownerDocument,
'mousemove',
- function(e) {
+ function (e) {
var chart = charts[H.hoverChartIndex];
+
if (chart) {
chart.pointer.onDocumentMouseMove(e);
}
@@ -17561,19 +23890,19 @@
}
// Issues related to crosshair #4927, #5269 #5066, #5658
- each(chart.axes, function drawAxisCrosshair(axis) {
+ chart.axes.forEach(function drawAxisCrosshair(axis) {
var snap = pick(axis.crosshair.snap, true),
point = !snap ?
- undefined :
- H.find(points, function(p) {
- return p.series[axis.coll] === axis;
- });
+ undefined :
+ H.find(points, function (p) {
+ return p.series[axis.coll] === axis;
+ });
// Axis has snapping crosshairs, and one of the hover points belongs
// to axis. Always call drawCrosshair when it is not snap.
if (point || !snap) {
axis.drawCrosshair(e, point);
- // Axis has snapping crosshairs, but no hover point belongs to axis
+ // Axis has snapping crosshairs, but no hover point belongs to axis
} else {
axis.hideCrosshair();
}
@@ -17584,22 +23913,29 @@
* Reset the tracking by hiding the tooltip, the hover series state and the
* hover point
*
- * @param allowMove {Boolean}
+ * @function Highcharts.Pointer#reset
+ *
+ * @param {boolean} allowMove
* Instead of destroying the tooltip altogether, allow moving it if
* possible.
+ *
+ * @param {number} delay
*/
- reset: function(allowMove, delay) {
+ reset: function (allowMove, delay) {
var pointer = this,
chart = pointer.chart,
hoverSeries = chart.hoverSeries,
hoverPoint = chart.hoverPoint,
hoverPoints = chart.hoverPoints,
tooltip = chart.tooltip,
- tooltipPoints = tooltip && tooltip.shared ? hoverPoints : hoverPoint;
+ tooltipPoints = tooltip && tooltip.shared ?
+ hoverPoints :
+ hoverPoint;
- // Check if the points have moved outside the plot area (#1003, #4736, #5101)
+ // Check if the points have moved outside the plot area (#1003, #4736,
+ // #5101)
if (allowMove && tooltipPoints) {
- each(splat(tooltipPoints), function(point) {
+ splat(tooltipPoints).forEach(function (point) {
if (point.series.isCartesian && point.plotX === undefined) {
allowMove = false;
}
@@ -17608,11 +23944,23 @@
// Just move the tooltip, #349
if (allowMove) {
- if (tooltip && tooltipPoints) {
+ if (tooltip && tooltipPoints && splat(tooltipPoints).length) {
tooltip.refresh(tooltipPoints);
- if (hoverPoint) { // #2500
+ if (tooltip.shared && hoverPoints) { // #8284
+ hoverPoints.forEach(function (point) {
+ point.setState(point.state, true);
+ if (point.series.isCartesian) {
+ if (point.series.xAxis.crosshair) {
+ point.series.xAxis.drawCrosshair(null, point);
+ }
+ if (point.series.yAxis.crosshair) {
+ point.series.yAxis.drawCrosshair(null, point);
+ }
+ }
+ });
+ } else if (hoverPoint) { // #2500
hoverPoint.setState(hoverPoint.state, true);
- each(chart.axes, function(axis) {
+ chart.axes.forEach(function (axis) {
if (axis.crosshair) {
axis.drawCrosshair(null, hoverPoint);
}
@@ -17620,7 +23968,7 @@
}
}
- // Full reset
+ // Full reset
} else {
if (hoverPoint) {
@@ -17628,7 +23976,7 @@
}
if (hoverPoints) {
- each(hoverPoints, function(point) {
+ hoverPoints.forEach(function (point) {
point.setState();
});
}
@@ -17646,7 +23994,7 @@
}
// Remove crosshairs
- each(chart.axes, function(axis) {
+ chart.axes.forEach(function (axis) {
axis.hideCrosshair();
});
@@ -17658,14 +24006,19 @@
* Scale series groups to a certain scale and translation.
*
* @private
+ * @function Highcharts.Pointer#scaleGroups
+ *
+ * @param {Highcharts.SeriesPlotBoxObject} attribs
+ *
+ * @param {boolean} clip
*/
- scaleGroups: function(attribs, clip) {
+ scaleGroups: function (attribs, clip) {
var chart = this.chart,
seriesAttribs;
// Scale each series
- each(chart.series, function(series) {
+ chart.series.forEach(function (series) {
seriesAttribs = attribs || series.getPlotBox(); // #1701
if (series.xAxis && series.xAxis.zoomEnabled && series.group) {
series.group.attr(seriesAttribs);
@@ -17687,8 +24040,11 @@
* Start a drag operation.
*
* @private
+ * @function Highcharts.Pointer#dragStart
+ *
+ * @param {Highcharts.PointerEventObject} e
*/
- dragStart: function(e) {
+ dragStart: function (e) {
var chart = this.chart;
// Record the start position
@@ -17703,8 +24059,11 @@
* is down.
*
* @private
+ * @function Highcharts.Pointer#drag
+ *
+ * @param {Highcharts.PointerEventObject} e
*/
- drag: function(e) {
+ drag: function (e) {
var chart = this.chart,
chartOptions = chart.options.chart,
@@ -17723,8 +24082,9 @@
mouseDownY = this.mouseDownY,
panKey = chartOptions.panKey && e[chartOptions.panKey + 'Key'];
- // If the device supports both touch and mouse (like IE11), and we are touch-dragging
- // inside the plot area, don't handle the mouse event. #4339.
+ // If the device supports both touch and mouse (like IE11), and we are
+ // touch-dragging inside the plot area, don't handle the mouse event.
+ // #4339.
if (selectionMarker && selectionMarker.touch) {
return;
}
@@ -17750,26 +24110,42 @@
);
if (this.hasDragged > 10) {
- clickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop);
+ clickedInside = chart.isInsidePlot(
+ mouseDownX - plotLeft,
+ mouseDownY - plotTop
+ );
// make a selection
- if (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside && !panKey) {
+ if (
+ chart.hasCartesianSeries &&
+ (this.zoomX || this.zoomY) &&
+ clickedInside &&
+ !panKey
+ ) {
if (!selectionMarker) {
- this.selectionMarker = selectionMarker = chart.renderer.rect(
+ this.selectionMarker = selectionMarker =
+ chart.renderer.rect(
plotLeft,
plotTop,
zoomHor ? 1 : plotWidth,
zoomVert ? 1 : plotHeight,
0
)
- .attr({
-
- fill: chartOptions.selectionMarkerFill || color('#335cad').setOpacity(0.25).get(),
-
- 'class': 'highcharts-selection-marker',
- 'zIndex': 7
- })
- .add();
+ .attr({
+ 'class': 'highcharts-selection-marker',
+ 'zIndex': 7
+ })
+ .add();
+
+ if (!chart.styledMode) {
+ selectionMarker.attr({
+ fill: (
+ chartOptions.selectionMarkerFill ||
+ color('#335cad')
+ .setOpacity(0.25).get()
+ )
+ });
+ }
}
}
@@ -17801,8 +24177,11 @@
* On mouse up or touch end across the entire document, drop the selection.
*
* @private
+ * @function Highcharts.Pointer#drop
+ *
+ * @param {global.Event} e
*/
- drop: function(e) {
+ drop: function (e) {
var pointer = this,
chart = this.chart,
hasPinched = this.hasPinched;
@@ -17814,44 +24193,82 @@
yAxis: []
},
selectionBox = this.selectionMarker,
- selectionLeft = selectionBox.attr ? selectionBox.attr('x') : selectionBox.x,
- selectionTop = selectionBox.attr ? selectionBox.attr('y') : selectionBox.y,
- selectionWidth = selectionBox.attr ? selectionBox.attr('width') : selectionBox.width,
- selectionHeight = selectionBox.attr ? selectionBox.attr('height') : selectionBox.height,
+ selectionLeft = selectionBox.attr ?
+ selectionBox.attr('x') :
+ selectionBox.x,
+ selectionTop = selectionBox.attr ?
+ selectionBox.attr('y') :
+ selectionBox.y,
+ selectionWidth = selectionBox.attr ?
+ selectionBox.attr('width') :
+ selectionBox.width,
+ selectionHeight = selectionBox.attr ?
+ selectionBox.attr('height') :
+ selectionBox.height,
runZoom;
// a selection has been made
if (this.hasDragged || hasPinched) {
// record each axis' min and max
- each(chart.axes, function(axis) {
- if (axis.zoomEnabled && defined(axis.min) && (hasPinched || pointer[{
- xAxis: 'zoomX',
- yAxis: 'zoomY'
- }[axis.coll]])) { // #859, #3569
+ chart.axes.forEach(function (axis) {
+ if (
+ axis.zoomEnabled &&
+ defined(axis.min) &&
+ (
+ hasPinched ||
+ pointer[{
+ xAxis: 'zoomX',
+ yAxis: 'zoomY'
+ }[axis.coll]]
+ )
+ ) { // #859, #3569
var horiz = axis.horiz,
- 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);
+ 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
+ );
selectionData[axis.coll].push({
axis: axis,
- min: Math.min(selectionMin, selectionMax), // for reversed axes
+ // Min/max for reversed axes
+ min: Math.min(selectionMin, selectionMax),
max: Math.max(selectionMin, selectionMax)
});
runZoom = true;
}
});
if (runZoom) {
- fireEvent(chart, 'selection', selectionData, function(args) {
- chart.zoom(extend(args, hasPinched ? {
- animation: false
- } : null));
- });
+ fireEvent(
+ chart,
+ 'selection',
+ selectionData,
+ function (args) {
+ chart.zoom(
+ extend(
+ args,
+ hasPinched ? { animation: false } : null
+ )
+ );
+ }
+ );
}
}
- this.selectionMarker = this.selectionMarker.destroy();
+
+ if (isNumber(chart.index)) {
+ this.selectionMarker = this.selectionMarker.destroy();
+ }
// Reset scaling preview
if (hasPinched) {
@@ -17859,34 +24276,46 @@
}
}
- // Reset all
- if (chart) { // it may be destroyed on mouse up - #877
- css(chart.container, {
- cursor: chart._cursor
- });
+ // Reset all. Check isNumber because it may be destroyed on mouse up
+ // (#877)
+ if (chart && isNumber(chart.index)) {
+ css(chart.container, { cursor: chart._cursor });
chart.cancelClick = this.hasDragged > 10; // #370
chart.mouseIsDown = this.hasDragged = this.hasPinched = false;
this.pinchDown = [];
}
},
- onContainerMouseDown: function(e) {
-
+ /**
+ * @private
+ * @function Highcharts.Pointer#onContainerMouseDown
+ *
+ * @param {global.Event} e
+ */
+ onContainerMouseDown: function (e) {
+ // Normalize before the 'if' for the legacy IE (#7850)
e = this.normalize(e);
- this.zoomOption(e);
-
- // issue #295, dragging not always working in Firefox
- if (e.preventDefault) {
- e.preventDefault();
- }
+ if (e.button !== 2) {
- this.dragStart(e);
- },
+ this.zoomOption(e);
+ // issue #295, dragging not always working in Firefox
+ if (e.preventDefault) {
+ e.preventDefault();
+ }
+ this.dragStart(e);
+ }
+ },
- onDocumentMouseUp: function(e) {
+ /**
+ * @private
+ * @function Highcharts.Pointer#onDocumentMouseUp
+ *
+ * @param {global.Event} e
+ */
+ onDocumentMouseUp: function (e) {
if (charts[H.hoverChartIndex]) {
charts[H.hoverChartIndex].pointer.drop(e);
}
@@ -17898,16 +24327,25 @@
* always fire.
*
* @private
+ * @function Highcharts.Pointer#onDocumentMouseMove
+ *
+ * @param {Highcharts.PointerEventObject} e
*/
- onDocumentMouseMove: function(e) {
+ onDocumentMouseMove: function (e) {
var chart = this.chart,
chartPosition = this.chartPosition;
e = this.normalize(e, chartPosition);
// If we're outside, hide the tooltip
- if (chartPosition && !this.inClass(e.target, 'highcharts-tracker') &&
- !chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
+ if (
+ chartPosition &&
+ !this.inClass(e.target, 'highcharts-tracker') &&
+ !chart.isInsidePlot(
+ e.chartX - chart.plotLeft,
+ e.chartY - chart.plotTop
+ )
+ ) {
this.reset();
}
},
@@ -17916,54 +24354,91 @@
* When mouse leaves the container, hide the tooltip.
*
* @private
+ * @function Highcharts.Pointer#onContainerMouseLeave
+ *
+ * @param {global.Event} e
*/
- onContainerMouseLeave: function(e) {
+ onContainerMouseLeave: function (e) {
var chart = charts[H.hoverChartIndex];
- if (chart && (e.relatedTarget || e.toElement)) { // #4886, MS Touch end fires mouseleave but with no related target
+
+ // #4886, MS Touch end fires mouseleave but with no related target
+ if (chart && (e.relatedTarget || e.toElement)) {
chart.pointer.reset();
- chart.pointer.chartPosition = null; // also reset the chart position, used in #149 fix
+ // Also reset the chart position, used in #149 fix
+ chart.pointer.chartPosition = null;
}
},
- // The mousemove, touchmove and touchstart event handler
- onContainerMouseMove: function(e) {
+ /**
+ * The mousemove, touchmove and touchstart event handler
+ *
+ * @private
+ * @function Highcharts.Pointer#onContainerMouseMove
+ *
+ * @param {Highcharts.PointerEventObject} e
+ */
+ onContainerMouseMove: function (e) {
var chart = this.chart;
- if (!defined(H.hoverChartIndex) || !charts[H.hoverChartIndex] || !charts[H.hoverChartIndex].mouseIsDown) {
+ if (
+ !defined(H.hoverChartIndex) ||
+ !charts[H.hoverChartIndex] ||
+ !charts[H.hoverChartIndex].mouseIsDown
+ ) {
H.hoverChartIndex = chart.index;
}
e = this.normalize(e);
- e.returnValue = false; // #2251, #3224
+
+ // In IE8 we apparently need this returnValue set to false in order to
+ // avoid text being selected. But in Chrome, e.returnValue is prevented,
+ // plus we don't need to run e.preventDefault to prevent selected text
+ // in modern browsers. So we set it conditionally. Remove it when IE8 is
+ // no longer needed. #2251, #3224.
+ if (!e.preventDefault) {
+ e.returnValue = false;
+ }
if (chart.mouseIsDown === 'mousedown') {
this.drag(e);
}
// Show the tooltip and run mouse over events (#977)
- if ((this.inClass(e.target, 'highcharts-tracker') ||
- chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) && !chart.openMenu) {
+ if (
+ (
+ this.inClass(e.target, 'highcharts-tracker') ||
+ chart.isInsidePlot(
+ e.chartX - chart.plotLeft,
+ e.chartY - chart.plotTop
+ )
+ ) &&
+ !chart.openMenu
+ ) {
this.runPointActions(e);
}
},
/**
- * Utility to detect whether an element has, or has a parent with, a specific
- * class name. Used on detection of tracker objects and on deciding whether
- * hovering the tooltip should cause the active series to mouse out.
+ * Utility to detect whether an element has, or has a parent with, a
+ * specificclass name. Used on detection of tracker objects and on deciding
+ * whether hovering the tooltip should cause the active series to mouse out.
+ *
+ * @function Highcharts.Pointer#inClass
*
- * @param {SVGDOMElement|HTMLDOMElement} element
- * The element to investigate.
- * @param {String} className
- * The class name to look for.
+ * @param {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement} element
+ * The element to investigate.
*
- * @return {Boolean}
+ * @param {string} className
+ * The class name to look for.
+ *
+ * @return {boolean}
* True if either the element or one of its parents has the given
* class name.
*/
- inClass: function(element, className) {
+ inClass: function (element, className) {
var elemClassName;
+
while (element) {
elemClassName = attr(element, 'class');
if (elemClassName) {
@@ -17978,7 +24453,13 @@
}
},
- onTrackerMouseOut: function(e) {
+ /**
+ * @private
+ * @function Highcharts.Pointer#onTrackerMouseOut
+ *
+ * @param {global.Event} e
+ */
+ onTrackerMouseOut: function (e) {
var series = this.chart.hoverSeries,
relatedTarget = e.relatedTarget || e.toElement;
@@ -17989,7 +24470,8 @@
relatedTarget &&
!series.stickyTracking &&
!this.inClass(relatedTarget, 'highcharts-tooltip') &&
- (!this.inClass(
+ (
+ !this.inClass(
relatedTarget,
'highcharts-series-' + series.index
) || // #2499, #4465
@@ -18000,7 +24482,13 @@
}
},
- onContainerClick: function(e) {
+ /**
+ * @private
+ * @function Highcharts.Pointer#onContainerClick
+ *
+ * @param {Highcharts.PointerEventObject} e
+ */
+ onContainerClick: function (e) {
var chart = this.chart,
hoverPoint = chart.hoverPoint,
plotLeft = chart.plotLeft,
@@ -18023,12 +24511,14 @@
hoverPoint.firePointEvent('click', e);
}
- // When clicking outside a tracker, fire a chart event
+ // When clicking outside a tracker, fire a chart event
} else {
extend(e, this.getCoordinates(e));
// fire a click event in the chart
- if (chart.isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {
+ if (
+ chart.isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)
+ ) {
fireEvent(chart, 'click', e);
}
}
@@ -18038,25 +24528,27 @@
},
/**
- * Set the JS DOM events on the container and document. This method should contain
- * a one-to-one assignment between methods and their handlers. Any advanced logic should
- * be moved to the handler reflecting the event's name.
+ * Set the JS DOM events on the container and document. This method should
+ * contain a one-to-one assignment between methods and their handlers. Any
+ * advanced logic should be moved to the handler reflecting the event's
+ * name.
*
* @private
+ * @function Highcharts.Pointer#setDOMEvents
*/
- setDOMEvents: function() {
+ setDOMEvents: function () {
var pointer = this,
container = pointer.chart.container,
ownerDoc = container.ownerDocument;
- container.onmousedown = function(e) {
+ container.onmousedown = function (e) {
pointer.onContainerMouseDown(e);
};
- container.onmousemove = function(e) {
+ container.onmousemove = function (e) {
pointer.onContainerMouseMove(e);
};
- container.onclick = function(e) {
+ container.onclick = function (e) {
pointer.onContainerClick(e);
};
this.unbindContainerMouseLeave = addEvent(
@@ -18072,10 +24564,10 @@
);
}
if (H.hasTouch) {
- container.ontouchstart = function(e) {
+ container.ontouchstart = function (e) {
pointer.onContainerTouchStart(e);
};
- container.ontouchmove = function(e) {
+ container.ontouchmove = function (e) {
pointer.onContainerTouchMove(e);
};
if (!H.unbindDocumentTouchEnd) {
@@ -18091,8 +24583,10 @@
/**
* Destroys the Pointer object and disconnects DOM events.
+ *
+ * @function Highcharts.Pointer#destroy
*/
- destroy: function() {
+ destroy: function () {
var pointer = this;
if (pointer.unDocMouseMove) {
@@ -18113,34 +24607,50 @@
// memory and CPU leak
clearInterval(pointer.tooltipTimeout);
- H.objectEach(pointer, function(val, prop) {
+ H.objectEach(pointer, function (val, prop) {
pointer[prop] = null;
});
}
};
}(Highcharts));
- (function(H) {
+ (function (H) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
+
var charts = H.charts,
- each = H.each,
extend = H.extend,
- map = H.map,
noop = H.noop,
pick = H.pick,
Pointer = H.Pointer;
- /* Support for touch devices */
+ // Support for touch devices
extend(Pointer.prototype, /** @lends Pointer.prototype */ {
/**
* Run translation operations
+ *
+ * @private
+ * @function Highcharts.Pointer#pinchTranslate
+ *
+ * @param {Array<*>} pinchDown
+ *
+ * @param {Array<*>} touches
+ *
+ * @param {*} transform
+ *
+ * @param {*} selectionMarker
+ *
+ * @param {*} clip
+ *
+ * @param {*} lastValidTouch
*/
- pinchTranslate: function(
+ pinchTranslate: function (
pinchDown,
touches,
transform,
@@ -18174,10 +24684,37 @@
/**
* Run translation operations for each direction (horizontal and vertical)
- * independently
+ * independently.
+ *
+ * @private
+ * @function Highcharts.Pointer#pinchTranslateDirection
+ *
+ * @param {boolean} horiz
+ *
+ * @param {Array<*>} pinchDown
+ *
+ * @param {Array<*>} touches
+ *
+ * @param {*} transform
+ *
+ * @param {*} selectionMarker
+ *
+ * @param {*} clip
+ *
+ * @param {*} lastValidTouch
+ *
+ * @param {number|undefined} [forcedScale=1]
*/
- pinchTranslateDirection: function(horiz, pinchDown, touches, transform,
- selectionMarker, clip, lastValidTouch, forcedScale) {
+ pinchTranslateDirection: function (
+ horiz,
+ pinchDown,
+ touches,
+ transform,
+ selectionMarker,
+ clip,
+ lastValidTouch,
+ forcedScale
+ ) {
var chart = this.chart,
xy = horiz ? 'x' : 'y',
XY = horiz ? 'X' : 'Y',
@@ -18198,7 +24735,7 @@
outOfBounds,
transformScale,
scaleKey,
- setScale = function() {
+ setScale = function () {
// Don't zoom if fingers are too close on this axis
if (!singleTouch && Math.abs(touch0Start - touch1Start) > 20) {
scale = forcedScale ||
@@ -18264,8 +24801,13 @@
/**
* Handle touch events with two touches
+ *
+ * @private
+ * @function Highcharts.Pointer#pinch
+ *
+ * @param {Highcharts.PointerEvent} e
*/
- pinch: function(e) {
+ pinch: function (e) {
var self = this,
chart = self.chart,
@@ -18276,9 +24818,13 @@
hasZoom = self.hasZoom,
selectionMarker = self.selectionMarker,
transform = {},
- fireClickEvent = touchesLength === 1 &&
- ((self.inClass(e.target, 'highcharts-tracker') &&
- chart.runTrackerClick) || self.runChartClick),
+ fireClickEvent = touchesLength === 1 && (
+ (
+ self.inClass(e.target, 'highcharts-tracker') &&
+ chart.runTrackerClick
+ ) ||
+ self.runChartClick
+ ),
clip = {};
// Don't initiate panning until the user has pinched. This prevents us
@@ -18295,27 +24841,22 @@
}
// Normalize each touch
- map(touches, function(e) {
+ [].map.call(touches, function (e) {
return self.normalize(e);
});
// Register the touch start position
if (e.type === 'touchstart') {
- each(touches, function(e, i) {
- pinchDown[i] = {
- chartX: e.chartX,
- chartY: e.chartY
- };
+ [].forEach.call(touches, function (e, i) {
+ pinchDown[i] = { chartX: e.chartX, chartY: e.chartY };
});
lastValidTouch.x = [pinchDown[0].chartX, pinchDown[1] &&
- pinchDown[1].chartX
- ];
+ pinchDown[1].chartX];
lastValidTouch.y = [pinchDown[0].chartY, pinchDown[1] &&
- pinchDown[1].chartY
- ];
+ pinchDown[1].chartY];
// Identify the data bounds in pixels
- each(chart.axes, function(axis) {
+ chart.axes.forEach(function (axis) {
if (axis.zoomEnabled) {
var bounds = chart.bounds[axis.horiz ? 'h' : 'v'],
minPixelPadding = axis.minPixelPadding,
@@ -18338,11 +24879,11 @@
});
self.res = true; // reset on next move
- // Optionally move the tooltip on touchmove
+ // Optionally move the tooltip on touchmove
} else if (self.followTouchMove && touchesLength === 1) {
this.runPointActions(self.normalize(e));
- // Event type is touchmove, handle panning and pinching
+ // Event type is touchmove, handle panning and pinching
} else if (pinchDown.length) { // can be 0 when releasing, if touchend
// fires first
@@ -18379,17 +24920,22 @@
/**
* General touch handler shared by touchstart and touchmove.
+ *
+ * @private
+ * @function Highcharts.Pointer#touch
+ *
+ * @param {Highcharts.PointerEvent} e
+ *
+ * @param {boolean} start
*/
- touch: function(e, start) {
+ touch: function (e, start) {
var chart = this.chart,
hasMoved,
pinchDown,
isInside;
if (chart.index !== H.hoverChartIndex) {
- this.onContainerMouseLeave({
- relatedTarget: true
- });
+ this.onContainerMouseLeave({ relatedTarget: true });
}
H.hoverChartIndex = chart.index;
@@ -18436,16 +24982,34 @@
}
},
- onContainerTouchStart: function(e) {
+ /**
+ * @private
+ * @function Highcharts.Pointer#onContainerTouchStart
+ *
+ * @param {Highcharts.PointerEvent} e
+ */
+ onContainerTouchStart: function (e) {
this.zoomOption(e);
this.touch(e, true);
},
- onContainerTouchMove: function(e) {
+ /**
+ * @private
+ * @function Highcharts.Pointer#onContainerTouchMove
+ *
+ * @param {Highcharts.PointerEvent} e
+ */
+ onContainerTouchMove: function (e) {
this.touch(e);
},
- onDocumentTouchEnd: function(e) {
+ /**
+ * @private
+ * @function Highcharts.Pointer#onDocumentTouchEnd
+ *
+ * @param {Highcharts.PointerEvent} e
+ */
+ onDocumentTouchEnd: function (e) {
if (charts[H.hoverChartIndex]) {
charts[H.hoverChartIndex].pointer.drop(e);
}
@@ -18454,12 +25018,15 @@
});
}(Highcharts));
- (function(H) {
+ (function (H) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
+
var addEvent = H.addEvent,
charts = H.charts,
css = H.css,
@@ -18477,12 +25044,13 @@
// The touches object keeps track of the points being touched at all times
var touches = {},
hasPointerEvent = !!win.PointerEvent,
- getWebkitTouches = function() {
+ getWebkitTouches = function () {
var fake = [];
- fake.item = function(i) {
+
+ fake.item = function (i) {
return this[i];
};
- H.objectEach(touches, function(touch) {
+ H.objectEach(touches, function (touch) {
fake.push({
pageX: touch.pageX,
pageY: touch.pageY,
@@ -18491,9 +25059,15 @@
});
return fake;
},
- translateMSPointer = function(e, method, wktype, func) {
+ translateMSPointer = function (e, method, wktype, func) {
var p;
- if ((e.pointerType === 'touch' || e.pointerType === e.MSPOINTER_TYPE_TOUCH) && charts[H.hoverChartIndex]) {
+
+ if (
+ (
+ e.pointerType === 'touch' ||
+ e.pointerType === e.MSPOINTER_TYPE_TOUCH
+ ) && charts[H.hoverChartIndex]
+ ) {
func(e);
p = charts[H.hoverChartIndex].pointer;
p[method]({
@@ -18505,48 +25079,96 @@
}
};
- /**
- * Extend the Pointer prototype with methods for each event handler and more
- */
+ // Extend the Pointer prototype with methods for each event handler and more
extend(Pointer.prototype, /** @lends Pointer.prototype */ {
- onContainerPointerDown: function(e) {
- translateMSPointer(e, 'onContainerTouchStart', 'touchstart', function(e) {
- touches[e.pointerId] = {
- pageX: e.pageX,
- pageY: e.pageY,
- target: e.currentTarget
- };
- });
+
+ /**
+ * @private
+ * @function Highcharts.Pointer#onContainerPointerDown
+ *
+ * @param {Highcharts.PointerEventObject} e
+ */
+ onContainerPointerDown: function (e) {
+ translateMSPointer(
+ e,
+ 'onContainerTouchStart',
+ 'touchstart',
+ function (e) {
+ touches[e.pointerId] = {
+ pageX: e.pageX,
+ pageY: e.pageY,
+ target: e.currentTarget
+ };
+ }
+ );
},
- onContainerPointerMove: function(e) {
- translateMSPointer(e, 'onContainerTouchMove', 'touchmove', function(e) {
- touches[e.pointerId] = {
- pageX: e.pageX,
- pageY: e.pageY
- };
- if (!touches[e.pointerId].target) {
- touches[e.pointerId].target = e.currentTarget;
+
+ /**
+ * @private
+ * @function Highcharts.Pointer#onContainerPointerMove
+ *
+ * @param {Highcharts.PointerEventObject} e
+ */
+ onContainerPointerMove: function (e) {
+ translateMSPointer(
+ e,
+ 'onContainerTouchMove',
+ 'touchmove',
+ function (e) {
+ touches[e.pointerId] = { pageX: e.pageX, pageY: e.pageY };
+ if (!touches[e.pointerId].target) {
+ touches[e.pointerId].target = e.currentTarget;
+ }
}
- });
+ );
},
- onDocumentPointerUp: function(e) {
- translateMSPointer(e, 'onDocumentTouchEnd', 'touchend', function(e) {
- delete touches[e.pointerId];
- });
+
+ /**
+ * @private
+ * @function Highcharts.Pointer#onDocumentPointerUp
+ *
+ * @param {Highcharts.PointerEventObject} e
+ */
+ onDocumentPointerUp: function (e) {
+ translateMSPointer(
+ e,
+ 'onDocumentTouchEnd',
+ 'touchend',
+ function (e) {
+ delete touches[e.pointerId];
+ }
+ );
},
/**
* Add or remove the MS Pointer specific events
+ *
+ * @private
+ * @function Highcharts.Pointer#batchMSEvents
+ *
+ * @param {Function} fn
*/
- batchMSEvents: function(fn) {
- fn(this.chart.container, hasPointerEvent ? 'pointerdown' : 'MSPointerDown', this.onContainerPointerDown);
- fn(this.chart.container, hasPointerEvent ? 'pointermove' : 'MSPointerMove', this.onContainerPointerMove);
- fn(doc, hasPointerEvent ? 'pointerup' : 'MSPointerUp', this.onDocumentPointerUp);
+ batchMSEvents: function (fn) {
+ fn(
+ this.chart.container,
+ hasPointerEvent ? 'pointerdown' : 'MSPointerDown',
+ this.onContainerPointerDown
+ );
+ fn(
+ this.chart.container,
+ hasPointerEvent ? 'pointermove' : 'MSPointerMove',
+ this.onContainerPointerMove
+ );
+ fn(
+ doc,
+ hasPointerEvent ? 'pointerup' : 'MSPointerUp',
+ this.onDocumentPointerUp
+ );
}
});
// Disable default IE actions for pinch and such on chart element
- wrap(Pointer.prototype, 'init', function(proceed, chart, options) {
+ wrap(Pointer.prototype, 'init', function (proceed, chart, options) {
proceed.call(this, chart, options);
if (this.hasZoom) { // #4014
css(chart.container, {
@@ -18557,33 +25179,36 @@
});
// Add IE specific touch events to chart
- wrap(Pointer.prototype, 'setDOMEvents', function(proceed) {
+ wrap(Pointer.prototype, 'setDOMEvents', function (proceed) {
proceed.apply(this);
if (this.hasZoom || this.followTouchMove) {
this.batchMSEvents(addEvent);
}
});
// Destroy MS events also
- wrap(Pointer.prototype, 'destroy', function(proceed) {
+ wrap(Pointer.prototype, 'destroy', function (proceed) {
this.batchMSEvents(removeEvent);
proceed.call(this);
});
}
}(Highcharts));
- (function(Highcharts) {
+ (function (Highcharts) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
+
var H = Highcharts,
addEvent = H.addEvent,
css = H.css,
discardElement = H.discardElement,
defined = H.defined,
- each = H.each,
+ fireEvent = H.fireEvent,
isFirefox = H.isFirefox,
marginNames = H.marginNames,
merge = H.merge,
@@ -18595,12 +25220,19 @@
/**
* The overview of the chart's series. The legend object is instanciated
- * internally in the chart constructor, and available from `chart.legend`. Each
- * chart has only one legend.
- *
+ * internally in the chart constructor, and is available from the `chart.legend`
+ * property. Each chart has only one legend.
+ *
* @class
+ * @name Highcharts.Legend
+ *
+ * @param {Highcharts.Chart} chart
+ * The chart instance.
+ *
+ * @param {Highcharts.LegendOptions} options
+ * Legend options.
*/
- Highcharts.Legend = function(chart, options) {
+ Highcharts.Legend = function (chart, options) {
this.init(chart, options);
};
@@ -18610,9 +25242,23 @@
* Initialize the legend.
*
* @private
+ * @function Highcharts.Legend#init
+ *
+ * @param {Highcharts.Chart} chart
+ * The chart instance.
+ *
+ * @param {Highcharts.LegendOptions} options
+ * Legend options.
*/
- init: function(chart, options) {
+ init: function (chart, options) {
+ /**
+ * Chart of this legend.
+ *
+ * @readonly
+ * @name Highcharts.Legend#chart
+ * @type {Highcharts.Chart}
+ */
this.chart = chart;
this.setOptions(options);
@@ -18623,44 +25269,82 @@
this.render();
// move checkboxes
- addEvent(this.chart, 'endResize', function() {
+ addEvent(this.chart, 'endResize', function () {
this.legend.positionCheckboxes();
});
+
+ if (this.proximate) {
+ this.unchartrender = addEvent(
+ this.chart,
+ 'render',
+ function () {
+ this.legend.proximatePositions();
+ this.legend.positionItems();
+ }
+ );
+ } else if (this.unchartrender) {
+ this.unchartrender();
+ }
}
},
- setOptions: function(options) {
+ /**
+ * @private
+ * @function Highcharts.Legend#setOptions
+ *
+ * @param {Highcharts.LegendOptions} options
+ */
+ setOptions: function (options) {
var padding = pick(options.padding, 8);
+ /**
+ * Legend options.
+ *
+ * @readonly
+ * @name Highcharts.Legend#options
+ * @type {Highcharts.LegendOptions}
+ */
this.options = options;
-
- this.itemStyle = options.itemStyle;
- this.itemHiddenStyle = merge(this.itemStyle, options.itemHiddenStyle);
+ if (!this.chart.styledMode) {
+ this.itemStyle = options.itemStyle;
+ this.itemHiddenStyle = merge(
+ this.itemStyle,
+ options.itemHiddenStyle
+ );
+ }
this.itemMarginTop = options.itemMarginTop || 0;
this.padding = padding;
this.initialItemY = padding - 5; // 5 is pixels above the text
- this.maxItemWidth = 0;
- this.itemHeight = 0;
this.symbolWidth = pick(options.symbolWidth, 16);
this.pages = [];
+ this.proximate = options.layout === 'proximate' && !this.chart.inverted;
},
/**
* Update the legend with new options. Equivalent to running `chart.update`
* with a legend configuration option.
- * @param {LegendOptions} options
- * Legend options.
- * @param {Boolean} [redraw=true]
- * Whether to redraw the chart.
*
* @sample highcharts/legend/legend-update/
* Legend update
+ *
+ * @function Highcharts.Legend#update
+ *
+ * @param {Highcharts.LegendOptions} options
+ * Legend options.
+ *
+ * @param {boolean} [redraw=true]
+ * Whether to redraw the chart after the axis is altered. If doing
+ * more operations on the chart, it is a good idea to set redraw to
+ * false and call {@link Chart#redraw} after.
+ * Whether to redraw the chart.
+ *
+ * @fires Highcharts.Legends#event:afterUpdate
*/
- update: function(options, redraw) {
+ update: function (options, redraw) {
var chart = this.chart;
this.setOptions(merge(true, this.options, options));
@@ -18669,71 +25353,97 @@
if (pick(redraw, true)) {
chart.redraw();
}
+
+ fireEvent(this, 'afterUpdate');
},
/**
* Set the colors for the legend item.
*
* @private
- * @param {Series|Point} item
- * A Series or Point instance
- * @param {Boolean} visible
- * Dimmed or colored
+ * @function Highcharts.Legend#colorizeItem
+ *
+ * @param {Highcharts.Point|Highcharts.Series} item
+ * A Series or Point instance
+ *
+ * @param {boolean} [visible=false]
+ * Dimmed or colored
+ *
+ * @todo
+ * Make events official: Fires the event `afterColorizeItem`.
*/
- colorizeItem: function(item, visible) {
+ colorizeItem: function (item, visible) {
item.legendGroup[visible ? 'removeClass' : 'addClass'](
'highcharts-legend-item-hidden'
);
+ if (!this.chart.styledMode) {
+ var legend = this,
+ options = legend.options,
+ legendItem = item.legendItem,
+ legendLine = item.legendLine,
+ legendSymbol = item.legendSymbol,
+ hiddenColor = legend.itemHiddenStyle.color,
+ textColor = visible ? options.itemStyle.color : hiddenColor,
+ symbolColor = visible ?
+ (item.color || hiddenColor) :
+ hiddenColor,
+ markerOptions = item.options && item.options.marker,
+ symbolAttr = { fill: symbolColor };
+
+ if (legendItem) {
+ legendItem.css({
+ fill: textColor,
+ color: textColor // #1553, oldIE
+ });
+ }
+ if (legendLine) {
+ legendLine.attr({ stroke: symbolColor });
+ }
+
+ if (legendSymbol) {
- var legend = this,
- options = legend.options,
- legendItem = item.legendItem,
- legendLine = item.legendLine,
- legendSymbol = item.legendSymbol,
- hiddenColor = legend.itemHiddenStyle.color,
- textColor = visible ? options.itemStyle.color : hiddenColor,
- symbolColor = visible ? (item.color || hiddenColor) : hiddenColor,
- markerOptions = item.options && item.options.marker,
- symbolAttr = {
- fill: symbolColor
- };
+ // Apply marker options
+ if (markerOptions && legendSymbol.isMarker) { // #585
+ symbolAttr = item.pointAttribs();
+ if (!visible) {
+ // #6769
+ symbolAttr.stroke = symbolAttr.fill = hiddenColor;
+ }
+ }
- if (legendItem) {
- legendItem.css({
- fill: textColor,
- color: textColor // #1553, oldIE
- });
- }
- if (legendLine) {
- legendLine.attr({
- stroke: symbolColor
- });
+ legendSymbol.attr(symbolAttr);
+ }
}
- if (legendSymbol) {
+ fireEvent(this, 'afterColorizeItem', { item: item, visible: visible });
+ },
- // Apply marker options
- if (markerOptions && legendSymbol.isMarker) { // #585
- symbolAttr = item.pointAttribs();
- if (!visible) {
- symbolAttr.stroke = symbolAttr.fill = hiddenColor; // #6769
- }
- }
+ /**
+ * @private
+ * @function Highcharts.Legend#positionItems
+ */
+ positionItems: function () {
- legendSymbol.attr(symbolAttr);
- }
+ // Now that the legend width and height are established, put the items
+ // in the final position
+ this.allItems.forEach(this.positionItem, this);
+ if (!this.chart.isResizing) {
+ this.positionCheckboxes();
+ }
},
/**
* Position the legend item.
*
* @private
- * @param {Series|Point} item
+ * @function Highcharts.Legend#positionItem
+ *
+ * @param {Highcharts.Point|Highcharts.Series} item
* The item to position
*/
- positionItem: function(item) {
+ positionItem: function (item) {
var legend = this,
options = legend.options,
symbolPadding = options.symbolPadding,
@@ -18745,12 +25455,12 @@
legendGroup = item.legendGroup;
if (legendGroup && legendGroup.element) {
- legendGroup.translate(
- ltr ?
- itemX :
- legend.legendWidth - itemX - 2 * symbolPadding - 4,
- itemY
- );
+ legendGroup[defined(legendGroup.translateY) ? 'animate' : 'attr']({
+ translateX: ltr ?
+ itemX :
+ legend.legendWidth - itemX - 2 * symbolPadding - 4,
+ translateY: itemY
+ });
}
if (checkbox) {
@@ -18761,17 +25471,19 @@
/**
* Destroy a single legend item, used internally on removing series items.
- *
- * @param {Series|Point} item
+ *
+ * @private
+ * @function Highcharts.Legend#destroyItem
+ *
+ * @param {Highcharts.Point|Highcharts.Series} item
* The item to remove
*/
- destroyItem: function(item) {
+ destroyItem: function (item) {
var checkbox = item.checkbox;
// destroy SVG elements
- each(
- ['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'],
- function(key) {
+ ['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'].forEach(
+ function (key) {
if (item[key]) {
item[key] = item[key].destroy();
}
@@ -18786,8 +25498,11 @@
/**
* Destroy the legend. Used internally. To reflow objects, `chart.redraw`
* must be called after destruction.
+ *
+ * @private
+ * @function Highcharts.Legend#destroy
*/
- destroy: function() {
+ destroy: function () {
function destroyItems(key) {
if (this[key]) {
this[key] = this[key].destroy();
@@ -18795,12 +25510,12 @@
}
// Destroy items
- each(this.getAllItems(), function(item) {
- each(['legendItem', 'legendGroup'], destroyItems, item);
+ this.getAllItems().forEach(function (item) {
+ ['legendItem', 'legendGroup'].forEach(destroyItems, item);
});
// Destroy legend elements
- each([
+ [
'clipRect',
'up',
'down',
@@ -18809,7 +25524,7 @@
'box',
'title',
'group'
- ], destroyItems, this);
+ ].forEach(destroyItems, this);
this.display = null; // Reset in .render on update.
},
@@ -18817,8 +25532,9 @@
* Position the checkboxes after the width is determined.
*
* @private
+ * @function Highcharts.Legend#positionCheckboxes
*/
- positionCheckboxes: function() {
+ positionCheckboxes: function () {
var alignAttr = this.group && this.group.alignAttr,
translateY,
clipHeight = this.clipHeight || this.legendHeight,
@@ -18826,7 +25542,7 @@
if (alignAttr) {
translateY = alignAttr.translateY;
- each(this.allItems, function(item) {
+ this.allItems.forEach(function (item) {
var checkbox = item.checkbox,
top;
@@ -18837,8 +25553,12 @@
left: (alignAttr.translateX + item.checkboxOffset +
checkbox.x - 20) + 'px',
top: top + 'px',
- display: top > translateY - 6 && top < translateY +
- clipHeight - 6 ? '' : 'none'
+ display: this.proximate || (
+ top > translateY - 6 &&
+ top < translateY + clipHeight - 6
+ ) ?
+ '' :
+ 'none'
});
}
}, this);
@@ -18849,8 +25569,9 @@
* Render the legend title on top of the legend.
*
* @private
+ * @function Highcharts.Legend#renderTitle
*/
- renderTitle: function() {
+ renderTitle: function () {
var options = this.options,
padding = this.padding,
titleOptions = options.title,
@@ -18859,31 +25580,44 @@
if (titleOptions.text) {
if (!this.title) {
+ /**
+ * SVG element of the legend title.
+ *
+ * @readonly
+ * @name Highcharts.Legend#title
+ * @type {Highcharts.SVGElement}
+ */
this.title = this.chart.renderer.label(
- titleOptions.text,
- padding - 3,
- padding - 4,
- null,
- null,
- null,
- options.useHTML,
- null,
- 'legend-title'
- )
- .attr({
- zIndex: 1
- })
+ titleOptions.text,
+ padding - 3,
+ padding - 4,
+ null,
+ null,
+ null,
+ options.useHTML,
+ null,
+ 'legend-title'
+ )
+ .attr({ zIndex: 1 });
- .css(titleOptions.style)
+ if (!this.chart.styledMode) {
+ this.title.css(titleOptions.style);
+ }
- .add(this.group);
+ this.title.add(this.group);
+ }
+
+ // Set the max title width (#7253)
+ if (!titleOptions.width) {
+ this.title.css({
+ width: this.maxLegendWidth + 'px'
+ });
}
+
bBox = this.title.getBBox();
titleHeight = bBox.height;
this.offsetWidth = bBox.width; // #1717
- this.contentGroup.attr({
- translateY: titleHeight
- });
+ this.contentGroup.attr({ translateY: titleHeight });
}
this.titleHeight = titleHeight;
},
@@ -18891,14 +25625,18 @@
/**
* Set the legend item text.
*
- * @param {Series|Point} item
- * The item for which to update the text in the legend.
+ * @function Highcharts.Legend#setText
+ *
+ * @param {Highcharts.Point|Highcharts.Series} item
+ * The item for which to update the text in the legend.
*/
- setText: function(item) {
+ setText: function (item) {
var options = this.options;
+
item.legendItem.attr({
text: options.labelFormat ?
- H.format(options.labelFormat, item) : options.labelFormatter.call(item)
+ H.format(options.labelFormat, item, this.chart.time) :
+ options.labelFormatter.call(item)
});
},
@@ -18907,10 +25645,12 @@
* function.
*
* @private
- * @param {Series|Point} item
+ * @function Highcharts.Legend#renderItem
+ *
+ * @param {Highcharts.Point|Highcharts.Series} item
* The item to render.
*/
- renderItem: function(item) {
+ renderItem: function (item) {
var legend = this,
chart = legend.chart,
renderer = chart.renderer,
@@ -18918,33 +25658,24 @@
horizontal = options.layout === 'horizontal',
symbolWidth = legend.symbolWidth,
symbolPadding = options.symbolPadding,
-
itemStyle = legend.itemStyle,
itemHiddenStyle = legend.itemHiddenStyle,
-
- padding = legend.padding,
itemDistance = horizontal ? pick(options.itemDistance, 20) : 0,
ltr = !options.rtl,
- itemHeight,
- widthOption = options.width,
- itemMarginBottom = options.itemMarginBottom || 0,
- itemMarginTop = legend.itemMarginTop,
bBox,
- itemWidth,
li = item.legendItem,
isSeries = !item.series,
series = !isSeries && item.series.drawLegendSymbol ?
- item.series :
- item,
+ item.series :
+ item,
seriesOptions = series.options,
showCheckbox = legend.createCheckboxForItem &&
- seriesOptions &&
- seriesOptions.showCheckbox,
+ seriesOptions &&
+ seriesOptions.showCheckbox,
// full width minus text width
itemExtraWidth = symbolWidth + symbolPadding + itemDistance +
- (showCheckbox ? 20 : 0),
+ (showCheckbox ? 20 : 0),
useHTML = options.useHTML,
- fontSize = 12,
itemClassName = item.options.className;
if (!li) { // generate it once, later move it
@@ -18958,39 +25689,37 @@
(itemClassName ? ' ' + itemClassName : '') +
(isSeries ? ' highcharts-series-' + item.index : '')
)
- .attr({
- zIndex: 1
- })
+ .attr({ zIndex: 1 })
.add(legend.scrollGroup);
// Generate the list item text and add it to the group
item.legendItem = li = renderer.text(
- '',
- ltr ? symbolWidth + symbolPadding : -symbolPadding,
- legend.baseline || 0,
- useHTML
- )
+ '',
+ ltr ? symbolWidth + symbolPadding : -symbolPadding,
+ legend.baseline || 0,
+ useHTML
+ );
+ if (!chart.styledMode) {
// merge to prevent modifying original (#1021)
- .css(merge(item.visible ? itemStyle : itemHiddenStyle))
+ li.css(merge(item.visible ? itemStyle : itemHiddenStyle));
+ }
- .attr({
- align: ltr ? 'left' : 'right',
- zIndex: 2
- })
+ li.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) {
-
- fontSize = itemStyle.fontSize;
-
legend.fontMetrics = renderer.fontMetrics(
- fontSize,
+ chart.styledMode ? 12 : itemStyle.fontSize,
li
);
- legend.baseline = legend.fontMetrics.f + 3 + itemMarginTop;
+ legend.baseline =
+ legend.fontMetrics.f + 3 + legend.itemMarginTop;
li.attr('y', legend.baseline);
}
@@ -19012,89 +25741,99 @@
legend.colorizeItem(item, item.visible);
// Take care of max width and text overflow (#6659)
-
- if (!itemStyle.width) {
-
+ if (chart.styledMode || !itemStyle.width) {
li.css({
width: (
options.itemWidth ||
- options.width ||
+ legend.widthOption ||
chart.spacingBox.width
) - itemExtraWidth
});
-
}
-
// Always update the text
legend.setText(item);
// calculate the positions for the next line
bBox = li.getBBox();
- itemWidth = item.checkboxOffset =
+ item.itemWidth = item.checkboxOffset =
options.itemWidth ||
item.legendItemWidth ||
bBox.width + itemExtraWidth;
- legend.itemHeight = itemHeight = Math.round(
+ legend.maxItemWidth = Math.max(legend.maxItemWidth, item.itemWidth);
+ legend.totalItemWidth += item.itemWidth;
+ legend.itemHeight = item.itemHeight = Math.round(
item.legendItemHeight || bBox.height || legend.symbolHeight
);
+ },
+
+ /**
+ * Get the position of the item in the layout. We now know the
+ * maxItemWidth from the previous loop.
+ *
+ * @private
+ * @function Highcharts.Legend#layoutItem
+ *
+ * @param {Highcharts.Point|Highcharts.Series} item
+ */
+ layoutItem: function (item) {
+
+ var options = this.options,
+ padding = this.padding,
+ horizontal = options.layout === 'horizontal',
+ itemHeight = item.itemHeight,
+ itemMarginBottom = options.itemMarginBottom || 0,
+ itemMarginTop = this.itemMarginTop,
+ itemDistance = horizontal ? pick(options.itemDistance, 20) : 0,
+ maxLegendWidth = this.maxLegendWidth,
+ itemWidth = (
+ options.alignColumns &&
+ this.totalItemWidth > maxLegendWidth
+ ) ?
+ this.maxItemWidth :
+ item.itemWidth;
// If the item exceeds the width, start a new line
if (
horizontal &&
- legend.itemX - padding + itemWidth > (
- widthOption || (
- chart.spacingBox.width - 2 * padding - options.x
- )
- )
+ this.itemX - padding + itemWidth > maxLegendWidth
) {
- legend.itemX = padding;
- legend.itemY += itemMarginTop + legend.lastLineHeight +
+ this.itemX = padding;
+ this.itemY += itemMarginTop + this.lastLineHeight +
itemMarginBottom;
- legend.lastLineHeight = 0; // reset for next line (#915, #3976)
- }
-
- // If the item exceeds the height, start a new column
- /*
- if (!horizontal && legend.itemY + options.y +
- itemHeight > chart.chartHeight - spacingTop - spacingBottom) {
- legend.itemY = legend.initialItemY;
- legend.itemX += legend.maxItemWidth;
- legend.maxItemWidth = 0;
+ this.lastLineHeight = 0; // reset for next line (#915, #3976)
}
- */
// Set the edge positions
- legend.maxItemWidth = Math.max(legend.maxItemWidth, itemWidth);
- legend.lastItemY = itemMarginTop + legend.itemY + itemMarginBottom;
- legend.lastLineHeight = Math.max( // #915
+ this.lastItemY = itemMarginTop + this.itemY + itemMarginBottom;
+ this.lastLineHeight = Math.max( // #915
itemHeight,
- legend.lastLineHeight
+ this.lastLineHeight
);
// cache the position of the newly generated or reordered items
- item._legendItemPos = [legend.itemX, legend.itemY];
+ item._legendItemPos = [this.itemX, this.itemY];
// advance
if (horizontal) {
- legend.itemX += itemWidth;
+ this.itemX += itemWidth;
} else {
- legend.itemY += itemMarginTop + itemHeight + itemMarginBottom;
- legend.lastLineHeight = itemHeight;
+ this.itemY += itemMarginTop + itemHeight + itemMarginBottom;
+ this.lastLineHeight = itemHeight;
}
// the width of the widest item
- legend.offsetWidth = widthOption || Math.max(
+ this.offsetWidth = this.widthOption || Math.max(
(
- horizontal ? legend.itemX - padding - (item.checkbox ?
+ horizontal ? this.itemX - padding - (item.checkbox ?
// decrease by itemDistance only when no checkbox #4853
0 :
itemDistance
) : itemWidth
) + padding,
- legend.offsetWidth
+ this.offsetWidth
);
},
@@ -19102,19 +25841,29 @@
* Get all items, which is one item per series for most series and one
* item per point for pie series and its derivatives.
*
- * @return {Array.}
+ * @private
+ * @function Highcharts.Legend#getAllItems
+ *
+ * @return {Array}
* The current items in the legend.
+ *
+ * @fires Highcharts.Legend#event:afterGetAllItems
+ *
+ * @todo
+ * Make events official: Fires the event `afterGetAllItems`.
*/
- getAllItems: function() {
+ getAllItems: function () {
var allItems = [];
- each(this.chart.series, function(series) {
+
+ this.chart.series.forEach(function (series) {
var seriesOptions = series && series.options;
// Handle showInLegend. If the series is linked to another series,
// defaults to false.
if (series && pick(
- seriesOptions.showInLegend, !defined(seriesOptions.linkedTo) ? undefined : false, true
- )) {
+ seriesOptions.showInLegend,
+ !defined(seriesOptions.linkedTo) ? undefined : false, true
+ )) {
// Use points or series for the legend item depending on
// legendType
@@ -19122,40 +25871,73 @@
series.legendItems ||
(
seriesOptions.legendType === 'point' ?
- series.data :
- series
+ series.data :
+ series
)
);
}
});
+
+ fireEvent(this, 'afterGetAllItems', { allItems: allItems });
+
return allItems;
},
+ /**
+ * Get a short, three letter string reflecting the alignment and layout.
+ *
+ * @private
+ * @function Highcharts.Legend#getAlignment
+ *
+ * @return {string}
+ * The alignment, empty string if floating
+ */
+ getAlignment: function () {
+ var options = this.options;
+
+ // Use the first letter of each alignment option in order to detect
+ // the side. (#4189 - use charAt(x) notation instead of [x] for IE7)
+ if (this.proximate) {
+ return options.align.charAt(0) + 'tv';
+ }
+ return options.floating ? '' : (
+ options.align.charAt(0) +
+ options.verticalAlign.charAt(0) +
+ options.layout.charAt(0)
+ );
+ },
+
/**
* Adjust the chart margins by reserving space for the legend on only one
* side of the chart. If the position is set to a corner, top or bottom is
* reserved for horizontal legends and left or right for vertical ones.
*
* @private
+ * @function Highcharts.Legend#adjustMargins
+ *
+ * @param {Array} margin
+ *
+ * @param {number} spacing
*/
- adjustMargins: function(margin, spacing) {
+ adjustMargins: function (margin, spacing) {
var chart = this.chart,
options = this.options,
- // Use the first letter of each alignment option in order to detect
- // the side. (#4189 - use charAt(x) notation instead of [x] for IE7)
- alignment = options.align.charAt(0) +
- options.verticalAlign.charAt(0) +
- options.layout.charAt(0);
+ alignment = this.getAlignment(),
+ titleMargin = chart.options.title.margin !== undefined ?
+ chart.titleOffset +
+ chart.options.title.margin :
+ 0;
- if (!options.floating) {
+ if (alignment) {
- each([
+ ([
/(lth|ct|rth)/,
/(rtv|rm|rbv)/,
/(rbh|cb|lbh)/,
/(lbv|lm|ltv)/
- ], function(alignments, side) {
+ ]).forEach(function (alignments, side) {
if (alignments.test(alignment) && !defined(margin[side])) {
+
// Now we have detected on which side of the chart we should
// reserve space for the legend
chart[marginNames[side]] = Math.max(
@@ -19163,11 +25945,16 @@
(
chart.legend[
(side + 1) % 2 ? 'legendHeight' : 'legendWidth'
- ] + [1, -1, -1, 1][side] * options[
+ ] +
+ [1, -1, -1, 1][side] * options[
(side % 2) ? 'x' : 'y'
] +
pick(options.margin, 12) +
- spacing[side]
+ spacing[side] +
+ (
+ side === 0 &&
+ (chart.titleOffset === 0 ? 0 : titleMargin)
+ ) // #7428, #7894
)
);
}
@@ -19175,13 +25962,62 @@
}
},
+ /**
+ * @private
+ * @function Highcharts.Legend#proximatePositions
+ */
+ proximatePositions: function () {
+ var chart = this.chart,
+ boxes = [],
+ alignLeft = this.options.align === 'left';
+
+ this.allItems.forEach(function (item) {
+ var lastPoint,
+ height,
+ useFirstPoint = alignLeft;
+
+ if (item.xAxis && item.points) {
+
+ if (item.xAxis.options.reversed) {
+ useFirstPoint = !useFirstPoint;
+ }
+ lastPoint = H.find(
+ useFirstPoint ?
+ item.points :
+ item.points.slice(0).reverse(),
+ function (item) {
+ return H.isNumber(item.plotY);
+ }
+ );
+ height = item.legendGroup.getBBox().height;
+ boxes.push({
+ target: item.visible ?
+ (lastPoint ? lastPoint.plotY : item.xAxis.height) -
+ 0.3 * height :
+ chart.plotHeight,
+ size: height,
+ item: item
+ });
+ }
+ }, this);
+ H.distribute(boxes, chart.plotHeight);
+ boxes.forEach(function (box) {
+ box.item._legendItemPos[1] =
+ chart.plotTop - chart.spacing[0] + box.pos;
+ });
+
+ },
+
/**
* Render the legend. This method can be called both before and after
* `chart.render`. If called after, it will only rearrange items instead
* of creating new ones. Called internally on initial render and after
* redraws.
+ *
+ * @private
+ * @function Highcharts.Legend#render
*/
- render: function() {
+ render: function () {
var legend = this,
chart = legend.chart,
renderer = chart.renderer,
@@ -19192,23 +26028,40 @@
legendHeight,
box = legend.box,
options = legend.options,
- padding = legend.padding;
+ padding = legend.padding,
+ alignTo,
+ allowedWidth,
+ y;
legend.itemX = padding;
legend.itemY = legend.initialItemY;
legend.offsetWidth = 0;
legend.lastItemY = 0;
+ legend.widthOption = H.relativeLength(
+ options.width,
+ chart.spacingBox.width - padding
+ );
+
+ // Compute how wide the legend is allowed to be
+ allowedWidth = chart.spacingBox.width - 2 * padding - options.x;
+ if (['rm', 'lm'].indexOf(legend.getAlignment().substring(0, 2)) > -1) {
+ allowedWidth /= 2;
+ }
+ legend.maxLegendWidth = legend.widthOption || allowedWidth;
if (!legendGroup) {
- legend.group = legendGroup = renderer.g('legend')
- .attr({
- zIndex: 7
- })
+ /**
+ * SVG group of the legend.
+ *
+ * @readonly
+ * @name Highcharts.Legend#group
+ * @type {Highcharts.SVGElement}
+ */
+ legend.group = legendGroup = renderer.g('legend')
+ .attr({ zIndex: 7 })
.add();
legend.contentGroup = renderer.g()
- .attr({
- zIndex: 1
- }) // above background
+ .attr({ zIndex: 1 }) // above background
.add(legendGroup);
legend.scrollGroup = renderer.g()
.add(legend.contentGroup);
@@ -19220,7 +26073,7 @@
allItems = legend.getAllItems();
// sort by legendIndex
- stableSort(allItems, function(a, b) {
+ stableSort(allItems, function (a, b) {
return ((a.options && a.options.legendIndex) || 0) -
((b.options && b.options.legendIndex) || 0);
});
@@ -19230,17 +26083,29 @@
allItems.reverse();
}
+ /**
+ * All items for the legend, which is an array of series for most series
+ * and an array of points for pie series and its derivatives.
+ *
+ * @readonly
+ * @name Highcharts.Legend#allItems
+ * @type {Array}
+ */
legend.allItems = allItems;
legend.display = display = !!allItems.length;
- // render the items
+ // Render the items. First we run a loop to set the text and properties
+ // and read all the bounding boxes. The next loop computes the item
+ // positions based on the bounding boxes.
legend.lastLineHeight = 0;
- each(allItems, function(item) {
- legend.renderItem(item);
- });
+ legend.maxItemWidth = 0;
+ legend.totalItemWidth = 0;
+ legend.itemHeight = 0;
+ allItems.forEach(legend.renderItem, legend);
+ allItems.forEach(legend.layoutItem, legend);
// Get the box
- legendWidth = (options.width || legend.offsetWidth) + padding;
+ legendWidth = (legend.widthOption || legend.offsetWidth) + padding;
legendHeight = legend.lastItemY + legend.lastLineHeight +
legend.titleHeight;
legendHeight = legend.handleOverflow(legendHeight);
@@ -19248,6 +26113,13 @@
// Draw the border and/or background
if (!box) {
+ /**
+ * SVG element of the legend box.
+ *
+ * @readonly
+ * @name Highcharts.Legend#box
+ * @type {Highcharts.SVGElement}
+ */
legend.box = box = renderer.rect()
.addClass('highcharts-legend-box')
.attr({
@@ -19257,16 +26129,16 @@
box.isNew = true;
}
-
// Presentational
- box
- .attr({
- stroke: options.borderColor,
- 'stroke-width': options.borderWidth || 0,
- fill: options.backgroundColor || 'none'
- })
- .shadow(options.shadow);
-
+ if (!chart.styledMode) {
+ box
+ .attr({
+ stroke: options.borderColor,
+ 'stroke-width': options.borderWidth || 0,
+ fill: options.backgroundColor || 'none'
+ })
+ .shadow(options.shadow);
+ }
if (legendWidth > 0 && legendHeight > 0) {
box[box.isNew ? 'attr' : 'animate'](
@@ -19283,27 +26155,40 @@
// hide the border if no items
box[display ? 'show' : 'hide']();
-
+ // Open for responsiveness
+ if (chart.styledMode && legendGroup.getStyle('display') === 'none') {
+ legendWidth = legendHeight = 0;
+ }
legend.legendWidth = legendWidth;
legend.legendHeight = legendHeight;
- // Now that the legend width and height are established, put the items
- // in the final position
- each(allItems, function(item) {
- legend.positionItem(item);
- });
-
if (display) {
+ // If aligning to the top and the layout is horizontal, adjust for
+ // the title (#7428)
+ alignTo = chart.spacingBox;
+ if (/(lth|ct|rth)/.test(legend.getAlignment())) {
+
+ y = alignTo.y + chart.titleOffset;
+
+ alignTo = merge(alignTo, {
+ y: chart.titleOffset > 0 ?
+ y += chart.options.title.margin : y
+ });
+ }
+
legendGroup.align(merge(options, {
width: legendWidth,
- height: legendHeight
- }), true, 'spacingBox');
+ height: legendHeight,
+ verticalAlign: this.proximate ? 'top' : options.verticalAlign
+ }), true, alignTo);
}
- if (!chart.isResizing) {
- this.positionCheckboxes();
+ if (!this.proximate) {
+ this.positionItems();
}
+
+ fireEvent(this, 'afterRender');
},
/**
@@ -19311,8 +26196,13 @@
* below the legend.
*
* @private
+ * @function Highcharts.Legend#handleOverflow
+ *
+ * @param {number} legendHeight
+ *
+ * @return {number}
*/
- handleOverflow: function(legendHeight) {
+ handleOverflow: function (legendHeight) {
var legend = this,
chart = this.chart,
renderer = chart.renderer,
@@ -19321,7 +26211,7 @@
alignTop = options.verticalAlign === 'top',
padding = this.padding,
spaceHeight = chart.spacingBox.height +
- (alignTop ? -optionsY : optionsY) - padding,
+ (alignTop ? -optionsY : optionsY) - padding,
maxHeight = options.maxHeight,
clipHeight,
clipRect = this.clipRect,
@@ -19332,7 +26222,7 @@
pages = this.pages,
lastY,
allItems = this.allItems,
- clipToHeight = function(height) {
+ clipToHeight = function (height) {
if (typeof height === 'number') {
clipRect.attr({
height: height
@@ -19346,7 +26236,7 @@
if (legend.contentGroup.div) {
legend.contentGroup.div.style.clip = height ?
'rect(' + padding + 'px,9999px,' +
- (padding + height) + 'px,0)' :
+ (padding + height) + 'px,0)' :
'auto';
}
};
@@ -19375,7 +26265,7 @@
// Fill pages with Y positions so that the top of each a legend item
// defines the scroll top for each page (#2098)
- each(allItems, function(item, i) {
+ allItems.forEach(function (item, i) {
var y = item._legendItemPos[1],
h = Math.round(item.legendItem.getBBox().height),
len = pages.length;
@@ -19386,10 +26276,21 @@
len++;
}
- if (i === allItems.length - 1 &&
- y + h - pages[len - 1] > clipHeight) {
+ // Keep track of which page each item is on
+ item.pageIx = len - 1;
+ if (lastY) {
+ allItems[i - 1].pageIx = len - 1;
+ }
+
+ if (
+ i === allItems.length - 1 &&
+ y + h - pages[len - 1] > clipHeight &&
+ y !== lastY // #2617
+ ) {
pages.push(y);
+ item.pageIx = len;
}
+
if (y !== lastY) {
lastY = y;
}
@@ -19408,9 +26309,7 @@
// Add navigation elements
if (!nav) {
this.nav = nav = renderer.g()
- .attr({
- zIndex: 1
- })
+ .attr({ zIndex: 1 })
.add(this.group);
this.up = renderer
@@ -19421,17 +26320,18 @@
arrowSize,
arrowSize
)
- .on('click', function() {
+ .on('click', function () {
legend.scroll(-1, animation);
})
.add(nav);
this.pager = renderer.text('', 15, 10)
- .addClass('highcharts-legend-navigation')
+ .addClass('highcharts-legend-navigation');
- .css(navOptions.style)
-
- .add(nav);
+ if (!chart.styledMode) {
+ this.pager.css(navOptions.style);
+ }
+ this.pager.add(nav);
this.down = renderer
.symbol(
@@ -19441,7 +26341,7 @@
arrowSize,
arrowSize
)
- .on('click', function() {
+ .on('click', function () {
legend.scroll(1, animation);
})
.add(nav);
@@ -19452,7 +26352,7 @@
legendHeight = spaceHeight;
- // Reset
+ // Reset
} else if (nav) {
clipToHeight();
this.nav = nav.destroy(); // #6322
@@ -19467,12 +26367,17 @@
/**
* Scroll the legend by a number of pages.
- * @param {Number} scrollBy
- * The number of pages to scroll.
- * @param {AnimationOptions} animation
- * Whether and how to apply animation.
+ *
+ * @private
+ * @function Highcharts.Legend#scroll
+ *
+ * @param {number} scrollBy
+ * The number of pages to scroll.
+ *
+ * @param {Highcharts.AnimationOptionsObject} animation
+ * Whether and how to apply animation.
*/
- scroll: function(scrollBy, animation) {
+ scroll: function (scrollBy, animation) {
var pages = this.pages,
pageCount = pages.length,
currentPage = this.currentPage + scrollBy,
@@ -19499,7 +26404,8 @@
});
this.up.attr({
'class': currentPage === 1 ?
- 'highcharts-legend-nav-inactive' : 'highcharts-legend-nav-active'
+ 'highcharts-legend-nav-inactive' :
+ 'highcharts-legend-nav-active'
});
pager.attr({
text: currentPage + '/' + pageCount
@@ -19507,27 +26413,32 @@
this.down.attr({
'x': 18 + this.pager.getBBox().width, // adjust to text width
'class': currentPage === pageCount ?
- 'highcharts-legend-nav-inactive' : 'highcharts-legend-nav-active'
+ 'highcharts-legend-nav-inactive' :
+ 'highcharts-legend-nav-active'
});
-
- this.up
- .attr({
- fill: currentPage === 1 ?
- navOptions.inactiveColor : navOptions.activeColor
- })
- .css({
- cursor: currentPage === 1 ? 'default' : 'pointer'
- });
- this.down
- .attr({
- fill: currentPage === pageCount ?
- navOptions.inactiveColor : navOptions.activeColor
- })
- .css({
- cursor: currentPage === pageCount ? 'default' : 'pointer'
- });
-
+ if (!this.chart.styledMode) {
+ this.up
+ .attr({
+ fill: currentPage === 1 ?
+ navOptions.inactiveColor :
+ navOptions.activeColor
+ })
+ .css({
+ cursor: currentPage === 1 ? 'default' : 'pointer'
+ });
+ this.down
+ .attr({
+ fill: currentPage === pageCount ?
+ navOptions.inactiveColor :
+ navOptions.activeColor
+ })
+ .css({
+ cursor: currentPage === pageCount ?
+ 'default' :
+ 'pointer'
+ });
+ }
this.scrollOffset = -pages[currentPage - 1] + this.initialItemY;
@@ -19543,31 +26454,39 @@
};
- /*
- * LegendSymbolMixin
+ /**
+ * Legend symbol mixin.
+ *
+ * @private
+ * @mixin Highcharts.LegendSymbolMixin
*/
-
H.LegendSymbolMixin = {
/**
* Get the series' symbol in the legend
*
- * @param {Object} legend The legend object
- * @param {Object} item The series (this) or point
+ * @private
+ * @function Highcharts.LegendSymbolMixin.drawRectangle
+ *
+ * @param {Highcharts.Legend} legend
+ * The legend object
+ *
+ * @param {Highcharts.Point|Highcharts.Series} item
+ * The series (this) or point
*/
- drawRectangle: function(legend, item) {
+ drawRectangle: function (legend, item) {
var options = legend.options,
symbolHeight = legend.symbolHeight,
square = options.squareSymbol,
symbolWidth = square ? symbolHeight : legend.symbolWidth;
item.legendSymbol = this.chart.renderer.rect(
- square ? (legend.symbolWidth - symbolHeight) / 2 : 0,
- legend.baseline - symbolHeight + 1, // #3988
- symbolWidth,
- symbolHeight,
- pick(legend.options.symbolRadius, symbolHeight / 2)
- )
+ square ? (legend.symbolWidth - symbolHeight) / 2 : 0,
+ legend.baseline - symbolHeight + 1, // #3988
+ symbolWidth,
+ symbolHeight,
+ pick(legend.options.symbolRadius, symbolHeight / 2)
+ )
.addClass('highcharts-point')
.attr({
zIndex: 3
@@ -19580,9 +26499,13 @@
* to create custom symbols through
* Highcharts.seriesTypes[type].prototype.drawLegendSymbols.
*
- * @param {Object} legend The legend object
+ * @private
+ * @function Highcharts.LegendSymbolMixin.drawLineMarker
+ *
+ * @param {Highcharts.Legend} legend
+ * The legend object.
*/
- drawLineMarker: function(legend) {
+ drawLineMarker: function (legend) {
var options = this.options,
markerOptions = options.marker,
@@ -19594,33 +26517,33 @@
renderer = this.chart.renderer,
legendItemGroup = this.legendGroup,
verticalCenter = legend.baseline -
- Math.round(legend.fontMetrics.b * 0.3),
+ Math.round(legend.fontMetrics.b * 0.3),
attr = {};
// Draw the line
-
- attr = {
- 'stroke-width': options.lineWidth || 0
- };
- if (options.dashStyle) {
- attr.dashstyle = options.dashStyle;
+ if (!this.chart.styledMode) {
+ attr = {
+ 'stroke-width': options.lineWidth || 0
+ };
+ if (options.dashStyle) {
+ attr.dashstyle = options.dashStyle;
+ }
}
-
this.legendLine = renderer.path([
- 'M',
- 0,
- verticalCenter,
- 'L',
- symbolWidth,
- verticalCenter
- ])
+ 'M',
+ 0,
+ verticalCenter,
+ 'L',
+ symbolWidth,
+ verticalCenter
+ ])
.addClass('highcharts-graph')
.attr(attr)
.add(legendItemGroup);
// Draw the marker
- if (markerOptions && markerOptions.enabled !== false) {
+ if (markerOptions && markerOptions.enabled !== false && symbolWidth) {
// Do not allow the marker to be larger than the symbolHeight
radius = Math.min(
@@ -19638,13 +26561,13 @@
}
this.legendSymbol = legendSymbol = renderer.symbol(
- this.symbol,
- (symbolWidth / 2) - radius,
- verticalCenter - radius,
- 2 * radius,
- 2 * radius,
- markerOptions
- )
+ this.symbol,
+ (symbolWidth / 2) - radius,
+ verticalCenter - radius,
+ 2 * radius,
+ 2 * radius,
+ markerOptions
+ )
.addClass('highcharts-point')
.add(legendItemGroup);
legendSymbol.isMarker = true;
@@ -19657,11 +26580,14 @@
// Explore if there's a general cause for this. The problem may be related
// to nested group elements, as the legend item texts are within 4 group
// elements.
- if (/Trident\/7\.0/.test(win.navigator.userAgent) || isFirefox) {
- wrap(Highcharts.Legend.prototype, 'positionItem', function(proceed, item) {
+ if (
+ /Trident\/7\.0/.test(win.navigator && win.navigator.userAgent) ||
+ isFirefox
+ ) {
+ wrap(Highcharts.Legend.prototype, 'positionItem', function (proceed, item) {
var legend = this,
// If chart destroyed in sync, this is undefined (#2030)
- runPositionItem = function() {
+ runPositionItem = function () {
if (item._legendItemPos) {
proceed.call(legend, item);
}
@@ -19671,17 +26597,72 @@
runPositionItem();
// Do it after to work around the core issue
- setTimeout(runPositionItem);
+ if (!legend.bubbleLegend) {
+ setTimeout(runPositionItem);
+ }
});
}
}(Highcharts));
- (function(H) {
+ (function (H) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+ /**
+ * Callback for chart constructors.
+ *
+ * @callback Highcharts.ChartCallbackFunction
+ *
+ * @param {Highcharts.Chart} chart
+ * Created chart.
+ */
+
+ /**
+ * The chart title. The title has an `update` method that allows modifying the
+ * options directly or indirectly via `chart.update`.
+ *
+ * @interface Highcharts.TitleObject
+ * @extends Highcharts.SVGElement
+ *//**
+ * Modify options for the title.
+ *
+ * @function Highcharts.TitleObject#update
+ *
+ * @param {Highcharts.TitleOptions} titleOptions
+ * Options to modify.
+ *
+ * @param {boolean} [redraw=true]
+ * Whether to redraw the chart after the title is altered. If doing more
+ * operations on the chart, it is a good idea to set redraw to false and
+ * call {@link Chart#redraw} after.
+ */
+
+ /**
+ * The chart subtitle. The subtitle has an `update` method that
+ * allows modifying the options directly or indirectly via
+ * `chart.update`.
+ *
+ * @interface Highcharts.SubtitleObject
+ * @extends Highcharts.SVGElement
+ *//**
+ * Modify options for the subtitle.
+ *
+ * @function Highcharts.SubtitleObject#update
+ *
+ * @param {Highcharts.SubtitleOptions} subtitleOptions
+ * Options to modify.
+ *
+ * @param {boolean} [redraw=true]
+ * Whether to redraw the chart after the subtitle is altered. If doing
+ * more operations on the chart, it is a good idea to set redraw to false
+ * and call {@link Chart#redraw} after.
+ */
+
+
+
var addEvent = H.addEvent,
animate = H.animate,
animObject = H.animObject,
@@ -19694,11 +26675,9 @@
charts = H.charts,
css = H.css,
defined = H.defined,
- each = H.each,
extend = H.extend,
find = H.find,
fireEvent = H.fireEvent,
- grep = H.grep,
isNumber = H.isNumber,
isObject = H.isObject,
isString = H.isString,
@@ -19712,49 +26691,43 @@
removeEvent = H.removeEvent,
seriesTypes = H.seriesTypes,
splat = H.splat,
- svg = H.svg,
syncTimeout = H.syncTimeout,
win = H.win;
+
/**
* The Chart class. The recommended constructor is {@link Highcharts#chart}.
- * @class Highcharts.Chart
- * @param {String|HTMLDOMElement} renderTo
- * The DOM element to render to, or its id.
- * @param {Options} options
- * The chart options structure.
- * @param {Function} [callback]
- * Function to run when the chart has loaded and and all external images
- * are loaded. Defining a {@link
- * https://api.highcharts.com/highcharts/chart.events.load|chart.event.load}
- * handler is equivalent.
*
* @example
* var chart = Highcharts.chart('container', {
- * title: {
- * text: 'My chart'
- * },
- * series: [{
- * data: [1, 3, 2, 4]
- * }]
+ * title: {
+ * text: 'My chart'
+ * },
+ * series: [{
+ * data: [1, 3, 2, 4]
+ * }]
* })
+ *
+ * @class
+ * @name Highcharts.Chart
+ *
+ * @param {string|Highcharts.HTMLDOMElement} [renderTo]
+ * The DOM element to render to, or its id.
+ *
+ * @param {Highcharts.Options} options
+ * The chart options structure.
+ *
+ * @param {Highcharts.ChartCallbackFunction} [callback]
+ * Function to run when the chart has loaded and and all external images
+ * are loaded. Defining a
+ * [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load)
+ * handler is equivalent.
*/
- var Chart = H.Chart = function() {
+ var Chart = H.Chart = function () {
this.getArgs.apply(this, arguments);
};
/**
- * Factory function for basic charts.
- *
- * @function #chart
- * @memberOf Highcharts
- * @param {String|HTMLDOMElement} renderTo - The DOM element to render to, or
- * its id.
- * @param {Options} options - The chart options structure.
- * @param {Function} [callback] - Function to run when the chart has loaded and
- * and all external images are loaded. Defining a {@link
- * https://api.highcharts.com/highcharts/chart.events.load|chart.event.load}
- * handler is equivalent.
- * @return {Highcharts.Chart} - Returns the Chart object.
+ * Factory function for basic charts.
*
* @example
* // Render a chart in to div#container
@@ -19766,8 +26739,25 @@
* data: [1, 3, 2, 4]
* }]
* });
+ *
+ * @function Highcharts.chart
+ *
+ * @param {string|Highcharts.HTMLDOMElement} [renderTo]
+ * The DOM element to render to, or its id.
+ *
+ * @param {Highcharts.Options} options
+ * The chart options structure.
+ *
+ * @param {Highcharts.ChartCallbackFunction} [callback]
+ * Function to run when the chart has loaded and and all external images
+ * are loaded. Defining a
+ * [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load)
+ * handler is equivalent.
+ *
+ * @return {Highcharts.Chart}
+ * Returns the Chart object.
*/
- H.chart = function(a, b, c) {
+ H.chart = function (a, b, c) {
return new Chart(a, b, c);
};
@@ -19780,9 +26770,18 @@
* Handle the arguments passed to the constructor.
*
* @private
- * @returns {Array} Arguments without renderTo
+ * @function Highcharts.Chart#getArgs
+ *
+ * @param {...Array<*>} arguments
+ * All arguments for the constructor.
+ *
+ * @return {Array<*>}
+ * Passed arguments without renderTo.
+ *
+ * @fires Highcharts.Chart#event:init
+ * @fires Highcharts.Chart#event:afterInit
*/
- getArgs: function() {
+ getArgs: function () {
var args = [].slice.call(arguments);
// Remove the optional first argument, renderTo, and
@@ -19796,158 +26795,191 @@
/**
* Overridable function that initializes the chart. The constructor's
* arguments are passed on directly.
+ *
+ * @function Highcharts.Chart#init
+ *
+ * @param {Highcharts.Options} userOptions
+ * Custom options.
+ *
+ * @param {Function} [callback]
+ * Function to run when the chart has loaded and and all external
+ * images are loaded.
+ *
+ * @fires Highcharts.Chart#event:init
+ * @fires Highcharts.Chart#event:afterInit
*/
- init: function(userOptions, callback) {
+ init: function (userOptions, callback) {
// Handle regular options
var options,
type,
- seriesOptions = userOptions.series, // skip merging data points to increase performance
+ // skip merging data points to increase performance
+ seriesOptions = userOptions.series,
userPlotOptions = userOptions.plotOptions || {};
- userOptions.series = null;
- options = merge(defaultOptions, userOptions); // do the merge
+ // Fire the event with a default function
+ fireEvent(this, 'init', { args: arguments }, function () {
- // Override (by copy of user options) or clear tooltip options
- // in chart.options.plotOptions (#6218)
- for (type in options.plotOptions) {
- options.plotOptions[type].tooltip = (
- userPlotOptions[type] &&
- merge(userPlotOptions[type].tooltip) // override by copy
- ) || undefined; // or clear
- }
- // User options have higher priority than default options (#6218).
- // In case of exporting: path is changed
- options.tooltip.userOptions = (userOptions.chart &&
- userOptions.chart.forExport && userOptions.tooltip.userOptions) ||
- userOptions.tooltip;
+ userOptions.series = null;
+ options = merge(defaultOptions, userOptions); // do the merge
- options.series = userOptions.series = seriesOptions; // set back the series data
- this.userOptions = userOptions;
+ // Override (by copy of user options) or clear tooltip options
+ // in chart.options.plotOptions (#6218)
+ for (type in options.plotOptions) {
+ options.plotOptions[type].tooltip = (
+ userPlotOptions[type] &&
+ merge(userPlotOptions[type].tooltip) // override by copy
+ ) || undefined; // or clear
+ }
+ // User options have higher priority than default options
+ // (#6218). In case of exporting: path is changed
+ options.tooltip.userOptions = (
+ userOptions.chart &&
+ userOptions.chart.forExport &&
+ userOptions.tooltip.userOptions
+ ) || userOptions.tooltip;
- var optionsChart = options.chart;
+ // set back the series data
+ options.series = userOptions.series = seriesOptions;
+ this.userOptions = userOptions;
- var chartEvents = optionsChart.events;
+ var optionsChart = options.chart;
- this.margin = [];
- this.spacing = [];
+ var chartEvents = optionsChart.events;
- this.bounds = {
- h: {},
- v: {}
- }; // Pixel data bounds for touch zoom
+ this.margin = [];
+ this.spacing = [];
- // An array of functions that returns labels that should be considered
- // for anti-collision
- this.labelCollectors = [];
+ // Pixel data bounds for touch zoom
+ this.bounds = { h: {}, v: {} };
- this.callback = callback;
- this.isResizing = 0;
+ // An array of functions that returns labels that should be
+ // considered for anti-collision
+ this.labelCollectors = [];
- /**
- * The options structure for the chart. It contains members for the sub
- * elements like series, legend, tooltip etc.
- *
- * @memberof Highcharts.Chart
- * @name options
- * @type {Options}
- */
- this.options = options;
- /**
- * All the axes in the chart.
- *
- * @memberof Highcharts.Chart
- * @name axes
- * @see Highcharts.Chart.xAxis
- * @see Highcharts.Chart.yAxis
- * @type {Array.}
- */
- this.axes = [];
+ this.callback = callback;
+ this.isResizing = 0;
- /**
- * All the current series in the chart.
- *
- * @memberof Highcharts.Chart
- * @name series
- * @type {Array.}
- */
- this.series = [];
+ /**
+ * The options structure for the chart. It contains members for
+ * the sub elements like series, legend, tooltip etc.
+ *
+ * @name Highcharts.Chart#options
+ * @type {Highcharts.Options}
+ */
+ this.options = options;
- /**
- * The chart title. The title has an `update` method that allows
- * modifying the options directly or indirectly via `chart.update`.
- *
- * @memberof Highcharts.Chart
- * @name title
- * @type Object
- *
- * @sample highcharts/members/title-update/
- * Updating titles
- */
+ /**
+ * All the axes in the chart.
+ *
+ * @see Highcharts.Chart.xAxis
+ * @see Highcharts.Chart.yAxis
+ *
+ * @name Highcharts.Chart#axes
+ * @type {Array}
+ */
+ this.axes = [];
- /**
- * The chart subtitle. The subtitle has an `update` method that allows
- * modifying the options directly or indirectly via `chart.update`.
- *
- * @memberof Highcharts.Chart
- * @name subtitle
- * @type Object
- */
+ /**
+ * All the current series in the chart.
+ *
+ * @name Highcharts.Chart#series
+ * @type {Array}
+ */
+ this.series = [];
+ /**
+ * The `Time` object associated with the chart. Since v6.0.5,
+ * time settings can be applied individually for each chart. If
+ * no individual settings apply, the `Time` object is shared by
+ * all instances.
+ *
+ * @name Highcharts.Chart#time
+ * @type {Highcharts.Time}
+ */
+ this.time =
+ userOptions.time && Object.keys(userOptions.time).length ?
+ new H.Time(userOptions.time) :
+ H.time;
+
+ /**
+ * Whether the chart is in styled mode, meaning all presentatinoal
+ * attributes are avoided.
+ *
+ * @name Highcharts.Chart#styledMode
+ * @type {boolean}
+ */
+ this.styledMode = optionsChart.styledMode;
+ this.hasCartesianSeries = optionsChart.showAxes;
+ var chart = this;
- this.hasCartesianSeries = optionsChart.showAxes;
+ // Add the chart to the global lookup
+ chart.index = charts.length;
- var chart = this;
+ charts.push(chart);
+ H.chartCount++;
- // Add the chart to the global lookup
- chart.index = charts.length;
+ // Chart event handlers
+ if (chartEvents) {
+ objectEach(chartEvents, function (event, eventType) {
+ addEvent(chart, eventType, event);
+ });
+ }
- charts.push(chart);
- H.chartCount++;
+ /**
+ * A collection of the X axes in the chart.
+ *
+ * @name Highcharts.Chart#xAxis
+ * @type {Array}
+ */
+ chart.xAxis = [];
- // Chart event handlers
- if (chartEvents) {
- objectEach(chartEvents, function(event, eventType) {
- addEvent(chart, eventType, event);
- });
- }
+ /**
+ * A collection of the Y axes in the chart.
+ *
+ * @name Highcharts.Chart#yAxis
+ * @type {Array}
+ *
+ * @todo
+ * Make events official: Fire the event `afterInit`.
+ */
+ chart.yAxis = [];
- /**
- * A collection of the X axes in the chart.
- * @type {Array.}
- * @name xAxis
- * @memberOf Highcharts.Chart
- */
- chart.xAxis = [];
- /**
- * A collection of the Y axes in the chart.
- * @type {Array.}
- * @name yAxis
- * @memberOf Highcharts.Chart
- */
- chart.yAxis = [];
+ chart.pointCount = chart.colorCounter = chart.symbolCounter = 0;
- chart.pointCount = chart.colorCounter = chart.symbolCounter = 0;
+ // Fire after init but before first render, before axes and series
+ // have been initialized.
+ fireEvent(chart, 'afterInit');
- chart.firstRender();
+ chart.firstRender();
+ });
},
/**
* Internal function to unitialize an individual series.
*
* @private
+ * @function Highcharts.Chart#initSeries
+ *
+ * @param {Highcharts.ChartOptions} options
+ *
+ * @return {Highcharts.Series}
*/
- initSeries: function(options) {
+ initSeries: function (options) {
var chart = this,
optionsChart = chart.options.chart,
- type = options.type || optionsChart.type || optionsChart.defaultSeriesType,
+ type = (
+ options.type ||
+ optionsChart.type ||
+ optionsChart.defaultSeriesType
+ ),
series,
Constr = seriesTypes[type];
// No such series type
if (!Constr) {
- H.error(17, true);
+ H.error(17, true, chart);
}
series = new Constr();
@@ -19961,18 +26993,19 @@
* #6112). This function is called on series initialization and destroy.
*
* @private
+ * @function Highcharts.Series#orderSeries
*
- * @param {number} fromIndex
- * If this is given, only the series above this index are handled.
+ * @param {number} fromIndex
+ * If this is given, only the series above this index are handled.
*/
- orderSeries: function(fromIndex) {
+ orderSeries: function (fromIndex) {
var series = this.series,
i = fromIndex || 0;
+
for (; i < series.length; i++) {
if (series[i]) {
series[i].index = i;
- series[i].name = series[i].name ||
- 'Series ' + (series[i].index + 1);
+ series[i].name = series[i].getName();
}
}
},
@@ -19980,17 +27013,21 @@
/**
* Check whether a given point is within the plot area.
*
- * @param {Number} plotX
- * Pixel x relative to the plot area.
- * @param {Number} plotY
- * Pixel y relative to the plot area.
- * @param {Boolean} inverted
- * Whether the chart is inverted.
+ * @function Highcharts.Chart#isInsidePlot
+ *
+ * @param {number} plotX
+ * Pixel x relative to the plot area.
+ *
+ * @param {number} plotY
+ * Pixel y relative to the plot area.
+ *
+ * @param {boolean} inverted
+ * Whether the chart is inverted.
*
- * @return {Boolean}
+ * @return {boolean}
* Returns true if the given point is inside the plot area.
*/
- isInsidePlot: function(plotX, plotY, inverted) {
+ isInsidePlot: function (plotX, plotY, inverted) {
var x = inverted ? plotY : plotX,
y = inverted ? plotX : plotY;
@@ -20009,15 +27046,28 @@
* cases it is a waste of resources to redraw the chart for each new point
* added. So you add the points and call `chart.redraw()` after.
*
- * @param {AnimationOptions} animation
- * If or how to apply animation to the redraw.
+ * @function Highcharts.Chart#redraw
+ *
+ * @param {boolean|Highcharts.AnimationOptionsObject} [animation]
+ * If or how to apply animation to the redraw.
+ *
+ * @fires Highcharts.Chart#event:afterSetExtremes
+ * @fires Highcharts.Chart#event:beforeRedraw
+ * @fires Highcharts.Chart#event:predraw
+ * @fires Highcharts.Chart#event:redraw
+ * @fires Highcharts.Chart#event:render
+ * @fires Highcharts.Chart#event:updatedData
*/
- redraw: function(animation) {
+ redraw: function (animation) {
+
+ fireEvent(this, 'beforeRedraw');
+
var chart = this,
axes = chart.axes,
series = chart.series,
pointer = chart.pointer,
legend = chart.legend,
+ legendUserOptions = chart.userOptions.legend,
redrawLegend = chart.isDirtyLegend,
hasStackedSeries,
hasDirtyStacks,
@@ -20068,13 +27118,21 @@
}
// Handle updated data in the series
- each(series, function(serie) {
+ series.forEach(function (serie) {
if (serie.isDirty) {
if (serie.options.legendType === 'point') {
if (serie.updateTotals) {
serie.updateTotals();
}
redrawLegend = true;
+ } else if (
+ legendUserOptions &&
+ (
+ legendUserOptions.labelFormatter ||
+ legendUserOptions.labelFormat
+ )
+ ) {
+ redrawLegend = true; // #2165
}
}
if (serie.isDirtyData) {
@@ -20083,7 +27141,7 @@
});
// handle added or removed series
- if (redrawLegend && legend.options.enabled) { // series or pie points are added or removed
+ if (redrawLegend && legend && legend.options.enabled) {
// draw legend graphics
legend.render();
@@ -20098,7 +27156,7 @@
if (hasCartesianSeries) {
// set axes scales
- each(axes, function(axis) {
+ axes.forEach(function (axis) {
axis.updateNames();
axis.setScale();
});
@@ -20108,21 +27166,28 @@
if (hasCartesianSeries) {
// If one axis is dirty, all axes must be redrawn (#792, #2169)
- each(axes, function(axis) {
+ axes.forEach(function (axis) {
if (axis.isDirty) {
isDirtyBox = true;
}
});
// redraw axes
- each(axes, function(axis) {
+ axes.forEach(function (axis) {
// Fire 'afterSetExtremes' only if extremes are set
var key = axis.min + ',' + axis.max;
+
if (axis.extKey !== key) { // #821, #4452
axis.extKey = key;
- afterRedraw.push(function() { // prevent a recursive call to chart.redraw() (#1119)
- fireEvent(axis, 'afterSetExtremes', extend(axis.eventArgs, axis.getExtremes())); // #747, #751
+
+ // prevent a recursive call to chart.redraw() (#1119)
+ afterRedraw.push(function () {
+ fireEvent(
+ axis,
+ 'afterSetExtremes',
+ extend(axis.eventArgs, axis.getExtremes())
+ ); // #747, #751
delete axis.eventArgs;
});
}
@@ -20142,7 +27207,7 @@
fireEvent(chart, 'predraw');
// redraw affected series
- each(series, function(serie) {
+ series.forEach(function (serie) {
if ((isDirtyBox || serie.isDirty) && serie.visible) {
serie.redraw();
}
@@ -20168,7 +27233,7 @@
}
// Fire callbacks that are put on hold until after the redraw
- each(afterRedraw, function(callback) {
+ afterRedraw.forEach(function (callback) {
callback.call();
});
},
@@ -20176,13 +27241,19 @@
/**
* Get an axis, series or point object by `id` as given in the configuration
* options. Returns `undefined` if no item is found.
- * @param id {String} The id as given in the configuration options.
- * @return {Highcharts.Axis|Highcharts.Series|Highcharts.Point|undefined}
- * The retrieved item.
+ *
* @sample highcharts/plotoptions/series-id/
* Get series by id
+ *
+ * @function Highcharts.Chart#get
+ *
+ * @param {string} id
+ * The id as given in the configuration options.
+ *
+ * @return {Highcharts.Axis|Highcharts.Series|Highcharts.Point|undefined}
+ * The retrieved item.
*/
- get: function(id) {
+ get: function (id) {
var ret,
series = this.series,
@@ -20211,71 +27282,91 @@
* Create the Axis instances based on the config options.
*
* @private
+ * @function Highcharts.Chart#getAxes
+ *
+ * @fires Highcharts.Chart#event:afterGetAxes
+ * @fires Highcharts.Chart#event:getAxes
*/
- getAxes: function() {
+ getAxes: function () {
var chart = this,
options = this.options,
xAxisOptions = options.xAxis = splat(options.xAxis || {}),
yAxisOptions = options.yAxis = splat(options.yAxis || {}),
optionsArray;
+ fireEvent(this, 'getAxes');
+
// make sure the options are arrays and add some members
- each(xAxisOptions, function(axis, i) {
+ xAxisOptions.forEach(function (axis, i) {
axis.index = i;
axis.isX = true;
});
- each(yAxisOptions, function(axis, i) {
+ yAxisOptions.forEach(function (axis, i) {
axis.index = i;
});
// concatenate all axis options into one array
optionsArray = xAxisOptions.concat(yAxisOptions);
- each(optionsArray, function(axisOptions) {
+ optionsArray.forEach(function (axisOptions) {
new Axis(chart, axisOptions); // eslint-disable-line no-new
});
+
+ fireEvent(this, 'afterGetAxes');
},
/**
* Returns an array of all currently selected points in the chart. Points
- * can be selected by clicking or programmatically by the {@link
- * Highcharts.Point#select} function.
- *
- * @return {Array.}
- * The currently selected points.
+ * can be selected by clicking or programmatically by the
+ * {@link Highcharts.Point#select}
+ * function.
*
* @sample highcharts/plotoptions/series-allowpointselect-line/
* Get selected points
+ *
+ * @function Highcharts.Chart#getSelectedPoints
+ *
+ * @return {Array}
+ * The currently selected points.
*/
- getSelectedPoints: function() {
+ getSelectedPoints: function () {
var points = [];
- each(this.series, function(serie) {
- // series.data - for points outside of viewed range (#6445)
- points = points.concat(grep(serie.data || [], function(point) {
- return point.selected;
- }));
+
+ this.series.forEach(function (serie) {
+ // For one-to-one points inspect series.data in order to retrieve
+ // points outside the visible range (#6445). For grouped data,
+ // inspect the generated series.points.
+ points = points.concat(
+ (serie[serie.hasGroupedData ? 'points' : 'data'] || []).filter(
+ function (point) {
+ return point.selected;
+ }
+ )
+ );
});
return points;
},
/**
* Returns an array of all currently selected series in the chart. Series
- * can be selected either programmatically by the {@link
- * Highcharts.Series#select} function or by checking the checkbox next to
- * the legend item if {@link
- * https://api.highcharts.com/highcharts/plotOptions.series.showCheckbox|
- * series.showCheckBox} is true.
- *
- * @return {Array.}
- * The currently selected series.
+ * can be selected either programmatically by the
+ * {@link Highcharts.Series#select}
+ * function or by checking the checkbox next to the legend item if
+ * [series.showCheckBox](https://api.highcharts.com/highcharts/plotOptions.series.showCheckbox)
+ * is true.
*
* @sample highcharts/members/chart-getselectedseries/
* Get selected series
+ *
+ * @function Highcharts.Chart#getSelectedSeries
+ *
+ * @return {Array}
+ * The currently selected series.
*/
- getSelectedSeries: function() {
- return grep(this.series, function(serie) {
+ getSelectedSeries: function () {
+ return this.series.filter(function (serie) {
return serie.selected;
});
},
@@ -20283,56 +27374,80 @@
/**
* Set a new title or subtitle for the chart.
*
- * @param titleOptions {TitleOptions}
- * New title options. The title text itself is set by the
- * `titleOptions.text` property.
- * @param subtitleOptions {SubtitleOptions}
- * New subtitle options. The subtitle text itself is set by the
- * `subtitleOptions.text` property.
- * @param redraw {Boolean}
- * Whether to redraw the chart or wait for a later call to
- * `chart.redraw()`.
+ * @sample highcharts/members/chart-settitle/
+ * Set title text and styles
+ *
+ * @function Highcharts.Chart#setTitle
*
- * @sample highcharts/members/chart-settitle/ Set title text and styles
+ * @param {Highcharts.TitleOptions} titleOptions
+ * New title options. The title text itself is set by the
+ * `titleOptions.text` property.
*
+ * @param {Highcharts.SubtitleOptions} subtitleOptions
+ * New subtitle options. The subtitle text itself is set by the
+ * `subtitleOptions.text` property.
+ *
+ * @param {boolean} redraw
+ * Whether to redraw the chart or wait for a later call to
+ * `chart.redraw()`.
*/
- setTitle: function(titleOptions, subtitleOptions, redraw) {
+ setTitle: function (titleOptions, subtitleOptions, redraw) {
var chart = this,
options = chart.options,
+ styledMode = chart.styledMode,
chartTitleOptions,
chartSubtitleOptions;
chartTitleOptions = options.title = merge(
-
// Default styles
- {
+ !styledMode && {
style: {
color: '#333333',
fontSize: options.isStock ? '16px' : '18px' // #2944
}
},
-
options.title,
titleOptions
);
chartSubtitleOptions = options.subtitle = merge(
-
// Default styles
- {
+ !styledMode && {
style: {
color: '#666666'
}
},
-
options.subtitle,
subtitleOptions
);
- // add title and subtitle
- each([
+
+ // add title and subtitle
+
+ /**
+ * The chart title. The title has an `update` method that allows
+ * modifying the options directly or indirectly via
+ * `chart.update`.
+ *
+ * @sample highcharts/members/title-update/
+ * Updating titles
+ *
+ * @name Highcharts.Chart#title
+ * @type {Highcharts.TitleObject}
+ */
+
+ /**
+ * The chart subtitle. The subtitle has an `update` method that
+ * allows modifying the options directly or indirectly via
+ * `chart.update`.
+ *
+ * @name Highcharts.Chart#subtitle
+ * @type {Highcharts.SubtitleObject}
+ */
+
+ [
['title', titleOptions, chartTitleOptions],
['subtitle', subtitleOptions, chartSubtitleOptions]
- ], function(arr, i) {
+ ].forEach(function (arr, i) {
var name = arr[0],
title = chart[name],
titleOptions = arr[1],
@@ -20344,11 +27459,11 @@
if (chartTitleOptions && !title) {
chart[name] = chart.renderer.text(
- chartTitleOptions.text,
- 0,
- 0,
- chartTitleOptions.useHTML
- )
+ chartTitleOptions.text,
+ 0,
+ 0,
+ chartTitleOptions.useHTML
+ )
.attr({
align: chartTitleOptions.align,
'class': 'highcharts-' + name,
@@ -20357,14 +27472,14 @@
.add();
// Update methods, shortcut to Chart.setTitle
- chart[name].update = function(o) {
+ chart[name].update = function (o) {
chart.setTitle(!i && o, i && o);
};
-
// Presentational
- chart[name].css(chartTitleOptions.style);
-
+ if (!styledMode) {
+ chart[name].css(chartTitleOptions.style);
+ }
}
});
@@ -20373,32 +27488,35 @@
/**
* Internal function to lay out the chart titles and cache the full offset
- * height for use in `getMargins`. The result is stored in
+ * height for use in `getMargins`. The result is stored in
* `this.titleOffset`.
*
* @private
+ * @function Highcharts.Chart#layOutTitles
+ *
+ * @param {boolean} [redraw=true]
*/
- layOutTitles: function(redraw) {
+ layOutTitles: function (redraw) {
var titleOffset = 0,
requiresDirtyBox,
renderer = this.renderer,
spacingBox = this.spacingBox;
// Lay out the title and the subtitle respectively
- each(['title', 'subtitle'], function(key) {
+ ['title', 'subtitle'].forEach(function (key) {
var title = this[key],
titleOptions = this.options[key],
offset = key === 'title' ? -3 :
- // Floating subtitle (#6574)
- titleOptions.verticalAlign ? 0 : titleOffset + 2,
+ // Floating subtitle (#6574)
+ titleOptions.verticalAlign ? 0 : titleOffset + 2,
titleSize;
if (title) {
- titleSize = titleOptions.style.fontSize;
-
+ if (!this.styledMode) {
+ titleSize = titleOptions.style.fontSize;
+ }
titleSize = renderer.fontMetrics(titleSize, title).b;
-
title
.css({
width: (titleOptions.width ||
@@ -20422,7 +27540,7 @@
this.titleOffset = titleOffset; // used in getMargins
if (!this.isDirtyBox && requiresDirtyBox) {
- this.isDirtyBox = requiresDirtyBox;
+ this.isDirtyBox = this.isDirtyLegend = requiresDirtyBox;
// Redraw if necessary (#2719, #2744)
if (this.hasRendered && pick(redraw, true) && this.isDirtyBox) {
this.redraw();
@@ -20432,10 +27550,13 @@
/**
* Internal function to get the chart width and height according to options
- * and container size. Sets {@link Chart.chartWidth} and {@link
- * Chart.chartHeight}.
+ * and container size. Sets
+ * {@link Chart.chartWidth} and
+ * {@link Chart.chartHeight}.
+ *
+ * @function Highcharts.Chart#getChartSize
*/
- getChartSize: function() {
+ getChartSize: function () {
var chart = this,
optionsChart = chart.options.chart,
widthOption = optionsChart.width,
@@ -20453,9 +27574,8 @@
/**
* The current pixel width of the chart.
*
- * @name chartWidth
- * @memberOf Chart
- * @type {Number}
+ * @name Highcharts.Chart#chartWidth
+ * @type {number}
*/
chart.chartWidth = Math.max( // #1393
0,
@@ -20464,9 +27584,8 @@
/**
* The current pixel height of the chart.
*
- * @name chartHeight
- * @memberOf Chart
- * @type {Number}
+ * @name Highcharts.Chart#chartHeight
+ * @type {number}
*/
chart.chartHeight = Math.max(
0,
@@ -20485,13 +27604,15 @@
* size is retrieved, reset them. Used on first render and on redraws.
*
* @private
- *
- * @param {Boolean} revert
- * Revert to the saved original styles.
+ * @function Highcharts.Chart#temporaryDisplay
+ *
+ * @param {boolean} revert
+ * Revert to the saved original styles.
*/
- temporaryDisplay: function(revert) {
+ temporaryDisplay: function (revert) {
var node = this.renderTo,
tempStyle;
+
if (!revert) {
while (node && node.style) {
@@ -20551,9 +27672,13 @@
/**
* Set the {@link Chart.container|chart container's} class name, in
- * addition to `highcharts-container`.
+ * addition to `highcharts-container`.
+ *
+ * @function Highcharts.Chart#setClassName
+ *
+ * @param {string} className
*/
- setClassName: function(className) {
+ setClassName: function (className) {
this.container.className = 'highcharts-container ' + (className || '');
},
@@ -20562,8 +27687,11 @@
* container div to hold the chart.
*
* @private
+ * @function Highcharts.Chart#afterGetContainer
+ *
+ * @fires Highcharts.Chart#event:afterGetContainer
*/
- getContainer: function() {
+ getContainer: function () {
var chart = this,
container,
options = chart.options,
@@ -20588,7 +27716,7 @@
// Display an error if the renderTo is wrong
if (!renderTo) {
- H.error(13, true);
+ H.error(13, true, chart);
}
// If the container already holds a chart, destroy it. The check for
@@ -20625,31 +27753,37 @@
chartWidth = chart.chartWidth;
chartHeight = chart.chartHeight;
- // Create the inner container
-
- containerStyle = extend({
- position: 'relative',
- overflow: 'hidden', // needed for context menu (avoid scrollbars)
- // and content overflow in IE
- width: chartWidth + 'px',
- height: chartHeight + 'px',
- textAlign: 'left',
- lineHeight: 'normal', // #427
- zIndex: 0, // #1072
- '-webkit-tap-highlight-color': 'rgba(0,0,0,0)'
- }, optionsChart.style);
+ // Allow table cells and flex-boxes to shrink without the chart blocking
+ // them out (#6427)
+ css(renderTo, { overflow: 'hidden' });
+ // Create the inner container
+ if (!chart.styledMode) {
+ containerStyle = extend({
+ position: 'relative',
+ // needed for context menu (avoidscrollbars) and content
+ // overflow in IE
+ overflow: 'hidden',
+ width: chartWidth + 'px',
+ height: chartHeight + 'px',
+ textAlign: 'left',
+ lineHeight: 'normal', // #427
+ zIndex: 0, // #1072
+ '-webkit-tap-highlight-color': 'rgba(0,0,0,0)'
+ }, optionsChart.style);
+ }
/**
* The containing HTML element of the chart. The container is
* dynamically inserted into the element given as the `renderTo`
- * parameterin the {@link Highcharts#chart} constructor.
+ * parameter in the {@link Highcharts#chart} constructor.
*
- * @memberOf Highcharts.Chart
- * @type {HTMLDOMElement}
+ * @name Highcharts.Chart#container
+ * @type {Highcharts.HTMLDOMElement}
*/
container = createElement(
- 'div', {
+ 'div',
+ {
id: containerId
},
containerStyle,
@@ -20666,9 +27800,9 @@
/**
* The renderer instance of the chart. Each chart instance has only one
* associated renderer.
- * @type {SVGRenderer}
- * @name renderer
- * @memberOf Chart
+ *
+ * @name Highcharts.Chart#renderer
+ * @type {Highcharts.SVGRenderer}
*/
chart.renderer = new Ren(
container,
@@ -20676,17 +27810,25 @@
chartHeight,
null,
optionsChart.forExport,
- options.exporting && options.exporting.allowHTML
+ options.exporting && options.exporting.allowHTML,
+ chart.styledMode
);
chart.setClassName(optionsChart.className);
-
- chart.renderer.setStyle(optionsChart.style);
-
+ if (!chart.styledMode) {
+ chart.renderer.setStyle(optionsChart.style);
+ } else {
+ // Initialize definitions
+ for (key in options.defs) {
+ this.renderer.definition(options.defs[key]);
+ }
+ }
// Add a reference to the charts index
chart.renderer.chartIndex = chart.index;
+
+ fireEvent(this, 'afterGetContainer');
},
/**
@@ -20695,8 +27837,13 @@
* will be moved into their final positions.
*
* @private
+ * @function Highcharts.Chart#getMargins
+ *
+ * @param {boolean} skipAxes
+ *
+ * @fires Highcharts.Chart#event:getMargins
*/
- getMargins: function(skipAxes) {
+ getMargins: function (skipAxes) {
var chart = this,
spacing = chart.spacing,
margin = chart.margin,
@@ -20717,23 +27864,18 @@
chart.legend.adjustMargins(margin, spacing);
}
- // adjust for scroller
- if (chart.extraMargin) {
- chart[chart.extraMargin.type] =
- (chart[chart.extraMargin.type] || 0) + chart.extraMargin.value;
- }
-
- // adjust for rangeSelector
- if (chart.adjustPlotArea) {
- chart.adjustPlotArea();
- }
+ fireEvent(this, 'getMargins');
if (!skipAxes) {
this.getAxisMargins();
}
},
- getAxisMargins: function() {
+ /**
+ * @private
+ * @function Highcharts.Chart#getAxisMargins
+ */
+ getAxisMargins: function () {
var chart = this,
// [top, right, bottom, left]
@@ -20742,7 +27884,7 @@
// pre-render axes to get labels offset width
if (chart.hasCartesianSeries) {
- each(chart.axes, function(axis) {
+ chart.axes.forEach(function (axis) {
if (axis.visible) {
axis.getOffset();
}
@@ -20750,7 +27892,7 @@
}
// Add the axis offsets
- each(marginNames, function(m, side) {
+ marginNames.forEach(function (m, side) {
if (!defined(margin[side])) {
chart[m] += axisOffset[side];
}
@@ -20763,21 +27905,23 @@
/**
* Reflows the chart to its container. By default, the chart reflows
* automatically to its container following a `window.resize` event, as per
- * the {@link https://api.highcharts/highcharts/chart.reflow|chart.reflow}
+ * the [chart.reflow](https://api.highcharts/highcharts/chart.reflow)
* option. However, there are no reliable events for div resize, so if the
* container is resized without a window resize event, this must be called
* explicitly.
*
- * @param {Object} e
- * Event arguments. Used primarily when the function is called
- * internally as a response to window resize.
- *
* @sample highcharts/members/chart-reflow/
* Resize div and reflow
* @sample highcharts/chart/events-container/
* Pop up and reflow
+ *
+ * @function Highcharts.Chart#reflow
+ *
+ * @param {global.Event} [e]
+ * Event arguments. Used primarily when the function is called
+ * internally as a response to window resize.
*/
- reflow: function(e) {
+ reflow: function (e) {
var chart = this,
optionsChart = chart.options.chart,
renderTo = chart.renderTo,
@@ -20791,7 +27935,8 @@
// Width and height checks for display:none. Target is doc in IE8 and
// Opera, win in Firefox, Chrome and IE9.
- if (!hasUserSize &&
+ if (
+ !hasUserSize &&
!chart.isPrinting &&
width &&
height &&
@@ -20801,10 +27946,10 @@
width !== chart.containerWidth ||
height !== chart.containerHeight
) {
- clearTimeout(chart.reflowTimeout);
+ H.clearTimeout(chart.reflowTimeout);
// When called from window.resize, e is set, else it's called
// directly (#2224)
- chart.reflowTimeout = syncTimeout(function() {
+ chart.reflowTimeout = syncTimeout(function () {
// Set size, it may have been destroyed in the meantime
// (#1257)
if (chart.container) {
@@ -20818,47 +27963,46 @@
},
/**
- * Add the event handlers necessary for auto resizing, depending on the
- * `chart.events.reflow` option.
+ * Toggle the event handlers necessary for auto resizing, depending on the
+ * `chart.reflow` option.
*
* @private
+ * @function Highcharts.Chart#setReflow
+ *
+ * @param {boolean} reflow
*/
- initReflow: function() {
- var chart = this,
- unbind;
+ setReflow: function (reflow) {
- unbind = addEvent(win, 'resize', function(e) {
- chart.reflow(e);
- });
- addEvent(chart, 'destroy', unbind);
+ var chart = this;
+
+ if (reflow !== false && !this.unbindReflow) {
+ this.unbindReflow = addEvent(win, 'resize', function (e) {
+ chart.reflow(e);
+ });
+ addEvent(this, 'destroy', this.unbindReflow);
+
+ } else if (reflow === false && this.unbindReflow) {
+
+ // Unbind and unset
+ this.unbindReflow = this.unbindReflow();
+ }
// The following will add listeners to re-fit the chart before and after
// printing (#2284). However it only works in WebKit. Should have worked
// in Firefox, but not supported in IE.
/*
if (win.matchMedia) {
- win.matchMedia('print').addListener(function reflow() {
- chart.reflow();
- });
+ win.matchMedia('print').addListener(function reflow() {
+ chart.reflow();
+ });
}
- */
+ //*/
},
/**
* Resize the chart to a given width and height. In order to set the width
* only, the height argument may be skipped. To set the height only, pass
- * `undefined for the width.
- * @param {Number|undefined|null} [width]
- * The new pixel width of the chart. Since v4.2.6, the argument can
- * be `undefined` in order to preserve the current value (when
- * setting height only), or `null` to adapt to the width of the
- * containing element.
- * @param {Number|undefined|null} [height]
- * The new pixel height of the chart. Since v4.2.6, the argument can
- * be `undefined` in order to preserve the current value, or `null`
- * in order to adapt to the height of the containing element.
- * @param {AnimationOptions} [animation=true]
- * Whether and how to apply animation.
+ * `undefined` for the width.
*
* @sample highcharts/members/chart-setsize-button/
* Test resizing from buttons
@@ -20866,8 +28010,27 @@
* Add a jQuery UI resizable
* @sample stock/members/chart-setsize/
* Highstock with UI resizable
+ *
+ * @function Highcharts.Chart#setSize
+ *
+ * @param {number|null} [width]
+ * The new pixel width of the chart. Since v4.2.6, the argument can
+ * be `undefined` in order to preserve the current value (when
+ * setting height only), or `null` to adapt to the width of the
+ * containing element.
+ *
+ * @param {number|null} [height]
+ * The new pixel height of the chart. Since v4.2.6, the argument can
+ * be `undefined` in order to preserve the current value, or `null`
+ * in order to adapt to the height of the containing element.
+ *
+ * @param {Highcharts.AnimationOptionsObject} [animation=true]
+ * Whether and how to apply animation.
+ *
+ * @fires Highcharts.Chart#event:endResize
+ * @fires Highcharts.Chart#event:resize
*/
- setSize: function(width, height, animation) {
+ setSize: function (width, height, animation) {
var chart = this,
renderer = chart.renderer,
globalAnimation;
@@ -20890,19 +28053,19 @@
// Resize the container with the global animation applied if enabled
// (#2503)
-
- globalAnimation = renderer.globalAnimation;
- (globalAnimation ? animate : css)(chart.container, {
- width: chart.chartWidth + 'px',
- height: chart.chartHeight + 'px'
- }, globalAnimation);
-
+ if (!chart.styledMode) {
+ globalAnimation = renderer.globalAnimation;
+ (globalAnimation ? animate : css)(chart.container, {
+ width: chart.chartWidth + 'px',
+ height: chart.chartHeight + 'px'
+ }, globalAnimation);
+ }
chart.setChartSize(true);
renderer.setSize(chart.chartWidth, chart.chartHeight, animation);
// handle axes
- each(chart.axes, function(axis) {
+ chart.axes.forEach(function (axis) {
axis.isDirty = true;
axis.setScale();
});
@@ -20921,9 +28084,9 @@
// Fire endResize and set isResizing back. If animation is disabled,
// fire without delay
- syncTimeout(function() {
+ syncTimeout(function () {
if (chart) {
- fireEvent(chart, 'endResize', null, function() {
+ fireEvent(chart, 'endResize', null, function () {
chart.isResizing -= 1;
});
}
@@ -20935,8 +28098,13 @@
* pre-render to determine margin sizes.
*
* @private
+ * @function Highcharts.Chart#setChartSize
+ *
+ * @param {boolean} skipAxes
+ *
+ * @fires Highcharts.Chart#event:afterSetChartSize
*/
- setChartSize: function(skipAxes) {
+ setChartSize: function (skipAxes) {
var chart = this,
inverted = chart.inverted,
renderer = chart.renderer,
@@ -20956,27 +28124,24 @@
/**
* The current left position of the plot area in pixels.
*
- * @name plotLeft
- * @memberOf Chart
- * @type {Number}
+ * @name Highcharts.Chart#plotLeft
+ * @type {number}
*/
chart.plotLeft = plotLeft = Math.round(chart.plotLeft);
/**
* The current top position of the plot area in pixels.
*
- * @name plotTop
- * @memberOf Chart
- * @type {Number}
+ * @name Highcharts.Chart#plotTop
+ * @type {number}
*/
chart.plotTop = plotTop = Math.round(chart.plotTop);
/**
* The current width of the plot area in pixels.
*
- * @name plotWidth
- * @memberOf Chart
- * @type {Number}
+ * @name Highcharts.Chart#plotWidth
+ * @type {number}
*/
chart.plotWidth = plotWidth = Math.max(
0,
@@ -20986,9 +28151,8 @@
/**
* The current height of the plot area in pixels.
*
- * @name plotHeight
- * @memberOf Chart
- * @type {Number}
+ * @name Highcharts.Chart#plotHeight
+ * @type {number}
*/
chart.plotHeight = plotHeight = Math.max(
0,
@@ -21036,28 +28200,39 @@
};
if (!skipAxes) {
- each(chart.axes, function(axis) {
+ chart.axes.forEach(function (axis) {
axis.setAxisSize();
axis.setAxisTranslation();
});
}
+
+ fireEvent(chart, 'afterSetChartSize', { skipAxes: skipAxes });
},
/**
* Initial margins before auto size margins are applied.
*
* @private
+ * @function Highcharts.Chart#resetMargins
*/
- resetMargins: function() {
+ resetMargins: function () {
+
+ fireEvent(this, 'resetMargins');
+
var chart = this,
chartOptions = chart.options.chart;
// Create margin and spacing array
- each(['margin', 'spacing'], function splashArrays(target) {
+ ['margin', 'spacing'].forEach(function splashArrays(target) {
var value = chartOptions[target],
values = isObject(value) ? value : [value, value, value, value];
- each(['Top', 'Right', 'Bottom', 'Left'], function(sideName, side) {
+ [
+ 'Top',
+ 'Right',
+ 'Bottom',
+ 'Left'
+ ].forEach(function (sideName, side) {
chart[target][side] = pick(
chartOptions[target + sideName],
values[side]
@@ -21067,7 +28242,7 @@
// Set margin names like chart.plotTop, chart.plotLeft,
// chart.marginRight, chart.marginBottom.
- each(marginNames, function(m, side) {
+ marginNames.forEach(function (m, side) {
chart[m] = pick(chart.margin[side], chart.spacing[side]);
});
chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
@@ -21079,8 +28254,11 @@
* and plot area.
*
* @private
+ * @function Highcharts.Chart#drawChartBox
+ *
+ * @fires Highcharts.Chart#event:afterDrawChartBox
*/
- drawChartBox: function() {
+ drawChartBox: function () {
var chart = this,
optionsChart = chart.options.chart,
renderer = chart.renderer,
@@ -21090,12 +28268,11 @@
plotBackground = chart.plotBackground,
plotBorder = chart.plotBorder,
chartBorderWidth,
-
+ styledMode = chart.styledMode,
plotBGImage = chart.plotBGImage,
chartBackgroundColor = optionsChart.backgroundColor,
plotBackgroundColor = optionsChart.plotBackgroundColor,
plotBackgroundImage = optionsChart.plotBackgroundImage,
-
mgn,
bgAttr,
plotLeft = chart.plotLeft,
@@ -21115,22 +28292,26 @@
verb = 'attr';
}
+ if (!styledMode) {
+ // Presentational
+ chartBorderWidth = optionsChart.borderWidth || 0;
+ mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);
- // Presentational
- chartBorderWidth = optionsChart.borderWidth || 0;
- mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);
-
- bgAttr = {
- fill: chartBackgroundColor || 'none'
- };
+ bgAttr = {
+ fill: chartBackgroundColor || 'none'
+ };
- if (chartBorderWidth || chartBackground['stroke-width']) { // #980
- bgAttr.stroke = optionsChart.borderColor;
- bgAttr['stroke-width'] = chartBorderWidth;
+ if (chartBorderWidth || chartBackground['stroke-width']) { // #980
+ bgAttr.stroke = optionsChart.borderColor;
+ bgAttr['stroke-width'] = chartBorderWidth;
+ }
+ chartBackground
+ .attr(bgAttr)
+ .shadow(optionsChart.shadow);
+ } else {
+ chartBorderWidth = mgn = chartBackground.strokeWidth();
}
- chartBackground
- .attr(bgAttr)
- .shadow(optionsChart.shadow);
+
chartBackground[verb]({
x: mgn / 2,
@@ -21150,30 +28331,30 @@
}
plotBackground[verb](plotBox);
-
- // Presentational attributes for the background
- plotBackground
- .attr({
- fill: plotBackgroundColor || 'none'
- })
- .shadow(optionsChart.plotShadow);
-
- // Create the background image
- if (plotBackgroundImage) {
- if (!plotBGImage) {
- chart.plotBGImage = renderer.image(
- plotBackgroundImage,
- plotLeft,
- plotTop,
- plotWidth,
- plotHeight
- ).add();
- } else {
- plotBGImage.animate(plotBox);
+ if (!styledMode) {
+ // Presentational attributes for the background
+ plotBackground
+ .attr({
+ fill: plotBackgroundColor || 'none'
+ })
+ .shadow(optionsChart.plotShadow);
+
+ // Create the background image
+ if (plotBackgroundImage) {
+ if (!plotBGImage) {
+ chart.plotBGImage = renderer.image(
+ plotBackgroundImage,
+ plotLeft,
+ plotTop,
+ plotWidth,
+ plotHeight
+ ).add();
+ } else {
+ plotBGImage.animate(plotBox);
+ }
}
}
-
// Plot clip
if (!clipRect) {
chart.clipRect = renderer.clipRect(clipBox);
@@ -21196,14 +28377,14 @@
.add();
}
-
- // Presentational
- plotBorder.attr({
- stroke: optionsChart.plotBorderColor,
- 'stroke-width': optionsChart.plotBorderWidth || 0,
- fill: 'none'
- });
-
+ if (!styledMode) {
+ // Presentational
+ plotBorder.attr({
+ stroke: optionsChart.plotBorderColor,
+ 'stroke-width': optionsChart.plotBorderWidth || 0,
+ fill: 'none'
+ });
+ }
plotBorder[verb](plotBorder.crisp({
x: plotLeft,
@@ -21214,6 +28395,8 @@
// reset
chart.isDirtyBox = false;
+
+ fireEvent(this, 'afterDrawChartBox');
},
/**
@@ -21222,8 +28405,9 @@
* and in extensions to the chart.angular and chart.polar properties.
*
* @private
+ * @function Highcharts.Chart#propFromSeries
*/
- propFromSeries: function() {
+ propFromSeries: function () {
var chart = this,
optionsChart = chart.options.chart,
klass,
@@ -21232,7 +28416,7 @@
value;
- each(['inverted', 'angular', 'polar'], function(key) {
+ ['inverted', 'angular', 'polar'].forEach(function (key) {
// The default series type's class
klass = seriesTypes[optionsChart.type ||
@@ -21260,24 +28444,28 @@
},
/**
- * Internal function to link two or more series together, based on the
+ * Internal function to link two or more series together, based on the
* `linkedTo` option. This is done from `Chart.render`, and after
* `Chart.addSeries` and `Series.remove`.
*
* @private
+ * @function Highcharts.Chart#linkSeries
+ *
+ * @fires Highcharts.Chart#event:afterLinkSeries
*/
- linkSeries: function() {
+ linkSeries: function () {
var chart = this,
chartSeries = chart.series;
// Reset links
- each(chartSeries, function(series) {
+ chartSeries.forEach(function (series) {
series.linkedSeries.length = 0;
});
// Apply new links
- each(chartSeries, function(series) {
+ chartSeries.forEach(function (series) {
var linkedTo = series.options.linkedTo;
+
if (isString(linkedTo)) {
if (linkedTo === ':previous') {
linkedTo = chart.series[series.index - 1];
@@ -21296,15 +28484,18 @@
}
}
});
+
+ fireEvent(this, 'afterLinkSeries');
},
/**
* Render series for the chart.
*
* @private
+ * @function Highcharts.Chart#renderSeries
*/
- renderSeries: function() {
- each(this.series, function(serie) {
+ renderSeries: function () {
+ this.series.forEach(function (serie) {
serie.translate();
serie.render();
});
@@ -21314,12 +28505,14 @@
* Render labels for the chart.
*
* @private
+ * @function Highcharts.Chart#renderLabels
*/
- renderLabels: function() {
+ renderLabels: function () {
var chart = this,
labels = chart.options.labels;
+
if (labels.items) {
- each(labels.items, function(label) {
+ labels.items.forEach(function (label) {
var style = extend(labels.style, label.style),
x = pInt(style.left) + chart.plotLeft,
y = pInt(style.top) + chart.plotTop + 12;
@@ -21329,13 +28522,11 @@
delete style.top;
chart.renderer.text(
- label.html,
- x,
- y
- )
- .attr({
- zIndex: 2
- })
+ label.html,
+ x,
+ y
+ )
+ .attr({ zIndex: 2 })
.css(style)
.add();
@@ -21347,12 +28538,14 @@
* Render all graphics for the chart. Runs internally on initialization.
*
* @private
+ * @function Highcharts.Chart#render
*/
- render: function() {
+ render: function () {
var chart = this,
axes = chart.axes,
renderer = chart.renderer,
options = chart.options,
+ correction = 0, // correction for X axis labels
tempWidth,
tempHeight,
redoHorizontal,
@@ -21361,8 +28554,12 @@
// Title
chart.setTitle();
-
- // Legend
+ /**
+ * The overview of the chart's series.
+ *
+ * @name Highcharts.Chart#legend
+ * @type {Highcharts.Legend}
+ */
chart.legend = new Legend(chart, options.legend);
// Get stacks
@@ -21376,25 +28573,45 @@
// Record preliminary dimensions for later comparison
tempWidth = chart.plotWidth;
- // 21 is the most common correction for X axis labels
+
+ axes.some(function (axis) {
+ if (
+ axis.horiz &&
+ axis.visible &&
+ axis.options.labels.enabled &&
+ axis.series.length
+ ) {
+ // 21 is the most common correction for X axis labels
+ correction = 21;
+ return true;
+ }
+ });
+
// use Math.max to prevent negative plotHeight
- tempHeight = chart.plotHeight = Math.max(chart.plotHeight - 21, 0);
+ chart.plotHeight = Math.max(chart.plotHeight - correction, 0);
+ tempHeight = chart.plotHeight;
// Get margins by pre-rendering axes
- each(axes, function(axis) {
+ axes.forEach(function (axis) {
axis.setScale();
});
chart.getAxisMargins();
- // If the plot area size has changed significantly, calculate tick positions again
+ // If the plot area size has changed significantly, calculate tick
+ // positions again
redoHorizontal = tempWidth / chart.plotWidth > 1.1;
- redoVertical = tempHeight / chart.plotHeight > 1.05; // Height is more sensitive
+ // Height is more sensitive, use lower threshold
+ redoVertical = tempHeight / chart.plotHeight > 1.05;
if (redoHorizontal || redoVertical) {
- each(axes, function(axis) {
- if ((axis.horiz && redoHorizontal) || (!axis.horiz && redoVertical)) {
- axis.setTickInterval(true); // update to reflect the new margins
+ axes.forEach(function (axis) {
+ if (
+ (axis.horiz && redoHorizontal) ||
+ (!axis.horiz && redoVertical)
+ ) {
+ // update to reflect the new margins
+ axis.setTickInterval(true);
}
});
chart.getMargins(); // second pass to check for new labels
@@ -21406,7 +28623,7 @@
// Axes
if (chart.hasCartesianSeries) {
- each(axes, function(axis) {
+ axes.forEach(function (axis) {
if (axis.visible) {
axis.render();
}
@@ -21416,9 +28633,7 @@
// The series
if (!chart.seriesGroup) {
chart.seriesGroup = renderer.g('series-group')
- .attr({
- zIndex: 3
- })
+ .attr({ zIndex: 3 })
.add();
}
chart.renderSeries();
@@ -21442,11 +28657,15 @@
/**
* Set a new credits label for the chart.
*
- * @param {CreditOptions} options
- * A configuration object for the new credits.
- * @sample highcharts/credits/credits-update/ Add and update credits
+ * @sample highcharts/credits/credits-update/
+ * Add and update credits
+ *
+ * @function Highcharts.Chart#addCredits
+ *
+ * @param {Highcharts.CreditsOptions} options
+ * A configuration object for the new credits.
*/
- addCredits: function(credits) {
+ addCredits: function (credits) {
var chart = this;
credits = merge(true, this.options.credits, credits);
@@ -21454,21 +28673,19 @@
/**
* The chart's credits label. The label has an `update` method that
- * allows setting new options as per the {@link
- * https://api.highcharts.com/highcharts/credits|
- * credits options set}.
+ * allows setting new options as per the
+ * [credits options set](https://api.highcharts.com/highcharts/credits).
*
- * @memberof Highcharts.Chart
- * @name credits
+ * @name Highcharts.Chart#credits
* @type {Highcharts.SVGElement}
*/
this.credits = this.renderer.text(
- credits.text + (this.mapCredits || ''),
- 0,
- 0
- )
+ credits.text + (this.mapCredits || ''),
+ 0,
+ 0
+ )
.addClass('highcharts-credits')
- .on('click', function() {
+ .on('click', function () {
if (credits.href) {
win.location.href = credits.href;
}
@@ -21476,15 +28693,19 @@
.attr({
align: credits.position.align,
zIndex: 8
- })
+ });
+
- .css(credits.style)
+ if (!chart.styledMode) {
+ this.credits.css(credits.style);
+ }
+ this.credits
.add()
.align(credits.position);
// Dynamically update
- this.credits.update = function(options) {
+ this.credits.update = function (options) {
chart.credits = chart.credits.destroy();
chart.addCredits(options);
};
@@ -21500,8 +28721,12 @@
* Destroy the chart from a button
* @sample stock/members/chart-destroy/
* Destroy with Highstock
+ *
+ * @function Highcharts.Chart#destroy
+ *
+ * @fires Highcharts.Chart#event:destroy
*/
- destroy: function() {
+ destroy: function () {
var chart = this,
axes = chart.axes,
series = chart.series,
@@ -21543,12 +28768,12 @@
}
// ==== Destroy chart properties:
- each([
+ [
'title', 'subtitle', 'chartBackground', 'plotBackground',
'plotBGImage', 'plotBorder', 'seriesGroup', 'clipRect', 'credits',
'pointer', 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip',
'renderer'
- ], function(name) {
+ ].forEach(function (name) {
var prop = chart[name];
if (prop && prop.destroy) {
@@ -21556,8 +28781,9 @@
}
});
- // remove container and all SVG
- if (container) { // can break in IE when destroyed before finished loading
+ // Remove container and all SVG, check container as it can break in IE
+ // when destroyed before finished loading
+ if (container) {
container.innerHTML = '';
removeEvent(container);
if (parentNode) {
@@ -21567,56 +28793,32 @@
}
// clean it all up
- objectEach(chart, function(val, key) {
+ objectEach(chart, function (val, key) {
delete chart[key];
});
},
-
- /**
- * VML namespaces can't be added until after complete. Listening
- * for Perini's doScroll hack is not enough.
- *
- * @private
- */
- isReadyToRender: function() {
- var chart = this;
-
- // Note: win == win.top is required
- if ((!svg && (win == win.top && doc.readyState !== 'complete'))) { // eslint-disable-line eqeqeq
- doc.attachEvent('onreadystatechange', function() {
- doc.detachEvent('onreadystatechange', chart.firstRender);
- if (doc.readyState === 'complete') {
- chart.firstRender();
- }
- });
- return false;
- }
- return true;
- },
-
/**
* Prepare for first rendering after all data are loaded.
*
* @private
+ * @function Highcharts.Chart#firstRender
+ *
+ * @fires Highcharts.Chart#event:beforeRender
*/
- firstRender: function() {
+ firstRender: function () {
var chart = this,
options = chart.options;
- // Check whether the chart is ready to render
- if (!chart.isReadyToRender()) {
+ // Hook for oldIE to check whether the chart is ready to render
+ if (chart.isReadyToRender && !chart.isReadyToRender()) {
return;
}
// Create the container
chart.getContainer();
- // Run an early event after the container and renderer are established
- fireEvent(chart, 'init');
-
-
chart.resetMargins();
chart.setChartSize();
@@ -21627,15 +28829,18 @@
chart.getAxes();
// Initialize the series
- each(options.series || [], function(serieOptions) {
- chart.initSeries(serieOptions);
- });
+ (H.isArray(options.series) ? options.series : []).forEach( // #9680
+ function (serieOptions) {
+ chart.initSeries(serieOptions);
+ }
+ );
chart.linkSeries();
- // Run an event after axes and series are initialized, but before render. At this stage,
- // the series data is indexed and cached in the xData and yData arrays, so we can access
- // those before rendering. Used in Highstock.
+ // Run an event after axes and series are initialized, but before
+ // render. At this stage, the series data is indexed and cached in the
+ // xData and yData arrays, so we can access those before rendering. Used
+ // in Highstock.
fireEvent(chart, 'beforeRender');
// depends on inverted and on margins being set
@@ -21644,67 +28849,387 @@
/**
* The Pointer that keeps track of mouse and touch interaction.
*
- * @memberof Chart
+ * @memberof Highcharts.Chart
* @name pointer
- * @type Pointer
+ * @type {Highcharts.Pointer}
+ * @instance
*/
chart.pointer = new Pointer(chart, options);
}
- chart.render();
+ chart.render();
+
+ // Fire the load event if there are no external images
+ if (!chart.renderer.imgCount && chart.onload) {
+ chart.onload();
+ }
+
+ // If the chart was rendered outside the top container, put it back in
+ // (#3679)
+ chart.temporaryDisplay(true);
+
+ },
+
+ /**
+ * Internal function that runs on chart load, async if any images are loaded
+ * in the chart. Runs the callbacks and triggers the `load` and `render`
+ * events.
+ *
+ * @private
+ * @function Highcharts.Chart#onload
+ *
+ * @fires Highcharts.Chart#event:load
+ * @fires Highcharts.Chart#event:render
+ */
+ onload: function () {
+
+ // Run callbacks
+ [this.callback].concat(this.callbacks).forEach(function (fn) {
+ // Chart destroyed in its own callback (#3600)
+ if (fn && this.index !== undefined) {
+ fn.apply(this, [this]);
+ }
+ }, this);
+
+ fireEvent(this, 'load');
+ fireEvent(this, 'render');
+
+
+ // Set up auto resize, check for not destroyed (#6068)
+ if (defined(this.index)) {
+ this.setReflow(this.options.chart.reflow);
+ }
+
+ // Don't run again
+ this.onload = null;
+ }
+
+ }); // end Chart
+
+ }(Highcharts));
+ (function (H) {
+ /**
+ * (c) 2010-2019 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ *
+ * Highcharts feature to make the Y axis stay fixed when scrolling the chart
+ * horizontally on mobile devices. Supports left and right side axes.
+ */
+
+
+
+ var addEvent = H.addEvent,
+ Chart = H.Chart;
+
+ /**
+ * Options for a scrollable plot area. This feature provides a minimum width for
+ * the plot area of the chart. If the width gets smaller than this, typically
+ * on mobile devices, a native browser scrollbar is presented below the chart.
+ * This scrollbar provides smooth scrolling for the contents of the plot area,
+ * whereas the title, legend and axes are fixed.
+ *
+ * @sample {highcharts} highcharts/chart/scrollable-plotarea
+ * Scrollable plot area
+ *
+ * @since 6.1.0
+ * @product highcharts gantt
+ * @apioption chart.scrollablePlotArea
+ */
+
+ /**
+ * The minimum width for the plot area. If it gets smaller than this, the plot
+ * area will become scrollable.
+ *
+ * @type {number}
+ * @apioption chart.scrollablePlotArea.minWidth
+ */
+
+ /**
+ * The initial scrolling position of the scrollable plot area. Ranges from 0 to
+ * 1, where 0 aligns the plot area to the left and 1 aligns it to the right.
+ * Typically we would use 1 if the chart has right aligned Y axes.
+ *
+ * @type {number}
+ * @apioption chart.scrollablePlotArea.scrollPositionX
+ */
+
+ addEvent(Chart, 'afterSetChartSize', function (e) {
+
+ var scrollablePlotArea = this.options.chart.scrollablePlotArea,
+ scrollableMinWidth =
+ scrollablePlotArea && scrollablePlotArea.minWidth,
+ scrollablePixels;
+
+ if (scrollableMinWidth && !this.renderer.forExport) {
+
+ // The amount of pixels to scroll, the difference between chart
+ // width and scrollable width
+ this.scrollablePixels = scrollablePixels = Math.max(
+ 0,
+ scrollableMinWidth - this.chartWidth
+ );
+
+ if (scrollablePixels) {
+ this.plotWidth += scrollablePixels;
+ this.clipBox.width += scrollablePixels;
+
+ if (!e.skipAxes) {
+ this.axes.forEach(function (axis) {
+ if (axis.side === 1) {
+ // Get the plot lines right in getPlotLinePath,
+ // temporarily set it to the adjusted plot width.
+ axis.getPlotLinePath = function () {
+ var right = this.right,
+ path;
+
+ this.right = right - axis.chart.scrollablePixels;
+ path = H.Axis.prototype.getPlotLinePath.apply(
+ this,
+ arguments
+ );
+ this.right = right;
+ return path;
+ };
+
+ } else {
+ // Apply the corrected plotWidth
+ axis.setAxisSize();
+ axis.setAxisTranslation();
+ }
+ });
+ }
+ }
+ }
+ });
+
+ addEvent(Chart, 'render', function () {
+ if (this.scrollablePixels) {
+ if (this.setUpScrolling) {
+ this.setUpScrolling();
+ }
+ this.applyFixed();
+
+ } else if (this.fixedDiv) { // Has been in scrollable mode
+ this.applyFixed();
+ }
+ });
+
+ /**
+ * @private
+ * @function Highcharts.Chart#setUpScrolling
+ */
+ Chart.prototype.setUpScrolling = function () {
+
+ // Add the necessary divs to provide scrolling
+ this.scrollingContainer = H.createElement('div', {
+ 'className': 'highcharts-scrolling'
+ }, {
+ overflowX: 'auto',
+ WebkitOverflowScrolling: 'touch'
+ }, this.renderTo);
+
+ this.innerContainer = H.createElement('div', {
+ 'className': 'highcharts-inner-container'
+ }, null, this.scrollingContainer);
+
+ // Now move the container inside
+ this.innerContainer.appendChild(this.container);
+
+ // Don't run again
+ this.setUpScrolling = null;
+ };
+
+ /**
+ * @private
+ * @function Highcharts.Chart#applyFixed
+ */
+ Chart.prototype.applyFixed = function () {
+ var container = this.container,
+ fixedRenderer,
+ scrollableWidth,
+ firstTime = !this.fixedDiv;
+
+ // First render
+ if (firstTime) {
- // Fire the load event if there are no external images
- if (!chart.renderer.imgCount && chart.onload) {
- chart.onload();
- }
+ this.fixedDiv = H.createElement(
+ 'div',
+ {
+ className: 'highcharts-fixed'
+ },
+ {
+ position: 'absolute',
+ overflow: 'hidden',
+ pointerEvents: 'none',
+ zIndex: 2
+ },
+ null,
+ true
+ );
+ this.renderTo.insertBefore(
+ this.fixedDiv,
+ this.renderTo.firstChild
+ );
+ this.renderTo.style.overflow = 'visible';
- // If the chart was rendered outside the top container, put it back in (#3679)
- chart.temporaryDisplay(true);
+ this.fixedRenderer = fixedRenderer = new H.Renderer(
+ this.fixedDiv,
+ 0,
+ 0
+ );
- },
+ // Mask
+ this.scrollableMask = fixedRenderer.path()
+ .attr({
+ fill: H.color(
+ this.options.chart.backgroundColor || '#fff'
+ ).setOpacity(0.85).get(),
+ zIndex: -1
+ })
+ .addClass('highcharts-scrollable-mask')
+ .add();
- /**
- * Internal function that runs on chart load, async if any images are loaded
- * in the chart. Runs the callbacks and triggers the `load` and `render`
- * events.
- *
- * @private
- */
- onload: function() {
+ // These elements are moved over to the fixed renderer and stay fixed
+ // when the user scrolls the chart.
+ ([
+ this.inverted ?
+ '.highcharts-xaxis' :
+ '.highcharts-yaxis',
+ this.inverted ?
+ '.highcharts-xaxis-labels' :
+ '.highcharts-yaxis-labels',
+ '.highcharts-contextbutton',
+ '.highcharts-credits',
+ '.highcharts-legend',
+ '.highcharts-subtitle',
+ '.highcharts-title',
+ '.highcharts-legend-checkbox'
+ ]).forEach(function (className) {
+ [].forEach.call(
+ container.querySelectorAll(className),
+ function (elem) {
+ (
+ elem.namespaceURI === fixedRenderer.SVG_NS ?
+ fixedRenderer.box :
+ fixedRenderer.box.parentNode
+ ).appendChild(elem);
+ elem.style.pointerEvents = 'auto';
+ }
+ );
+ });
+ }
- // Run callbacks
- each([this.callback].concat(this.callbacks), function(fn) {
- if (fn && this.index !== undefined) { // Chart destroyed in its own callback (#3600)
- fn.apply(this, [this]);
- }
- }, this);
+ // Set the size of the fixed renderer to the visible width
+ this.fixedRenderer.setSize(
+ this.chartWidth,
+ this.chartHeight
+ );
- fireEvent(this, 'load');
- fireEvent(this, 'render');
+ // Increase the size of the scrollable renderer and background
+ scrollableWidth = this.chartWidth + this.scrollablePixels;
+ H.stop(this.container);
+ this.container.style.width = scrollableWidth + 'px';
+ this.renderer.boxWrapper.attr({
+ width: scrollableWidth,
+ height: this.chartHeight,
+ viewBox: [0, 0, scrollableWidth, this.chartHeight].join(' ')
+ });
+ this.chartBackground.attr({ width: scrollableWidth });
+ // Set scroll position
+ if (firstTime) {
+ var options = this.options.chart.scrollablePlotArea;
- // Set up auto resize, check for not destroyed (#6068)
- if (defined(this.index) && this.options.chart.reflow !== false) {
- this.initReflow();
+ if (options.scrollPositionX) {
+ this.scrollingContainer.scrollLeft =
+ this.scrollablePixels * options.scrollPositionX;
}
-
- // Don't run again
- this.onload = null;
}
- }); // end Chart
+ // Mask behind the left and right side
+ var axisOffset = this.axisOffset,
+ maskTop = this.plotTop - axisOffset[0] - 1,
+ maskBottom = this.plotTop + this.plotHeight + axisOffset[2],
+ maskPlotRight = this.plotLeft + this.plotWidth -
+ this.scrollablePixels;
+
+ this.scrollableMask.attr({
+ d: this.scrollablePixels ? [
+ // Left side
+ 'M', 0, maskTop,
+ 'L', this.plotLeft - 1, maskTop,
+ 'L', this.plotLeft - 1, maskBottom,
+ 'L', 0, maskBottom,
+ 'Z',
+
+ // Right side
+ 'M', maskPlotRight, maskTop,
+ 'L', this.chartWidth, maskTop,
+ 'L', this.chartWidth, maskBottom,
+ 'L', maskPlotRight, maskBottom,
+ 'Z'
+ ] : ['M', 0, 0]
+ });
+ };
}(Highcharts));
- (function(Highcharts) {
+ (function (Highcharts) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+ /**
+ * Configuration hash for the data label and tooltip formatters.
+ *
+ * @interface Highcharts.PointLabelObject
+ *//**
+ * The point's current color.
+ * @name Highcharts.PointLabelObject#color
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ *//**
+ * The point's current color index, used in styled mode instead of `color`. The
+ * color index is inserted in class names used for styling.
+ * @name Highcharts.PointLabelObject#colorIndex
+ * @type {number}
+ *//**
+ * The name of the related point.
+ * @name Highcharts.PointLabelObject#key
+ * @type {number|string}
+ *//**
+ * The percentage for related points in a stacked series or pies.
+ * @name Highcharts.PointLabelObject#percentage
+ * @type {number}
+ *//**
+ * The related point.
+ * @name Highcharts.PointLabelObject#point
+ * @type {Highcharts.Point}
+ *//**
+ * The related series.
+ * @name Highcharts.PointLabelObject#series
+ * @type {Highcharts.Series}
+ *//**
+ * The total of values in either a stack for stacked series, or a pie in a pie
+ * series.
+ * @name Highcharts.PointLabelObject#total
+ * @type {number}
+ *//**
+ * For categorized axes this property holds the category name for the point. For
+ * other axes it holds the X value.
+ * @name Highcharts.PointLabelObject#x
+ * @type {number|string}
+ *//**
+ * The y value of the point.
+ * @name Highcharts.PointLabelObject#y
+ * @type {number|undefined}
+ */
+
+
+
var Point,
H = Highcharts,
-
- each = H.each,
extend = H.extend,
erase = H.erase,
fireEvent = H.fireEvent,
@@ -21712,63 +29237,79 @@
isArray = H.isArray,
isNumber = H.isNumber,
pick = H.pick,
+ uniqueKey = H.uniqueKey,
+ defined = H.defined,
removeEvent = H.removeEvent;
/**
- * The Point object. The point objects are generated from the `series.data`
+ * The Point object. The point objects are generated from the `series.data`
* configuration objects or raw numbers. They can be accessed from the
- * `Series.points` array. Other ways to instaniate points are through {@link
+ * `Series.points` array. Other ways to instantiate points are through {@link
* Highcharts.Series#addPoint} or {@link Highcharts.Series#setData}.
*
* @class
+ * @name Highcharts.Point
*/
-
- Highcharts.Point = Point = function() {};
+ Highcharts.Point = Point = function () {};
Highcharts.Point.prototype = {
/**
* Initialize the point. Called internally based on the `series.data`
* option.
- * @param {Series} series
- * The series object containing this point.
- * @param {Number|Array|Object} options
- * The data in either number, array or object format.
- * @param {Number} x Optionally, the X value of the point.
- * @return {Point} The Point instance.
+ *
+ * @function Highcharts.Point#init
+ *
+ * @param {Highcharts.Series} series
+ * The series object containing this point.
+ *
+ * @param {number|object|Array|null} options
+ * The data in either number, array or object format.
+ *
+ * @param {number} [x]
+ * Optionally, the X value of the point.
+ *
+ * @return {Highcharts.Point}
+ * The Point instance.
+ *
+ * @fires Highcharts.Point#event:afterInit
*/
- init: function(series, options, x) {
+ init: function (series, options, x) {
var point = this,
colors,
- colorCount = series.chart.options.chart.colorCount,
+ optionsChart = series.chart.options.chart,
+ colorCount = optionsChart.colorCount,
+ styledMode = series.chart.styledMode,
colorIndex;
/**
* The series object associated with the point.
*
- * @name series
- * @memberof Highcharts.Point
- * @type Highcharts.Series
+ * @name Highcharts.Point#series
+ * @type {Highcharts.Series}
*/
point.series = series;
-
/**
* The point's current color.
- * @name color
- * @memberof Highcharts.Point
- * @type {Color}
+ *
+ * @name Highcharts.Point#color
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
*/
- point.color = series.color; // #3445
-
+ if (!styledMode) {
+ point.color = series.color; // #3445
+ }
point.applyOptions(options, x);
- if (series.options.colorByPoint) {
-
- colors = series.options.colors || series.chart.options.colors;
- point.color = point.color || colors[series.colorCounter];
- colorCount = colors.length;
+ // Add a unique ID to the point if none is assigned
+ point.id = defined(point.id) ? point.id : uniqueKey();
+ if (series.options.colorByPoint) {
+ if (!styledMode) {
+ colors = series.options.colors || series.chart.options.colors;
+ point.color = point.color || colors[series.colorCounter];
+ colorCount = colors.length;
+ }
colorIndex = series.colorCounter;
series.colorCounter++;
// loop back to zero
@@ -21780,15 +29321,18 @@
}
/**
- * The point's current color index, used in styled mode instead of
+ * The point's current color index, used in styled mode instead of
* `color`. The color index is inserted in class names used for styling.
- * @name colorIndex
- * @memberof Highcharts.Point
- * @type {Number}
+ *
+ * @name Highcharts.Point#colorIndex
+ * @type {number}
*/
point.colorIndex = pick(point.colorIndex, colorIndex);
series.chart.pointCount++;
+
+ fireEvent(point, 'afterInit');
+
return point;
},
/**
@@ -21796,11 +29340,18 @@
* properties. Called on point init or from point.update.
*
* @private
- * @param {Object} options The point options as defined in series.data.
- * @param {Number} x Optionally, the X value.
- * @returns {Object} The Point instance.
+ * @function Highcharts.Point#applyOptions
+ *
+ * @param {number|object|Array|null} options
+ * The point options as defined in series.data.
+ *
+ * @param {number} [x]
+ * Optionally, the x value.
+ *
+ * @return {Highcharts.Point}
+ * The Point instance.
*/
- applyOptions: function(options, x) {
+ applyOptions: function (options, x) {
var point = this,
series = point.series,
pointValKey = series.options.pointValKey || series.pointValKey;
@@ -21809,14 +29360,33 @@
// copy options directly to point
extend(point, options);
- point.options = point.options ? extend(point.options, options) : options;
- // Since options are copied into the Point instance, some accidental options must be shielded (#5681)
+ /**
+ * The point's options as applied in the initial configuration, or
+ * extended through `Point.update`.
+ * @name Highcharts.Point#options
+ * @type {object}
+ */
+ point.options = point.options ?
+ extend(point.options, options) :
+ options;
+
+ // Since options are copied into the Point instance, some accidental
+ // options must be shielded (#5681)
if (options.group) {
delete point.group;
}
+ if (options.dataLabels) {
+ delete point.dataLabels;
+ }
- // For higher dimension series types. For instance, for ranges, point.y is mapped to point.low.
+ /**
+ * The y value of the point.
+ * @name Highcharts.Point#y
+ * @type {number|undefined}
+ */
+ // For higher dimension series types. For instance, for ranges, point.y
+ // is mapped to point.low.
if (pointValKey) {
point.y = point[pointValKey];
}
@@ -21830,9 +29400,20 @@
point.state = 'select';
}
- // If no x is set by now, get auto incremented value. All points must have an
- // x value, however the y value can be null to create a gap in the series
- if ('name' in point && x === undefined && series.xAxis && series.xAxis.hasNames) {
+ /**
+ * The x value of the point.
+ * @name Highcharts.Point#x
+ * @type {number}
+ */
+ // If no x is set by now, get auto incremented value. All points must
+ // have an x value, however the y value can be null to create a gap in
+ // the series
+ if (
+ 'name' in point &&
+ x === undefined &&
+ series.xAxis &&
+ series.xAxis.hasNames
+ ) {
point.x = series.xAxis.nameToX(point);
}
if (point.x === undefined && series) {
@@ -21846,6 +29427,41 @@
return point;
},
+ /**
+ * Set a value in an object, on the property defined by key. The key
+ * supports nested properties using dot notation. The function modifies the
+ * input object and does not make a copy.
+ *
+ * @function Highcharts.Point#setNestedProperty
+ *
+ * @param {object} object
+ * The object to set the value on.
+ *
+ * @param {*} value
+ * The value to set.
+ *
+ * @param {string} key
+ * Key to the property to set.
+ *
+ * @return {object}
+ * The modified object.
+ */
+ setNestedProperty: function (object, value, key) {
+ var nestedKeys = key.split('.');
+
+ nestedKeys.reduce(function (result, key, i, arr) {
+ var isLastKey = arr.length - 1 === i;
+
+ result[key] = (
+ isLastKey ?
+ value :
+ (H.isObject(result[key], true) ? result[key] : {})
+ );
+ return result[key];
+ }, object);
+ return object;
+ },
+
/**
* Transform number or array configs into objects. Used internally to unify
* the different configuration formats for points. For example, a simple
@@ -21853,11 +29469,15 @@
* array config like `[1, 10]` in a scatter series will be transformed to
* `{ x: 1, y: 10 }`.
*
- * @param {Number|Array|Object} options
- * The input options
- * @return {Object} Transformed options.
+ * @function Highcharts.Point#optionsToObject
+ *
+ * @param {number|object|Array|null} options
+ * The input option.
+ *
+ * @return {object}
+ * Transformed options.
*/
- optionsToObject: function(options) {
+ optionsToObject: function (options) {
var ret = {},
series = this.series,
keys = series.options.keys,
@@ -21882,8 +29502,17 @@
i++;
}
while (j < valueCount) {
- if (!keys || options[i] !== undefined) { // Skip undefined positions for keys
- ret[pointArrayMap[j]] = options[i];
+ // Skip undefined positions for keys
+ if (!keys || options[i] !== undefined) {
+ if (pointArrayMap[j].indexOf('.') > 0) {
+ // Handle nested keys, e.g. ['color.pattern.image']
+ // Avoid function call unless necessary.
+ H.Point.prototype.setNestedProperty(
+ ret, options[i], pointArrayMap[j]
+ );
+ } else {
+ ret[pointArrayMap[j]] = options[i];
+ }
}
i++;
j++;
@@ -21891,8 +29520,9 @@
} else if (typeof options === 'object') {
ret = options;
- // This is the fastest way to detect if there are individual point dataLabels that need
- // to be considered in drawDataLabels. These can only occur in object configs.
+ // This is the fastest way to detect if there are individual point
+ // dataLabels that need to be considered in drawDataLabels. These
+ // can only occur in object configs.
if (options.dataLabels) {
series._hasPointLabels = true;
}
@@ -21908,10 +29538,13 @@
/**
* Get the CSS class names for individual points. Used internally where the
* returned value is set on every point.
- *
- * @returns {String} The class names.
+ *
+ * @function Highcharts.Point#getClassName
+ *
+ * @return {string}
+ * The class names.
*/
- getClassName: function() {
+ getClassName: function () {
return 'highcharts-point' +
(this.selected ? ' highcharts-point-select' : '') +
(this.negative ? ' highcharts-negative' : '') +
@@ -21926,10 +29559,12 @@
/**
* In a series with `zones`, return the zone that the point belongs to.
*
- * @return {Object}
+ * @function Highcharts.Point#getZone
+ *
+ * @return {Highcharts.PlotSeriesZonesOptions}
* The zone item.
*/
- getZone: function() {
+ getZone: function () {
var series = this.series,
zones = series.zones,
zoneAxis = series.zoneAxis || 'y',
@@ -21941,8 +29576,15 @@
zone = zones[++i];
}
+ // For resetting or reusing the point (#8100)
+ if (!this.nonZonedColor) {
+ this.nonZonedColor = this.color;
+ }
+
if (zone && zone.color && !this.options.color) {
this.color = zone.color;
+ } else {
+ this.color = this.nonZonedColor;
}
return zone;
@@ -21953,8 +29595,9 @@
* `series.data`.
*
* @private
+ * @function Highcharts.Point#destroy
*/
- destroy: function() {
+ destroy: function () {
var point = this,
series = point.series,
chart = series.chart,
@@ -21975,8 +29618,8 @@
point.onMouseOut();
}
- // remove all events
- if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive
+ // Remove all events and elements
+ if (point.graphic || point.dataLabel || point.dataLabels) {
removeEvent(point);
point.destroyElements();
}
@@ -21988,36 +29631,61 @@
for (prop in point) {
point[prop] = null;
}
-
-
},
/**
* Destroy SVG elements associated with the point.
*
* @private
+ * @function Highcharts.Point#destroyElements
*/
- destroyElements: function() {
+ destroyElements: function () {
var point = this,
- props = ['graphic', 'dataLabel', 'dataLabelUpper', 'connector', 'shadowGroup'],
+ props = [
+ 'graphic',
+ 'dataLabel',
+ 'dataLabelUpper',
+ 'connector',
+ 'shadowGroup'
+ ],
prop,
i = 6;
+
while (i--) {
prop = props[i];
if (point[prop]) {
point[prop] = point[prop].destroy();
}
}
+ // Handle point.dataLabels and point.connectors
+ if (point.dataLabels) {
+ point.dataLabels.forEach(function (label) {
+ if (label.element) {
+ label.destroy();
+ }
+ });
+ delete point.dataLabels;
+ }
+ if (point.connectors) {
+ point.connectors.forEach(function (connector) {
+ if (connector.element) {
+ connector.destroy();
+ }
+ });
+ delete point.connectors;
+ }
},
/**
* Return the configuration hash needed for the data label and tooltip
* formatters.
*
- * @returns {Object}
- * Abstract object used in formatters and formats.
+ * @function Highcharts.Point#getLabelConfig
+ *
+ * @return {Highcharts.PointLabelObject}
+ * Abstract object used in formatters and formats.
*/
- getLabelConfig: function() {
+ getLabelConfig: function () {
return {
x: this.category,
y: this.y,
@@ -22031,169 +29699,508 @@
};
},
- /**
- * Extendable method for formatting each point's tooltip line.
- *
- * @param {String} pointFormat
- * The point format.
- * @return {String}
- * A string to be concatenated in to the common tooltip text.
- */
- tooltipFormatter: function(pointFormat) {
+ /**
+ * Extendable method for formatting each point's tooltip line.
+ *
+ * @function Highcharts.Point#tooltipFormatter
+ *
+ * @param {string} pointFormat
+ * The point format.
+ *
+ * @return {string}
+ * A string to be concatenated in to the common tooltip text.
+ */
+ tooltipFormatter: function (pointFormat) {
+
+ // Insert options for valueDecimals, valuePrefix, and valueSuffix
+ var series = this.series,
+ seriesTooltipOptions = series.tooltipOptions,
+ valueDecimals = pick(seriesTooltipOptions.valueDecimals, ''),
+ valuePrefix = seriesTooltipOptions.valuePrefix || '',
+ valueSuffix = seriesTooltipOptions.valueSuffix || '';
+
+ // Replace default point style with class name
+ if (series.chart.styledMode) {
+ pointFormat = series.chart.tooltip.styledModeFormat(pointFormat);
+ }
+
+ // Loop over the point array map and replace unformatted values with
+ // sprintf formatting markup
+ (series.pointArrayMap || ['y']).forEach(function (key) {
+ key = '{point.' + key; // without the closing bracket
+ if (valuePrefix || valueSuffix) {
+ pointFormat = pointFormat.replace(
+ RegExp(key + '}', 'g'),
+ valuePrefix + key + '}' + valueSuffix
+ );
+ }
+ pointFormat = pointFormat.replace(
+ RegExp(key + '}', 'g'),
+ key + ':,.' + valueDecimals + 'f}'
+ );
+ });
+
+ return format(pointFormat, {
+ point: this,
+ series: this.series
+ }, series.chart.time);
+ },
+
+ /**
+ * Fire an event on the Point object.
+ *
+ * @private
+ * @function Highcharts.Point#firePointEvent
+ *
+ * @param {string} eventType
+ * Type of the event.
+ *
+ * @param {object} eventArgs
+ * Additional event arguments.
+ *
+ * @param {Function} defaultFunction
+ * Default event handler.
+ *
+ * @fires Highcharts.Point#event:*
+ */
+ firePointEvent: function (eventType, eventArgs, defaultFunction) {
+ var point = this,
+ series = this.series,
+ seriesOptions = series.options;
+
+ // load event handlers on demand to save time on mouseover/out
+ if (
+ seriesOptions.point.events[eventType] ||
+ (
+ point.options &&
+ point.options.events &&
+ point.options.events[eventType]
+ )
+ ) {
+ this.importEvents();
+ }
+
+ // add default handler if in selection mode
+ if (eventType === 'click' && seriesOptions.allowPointSelect) {
+ defaultFunction = function (event) {
+ // Control key is for Windows, meta (= Cmd key) for Mac, Shift
+ // for Opera.
+ if (point.select) { // #2911
+ point.select(
+ null,
+ event.ctrlKey || event.metaKey || event.shiftKey
+ );
+ }
+ };
+ }
+
+ fireEvent(this, eventType, eventArgs, defaultFunction);
+ },
+
+ /**
+ * For categorized axes this property holds the category name for the
+ * point. For other axes it holds the X value.
+ *
+ * @name Highcharts.Point#category
+ * @type {number|string}
+ */
+
+ /**
+ * The name of the point. The name can be given as the first position of the
+ * point configuration array, or as a `name` property in the configuration:
+ *
+ * @example
+ * // Array config
+ * data: [
+ * ['John', 1],
+ * ['Jane', 2]
+ * ]
+ *
+ * // Object config
+ * data: [{
+ * name: 'John',
+ * y: 1
+ * }, {
+ * name: 'Jane',
+ * y: 2
+ * }]
+ *
+ * @name Highcharts.Point#name
+ * @type {string}
+ */
+
+ /**
+ * The percentage for points in a stacked series or pies.
+ *
+ * @name Highcharts.Point#percentage
+ * @type {number}
+ */
+
+ /**
+ * The total of values in either a stack for stacked series, or a pie in a
+ * pie series.
+ *
+ * @name Highcharts.Point#total
+ * @type {number}
+ */
+
+ /**
+ * For certain series types, like pie charts, where individual points can
+ * be shown or hidden.
+ *
+ * @name Highcharts.Point#visible
+ * @type {boolean}
+ */
+ visible: true
+ };
+
+ }(Highcharts));
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+
+ /**
+ * Function callback when a series has been animated.
+ *
+ * @callback Highcharts.SeriesAfterAnimateCallbackFunction
+ *
+ * @param {Highcharts.Series} this
+ * The series where the event occured.
+ *
+ * @param {Highcharts.SeriesAfterAnimateEventObject} event
+ * Event arguments.
+ */
+
+ /**
+ * Event information regarding completed animation of a series.
+ *
+ * @interface Highcharts.SeriesAfterAnimateEventObject
+ *//**
+ * Animated series.
+ * @name Highcharts.SeriesAfterAnimateEventObject#target
+ * @type {Highcharts.Series}
+ *//**
+ * Event type.
+ * @name Highcharts.SeriesAfterAnimateEventObject#type
+ * @type {"afterAnimate"}
+ */
+
+ /**
+ * Function callback when the checkbox next to the series' name in the legend is
+ * clicked.
+ *
+ * @callback Highcharts.SeriesCheckboxClickCallbackFunction
+ *
+ * @param {Highcharts.Series} this
+ * The series where the event occured.
+ *
+ * @param {Highcharts.SeriesCheckboxClickEventObject} event
+ * Event arguments.
+ */
+
+ /**
+ * Event information regarding check of a series box.
+ *
+ * @interface Highcharts.SeriesCheckboxClickEventObject
+ *//**
+ * Whether the box has been checked.
+ * @name Highcharts.SeriesCheckboxClickEventObject#checked
+ * @type {boolean}
+ *//**
+ * Related series.
+ * @name Highcharts.SeriesCheckboxClickEventObject#item
+ * @type {Highcharts.Series}
+ *//**
+ * Related series.
+ * @name Highcharts.SeriesCheckboxClickEventObject#target
+ * @type {Highcharts.Series}
+ *//**
+ * Event type.
+ * @name Highcharts.SeriesCheckboxClickEventObject#type
+ * @type {"checkboxClick"}
+ */
+
+ /**
+ * Function callback when a series is clicked. Return false to cancel toogle
+ * actions.
+ *
+ * @callback Highcharts.SeriesClickCallbackFunction
+ *
+ * @param {Highcharts.Series} this
+ * The series where the event occured.
+ *
+ * @param {Highcharts.SeriesClickEventObject} event
+ * Event arguments.
+ */
+
+ /**
+ * Common information for a click event on a series.
+ *
+ * @interface Highcharts.SeriesClickEventObject
+ * @implements {global.Event}
+ *//**
+ * Nearest point on the graph.
+ * @name Highcharts.SeriesClickEventObject#point
+ * @type {Highcharts.Point}
+ */
+
+ /**
+ * @interface Highcharts.SeriesDataLabelsFormatterContextObject
+ *//**
+ * @name Highcharts.SeriesDataLabelsFormatterContextObject#point
+ * @type {Highcharts.Point}
+ */
+
+ /**
+ * Gets fired when the series is hidden after chart generation time, either by
+ * clicking the legend item or by calling `.hide()`.
+ *
+ * @callback Highcharts.SeriesHideCallbackFunction
+ *
+ * @param {Highcharts.Series} this
+ * The series where the event occured.
+ *
+ * @param {global.Event} event
+ * The event that occured.
+ */
+
+ /**
+ * Gets fired when the legend item belonging to the series is clicked. The
+ * default action is to toggle the visibility of the series. This can be
+ * prevented by returning `false` or calling `event.preventDefault()`.
+ *
+ * @callback Highcharts.SeriesLegendItemClickCallbackFunction
+ *
+ * @param {Highcharts.Series} this
+ * The series where the event occured.
+ *
+ * @param {Highcharts.SeriesLegendItemClickEventObject} event
+ * The event that occured.
+ */
- // Insert options for valueDecimals, valuePrefix, and valueSuffix
- var series = this.series,
- seriesTooltipOptions = series.tooltipOptions,
- valueDecimals = pick(seriesTooltipOptions.valueDecimals, ''),
- valuePrefix = seriesTooltipOptions.valuePrefix || '',
- valueSuffix = seriesTooltipOptions.valueSuffix || '';
+ /**
+ * Information about the event.
+ *
+ * @interface Highcharts.SeriesLegendItemClickEventObject
+ *//**
+ * Related browser event.
+ * @name Highcharts.SeriesLegendItemClickEventObject#browserEvent
+ * @type {Highcharts.PointerEvent}
+ *//**
+ * Prevent the default action of toggle the visibility of the series.
+ * @name Highcharts.SeriesLegendItemClickEventObject#preventDefault
+ * @type {Function}
+ *//**
+ * Related series.
+ * @name Highcharts.SeriesCheckboxClickEventObject#target
+ * @type {Highcharts.Series}
+ *//**
+ * Event type.
+ * @name Highcharts.SeriesCheckboxClickEventObject#type
+ * @type {"checkboxClick"}
+ */
- // Loop over the point array map and replace unformatted values with sprintf formatting markup
- each(series.pointArrayMap || ['y'], function(key) {
- key = '{point.' + key; // without the closing bracket
- if (valuePrefix || valueSuffix) {
- pointFormat = pointFormat.replace(key + '}', valuePrefix + key + '}' + valueSuffix);
- }
- pointFormat = pointFormat.replace(key + '}', key + ':,.' + valueDecimals + 'f}');
- });
+ /**
+ * Gets fired when the mouse leaves the graph.
+ *
+ * @callback Highcharts.SeriesMouseOutCallbackFunction
+ *
+ * @param {Highcharts.Series} this
+ * Series where the event occured.
+ *
+ * @param {global.Event} event
+ * Event that occured.
+ */
- return format(pointFormat, {
- point: this,
- series: this.series
- });
- },
+ /**
+ * Gets fired when the mouse enters the graph.
+ *
+ * @callback Highcharts.SeriesMouseOverCallbackFunction
+ *
+ * @param {Highcharts.Series} this
+ * Series where the event occured.
+ *
+ * @param {global.Event} event
+ * Event that occured.
+ */
- /**
- * Fire an event on the Point object.
- *
- * @private
- * @param {String} eventType
- * @param {Object} eventArgs Additional event arguments
- * @param {Function} defaultFunction Default event handler
- */
- firePointEvent: function(eventType, eventArgs, defaultFunction) {
- var point = this,
- series = this.series,
- seriesOptions = series.options;
+ /**
+ * Translation and scale for the plot area of a series.
+ *
+ * @interface Highcharts.SeriesPlotBoxObject
+ *//**
+ * @name Highcharts.SeriesPlotBoxObject#translateX
+ * @type {number}
+ *//**
+ * @name Highcharts.SeriesPlotBoxObject#translateY
+ * @type {number}
+ *//**
+ * @name Highcharts.SeriesPlotBoxObject#scaleX
+ * @type {number}
+ *//**
+ * @name Highcharts.SeriesPlotBoxObject#scaleY
+ * @type {number}
+ */
- // load event handlers on demand to save time on mouseover/out
- if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) {
- this.importEvents();
- }
+ /**
+ * Function callback when a series point is clicked. Return false to cancel the
+ * action.
+ *
+ * @callback Highcharts.SeriesPointClickCallbackFunction
+ *
+ * @param {Highcharts.Point} this
+ * The point where the event occured.
+ *
+ * @param {Highcharts.SeriesPointClickEventObject} event
+ * Event arguments.
+ */
- // add default handler if in selection mode
- if (eventType === 'click' && seriesOptions.allowPointSelect) {
- defaultFunction = function(event) {
- // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
- if (point.select) { // Could be destroyed by prior event handlers (#2911)
- point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
- }
- };
- }
+ /**
+ * Common information for a click event on a series point.
+ *
+ * @interface Highcharts.SeriesPointClickEventObject
+ * @implements {Highcharts.PointerEventObject}
+ *//**
+ * Clicked point.
+ * @name Highcharts.SeriesPointClickEventObject#point
+ * @type {Highcharts.Point}
+ */
- fireEvent(this, eventType, eventArgs, defaultFunction);
- },
+ /**
+ * Gets fired when the mouse leaves the area close to the point.
+ *
+ * @callback Highcharts.SeriesPointMouseOutCallbackFunction
+ *
+ * @param {Highcharts.Point} this
+ * Point where the event occured.
+ *
+ * @param {global.Event} event
+ * Event that occured.
+ */
- /**
- * For certain series types, like pie charts, where individual points can
- * be shown or hidden.
- *
- * @name visible
- * @memberOf Highcharts.Point
- * @type {Boolean}
- */
- visible: true
- };
+ /**
+ * Gets fired when the mouse enters the area close to the point.
+ *
+ * @callback Highcharts.SeriesPointMouseOverCallbackFunction
+ *
+ * @param {Highcharts.Point} this
+ * Point where the event occured.
+ *
+ * @param {global.Event} event
+ * Event that occured.
+ */
/**
- * For categorized axes this property holds the category name for the
- * point. For other axes it holds the X value.
+ * Gets fired when the point is removed using the `.remove()` method.
+ *
+ * @callback Highcharts.SeriesPointRemoveCallbackFunction
*
- * @name category
- * @memberOf Highcharts.Point
- * @type {String|Number}
+ * @param {Highcharts.Point} this
+ * Point where the event occured.
+ *
+ * @param {global.Event} event
+ * Event that occured.
*/
/**
- * The name of the point. The name can be given as the first position of the
- * point configuration array, or as a `name` property in the configuration:
+ * Gets fired when the point is selected either programmatically or following a
+ * click on the point.
*
- * @example
- * // Array config
- * data: [
- * ['John', 1],
- * ['Jane', 2]
- * ]
- *
- * // Object config
- * data: [{
- * name: 'John',
- * y: 1
- * }, {
- * name: 'Jane',
- * y: 2
- * }]
+ * @callback Highcharts.SeriesPointSelectCallbackFunction
+ *
+ * @param {Highcharts.Point} this
+ * Point where the event occured.
*
- * @name name
- * @memberOf Highcharts.Point
- * @type {String}
+ * @param {Highcharts.SeriesPointSelectEventObject} event
+ * Event that occured.
*/
+ /**
+ * Information about the select event.
+ *
+ * @interface Highcharts.SeriesPointSelectEventObject
+ * @implements {global.Event}
+ *//**
+ * @name Highcharts.SeriesPointSelectEventObject#accumulate
+ * @type {boolean}
+ */
/**
- * The percentage for points in a stacked series or pies.
+ * Fires when the point is unselected either programmatically or following a
+ * click on the point.
+ *
+ * @callback Highcharts.SeriesPointUnselectCallbackFunction
*
- * @name percentage
- * @memberOf Highcharts.Point
- * @type {Number}
+ * @param {Highcharts.Point} this
+ * Point where the event occured.
+ *
+ * @param {Highcharts.SeriesPointUnselectEventObject} event
+ * Event that occured.
*/
/**
- * The total of values in either a stack for stacked series, or a pie in a pie
- * series.
+ * Information about the unselect event.
*
- * @name total
- * @memberOf Highcharts.Point
- * @type {Number}
+ * @interface Highcharts.SeriesPointUnselectEventObject
+ * @implements {global.Event}
+ *//**
+ * @name Highcharts.SeriesPointUnselectEventObject#accumulate
+ * @type {boolean}
*/
/**
- * The x value of the point.
+ * Gets fired when the point is updated programmatically through the `.update()`
+ * method.
*
- * @name x
- * @memberOf Highcharts.Point
- * @type {Number}
+ * @callback Highcharts.SeriesPointUpdateCallbackFunction
+ *
+ * @param {Highcharts.Point} this
+ * Point where the event occured.
+ *
+ * @param {Highcharts.SeriesPointUpdateEventObject} event
+ * Event that occured.
*/
/**
- * The y value of the point.
+ * Information about the update event.
*
- * @name y
- * @memberOf Highcharts.Point
- * @type {Number}
+ * @interface Highcharts.SeriesPointUpdateEventObject
+ * @implements {global.Event}
+ *//**
+ * Options data of the update event.
+ * @name Highcharts.SeriesPointUpdateEventObject#options
+ * @type {number|object|Array<(number|string)>|null}
*/
- }(Highcharts));
- (function(H) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * Gets fired when the series is shown after chart generation time, either by
+ * clicking the legend item or by calling `.show()`.
*
- * License: www.highcharts.com/license
+ * @callback Highcharts.SeriesShowCallbackFunction
+ *
+ * @param {Highcharts.Series} this
+ * Series where the event occured.
+ *
+ * @param {global.Event} event
+ * Event that occured.
*/
+
+
+
var addEvent = H.addEvent,
animObject = H.animObject,
arrayMax = H.arrayMax,
arrayMin = H.arrayMin,
correctFloat = H.correctFloat,
- Date = H.Date,
defaultOptions = H.defaultOptions,
defaultPlotOptions = H.defaultPlotOptions,
defined = H.defined,
- each = H.each,
erase = H.erase,
extend = H.extend,
fireEvent = H.fireEvent,
- grep = H.grep,
isArray = H.isArray,
isNumber = H.isNumber,
isString = H.isString,
@@ -22211,818 +30218,920 @@
/**
* This is the base series prototype that all other series types inherit from.
* A new series is initialized either through the
- * {@link https://api.highcharts.com/highcharts/series|series} option structure,
- * or after the chart is initialized, through
+ * [series](https://api.highcharts.com/highcharts/series)
+ * option structure, or after the chart is initialized, through
* {@link Highcharts.Chart#addSeries}.
*
* The object can be accessed in a number of ways. All series and point event
* handlers give a reference to the `series` object. The chart object has a
- * {@link Highcharts.Chart.series|series} property that is a collection of all
+ * {@link Highcharts.Chart#series|series} property that is a collection of all
* the chart's series. The point objects and axis objects also have the same
* reference.
*
* Another way to reference the series programmatically is by `id`. Add an id
- * in the series configuration options, and get the series object by {@link
- * Highcharts.Chart#get}.
+ * in the series configuration options, and get the series object by
+ * {@link Highcharts.Chart#get}.
*
* Configuration options for the series are given in three levels. Options for
* all series in a chart are given in the
- * {@link https://api.highcharts.com/highcharts/plotOptions.series|
- * plotOptions.series} object. Then options for all series of a specific type
+ * [plotOptions.series](https://api.highcharts.com/highcharts/plotOptions.series)
+ * object. Then options for all series of a specific type
* are given in the plotOptions of that type, for example `plotOptions.line`.
* Next, options for one single series are given in the series array, or as
- * arguements to `chart.addSeries`.
+ * arguments to `chart.addSeries`.
*
* The data in the series is stored in various arrays.
*
* - First, `series.options.data` contains all the original config options for
- * each point whether added by options or methods like `series.addPoint`.
+ * each point whether added by options or methods like `series.addPoint`.
+ *
* - Next, `series.data` contains those values converted to points, but in case
- * the series data length exceeds the `cropThreshold`, or if the data is
- * grouped, `series.data` doesn't contain all the points. It only contains the
- * points that have been created on demand.
+ * the series data length exceeds the `cropThreshold`, or if the data is
+ * grouped, `series.data` doesn't contain all the points. It only contains the
+ * points that have been created on demand.
+ *
* - Then there's `series.points` that contains all currently visible point
- * objects. In case of cropping, the cropped-away points are not part of this
- * array. The `series.points` array starts at `series.cropStart` compared to
- * `series.data` and `series.options.data`. If however the series data is
- * grouped, these can't be correlated one to one.
+ * objects. In case of cropping, the cropped-away points are not part of this
+ * array. The `series.points` array starts at `series.cropStart` compared to
+ * `series.data` and `series.options.data`. If however the series data is
+ * grouped, these can't be correlated one to one.
+ *
* - `series.xData` and `series.processedXData` contain clean x values,
- * equivalent to `series.data` and `series.points`.
+ * equivalent to `series.data` and `series.points`.
+ *
* - `series.yData` and `series.processedYData` contain clean y values,
- * equivalent to `series.data` and `series.points`.
+ * equivalent to `series.data` and `series.points`.
+ *
+ * @class
+ * @name Highcharts.Series
+ *
+ * @param {Highcharts.Chart} chart
+ * The chart instance.
*
- * @class Highcharts.Series
- * @param {Highcharts.Chart} chart
- * The chart instance.
- * @param {Options.plotOptions.series} options
- * The series options.
+ * @param {Highcharts.SeriesOptionsType|object} options
+ * The series options.
+ *//**
+ * The line series is the base type and is therefor the series base prototype.
+ *
+ * @private
+ * @class
+ * @name Highcharts.seriesTypes.line
*
+ * @augments Highcharts.Series
*/
+ H.Series = H.seriesType(
+ 'line',
- /**
- * General options for all series types.
- * @optionparent plotOptions.series
- */
- H.Series = H.seriesType('line', null, { // base series options
+ /**
+ * Series options for specific data and the data itself. In TypeScript you
+ * have to cast the series options to specific series types, to get all
+ * possible options for a series.
+ *
+ * @example
+ * // TypeScript example
+ * Highcharts.chart('container', {
+ * series: [{
+ * color: '#06C',
+ * data: [[0, 1], [2, 3]]
+ * } as Highcharts.SeriesLineOptions ]
+ * });
+ *
+ * @type {Array<*>}
+ * @apioption series
+ */
+
+ /**
+ * An id for the series. This can be used after render time to get a pointer
+ * to the series object through `chart.get()`.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-id/
+ * Get series by id
+ *
+ * @type {string}
+ * @since 1.2.0
+ * @apioption series.id
+ */
+
+ /**
+ * The index of the series in the chart, affecting the internal index in the
+ * `chart.series` array, the visible Z index as well as the order in the
+ * legend.
+ *
+ * @type {number}
+ * @since 2.3.0
+ * @apioption series.index
+ */
+
+ /**
+ * The sequential index of the series in the legend.
+ *
+ * @see [legend.reversed](#legend.reversed),
+ * [yAxis.reversedStacks](#yAxis.reversedStacks)
+ *
+ * @sample {highcharts|highstock} highcharts/series/legendindex/
+ * Legend in opposite order
+ *
+ * @type {number}
+ * @apioption series.legendIndex
+ */
+ /**
+ * The name of the series as shown in the legend, tooltip etc.
+ *
+ * @sample {highcharts} highcharts/series/name/
+ * Series name
+ * @sample {highmaps} maps/demo/category-map/
+ * Series name
+ *
+ * @type {string}
+ * @apioption series.name
+ */
+
+ /**
+ * This option allows grouping series in a stacked chart. The stack option
+ * can be a string or anything else, as long as the grouped series' stack
+ * options match each other after conversion into a string.
+ *
+ * @sample {highcharts} highcharts/series/stack/
+ * Stacked and grouped columns
+ *
+ * @type {string|object}
+ * @since 2.1
+ * @product highcharts highstock
+ * @apioption series.stack
+ */
+
+ /**
+ * The type of series, for example `line` or `column`. By default, the
+ * series type is inherited from [chart.type](#chart.type), so unless the
+ * chart is a combination of series types, there is no need to set it on the
+ * series level.
+ *
+ * In TypeScript instead the `type` option must always be set.
+ *
+ * @sample {highcharts} highcharts/series/type/
+ * Line and column in the same chart
+ * @sample {highmaps} maps/demo/mapline-mappoint/
+ * Multiple types in the same map
+ *
+ * @type {string}
+ * @apioption series.type
+ */
+
+ /**
+ * When using dual or multiple x axes, this number defines which xAxis the
+ * particular series is connected to. It refers to either the
+ * {@link #xAxis.id|axis id}
+ * or the index of the axis in the xAxis array, with 0 being the first.
+ *
+ * @type {number|string}
+ * @default 0
+ * @product highcharts highstock
+ * @apioption series.xAxis
+ */
+
+ /**
+ * When using dual or multiple y axes, this number defines which yAxis the
+ * particular series is connected to. It refers to either the
+ * {@link #yAxis.id|axis id}
+ * or the index of the axis in the yAxis array, with 0 being the first.
+ *
+ * @sample {highcharts} highcharts/series/yaxis/
+ * Apply the column series to the secondary Y axis
+ *
+ * @type {number|string}
+ * @default 0
+ * @product highcharts highstock
+ * @apioption series.yAxis
+ */
+
+ /**
+ * Define the visual z index of the series.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-zindex-default/
+ * With no z index, the series defined last are on top
+ * @sample {highcharts} highcharts/plotoptions/series-zindex/
+ * With a z index, the series with the highest z index is on top
+ * @sample {highstock} highcharts/plotoptions/series-zindex-default/
+ * With no z index, the series defined last are on top
+ * @sample {highstock} highcharts/plotoptions/series-zindex/
+ * With a z index, the series with the highest z index is on top
+ *
+ * @type {number}
+ * @product highcharts highstock
+ * @apioption series.zIndex
+ */
+
+ null,
+
+ /**
+ * General options for all series types.
+ *
+ * @optionparent plotOptions.series
+ */
+ { // base series options
+
+ /**
+ * The SVG value used for the `stroke-linecap` and `stroke-linejoin`
+ * of a line graph. Round means that lines are rounded in the ends and
+ * bends.
+ *
+ * @type {string}
+ * @validvalue ["round", "butt", "square"]
+ * @default round
+ * @since 3.0.7
+ * @apioption plotOptions.line.linecap
+ */
+
+ /**
+ * Pixel width of the graph line.
+ *
+ * @see In styled mode, the line stroke-width can be set with the
+ * `.highcharts-graph` class name.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-linewidth-general/
+ * On all series
+ * @sample {highcharts} highcharts/plotoptions/series-linewidth-specific/
+ * On one single series
+ *
+ * @product highcharts highstock
+ */
+ lineWidth: 2,
+
+ /**
+ * For some series, there is a limit that shuts down initial animation
+ * by default when the total number of points in the chart is too high.
+ * For example, for a column chart and its derivatives, animation does
+ * not run if there is more than 250 points totally. To disable this
+ * cap, set `animationLimit` to `Infinity`.
+ *
+ * @type {number}
+ * @apioption plotOptions.series.animationLimit
+ */
+
+ /**
+ * Allow this series' points to be selected by clicking on the graphic
+ * (columns, point markers, pie slices, map areas etc).
+ *
+ * @see {@link Highcharts.Chart#getSelectedPoints}.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-allowpointselect-line/
+ * Line
+ * @sample {highcharts} highcharts/plotoptions/series-allowpointselect-column/
+ * Column
+ * @sample {highcharts} highcharts/plotoptions/series-allowpointselect-pie/
+ * Pie
+ * @sample {highmaps} maps/plotoptions/series-allowpointselect/
+ * Map area
+ * @sample {highmaps} maps/plotoptions/mapbubble-allowpointselect/
+ * Map bubble
+ *
+ * @since 1.2.0
+ */
+ allowPointSelect: false,
+
+ /**
+ * If true, a checkbox is displayed next to the legend item to allow
+ * selecting the series. The state of the checkbox is determined by
+ * the `selected` option.
+ *
+ * @productdesc {highmaps}
+ * Note that if a `colorAxis` is defined, the color axis is represented
+ * in the legend, not the series.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-showcheckbox-true/
+ * Show select box
+ *
+ * @since 1.2.0
+ */
+ showCheckbox: false,
+
+ /**
+ * Enable or disable the initial animation when a series is displayed.
+ * The animation can also be set as a configuration object. Please
+ * note that this option only applies to the initial animation of the
+ * series itself. For other animations, see [chart.animation](
+ * #chart.animation) and the animation parameter under the API methods.
+ * The following properties are supported:
+ *
+ * - `duration`: The duration of the animation in milliseconds.
+ *
+ * - `easing`: Can be a string reference to an easing function set on
+ * the `Math` object or a function. See the _Custom easing function_
+ * demo below.
+ *
+ * Due to poor performance, animation is disabled in old IE browsers
+ * for several chart types.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-animation-disabled/
+ * Animation disabled
+ * @sample {highcharts} highcharts/plotoptions/series-animation-slower/
+ * Slower animation
+ * @sample {highcharts} highcharts/plotoptions/series-animation-easing/
+ * Custom easing function
+ * @sample {highstock} stock/plotoptions/animation-slower/
+ * Slower animation
+ * @sample {highstock} stock/plotoptions/animation-easing/
+ * Custom easing function
+ * @sample {highmaps} maps/plotoptions/series-animation-true/
+ * Animation enabled on map series
+ * @sample {highmaps} maps/plotoptions/mapbubble-animation-false/
+ * Disabled on mapbubble series
+ *
+ * @type {boolean|Highcharts.AnimationOptionsObject}
+ * @default {highcharts} true
+ * @default {highstock} true
+ * @default {highmaps} false
+ */
+ animation: {
+
+ /**
+ * @type {number}
+ * @default 1000
+ * @apioption plotOptions.series.animation.duration
+ */
+ duration: 1000
+ },
+
+ /**
+ * An additional class name to apply to the series' graphical elements.
+ * This option does not replace default class names of the graphical
+ * element.
+ *
+ * @type {string}
+ * @since 5.0.0
+ * @apioption plotOptions.series.className
+ */
- /**
- * The SVG value used for the `stroke-linecap` and `stroke-linejoin`
- * of a line graph. Round means that lines are rounded in the ends and
- * bends.
- *
- * @validvalue ["round", "butt", "square"]
- * @type {String}
- * @default round
- * @since 3.0.7
- * @apioption plotOptions.line.linecap
- */
+ /**
+ * Disable this option to allow series rendering in the whole plotting
+ * area.
+ *
+ * **Note:** Clipping should be always enabled when
+ * [chart.zoomType](#chart.zoomType) is set
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-clip/
+ * Disabled clipping
+ *
+ * @default true
+ * @type {boolean}
+ * @since 3.0.0
+ * @apioption plotOptions.series.clip
+ */
- /**
- * Pixel with of the graph line.
- *
- * @type {Number}
- * @see In styled mode, the line stroke-width can be set with the
- * `.highcharts-graph` class name.
- * @sample {highcharts} highcharts/plotoptions/series-linewidth-general/
- * On all series
- * @sample {highcharts} highcharts/plotoptions/series-linewidth-specific/
- * On one single series
- * @default 2
- * @product highcharts highstock
- */
- lineWidth: 2,
+ /**
+ * The main color of the series. In line type series it applies to the
+ * line and the point markers unless otherwise specified. In bar type
+ * series it applies to the bars unless a color is specified per point.
+ * The default value is pulled from the `options.colors` array.
+ *
+ * In styled mode, the color can be defined by the
+ * [colorIndex](#plotOptions.series.colorIndex) option. Also, the series
+ * color can be set with the `.highcharts-series`,
+ * `.highcharts-color-{n}`, `.highcharts-{type}-series` or
+ * `.highcharts-series-{n}` class, or individual classes given by the
+ * `className` option.
+ *
+ * @productdesc {highmaps}
+ * In maps, the series color is rarely used, as most choropleth maps use
+ * the color to denote the value of each point. The series color can
+ * however be used in a map with multiple series holding categorized
+ * data.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-color-general/
+ * General plot option
+ * @sample {highcharts} highcharts/plotoptions/series-color-specific/
+ * One specific series
+ * @sample {highcharts} highcharts/plotoptions/series-color-area/
+ * Area color
+ * @sample {highcharts} highcharts/series/infographic/
+ * Pattern fill
+ * @sample {highmaps} maps/demo/category-map/
+ * Category map by multiple series
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @apioption plotOptions.series.color
+ */
+ /**
+ * Styled mode only. A specific color index to use for the series, so
+ * its graphic representations are given the class name
+ * `highcharts-color-{n}`.
+ *
+ * @type {number}
+ * @since 5.0.0
+ * @apioption plotOptions.series.colorIndex
+ */
- /**
- * For some series, there is a limit that shuts down initial animation
- * by default when the total number of points in the chart is too high.
- * For example, for a column chart and its derivatives, animation doesn't
- * run if there is more than 250 points totally. To disable this cap, set
- * `animationLimit` to `Infinity`.
- *
- * @type {Number}
- * @apioption plotOptions.series.animationLimit
- */
- /**
- * Allow this series' points to be selected by clicking on the graphic
- * (columns, point markers, pie slices, map areas etc).
- *
- * @see [Chart#getSelectedPoints]
- * (../class-reference/Highcharts.Chart#getSelectedPoints).
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/series-allowpointselect-line/
- * Line
- * @sample {highcharts}
- * highcharts/plotoptions/series-allowpointselect-column/
- * Column
- * @sample {highcharts} highcharts/plotoptions/series-allowpointselect-pie/
- * Pie
- * @sample {highmaps} maps/plotoptions/series-allowpointselect/
- * Map area
- * @sample {highmaps} maps/plotoptions/mapbubble-allowpointselect/
- * Map bubble
- * @default false
- * @since 1.2.0
- */
- allowPointSelect: false,
+ /**
+ * Whether to connect a graph line across null points, or render a gap
+ * between the two points on either side of the null.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-connectnulls-false/
+ * False by default
+ * @sample {highcharts} highcharts/plotoptions/series-connectnulls-true/
+ * True
+ *
+ * @type {boolean}
+ * @default false
+ * @product highcharts highstock
+ * @apioption plotOptions.series.connectNulls
+ */
+ /**
+ * You can set the cursor to "pointer" if you have click events attached
+ * to the series, to signal to the user that the points and lines can
+ * be clicked.
+ *
+ * In styled mode, the series cursor can be set with the same classes
+ * as listed under [series.color](#plotOptions.series.color).
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-cursor-line/
+ * On line graph
+ * @sample {highcharts} highcharts/plotoptions/series-cursor-column/
+ * On columns
+ * @sample {highcharts} highcharts/plotoptions/series-cursor-scatter/
+ * On scatter markers
+ * @sample {highstock} stock/plotoptions/cursor/
+ * Pointer on a line graph
+ * @sample {highmaps} maps/plotoptions/series-allowpointselect/
+ * Map area
+ * @sample {highmaps} maps/plotoptions/mapbubble-allowpointselect/
+ * Map bubble
+ *
+ * @type {string|Highcharts.CursorType}
+ * @apioption plotOptions.series.cursor
+ */
- /**
- * If true, a checkbox is displayed next to the legend item to allow
- * selecting the series. The state of the checkbox is determined by
- * the `selected` option.
- *
- * @productdesc {highmaps}
- * Note that if a `colorAxis` is defined, the color axis is represented in
- * the legend, not the series.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/series-showcheckbox-true/
- * Show select box
- * @default false
- * @since 1.2.0
- */
- showCheckbox: false,
-
-
-
- /**
- * Enable or disable the initial animation when a series is displayed.
- * The animation can also be set as a configuration object. Please
- * note that this option only applies to the initial animation of the
- * series itself. For other animations, see [chart.animation](#chart.
- * animation) and the animation parameter under the API methods. The
- * following properties are supported:
- *
- *
- *
- * duration
- *
- * The duration of the animation in milliseconds.
- *
- * easing
- *
- * A string reference to an easing function set on the `Math` object.
- * See the _Custom easing function_ demo below.
- *
- *
- *
- * Due to poor performance, animation is disabled in old IE browsers
- * for several chart types.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/series-animation-disabled/
- * Animation disabled
- * @sample {highcharts} highcharts/plotoptions/series-animation-slower/
- * Slower animation
- * @sample {highcharts} highcharts/plotoptions/series-animation-easing/
- * Custom easing function
- * @sample {highstock} stock/plotoptions/animation-slower/
- * Slower animation
- * @sample {highstock} stock/plotoptions/animation-easing/
- * Custom easing function
- * @sample {highmaps} maps/plotoptions/series-animation-true/
- * Animation enabled on map series
- * @sample {highmaps} maps/plotoptions/mapbubble-animation-false/
- * Disabled on mapbubble series
- * @default {highcharts} true
- * @default {highstock} true
- * @default {highmaps} false
- */
- animation: {
- duration: 1000
- },
-
- /**
- * A class name to apply to the series' graphical elements.
- *
- * @type {String}
- * @since 5.0.0
- * @apioption plotOptions.series.className
- */
-
- /**
- * The main color of the series. In line type series it applies to the
- * line and the point markers unless otherwise specified. In bar type
- * series it applies to the bars unless a color is specified per point.
- * The default value is pulled from the `options.colors` array.
- *
- * In styled mode, the color can be defined by the
- * [colorIndex](#plotOptions.series.colorIndex) option. Also, the series
- * color can be set with the `.highcharts-series`, `.highcharts-color-{n}`,
- * `.highcharts-{type}-series` or `.highcharts-series-{n}` class, or
- * individual classes given by the `className` option.
- *
- * @productdesc {highmaps}
- * In maps, the series color is rarely used, as most choropleth maps use the
- * color to denote the value of each point. The series color can however be
- * used in a map with multiple series holding categorized data.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/plotoptions/series-color-general/
- * General plot option
- * @sample {highcharts} highcharts/plotoptions/series-color-specific/
- * One specific series
- * @sample {highcharts} highcharts/plotoptions/series-color-area/
- * Area color
- * @sample {highmaps} maps/demo/category-map/
- * Category map by multiple series
- * @apioption plotOptions.series.color
- */
- /**
- * Styled mode only. A specific color index to use for the series, so its
- * graphic representations are given the class name `highcharts-color-
- * {n}`.
- *
- * @type {Number}
- * @since 5.0.0
- * @apioption plotOptions.series.colorIndex
- */
+ /**
+ * A name for the dash style to use for the graph, or for some series
+ * types the outline of each shape.
+ *
+ * In styled mode, the
+ * [stroke dash-array](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/series-dashstyle/)
+ * can be set with the same classes as listed under
+ * [series.color](#plotOptions.series.color).
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-dashstyle-all/
+ * Possible values demonstrated
+ * @sample {highcharts} highcharts/plotoptions/series-dashstyle/
+ * Chart suitable for printing in black and white
+ * @sample {highstock} highcharts/plotoptions/series-dashstyle-all/
+ * Possible values demonstrated
+ * @sample {highmaps} highcharts/plotoptions/series-dashstyle-all/
+ * Possible values demonstrated
+ * @sample {highmaps} maps/plotoptions/series-dashstyle/
+ * Dotted borders on a map
+ *
+ * @type {Highcharts.DashStyleType}
+ * @default Solid
+ * @since 2.1
+ * @apioption plotOptions.series.dashStyle
+ */
+ /**
+ * Requires the Accessibility module.
+ *
+ * A description of the series to add to the screen reader information
+ * about the series.
+ *
+ * @type {string}
+ * @since 5.0.0
+ * @apioption plotOptions.series.description
+ */
- /**
- * Whether to connect a graph line across null points, or render a gap
- * between the two points on either side of the null.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/series-connectnulls-false/
- * False by default
- * @sample {highcharts} highcharts/plotoptions/series-connectnulls-true/
- * True
- * @product highcharts highstock
- * @apioption plotOptions.series.connectNulls
- */
-
-
- /**
- * You can set the cursor to "pointer" if you have click events attached
- * to the series, to signal to the user that the points and lines can
- * be clicked.
- *
- * @validvalue [null, "default", "none", "help", "pointer", "crosshair"]
- * @type {String}
- * @see In styled mode, the series cursor can be set with the same classes
- * as listed under [series.color](#plotOptions.series.color).
- * @sample {highcharts} highcharts/plotoptions/series-cursor-line/
- * On line graph
- * @sample {highcharts} highcharts/plotoptions/series-cursor-column/
- * On columns
- * @sample {highcharts} highcharts/plotoptions/series-cursor-scatter/
- * On scatter markers
- * @sample {highstock} stock/plotoptions/cursor/
- * Pointer on a line graph
- * @sample {highmaps} maps/plotoptions/series-allowpointselect/
- * Map area
- * @sample {highmaps} maps/plotoptions/mapbubble-allowpointselect/
- * Map bubble
- * @apioption plotOptions.series.cursor
- */
-
-
- /**
- * A name for the dash style to use for the graph, or for some series types
- * the outline of each shape. The value for the `dashStyle` include:
- *
- * * Solid
- * * ShortDash
- * * ShortDot
- * * ShortDashDot
- * * ShortDashDotDot
- * * Dot
- * * Dash
- * * LongDash
- * * DashDot
- * * LongDashDot
- * * LongDashDotDot
- *
- * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
- * "ShortDashDotDot", "Dot", "Dash" ,"LongDash", "DashDot",
- * "LongDashDot", "LongDashDotDot"]
- * @type {String}
- * @see In styled mode, the [stroke dash-array](http://jsfiddle.net/gh/get/
- * library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/
- * series-dashstyle/) can be set with the same classes as listed under
- * [series.color](#plotOptions.series.color).
- *
- * @sample {highcharts} highcharts/plotoptions/series-dashstyle-all/
- * Possible values demonstrated
- * @sample {highcharts} highcharts/plotoptions/series-dashstyle/
- * Chart suitable for printing in black and white
- * @sample {highstock} highcharts/plotoptions/series-dashstyle-all/
- * Possible values demonstrated
- * @sample {highmaps} highcharts/plotoptions/series-dashstyle-all/
- * Possible values demonstrated
- * @sample {highmaps} maps/plotoptions/series-dashstyle/
- * Dotted borders on a map
- * @default Solid
- * @since 2.1
- * @apioption plotOptions.series.dashStyle
- */
-
- /**
- * Requires the Accessibility module.
- *
- * A description of the series to add to the screen reader information
- * about the series.
- *
- * @type {String}
- * @default undefined
- * @since 5.0.0
- * @apioption plotOptions.series.description
- */
-
-
-
-
-
- /**
- * Enable or disable the mouse tracking for a specific series. This
- * includes point tooltips and click events on graphs and points. For
- * large datasets it improves performance.
- *
- * @type {Boolean}
- * @sample {highcharts}
- * highcharts/plotoptions/series-enablemousetracking-false/
- * No mouse tracking
- * @sample {highmaps}
- * maps/plotoptions/series-enablemousetracking-false/
- * No mouse tracking
- * @default true
- * @apioption plotOptions.series.enableMouseTracking
- */
-
- /**
- * By default, series are exposed to screen readers as regions. By enabling
- * this option, the series element itself will be exposed in the same
- * way as the data points. This is useful if the series is not used
- * as a grouping entity in the chart, but you still want to attach a
- * description to the series.
- *
- * Requires the Accessibility module.
- *
- * @type {Boolean}
- * @sample highcharts/accessibility/art-grants/
- * Accessible data visualization
- * @default undefined
- * @since 5.0.12
- * @apioption plotOptions.series.exposeElementToA11y
- */
-
- /**
- * Whether to use the Y extremes of the total chart width or only the
- * zoomed area when zooming in on parts of the X axis. By default, the
- * Y axis adjusts to the min and max of the visible data. Cartesian
- * series only.
- *
- * @type {Boolean}
- * @default false
- * @since 4.1.6
- * @product highcharts highstock
- * @apioption plotOptions.series.getExtremesFromAll
- */
+ /**
+ * Enable or disable the mouse tracking for a specific series. This
+ * includes point tooltips and click events on graphs and points. For
+ * large datasets it improves performance.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-enablemousetracking-false/
+ * No mouse tracking
+ * @sample {highmaps} maps/plotoptions/series-enablemousetracking-false/
+ * No mouse tracking
+ *
+ * @type {boolean}
+ * @default true
+ * @apioption plotOptions.series.enableMouseTracking
+ */
- /**
- * An id for the series. This can be used after render time to get a
- * pointer to the series object through `chart.get()`.
- *
- * @type {String}
- * @sample {highcharts} highcharts/plotoptions/series-id/ Get series by id
- * @since 1.2.0
- * @apioption series.id
- */
+ /**
+ * By default, series are exposed to screen readers as regions. By
+ * enabling this option, the series element itself will be exposed in
+ * the same way as the data points. This is useful if the series is not
+ * used as a grouping entity in the chart, but you still want to attach
+ * a description to the series.
+ *
+ * Requires the Accessibility module.
+ *
+ * @sample highcharts/accessibility/art-grants/
+ * Accessible data visualization
+ *
+ * @type {boolean}
+ * @since 5.0.12
+ * @apioption plotOptions.series.exposeElementToA11y
+ */
- /**
- * The index of the series in the chart, affecting the internal index
- * in the `chart.series` array, the visible Z index as well as the order
- * in the legend.
- *
- * @type {Number}
- * @default undefined
- * @since 2.3.0
- * @apioption series.index
- */
+ /**
+ * Whether to use the Y extremes of the total chart width or only the
+ * zoomed area when zooming in on parts of the X axis. By default, the
+ * Y axis adjusts to the min and max of the visible data. Cartesian
+ * series only.
+ *
+ * @type {boolean}
+ * @default false
+ * @since 4.1.6
+ * @product highcharts highstock gantt
+ * @apioption plotOptions.series.getExtremesFromAll
+ */
- /**
- * An array specifying which option maps to which key in the data point
- * array. This makes it convenient to work with unstructured data arrays
- * from different sources.
- *
- * @type {Array}
- * @see [series.data](#series.line.data)
- * @sample {highcharts|highstock} highcharts/series/data-keys/
- * An extended data array with keys
- * @since 4.1.6
- * @product highcharts highstock
- * @apioption plotOptions.series.keys
- */
+ /**
+ * An array specifying which option maps to which key in the data point
+ * array. This makes it convenient to work with unstructured data arrays
+ * from different sources.
+ *
+ * @see [series.data](#series.line.data)
+ *
+ * @sample {highcharts|highstock} highcharts/series/data-keys/
+ * An extended data array with keys
+ * @sample {highcharts|highstock} highcharts/series/data-nested-keys/
+ * Nested keys used to access object properties
+ *
+ * @type {Array}
+ * @since 4.1.6
+ * @apioption plotOptions.series.keys
+ */
- /**
- * The sequential index of the series in the legend.
- *
- * @sample {highcharts|highstock} highcharts/series/legendindex/
- * Legend in opposite order
- * @type {Number}
- * @see [legend.reversed](#legend.reversed), [yAxis.reversedStacks](#yAxis.
- * reversedStacks)
- * @apioption series.legendIndex
- */
+ /**
+ * The line cap used for line ends and line joins on the graph.
+ *
+ * @type {string}
+ * @default round
+ * @product highcharts highstock
+ * @validvalue ["round", "square"]
+ * @apioption plotOptions.series.linecap
+ */
- /**
- * The line cap used for line ends and line joins on the graph.
- *
- * @validvalue ["round", "square"]
- * @type {String}
- * @default round
- * @product highcharts highstock
- * @apioption plotOptions.series.linecap
- */
+ /**
+ * The [id](#series.id) of another series to link to. Additionally,
+ * the value can be ":previous" to link to the previous series. When
+ * two series are linked, only the first one appears in the legend.
+ * Toggling the visibility of this also toggles the linked series.
+ *
+ * @sample {highcharts|highstock} highcharts/demo/arearange-line/
+ * Linked series
+ *
+ * @type {string}
+ * @since 3.0
+ * @product highcharts highstock gantt
+ * @apioption plotOptions.series.linkedTo
+ */
- /**
- * The [id](#series.id) of another series to link to. Additionally,
- * the value can be ":previous" to link to the previous series. When
- * two series are linked, only the first one appears in the legend.
- * Toggling the visibility of this also toggles the linked series.
- *
- * @type {String}
- * @sample {highcharts} highcharts/demo/arearange-line/ Linked series
- * @sample {highstock} highcharts/demo/arearange-line/ Linked series
- * @since 3.0
- * @product highcharts highstock
- * @apioption plotOptions.series.linkedTo
- */
+ /**
+ * Options for the corresponding navigator series if `showInNavigator`
+ * is `true` for this series. Available options are the same as any
+ * series, documented at [plotOptions](#plotOptions.series) and
+ * [series](#series).
+ *
+ * These options are merged with options in [navigator.series](
+ * #navigator.series), and will take precedence if the same option is
+ * defined both places.
+ *
+ * @see [navigator.series](#navigator.series)
+ *
+ * @type {Highcharts.PlotSeriesOptions}
+ * @since 5.0.0
+ * @product highstock
+ * @apioption plotOptions.series.navigatorOptions
+ */
- /**
- * The name of the series as shown in the legend, tooltip etc.
- *
- * @type {String}
- * @sample {highcharts} highcharts/series/name/ Series name
- * @sample {highmaps} maps/demo/category-map/ Series name
- * @apioption series.name
- */
+ /**
+ * The color for the parts of the graph or points that are below the
+ * [threshold](#plotOptions.series.threshold).
+ *
+ * @see In styled mode, a negative color is applied by setting this option
+ * to `true` combined with the `.highcharts-negative` class name.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-negative-color/
+ * Spline, area and column
+ * @sample {highcharts} highcharts/plotoptions/arearange-negativecolor/
+ * Arearange
+ * @sample {highcharts} highcharts/css/series-negative-color/
+ * Styled mode
+ * @sample {highstock} highcharts/plotoptions/series-negative-color/
+ * Spline, area and column
+ * @sample {highstock} highcharts/plotoptions/arearange-negativecolor/
+ * Arearange
+ * @sample {highmaps} highcharts/plotoptions/series-negative-color/
+ * Spline, area and column
+ * @sample {highmaps} highcharts/plotoptions/arearange-negativecolor/
+ * Arearange
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @since 3.0
+ * @apioption plotOptions.series.negativeColor
+ */
- /**
- * The color for the parts of the graph or points that are below the
- * [threshold](#plotOptions.series.threshold).
- *
- * @type {Color}
- * @see In styled mode, a negative color is applied by setting this
- * option to `true` combined with the `.highcharts-negative` class name.
- *
- * @sample {highcharts} highcharts/plotoptions/series-negative-color/
- * Spline, area and column
- * @sample {highcharts} highcharts/plotoptions/arearange-negativecolor/
- * Arearange
- * @sample {highcharts} highcharts/css/series-negative-color/
- * Styled mode
- * @sample {highstock} highcharts/plotoptions/series-negative-color/
- * Spline, area and column
- * @sample {highstock} highcharts/plotoptions/arearange-negativecolor/
- * Arearange
- * @sample {highmaps} highcharts/plotoptions/series-negative-color/
- * Spline, area and column
- * @sample {highmaps} highcharts/plotoptions/arearange-negativecolor/
- * Arearange
- * @default null
- * @since 3.0
- * @apioption plotOptions.series.negativeColor
- */
-
- /**
- * Same as [accessibility.pointDescriptionFormatter](#accessibility.
- * pointDescriptionFormatter), but for an individual series. Overrides
- * the chart wide configuration.
- *
- * @type {Function}
- * @since 5.0.12
- * @apioption plotOptions.series.pointDescriptionFormatter
- */
-
- /**
- * If no x values are given for the points in a series, `pointInterval`
- * defines the interval of the x values. For example, if a series contains
- * one value every decade starting from year 0, set `pointInterval` to
- * `10`. In true `datetime` axes, the `pointInterval` is set in
- * milliseconds.
- *
- * It can be also be combined with `pointIntervalUnit` to draw irregular
- * time intervals.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/series-pointstart-datetime/
- * Datetime X axis
- * @sample {highstock} stock/plotoptions/pointinterval-pointstart/
- * Using pointStart and pointInterval
- * @default 1
- * @product highcharts highstock
- * @apioption plotOptions.series.pointInterval
- */
-
- /**
- * On datetime series, this allows for setting the
- * [pointInterval](#plotOptions.series.pointInterval) to irregular time
- * units, `day`, `month` and `year`. A day is usually the same as 24 hours,
- * but `pointIntervalUnit` also takes the DST crossover into consideration
- * when dealing with local time. Combine this option with `pointInterval`
- * to draw weeks, quarters, 6 months, 10 years etc.
- *
- * @validvalue [null, "day", "month", "year"]
- * @type {String}
- * @sample {highcharts} highcharts/plotoptions/series-pointintervalunit/
- * One point a month
- * @sample {highstock} highcharts/plotoptions/series-pointintervalunit/
- * One point a month
- * @since 4.1.0
- * @product highcharts highstock
- * @apioption plotOptions.series.pointIntervalUnit
- */
-
- /**
- * Possible values: `null`, `"on"`, `"between"`.
- *
- * In a column chart, when pointPlacement is `"on"`, the point will
- * not create any padding of the X axis. In a polar column chart this
- * means that the first column points directly north. If the pointPlacement
- * is `"between"`, the columns will be laid out between ticks. This
- * is useful for example for visualising an amount between two points
- * in time or in a certain sector of a polar chart.
- *
- * Since Highcharts 3.0.2, the point placement can also be numeric,
- * where 0 is on the axis value, -0.5 is between this value and the
- * previous, and 0.5 is between this value and the next. Unlike the
- * textual options, numeric point placement options won't affect axis
- * padding.
- *
- * Note that pointPlacement needs a [pointRange](#plotOptions.series.
- * pointRange) to work. For column series this is computed, but for
- * line-type series it needs to be set.
- *
- * Defaults to `null` in cartesian charts, `"between"` in polar charts.
- *
- * @validvalue [null, "on", "between"]
- * @type {String|Number}
- * @see [xAxis.tickmarkPlacement](#xAxis.tickmarkPlacement)
- * @sample {highcharts|highstock}
- * highcharts/plotoptions/series-pointplacement-between/
- * Between in a column chart
- * @sample {highcharts|highstock}
- * highcharts/plotoptions/series-pointplacement-numeric/
- * Numeric placement for custom layout
- * @default null
- * @since 2.3.0
- * @product highcharts highstock
- * @apioption plotOptions.series.pointPlacement
- */
-
- /**
- * If no x values are given for the points in a series, pointStart defines
- * on what value to start. For example, if a series contains one yearly
- * value starting from 1945, set pointStart to 1945.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/series-pointstart-linear/
- * Linear
- * @sample {highcharts} highcharts/plotoptions/series-pointstart-datetime/
- * Datetime
- * @sample {highstock} stock/plotoptions/pointinterval-pointstart/
- * Using pointStart and pointInterval
- * @default 0
- * @product highcharts highstock
- * @apioption plotOptions.series.pointStart
- */
+ /**
+ * Same as
+ * [accessibility.pointDescriptionFormatter](#accessibility.pointDescriptionFormatter),
+ * but for an individual series. Overrides the chart wide configuration.
+ *
+ * @type {Function}
+ * @since 5.0.12
+ * @apioption plotOptions.series.pointDescriptionFormatter
+ */
- /**
- * Whether to select the series initially. If `showCheckbox` is true,
- * the checkbox next to the series name in the legend will be checked for a
- * selected series.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/series-selected/
- * One out of two series selected
- * @default false
- * @since 1.2.0
- * @apioption plotOptions.series.selected
- */
+ /**
+ * If no x values are given for the points in a series, `pointInterval`
+ * defines the interval of the x values. For example, if a series
+ * contains one value every decade starting from year 0, set
+ * `pointInterval` to `10`. In true `datetime` axes, the `pointInterval`
+ * is set in milliseconds.
+ *
+ * It can be also be combined with `pointIntervalUnit` to draw irregular
+ * time intervals.
+ *
+ * Please note that this options applies to the _series data_, not the
+ * interval of the axis ticks, which is independent.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-pointstart-datetime/
+ * Datetime X axis
+ * @sample {highstock} stock/plotoptions/pointinterval-pointstart/
+ * Using pointStart and pointInterval
+ *
+ * @type {number}
+ * @default 1
+ * @product highcharts highstock gantt
+ * @apioption plotOptions.series.pointInterval
+ */
- /**
- * Whether to apply a drop shadow to the graph line. Since 2.3 the shadow
- * can be an object configuration containing `color`, `offsetX`, `offsetY`,
- * `opacity` and `width`.
- *
- * @type {Boolean|Object}
- * @sample {highcharts} highcharts/plotoptions/series-shadow/ Shadow enabled
- * @default false
- * @apioption plotOptions.series.shadow
- */
+ /**
+ * On datetime series, this allows for setting the
+ * [pointInterval](#plotOptions.series.pointInterval) to irregular time
+ * units, `day`, `month` and `year`. A day is usually the same as 24
+ * hours, but `pointIntervalUnit` also takes the DST crossover into
+ * consideration when dealing with local time. Combine this option with
+ * `pointInterval` to draw weeks, quarters, 6 months, 10 years etc.
+ *
+ * Please note that this options applies to the _series data_, not the
+ * interval of the axis ticks, which is independent.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-pointintervalunit/
+ * One point a month
+ * @sample {highstock} highcharts/plotoptions/series-pointintervalunit/
+ * One point a month
+ *
+ * @type {string}
+ * @since 4.1.0
+ * @product highcharts highstock gantt
+ * @validvalue ["day", "month", "year"]
+ * @apioption plotOptions.series.pointIntervalUnit
+ */
- /**
- * Whether to display this particular series or series type in the legend.
- * The default value is `true` for standalone series, `false` for linked
- * series.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/series-showinlegend/
- * One series in the legend, one hidden
- * @default true
- * @apioption plotOptions.series.showInLegend
- */
+ /**
+ * Possible values: `"on"`, `"between"`, `number`.
+ *
+ * In a column chart, when pointPlacement is `"on"`, the point will not
+ * create any padding of the X axis. In a polar column chart this means
+ * that the first column points directly north. If the pointPlacement is
+ * `"between"`, the columns will be laid out between ticks. This is
+ * useful for example for visualising an amount between two points in
+ * time or in a certain sector of a polar chart.
+ *
+ * Since Highcharts 3.0.2, the point placement can also be numeric,
+ * where 0 is on the axis value, -0.5 is between this value and the
+ * previous, and 0.5 is between this value and the next. Unlike the
+ * textual options, numeric point placement options won't affect axis
+ * padding.
+ *
+ * Note that pointPlacement needs a [pointRange](
+ * #plotOptions.series.pointRange) to work. For column series this is
+ * computed, but for line-type series it needs to be set.
+ *
+ * For the `xrange` series type and gantt charts, if the Y axis is a
+ * category axis, the `pointPlacement` applies to the Y axis rather than
+ * the (typically datetime) X axis.
+ *
+ * Defaults to `undefined` in cartesian charts, `"between"` in polar
+ * charts.
+ *
+ * @see [xAxis.tickmarkPlacement](#xAxis.tickmarkPlacement)
+ *
+ * @sample {highcharts|highstock} highcharts/plotoptions/series-pointplacement-between/
+ * Between in a column chart
+ * @sample {highcharts|highstock} highcharts/plotoptions/series-pointplacement-numeric/
+ * Numeric placement for custom layout
+ * @sample {highcharts|highstock} maps/plotoptions/heatmap-pointplacement/
+ * Placement in heatmap
+ *
+ * @type {string|number}
+ * @since 2.3.0
+ * @product highcharts highstock gantt
+ * @apioption plotOptions.series.pointPlacement
+ */
- /**
- * If set to `True`, the accessibility module will skip past the points
- * in this series for keyboard navigation.
- *
- * @type {Boolean}
- * @since 5.0.12
- * @apioption plotOptions.series.skipKeyboardNavigation
- */
+ /**
+ * If no x values are given for the points in a series, pointStart
+ * defines on what value to start. For example, if a series contains one
+ * yearly value starting from 1945, set pointStart to 1945.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-pointstart-linear/
+ * Linear
+ * @sample {highcharts} highcharts/plotoptions/series-pointstart-datetime/
+ * Datetime
+ * @sample {highstock} stock/plotoptions/pointinterval-pointstart/
+ * Using pointStart and pointInterval
+ *
+ * @type {number}
+ * @default 0
+ * @product highcharts highstock gantt
+ * @apioption plotOptions.series.pointStart
+ */
- /**
- * This option allows grouping series in a stacked chart. The stack
- * option can be a string or a number or anything else, as long as the
- * grouped series' stack options match each other.
- *
- * @type {String}
- * @sample {highcharts} highcharts/series/stack/ Stacked and grouped columns
- * @default null
- * @since 2.1
- * @product highcharts highstock
- * @apioption series.stack
- */
+ /**
+ * Whether to select the series initially. If `showCheckbox` is true,
+ * the checkbox next to the series name in the legend will be checked
+ * for a selected series.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-selected/
+ * One out of two series selected
+ *
+ * @type {boolean}
+ * @default false
+ * @since 1.2.0
+ * @apioption plotOptions.series.selected
+ */
- /**
- * Whether to stack the values of each series on top of each other.
- * Possible values are `null` to disable, `"normal"` to stack by value or
- * `"percent"`. When stacking is enabled, data must be sorted in ascending
- * X order. A special stacking option is with the streamgraph series type,
- * where the stacking option is set to `"stream"`.
- *
- * @validvalue [null, "normal", "percent"]
- * @type {String}
- * @see [yAxis.reversedStacks](#yAxis.reversedStacks)
- * @sample {highcharts} highcharts/plotoptions/series-stacking-line/
- * Line
- * @sample {highcharts} highcharts/plotoptions/series-stacking-column/
- * Column
- * @sample {highcharts} highcharts/plotoptions/series-stacking-bar/
- * Bar
- * @sample {highcharts} highcharts/plotoptions/series-stacking-area/
- * Area
- * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-line/
- * Line
- * @sample {highcharts}
- * highcharts/plotoptions/series-stacking-percent-column/
- * Column
- * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-bar/
- * Bar
- * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-area/
- * Area
- * @sample {highstock} stock/plotoptions/stacking/
- * Area
- * @default null
- * @product highcharts highstock
- * @apioption plotOptions.series.stacking
- */
-
- /**
- * Whether to apply steps to the line. Possible values are `left`, `center`
- * and `right`.
- *
- * @validvalue [null, "left", "center", "right"]
- * @type {String}
- * @sample {highcharts} highcharts/plotoptions/line-step/
- * Different step line options
- * @sample {highcharts} highcharts/plotoptions/area-step/
- * Stepped, stacked area
- * @sample {highstock} stock/plotoptions/line-step/
- * Step line
- * @default {highcharts} null
- * @default {highstock} false
- * @since 1.2.5
- * @product highcharts highstock
- * @apioption plotOptions.series.step
- */
+ /**
+ * Whether to apply a drop shadow to the graph line. Since 2.3 the
+ * shadow can be an object configuration containing `color`, `offsetX`,
+ * `offsetY`, `opacity` and `width`.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-shadow/
+ * Shadow enabled
+ *
+ * @type {boolean|Highcharts.ShadowOptionsObject}
+ * @default false
+ * @apioption plotOptions.series.shadow
+ */
- /**
- * The threshold, also called zero level or base level. For line type
- * series this is only used in conjunction with
- * [negativeColor](#plotOptions.series.negativeColor).
- *
- * @type {Number}
- * @see [softThreshold](#plotOptions.series.softThreshold).
- * @default 0
- * @since 3.0
- * @product highcharts highstock
- * @apioption plotOptions.series.threshold
- */
+ /**
+ * Whether to display this particular series or series type in the
+ * legend. The default value is `true` for standalone series, `false`
+ * for linked series.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-showinlegend/
+ * One series in the legend, one hidden
+ *
+ * @type {boolean}
+ * @default true
+ * @apioption plotOptions.series.showInLegend
+ */
- /**
- * The type of series, for example `line` or `column`.
- *
- * @validvalue [null, "line", "spline", "column", "area", "areaspline",
- * "pie", "arearange", "areasplinerange", "boxplot", "bubble",
- * "columnrange", "errorbar", "funnel", "gauge", "scatter",
- * "waterfall"]
- * @type {String}
- * @sample {highcharts} highcharts/series/type/
- * Line and column in the same chart
- * @sample {highmaps} maps/demo/mapline-mappoint/
- * Multiple types in the same map
- * @apioption series.type
- */
+ /**
+ * Whether or not to show the series in the navigator. Takes precedence
+ * over [navigator.baseSeries](#navigator.baseSeries) if defined.
+ *
+ * @type {boolean}
+ * @since 5.0.0
+ * @product highstock
+ * @apioption plotOptions.series.showInNavigator
+ */
- /**
- * Set the initial visibility of the series.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/series-visible/
- * Two series, one hidden and one visible
- * @sample {highstock} stock/plotoptions/series-visibility/
- * Hidden series
- * @default true
- * @apioption plotOptions.series.visible
- */
+ /**
+ * If set to `true`, the accessibility module will skip past the points
+ * in this series for keyboard navigation.
+ *
+ * @type {boolean}
+ * @since 5.0.12
+ * @apioption plotOptions.series.skipKeyboardNavigation
+ */
- /**
- * When using dual or multiple x axes, this number defines which xAxis
- * the particular series is connected to. It refers to either the [axis
- * id](#xAxis.id) or the index of the axis in the xAxis array, with
- * 0 being the first.
- *
- * @type {Number|String}
- * @default 0
- * @product highcharts highstock
- * @apioption series.xAxis
- */
+ /**
+ * Whether to stack the values of each series on top of each other.
+ * Possible values are `undefined` to disable, `"normal"` to stack by
+ * value or `"percent"`. When stacking is enabled, data must be sorted
+ * in ascending X order. A special stacking option is with the
+ * streamgraph series type, where the stacking option is set to
+ * `"stream"`. The second one is `"overlap"`, which only applies to
+ * waterfall series.
+ *
+ * @see [yAxis.reversedStacks](#yAxis.reversedStacks)
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-stacking-line/
+ * Line
+ * @sample {highcharts} highcharts/plotoptions/series-stacking-column/
+ * Column
+ * @sample {highcharts} highcharts/plotoptions/series-stacking-bar/
+ * Bar
+ * @sample {highcharts} highcharts/plotoptions/series-stacking-area/
+ * Area
+ * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-line/
+ * Line
+ * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-column/
+ * Column
+ * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-bar/
+ * Bar
+ * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-area/
+ * Area
+ * @sample {highcharts} highcharts/plotoptions/series-waterfall-with-normal-stacking
+ * Waterfall with normal stacking
+ * @sample {highcharts} highcharts/plotoptions/series-waterfall-with-overlap-stacking
+ * Waterfall with overlap stacking
+ * @sample {highstock} stock/plotoptions/stacking/
+ * Area
+ *
+ * @type {string}
+ * @product highcharts highstock
+ * @validvalue ["normal", "percent"]
+ * @apioption plotOptions.series.stacking
+ */
- /**
- * When using dual or multiple y axes, this number defines which yAxis
- * the particular series is connected to. It refers to either the [axis
- * id](#yAxis.id) or the index of the axis in the yAxis array, with
- * 0 being the first.
- *
- * @type {Number|String}
- * @sample {highcharts} highcharts/series/yaxis/
- * Apply the column series to the secondary Y axis
- * @default 0
- * @product highcharts highstock
- * @apioption series.yAxis
- */
+ /**
+ * Whether to apply steps to the line. Possible values are `left`,
+ * `center` and `right`.
+ *
+ * @sample {highcharts} highcharts/plotoptions/line-step/
+ * Different step line options
+ * @sample {highcharts} highcharts/plotoptions/area-step/
+ * Stepped, stacked area
+ * @sample {highstock} stock/plotoptions/line-step/
+ * Step line
+ *
+ * @type {string}
+ * @since 1.2.5
+ * @product highcharts highstock
+ * @validvalue ["left", "center", "right"]
+ * @apioption plotOptions.series.step
+ */
- /**
- * Defines the Axis on which the zones are applied.
- *
- * @type {String}
- * @see [zones](#plotOptions.series.zones)
- * @sample {highcharts} highcharts/series/color-zones-zoneaxis-x/
- * Zones on the X-Axis
- * @sample {highstock} highcharts/series/color-zones-zoneaxis-x/
- * Zones on the X-Axis
- * @default y
- * @since 4.1.0
- * @product highcharts highstock
- * @apioption plotOptions.series.zoneAxis
- */
+ /**
+ * The threshold, also called zero level or base level. For line type
+ * series this is only used in conjunction with
+ * [negativeColor](#plotOptions.series.negativeColor).
+ *
+ * @see [softThreshold](#plotOptions.series.softThreshold).
+ *
+ * @type {number}
+ * @default 0
+ * @since 3.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.threshold
+ */
- /**
- * Define the visual z index of the series.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/series-zindex-default/
- * With no z index, the series defined last are on top
- * @sample {highcharts} highcharts/plotoptions/series-zindex/
- * With a z index, the series with the highest z index is on top
- * @sample {highstock} highcharts/plotoptions/series-zindex-default/
- * With no z index, the series defined last are on top
- * @sample {highstock} highcharts/plotoptions/series-zindex/
- * With a z index, the series with the highest z index is on top
- * @product highcharts highstock
- * @apioption series.zIndex
- */
+ /**
+ * Set the initial visibility of the series.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-visible/
+ * Two series, one hidden and one visible
+ * @sample {highstock} stock/plotoptions/series-visibility/
+ * Hidden series
+ *
+ * @type {boolean}
+ * @default true
+ * @apioption plotOptions.series.visible
+ */
- /**
- * General event handlers for the series items. These event hooks can also
- * be attached to the series at run time using the `Highcharts.addEvent`
- * function.
- */
- events: {
+ /**
+ * Defines the Axis on which the zones are applied.
+ *
+ * @see [zones](#plotOptions.series.zones)
+ *
+ * @sample {highcharts} highcharts/series/color-zones-zoneaxis-x/
+ * Zones on the X-Axis
+ * @sample {highstock} highcharts/series/color-zones-zoneaxis-x/
+ * Zones on the X-Axis
+ *
+ * @type {string}
+ * @default y
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.zoneAxis
+ */
/**
- * Fires after the series has finished its initial animation, or in
- * case animation is disabled, immediately as the series is displayed.
- *
- * @type {Function}
- * @context Series
- * @sample {highcharts}
- * highcharts/plotoptions/series-events-afteranimate/
+ * General event handlers for the series items. These event hooks can
+ * also be attached to the series at run time using the
+ * `Highcharts.addEvent` function.
+ */
+ events: {},
+
+ /**
+ * Fires after the series has finished its initial animation, or in case
+ * animation is disabled, immediately as the series is displayed.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-events-afteranimate/
* Show label after animate
- * @sample {highstock}
- * highcharts/plotoptions/series-events-afteranimate/
+ * @sample {highstock} highcharts/plotoptions/series-events-afteranimate/
* Show label after animate
- * @since 4.0
- * @product highcharts highstock
+ *
+ * @type {Highcharts.SeriesAfterAnimateCallbackFunction}
+ * @since 4.0
+ * @product highcharts highstock gantt
+ * @context Highcharts.Series
* @apioption plotOptions.series.events.afterAnimate
*/
@@ -23032,13 +31141,13 @@
* of the checkbox is found by `event.checked`. The checked item is
* found by `event.item`. Return `false` to prevent the default action
* which is to toggle the select state of the series.
- *
- * @type {Function}
- * @context Series
- * @sample {highcharts}
- * highcharts/plotoptions/series-events-checkboxclick/
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-events-checkboxclick/
* Alert checkbox status
- * @since 1.2.0
+ *
+ * @type {Highcharts.SeriesCheckboxClickCallbackFunction}
+ * @since 1.2.0
+ * @context Highcharts.Series
* @apioption plotOptions.series.events.checkboxClick
*/
@@ -23046,27 +31155,29 @@
* Fires when the series is clicked. One parameter, `event`, is passed
* to the function, containing common event information. Additionally,
* `event.point` holds a pointer to the nearest point on the graph.
- *
- * @type {Function}
- * @context Series
+ *
* @sample {highcharts} highcharts/plotoptions/series-events-click/
* Alert click info
* @sample {highstock} stock/plotoptions/series-events-click/
* Alert click info
* @sample {highmaps} maps/plotoptions/series-events-click/
* Display click info in subtitle
+ *
+ * @type {Highcharts.SeriesClickCallbackFunction}
+ * @context Highcharts.Series
* @apioption plotOptions.series.events.click
*/
/**
* Fires when the series is hidden after chart generation time, either
* by clicking the legend item or by calling `.hide()`.
- *
- * @type {Function}
- * @context Series
+ *
* @sample {highcharts} highcharts/plotoptions/series-events-hide/
* Alert when the series is hidden by clicking the legend item
- * @since 1.2.0
+ *
+ * @type {Highcharts.SeriesHideCallbackFunction}
+ * @since 1.2.0
+ * @context Highcharts.Series
* @apioption plotOptions.series.events.hide
*/
@@ -23075,12 +31186,12 @@
* parameter, `event`, is passed to the function. The default action
* is to toggle the visibility of the series. This can be prevented
* by returning `false` or calling `event.preventDefault()`.
- *
- * @type {Function}
- * @context Series
- * @sample {highcharts}
- * highcharts/plotoptions/series-events-legenditemclick/
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-events-legenditemclick/
* Confirm hiding and showing
+ *
+ * @type {Highcharts.SeriesLegendItemClickCallbackFunction}
+ * @context Highcharts.Series
* @apioption plotOptions.series.events.legendItemClick
*/
@@ -23090,420 +31201,408 @@
* [stickyTracking](#plotOptions.series) option is true, `mouseOut`
* doesn't happen before the mouse enters another graph or leaves the
* plot area.
- *
- * @type {Function}
- * @context Series
- * @sample {highcharts}
- * highcharts/plotoptions/series-events-mouseover-sticky/
- * With sticky tracking by default
- * @sample {highcharts}
- * highcharts/plotoptions/series-events-mouseover-no-sticky/
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-events-mouseover-sticky/
+ * With sticky tracking by default
+ * @sample {highcharts} highcharts/plotoptions/series-events-mouseover-no-sticky/
* Without sticky tracking
+ *
+ * @type {Highcharts.SeriesMouseOutCallbackFunction}
+ * @context Highcharts.Series
* @apioption plotOptions.series.events.mouseOut
*/
/**
* Fires when the mouse enters the graph. One parameter, `event`, is
* passed to the function, containing common event information.
- *
- * @type {Function}
- * @context Series
- * @sample {highcharts}
- * highcharts/plotoptions/series-events-mouseover-sticky/
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-events-mouseover-sticky/
* With sticky tracking by default
- * @sample {highcharts}
- * highcharts/plotoptions/series-events-mouseover-no-sticky/
+ * @sample {highcharts} highcharts/plotoptions/series-events-mouseover-no-sticky/
* Without sticky tracking
+ *
+ * @type {Highcharts.SeriesMouseOverCallbackFunction}
+ * @context Highcharts.Series
* @apioption plotOptions.series.events.mouseOver
*/
/**
* Fires when the series is shown after chart generation time, either
* by clicking the legend item or by calling `.show()`.
- *
- * @type {Function}
- * @context Series
+ *
* @sample {highcharts} highcharts/plotoptions/series-events-show/
* Alert when the series is shown by clicking the legend item.
- * @since 1.2.0
+ *
+ * @type {Highcharts.SeriesShowCallbackFunction}
+ * @since 1.2.0
+ * @context Highcharts.Series
* @apioption plotOptions.series.events.show
*/
- },
-
-
-
- /**
- * Options for the point markers of line-like series. Properties like
- * `fillColor`, `lineColor` and `lineWidth` define the visual appearance
- * of the markers. Other series types, like column series, don't have
- * markers, but have visual options on the series level instead.
- *
- * In styled mode, the markers can be styled with the `.highcharts-point`,
- * `.highcharts-point-hover` and `.highcharts-point-select`
- * class names.
- *
- * @product highcharts highstock
- */
- marker: {
-
-
-
/**
- * The width of the point marker's outline.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/
- * 2px blue marker
- * @default 0
- * @product highcharts highstock
+ * Options for the point markers of line-like series. Properties like
+ * `fillColor`, `lineColor` and `lineWidth` define the visual appearance
+ * of the markers. Other series types, like column series, don't have
+ * markers, but have visual options on the series level instead.
+ *
+ * In styled mode, the markers can be styled with the
+ * `.highcharts-point`, `.highcharts-point-hover` and
+ * `.highcharts-point-select` class names.
*/
- lineWidth: 0,
+ marker: {
+ /**
+ * The width of the point marker's outline.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/
+ * 2px blue marker
+ */
+ lineWidth: 0,
- /**
- * The color of the point marker's outline. When `null`, the series'
- * or point's color is used.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/
- * Inherit from series color (null)
- * @product highcharts highstock
- */
- lineColor: '#ffffff',
+ /**
+ * The color of the point marker's outline. When `undefined`, the
+ * series' or point's color is used.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/
+ * Inherit from series color (undefined)
+ *
+ * @type {Highcharts.ColorString}
+ */
+ lineColor: '#ffffff',
- /**
- * The fill color of the point marker. When `null`, the series' or
- * point's color is used.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/
- * White fill
- * @default null
- * @product highcharts highstock
- * @apioption plotOptions.series.marker.fillColor
- */
+ /**
+ * The fill color of the point marker. When `undefined`, the series'
+ * or point's color is used.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/
+ * White fill
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @apioption plotOptions.series.marker.fillColor
+ */
+ /**
+ * Enable or disable the point marker. If `undefined`, the markers
+ * are hidden when the data is dense, and shown for more widespread
+ * data points.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-marker-enabled/
+ * Disabled markers
+ * @sample {highcharts} highcharts/plotoptions/series-marker-enabled-false/
+ * Disabled in normal state but enabled on hover
+ * @sample {highstock} stock/plotoptions/series-marker/
+ * Enabled markers
+ *
+ * @type {boolean}
+ * @default {highcharts} undefined
+ * @default {highstock} false
+ * @apioption plotOptions.series.marker.enabled
+ */
+ /**
+ * Image markers only. Set the image width explicitly. When using
+ * this option, a `width` must also be set.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-marker-width-height/
+ * Fixed width and height
+ * @sample {highstock} highcharts/plotoptions/series-marker-width-height/
+ * Fixed width and height
+ *
+ * @type {number}
+ * @since 4.0.4
+ * @apioption plotOptions.series.marker.height
+ */
- /**
- * Enable or disable the point marker. If `null`, the markers are hidden
- * when the data is dense, and shown for more widespread data points.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/series-marker-enabled/
- * Disabled markers
- * @sample {highcharts}
- * highcharts/plotoptions/series-marker-enabled-false/
- * Disabled in normal state but enabled on hover
- * @sample {highstock} stock/plotoptions/series-marker/
- * Enabled markers
- * @default {highcharts} null
- * @default {highstock} false
- * @product highcharts highstock
- * @apioption plotOptions.series.marker.enabled
- */
-
- /**
- * Image markers only. Set the image width explicitly. When using this
- * option, a `width` must also be set.
- *
- * @type {Number}
- * @sample {highcharts}
- * highcharts/plotoptions/series-marker-width-height/
- * Fixed width and height
- * @sample {highstock}
- * highcharts/plotoptions/series-marker-width-height/
- * Fixed width and height
- * @default null
- * @since 4.0.4
- * @product highcharts highstock
- * @apioption plotOptions.series.marker.height
- */
+ /**
+ * A predefined shape or symbol for the marker. When undefined, the
+ * symbol is pulled from options.symbols. Other possible values are
+ * "circle", "square", "diamond", "triangle" and "triangle-down".
+ *
+ * Additionally, the URL to a graphic can be given on this form:
+ * "url(graphic.png)". Note that for the image to be applied to
+ * exported charts, its URL needs to be accessible by the export
+ * server.
+ *
+ * Custom callbacks for symbol path generation can also be added to
+ * `Highcharts.SVGRenderer.prototype.symbols`. The callback is then
+ * used by its method name, as shown in the demo.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-marker-symbol/
+ * Predefined, graphic and custom markers
+ * @sample {highstock} highcharts/plotoptions/series-marker-symbol/
+ * Predefined, graphic and custom markers
+ *
+ * @type {string}
+ * @apioption plotOptions.series.marker.symbol
+ */
- /**
- * A predefined shape or symbol for the marker. When null, the symbol
- * is pulled from options.symbols. Other possible values are "circle",
- * "square", "diamond", "triangle" and "triangle-down".
- *
- * Additionally, the URL to a graphic can be given on this form:
- * "url(graphic.png)". Note that for the image to be applied to exported
- * charts, its URL needs to be accessible by the export server.
- *
- * Custom callbacks for symbol path generation can also be added to
- * `Highcharts.SVGRenderer.prototype.symbols`. The callback is then
- * used by its method name, as shown in the demo.
- *
- * @validvalue [null, "circle", "square", "diamond", "triangle",
- * "triangle-down"]
- * @type {String}
- * @sample {highcharts} highcharts/plotoptions/series-marker-symbol/
- * Predefined, graphic and custom markers
- * @sample {highstock} highcharts/plotoptions/series-marker-symbol/
- * Predefined, graphic and custom markers
- * @default null
- * @product highcharts highstock
- * @apioption plotOptions.series.marker.symbol
- */
+ /**
+ * The threshold for how dense the point markers should be before
+ * they are hidden, given that `enabled` is not defined. The number
+ * indicates the horizontal distance between the two closest points
+ * in the series, as multiples of the `marker.radius`. In other
+ * words, the default value of 2 means points are hidden if
+ * overlapping horizontally.
+ *
+ * @sample highcharts/plotoptions/series-marker-enabledthreshold
+ * A higher threshold
+ *
+ * @since 6.0.5
+ */
+ enabledThreshold: 2,
- /**
- * The radius of the point marker.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/series-marker-radius/
- * Bigger markers
- * @default 4
- * @product highcharts highstock
- */
- radius: 4,
-
- /**
- * Image markers only. Set the image width explicitly. When using this
- * option, a `height` must also be set.
- *
- * @type {Number}
- * @sample {highcharts}
- * highcharts/plotoptions/series-marker-width-height/
- * Fixed width and height
- * @sample {highstock}
- * highcharts/plotoptions/series-marker-width-height/
- * Fixed width and height
- * @default null
- * @since 4.0.4
- * @product highcharts highstock
- * @apioption plotOptions.series.marker.width
- */
+ /**
+ * The radius of the point marker.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-marker-radius/
+ * Bigger markers
+ *
+ * @default {highstock} 2
+ */
+ radius: 4,
+ /**
+ * Image markers only. Set the image width explicitly. When using
+ * this option, a `height` must also be set.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-marker-width-height/
+ * Fixed width and height
+ * @sample {highstock} highcharts/plotoptions/series-marker-width-height/
+ * Fixed width and height
+ *
+ * @type {number}
+ * @since 4.0.4
+ * @apioption plotOptions.series.marker.width
+ */
- /**
- * States for a single point marker.
- * @product highcharts highstock
- */
- states: {
/**
- * The hover state for a single point marker.
- * @product highcharts highstock
+ * States for a single point marker.
*/
- hover: {
+ states: {
/**
- * Animation when hovering over the marker.
- * @type {Boolean|Object}
+ * The normal state of a single point marker. Currently only
+ * used for setting animation when returning to normal state
+ * from hover.
*/
- animation: {
- duration: 50
+ normal: {
+ /**
+ * Animation when returning to normal state after hovering.
+ *
+ * @type {boolean|Highcharts.AnimationOptionsObject}
+ */
+ animation: true
},
/**
- * Enable or disable the point marker.
- *
- * @type {Boolean}
- * @sample {highcharts}
- * highcharts/plotoptions/series-marker-states-hover-enabled/
- * Disabled hover state
- * @default true
- * @product highcharts highstock
- */
- enabled: true,
-
- /**
- * The fill color of the marker in hover state.
- *
- * @type {Color}
- * @default null
- * @product highcharts highstock
- * @apioption plotOptions.series.marker.states.hover.fillColor
- */
-
- /**
- * The color of the point marker's outline. When `null`, the
- * series' or point's color is used.
- *
- * @type {Color}
- * @sample {highcharts}
- * highcharts/plotoptions/series-marker-states-hover-linecolor/
- * White fill color, black line color
- * @default #ffffff
- * @product highcharts highstock
- * @apioption plotOptions.series.marker.states.hover.lineColor
- */
-
- /**
- * The width of the point marker's outline.
- *
- * @type {Number}
- * @sample {highcharts}
- * highcharts/plotoptions/series-marker-states-hover-linewidth/
- * 3px line width
- * @default 0
- * @product highcharts highstock
- * @apioption plotOptions.series.marker.states.hover.lineWidth
- */
-
- /**
- * The radius of the point marker. In hover state, it defaults to the
- * normal state's radius + 2 as per the [radiusPlus](#plotOptions.series.
- * marker.states.hover.radiusPlus) option.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-radius/ 10px radius
- * @product highcharts highstock
- * @apioption plotOptions.series.marker.states.hover.radius
- */
-
- /**
- * The number of pixels to increase the radius of the hovered point.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/ 5 pixels greater radius on hover
- * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/ 5 pixels greater radius on hover
- * @default 2
- * @since 4.0.3
- * @product highcharts highstock
+ * The hover state for a single point marker.
*/
- radiusPlus: 2,
-
-
+ hover: {
- /**
- * The additional line width for a hovered point.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/ 2 pixels wider on hover
- * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/ 2 pixels wider on hover
- * @default 1
- * @since 4.0.3
- * @product highcharts highstock
- */
- lineWidthPlus: 1
+ /**
+ * Animation when hovering over the marker.
+ *
+ * @type {boolean|Highcharts.AnimationOptionsObject}
+ */
+ animation: {
- },
+ duration: 50
+ },
+ /**
+ * Enable or disable the point marker.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-enabled/
+ * Disabled hover state
+ */
+ enabled: true,
+ /**
+ * The fill color of the marker in hover state. When
+ * `undefined`, the series' or point's fillColor for normal
+ * state is used.
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @apioption plotOptions.series.marker.states.hover.fillColor
+ */
- /**
- * The appearance of the point marker when selected. In order to
- * allow a point to be selected, set the `series.allowPointSelect`
- * option to true.
- *
- * @product highcharts highstock
- */
- select: {
+ /**
+ * The color of the point marker's outline. When
+ * `undefined`, the series' or point's lineColor for normal
+ * state is used.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-linecolor/
+ * White fill color, black line color
+ *
+ * @type {Highcharts.ColorString}
+ * @apioption plotOptions.series.marker.states.hover.lineColor
+ */
- /**
- * Enable or disable visible feedback for selection.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-enabled/
- * Disabled select state
- * @default true
- * @product highcharts highstock
- * @apioption plotOptions.series.marker.states.select.enabled
- */
+ /**
+ * The width of the point marker's outline. When
+ * `undefined`, the series' or point's lineWidth for normal
+ * state is used.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-linewidth/
+ * 3px line width
+ *
+ * @type {number}
+ * @apioption plotOptions.series.marker.states.hover.lineWidth
+ */
- /**
- * The fill color of the point marker.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-fillcolor/
- * Solid red discs for selected points
- * @default null
- * @product highcharts highstock
- */
- fillColor: '#cccccc',
+ /**
+ * The radius of the point marker. In hover state, it
+ * defaults to the normal state's radius + 2 as per the
+ * [radiusPlus](#plotOptions.series.marker.states.hover.radiusPlus)
+ * option.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-radius/
+ * 10px radius
+ *
+ * @type {number}
+ * @apioption plotOptions.series.marker.states.hover.radius
+ */
+ /**
+ * The number of pixels to increase the radius of the
+ * hovered point.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/
+ * 5 pixels greater radius on hover
+ * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/
+ * 5 pixels greater radius on hover
+ *
+ * @since 4.0.3
+ */
+ radiusPlus: 2,
+ /**
+ * The additional line width for a hovered point.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/
+ * 2 pixels wider on hover
+ * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/
+ * 2 pixels wider on hover
+ *
+ * @since 4.0.3
+ */
+ lineWidthPlus: 1
+ },
/**
- * The color of the point marker's outline. When `null`, the series'
- * or point's color is used.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-linecolor/
- * Red line color for selected points
- * @default #000000
- * @product highcharts highstock
+ * The appearance of the point marker when selected. In order to
+ * allow a point to be selected, set the
+ * `series.allowPointSelect` option to true.
*/
- lineColor: '#000000',
+ select: {
+ /**
+ * The radius of the point marker. In hover state, it
+ * defaults to the normal state's radius + 2.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-radius/
+ * 10px radius for selected points
+ *
+ * @type {number}
+ * @apioption plotOptions.series.marker.states.select.radius
+ */
+ /**
+ * Enable or disable visible feedback for selection.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-enabled/
+ * Disabled select state
+ *
+ * @type {boolean}
+ * @default true
+ * @apioption plotOptions.series.marker.states.select.enabled
+ */
- /**
- * The width of the point marker's outline.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-linewidth/
- * 3px line width for selected points
- * @default 0
- * @product highcharts highstock
- */
- lineWidth: 2
+ /**
+ * The fill color of the point marker.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-fillcolor/
+ * Solid red discs for selected points
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ */
+ fillColor: '#cccccc',
- /**
- * The radius of the point marker. In hover state, it defaults to the
- * normal state's radius + 2.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-radius/
- * 10px radius for selected points
- * @product highcharts highstock
- * @apioption plotOptions.series.marker.states.select.radius
- */
+ /**
+ * The color of the point marker's outline. When
+ * `undefined`, the series' or point's color is used.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-linecolor/
+ * Red line color for selected points
+ *
+ * @type {Highcharts.ColorString}
+ */
+ lineColor: '#000000',
+ /**
+ * The width of the point marker's outline.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-linewidth/
+ * 3px line width for selected points
+ */
+ lineWidth: 2
+ }
}
-
- }
- },
-
-
-
- /**
- * Properties for each single point.
- */
- point: {
-
+ },
/**
- * Events for each single point.
+ * Properties for each single point.
*/
- events: {
+ point: {
/**
* Fires when a point is clicked. One parameter, `event`, is passed
* to the function, containing common event information.
- *
- * If the `series.allowPointSelect` option is true, the default action
- * for the point's click event is to toggle the point's select state.
- * Returning `false` cancels this action.
- *
- * @type {Function}
- * @context Point
- * @sample {highcharts} highcharts/plotoptions/series-point-events-click/ Click marker to alert values
- * @sample {highcharts} highcharts/plotoptions/series-point-events-click-column/ Click column
- * @sample {highcharts} highcharts/plotoptions/series-point-events-click-url/ Go to URL
- * @sample {highmaps} maps/plotoptions/series-point-events-click/ Click marker to display values
- * @sample {highmaps} maps/plotoptions/series-point-events-click-url/ Go to URL
+ *
+ * If the `series.allowPointSelect` option is true, the default
+ * action for the point's click event is to toggle the point's
+ * select state. Returning `false` cancels this action.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-click/
+ * Click marker to alert values
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-click-column/
+ * Click column
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-click-url/
+ * Go to URL
+ * @sample {highmaps} maps/plotoptions/series-point-events-click/
+ * Click marker to display values
+ * @sample {highmaps} maps/plotoptions/series-point-events-click-url/
+ * Go to URL
+ *
+ * @type {Highcharts.SeriesPointClickCallbackFunction}
+ * @context Highcharts.Point
* @apioption plotOptions.series.point.events.click
*/
/**
- * Fires when the mouse leaves the area close to the point. One parameter,
- * `event`, is passed to the function, containing common event information.
- *
- * @type {Function}
- * @context Point
- * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ Show values in the chart's corner on mouse over
+ * Fires when the mouse leaves the area close to the point. One
+ * parameter, `event`, is passed to the function, containing common
+ * event information.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/
+ * Show values in the chart's corner on mouse over
+ *
+ * @type {Highcharts.SeriesPointMouseOutCallbackFunction}
+ * @context Highcharts.Point
* @apioption plotOptions.series.point.events.mouseOut
*/
/**
- * Fires when the mouse enters the area close to the point. One parameter,
- * `event`, is passed to the function, containing common event information.
- *
- * @type {Function}
- * @context Point
- * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ Show values in the chart's corner on mouse over
+ * Fires when the mouse enters the area close to the point. One
+ * parameter, `event`, is passed to the function, containing common
+ * event information.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/
+ * Show values in the chart's corner on mouse over
+ *
+ * @type {Highcharts.SeriesPointMouseOverCallbackFunction}
+ * @context Highcharts.Point
* @apioption plotOptions.series.point.events.mouseOver
*/
@@ -23511,3364 +31610,3982 @@
* Fires when the point is removed using the `.remove()` method. One
* parameter, `event`, is passed to the function. Returning `false`
* cancels the operation.
- *
- * @type {Function}
- * @context Point
- * @sample {highcharts} highcharts/plotoptions/series-point-events-remove/ Remove point and confirm
- * @since 1.2.0
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-remove/
+ * Remove point and confirm
+ *
+ * @type {Highcharts.SeriesPointRemoveCallbackFunction}
+ * @since 1.2.0
+ * @context Highcharts.Point
* @apioption plotOptions.series.point.events.remove
*/
/**
- * Fires when the point is selected either programmatically or following
- * a click on the point. One parameter, `event`, is passed to the function.
- * Returning `false` cancels the operation.
- *
- * @type {Function}
- * @context Point
- * @sample {highcharts} highcharts/plotoptions/series-point-events-select/ Report the last selected point
- * @sample {highmaps} maps/plotoptions/series-allowpointselect/ Report select and unselect
- * @since 1.2.0
+ * Fires when the point is selected either programmatically or
+ * following a click on the point. One parameter, `event`, is passed
+ * to the function. Returning `false` cancels the operation.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-select/
+ * Report the last selected point
+ * @sample {highmaps} maps/plotoptions/series-allowpointselect/
+ * Report select and unselect
+ *
+ * @type {Highcharts.SeriesPointSelectCallbackFunction}
+ * @since 1.2.0
+ * @context Highcharts.Point
* @apioption plotOptions.series.point.events.select
*/
/**
- * Fires when the point is unselected either programmatically or following
- * a click on the point. One parameter, `event`, is passed to the function.
+ * Fires when the point is unselected either programmatically or
+ * following a click on the point. One parameter, `event`, is passed
+ * to the function.
* Returning `false` cancels the operation.
- *
- * @type {Function}
- * @context Point
- * @sample {highcharts} highcharts/plotoptions/series-point-events-unselect/ Report the last unselected point
- * @sample {highmaps} maps/plotoptions/series-allowpointselect/ Report select and unselect
- * @since 1.2.0
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-unselect/
+ * Report the last unselected point
+ * @sample {highmaps} maps/plotoptions/series-allowpointselect/
+ * Report select and unselect
+ *
+ * @type {Highcharts.SeriesPointUnselectCallbackFunction}
+ * @since 1.2.0
+ * @context Highcharts.Point
* @apioption plotOptions.series.point.events.unselect
*/
/**
- * Fires when the point is updated programmatically through the `.update()`
- * method. One parameter, `event`, is passed to the function. The new
- * point options can be accessed through `event.options`. Returning
- * `false` cancels the operation.
- *
- * @type {Function}
- * @context Point
- * @sample {highcharts} highcharts/plotoptions/series-point-events-update/ Confirm point updating
- * @since 1.2.0
+ * Fires when the point is updated programmatically through the
+ * `.update()` method. One parameter, `event`, is passed to the
+ * function. The new point options can be accessed through
+ * `event.options`. Returning `false` cancels the operation.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-update/
+ * Confirm point updating
+ *
+ * @type {Highcharts.SeriesPointUpdateCallbackFunction}
+ * @since 1.2.0
+ * @context Highcharts.Point
* @apioption plotOptions.series.point.events.update
*/
- }
- },
-
-
-
- /**
- * Options for the series data labels, appearing next to each data
- * point.
- *
- * In styled mode, the data labels can be styled wtih the `.highcharts-data-label-box` and `.highcharts-data-label` class names ([see example](http://jsfiddle.
- * net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/series-
- * datalabels)).
- */
- dataLabels: {
-
+ /**
+ * Events for each single point.
+ */
+ events: {}
+ },
/**
- * The alignment of the data label compared to the point. If `right`,
- * the right side of the label should be touching the point. For
- * points with an extent, like columns, the alignments also dictates
- * how to align it inside the box, as given with the [inside](#plotOptions.
- * column.dataLabels.inside) option. Can be one of "left", "center"
- * or "right".
- *
- * @validvalue ["left", "center", "right"]
- * @type {String}
- * @sample {highcharts} highcharts/plotoptions/series-datalabels-align-left/ Left aligned
- * @default center
+ * Options for the series data labels, appearing next to each data
+ * point.
+ *
+ * Since v6.2.0, multiple data labels can be applied to each single
+ * point by defining them as an array of configs.
+ *
+ * In styled mode, the data labels can be styled with the
+ * `.highcharts-data-label-box` and `.highcharts-data-label` class names
+ * ([see example](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/series-datalabels)).
+ *
+ * @sample highcharts/plotoptions/series-datalabels-enabled
+ * Data labels enabled
+ * @sample highcharts/plotoptions/series-datalabels-multiple
+ * Multiple data labels on a bar series
*/
- align: 'center',
+ dataLabels: {
- /**
- * Whether to allow data labels to overlap. To make the labels less
- * sensitive for overlapping, the [dataLabels.padding](#plotOptions.
- * series.dataLabels.padding) can be set to 0.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/series-datalabels-allowoverlap-false/ Don't allow overlap
- * @sample {highstock} highcharts/plotoptions/series-datalabels-allowoverlap-false/ Don't allow overlap
- * @sample {highmaps} highcharts/plotoptions/series-datalabels-allowoverlap-false/ Don't allow overlap
- * @default false
- * @since 4.1.0
- * @apioption plotOptions.series.dataLabels.allowOverlap
- */
+ /**
+ * The alignment of the data label compared to the point. If
+ * `right`, the right side of the label should be touching the
+ * point. For points with an extent, like columns, the alignments
+ * also dictates how to align it inside the box, as given with the
+ * [inside](#plotOptions.column.dataLabels.inside) option. Can be
+ * one of `left`, `center` or `right`.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-align-left/
+ * Left aligned
+ *
+ * @type {Highcharts.AlignType}
+ */
+ align: 'center',
- /**
- * The border radius in pixels for the data label.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
- * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
- * @sample {highmaps} maps/plotoptions/series-datalabels-box/ Data labels box options
- * @default 0
- * @since 2.2.1
- * @apioption plotOptions.series.dataLabels.borderRadius
- */
+ /**
+ * Whether to allow data labels to overlap. To make the labels less
+ * sensitive for overlapping, the [dataLabels.padding](
+ * #plotOptions.series.dataLabels.padding) can be set to 0.
+ *
+ * @sample highcharts/plotoptions/series-datalabels-allowoverlap-false/
+ * Don't allow overlap
+ *
+ * @type {boolean}
+ * @default false
+ * @since 4.1.0
+ * @apioption plotOptions.series.dataLabels.allowOverlap
+ */
- /**
- * The border width in pixels for the data label.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
- * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
- * @default 0
- * @since 2.2.1
- * @apioption plotOptions.series.dataLabels.borderWidth
- */
+ /**
+ * The border radius in pixels for the data label.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/
+ * Data labels box options
+ * @sample {highstock} highcharts/plotoptions/series-datalabels-box/
+ * Data labels box options
+ * @sample {highmaps} maps/plotoptions/series-datalabels-box/
+ * Data labels box options
+ *
+ * @type {number}
+ * @default 0
+ * @since 2.2.1
+ * @apioption plotOptions.series.dataLabels.borderRadius
+ */
- /**
- * A class name for the data label. Particularly in styled mode, this can
- * be used to give each series' or point's data label unique styling.
- * In addition to this option, a default color class name is added
- * so that we can give the labels a [contrast text shadow](http://jsfiddle.
- * net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/data-
- * label-contrast/).
- *
- * @type {String}
- * @sample {highcharts} highcharts/css/series-datalabels/ Styling by CSS
- * @sample {highstock} highcharts/css/series-datalabels/ Styling by CSS
- * @sample {highmaps} highcharts/css/series-datalabels/ Styling by CSS
- * @since 5.0.0
- * @apioption plotOptions.series.dataLabels.className
- */
- /**
- * The text color for the data labels. Defaults to `null`. For certain series
- * types, like column or map, the data labels can be drawn inside the points.
- * In this case the data label will be drawn with maximum contrast by default.
- * Additionally, it will be given a `text-outline` style with the opposite
- * color, to further increase the contrast. This can be overridden by setting
- * the `text-outline` style to `none` in the `dataLabels.style` option.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/plotoptions/series-datalabels-color/
- * Red data labels
- * @sample {highmaps} maps/demo/color-axis/
- * White data labels
- * @apioption plotOptions.series.dataLabels.color
- */
+ /**
+ * The border width in pixels for the data label.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/
+ * Data labels box options
+ * @sample {highstock} highcharts/plotoptions/series-datalabels-box/
+ * Data labels box options
+ *
+ * @type {number}
+ * @default 0
+ * @since 2.2.1
+ * @apioption plotOptions.series.dataLabels.borderWidth
+ */
- /**
- * Whether to hide data labels that are outside the plot area. By default,
- * the data label is moved inside the plot area according to the [overflow](#plotOptions.
- * series.dataLabels.overflow) option.
- *
- * @type {Boolean}
- * @default true
- * @since 2.3.3
- * @apioption plotOptions.series.dataLabels.crop
- */
+ /**
+ * A class name for the data label. Particularly in styled mode,
+ * this can be used to give each series' or point's data label
+ * unique styling. In addition to this option, a default color class
+ * name is added so that we can give the labels a
+ * [contrast text shadow](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/data-label-contrast/).
+ *
+ * @sample {highcharts} highcharts/css/series-datalabels/
+ * Styling by CSS
+ * @sample {highstock} highcharts/css/series-datalabels/
+ * Styling by CSS
+ * @sample {highmaps} highcharts/css/series-datalabels/
+ * Styling by CSS
+ *
+ * @type {string}
+ * @since 5.0.0
+ * @apioption plotOptions.series.dataLabels.className
+ */
- /**
- * Whether to defer displaying the data labels until the initial series
- * animation has finished.
- *
- * @type {Boolean}
- * @default true
- * @since 4.0
- * @product highcharts highstock
- * @apioption plotOptions.series.dataLabels.defer
- */
+ /**
+ * The text color for the data labels. Defaults to `undefined`. For
+ * certain series types, like column or map, the data labels can be
+ * drawn inside the points. In this case the data label will be
+ * drawn with maximum contrast by default. Additionally, it will be
+ * given a `text-outline` style with the opposite color, to further
+ * increase the contrast. This can be overridden by setting the
+ * `text-outline` style to `none` in the `dataLabels.style` option.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-color/
+ * Red data labels
+ * @sample {highmaps} maps/demo/color-axis/
+ * White data labels
+ *
+ * @type {Highcharts.ColorString}
+ * @apioption plotOptions.series.dataLabels.color
+ */
- /**
- * Enable or disable the data labels.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/series-datalabels-enabled/ Data labels enabled
- * @sample {highmaps} maps/demo/color-axis/ Data labels enabled
- * @default false
- * @apioption plotOptions.series.dataLabels.enabled
- */
+ /**
+ * Whether to hide data labels that are outside the plot area. By
+ * default, the data label is moved inside the plot area according
+ * to the [overflow](#plotOptions.series.dataLabels.overflow)
+ * option.
+ *
+ * @type {boolean}
+ * @default true
+ * @since 2.3.3
+ * @apioption plotOptions.series.dataLabels.crop
+ */
- /**
- * A [format string](http://www.highcharts.com/docs/chart-concepts/labels-
- * and-string-formatting) for the data label. Available variables are
- * the same as for `formatter`.
- *
- * @type {String}
- * @sample {highcharts} highcharts/plotoptions/series-datalabels-format/ Add a unit
- * @sample {highstock} highcharts/plotoptions/series-datalabels-format/ Add a unit
- * @sample {highmaps} maps/plotoptions/series-datalabels-format/ Formatted value in the data label
- * @default {highcharts} {y}
- * @default {highstock} {y}
- * @default {highmaps} {point.value}
- * @since 3.0
- * @apioption plotOptions.series.dataLabels.format
- */
-
- /**
- * Callback JavaScript function to format the data label. Note that
- * if a `format` is defined, the format takes precedence and the formatter
- * is ignored. Available data are:
- *
- *
- *
- *
- *
- *
- *
- * `this.percentage`
- *
- * Stacked series and pies only. The point's percentage of the
- * total.
- *
- *
- *
- *
- *
- * `this.point`
- *
- * The point object. The point name, if defined, is available
- * through `this.point.name`.
- *
- *
- *
- *
- *
- * `this.series`:
- *
- * The series object. The series name is available through `this.
- * series.name`.
- *
- *
- *
- *
- *
- * `this.total`
- *
- * Stacked series only. The total value at this point's x value.
- *
- *
- *
- *
- *
- *
- * `this.x`:
- *
- * The x value.
- *
- *
- *
- *
- *
- * `this.y`:
- *
- * The y value.
- *
- *
- *
- *
- *
- *
- *
- * @type {Function}
- * @sample {highmaps} maps/plotoptions/series-datalabels-format/ Formatted value
- */
- formatter: function() {
- return this.y === null ? '' : H.numberFormat(this.y, -1);
- },
+ /**
+ * Whether to defer displaying the data labels until the initial
+ * series animation has finished.
+ *
+ * @type {boolean}
+ * @default true
+ * @since 4.0
+ * @product highcharts highstock gantt
+ * @apioption plotOptions.series.dataLabels.defer
+ */
+ /**
+ * Enable or disable the data labels.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-enabled/
+ * Data labels enabled
+ * @sample {highmaps} maps/demo/color-axis/
+ * Data labels enabled
+ *
+ * @type {boolean}
+ * @default false
+ * @apioption plotOptions.series.dataLabels.enabled
+ */
+ /**
+ * A [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
+ * for the data label. Available variables are the same as for
+ * `formatter`.
+ *
+ * @sample {highcharts|highstock} highcharts/plotoptions/series-datalabels-format/
+ * Add a unit
+ * @sample {highmaps} maps/plotoptions/series-datalabels-format/
+ * Formatted value in the data label
+ *
+ * @type {string}
+ * @default {highcharts} {y}
+ * @default {highstock} {y}
+ * @default {highmaps} {point.value}
+ * @since 3.0
+ * @apioption plotOptions.series.dataLabels.format
+ */
- /**
- * Styles for the label. The default `color` setting is `"contrast"`,
- * which is a pseudo color that Highcharts picks up and applies the
- * maximum contrast to the underlying point item, for example the
- * bar in a bar chart.
- *
- * The `textOutline` is a pseudo property that
- * applies an outline of the given width with the given color, which
- * by default is the maximum contrast to the text. So a bright text
- * color will result in a black text outline for maximum readability
- * on a mixed background. In some cases, especially with grayscale
- * text, the text outline doesn't work well, in which cases it can
- * be disabled by setting it to `"none"`. When `useHTML` is true, the
- * `textOutline` will not be picked up. In this, case, the same effect
- * can be acheived through the `text-shadow` CSS property.
- *
- * @type {CSSObject}
- * @sample {highcharts} highcharts/plotoptions/series-datalabels-style/
- * Bold labels
- * @sample {highmaps} maps/demo/color-axis/ Bold labels
- * @default {"color": "contrast", "fontSize": "11px", "fontWeight": "bold", "textOutline": "1px contrast" }
- * @since 4.1.0
- */
- style: {
- fontSize: '11px',
- fontWeight: 'bold',
- color: 'contrast',
- textOutline: '1px contrast'
- },
+ /**
+ * Callback JavaScript function to format the data label. Note that
+ * if a `format` is defined, the format takes precedence and the
+ * formatter is ignored. Available data are:
+ *
+ * - `this.percentage`: Stacked series and pies only. The point's
+ * percentage of the total.
+ *
+ * - `this.point`: The point object. The point name, if defined, is
+ * available through `this.point.name`.
+ *
+ * - `this.series`: The series object. The series name is available
+ * through`this.series.name`.
+ *
+ * - `this.total`: Stacked series only. The total value at this
+ * point's x value.
+ *
+ * - `this.x`: The x value.
+ *
+ * - `this.y`: The y value.
+ *
+ * @sample {highmaps} maps/plotoptions/series-datalabels-format/
+ * Formatted value
+ *
+ * @type {Highcharts.FormatterCallbackFunction}
+ * @default function () { return this.y; }
+ */
+ formatter: function () {
+ return this.y === null ? '' : H.numberFormat(this.y, -1);
+ },
- /**
- * The background color or gradient for the data label.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
- * @sample {highmaps} maps/plotoptions/series-datalabels-box/ Data labels box options
- * @since 2.2.1
- * @apioption plotOptions.series.dataLabels.backgroundColor
- */
+ /**
+ * Styles for the label. The default `color` setting is
+ * `"contrast"`, which is a pseudo color that Highcharts picks up
+ * and applies the maximum contrast to the underlying point item,
+ * for example the bar in a bar chart.
+ *
+ * The `textOutline` is a pseudo property that
+ * applies an outline of the given width with the given color, which
+ * by default is the maximum contrast to the text. So a bright text
+ * color will result in a black text outline for maximum readability
+ * on a mixed background. In some cases, especially with grayscale
+ * text, the text outline doesn't work well, in which cases it can
+ * be disabled by setting it to `"none"`. When `useHTML` is true,
+ * the `textOutline` will not be picked up. In this, case, the same
+ * effect can be acheived through the `text-shadow` CSS property.
+ *
+ * For some series types, where each point has an extent, like for
+ * example tree maps, the data label may overflow the point. There
+ * are two strategies for handling overflow. By default, the text
+ * will wrap to multiple lines. The other strategy is to set
+ * `style.textOverflow` to `ellipsis`, which will keep the text on
+ * one line plus it will break inside long words.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-style/
+ * Bold labels
+ * @sample {highmaps} maps/demo/color-axis/
+ * Bold labels
+ *
+ * @type {Highcharts.CSSObject}
+ * @default {"color": "contrast", "fontSize": "11px", "fontWeight": "bold", "textOutline": "1px contrast" }
+ * @since 4.1.0
+ */
+ style: {
+ /** @ignore */
+ fontSize: '11px',
+ /** @ignore */
+ fontWeight: 'bold',
+ /** @ignore */
+ color: 'contrast',
+ /** @ignore */
+ textOutline: '1px contrast'
+ },
- /**
- * The border color for the data label. Defaults to `undefined`.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
- * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
- * @default undefined
- * @since 2.2.1
- * @apioption plotOptions.series.dataLabels.borderColor
- */
+ /**
+ * The name of a symbol to use for the border around the label.
+ * Symbols are predefined functions on the Renderer object.
+ *
+ * @sample highcharts/plotoptions/series-datalabels-shape/
+ * A callout for annotations
+ *
+ * @type {string}
+ * @default square
+ * @since 4.1.2
+ * @apioption plotOptions.series.dataLabels.shape
+ */
- /**
- * The shadow of the box. Works best with `borderWidth` or `backgroundColor`.
- * Since 2.3 the shadow can be an object configuration containing `color`,
- * `offsetX`, `offsetY`, `opacity` and `width`.
- *
- * @type {Boolean|Object}
- * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
- * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
- * @default false
- * @since 2.2.1
- * @apioption plotOptions.series.dataLabels.shadow
- */
+ /**
+ * The Z index of the data labels. The default Z index puts it above
+ * the series. Use a Z index of 2 to display it behind the series.
+ *
+ * @type {number}
+ * @default 6
+ * @since 2.3.5
+ * @apioption plotOptions.series.dataLabels.zIndex
+ */
+ /**
+ * A declarative filter for which data labels to display. The
+ * declarative filter is designed for use when callback functions
+ * are not available, like when the chart options require a pure
+ * JSON structure or for use with graphical editors. For
+ * programmatic control, use the `formatter` instead, and return
+ * `undefined` to disable a single data label.
+ *
+ * @example
+ * filter: {
+ * property: 'percentage',
+ * operator: '>',
+ * value: 4
+ * }
+ *
+ * @sample highcharts/demo/pie-monochrome
+ * Data labels filtered by percentage
+ *
+ * @since 6.0.3
+ * @apioption plotOptions.series.dataLabels.filter
+ */
- /**
- * For points with an extent, like columns or map areas, whether to align the data
- * label inside the box or to the actual value point. Defaults to `false`
- * in most cases, `true` in stacked columns.
- *
- * @type {Boolean}
- * @since 3.0
- * @apioption plotOptions.series.dataLabels.inside
- */
+ /**
+ * The point property to filter by. Point options are passed
+ * directly to properties, additionally there are `y` value,
+ * `percentage` and others listed under
+ * [Point](https://api.highcharts.com/class-reference/Highcharts.Point)
+ * members.
+ *
+ * @type {string}
+ * @apioption plotOptions.series.dataLabels.filter.property
+ */
- /**
- * How to handle data labels that flow outside the plot area. The default
- * is `justify`, which aligns them inside the plot area. For columns
- * and bars, this means it will be moved inside the bar. To display
- * data labels outside the plot area, set `crop` to `false` and `overflow`
- * to `"none"`.
- *
- * @validvalue ["justify", "none"]
- * @type {String}
- * @default justify
- * @since 3.0.6
- * @apioption plotOptions.series.dataLabels.overflow
- */
+ /**
+ * The operator to compare by. Can be one of `>`, `<`, `>=`, `<=`,
+ * `==`, and `===`.
+ *
+ * @type {string}
+ * @validvalue [">", "<", ">=", "<=", "==", "==="]
+ * @apioption plotOptions.series.dataLabels.filter.operator
+ */
- /**
- * Text rotation in degrees. Note that due to a more complex structure,
- * backgrounds, borders and padding will be lost on a rotated data
- * label.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ Vertical labels
- * @default 0
- * @apioption plotOptions.series.dataLabels.rotation
- */
+ /**
+ * The value to compare against.
+ *
+ * @type {*}
+ * @apioption plotOptions.series.dataLabels.filter.value
+ */
- /**
- * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
- * and-string-formatting#html) to render the labels.
- *
- * @type {Boolean}
- * @default false
- * @apioption plotOptions.series.dataLabels.useHTML
- */
+ /**
+ * The background color or gradient for the data label.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/
+ * Data labels box options
+ * @sample {highmaps} maps/plotoptions/series-datalabels-box/
+ * Data labels box options
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @since 2.2.1
+ * @apioption plotOptions.series.dataLabels.backgroundColor
+ */
- /**
- * The vertical alignment of a data label. Can be one of `top`, `middle`
- * or `bottom`. The default value depends on the data, for instance
- * in a column chart, the label is above positive values and below
- * negative values.
- *
- * @validvalue ["top", "middle", "bottom"]
- * @type {String}
- * @since 2.3.3
- */
- verticalAlign: 'bottom', // above singular point
+ /**
+ * The border color for the data label. Defaults to `undefined`.
+ *
+ * @sample {highcharts|highstock} highcharts/plotoptions/series-datalabels-box/
+ * Data labels box options
+ *
+ * @type {Highcharts.ColorString}
+ * @since 2.2.1
+ * @apioption plotOptions.series.dataLabels.borderColor
+ */
+ /**
+ * The shadow of the box. Works best with `borderWidth` or
+ * `backgroundColor`. Since 2.3 the shadow can be an object
+ * configuration containing `color`, `offsetX`, `offsetY`, `opacity`
+ * and `width`.
+ *
+ * @sample {highcharts|highstock} highcharts/plotoptions/series-datalabels-box/
+ * Data labels box options
+ *
+ * @type {boolean|Highcharts.ShadowOptionsObject}
+ * @default false
+ * @since 2.2.1
+ * @apioption plotOptions.series.dataLabels.shadow
+ */
- /**
- * The x position offset of the label relative to the point.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ Vertical and positioned
- * @default 0
- */
- x: 0,
+ /**
+ * For points with an extent, like columns or map areas, whether to
+ * align the data label inside the box or to the actual value point.
+ * Defaults to `false` in most cases, `true` in stacked columns.
+ *
+ * @type {boolean}
+ * @since 3.0
+ * @apioption plotOptions.series.dataLabels.inside
+ */
+ /**
+ * How to handle data labels that flow outside the plot area. The
+ * default is `"justify"`, which aligns them inside the plot area.
+ * For columns and bars, this means it will be moved inside the bar.
+ * To display data labels outside the plot area, set `crop` to
+ * `false` and `overflow` to `"allow"`.
+ *
+ * @type {string}
+ * @default justify
+ * @since 3.0.6
+ * @validvalue ["allow", "justify"]
+ * @apioption plotOptions.series.dataLabels.overflow
+ */
- /**
- * The y position offset of the label relative to the point.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ Vertical and positioned
- * @default -6
- */
- y: 0,
+ /**
+ * Text rotation in degrees. Note that due to a more complex
+ * structure, backgrounds, borders and padding will be lost on a
+ * rotated data label.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/
+ * Vertical labels
+ *
+ * @type {number}
+ * @default 0
+ * @apioption plotOptions.series.dataLabels.rotation
+ */
+ /**
+ * Whether to
+ * [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
+ * to render the labels.
+ *
+ * @type {boolean}
+ * @default false
+ * @apioption plotOptions.series.dataLabels.useHTML
+ */
- /**
- * When either the `borderWidth` or the `backgroundColor` is set,
- * this is the padding within the box.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
- * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
- * @sample {highmaps} maps/plotoptions/series-datalabels-box/ Data labels box options
- * @default {highcharts} 5
- * @default {highstock} 5
- * @default {highmaps} 0
- * @since 2.2.1
- */
- padding: 5
+ /**
+ * The vertical alignment of a data label. Can be one of `top`,
+ * `middle` or `bottom`. The default value depends on the data, for
+ * instance in a column chart, the label is above positive values
+ * and below negative values.
+ *
+ * @type {Highcharts.VerticalAlignType}
+ * @since 2.3.3
+ */
+ verticalAlign: 'bottom', // above singular point
- /**
- * The name of a symbol to use for the border around the label. Symbols
- * are predefined functions on the Renderer object.
- *
- * @type {String}
- * @sample {highcharts} highcharts/plotoptions/series-datalabels-shape/ A callout for annotations
- * @sample {highstock} highcharts/plotoptions/series-datalabels-shape/ A callout for annotations
- * @sample {highmaps} highcharts/plotoptions/series-datalabels-shape/ A callout for annotations (Highcharts demo)
- * @default square
- * @since 4.1.2
- * @apioption plotOptions.series.dataLabels.shape
- */
- /**
- * The Z index of the data labels. The default Z index puts it above
- * the series. Use a Z index of 2 to display it behind the series.
- *
- * @type {Number}
- * @default 6
- * @since 2.3.5
- * @apioption plotOptions.series.dataLabels.zIndex
- */
+ /**
+ * The x position offset of the label relative to the point in
+ * pixels.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/
+ * Vertical and positioned
+ */
+ x: 0,
- /**
- * A declarative filter for which data labels to display. The
- * declarative filter is designed for use when callback functions are
- * not available, like when the chart options require a pure JSON
- * structure or for use with graphical editors. For programmatic
- * control, use the `formatter` instead, and return `false` to disable
- * a single data label.
- *
- * @example
- * filter: {
- * property: 'percentage',
- * operator: '>',
- * value: 4
- * }
- *
- * @sample highcharts/demo/pie-monochrome
- * Data labels filtered by percentage
- *
- * @type {Object}
- * @since 6.0.3
- * @apioption plotOptions.series.dataLabels.filter
- */
- /**
- * The point property to filter by. Point options are passed directly to
- * properties, additionally there are `y` value, `percentage` and others
- * listed under [Point](https://api.highcharts.com/class-reference/Highcharts.Point)
- * members.
- *
- * @type {String}
- * @apioption plotOptions.series.dataLabels.filter.property
- */
+ /**
+ * The y position offset of the label relative to the point in
+ * pixels.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/
+ * Vertical and positioned
+ */
+ y: 0,
- /**
- * The operator to compare by. Can be one of `>`, `<`, `>=`, `<=`, `==`,
- * and `===`.
- *
- * @type {String}
- * @validvalue [">", "<", ">=", "<=", "==", "===""]
- * @apioption plotOptions.series.dataLabels.filter.operator
- */
+
+ /**
+ * When either the `borderWidth` or the `backgroundColor` is set,
+ * this is the padding within the box.
+ *
+ * @sample {highcharts|highstock} highcharts/plotoptions/series-datalabels-box/
+ * Data labels box options
+ * @sample {highmaps} maps/plotoptions/series-datalabels-box/
+ * Data labels box options
+ *
+ * @default {highcharts} 5
+ * @default {highstock} 5
+ * @default {highmaps} 0
+ * @since 2.2.1
+ */
+ padding: 5
+ },
/**
- * The value to compare against.
+ * When the series contains less points than the crop threshold, all
+ * points are drawn, even if the points fall outside the visible plot
+ * area at the current zoom. The advantage of drawing all points
+ * (including markers and columns), is that animation is performed on
+ * updates. On the other hand, when the series contains more points than
+ * the crop threshold, the series data is cropped to only contain points
+ * that fall within the plot area. The advantage of cropping away
+ * invisible points is to increase performance on large series.
*
- * @type {Mixed}
- * @apioption plotOptions.series.dataLabels.filter.value
+ * @since 2.2
+ * @product highcharts highstock
*/
- },
- // draw points outside the plot area when the number of points is less than
- // this
-
-
-
- /**
- * When the series contains less points than the crop threshold, all
- * points are drawn, even if the points fall outside the visible plot
- * area at the current zoom. The advantage of drawing all points (including
- * markers and columns), is that animation is performed on updates.
- * On the other hand, when the series contains more points than the
- * crop threshold, the series data is cropped to only contain points
- * that fall within the plot area. The advantage of cropping away invisible
- * points is to increase performance on large series.
- *
- * @type {Number}
- * @default 300
- * @since 2.2
- * @product highcharts highstock
- */
- cropThreshold: 300,
-
-
-
- /**
- * The width of each point on the x axis. For example in a column chart
- * with one value each day, the pointRange would be 1 day (= 24 * 3600
- * * 1000 milliseconds). This is normally computed automatically, but
- * this option can be used to override the automatic value.
- *
- * @type {Number}
- * @default 0
- * @product highstock
- */
- pointRange: 0,
-
- /**
- * When this is true, the series will not cause the Y axis to cross
- * the zero plane (or [threshold](#plotOptions.series.threshold) option)
- * unless the data actually crosses the plane.
- *
- * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
- * 3 will make the Y axis show negative values according to the `minPadding`
- * option. If `softThreshold` is `true`, the Y axis starts at 0.
- *
- * @type {Boolean}
- * @default true
- * @since 4.1.9
- * @product highcharts highstock
- */
- softThreshold: true,
-
-
-
- /**
- * A wrapper object for all the series options in specific states.
- *
- * @type {plotOptions.series.states}
- */
- states: {
+ cropThreshold: 300,
/**
- * Options for the hovered series. These settings override the normal
- * state options when a series is moused over or touched.
+ * The width of each point on the x axis. For example in a column chart
+ * with one value each day, the pointRange would be 1 day (= 24 * 3600
+ * * 1000 milliseconds). This is normally computed automatically, but
+ * this option can be used to override the automatic value.
*
+ * @product highstock
*/
- hover: {
+ pointRange: 0,
- /**
- * Enable separate styles for the hovered series to visualize that the
- * user hovers either the series itself or the legend. .
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled/ Line
- * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled-column/ Column
- * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled-pie/ Pie
- * @default true
- * @since 1.2
- * @apioption plotOptions.series.states.hover.enabled
- */
+ /**
+ * When this is true, the series will not cause the Y axis to cross
+ * the zero plane (or [threshold](#plotOptions.series.threshold) option)
+ * unless the data actually crosses the plane.
+ *
+ * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
+ * 3 will make the Y axis show negative values according to the
+ * `minPadding` option. If `softThreshold` is `true`, the Y axis starts
+ * at 0.
+ *
+ * @since 4.1.9
+ * @product highcharts highstock
+ */
+ softThreshold: true,
+ /**
+ * A wrapper object for all the series options in specific states.
+ */
+ states: {
/**
- * Animation setting for hovering the graph in line-type series.
- *
- * @type {Boolean|Object}
- * @default { "duration": 50 }
- * @since 5.0.8
- * @product highcharts
+ * The normal state of a series, or for point items in column, pie
+ * and similar series. Currently only used for setting animation
+ * when returning to normal state from hover.
*/
- animation: {
+ normal: {
/**
- * The duration of the hover animation in milliseconds. By
- * default the hover state animates quickly in, and slowly back
- * to normal.
+ * Animation when returning to normal state after hovering.
+ *
+ * @type {boolean|Highcharts.AnimationOptionsObject}
*/
- duration: 50
+ animation: true
},
/**
- * Pixel with of the graph line. By default this property is
- * undefined, and the `lineWidthPlus` property dictates how much
- * to increase the linewidth from normal state.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidth/
- * 5px line on hover
- * @default undefined
- * @product highcharts highstock
- * @apioption plotOptions.series.states.hover.lineWidth
- */
-
-
- /**
- * The additional line width for the graph of a hovered series.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/
- * 5 pixels wider
- * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/
- * 5 pixels wider
- * @default 1
- * @since 4.0.3
- * @product highcharts highstock
+ * Options for the hovered series. These settings override the
+ * normal state options when a series is moused over or touched.
*/
- lineWidthPlus: 1,
+ hover: {
+ /**
+ * Enable separate styles for the hovered series to visualize
+ * that the user hovers either the series itself or the legend.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled/
+ * Line
+ * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled-column/
+ * Column
+ * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled-pie/
+ * Pie
+ *
+ * @type {boolean}
+ * @default true
+ * @since 1.2
+ * @apioption plotOptions.series.states.hover.enabled
+ */
- /**
- * In Highcharts 1.0, the appearance of all markers belonging to
- * the hovered series. For settings on the hover state of the individual
- * point, see [marker.states.hover](#plotOptions.series.marker.states.
- * hover).
- *
- * @extends plotOptions.series.marker
- * @deprecated
- * @product highcharts highstock
- */
- marker: {
- // lineWidth: base + 1,
- // radius: base + 1
- },
+ /**
+ * Animation setting for hovering the graph in line-type series.
+ *
+ * @type {boolean|Highcharts.AnimationOptionsObject}
+ * @since 5.0.8
+ * @product highcharts
+ */
+ animation: {
+ /**
+ * The duration of the hover animation in milliseconds. By
+ * default the hover state animates quickly in, and slowly
+ * back to normal.
+ */
+ duration: 50
+ },
+ /**
+ * Pixel width of the graph line. By default this property is
+ * undefined, and the `lineWidthPlus` property dictates how much
+ * to increase the linewidth from normal state.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidth/
+ * 5px line on hover
+ *
+ * @type {number}
+ * @product highcharts highstock
+ * @apioption plotOptions.series.states.hover.lineWidth
+ */
- /**
- * Options for the halo appearing around the hovered point in line-
- * type series as well as outside the hovered slice in pie charts.
- * By default the halo is filled by the current point or series
- * color with an opacity of 0.25\. The halo can be disabled by setting
- * the `halo` option to `false`.
- *
- * In styled mode, the halo is styled with the `.highcharts-halo` class, with colors inherited from `.highcharts-color-{n}`.
- *
- * @type {Object}
- * @sample {highcharts} highcharts/plotoptions/halo/ Halo options
- * @sample {highstock} highcharts/plotoptions/halo/ Halo options
- * @since 4.0
- * @product highcharts highstock
- */
- halo: {
/**
- * A collection of SVG attributes to override the appearance of the
- * halo, for example `fill`, `stroke` and `stroke-width`.
- *
- * @type {Object}
- * @since 4.0
+ * The additional line width for the graph of a hovered series.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/
+ * 5 pixels wider
+ * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/
+ * 5 pixels wider
+ *
+ * @since 4.0.3
* @product highcharts highstock
- * @apioption plotOptions.series.states.hover.halo.attributes
*/
-
+ lineWidthPlus: 1,
/**
- * The pixel size of the halo. For point markers this is the radius
- * of the halo. For pie slices it is the width of the halo outside
- * the slice. For bubbles it defaults to 5 and is the width of the
- * halo outside the bubble.
- *
- * @type {Number}
- * @default 10
- * @since 4.0
+ * In Highcharts 1.0, the appearance of all markers belonging
+ * to the hovered series. For settings on the hover state of the
+ * individual point, see
+ * [marker.states.hover](#plotOptions.series.marker.states.hover).
+ *
+ * @deprecated
+ *
+ * @extends plotOptions.series.marker
* @product highcharts highstock
*/
- size: 10,
-
-
-
+ marker: {
+ // lineWidth: base + 1,
+ // radius: base + 1
+ },
/**
- * Opacity for the halo unless a specific fill is overridden using
- * the `attributes` setting. Note that Highcharts is only able to
- * apply opacity to colors of hex or rgb(a) formats.
- *
- * @type {Number}
- * @default 0.25
- * @since 4.0
+ * Options for the halo appearing around the hovered point in
+ * line-type series as well as outside the hovered slice in pie
+ * charts. By default the halo is filled by the current point or
+ * series color with an opacity of 0.25\. The halo can be
+ * disabled by setting the `halo` option to `false`.
+ *
+ * In styled mode, the halo is styled with the
+ * `.highcharts-halo` class, with colors inherited from
+ * `.highcharts-color-{n}`.
+ *
+ * @sample {highcharts} highcharts/plotoptions/halo/
+ * Halo options
+ * @sample {highstock} highcharts/plotoptions/halo/
+ * Halo options
+ *
+ * @since 4.0
* @product highcharts highstock
*/
- opacity: 0.25
+ halo: {
- }
- },
+ /**
+ * A collection of SVG attributes to override the appearance
+ * of the halo, for example `fill`, `stroke` and
+ * `stroke-width`.
+ *
+ * @type {Highcharts.SVGAttributes}
+ * @since 4.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.states.hover.halo.attributes
+ */
- /**
- * Specific options for point in selected states, after being selected
- * by [allowPointSelect](#plotOptions.series.allowPointSelect) or
- * programmatically.
- *
- * @type {Object}
- * @extends plotOptions.series.states.hover
- * @excluding brightness
- * @sample {highmaps} maps/plotoptions/series-allowpointselect/
- * Allow point select demo
- * @product highmaps
- */
- select: {
- marker: {}
- }
- },
+ /**
+ * The pixel size of the halo. For point markers this is the
+ * radius of the halo. For pie slices it is the width of the
+ * halo outside the slice. For bubbles it defaults to 5 and
+ * is the width of the halo outside the bubble.
+ *
+ * @since 4.0
+ * @product highcharts highstock
+ */
+ size: 10,
+ /**
+ * Opacity for the halo unless a specific fill is overridden
+ * using the `attributes` setting. Note that Highcharts is
+ * only able to apply opacity to colors of hex or rgb(a)
+ * formats.
+ *
+ * @since 4.0
+ * @product highcharts highstock
+ */
+ opacity: 0.25
+ }
+ },
- /**
- * Sticky tracking of mouse events. When true, the `mouseOut` event
- * on a series isn't triggered until the mouse moves over another series,
- * or out of the plot area. When false, the `mouseOut` event on a
- * series is triggered when the mouse leaves the area around the series'
- * graph or markers. This also implies the tooltip when not shared. When
- * `stickyTracking` is false and `tooltip.shared` is false, the tooltip will
- * be hidden when moving the mouse between series. Defaults to true for line
- * and area type series, but to false for columns, pies etc.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/series-stickytracking-true/
- * True by default
- * @sample {highcharts} highcharts/plotoptions/series-stickytracking-false/
- * False
- * @default {highcharts} true
- * @default {highstock} true
- * @default {highmaps} false
- * @since 2.0
- */
- stickyTracking: true,
-
- /**
- * A configuration object for the tooltip rendering of each single series.
- * Properties are inherited from [tooltip](#tooltip), but only the
- * following properties can be defined on a series level.
- *
- * @type {Object}
- * @extends tooltip
- * @excluding animation,backgroundColor,borderColor,borderRadius,borderWidth,crosshairs,enabled,formatter,positioner,shadow,shared,shape,snap,style,useHTML
- * @since 2.3
- * @apioption plotOptions.series.tooltip
- */
-
- /**
- * When a series contains a data array that is longer than this, only
- * one dimensional arrays of numbers, or two dimensional arrays with
- * x and y values are allowed. Also, only the first point is tested,
- * and the rest are assumed to be the same format. This saves expensive
- * data checking and indexing in long series. Set it to `0` disable.
- *
- * @type {Number}
- * @default 1000
- * @since 2.2
- * @product highcharts highstock
- */
- turboThreshold: 1000,
+ /**
+ * Specific options for point in selected states, after being
+ * selected by
+ * [allowPointSelect](#plotOptions.series.allowPointSelect) or
+ * programmatically.
+ *
+ * @sample {highmaps} maps/plotoptions/series-allowpointselect/
+ * Allow point select demo
+ *
+ * @extends plotOptions.series.states.hover
+ * @excluding brightness
+ * @product highmaps
+ */
+ select: {
+ animation: {
+ duration: 0
+ }
+ }
+ },
- /**
- * An array defining zones within a series. Zones can be applied to
- * the X axis, Y axis or Z axis for bubbles, according to the `zoneAxis`
- * option.
- *
- * In styled mode, the color zones are styled with the `.highcharts-
- * zone-{n}` class, or custom classed from the `className` option ([view
- * live demo](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/color-
- * zones/)).
- *
- * @type {Array}
- * @see [zoneAxis](#plotOptions.series.zoneAxis)
- * @sample {highcharts} highcharts/series/color-zones-simple/ Color zones
- * @sample {highstock} highcharts/series/color-zones-simple/ Color zones
- * @since 4.1.0
- * @product highcharts highstock
- * @apioption plotOptions.series.zones
- */
+ /**
+ * Sticky tracking of mouse events. When true, the `mouseOut` event on a
+ * series isn't triggered until the mouse moves over another series, or
+ * out of the plot area. When false, the `mouseOut` event on a series is
+ * triggered when the mouse leaves the area around the series' graph or
+ * markers. This also implies the tooltip when not shared. When
+ * `stickyTracking` is false and `tooltip.shared` is false, the tooltip
+ * will be hidden when moving the mouse between series. Defaults to true
+ * for line and area type series, but to false for columns, pies etc.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-stickytracking-true/
+ * True by default
+ * @sample {highcharts} highcharts/plotoptions/series-stickytracking-false/
+ * False
+ *
+ * @default {highcharts} true
+ * @default {highstock} true
+ * @default {highmaps} false
+ * @since 2.0
+ */
+ stickyTracking: true,
- /**
- * Styled mode only. A custom class name for the zone.
- *
- * @type {String}
- * @sample {highcharts} highcharts/css/color-zones/ Zones styled by class name
- * @sample {highstock} highcharts/css/color-zones/ Zones styled by class name
- * @sample {highmaps} highcharts/css/color-zones/ Zones styled by class name
- * @since 5.0.0
- * @apioption plotOptions.series.zones.className
- */
+ /**
+ * A configuration object for the tooltip rendering of each single
+ * series. Properties are inherited from [tooltip](#tooltip), but only
+ * the following properties can be defined on a series level.
+ *
+ * @since 2.3
+ * @extends tooltip
+ * @excluding animation, backgroundColor, borderColor, borderRadius,
+ * borderWidth, crosshairs, enabled, formatter, positioner,
+ * shadow, shape, shared, snap, style, useHTML
+ * @apioption plotOptions.series.tooltip
+ */
- /**
- * Defines the color of the series.
- *
- * @type {Color}
- * @see [series color](#plotOptions.series.color)
- * @since 4.1.0
- * @product highcharts highstock
- * @apioption plotOptions.series.zones.color
- */
+ /**
+ * When a series contains a data array that is longer than this, only
+ * one dimensional arrays of numbers, or two dimensional arrays with
+ * x and y values are allowed. Also, only the first point is tested,
+ * and the rest are assumed to be the same format. This saves expensive
+ * data checking and indexing in long series. Set it to `0` disable.
+ *
+ * @since 2.2
+ * @product highcharts highstock gantt
+ */
+ turboThreshold: 1000,
- /**
- * A name for the dash style to use for the graph.
- *
- * @type {String}
- * @see [series.dashStyle](#plotOptions.series.dashStyle)
- * @sample {highcharts} highcharts/series/color-zones-dashstyle-dot/
- * Dashed line indicates prognosis
- * @sample {highstock} highcharts/series/color-zones-dashstyle-dot/
- * Dashed line indicates prognosis
- * @since 4.1.0
- * @product highcharts highstock
- * @apioption plotOptions.series.zones.dashStyle
- */
+ /**
+ * An array defining zones within a series. Zones can be applied to the
+ * X axis, Y axis or Z axis for bubbles, according to the `zoneAxis`
+ * option. The zone definitions have to be in ascending order regarding
+ * to the value.
+ *
+ * In styled mode, the color zones are styled with the
+ * `.highcharts-zone-{n}` class, or custom classed from the `className`
+ * option
+ * ([view live demo](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/color-zones/)).
+ *
+ * @see [zoneAxis](#plotOptions.series.zoneAxis)
+ *
+ * @sample {highcharts} highcharts/series/color-zones-simple/
+ * Color zones
+ * @sample {highstock} highcharts/series/color-zones-simple/
+ * Color zones
+ *
+ * @type {Array<*>}
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.zones
+ */
- /**
- * Defines the fill color for the series (in area type series)
- *
- * @type {Color}
- * @see [fillColor](#plotOptions.area.fillColor)
- * @since 4.1.0
- * @product highcharts highstock
- * @apioption plotOptions.series.zones.fillColor
- */
+ /**
+ * Styled mode only. A custom class name for the zone.
+ *
+ * @sample highcharts/css/color-zones/
+ * Zones styled by class name
+ *
+ * @type {string}
+ * @since 5.0.0
+ * @apioption plotOptions.series.zones.className
+ */
- /**
- * The value up to where the zone extends, if undefined the zones stretches
- * to the last value in the series.
- *
- * @type {Number}
- * @default undefined
- * @since 4.1.0
- * @product highcharts highstock
- * @apioption plotOptions.series.zones.value
- */
-
-
-
- /**
- * Determines whether the series should look for the nearest point
- * in both dimensions or just the x-dimension when hovering the series.
- * Defaults to `'xy'` for scatter series and `'x'` for most other
- * series. If the data has duplicate x-values, it is recommended to
- * set this to `'xy'` to allow hovering over all points.
- *
- * Applies only to series types using nearest neighbor search (not
- * direct hover) for tooltip.
- *
- * @validvalue ['x', 'xy']
- * @type {String}
- * @sample {highcharts} highcharts/series/findnearestpointby/
- * Different hover behaviors
- * @sample {highstock} highcharts/series/findnearestpointby/
- * Different hover behaviors
- * @sample {highmaps} highcharts/series/findnearestpointby/
- * Different hover behaviors
- * @since 5.0.10
- */
- findNearestPointBy: 'x'
-
- }, /** @lends Highcharts.Series.prototype */ {
- isCartesian: true,
- pointClass: Point,
- sorted: true, // requires the data to be sorted
- requireSorting: true,
- directTouch: false,
- axisTypes: ['xAxis', 'yAxis'],
- colorCounter: 0,
- // each point's x and y values are stored in this.xData and this.yData
- parallelArrays: ['x', 'y'],
- coll: 'series',
- init: function(chart, options) {
- var series = this,
- events,
- chartSeries = chart.series,
- lastSeries;
+ /**
+ * Defines the color of the series.
+ *
+ * @see [series color](#plotOptions.series.color)
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.zones.color
+ */
/**
- * Read only. The chart that the series belongs to.
+ * A name for the dash style to use for the graph.
+ *
+ * @see [series.dashStyle](#plotOptions.series.dashStyle)
*
- * @name chart
- * @memberOf Series
- * @type {Chart}
+ * @sample {highcharts|highstock} highcharts/series/color-zones-dashstyle-dot/
+ * Dashed line indicates prognosis
+ *
+ * @type {Highcharts.DashStyleType}
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.zones.dashStyle
*/
- series.chart = chart;
/**
- * Read only. The series' type, like "line", "area", "column" etc. The
- * type in the series options anc can be altered using {@link
- * Series#update}.
+ * Defines the fill color for the series (in area type series)
+ *
+ * @see [fillColor](#plotOptions.area.fillColor)
*
- * @name type
- * @memberOf Series
- * @type String
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.zones.fillColor
*/
/**
- * Read only. The series' current options. To update, use {@link
- * Series#update}.
+ * The value up to where the zone extends, if undefined the zones
+ * stretches to the last value in the series.
*
- * @name options
- * @memberOf Series
- * @type SeriesOptions
+ * @type {number}
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.zones.value
*/
- series.options = options = series.setOptions(options);
- series.linkedSeries = [];
- // bind the axes
- series.bindAxes();
+ /**
+ * Determines whether the series should look for the nearest point
+ * in both dimensions or just the x-dimension when hovering the series.
+ * Defaults to `'xy'` for scatter series and `'x'` for most other
+ * series. If the data has duplicate x-values, it is recommended to
+ * set this to `'xy'` to allow hovering over all points.
+ *
+ * Applies only to series types using nearest neighbor search (not
+ * direct hover) for tooltip.
+ *
+ * @sample {highcharts} highcharts/series/findnearestpointby/
+ * Different hover behaviors
+ * @sample {highstock} highcharts/series/findnearestpointby/
+ * Different hover behaviors
+ * @sample {highmaps} highcharts/series/findnearestpointby/
+ * Different hover behaviors
+ *
+ * @since 5.0.10
+ * @validvalue ["x", "xy"]
+ */
+ findNearestPointBy: 'x'
+
+ },
+ /** @lends Highcharts.Series.prototype */
+ {
+ isCartesian: true,
+ pointClass: Point,
+ sorted: true, // requires the data to be sorted
+ requireSorting: true,
+ directTouch: false,
+ axisTypes: ['xAxis', 'yAxis'],
+ colorCounter: 0,
+ // each point's x and y values are stored in this.xData and this.yData
+ parallelArrays: ['x', 'y'],
+ coll: 'series',
+ cropShoulder: 1,
+ init: function (chart, options) {
+
+ fireEvent(this, 'init', { options: options });
+
+ var series = this,
+ events,
+ chartSeries = chart.series,
+ lastSeries;
- // set some variables
- extend(series, {
/**
- * The series name as given in the options. Defaults to
- * "Series {n}".
+ * Read only. The chart that the series belongs to.
*
- * @name name
- * @memberOf Series
- * @type {String}
+ * @name Highcharts.Series#chart
+ * @type {Highcharts.Chart}
*/
- name: options.name,
- state: '',
+ series.chart = chart;
+
/**
- * Read only. The series' visibility state as set by {@link
- * Series#show}, {@link Series#hide}, or in the initial
- * configuration.
+ * Read only. The series' type, like "line", "area", "column" etc.
+ * The type in the series options anc can be altered using
+ * {@link Series#update}.
*
- * @name visible
- * @memberOf Series
- * @type {Boolean}
+ * @name Highcharts.Series#type
+ * @type {string}
*/
- visible: options.visible !== false, // true by default
+
/**
- * Read only. The series' selected state as set by {@link
- * Highcharts.Series#select}.
+ * Read only. The series' current options. To update, use
+ * {@link Series#update}.
*
- * @name selected
- * @memberOf Series
- * @type {Boolean}
+ * @name Highcharts.Series#options
+ * @type {Highcharts.SeriesOptionsType}
*/
- selected: options.selected === true // false by default
- });
+ series.options = options = series.setOptions(options);
+ series.linkedSeries = [];
+ // bind the axes
+ series.bindAxes();
- // register event listeners
- events = options.events;
+ // set some variables
+ extend(series, {
+ /**
+ * The series name as given in the options. Defaults to
+ * "Series {n}".
+ *
+ * @name Highcharts.Series#name
+ * @type {string}
+ */
+ name: options.name,
+ state: '',
+ /**
+ * Read only. The series' visibility state as set by {@link
+ * Series#show}, {@link Series#hide}, or in the initial
+ * configuration.
+ *
+ * @name Highcharts.Series#visible
+ * @type {boolean}
+ */
+ visible: options.visible !== false, // true by default
+ /**
+ * Read only. The series' selected state as set by {@link
+ * Highcharts.Series#select}.
+ *
+ * @name Highcharts.Series#selected
+ * @type {boolean}
+ */
+ selected: options.selected === true // false by default
+ });
- objectEach(events, function(event, eventType) {
- addEvent(series, eventType, event);
- });
- if (
- (events && events.click) ||
- (
- options.point &&
- options.point.events &&
- options.point.events.click
- ) ||
- options.allowPointSelect
- ) {
- chart.runTrackerClick = true;
- }
+ // register event listeners
+ events = options.events;
- series.getColor();
- series.getSymbol();
+ objectEach(events, function (event, eventType) {
+ if (
+ // In case we're doing Series.update(), first check if the
+ // event already exists.
+ !series.hcEvents ||
+ !series.hcEvents[eventType] ||
+ series.hcEvents[eventType].indexOf(event) === -1
+ ) {
+ addEvent(series, eventType, event);
+ }
+ });
+ if (
+ (events && events.click) ||
+ (
+ options.point &&
+ options.point.events &&
+ options.point.events.click
+ ) ||
+ options.allowPointSelect
+ ) {
+ chart.runTrackerClick = true;
+ }
- // Set the data
- each(series.parallelArrays, function(key) {
- series[key + 'Data'] = [];
- });
- series.setData(options.data, false);
+ series.getColor();
+ series.getSymbol();
- // Mark cartesian
- if (series.isCartesian) {
- chart.hasCartesianSeries = true;
- }
+ // Set the data
+ series.parallelArrays.forEach(function (key) {
+ series[key + 'Data'] = [];
+ });
+ series.setData(options.data, false);
- // Get the index and register the series in the chart. The index is one
- // more than the current latest series index (#5960).
- if (chartSeries.length) {
- lastSeries = chartSeries[chartSeries.length - 1];
- }
- series._i = pick(lastSeries && lastSeries._i, -1) + 1;
+ // Mark cartesian
+ if (series.isCartesian) {
+ chart.hasCartesianSeries = true;
+ }
- // Insert the series and re-order all series above the insertion point.
- chart.orderSeries(this.insert(chartSeries));
- },
+ // Get the index and register the series in the chart. The index is
+ // one more than the current latest series index (#5960).
+ if (chartSeries.length) {
+ lastSeries = chartSeries[chartSeries.length - 1];
+ }
+ series._i = pick(lastSeries && lastSeries._i, -1) + 1;
- /**
- * Insert the series in a collection with other series, either the chart
- * series or yAxis series, in the correct order according to the index
- * option. Used internally when adding series.
- *
- * @private
- * @param {Array.} collection
- * A collection of series, like `chart.series` or `xAxis.series`.
- * @returns {Number} The index of the series in the collection.
- */
- insert: function(collection) {
- var indexOption = this.options.index,
- i;
+ // Insert the series and re-order all series above the insertion
+ // point.
+ chart.orderSeries(this.insert(chartSeries));
- // Insert by index option
- if (isNumber(indexOption)) {
- i = collection.length;
- while (i--) {
- // Loop down until the interted element has higher index
- if (indexOption >=
- pick(collection[i].options.index, collection[i]._i)) {
- collection.splice(i + 1, 0, this);
- break;
+ fireEvent(this, 'afterInit');
+ },
+
+ /**
+ * Insert the series in a collection with other series, either the chart
+ * series or yAxis series, in the correct order according to the index
+ * option. Used internally when adding series.
+ *
+ * @private
+ * @function Highcharts.Series#insert
+ *
+ * @param {Array} collection
+ * A collection of series, like `chart.series` or `xAxis.series`.
+ *
+ * @return {number}
+ * The index of the series in the collection.
+ */
+ insert: function (collection) {
+ var indexOption = this.options.index,
+ i;
+
+ // Insert by index option
+ if (isNumber(indexOption)) {
+ i = collection.length;
+ while (i--) {
+ // Loop down until the interted element has higher index
+ if (indexOption >=
+ pick(collection[i].options.index, collection[i]._i)
+ ) {
+ collection.splice(i + 1, 0, this);
+ break;
+ }
}
- }
- if (i === -1) {
- collection.unshift(this);
- }
- i = i + 1;
+ if (i === -1) {
+ collection.unshift(this);
+ }
+ i = i + 1;
+
// Or just push it to the end
- } else {
- collection.push(this);
- }
- return pick(i, collection.length - 1);
- },
+ } else {
+ collection.push(this);
+ }
+ return pick(i, collection.length - 1);
+ },
- /**
- * Set the xAxis and yAxis properties of cartesian series, and register the
- * series in the `axis.series` array.
- *
- * @private
- */
- bindAxes: function() {
- var series = this,
- seriesOptions = series.options,
- chart = series.chart,
- axisOptions;
+ /**
+ * Set the xAxis and yAxis properties of cartesian series, and register
+ * the series in the `axis.series` array.
+ *
+ * @private
+ * @function Highcharts.Series#bindAxes
+ *
+ * @exception 18
+ */
+ bindAxes: function () {
+ var series = this,
+ seriesOptions = series.options,
+ chart = series.chart,
+ axisOptions;
- // repeat for xAxis and yAxis
- each(series.axisTypes || [], function(AXIS) {
+ fireEvent(this, 'bindAxes', null, function () {
- // loop through the chart's axis objects
- each(chart[AXIS], function(axis) {
- axisOptions = axis.options;
+ // repeat for xAxis and yAxis
+ (series.axisTypes || []).forEach(function (AXIS) {
- // apply if the series xAxis or yAxis option mathches the number
- // of the axis, or if undefined, use the first axis
- if (
- seriesOptions[AXIS] === axisOptions.index ||
- (
- seriesOptions[AXIS] !== undefined &&
- seriesOptions[AXIS] === axisOptions.id
- ) ||
- (
- seriesOptions[AXIS] === undefined &&
- axisOptions.index === 0
- )
- ) {
+ // loop through the chart's axis objects
+ chart[AXIS].forEach(function (axis) {
+ axisOptions = axis.options;
+
+ // apply if the series xAxis or yAxis option mathches
+ // the number of the axis, or if undefined, use the
+ // first axis
+ if (
+ seriesOptions[AXIS] === axisOptions.index ||
+ (
+ seriesOptions[AXIS] !== undefined &&
+ seriesOptions[AXIS] === axisOptions.id
+ ) ||
+ (
+ seriesOptions[AXIS] === undefined &&
+ axisOptions.index === 0
+ )
+ ) {
+
+ // register this series in the axis.series lookup
+ series.insert(axis.series);
+
+ // set this series.xAxis or series.yAxis reference
+ /**
+ * Read only. The unique xAxis object associated
+ * with the series.
+ *
+ * @name Highcharts.Series#xAxis
+ * @type {Highcharts.Axis}
+ */
+ /**
+ * Read only. The unique yAxis object associated
+ * with the series.
+ *
+ * @name Highcharts.Series#yAxis
+ * @type {Highcharts.Axis}
+ */
+ series[AXIS] = axis;
+
+ // mark dirty for redraw
+ axis.isDirty = true;
+ }
+ });
+
+ // The series needs an X and an Y axis
+ if (!series[AXIS] && series.optionalAxis !== AXIS) {
+ H.error(18, true, chart);
+ }
+
+ });
+ });
+ },
+
+ /**
+ * For simple series types like line and column, the data values are
+ * held in arrays like xData and yData for quick lookup to find extremes
+ * and more. For multidimensional series like bubble and map, this can
+ * be extended with arrays like zData and valueData by adding to the
+ * `series.parallelArrays` array.
+ *
+ * @private
+ * @function Highcharts.Series#updateParallelArrays
+ *
+ * @param {Highcharts.Point} point
+ *
+ * @param {number|string} i
+ */
+ updateParallelArrays: function (point, i) {
+ var series = point.series,
+ args = arguments,
+ fn = isNumber(i) ?
+ // Insert the value in the given position
+ function (key) {
+ var val = key === 'y' && series.toYData ?
+ series.toYData(point) :
+ point[key];
+
+ series[key + 'Data'][i] = val;
+ } :
+ // Apply the method specified in i with the following
+ // arguments as arguments
+ function (key) {
+ Array.prototype[i].apply(
+ series[key + 'Data'],
+ Array.prototype.slice.call(args, 2)
+ );
+ };
+
+ series.parallelArrays.forEach(fn);
+ },
+
+ /**
+ * Return an auto incremented x value based on the pointStart and
+ * pointInterval options. This is only used if an x value is not given
+ * for the point that calls autoIncrement.
+ *
+ * @private
+ * @function Highcharts.Series#autoIncrement
+ *
+ * @return {number}
+ */
+ autoIncrement: function () {
- // register this series in the axis.series lookup
- series.insert(axis.series);
+ var options = this.options,
+ xIncrement = this.xIncrement,
+ date,
+ pointInterval,
+ pointIntervalUnit = options.pointIntervalUnit,
+ time = this.chart.time;
- // set this series.xAxis or series.yAxis reference
- /**
- * Read only. The unique xAxis object associated with the
- * series.
- *
- * @name xAxis
- * @memberOf Series
- * @type Axis
- */
- /**
- * Read only. The unique yAxis object associated with the
- * series.
- *
- * @name yAxis
- * @memberOf Series
- * @type Axis
- */
- series[AXIS] = axis;
+ xIncrement = pick(xIncrement, options.pointStart, 0);
+
+ this.pointInterval = pointInterval = pick(
+ this.pointInterval,
+ options.pointInterval,
+ 1
+ );
- // mark dirty for redraw
- axis.isDirty = true;
+ // Added code for pointInterval strings
+ if (pointIntervalUnit) {
+ date = new time.Date(xIncrement);
+
+ if (pointIntervalUnit === 'day') {
+ time.set(
+ 'Date',
+ date,
+ time.get('Date', date) + pointInterval
+ );
+ } else if (pointIntervalUnit === 'month') {
+ time.set(
+ 'Month',
+ date,
+ time.get('Month', date) + pointInterval
+ );
+ } else if (pointIntervalUnit === 'year') {
+ time.set(
+ 'FullYear',
+ date,
+ time.get('FullYear', date) + pointInterval
+ );
}
- });
- // The series needs an X and an Y axis
- if (!series[AXIS] && series.optionalAxis !== AXIS) {
- H.error(18, true);
+ pointInterval = date.getTime() - xIncrement;
+
}
- });
- },
+ this.xIncrement = xIncrement + pointInterval;
+ return xIncrement;
+ },
- /**
- * For simple series types like line and column, the data values are held in
- * arrays like xData and yData for quick lookup to find extremes and more.
- * For multidimensional series like bubble and map, this can be extended
- * with arrays like zData and valueData by adding to the
- * `series.parallelArrays` array.
- *
- * @private
- */
- updateParallelArrays: function(point, i) {
- var series = point.series,
- args = arguments,
- fn = isNumber(i) ?
- // Insert the value in the given position
- function(key) {
- var val = key === 'y' && series.toYData ?
- series.toYData(point) :
- point[key];
- series[key + 'Data'][i] = val;
- } :
- // Apply the method specified in i with the following arguments
- // as arguments
- function(key) {
- Array.prototype[i].apply(
- series[key + 'Data'],
- Array.prototype.slice.call(args, 2)
- );
- };
+ /**
+ * Set the series options by merging from the options tree. Called
+ * internally on initializing and updating series. This function will
+ * not redraw the series. For API usage, use {@link Series#update}.
+ *
+ * @function Highcharts.Series#setOptions
+ *
+ * @param {Highcharts.SeriesOptionsType} itemOptions
+ * The series options.
+ *
+ * @return {Highcharts.SeriesOptionsType}
+ *
+ * @fires Highcharts.Series#event:afterSetOptions
+ */
+ setOptions: function (itemOptions) {
+ var chart = this.chart,
+ chartOptions = chart.options,
+ plotOptions = chartOptions.plotOptions,
+ userOptions = chart.userOptions || {},
+ userPlotOptions = userOptions.plotOptions || {},
+ typeOptions = plotOptions[this.type],
+ seriesUserOptions = merge(itemOptions),
+ options,
+ zones,
+ zone,
+ styledMode = chart.styledMode;
+
+ fireEvent(this, 'setOptions', { userOptions: seriesUserOptions });
+
+ // use copy to prevent undetected changes (#9762)
+ this.userOptions = seriesUserOptions;
+
+ // General series options take precedence over type options because
+ // otherwise, default type options like column.animation would be
+ // overwritten by the general option. But issues have been raised
+ // here (#3881), and the solution may be to distinguish between
+ // default option and userOptions like in the tooltip below.
+ options = merge(
+ typeOptions,
+ plotOptions.series,
+ seriesUserOptions
+ );
- each(series.parallelArrays, fn);
- },
+ // The tooltip options are merged between global and series specific
+ // options. Importance order asscendingly:
+ // globals: (1)tooltip, (2)plotOptions.series,
+ // (3)plotOptions[this.type]
+ // init userOptions with possible later updates: 4-6 like 1-3 and
+ // (7)this series options
+ this.tooltipOptions = merge(
+ defaultOptions.tooltip, // 1
+ defaultOptions.plotOptions.series &&
+ defaultOptions.plotOptions.series.tooltip, // 2
+ defaultOptions.plotOptions[this.type].tooltip, // 3
+ chartOptions.tooltip.userOptions, // 4
+ plotOptions.series && plotOptions.series.tooltip, // 5
+ plotOptions[this.type].tooltip, // 6
+ seriesUserOptions.tooltip // 7
+ );
- /**
- * Return an auto incremented x value based on the pointStart and
- * pointInterval options. This is only used if an x value is not given for
- * the point that calls autoIncrement.
- *
- * @private
- */
- autoIncrement: function() {
+ // When shared tooltip, stickyTracking is true by default,
+ // unless user says otherwise.
+ this.stickyTracking = pick(
+ seriesUserOptions.stickyTracking,
+ userPlotOptions[this.type] &&
+ userPlotOptions[this.type].stickyTracking,
+ userPlotOptions.series && userPlotOptions.series.stickyTracking,
+ (
+ this.tooltipOptions.shared && !this.noSharedTooltip ?
+ true :
+ options.stickyTracking
+ )
+ );
- var options = this.options,
- xIncrement = this.xIncrement,
- date,
- pointInterval,
- pointIntervalUnit = options.pointIntervalUnit;
+ // Delete marker object if not allowed (#1125)
+ if (typeOptions.marker === null) {
+ delete options.marker;
+ }
- xIncrement = pick(xIncrement, options.pointStart, 0);
+ // Handle color zones
+ this.zoneAxis = options.zoneAxis;
+ zones = this.zones = (options.zones || []).slice();
+ if (
+ (options.negativeColor || options.negativeFillColor) &&
+ !options.zones
+ ) {
+ zone = {
+ value:
+ options[this.zoneAxis + 'Threshold'] ||
+ options.threshold ||
+ 0,
+ className: 'highcharts-negative'
+ };
+ if (!styledMode) {
+ zone.color = options.negativeColor;
+ zone.fillColor = options.negativeFillColor;
+ }
+ zones.push(zone);
+ }
+ if (zones.length) { // Push one extra zone for the rest
+ if (defined(zones[zones.length - 1].value)) {
+ zones.push(styledMode ? {} : {
+ color: this.color,
+ fillColor: this.fillColor
+ });
+ }
+ }
- this.pointInterval = pointInterval = pick(
- this.pointInterval,
- options.pointInterval,
- 1
- );
+ fireEvent(this, 'afterSetOptions', { options: options });
- // Added code for pointInterval strings
- if (pointIntervalUnit) {
- date = new Date(xIncrement);
+ return options;
+ },
- if (pointIntervalUnit === 'day') {
- date = +date[Date.hcSetDate](
- date[Date.hcGetDate]() + pointInterval
- );
- } else if (pointIntervalUnit === 'month') {
- date = +date[Date.hcSetMonth](
- date[Date.hcGetMonth]() + pointInterval
- );
- } else if (pointIntervalUnit === 'year') {
- date = +date[Date.hcSetFullYear](
- date[Date.hcGetFullYear]() + pointInterval
+ /**
+ * Return series name in "Series {Number}" format or the one defined by
+ * a user. This method can be simply overridden as series name format
+ * can vary (e.g. technical indicators).
+ *
+ * @function Highcharts.Series#getName
+ *
+ * @return {string}
+ * The series name.
+ */
+ getName: function () {
+ // #4119
+ return pick(this.options.name, 'Series ' + (this.index + 1));
+ },
+
+ /**
+ * @private
+ * @function Highcharts.Series#getCyclic
+ *
+ * @param {string} prop
+ *
+ * @param {*} value
+ *
+ * @param {*} [defaults]
+ */
+ getCyclic: function (prop, value, defaults) {
+ var i,
+ chart = this.chart,
+ userOptions = this.userOptions,
+ indexName = prop + 'Index',
+ counterName = prop + 'Counter',
+ len = defaults ? defaults.length : pick(
+ chart.options.chart[prop + 'Count'],
+ chart[prop + 'Count']
+ ),
+ setting;
+
+ if (!value) {
+ // Pick up either the colorIndex option, or the _colorIndex
+ // after Series.update()
+ setting = pick(
+ userOptions[indexName],
+ userOptions['_' + indexName]
);
+ if (defined(setting)) { // after Series.update()
+ i = setting;
+ } else {
+ // #6138
+ if (!chart.series.length) {
+ chart[counterName] = 0;
+ }
+ userOptions['_' + indexName] = i = chart[counterName] % len;
+ chart[counterName] += 1;
+ }
+ if (defaults) {
+ value = defaults[i];
+ }
}
- pointInterval = date - xIncrement;
-
- }
+ // Set the colorIndex
+ if (i !== undefined) {
+ this[indexName] = i;
+ }
+ this[prop] = value;
+ },
- this.xIncrement = xIncrement + pointInterval;
- return xIncrement;
- },
+ /**
+ * Get the series' color based on either the options or pulled from
+ * global options.
+ *
+ * @function Highcharts.Series#getColor
+ */
+ getColor: function () {
+ if (this.chart.styledMode) {
+ this.getCyclic('color');
- /**
- * Set the series options by merging from the options tree. Called
- * internally on initiating and updating series. This function will not
- * redraw the series. For API usage, use {@link Series#update}.
- *
- * @param {Options.plotOptions.series} itemOptions
- * The series options.
- */
- setOptions: function(itemOptions) {
- var chart = this.chart,
- chartOptions = chart.options,
- plotOptions = chartOptions.plotOptions,
- userOptions = chart.userOptions || {},
- userPlotOptions = userOptions.plotOptions || {},
- typeOptions = plotOptions[this.type],
- options,
- zones;
-
- this.userOptions = itemOptions;
-
- // General series options take precedence over type options because
- // otherwise, default type options like column.animation would be
- // overwritten by the general option. But issues have been raised here
- // (#3881), and the solution may be to distinguish between default
- // option and userOptions like in the tooltip below.
- options = merge(
- typeOptions,
- plotOptions.series,
- itemOptions
- );
+ } else if (this.options.colorByPoint) {
+ // #4359, selected slice got series.color even when colorByPoint
+ // was set.
+ this.options.color = null;
+ } else {
+ this.getCyclic(
+ 'color',
+ this.options.color || defaultPlotOptions[this.type].color,
+ this.chart.options.colors
+ );
+ }
+ },
- // The tooltip options are merged between global and series specific
- // options. Importance order asscendingly:
- // globals: (1)tooltip, (2)plotOptions.series, (3)plotOptions[this.type]
- // init userOptions with possible later updates: 4-6 like 1-3 and
- // (7)this series options
- this.tooltipOptions = merge(
- defaultOptions.tooltip, // 1
- defaultOptions.plotOptions.series &&
- defaultOptions.plotOptions.series.tooltip, // 2
- defaultOptions.plotOptions[this.type].tooltip, // 3
- chartOptions.tooltip.userOptions, // 4
- plotOptions.series && plotOptions.series.tooltip, // 5
- plotOptions[this.type].tooltip, // 6
- itemOptions.tooltip // 7
- );
+ /**
+ * Get the series' symbol based on either the options or pulled from
+ * global options.
+ *
+ * @function Highcharts.Series#getSymbol
+ */
+ getSymbol: function () {
+ var seriesMarkerOption = this.options.marker;
- // When shared tooltip, stickyTracking is true by default,
- // unless user says otherwise.
- this.stickyTracking = pick(
- itemOptions.stickyTracking,
- userPlotOptions[this.type] &&
- userPlotOptions[this.type].stickyTracking,
- userPlotOptions.series && userPlotOptions.series.stickyTracking,
- (
- this.tooltipOptions.shared && !this.noSharedTooltip ?
- true :
- options.stickyTracking
- )
- );
+ this.getCyclic(
+ 'symbol',
+ seriesMarkerOption.symbol,
+ this.chart.options.symbols
+ );
+ },
- // Delete marker object if not allowed (#1125)
- if (typeOptions.marker === null) {
- delete options.marker;
- }
+ /**
+ * @private
+ * @borrows LegendSymbolMixin.drawLineMarker as Highcharts.Series#drawLegendSymbol
+ */
+ drawLegendSymbol: LegendSymbolMixin.drawLineMarker,
- // Handle color zones
- this.zoneAxis = options.zoneAxis;
- zones = this.zones = (options.zones || []).slice();
- if (
- (options.negativeColor || options.negativeFillColor) &&
- !options.zones
- ) {
- zones.push({
- value: options[this.zoneAxis + 'Threshold'] ||
- options.threshold ||
- 0,
- className: 'highcharts-negative',
+ /**
+ * Internal function called from setData. If the point count is the same
+ * as is was, or if there are overlapping X values, just run
+ * Point.update which is cheaper, allows animation, and keeps references
+ * to points. This also allows adding or removing points if the X-es
+ * don't match.
+ *
+ * @private
+ * @function Highcharts.Series#updateData
+ *
+ * @param {Array<*>} data
+ *
+ * @return {boolean}
+ */
+ updateData: function (data) {
+ var options = this.options,
+ oldData = this.points,
+ pointsToAdd = [],
+ hasUpdatedByKey,
+ i,
+ point,
+ lastIndex,
+ requireSorting = this.requireSorting;
- color: options.negativeColor,
- fillColor: options.negativeFillColor
+ this.xIncrement = null;
- });
- }
- if (zones.length) { // Push one extra zone for the rest
- if (defined(zones[zones.length - 1].value)) {
- zones.push({
+ // Iterate the new data
+ data.forEach(function (pointOptions) {
+ var id,
+ matchingPoint,
+ x,
+ pointIndex,
+ optionsObject = (
+ H.defined(pointOptions) &&
+ this.pointClass.prototype.optionsToObject.call(
+ { series: this },
+ pointOptions
+ )
+ ) || {};
- color: this.color,
- fillColor: this.fillColor
+ // Get the x of the new data point
+ x = optionsObject.x;
+ id = optionsObject.id;
- });
- }
- }
- return options;
- },
+ if (id || isNumber(x)) {
+ if (id) {
+ matchingPoint = this.chart.get(id);
+ pointIndex = matchingPoint && matchingPoint.index;
+ }
- getCyclic: function(prop, value, defaults) {
- var i,
- chart = this.chart,
- userOptions = this.userOptions,
- indexName = prop + 'Index',
- counterName = prop + 'Counter',
- len = defaults ? defaults.length : pick(
- chart.options.chart[prop + 'Count'],
- chart[prop + 'Count']
- ),
- setting;
-
- if (!value) {
- // Pick up either the colorIndex option, or the _colorIndex after
- // Series.update()
- setting = pick(
- userOptions[indexName],
- userOptions['_' + indexName]
- );
- if (defined(setting)) { // after Series.update()
- i = setting;
- } else {
- // #6138
- if (!chart.series.length) {
- chart[counterName] = 0;
- }
- userOptions['_' + indexName] = i = chart[counterName] % len;
- chart[counterName] += 1;
- }
- if (defaults) {
- value = defaults[i];
- }
- }
- // Set the colorIndex
- if (i !== undefined) {
- this[indexName] = i;
- }
- this[prop] = value;
- },
+ // Search for the same X in the existing data set
+ if (pointIndex === undefined && isNumber(x)) {
+ pointIndex = this.xData.indexOf(x, lastIndex);
+ }
- /**
- * Get the series' color based on either the options or pulled from global
- * options.
- *
- * @return {Color} The series color.
- */
+ // Reduce pointIndex if data is cropped
+ if (pointIndex !== -1 &&
+ pointIndex !== undefined &&
+ this.cropped
+ ) {
+ pointIndex = (pointIndex >= this.cropStart) ?
+ pointIndex - this.cropStart : pointIndex;
+ }
- getColor: function() {
- if (this.options.colorByPoint) {
- // #4359, selected slice got series.color even when colorByPoint was
- // set.
- this.options.color = null;
- } else {
- this.getCyclic(
- 'color',
- this.options.color || defaultPlotOptions[this.type].color,
- this.chart.options.colors
- );
- }
- },
+ // Matching X not found
+ // or used already due to ununique x values (#8995),
+ // add point (but later)
+ if (
+ pointIndex === -1 ||
+ pointIndex === undefined ||
+ (oldData[pointIndex] &&
+ oldData[pointIndex].touched
+ )
+ ) {
+ pointsToAdd.push(pointOptions);
- /**
- * Get the series' symbol based on either the options or pulled from global
- * options.
- */
- getSymbol: function() {
- var seriesMarkerOption = this.options.marker;
+ // Matching X found, update
+ } else if (pointOptions !== options.data[pointIndex]) {
+ oldData[pointIndex].update(
+ pointOptions,
+ false,
+ null,
+ false
+ );
- this.getCyclic(
- 'symbol',
- seriesMarkerOption.symbol,
- this.chart.options.symbols
- );
- },
+ // Mark it touched, below we will remove all points that
+ // are not touched.
+ oldData[pointIndex].touched = true;
- drawLegendSymbol: LegendSymbolMixin.drawLineMarker,
-
- /**
- * Apply a new set of data to the series and optionally redraw it. The new
- * data array is passed by reference (except in case of `updatePoints`), and
- * may later be mutated when updating the chart data.
- *
- * Note the difference in behaviour when setting the same amount of points,
- * or a different amount of points, as handled by the `updatePoints`
- * parameter.
- *
- * @param {SeriesDataOptions} data
- * Takes an array of data in the same format as described under
- * `series.typedata` for the given series type.
- * @param {Boolean} [redraw=true]
- * Whether to redraw the chart after the series is altered. If doing
- * more operations on the chart, it is a good idea to set redraw to
- * false and call {@link Chart#redraw} after.
- * @param {AnimationOptions} [animation]
- * When the updated data is the same length as the existing data,
- * points will be updated by default, and animation visualizes how
- * the points are changed. Set false to disable animation, or a
- * configuration object to set duration or easing.
- * @param {Boolean} [updatePoints=true]
- * When the updated data is the same length as the existing data,
- * points will be updated instead of replaced. This allows updating
- * with animation and performs better. In this case, the original
- * array is not passed by reference. Set false to prevent.
- *
- * @sample highcharts/members/series-setdata/
- * Set new data from a button
- * @sample highcharts/members/series-setdata-pie/
- * Set data in a pie
- * @sample stock/members/series-setdata/
- * Set new data in Highstock
- * @sample maps/members/series-setdata/
- * Set new data in Highmaps
- */
- setData: function(data, redraw, animation, updatePoints) {
- var series = this,
- oldData = series.points,
- oldDataLength = (oldData && oldData.length) || 0,
- dataLength,
- options = series.options,
- chart = series.chart,
- firstPoint = null,
- xAxis = series.xAxis,
- i,
- turboThreshold = options.turboThreshold,
- pt,
- xData = this.xData,
- yData = this.yData,
- pointArrayMap = series.pointArrayMap,
- valueCount = pointArrayMap && pointArrayMap.length;
-
- data = data || [];
- dataLength = data.length;
- redraw = pick(redraw, true);
+ // Speed optimize by only searching after last known
+ // index. Performs ~20% bettor on large data sets.
+ if (requireSorting) {
+ lastIndex = pointIndex + 1;
+ }
+ // Point exists, no changes, don't remove it
+ } else if (oldData[pointIndex]) {
+ oldData[pointIndex].touched = true;
+ }
+ hasUpdatedByKey = true;
+ }
+ }, this);
- // If the point count is the same as is was, just run Point.update which
- // is cheaper, allows animation, and keeps references to points.
- if (
- updatePoints !== false &&
- dataLength &&
- oldDataLength === dataLength &&
- !series.cropped &&
- !series.hasGroupedData &&
- series.visible
- ) {
- each(data, function(point, i) {
- // .update doesn't exist on a linked, hidden series (#3709)
- if (oldData[i].update && point !== options.data[i]) {
- oldData[i].update(point, false, null, false);
+ // Remove points that don't exist in the updated data set
+ if (hasUpdatedByKey) {
+ i = oldData.length;
+ while (i--) {
+ point = oldData[i];
+ if (!point.touched) {
+ point.remove(false);
+ }
+ point.touched = false;
}
- });
- } else {
+ // If we did not find keys (x-values), and the length is the same,
+ // update one-to-one
+ } else if (data.length === oldData.length) {
+ data.forEach(function (point, i) {
+ // .update doesn't exist on a linked, hidden series (#3709)
+ if (oldData[i].update && point !== options.data[i]) {
+ oldData[i].update(point, false, null, false);
+ }
+ });
- // Reset properties
- series.xIncrement = null;
+ // Did not succeed in updating data
+ } else {
+ return false;
+ }
- series.colorCounter = 0; // for series with colorByPoint (#1547)
+ // Add new points
+ pointsToAdd.forEach(function (point) {
+ this.addPoint(point, false);
+ }, this);
- // Update parallel arrays
- each(this.parallelArrays, function(key) {
- series[key + 'Data'].length = 0;
- });
+ return true;
+ },
- // In turbo mode, only one- or twodimensional arrays of numbers are
- // allowed. The first value is tested, and we assume that all the
- // rest are defined the same way. Although the 'for' loops are
- // similar, they are repeated inside each if-else conditional for
- // max performance.
- if (turboThreshold && dataLength > turboThreshold) {
+ /**
+ * Apply a new set of data to the series and optionally redraw it. The
+ * new data array is passed by reference (except in case of
+ * `updatePoints`), and may later be mutated when updating the chart
+ * data.
+ *
+ * Note the difference in behaviour when setting the same amount of
+ * points, or a different amount of points, as handled by the
+ * `updatePoints` parameter.
+ *
+ * @sample highcharts/members/series-setdata/
+ * Set new data from a button
+ * @sample highcharts/members/series-setdata-pie/
+ * Set data in a pie
+ * @sample stock/members/series-setdata/
+ * Set new data in Highstock
+ * @sample maps/members/series-setdata/
+ * Set new data in Highmaps
+ *
+ * @function Highcharts.Series#setData
+ *
+ * @param {Array<*>} data
+ * Takes an array of data in the same format as described under
+ * `series.{type}.data` for the given series type, for example a
+ * line series would take data in the form described under
+ * [series.line.data](https://api.highcharts.com/highcharts/series.line.data).
+ *
+ * @param {boolean} [redraw=true]
+ * Whether to redraw the chart after the series is altered. If
+ * doing more operations on the chart, it is a good idea to set
+ * redraw to false and call {@link Chart#redraw} after.
+ *
+ * @param {Highcharts.AnimationOptionsObject} [animation]
+ * When the updated data is the same length as the existing data,
+ * points will be updated by default, and animation visualizes
+ * how the points are changed. Set false to disable animation, or
+ * a configuration object to set duration or easing.
+ *
+ * @param {boolean} [updatePoints=true]
+ * When this is true, points will be updated instead of replaced
+ * whenever possible. This occurs a) when the updated data is the
+ * same length as the existing data, b) when points are matched
+ * by their id's, or c) when points can be matched by X values.
+ * This allows updating with animation and performs better. In
+ * this case, the original array is not passed by reference. Set
+ * `false` to prevent.
+ */
+ setData: function (data, redraw, animation, updatePoints) {
+ var series = this,
+ oldData = series.points,
+ oldDataLength = (oldData && oldData.length) || 0,
+ dataLength,
+ options = series.options,
+ chart = series.chart,
+ firstPoint = null,
+ xAxis = series.xAxis,
+ i,
+ turboThreshold = options.turboThreshold,
+ pt,
+ xData = this.xData,
+ yData = this.yData,
+ pointArrayMap = series.pointArrayMap,
+ valueCount = pointArrayMap && pointArrayMap.length,
+ keys = options.keys,
+ indexOfX = 0,
+ indexOfY = 1,
+ updatedData;
+
+ data = data || [];
+ dataLength = data.length;
+ redraw = pick(redraw, true);
+
+ // If the point count is the same as is was, just run Point.update
+ // which is cheaper, allows animation, and keeps references to
+ // points.
+ if (
+ updatePoints !== false &&
+ dataLength &&
+ oldDataLength &&
+ !series.cropped &&
+ !series.hasGroupedData &&
+ series.visible &&
+ // Soft updating has no benefit in boost, and causes JS error
+ // (#8355)
+ !series.isSeriesBoosting
+ ) {
+ updatedData = this.updateData(data);
+ }
- // find the first non-null point
- i = 0;
- while (firstPoint === null && i < dataLength) {
- firstPoint = data[i];
- i++;
- }
+ if (!updatedData) {
+ // Reset properties
+ series.xIncrement = null;
- if (isNumber(firstPoint)) { // assume all points are numbers
- for (i = 0; i < dataLength; i++) {
- xData[i] = this.autoIncrement();
- yData[i] = data[i];
+ series.colorCounter = 0; // for series with colorByPoint (#1547)
+
+ // Update parallel arrays
+ this.parallelArrays.forEach(function (key) {
+ series[key + 'Data'].length = 0;
+ });
+
+ // In turbo mode, only one- or twodimensional arrays of numbers
+ // are allowed. The first value is tested, and we assume that
+ // all the rest are defined the same way. Although the 'for'
+ // loops are similar, they are repeated inside each if-else
+ // conditional for max performance.
+ if (turboThreshold && dataLength > turboThreshold) {
+
+ // find the first non-null point
+ i = 0;
+ while (firstPoint === null && i < dataLength) {
+ firstPoint = data[i];
+ i++;
}
- // Assume all points are arrays when first point is
- } else if (isArray(firstPoint)) {
- if (valueCount) { // [x, low, high] or [x, o, h, l, c]
+
+ if (isNumber(firstPoint)) { // assume all points are numbers
for (i = 0; i < dataLength; i++) {
- pt = data[i];
- xData[i] = pt[0];
- yData[i] = pt.slice(1, valueCount + 1);
+ xData[i] = this.autoIncrement();
+ yData[i] = data[i];
}
- } else { // [x, y]
- for (i = 0; i < dataLength; i++) {
- pt = data[i];
- xData[i] = pt[0];
- yData[i] = pt[1];
+
+ // Assume all points are arrays when first point is
+ } else if (isArray(firstPoint)) {
+ if (valueCount) { // [x, low, high] or [x, o, h, l, c]
+ for (i = 0; i < dataLength; i++) {
+ pt = data[i];
+ xData[i] = pt[0];
+ yData[i] = pt.slice(1, valueCount + 1);
+ }
+ } else { // [x, y]
+ if (keys) {
+ indexOfX = keys.indexOf('x');
+ indexOfY = keys.indexOf('y');
+
+ indexOfX = indexOfX >= 0 ? indexOfX : 0;
+ indexOfY = indexOfY >= 0 ? indexOfY : 1;
+ }
+
+ for (i = 0; i < dataLength; i++) {
+ pt = data[i];
+ xData[i] = pt[indexOfX];
+ yData[i] = pt[indexOfY];
+ }
}
+ } else {
+ // Highcharts expects configs to be numbers or arrays in
+ // turbo mode
+ H.error(12, false, chart);
}
} else {
- // Highcharts expects configs to be numbers or arrays in
- // turbo mode
- H.error(12);
- }
- } else {
- for (i = 0; i < dataLength; i++) {
- if (data[i] !== undefined) { // stray commas in oldIE
- pt = {
- series: series
- };
- series.pointClass.prototype.applyOptions.apply(
- pt, [data[i]]
- );
- series.updateParallelArrays(pt, i);
+ for (i = 0; i < dataLength; i++) {
+ if (data[i] !== undefined) { // stray commas in oldIE
+ pt = { series: series };
+ series.pointClass.prototype.applyOptions.apply(
+ pt,
+ [data[i]]
+ );
+ series.updateParallelArrays(pt, i);
+ }
}
}
- }
-
- // Forgetting to cast strings to numbers is a common caveat when
- // handling CSV or JSON
- if (yData && isString(yData[0])) {
- H.error(14, true);
- }
-
- series.data = [];
- series.options.data = series.userOptions.data = data;
- // destroy old points
- i = oldDataLength;
- while (i--) {
- if (oldData[i] && oldData[i].destroy) {
- oldData[i].destroy();
+ // Forgetting to cast strings to numbers is a common caveat when
+ // handling CSV or JSON
+ if (yData && isString(yData[0])) {
+ H.error(14, true, chart);
}
- }
-
- // reset minRange (#878)
- if (xAxis) {
- xAxis.minRange = xAxis.userMinRange;
- }
- // redraw
- series.isDirty = chart.isDirtyBox = true;
- series.isDirtyData = !!oldData;
- animation = false;
- }
+ series.data = [];
+ series.options.data = series.userOptions.data = data;
- // Typically for pie series, points need to be processed and generated
- // prior to rendering the legend
- if (options.legendType === 'point') {
- this.processData();
- this.generatePoints();
- }
+ // destroy old points
+ i = oldDataLength;
+ while (i--) {
+ if (oldData[i] && oldData[i].destroy) {
+ oldData[i].destroy();
+ }
+ }
- if (redraw) {
- chart.redraw(animation);
- }
- },
+ // reset minRange (#878)
+ if (xAxis) {
+ xAxis.minRange = xAxis.userMinRange;
+ }
- /**
- * Internal function to process the data by cropping away unused data points
- * if the series is longer than the crop threshold. This saves computing
- * time for large series. In Highstock, this function is extended to
- * provide data grouping.
- *
- * @private
- * @param {Boolean} force
- * Force data grouping.
- */
- processData: function(force) {
- var series = this,
- processedXData = series.xData, // copied during slice operation
- processedYData = series.yData,
- dataLength = processedXData.length,
- croppedData,
- cropStart = 0,
- cropped,
- distance,
- closestPointRange,
- xAxis = series.xAxis,
- i, // loop variable
- options = series.options,
- cropThreshold = options.cropThreshold,
- getExtremesFromAll =
- series.getExtremesFromAll ||
- options.getExtremesFromAll, // #4599
- isCartesian = series.isCartesian,
- xExtremes,
- val2lin = xAxis && xAxis.val2lin,
- isLog = xAxis && xAxis.isLog,
- throwOnUnsorted = series.requireSorting,
- min,
- max;
+ // redraw
+ series.isDirty = chart.isDirtyBox = true;
+ series.isDirtyData = !!oldData;
+ animation = false;
+ }
- // 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.
- if (
- isCartesian &&
- !series.isDirty &&
- !xAxis.isDirty &&
- !series.yAxis.isDirty &&
- !force
- ) {
- return false;
- }
+ // Typically for pie series, points need to be processed and
+ // generated prior to rendering the legend
+ if (options.legendType === 'point') {
+ this.processData();
+ this.generatePoints();
+ }
- if (xAxis) {
- xExtremes = xAxis.getExtremes(); // corrected for log axis (#3053)
- min = xExtremes.min;
- max = xExtremes.max;
- }
+ if (redraw) {
+ chart.redraw(animation);
+ }
+ },
- // optionally filter out points outside the plot area
- if (
- isCartesian &&
- series.sorted &&
- !getExtremesFromAll &&
- (!cropThreshold || dataLength > cropThreshold || series.forceCrop)
- ) {
+ /**
+ * Internal function to process the data by cropping away unused data
+ * points if the series is longer than the crop threshold. This saves
+ * computing time for large series. In Highstock, this function is
+ * extended to provide data grouping.
+ *
+ * @private
+ * @function Highcharts.Series#processData
+ *
+ * @param {boolean} force
+ * Force data grouping.
+ *
+ * @return {boolean|undefined}
+ */
+ processData: function (force) {
+ var series = this,
+ processedXData = series.xData, // copied during slice operation
+ processedYData = series.yData,
+ dataLength = processedXData.length,
+ croppedData,
+ cropStart = 0,
+ cropped,
+ distance,
+ closestPointRange,
+ xAxis = series.xAxis,
+ i, // loop variable
+ options = series.options,
+ cropThreshold = options.cropThreshold,
+ getExtremesFromAll =
+ series.getExtremesFromAll ||
+ options.getExtremesFromAll, // #4599
+ isCartesian = series.isCartesian,
+ xExtremes,
+ val2lin = xAxis && xAxis.val2lin,
+ isLog = xAxis && xAxis.isLog,
+ throwOnUnsorted = series.requireSorting,
+ min,
+ max;
- // it's outside current extremes
+ // 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.
if (
- processedXData[dataLength - 1] < min ||
- processedXData[0] > max
- ) {
- processedXData = [];
- processedYData = [];
-
- // only crop if it's actually spilling out
- } else if (
- processedXData[0] < min ||
- processedXData[dataLength - 1] > max
+ isCartesian &&
+ !series.isDirty &&
+ !xAxis.isDirty &&
+ !series.yAxis.isDirty &&
+ !force
) {
- croppedData = this.cropData(
- series.xData,
- series.yData,
- min,
- max
- );
- processedXData = croppedData.xData;
- processedYData = croppedData.yData;
- cropStart = croppedData.start;
- cropped = true;
+ return false;
}
- }
-
- // Find the closest distance between processed points
- i = processedXData.length || 1;
- while (--i) {
- distance = isLog ?
- val2lin(processedXData[i]) - val2lin(processedXData[i - 1]) :
- processedXData[i] - processedXData[i - 1];
+ if (xAxis) {
+ // corrected for log axis (#3053)
+ xExtremes = xAxis.getExtremes();
+ min = xExtremes.min;
+ max = xExtremes.max;
+ }
+ // optionally filter out points outside the plot area
if (
- distance > 0 &&
+ isCartesian &&
+ series.sorted &&
+ !getExtremesFromAll &&
(
- closestPointRange === undefined ||
- distance < closestPointRange
+ !cropThreshold ||
+ dataLength > cropThreshold ||
+ series.forceCrop
)
) {
- closestPointRange = distance;
- // Unsorted data is not supported by the line tooltip, as well as
- // data grouping and navigation in Stock charts (#725) and width
- // calculation of columns (#1900)
- } else if (distance < 0 && throwOnUnsorted) {
- H.error(15);
- throwOnUnsorted = false; // Only once
+ // it's outside current extremes
+ if (
+ processedXData[dataLength - 1] < min ||
+ processedXData[0] > max
+ ) {
+ processedXData = [];
+ processedYData = [];
+
+ // only crop if it's actually spilling out
+ } else if (
+ series.yData && (
+ processedXData[0] < min ||
+ processedXData[dataLength - 1] > max
+ )
+ ) {
+ croppedData = this.cropData(
+ series.xData,
+ series.yData,
+ min,
+ max
+ );
+ processedXData = croppedData.xData;
+ processedYData = croppedData.yData;
+ cropStart = croppedData.start;
+ cropped = true;
+ }
+ }
+
+
+ // Find the closest distance between processed points
+ i = processedXData.length || 1;
+ while (--i) {
+ distance = (
+ isLog ?
+ (
+ val2lin(processedXData[i]) -
+ val2lin(processedXData[i - 1])
+ ) :
+ processedXData[i] - processedXData[i - 1]
+ );
+
+ if (
+ distance > 0 &&
+ (
+ closestPointRange === undefined ||
+ distance < closestPointRange
+ )
+ ) {
+ closestPointRange = distance;
+
+ // Unsorted data is not supported by the line tooltip, as well
+ // as data grouping and navigation in Stock charts (#725) and
+ // width calculation of columns (#1900)
+ } else if (distance < 0 && throwOnUnsorted) {
+ H.error(15, false, series.chart);
+ throwOnUnsorted = false; // Only once
+ }
}
- }
- // Record the properties
- series.cropped = cropped; // undefined or true
- series.cropStart = cropStart;
- series.processedXData = processedXData;
- series.processedYData = processedYData;
+ // Record the properties
+ series.cropped = cropped; // undefined or true
+ series.cropStart = cropStart;
+ series.processedXData = processedXData;
+ series.processedYData = processedYData;
- series.closestPointRange = closestPointRange;
+ series.closestPointRange = closestPointRange;
- },
+ },
+
+ /**
+ * Iterate over xData and crop values between min and max. Returns
+ * object containing crop start/end cropped xData with corresponding
+ * part of yData, dataMin and dataMax within the cropped range.
+ *
+ * @private
+ * @function Highcharts.Series#cropData
+ *
+ * @param {Array} xData
+ *
+ * @param {Array} yData
+ *
+ * @param {number} min
+ *
+ * @param {number} max
+ *
+ * @param {number} [cropShoulder]
+ *
+ * @return {*}
+ */
+ cropData: function (xData, yData, min, max, cropShoulder) {
+ var dataLength = xData.length,
+ cropStart = 0,
+ cropEnd = dataLength,
+ i,
+ j;
- /**
- * Iterate over xData and crop values between min and max. Returns object
- * containing crop start/end cropped xData with corresponding part of yData,
- * dataMin and dataMax within the cropped range.
- *
- * @private
- */
- cropData: function(xData, yData, min, max) {
- var dataLength = xData.length,
- cropStart = 0,
- cropEnd = dataLength,
// line-type series need one point outside
- cropShoulder = pick(this.cropShoulder, 1),
- i,
- j;
+ cropShoulder = pick(cropShoulder, this.cropShoulder);
- // iterate up to find slice start
- for (i = 0; i < dataLength; i++) {
- if (xData[i] >= min) {
- cropStart = Math.max(0, i - cropShoulder);
- break;
+ // iterate up to find slice start
+ for (i = 0; i < dataLength; i++) {
+ if (xData[i] >= min) {
+ cropStart = Math.max(0, i - cropShoulder);
+ break;
+ }
}
- }
- // proceed to find slice end
- for (j = i; j < dataLength; j++) {
- if (xData[j] > max) {
- cropEnd = j + cropShoulder;
- break;
+ // proceed to find slice end
+ for (j = i; j < dataLength; j++) {
+ if (xData[j] > max) {
+ cropEnd = j + cropShoulder;
+ break;
+ }
}
- }
- return {
- xData: xData.slice(cropStart, cropEnd),
- yData: yData.slice(cropStart, cropEnd),
- start: cropStart,
- end: cropEnd
- };
- },
+ return {
+ xData: xData.slice(cropStart, cropEnd),
+ yData: yData.slice(cropStart, cropEnd),
+ start: cropStart,
+ end: cropEnd
+ };
+ },
- /**
- * Generate the data point after the data has been processed by cropping
- * away unused points and optionally grouped in Highcharts Stock.
- *
- * @private
- */
- generatePoints: function() {
- var series = this,
- options = series.options,
- dataOptions = options.data,
- data = series.data,
- dataLength,
- processedXData = series.processedXData,
- processedYData = series.processedYData,
- PointClass = series.pointClass,
- processedDataLength = processedXData.length,
- cropStart = series.cropStart || 0,
- cursor,
- hasGroupedData = series.hasGroupedData,
- keys = options.keys,
- point,
- points = [],
- i;
+ /**
+ * Generate the data point after the data has been processed by cropping
+ * away unused points and optionally grouped in Highcharts Stock.
+ *
+ * @private
+ * @function Highcharts.Series#generatePoints
+ */
+ generatePoints: function () {
+ var series = this,
+ options = series.options,
+ dataOptions = options.data,
+ data = series.data,
+ dataLength,
+ processedXData = series.processedXData,
+ processedYData = series.processedYData,
+ PointClass = series.pointClass,
+ processedDataLength = processedXData.length,
+ cropStart = series.cropStart || 0,
+ cursor,
+ hasGroupedData = series.hasGroupedData,
+ keys = options.keys,
+ point,
+ points = [],
+ i;
- if (!data && !hasGroupedData) {
- var arr = [];
- arr.length = dataOptions.length;
- data = series.data = arr;
- }
+ if (!data && !hasGroupedData) {
+ var arr = [];
- if (keys && hasGroupedData) {
- // grouped data has already applied keys (#6590)
- series.options.keys = false;
- }
+ arr.length = dataOptions.length;
+ data = series.data = arr;
+ }
- for (i = 0; i < processedDataLength; i++) {
- cursor = cropStart + i;
- if (!hasGroupedData) {
- point = data[cursor];
- if (!point && dataOptions[cursor] !== undefined) { // #970
- data[cursor] = point = (new PointClass()).init(
+ if (keys && hasGroupedData) {
+ // grouped data has already applied keys (#6590)
+ series.options.keys = false;
+ }
+
+ for (i = 0; i < processedDataLength; i++) {
+ cursor = cropStart + i;
+ if (!hasGroupedData) {
+ point = data[cursor];
+ if (!point && dataOptions[cursor] !== undefined) { // #970
+ data[cursor] = point = (new PointClass()).init(
+ series,
+ dataOptions[cursor],
+ processedXData[i]
+ );
+ }
+ } else {
+ // splat the y data in case of ohlc data array
+ point = (new PointClass()).init(
series,
- dataOptions[cursor],
- processedXData[i]
+ [processedXData[i]].concat(splat(processedYData[i]))
);
- }
- } else {
- // splat the y data in case of ohlc data array
- point = (new PointClass()).init(
- series, [processedXData[i]].concat(splat(processedYData[i]))
- );
- /**
- * Highstock only. If a point object is created by data
- * grouping, it doesn't reflect actual points in the raw data.
- * In this case, the `dataGroup` property holds information
- * that points back to the raw data.
- *
- * - `dataGroup.start` is the index of the first raw data point
- * in the group.
- * - `dataGroup.length` is the amount of points in the group.
- *
- * @name dataGroup
- * @memberOf Point
- * @type {Object}
- *
- */
- point.dataGroup = series.groupMap[i];
- }
- if (point) { // #6279
- point.index = cursor; // For faster access in Point.update
- points[i] = point;
+ /**
+ * Highstock only. If a point object is created by data
+ * grouping, it doesn't reflect actual points in the raw
+ * data. In this case, the `dataGroup` property holds
+ * information that points back to the raw data.
+ *
+ * - `dataGroup.start` is the index of the first raw data
+ * point in the group.
+ *
+ * - `dataGroup.length` is the amount of points in the
+ * group.
+ *
+ * @product highstock
+ *
+ * @name Highcharts.Point#dataGroup
+ * @type {Highcharts.SVGElement|undefined}
+ */
+ point.dataGroup = series.groupMap[i];
+ if (point.dataGroup.options) {
+ point.options = point.dataGroup.options;
+ extend(point, point.dataGroup.options);
+ // Collision of props and options (#9770)
+ delete point.dataLabels;
+ }
+ }
+ if (point) { // #6279
+ point.index = cursor; // For faster access in Point.update
+ points[i] = point;
+ }
}
- }
- // restore keys options (#6590)
- series.options.keys = keys;
+ // restore keys options (#6590)
+ series.options.keys = keys;
- // Hide cropped-away points - this only runs when the number of points
- // is above cropThreshold, or when swithching view from non-grouped
- // data to grouped data (#637)
- if (
- data &&
- (
- processedDataLength !== (dataLength = data.length) ||
- hasGroupedData
- )
- ) {
- for (i = 0; i < dataLength; i++) {
- // when has grouped data, clear all points
- if (i === cropStart && !hasGroupedData) {
- i += processedDataLength;
- }
- if (data[i]) {
- data[i].destroyElements();
- data[i].plotX = undefined; // #1003
+ // Hide cropped-away points - this only runs when the number of
+ // points is above cropThreshold, or when swithching view from
+ // non-grouped data to grouped data (#637)
+ if (
+ data &&
+ (
+ processedDataLength !== (dataLength = data.length) ||
+ hasGroupedData
+ )
+ ) {
+ for (i = 0; i < dataLength; i++) {
+ // when has grouped data, clear all points
+ if (i === cropStart && !hasGroupedData) {
+ i += processedDataLength;
+ }
+ if (data[i]) {
+ data[i].destroyElements();
+ data[i].plotX = undefined; // #1003
+ }
}
}
- }
+
+ /**
+ * Read only. An array containing those values converted to points.
+ * In case the series data length exceeds the `cropThreshold`, or if
+ * the data is grouped, `series.data` doesn't contain all the
+ * points. Also, in case a series is hidden, the `data` array may be
+ * empty. To access raw values, `series.options.data` will always be
+ * up to date. `Series.data` only contains the points that have been
+ * created on demand. To modify the data, use
+ * {@link Highcharts.Series#setData} or
+ * {@link Highcharts.Point#update}.
+ *
+ * @see Series.points
+ *
+ * @name Highcharts.Series#data
+ * @type {Array}
+ */
+ series.data = data;
+
+ /**
+ * An array containing all currently visible point objects. In case
+ * of cropping, the cropped-away points are not part of this array.
+ * The `series.points` array starts at `series.cropStart` compared
+ * to `series.data` and `series.options.data`. If however the series
+ * data is grouped, these can't be correlated one to one. To modify
+ * the data, use {@link Highcharts.Series#setData} or
+ * {@link Highcharts.Point#update}.
+ *
+ * @name Highcharts.Series#points
+ * @type {Array}
+ */
+ series.points = points;
+
+ fireEvent(this, 'afterGeneratePoints');
+ },
/**
- * Read only. An array containing those values converted to points, but
- * in case the series data length exceeds the `cropThreshold`, or if the
- * data is grouped, `series.data` doesn't contain all the points. It
- * only contains the points that have been created on demand. To
- * modify the data, use {@link Highcharts.Series#setData} or {@link
- * Highcharts.Point#update}.
+ * Calculate Y extremes for the visible data. The result is set as
+ * `dataMin` and `dataMax` on the Series item.
*
- * @name data
- * @memberOf Highcharts.Series
- * @see Series.points
- * @type {Array.}
- */
- series.data = data;
+ * @function Highcharts.Series#getExtremes
+ *
+ * @param {Array} [yData]
+ * The data to inspect. Defaults to the current data within the
+ * visible range.
+ */
+ getExtremes: function (yData) {
+ var xAxis = this.xAxis,
+ yAxis = this.yAxis,
+ xData = this.processedXData,
+ yDataLength,
+ activeYData = [],
+ activeCounter = 0,
+ // #2117, need to compensate for log X axis
+ xExtremes = xAxis.getExtremes(),
+ xMin = xExtremes.min,
+ xMax = xExtremes.max,
+ validValue,
+ withinRange,
+ // Handle X outside the viewed area. This does not work with
+ // non-sorted data like scatter (#7639).
+ shoulder = this.requireSorting ? this.cropShoulder : 0,
+ x,
+ y,
+ i,
+ j;
- /**
- * An array containing all currently visible point objects. In case of
- * cropping, the cropped-away points are not part of this array. The
- * `series.points` array starts at `series.cropStart` compared to
- * `series.data` and `series.options.data`. If however the series data
- * is grouped, these can't be correlated one to one. To
- * modify the data, use {@link Highcharts.Series#setData} or {@link
- * Highcharts.Point#update}.
- * @name points
- * @memberof Series
- * @type {Array.}
- */
- series.points = points;
- },
+ yData = yData || this.stackedYData || this.processedYData || [];
+ yDataLength = yData.length;
- /**
- * Calculate Y extremes for the visible data. The result is set as
- * `dataMin` and `dataMax` on the Series item.
- *
- * @param {Array.} [yData]
- * The data to inspect. Defaults to the current data within the
- * visible range.
- *
- */
- getExtremes: function(yData) {
- var xAxis = this.xAxis,
- yAxis = this.yAxis,
- xData = this.processedXData,
- yDataLength,
- activeYData = [],
- activeCounter = 0,
- // #2117, need to compensate for log X axis
- xExtremes = xAxis.getExtremes(),
- xMin = xExtremes.min,
- xMax = xExtremes.max,
- validValue,
- withinRange,
- x,
- y,
- i,
- j;
-
- yData = yData || this.stackedYData || this.processedYData || [];
- yDataLength = yData.length;
-
- for (i = 0; i < yDataLength; i++) {
-
- x = xData[i];
- y = yData[i];
-
- // For points within the visible range, including the first point
- // outside the visible range (#7061), consider y extremes.
- validValue =
- (isNumber(y, true) || isArray(y)) &&
- (!yAxis.positiveValuesOnly || (y.length || y > 0));
- withinRange =
- this.getExtremesFromAll ||
- this.options.getExtremesFromAll ||
- this.cropped ||
- ((xData[i + 1] || x) >= xMin && (xData[i - 1] || x) <= xMax);
-
- if (validValue && withinRange) {
-
- j = y.length;
- if (j) { // array, like ohlc or range data
- while (j--) {
- if (y[j] !== null) {
- activeYData[activeCounter++] = y[j];
+ for (i = 0; i < yDataLength; i++) {
+
+ x = xData[i];
+ y = yData[i];
+
+ // For points within the visible range, including the first
+ // point outside the visible range (#7061), consider y extremes.
+ validValue = (
+ (isNumber(y, true) || isArray(y)) &&
+ (!yAxis.positiveValuesOnly || (y.length || y > 0))
+ );
+ withinRange = (
+ this.getExtremesFromAll ||
+ this.options.getExtremesFromAll ||
+ this.cropped ||
+ (
+ (xData[i + shoulder] || x) >= xMin &&
+ (xData[i - shoulder] || x) <= xMax
+ )
+ );
+
+ if (validValue && withinRange) {
+
+ j = y.length;
+ if (j) { // array, like ohlc or range data
+ while (j--) {
+ if (typeof y[j] === 'number') { // #7380
+ activeYData[activeCounter++] = y[j];
+ }
}
+ } else {
+ activeYData[activeCounter++] = y;
}
- } else {
- activeYData[activeCounter++] = y;
}
}
- }
- this.dataMin = arrayMin(activeYData);
- this.dataMax = arrayMax(activeYData);
- },
+ this.dataMin = arrayMin(activeYData);
+ this.dataMax = arrayMax(activeYData);
- /**
- * Translate data points from raw data values to chart specific positioning
- * data needed later in the `drawPoints` and `drawGraph` functions. This
- * function can be overridden in plugins and custom series type
- * implementations.
- */
- translate: function() {
- if (!this.processedXData) { // hidden series
- this.processData();
- }
- this.generatePoints();
- var series = this,
- options = series.options,
- stacking = options.stacking,
- xAxis = series.xAxis,
- categories = xAxis.categories,
- yAxis = series.yAxis,
- points = series.points,
- dataLength = points.length,
- hasModifyValue = !!series.modifyValue,
- i,
- pointPlacement = options.pointPlacement,
- dynamicallyPlaced =
- pointPlacement === 'between' ||
- isNumber(pointPlacement),
- threshold = options.threshold,
- stackThreshold = options.startFromThreshold ? threshold : 0,
- plotX,
- plotY,
- lastPlotX,
- stackIndicator,
- closestPointRangePx = Number.MAX_VALUE;
-
- // Point placement is relative to each series pointRange (#5889)
- if (pointPlacement === 'between') {
- pointPlacement = 0.5;
- }
- if (isNumber(pointPlacement)) {
- pointPlacement *= pick(options.pointRange || xAxis.pointRange);
- }
-
- // Translate each point
- for (i = 0; i < dataLength; i++) {
- var point = points[i],
- xValue = point.x,
- yValue = point.y,
- yBottom = point.low,
- stack = stacking && yAxis.stacks[(
- series.negStacks &&
- yValue < (stackThreshold ? 0 : threshold) ? '-' : ''
- ) + series.stackKey],
- pointStack,
- stackValues;
-
- // Discard disallowed y values for log axes (#3434)
- if (yAxis.positiveValuesOnly && yValue !== null && yValue <= 0) {
- point.isNull = true;
- }
-
- // Get the plotX translation
- point.plotX = plotX = correctFloat( // #5236
- Math.min(Math.max(-1e5, xAxis.translate(
- xValue,
- 0,
- 0,
- 0,
- 1,
- pointPlacement,
- this.type === 'flags'
- )), 1e5) // #3923
- );
+ fireEvent(this, 'afterGetExtremes');
+ },
- // Calculate the bottom y value for stacked series
- if (
- stacking &&
- series.visible &&
- !point.isNull &&
- stack &&
- stack[xValue]
- ) {
- stackIndicator = series.getStackIndicator(
- stackIndicator,
- xValue,
- series.index
+ /**
+ * Translate data points from raw data values to chart specific
+ * positioning data needed later in the `drawPoints` and `drawGraph`
+ * functions. This function can be overridden in plugins and custom
+ * series type implementations.
+ *
+ * @function Highcharts.Series#translate
+ *
+ * @fires Highcharts.Series#events:translate
+ */
+ translate: function () {
+ if (!this.processedXData) { // hidden series
+ this.processData();
+ }
+ this.generatePoints();
+ var series = this,
+ options = series.options,
+ stacking = options.stacking,
+ xAxis = series.xAxis,
+ categories = xAxis.categories,
+ yAxis = series.yAxis,
+ points = series.points,
+ dataLength = points.length,
+ hasModifyValue = !!series.modifyValue,
+ i,
+ pointPlacement = series.pointPlacementToXValue(), // #7860
+ dynamicallyPlaced = isNumber(pointPlacement),
+ threshold = options.threshold,
+ stackThreshold = options.startFromThreshold ? threshold : 0,
+ plotX,
+ plotY,
+ lastPlotX,
+ stackIndicator,
+ zoneAxis = this.zoneAxis || 'y',
+ closestPointRangePx = Number.MAX_VALUE;
+
+ // Plotted coordinates need to be within a limited range. Drawing
+ // too far outside the viewport causes various rendering issues
+ // (#3201, #3923, #7555).
+ function limitedRange(val) {
+ return Math.min(Math.max(-1e5, val), 1e5);
+ }
+
+ // Translate each point
+ for (i = 0; i < dataLength; i++) {
+ var point = points[i],
+ xValue = point.x,
+ yValue = point.y,
+ yBottom = point.low,
+ stack = stacking && yAxis.stacks[(
+ series.negStacks &&
+ yValue < (stackThreshold ? 0 : threshold) ? '-' : ''
+ ) + series.stackKey],
+ pointStack,
+ stackValues;
+
+ // Discard disallowed y values for log axes (#3434)
+ if (yAxis.positiveValuesOnly &&
+ yValue !== null &&
+ yValue <= 0
+ ) {
+ point.isNull = true;
+ }
+
+ // Get the plotX translation
+ point.plotX = plotX = correctFloat( // #5236
+ limitedRange(xAxis.translate( // #3923
+ xValue,
+ 0,
+ 0,
+ 0,
+ 1,
+ pointPlacement,
+ this.type === 'flags'
+ )) // #3923
);
- pointStack = stack[xValue];
- stackValues = pointStack.points[stackIndicator.key];
- yBottom = stackValues[0];
- yValue = stackValues[1];
+ // Calculate the bottom y value for stacked series
if (
- yBottom === stackThreshold &&
- stackIndicator.key === stack[xValue].base
+ stacking &&
+ series.visible &&
+ !point.isNull &&
+ stack &&
+ stack[xValue]
) {
- yBottom = pick(threshold, yAxis.min);
- }
- if (yAxis.positiveValuesOnly && yBottom <= 0) { // #1200, #1232
- yBottom = null;
- }
+ stackIndicator = series.getStackIndicator(
+ stackIndicator,
+ xValue,
+ series.index
+ );
+ pointStack = stack[xValue];
+ stackValues = pointStack.points[stackIndicator.key];
+ yBottom = stackValues[0];
+ yValue = stackValues[1];
- point.total = point.stackTotal = pointStack.total;
- point.percentage =
- pointStack.total &&
- (point.y / pointStack.total * 100);
- point.stackY = yValue;
-
- // Place the stack label
- pointStack.setOffset(
- series.pointXOffset || 0,
- series.barW || 0
- );
+ if (
+ yBottom === stackThreshold &&
+ stackIndicator.key === stack[xValue].base
+ ) {
+ yBottom = (
+ pick(isNumber(threshold) && threshold, yAxis.min)
+ );
+ }
- }
+ // #1200, #1232
+ if (yAxis.positiveValuesOnly && yBottom <= 0) {
+ yBottom = null;
+ }
- // Set translated yBottom or remove it
- point.yBottom = defined(yBottom) ?
- yAxis.translate(yBottom, 0, 1, 0, 1) :
- null;
+ point.total = point.stackTotal = pointStack.total;
+ point.percentage =
+ pointStack.total &&
+ (point.y / pointStack.total * 100);
+ point.stackY = yValue;
- // general hook, used for Highstock compare mode
- if (hasModifyValue) {
- yValue = series.modifyValue(yValue, point);
- }
+ // Place the stack label
+ pointStack.setOffset(
+ series.pointXOffset || 0,
+ series.barW || 0
+ );
- // Set the the plotY value, reset it for redraws
- point.plotY = plotY =
- (typeof yValue === 'number' && yValue !== Infinity) ?
- Math.min(Math.max(-1e5,
- yAxis.translate(yValue, 0, 1, 0, 1)), 1e5) : // #3201
- undefined;
+ }
- point.isInside =
- plotY !== undefined &&
- plotY >= 0 &&
- plotY <= yAxis.len && // #3519
- plotX >= 0 &&
- plotX <= xAxis.len;
+ // Set translated yBottom or remove it
+ point.yBottom = defined(yBottom) ?
+ limitedRange(yAxis.translate(yBottom, 0, 1, 0, 1)) :
+ null;
+ // general hook, used for Highstock compare mode
+ if (hasModifyValue) {
+ yValue = series.modifyValue(yValue, point);
+ }
- // Set client related positions for mouse tracking
- point.clientX = dynamicallyPlaced ?
- correctFloat(
- xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement)
- ) :
- plotX; // #1514, #5383, #5518
+ // Set the the plotY value, reset it for redraws
+ // #3201
+ point.plotY = plotY = (
+ (typeof yValue === 'number' && yValue !== Infinity) ?
+ limitedRange(yAxis.translate(yValue, 0, 1, 0, 1)) :
+ undefined
+ );
- point.negative = point.y < (threshold || 0);
+ point.isInside =
+ plotY !== undefined &&
+ plotY >= 0 &&
+ plotY <= yAxis.len && // #3519
+ plotX >= 0 &&
+ plotX <= xAxis.len;
- // some API data
- point.category = categories && categories[point.x] !== undefined ?
- categories[point.x] : point.x;
- // Determine auto enabling of markers (#3635, #5099)
- if (!point.isNull) {
- if (lastPlotX !== undefined) {
- closestPointRangePx = Math.min(
- closestPointRangePx,
- Math.abs(plotX - lastPlotX)
- );
- }
- lastPlotX = plotX;
- }
+ // Set client related positions for mouse tracking
+ point.clientX = dynamicallyPlaced ?
+ correctFloat(
+ xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement)
+ ) :
+ plotX; // #1514, #5383, #5518
- // Find point zone
- point.zone = this.zones.length && point.getZone();
- }
- series.closestPointRangePx = closestPointRangePx;
- },
+ // Negative points. For bubble charts, this means negative z
+ // values (#9728)
+ point.negative = point[zoneAxis] < (
+ options[zoneAxis + 'Threshold'] ||
+ threshold ||
+ 0
+ );
- /**
- * Return the series points with null points filtered out.
- *
- * @param {Array.} [points]
- * The points to inspect, defaults to {@link Series.points}.
- * @param {Boolean} [insideOnly=false]
- * Whether to inspect only the points that are inside the visible
- * view.
- *
- * @return {Array.}
- * The valid points.
- */
- getValidPoints: function(points, insideOnly) {
- var chart = this.chart;
- // #3916, #5029, #5085
- return grep(points || this.points || [], function isValidPoint(point) {
- if (insideOnly && !chart.isInsidePlot(
- point.plotX,
- point.plotY,
- chart.inverted
- )) {
- return false;
+ // some API data
+ point.category = (
+ categories &&
+ categories[point.x] !== undefined ?
+ categories[point.x] :
+ point.x
+ );
+
+ // Determine auto enabling of markers (#3635, #5099)
+ if (!point.isNull) {
+ if (lastPlotX !== undefined) {
+ closestPointRangePx = Math.min(
+ closestPointRangePx,
+ Math.abs(plotX - lastPlotX)
+ );
+ }
+ lastPlotX = plotX;
+ }
+
+ // Find point zone
+ point.zone = this.zones.length && point.getZone();
}
- return !point.isNull;
- });
- },
+ series.closestPointRangePx = closestPointRangePx;
- /**
- * Set the clipping for the series. For animated series it is called twice,
- * first to initiate animating the clip then the second time without the
- * animation to set the final clip.
- *
- * @private
- */
- setClip: function(animation) {
- var chart = this.chart,
- options = this.options,
- renderer = chart.renderer,
- inverted = chart.inverted,
- seriesClipBox = this.clipBox,
- clipBox = seriesClipBox || chart.clipBox,
- sharedClipKey =
- this.sharedClipKey || [
- '_sharedClip',
- animation && animation.duration,
- animation && animation.easing,
- clipBox.height,
- options.xAxis,
- options.yAxis
- ].join(','), // #4526
- clipRect = chart[sharedClipKey],
- markerClipRect = chart[sharedClipKey + 'm'];
-
- // If a clipping rectangle with the same properties is currently present
- // in the chart, use that.
- if (!clipRect) {
+ fireEvent(this, 'afterTranslate');
+ },
+
+ /**
+ * Return the series points with null points filtered out.
+ *
+ * @param {Array} [points]
+ * The points to inspect, defaults to {@link Series.points}.
+ *
+ * @param {boolean} [insideOnly=false]
+ * Whether to inspect only the points that are inside the visible
+ * view.
+ *
+ * @param {boolean} [allowNull=false]
+ * Whether to allow null points to pass as valid points.
+ *
+ * @return {Array}
+ * The valid points.
+ */
+ getValidPoints: function (points, insideOnly, allowNull) {
+ var chart = this.chart;
+
+ // #3916, #5029, #5085
+ return (points || this.points || []).filter(
+ function isValidPoint(point) {
+ if (insideOnly && !chart.isInsidePlot(
+ point.plotX,
+ point.plotY,
+ chart.inverted
+ )) {
+ return false;
+ }
+ return allowNull || !point.isNull;
+ }
+ );
+ },
- // When animation is set, prepare the initial positions
- if (animation) {
- clipBox.width = 0;
- if (inverted) {
- clipBox.x = chart.plotSizeX;
+ /**
+ * Set the clipping for the series. For animated series it is called
+ * twice, first to initiate animating the clip then the second time
+ * without the animation to set the final clip.
+ *
+ * @private
+ * @function Highcharts.Series#setClip
+ *
+ * @param {boolean} [animation]
+ */
+ setClip: function (animation) {
+ var chart = this.chart,
+ options = this.options,
+ renderer = chart.renderer,
+ inverted = chart.inverted,
+ seriesClipBox = this.clipBox,
+ clipBox = seriesClipBox || chart.clipBox,
+ sharedClipKey =
+ this.sharedClipKey ||
+ [
+ '_sharedClip',
+ animation && animation.duration,
+ animation && animation.easing,
+ clipBox.height,
+ options.xAxis,
+ options.yAxis
+ ].join(','), // #4526
+ clipRect = chart[sharedClipKey],
+ markerClipRect = chart[sharedClipKey + 'm'];
+
+ // If a clipping rectangle with the same properties is currently
+ // present in the chart, use that.
+ if (!clipRect) {
+
+ // When animation is set, prepare the initial positions
+ if (animation) {
+ clipBox.width = 0;
+ if (inverted) {
+ clipBox.x = chart.plotSizeX;
+ }
+
+ chart[sharedClipKey + 'm'] = markerClipRect = renderer
+ .clipRect(
+ // include the width of the first marker
+ inverted ? chart.plotSizeX + 99 : -99,
+ inverted ? -chart.plotLeft : -chart.plotTop,
+ 99,
+ inverted ? chart.chartWidth : chart.chartHeight
+ );
}
+ chart[sharedClipKey] = clipRect = renderer.clipRect(clipBox);
+ // Create hashmap for series indexes
+ clipRect.count = { length: 0 };
- chart[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect(
- inverted ? chart.plotSizeX + 99 : -99, // include the width of the first marker
- inverted ? -chart.plotLeft : -chart.plotTop,
- 99,
- inverted ? chart.chartWidth : chart.chartHeight
- );
}
- chart[sharedClipKey] = clipRect = renderer.clipRect(clipBox);
- // Create hashmap for series indexes
- clipRect.count = {
- length: 0
- };
-
- }
- if (animation) {
- if (!clipRect.count[this.index]) {
- clipRect.count[this.index] = true;
- clipRect.count.length += 1;
+ if (animation) {
+ if (!clipRect.count[this.index]) {
+ clipRect.count[this.index] = true;
+ clipRect.count.length += 1;
+ }
}
- }
-
- if (options.clip !== false) {
- this.group.clip(animation || seriesClipBox ? clipRect : chart.clipRect);
- this.markerGroup.clip(markerClipRect);
- this.sharedClipKey = sharedClipKey;
- }
- // Remove the shared clipping rectangle when all series are shown
- if (!animation) {
- if (clipRect.count[this.index]) {
- delete clipRect.count[this.index];
- clipRect.count.length -= 1;
+ if (options.clip !== false) {
+ this.group.clip(
+ animation || seriesClipBox ? clipRect : chart.clipRect
+ );
+ this.markerGroup.clip(markerClipRect);
+ this.sharedClipKey = sharedClipKey;
}
- if (clipRect.count.length === 0 && sharedClipKey && chart[sharedClipKey]) {
- if (!seriesClipBox) {
- chart[sharedClipKey] = chart[sharedClipKey].destroy();
+ // Remove the shared clipping rectangle when all series are shown
+ if (!animation) {
+ if (clipRect.count[this.index]) {
+ delete clipRect.count[this.index];
+ clipRect.count.length -= 1;
}
- if (chart[sharedClipKey + 'm']) {
- chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy();
+
+ if (
+ clipRect.count.length === 0 &&
+ sharedClipKey &&
+ chart[sharedClipKey]
+ ) {
+ if (!seriesClipBox) {
+ chart[sharedClipKey] = chart[sharedClipKey].destroy();
+ }
+ if (chart[sharedClipKey + 'm']) {
+ chart[sharedClipKey + 'm'] =
+ chart[sharedClipKey + 'm'].destroy();
+ }
}
}
- }
- },
+ },
- /**
- * Animate in the series. Called internally twice. First with the `init`
- * parameter set to true, which sets up the initial state of the animation.
- * Then when ready, it is called with the `init` parameter undefined, in
- * order to perform the actual animation. After the second run, the function
- * is removed.
- *
- * @param {Boolean} init
- * Initialize the animation.
- */
- animate: function(init) {
- var series = this,
- chart = series.chart,
- clipRect,
- animation = animObject(series.options.animation),
- sharedClipKey;
+ /**
+ * Animate in the series. Called internally twice. First with the `init`
+ * parameter set to true, which sets up the initial state of the
+ * animation. Then when ready, it is called with the `init` parameter
+ * undefined, in order to perform the actual animation. After the
+ * second run, the function is removed.
+ *
+ * @function Highcharts.Series#animate
+ *
+ * @param {boolean} init
+ * Initialize the animation.
+ */
+ animate: function (init) {
+ var series = this,
+ chart = series.chart,
+ clipRect,
+ animation = animObject(series.options.animation),
+ sharedClipKey;
- // Initialize the animation. Set up the clipping rectangle.
- if (init) {
+ // Initialize the animation. Set up the clipping rectangle.
+ if (init) {
- series.setClip(animation);
+ series.setClip(animation);
// Run the animation
- } else {
- sharedClipKey = this.sharedClipKey;
- clipRect = chart[sharedClipKey];
- if (clipRect) {
- clipRect.animate({
- width: chart.plotSizeX,
- x: 0
- }, animation);
- }
- if (chart[sharedClipKey + 'm']) {
- chart[sharedClipKey + 'm'].animate({
- width: chart.plotSizeX + 99,
- x: 0
- }, animation);
- }
-
- // Delete this function to allow it only once
- series.animate = null;
-
- }
- },
+ } else {
+ sharedClipKey = this.sharedClipKey;
+ clipRect = chart[sharedClipKey];
+ if (clipRect) {
+ clipRect.animate({
+ width: chart.plotSizeX,
+ x: 0
+ }, animation);
+ }
+ if (chart[sharedClipKey + 'm']) {
+ chart[sharedClipKey + 'm'].animate({
+ width: chart.plotSizeX + 99,
+ x: 0
+ }, animation);
+ }
- /**
- * This runs after animation to land on the final plot clipping.
- *
- * @private
- */
- afterAnimate: function() {
- this.setClip();
- fireEvent(this, 'afterAnimate');
- this.finishedAnimating = true;
- },
+ // Delete this function to allow it only once
+ series.animate = null;
- /**
- * Draw the markers for line-like series types, and columns or other
- * graphical representation for {@link Point} objects for other series
- * types. The resulting element is typically stored as {@link
- * Point.graphic}, and is created on the first call and updated and moved on
- * subsequent calls.
- */
- drawPoints: function() {
- var series = this,
- points = series.points,
- chart = series.chart,
- i,
- point,
- symbol,
- graphic,
- options = series.options,
- seriesMarkerOptions = options.marker,
- pointMarkerOptions,
- hasPointMarker,
- enabled,
- isInside,
- markerGroup = series[series.specialGroup] || series.markerGroup,
- xAxis = series.xAxis,
- markerAttribs,
- globallyEnabled = pick(
- seriesMarkerOptions.enabled,
- xAxis.isRadial ? true : null,
- // Use larger or equal as radius is null in bubbles (#6321)
- series.closestPointRangePx >= 2 * seriesMarkerOptions.radius
- );
+ }
+ },
- if (seriesMarkerOptions.enabled !== false || series._hasPointMarkers) {
+ /**
+ * This runs after animation to land on the final plot clipping.
+ *
+ * @private
+ * @function Highcharts.Series#afterAnimate
+ *
+ * @fires Highcharts.Series#event:afterAnimate
+ */
+ afterAnimate: function () {
+ this.setClip();
+ fireEvent(this, 'afterAnimate');
+ this.finishedAnimating = true;
+ },
- for (i = 0; i < points.length; i++) {
- point = points[i];
- graphic = point.graphic;
- pointMarkerOptions = point.marker || {};
- hasPointMarker = !!point.marker;
- enabled = (globallyEnabled && pointMarkerOptions.enabled === undefined) || pointMarkerOptions.enabled;
- isInside = point.isInside;
+ /**
+ * Draw the markers for line-like series types, and columns or other
+ * graphical representation for {@link Point} objects for other series
+ * types. The resulting element is typically stored as
+ * {@link Point.graphic}, and is created on the first call and updated
+ * and moved on subsequent calls.
+ *
+ * @function Highcharts.Series#drawPoints
+ */
+ drawPoints: function () {
+ var series = this,
+ points = series.points,
+ chart = series.chart,
+ i,
+ point,
+ symbol,
+ graphic,
+ options = series.options,
+ seriesMarkerOptions = options.marker,
+ pointMarkerOptions,
+ hasPointMarker,
+ enabled,
+ isInside,
+ markerGroup = series[series.specialGroup] || series.markerGroup,
+ xAxis = series.xAxis,
+ markerAttribs,
+ globallyEnabled = pick(
+ seriesMarkerOptions.enabled,
+ !xAxis || xAxis.isRadial ? true : null,
+ // Use larger or equal as radius is null in bubbles (#6321)
+ series.closestPointRangePx >= (
+ seriesMarkerOptions.enabledThreshold *
+ seriesMarkerOptions.radius
+ )
+ );
- // only draw the point if y is defined
- if (enabled && !point.isNull) {
+ if (seriesMarkerOptions.enabled !== false ||
+ series._hasPointMarkers
+ ) {
- // Shortcuts
- symbol = pick(pointMarkerOptions.symbol, series.symbol);
- point.hasImage = symbol.indexOf('url') === 0;
+ for (i = 0; i < points.length; i++) {
+ point = points[i];
+ graphic = point.graphic;
+ pointMarkerOptions = point.marker || {};
+ hasPointMarker = !!point.marker;
+ enabled = (
+ globallyEnabled &&
+ pointMarkerOptions.enabled === undefined
+ ) || pointMarkerOptions.enabled;
+ isInside = point.isInside !== false;
+
+ // only draw the point if y is defined
+ if (enabled && !point.isNull) {
+
+ // Shortcuts
+ symbol = pick(pointMarkerOptions.symbol, series.symbol);
+
+ markerAttribs = series.markerAttribs(
+ point,
+ point.selected && 'select'
+ );
- markerAttribs = series.markerAttribs(
- point,
- point.selected && 'select'
- );
+ if (graphic) { // update
+ // Since the marker group isn't clipped, each
+ // individual marker must be toggled
+ graphic[isInside ? 'show' : 'hide'](true)
+ .animate(markerAttribs);
- if (graphic) { // update
- graphic[isInside ? 'show' : 'hide'](true) // Since the marker group isn't clipped, each individual marker must be toggled
- .animate(markerAttribs);
- } else if (isInside && (markerAttribs.width > 0 || point.hasImage)) {
-
- /**
- * The graphic representation of the point. Typically
- * this is a simple shape, like a `rect` for column
- * charts or `path` for line markers, but for some
- * complex series types like boxplot or 3D charts, the
- * graphic may be a `g` element containing other shapes.
- * The graphic is generated the first time {@link
- * Series#drawPoints} runs, and updated and moved on
- * subsequent runs.
- *
- * @memberof Point
- * @name graphic
- * @type {SVGElement}
- */
- point.graphic = graphic = chart.renderer.symbol(
- symbol,
- markerAttribs.x,
- markerAttribs.y,
- markerAttribs.width,
- markerAttribs.height,
- hasPointMarker ? pointMarkerOptions : seriesMarkerOptions
- )
- .add(markerGroup);
- }
+ } else if (
+ isInside &&
+ (markerAttribs.width > 0 || point.hasImage)
+ ) {
+ /**
+ * The graphic representation of the point.
+ * Typically this is a simple shape, like a `rect`
+ * for column charts or `path` for line markers, but
+ * for some complex series types like boxplot or 3D
+ * charts, the graphic may be a `g` element
+ * containing other shapes. The graphic is generated
+ * the first time {@link Series#drawPoints} runs,
+ * and updated and moved on subsequent runs.
+ *
+ * @name Point#graphic
+ * @type {SVGElement}
+ */
+ point.graphic = graphic = chart.renderer
+ .symbol(
+ symbol,
+ markerAttribs.x,
+ markerAttribs.y,
+ markerAttribs.width,
+ markerAttribs.height,
+ hasPointMarker ?
+ pointMarkerOptions :
+ seriesMarkerOptions
+ )
+ .add(markerGroup);
+ }
- // Presentational attributes
- if (graphic) {
- graphic.attr(series.pointAttribs(point, point.selected && 'select'));
- }
+ // Presentational attributes
+ if (graphic && !chart.styledMode) {
+ graphic.attr(
+ series.pointAttribs(
+ point,
+ point.selected && 'select'
+ )
+ );
+ }
+ if (graphic) {
+ graphic.addClass(point.getClassName(), true);
+ }
- if (graphic) {
- graphic.addClass(point.getClassName(), true);
+ } else if (graphic) {
+ point.graphic = graphic.destroy(); // #1269
}
-
- } else if (graphic) {
- point.graphic = graphic.destroy(); // #1269
}
}
- }
- },
+ },
- /**
- * Get non-presentational attributes for a point. Used internally for both
- * styled mode and classic. Can be overridden for different series types.
- *
- * @see Series#pointAttribs
- *
- * @param {Point} point
- * The Point to inspect.
- * @param {String} [state]
- * The state, can be either `hover`, `select` or undefined.
- *
- * @return {SVGAttributes}
- * A hash containing those attributes that are not settable from
- * CSS.
- */
- markerAttribs: function(point, state) {
- var seriesMarkerOptions = this.options.marker,
- seriesStateOptions,
- pointMarkerOptions = point.marker || {},
- pointStateOptions,
- radius = pick(
- pointMarkerOptions.radius,
- seriesMarkerOptions.radius
- ),
- attribs;
-
- // Handle hover and select states
- if (state) {
- seriesStateOptions = seriesMarkerOptions.states[state];
- pointStateOptions = pointMarkerOptions.states &&
- pointMarkerOptions.states[state];
-
- radius = pick(
- pointStateOptions && pointStateOptions.radius,
- seriesStateOptions && seriesStateOptions.radius,
- radius + (seriesStateOptions && seriesStateOptions.radiusPlus || 0)
- );
- }
+ /**
+ * Get non-presentational attributes for a point. Used internally for
+ * both styled mode and classic. Can be overridden for different series
+ * types.
+ *
+ * @see Series#pointAttribs
+ *
+ * @function Highcharts.Series#markerAttribs
+ *
+ * @param {Highcharts.Point} point
+ * The Point to inspect.
+ *
+ * @param {string} [state]
+ * The state, can be either `hover`, `select` or undefined.
+ *
+ * @return {Highcharts.SVGAttributes}
+ * A hash containing those attributes that are not settable from
+ * CSS.
+ */
+ markerAttribs: function (point, state) {
+ var seriesMarkerOptions = this.options.marker,
+ seriesStateOptions,
+ pointMarkerOptions = point.marker || {},
+ symbol = (
+ pointMarkerOptions.symbol || seriesMarkerOptions.symbol
+ ),
+ pointStateOptions,
+ radius = pick(
+ pointMarkerOptions.radius,
+ seriesMarkerOptions.radius
+ ),
+ attribs;
- if (point.hasImage) {
- radius = 0; // and subsequently width and height is not set
- }
+ // Handle hover and select states
+ if (state) {
+ seriesStateOptions = seriesMarkerOptions.states[state];
+ pointStateOptions = pointMarkerOptions.states &&
+ pointMarkerOptions.states[state];
+
+ radius = pick(
+ pointStateOptions && pointStateOptions.radius,
+ seriesStateOptions && seriesStateOptions.radius,
+ radius + (
+ seriesStateOptions && seriesStateOptions.radiusPlus ||
+ 0
+ )
+ );
+ }
- attribs = {
- x: Math.floor(point.plotX) - radius, // Math.floor for #1843
- y: point.plotY - radius
- };
+ point.hasImage = symbol && symbol.indexOf('url') === 0;
- if (radius) {
- attribs.width = attribs.height = 2 * radius;
- }
+ if (point.hasImage) {
+ radius = 0; // and subsequently width and height is not set
+ }
- return attribs;
+ attribs = {
+ x: Math.floor(point.plotX) - radius, // Math.floor for #1843
+ y: point.plotY - radius
+ };
- },
+ if (radius) {
+ attribs.width = attribs.height = 2 * radius;
+ }
+ return attribs;
- /**
- * Internal function to get presentational attributes for each point. Unlike
- * {@link Series#markerAttribs}, this function should return those
- * attributes that can also be set in CSS. In styled mode, `pointAttribs`
- * won't be called.
- *
- * @param {Point} point
- * The point instance to inspect.
- * @param {String} [state]
- * The point state, can be either `hover`, `select` or undefined for
- * normal state.
- *
- * @return {SVGAttributes}
- * The presentational attributes to be set on the point.
- */
- pointAttribs: function(point, state) {
- var seriesMarkerOptions = this.options.marker,
- seriesStateOptions,
- pointOptions = point && point.options,
- pointMarkerOptions = (pointOptions && pointOptions.marker) || {},
- pointStateOptions,
- color = this.color,
- pointColorOption = pointOptions && pointOptions.color,
- pointColor = point && point.color,
- strokeWidth = pick(
- pointMarkerOptions.lineWidth,
- seriesMarkerOptions.lineWidth
- ),
- zoneColor = point && point.zone && point.zone.color,
- fill,
- stroke;
-
- color = pointColorOption || zoneColor || pointColor || color;
- fill = pointMarkerOptions.fillColor || seriesMarkerOptions.fillColor || color;
- stroke = pointMarkerOptions.lineColor || seriesMarkerOptions.lineColor || color;
-
- // Handle hover and select states
- if (state) {
- seriesStateOptions = seriesMarkerOptions.states[state];
- pointStateOptions = (pointMarkerOptions.states && pointMarkerOptions.states[state]) || {};
- strokeWidth = pick(
- pointStateOptions.lineWidth,
- seriesStateOptions.lineWidth,
- strokeWidth + pick(
- pointStateOptions.lineWidthPlus,
- seriesStateOptions.lineWidthPlus,
- 0
- )
+ },
+
+ /**
+ * Internal function to get presentational attributes for each point.
+ * Unlike {@link Series#markerAttribs}, this function should return
+ * those attributes that can also be set in CSS. In styled mode,
+ * `pointAttribs` won't be called.
+ *
+ * @private
+ * @function Highcharts.Series#pointAttribs
+ *
+ * @param {Highcharts.Point} point
+ * The point instance to inspect.
+ *
+ * @param {string} [state]
+ * The point state, can be either `hover`, `select` or undefined
+ * for normal state.
+ *
+ * @return {Highcharts.SVGAttributes}
+ * The presentational attributes to be set on the point.
+ */
+ pointAttribs: function (point, state) {
+ var seriesMarkerOptions = this.options.marker,
+ seriesStateOptions,
+ pointOptions = point && point.options,
+ pointMarkerOptions = (
+ (pointOptions && pointOptions.marker) || {}
+ ),
+ pointStateOptions,
+ color = this.color,
+ pointColorOption = pointOptions && pointOptions.color,
+ pointColor = point && point.color,
+ strokeWidth = pick(
+ pointMarkerOptions.lineWidth,
+ seriesMarkerOptions.lineWidth
+ ),
+ zoneColor = point && point.zone && point.zone.color,
+ fill,
+ stroke;
+
+ color = (
+ pointColorOption ||
+ zoneColor ||
+ pointColor ||
+ color
+ );
+ fill = (
+ pointMarkerOptions.fillColor ||
+ seriesMarkerOptions.fillColor ||
+ color
+ );
+ stroke = (
+ pointMarkerOptions.lineColor ||
+ seriesMarkerOptions.lineColor ||
+ color
);
- fill = pointStateOptions.fillColor || seriesStateOptions.fillColor || fill;
- stroke = pointStateOptions.lineColor || seriesStateOptions.lineColor || stroke;
- }
- return {
- 'stroke': stroke,
- 'stroke-width': strokeWidth,
- 'fill': fill
- };
- },
+ // Handle hover and select states
+ if (state) {
+ seriesStateOptions = seriesMarkerOptions.states[state];
+ pointStateOptions = (
+ pointMarkerOptions.states &&
+ pointMarkerOptions.states[state]
+ ) || {};
+ strokeWidth = pick(
+ pointStateOptions.lineWidth,
+ seriesStateOptions.lineWidth,
+ strokeWidth + pick(
+ pointStateOptions.lineWidthPlus,
+ seriesStateOptions.lineWidthPlus,
+ 0
+ )
+ );
+ fill = (
+ pointStateOptions.fillColor ||
+ seriesStateOptions.fillColor ||
+ fill
+ );
+ stroke = (
+ pointStateOptions.lineColor ||
+ seriesStateOptions.lineColor ||
+ stroke
+ );
+ }
- /**
- * Clear DOM objects and free up memory.
- *
- * @private
- */
- destroy: function() {
- var series = this,
- chart = series.chart,
- issue134 = /AppleWebKit\/533/.test(win.navigator.userAgent),
- destroy,
- i,
- data = series.data || [],
- point,
- axis;
+ return {
+ 'stroke': stroke,
+ 'stroke-width': strokeWidth,
+ 'fill': fill
+ };
+ },
- // add event hook
- fireEvent(series, 'destroy');
+ /**
+ * Clear DOM objects and free up memory.
+ *
+ * @private
+ * @function Highcharts.Series#destroy
+ *
+ * @fires Highcharts.Series#event:destroy
+ */
+ destroy: function (keepEvents) {
+ var series = this,
+ chart = series.chart,
+ issue134 = /AppleWebKit\/533/.test(win.navigator.userAgent),
+ destroy,
+ i,
+ data = series.data || [],
+ point,
+ axis;
- // remove all events
- removeEvent(series);
+ // add event hook
+ fireEvent(series, 'destroy');
- // erase from axes
- each(series.axisTypes || [], function(AXIS) {
- axis = series[AXIS];
- if (axis && axis.series) {
- erase(axis.series, series);
- axis.isDirty = axis.forceRedraw = true;
+ // remove all events
+ if (!keepEvents) {
+ removeEvent(series);
}
- });
- // remove legend items
- if (series.legendItem) {
- series.chart.legend.destroyItem(series);
- }
+ // erase from axes
+ (series.axisTypes || []).forEach(function (AXIS) {
+ axis = series[AXIS];
+ if (axis && axis.series) {
+ erase(axis.series, series);
+ axis.isDirty = axis.forceRedraw = true;
+ }
+ });
- // destroy all points with their elements
- i = data.length;
- while (i--) {
- point = data[i];
- if (point && point.destroy) {
- point.destroy();
+ // remove legend items
+ if (series.legendItem) {
+ series.chart.legend.destroyItem(series);
}
- }
- series.points = null;
- // Clear the animation timeout if we are destroying the series during initial animation
- clearTimeout(series.animationTimeout);
+ // destroy all points with their elements
+ i = data.length;
+ while (i--) {
+ point = data[i];
+ if (point && point.destroy) {
+ point.destroy();
+ }
+ }
+ series.points = null;
+
+ // Clear the animation timeout if we are destroying the series
+ // during initial animation
+ H.clearTimeout(series.animationTimeout);
- // Destroy all SVGElements associated to the series
- objectEach(series, function(val, prop) {
- if (val instanceof SVGElement && !val.survive) { // Survive provides a hook for not destroying
+ // Destroy all SVGElements associated to the series
+ objectEach(series, function (val, prop) {
+ // Survive provides a hook for not destroying
+ if (val instanceof SVGElement && !val.survive) {
- // issue 134 workaround
- destroy = issue134 && prop === 'group' ?
- 'hide' :
- 'destroy';
+ // issue 134 workaround
+ destroy = issue134 && prop === 'group' ?
+ 'hide' :
+ 'destroy';
- val[destroy]();
- }
- });
+ val[destroy]();
+ }
+ });
- // remove from hoverSeries
- if (chart.hoverSeries === series) {
- chart.hoverSeries = null;
- }
- erase(chart.series, series);
- chart.orderSeries();
+ // remove from hoverSeries
+ if (chart.hoverSeries === series) {
+ chart.hoverSeries = null;
+ }
+ erase(chart.series, series);
+ chart.orderSeries();
- // clear all members
- objectEach(series, function(val, prop) {
- delete series[prop];
- });
- },
+ // clear all members
+ objectEach(series, function (val, prop) {
+ if (!keepEvents || prop !== 'hcEvents') {
+ delete series[prop];
+ }
+ });
+ },
- /**
- * Get the graph path.
- *
- * @private
- */
- getGraphPath: function(points, nullsAsZeroes, connectCliffs) {
- var series = this,
- options = series.options,
- step = options.step,
- reversed,
- graphPath = [],
- xMap = [],
- gap;
+ /**
+ * Get the graph path.
+ *
+ * @private
+ * @function Highcharts.Series#getGraphPath
+ *
+ * @param {Array<*>} points
+ *
+ * @param {boolean} nullsAsZeroes
+ *
+ * @param {boolean} connectCliffs
+ *
+ * @return {Array}
+ */
+ getGraphPath: function (points, nullsAsZeroes, connectCliffs) {
+ var series = this,
+ options = series.options,
+ step = options.step,
+ reversed,
+ graphPath = [],
+ xMap = [],
+ gap;
- points = points || series.points;
+ points = points || series.points;
- // Bottom of a stack is reversed
- reversed = points.reversed;
- if (reversed) {
- points.reverse();
- }
- // Reverse the steps (#5004)
- step = {
- right: 1,
- center: 2
- }[step] || (step && 3);
- if (step && reversed) {
- step = 4 - step;
- }
+ // Bottom of a stack is reversed
+ reversed = points.reversed;
+ if (reversed) {
+ points.reverse();
+ }
+ // Reverse the steps (#5004)
+ step = { right: 1, center: 2 }[step] || (step && 3);
+ if (step && reversed) {
+ step = 4 - step;
+ }
- // Remove invalid points, especially in spline (#5015)
- if (options.connectNulls && !nullsAsZeroes && !connectCliffs) {
- points = this.getValidPoints(points);
- }
+ // Remove invalid points, especially in spline (#5015)
+ if (options.connectNulls && !nullsAsZeroes && !connectCliffs) {
+ points = this.getValidPoints(points);
+ }
- // Build the line
- each(points, function(point, i) {
+ // Build the line
+ points.forEach(function (point, i) {
- var plotX = point.plotX,
- plotY = point.plotY,
- lastPoint = points[i - 1],
- pathToPoint; // the path to this point from the previous
+ var plotX = point.plotX,
+ plotY = point.plotY,
+ lastPoint = points[i - 1],
+ pathToPoint; // the path to this point from the previous
- if ((point.leftCliff || (lastPoint && lastPoint.rightCliff)) && !connectCliffs) {
- gap = true; // ... and continue
- }
+ if (
+ (point.leftCliff || (lastPoint && lastPoint.rightCliff)) &&
+ !connectCliffs
+ ) {
+ gap = true; // ... and continue
+ }
- // Line series, nullsAsZeroes is not handled
- if (point.isNull && !defined(nullsAsZeroes) && i > 0) {
- gap = !options.connectNulls;
+ // Line series, nullsAsZeroes is not handled
+ if (point.isNull && !defined(nullsAsZeroes) && i > 0) {
+ gap = !options.connectNulls;
// Area series, nullsAsZeroes is set
- } else if (point.isNull && !nullsAsZeroes) {
- gap = true;
+ } else if (point.isNull && !nullsAsZeroes) {
+ gap = true;
- } else {
+ } else {
- if (i === 0 || gap) {
- pathToPoint = ['M', point.plotX, point.plotY];
+ if (i === 0 || gap) {
+ pathToPoint = ['M', point.plotX, point.plotY];
- } else if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
+ // Generate the spline as defined in the SplineSeries object
+ } else if (series.getPointSpline) {
- pathToPoint = series.getPointSpline(points, point, i);
+ pathToPoint = series.getPointSpline(points, point, i);
- } else if (step) {
+ } else if (step) {
- if (step === 1) { // right
- pathToPoint = [
- 'L',
- lastPoint.plotX,
- plotY
- ];
+ if (step === 1) { // right
+ pathToPoint = [
+ 'L',
+ lastPoint.plotX,
+ plotY
+ ];
- } else if (step === 2) { // center
- pathToPoint = [
- 'L',
- (lastPoint.plotX + plotX) / 2,
- lastPoint.plotY,
- 'L',
- (lastPoint.plotX + plotX) / 2,
- plotY
- ];
+ } else if (step === 2) { // center
+ pathToPoint = [
+ 'L',
+ (lastPoint.plotX + plotX) / 2,
+ lastPoint.plotY,
+ 'L',
+ (lastPoint.plotX + plotX) / 2,
+ plotY
+ ];
+
+ } else {
+ pathToPoint = [
+ 'L',
+ plotX,
+ lastPoint.plotY
+ ];
+ }
+ pathToPoint.push('L', plotX, plotY);
} else {
+ // normal line to next point
pathToPoint = [
'L',
plotX,
- lastPoint.plotY
+ plotY
];
}
- pathToPoint.push('L', plotX, plotY);
-
- } else {
- // normal line to next point
- pathToPoint = [
- 'L',
- plotX,
- plotY
- ];
- }
- // Prepare for animation. When step is enabled, there are two path nodes for each x value.
- xMap.push(point.x);
- if (step) {
+ // Prepare for animation. When step is enabled, there are
+ // two path nodes for each x value.
xMap.push(point.x);
- }
+ if (step) {
+ xMap.push(point.x);
+ if (step === 2) { // step = center (#8073)
+ xMap.push(point.x);
+ }
+ }
- graphPath.push.apply(graphPath, pathToPoint);
- gap = false;
- }
- });
+ graphPath.push.apply(graphPath, pathToPoint);
+ gap = false;
+ }
+ });
- graphPath.xMap = xMap;
- series.graphPath = graphPath;
+ graphPath.xMap = xMap;
+ series.graphPath = graphPath;
- return graphPath;
+ return graphPath;
- },
+ },
- /**
- * Draw the graph. Called internally when rendering line-like series types.
- * The first time it generates the `series.graph` item and optionally other
- * series-wide items like `series.area` for area charts. On subsequent calls
- * these items are updated with new positions and attributes.
- */
- drawGraph: function() {
- var series = this,
- options = this.options,
- graphPath = (this.gappedPath || this.getGraphPath).call(this),
- props = [
- [
+ /**
+ * Draw the graph. Called internally when rendering line-like series
+ * types. The first time it generates the `series.graph` item and
+ * optionally other series-wide items like `series.area` for area
+ * charts. On subsequent calls these items are updated with new
+ * positions and attributes.
+ *
+ * @function Highcharts.Series#drawGraph
+ */
+ drawGraph: function () {
+ var series = this,
+ options = this.options,
+ graphPath = (this.gappedPath || this.getGraphPath).call(this),
+ styledMode = this.chart.styledMode,
+ props = [[
'graph',
- 'highcharts-graph',
+ 'highcharts-graph'
+ ]];
+ // Presentational properties
+ if (!styledMode) {
+ props[0].push(
options.lineColor || this.color,
options.dashStyle
+ );
+ }
- ]
- ];
-
- // Add the zone properties if any
- each(this.zones, function(zone, i) {
- props.push([
- 'zone-graph-' + i,
- 'highcharts-graph highcharts-zone-graph-' + i + ' ' + (zone.className || ''),
+ props = series.getZonesGraphs(props);
- zone.color || series.color,
- zone.dashStyle || options.dashStyle
+ // Draw the graph
+ props.forEach(function (prop, i) {
+ var graphKey = prop[0],
+ graph = series[graphKey],
+ attribs;
- ]);
- });
+ if (graph) {
+ graph.endX = series.preventGraphAnimation ?
+ null :
+ graphPath.xMap;
+ graph.animate({ d: graphPath });
- // Draw the graph
- each(props, function(prop, i) {
- var graphKey = prop[0],
- graph = series[graphKey],
- attribs;
+ } else if (graphPath.length) { // #1487
- if (graph) {
- graph.endX = series.preventGraphAnimation ?
- null :
- graphPath.xMap;
- graph.animate({
- d: graphPath
- });
+ series[graphKey] = series.chart.renderer.path(graphPath)
+ .addClass(prop[1])
+ .attr({ zIndex: 1 }) // #1069
+ .add(series.group);
- } else if (graphPath.length) { // #1487
- series[graphKey] = series.chart.renderer.path(graphPath)
- .addClass(prop[1])
- .attr({
- zIndex: 1
- }) // #1069
- .add(series.group);
+ if (!styledMode) {
+ attribs = {
+ 'stroke': prop[2],
+ 'stroke-width': options.lineWidth,
+ // Polygon series use filled graph
+ 'fill': (series.fillGraph && series.color) || 'none'
+ };
+ if (prop[3]) {
+ attribs.dashstyle = prop[3];
+ } else if (options.linecap !== 'square') {
+ attribs['stroke-linecap'] =
+ attribs['stroke-linejoin'] = 'round';
+ }
- attribs = {
- 'stroke': prop[2],
- 'stroke-width': options.lineWidth,
- 'fill': (series.fillGraph && series.color) || 'none' // Polygon series use filled graph
- };
+ graph = series[graphKey]
+ .attr(attribs)
+ // Add shadow to normal series (0) or to first
+ // zone (1) #3932
+ .shadow((i < 2) && options.shadow);
+ }
+ }
- if (prop[3]) {
- attribs.dashstyle = prop[3];
- } else if (options.linecap !== 'square') {
- attribs['stroke-linecap'] = attribs['stroke-linejoin'] = 'round';
+ // Helpers for animation
+ if (graph) {
+ graph.startX = graphPath.xMap;
+ graph.isArea = graphPath.isArea; // For arearange animation
}
+ });
+ },
- graph = series[graphKey]
- .attr(attribs)
- .shadow((i < 2) && options.shadow); // add shadow to normal series (0) or to first zone (1) #3932
+ /**
+ * Get zones properties for building graphs. Extendable by series with
+ * multiple lines within one series.
+ *
+ * @private
+ * @function Highcharts.Series#getZonesGraphs
+ *
+ * @param {Array>} props
+ *
+ * @return {Array>}
+ */
+ getZonesGraphs: function (props) {
+ // Add the zone properties if any
+ this.zones.forEach(function (zone, i) {
+ var propset = [
+ 'zone-graph-' + i,
+ 'highcharts-graph highcharts-zone-graph-' + i + ' ' +
+ (zone.className || '')
+ ];
- }
+ if (!this.chart.styledMode) {
+ propset.push(
+ zone.color || this.color,
+ zone.dashStyle || this.options.dashStyle
+ );
+ }
+ props.push(propset);
+ }, this);
- // Helpers for animation
- if (graph) {
- graph.startX = graphPath.xMap;
- graph.isArea = graphPath.isArea; // For arearange animation
- }
- });
- },
+ return props;
+ },
- /**
- * Clip the graphs into zones for colors and styling.
- *
- * @private
- */
- applyZones: function() {
- var series = this,
- chart = this.chart,
- renderer = chart.renderer,
- zones = this.zones,
- translatedFrom,
- translatedTo,
- clips = this.clips || [],
- clipAttr,
- graph = this.graph,
- area = this.area,
- chartSizeMax = Math.max(chart.chartWidth, chart.chartHeight),
- axis = this[(this.zoneAxis || 'y') + 'Axis'],
- extremes,
- reversed,
- inverted = chart.inverted,
- horiz,
- pxRange,
- pxPosMin,
- pxPosMax,
- ignoreZones = false;
-
- if (zones.length && (graph || area) && axis && axis.min !== undefined) {
- reversed = axis.reversed;
- horiz = axis.horiz;
- // The use of the Color Threshold assumes there are no gaps
- // so it is safe to hide the original graph and area
- if (graph) {
- graph.hide();
- }
- if (area) {
- area.hide();
- }
-
- // Create the clips
- extremes = axis.getExtremes();
- each(zones, function(threshold, i) {
-
- translatedFrom = reversed ?
- (horiz ? chart.plotWidth : 0) :
- (horiz ? 0 : axis.toPixels(extremes.min));
- translatedFrom = Math.min(Math.max(pick(translatedTo, translatedFrom), 0), chartSizeMax);
- translatedTo = Math.min(Math.max(Math.round(axis.toPixels(pick(threshold.value, extremes.max), true)), 0), chartSizeMax);
-
- if (ignoreZones) {
- translatedFrom = translatedTo = axis.toPixels(extremes.max);
- }
-
- pxRange = Math.abs(translatedFrom - translatedTo);
- pxPosMin = Math.min(translatedFrom, translatedTo);
- pxPosMax = Math.max(translatedFrom, translatedTo);
- if (axis.isXAxis) {
- clipAttr = {
- x: inverted ? pxPosMax : pxPosMin,
- y: 0,
- width: pxRange,
- height: chartSizeMax
- };
- if (!horiz) {
- clipAttr.x = chart.plotHeight - clipAttr.x;
- }
- } else {
- clipAttr = {
- x: 0,
- y: inverted ? pxPosMax : pxPosMin,
- width: chartSizeMax,
- height: pxRange
- };
- if (horiz) {
- clipAttr.y = chart.plotWidth - clipAttr.y;
- }
+ /**
+ * Clip the graphs into zones for colors and styling.
+ *
+ * @private
+ * @function Highcharts.Series#applyZones
+ */
+ applyZones: function () {
+ var series = this,
+ chart = this.chart,
+ renderer = chart.renderer,
+ zones = this.zones,
+ translatedFrom,
+ translatedTo,
+ clips = this.clips || [],
+ clipAttr,
+ graph = this.graph,
+ area = this.area,
+ chartSizeMax = Math.max(chart.chartWidth, chart.chartHeight),
+ axis = this[(this.zoneAxis || 'y') + 'Axis'],
+ extremes,
+ reversed,
+ inverted = chart.inverted,
+ horiz,
+ pxRange,
+ pxPosMin,
+ pxPosMax,
+ ignoreZones = false;
+
+ if (zones.length &&
+ (graph || area) &&
+ axis &&
+ axis.min !== undefined
+ ) {
+ reversed = axis.reversed;
+ horiz = axis.horiz;
+ // The use of the Color Threshold assumes there are no gaps
+ // so it is safe to hide the original graph and area
+ // unless it is not waterfall series, then use showLine property
+ // to set lines between columns to be visible (#7862)
+ if (graph && !this.showLine) {
+ graph.hide();
+ }
+ if (area) {
+ area.hide();
}
+ // Create the clips
+ extremes = axis.getExtremes();
+ zones.forEach(function (threshold, i) {
+
+ translatedFrom = reversed ?
+ (horiz ? chart.plotWidth : 0) :
+ (horiz ? 0 : (axis.toPixels(extremes.min) || 0));
+
+ translatedFrom = Math.min(
+ Math.max(
+ pick(translatedTo, translatedFrom), 0
+ ),
+ chartSizeMax
+ );
+ translatedTo = Math.min(
+ Math.max(
+ Math.round(
+ axis.toPixels(
+ pick(threshold.value, extremes.max),
+ true
+ ) || 0
+ ),
+ 0
+ ),
+ chartSizeMax
+ );
+
+ if (ignoreZones) {
+ translatedFrom = translatedTo =
+ axis.toPixels(extremes.max);
+ }
- // VML SUPPPORT
- if (inverted && renderer.isVML) {
+ pxRange = Math.abs(translatedFrom - translatedTo);
+ pxPosMin = Math.min(translatedFrom, translatedTo);
+ pxPosMax = Math.max(translatedFrom, translatedTo);
if (axis.isXAxis) {
clipAttr = {
- x: 0,
- y: reversed ? pxPosMin : pxPosMax,
- height: clipAttr.width,
- width: chart.chartWidth
+ x: inverted ? pxPosMax : pxPosMin,
+ y: 0,
+ width: pxRange,
+ height: chartSizeMax
};
+ if (!horiz) {
+ clipAttr.x = chart.plotHeight - clipAttr.x;
+ }
} else {
clipAttr = {
- x: clipAttr.y - chart.plotLeft - chart.spacingBox.x,
- y: 0,
- width: clipAttr.height,
- height: chart.chartHeight
+ x: 0,
+ y: inverted ? pxPosMax : pxPosMin,
+ width: chartSizeMax,
+ height: pxRange
};
+ if (horiz) {
+ clipAttr.y = chart.plotWidth - clipAttr.y;
+ }
}
- }
- // END OF VML SUPPORT
+ // VML SUPPPORT
+ if (inverted && renderer.isVML) {
+ if (axis.isXAxis) {
+ clipAttr = {
+ x: 0,
+ y: reversed ? pxPosMin : pxPosMax,
+ height: clipAttr.width,
+ width: chart.chartWidth
+ };
+ } else {
+ clipAttr = {
+ x: (
+ clipAttr.y -
+ chart.plotLeft -
+ chart.spacingBox.x
+ ),
+ y: 0,
+ width: clipAttr.height,
+ height: chart.chartHeight
+ };
+ }
+ }
+ // END OF VML SUPPORT
- if (clips[i]) {
- clips[i].animate(clipAttr);
- } else {
- clips[i] = renderer.clipRect(clipAttr);
+ if (clips[i]) {
+ clips[i].animate(clipAttr);
+ } else {
+ clips[i] = renderer.clipRect(clipAttr);
- if (graph) {
- series['zone-graph-' + i].clip(clips[i]);
- }
+ if (graph) {
+ series['zone-graph-' + i].clip(clips[i]);
+ }
- if (area) {
- series['zone-area-' + i].clip(clips[i]);
+ if (area) {
+ series['zone-area-' + i].clip(clips[i]);
+ }
}
- }
- // if this zone extends out of the axis, ignore the others
- ignoreZones = threshold.value > extremes.max;
- });
- this.clips = clips;
- }
- },
+ // if this zone extends out of the axis, ignore the others
+ ignoreZones = threshold.value > extremes.max;
- /**
- * Initialize and perform group inversion on series.group and
- * series.markerGroup.
- *
- * @private
- */
- invertGroups: function(inverted) {
- var series = this,
- chart = series.chart,
- remover;
+ // Clear translatedTo for indicators
+ if (series.resetZones && translatedTo === 0) {
+ translatedTo = undefined;
+ }
+ });
+ this.clips = clips;
+ }
+ },
- function setInvert() {
- each(['group', 'markerGroup'], function(groupName) {
- if (series[groupName]) {
+ /**
+ * Initialize and perform group inversion on series.group and
+ * series.markerGroup.
+ *
+ * @private
+ * @function Highcharts.Series#invertGroups
+ *
+ * @param {boolean} inverted
+ */
+ invertGroups: function (inverted) {
+ var series = this,
+ chart = series.chart,
+ remover;
+
+ function setInvert() {
+ ['group', 'markerGroup'].forEach(function (groupName) {
+ if (series[groupName]) {
+
+ // VML/HTML needs explicit attributes for flipping
+ if (chart.renderer.isVML) {
+ series[groupName].attr({
+ width: series.yAxis.len,
+ height: series.xAxis.len
+ });
+ }
- // VML/HTML needs explicit attributes for flipping
- if (chart.renderer.isVML) {
- series[groupName].attr({
- width: series.yAxis.len,
- height: series.xAxis.len
- });
+ series[groupName].width = series.yAxis.len;
+ series[groupName].height = series.xAxis.len;
+ series[groupName].invert(inverted);
}
+ });
+ }
- series[groupName].width = series.yAxis.len;
- series[groupName].height = series.xAxis.len;
- series[groupName].invert(inverted);
- }
- });
- }
-
- // Pie, go away (#1736)
- if (!series.xAxis) {
- return;
- }
+ // Pie, go away (#1736)
+ if (!series.xAxis) {
+ return;
+ }
- // A fixed size is needed for inversion to work
- remover = addEvent(chart, 'resize', setInvert);
- addEvent(series, 'destroy', remover);
+ // A fixed size is needed for inversion to work
+ remover = addEvent(chart, 'resize', setInvert);
+ addEvent(series, 'destroy', remover);
- // Do it now
- setInvert(inverted); // do it now
+ // Do it now
+ setInvert(inverted); // do it now
- // On subsequent render and redraw, just do setInvert without setting up events again
- series.invertGroups = setInvert;
- },
+ // On subsequent render and redraw, just do setInvert without
+ // setting up events again
+ series.invertGroups = setInvert;
+ },
- /**
- * General abstraction for creating plot groups like series.group,
- * series.dataLabelsGroup and series.markerGroup. On subsequent calls, the
- * group will only be adjusted to the updated plot size.
- *
- * @private
- */
- plotGroup: function(prop, name, visibility, zIndex, parent) {
- var group = this[prop],
- isNew = !group;
+ /**
+ * General abstraction for creating plot groups like series.group,
+ * series.dataLabelsGroup and series.markerGroup. On subsequent calls,
+ * the group will only be adjusted to the updated plot size.
+ *
+ * @private
+ * @function Highcharts.Series#plotGroup
+ *
+ * @param {string} prop
+ *
+ * @param {string} name
+ *
+ * @param {string} visibility
+ *
+ * @param {number} zIndex
+ *
+ * @param {Highcharts.SVGElement} parent
+ *
+ * @return {Highcharts.SVGElement}
+ */
+ plotGroup: function (prop, name, visibility, zIndex, parent) {
+ var group = this[prop],
+ isNew = !group;
- // Generate it on first call
- if (isNew) {
- this[prop] = group = this.chart.renderer.g()
- .attr({
- zIndex: zIndex || 0.1 // IE8 and pointer logic use this
- })
- .add(parent);
+ // Generate it on first call
+ if (isNew) {
+ this[prop] = group = this.chart.renderer.g()
+ .attr({
+ zIndex: zIndex || 0.1 // IE8 and pointer logic use this
+ })
+ .add(parent);
- }
+ }
- // Add the class names, and replace existing ones as response to
- // Series.update (#6660)
- group.addClass(
- (
- 'highcharts-' + name +
- ' highcharts-series-' + this.index +
- ' highcharts-' + this.type + '-series ' +
+ // Add the class names, and replace existing ones as response to
+ // Series.update (#6660)
+ group.addClass(
(
- defined(this.colorIndex) ?
- 'highcharts-color-' + this.colorIndex + ' ' :
- ''
- ) +
- (this.options.className || '') +
- (group.hasClass('highcharts-tracker') ? ' highcharts-tracker' : '')
- ),
- true
- );
+ 'highcharts-' + name +
+ ' highcharts-series-' + this.index +
+ ' highcharts-' + this.type + '-series ' +
+ (
+ defined(this.colorIndex) ?
+ 'highcharts-color-' + this.colorIndex + ' ' :
+ ''
+ ) +
+ (this.options.className || '') +
+ (
+ group.hasClass('highcharts-tracker') ?
+ ' highcharts-tracker' :
+ ''
+ )
+ ),
+ true
+ );
- // Place it on first and subsequent (redraw) calls
- group.attr({
- visibility: visibility
- })[isNew ? 'attr' : 'animate'](
- this.getPlotBox()
- );
- return group;
- },
+ // Place it on first and subsequent (redraw) calls
+ group.attr({ visibility: visibility })[isNew ? 'attr' : 'animate'](
+ this.getPlotBox()
+ );
+ return group;
+ },
- /**
- * Get the translation and scale for the plot area of this series.
- */
- getPlotBox: function() {
- var chart = this.chart,
- xAxis = this.xAxis,
- yAxis = this.yAxis;
- // Swap axes for inverted (#2339)
- if (chart.inverted) {
- xAxis = yAxis;
- yAxis = this.xAxis;
- }
- return {
- translateX: xAxis ? xAxis.left : chart.plotLeft,
- translateY: yAxis ? yAxis.top : chart.plotTop,
- scaleX: 1, // #1623
- scaleY: 1
- };
- },
+ /**
+ * Get the translation and scale for the plot area of this series.
+ *
+ * @function Highcharts.Series#getPlotBox
+ *
+ * @return {Highcharts.SeriesPlotBoxObject}
+ */
+ getPlotBox: function () {
+ var chart = this.chart,
+ xAxis = this.xAxis,
+ yAxis = this.yAxis;
- /**
- * Render the graph and markers. Called internally when first rendering and
- * later when redrawing the chart. This function can be extended in plugins,
- * but normally shouldn't be called directly.
- */
- render: function() {
- var series = this,
- chart = series.chart,
- group,
- options = series.options,
- // Animation doesn't work in IE8 quirks when the group div is
- // hidden, and looks bad in other oldIE
- animDuration = (!!series.animate &&
- chart.renderer.isSVG &&
- animObject(options.animation).duration
- ),
- visibility = series.visible ? 'inherit' : 'hidden', // #2597
- zIndex = options.zIndex,
- hasRendered = series.hasRendered,
- chartSeriesGroup = chart.seriesGroup,
- inverted = chart.inverted;
-
- // the group
- group = series.plotGroup(
- 'group',
- 'series',
- visibility,
- zIndex,
- chartSeriesGroup
- );
+ // Swap axes for inverted (#2339)
+ if (chart.inverted) {
+ xAxis = yAxis;
+ yAxis = this.xAxis;
+ }
+ return {
+ translateX: xAxis ? xAxis.left : chart.plotLeft,
+ translateY: yAxis ? yAxis.top : chart.plotTop,
+ scaleX: 1, // #1623
+ scaleY: 1
+ };
+ },
- series.markerGroup = series.plotGroup(
- 'markerGroup',
- 'markers',
- visibility,
- zIndex,
- chartSeriesGroup
- );
+ /**
+ * Render the graph and markers. Called internally when first rendering
+ * and later when redrawing the chart. This function can be extended in
+ * plugins, but normally shouldn't be called directly.
+ *
+ * @function Highcharts.Series#render
+ *
+ * @fires Highcharts.Series#event:afterRender
+ */
+ render: function () {
+ var series = this,
+ chart = series.chart,
+ group,
+ options = series.options,
+ // Animation doesn't work in IE8 quirks when the group div is
+ // hidden, and looks bad in other oldIE
+ animDuration = (
+ !!series.animate &&
+ chart.renderer.isSVG &&
+ animObject(options.animation).duration
+ ),
+ visibility = series.visible ? 'inherit' : 'hidden', // #2597
+ zIndex = options.zIndex,
+ hasRendered = series.hasRendered,
+ chartSeriesGroup = chart.seriesGroup,
+ inverted = chart.inverted;
- // initiate the animation
- if (animDuration) {
- series.animate(true);
- }
+ fireEvent(this, 'render');
- // SVGRenderer needs to know this before drawing elements (#1089, #1795)
- group.inverted = series.isCartesian ? inverted : false;
+ // the group
+ group = series.plotGroup(
+ 'group',
+ 'series',
+ visibility,
+ zIndex,
+ chartSeriesGroup
+ );
- // draw the graph if any
- if (series.drawGraph) {
- series.drawGraph();
- series.applyZones();
- }
+ series.markerGroup = series.plotGroup(
+ 'markerGroup',
+ 'markers',
+ visibility,
+ zIndex,
+ chartSeriesGroup
+ );
- /* each(series.points, function (point) {
- if (point.redraw) {
- point.redraw();
- }
- });*/
+ // initiate the animation
+ if (animDuration) {
+ series.animate(true);
+ }
- // draw the data labels (inn pies they go before the points)
- if (series.drawDataLabels) {
- series.drawDataLabels();
- }
+ // SVGRenderer needs to know this before drawing elements (#1089,
+ // #1795)
+ group.inverted = series.isCartesian ? inverted : false;
- // draw the points
- if (series.visible) {
- series.drawPoints();
- }
+ // draw the graph if any
+ if (series.drawGraph) {
+ series.drawGraph();
+ series.applyZones();
+ }
+ /* series.points.forEach(function (point) {
+ if (point.redraw) {
+ point.redraw();
+ }
+ }); */
- // draw the mouse tracking area
- if (
- series.drawTracker &&
- series.options.enableMouseTracking !== false
- ) {
- series.drawTracker();
- }
+ // draw the data labels (inn pies they go before the points)
+ if (series.drawDataLabels) {
+ series.drawDataLabels();
+ }
- // Handle inverted series and tracker groups
- series.invertGroups(inverted);
+ // draw the points
+ if (series.visible) {
+ series.drawPoints();
+ }
- // Initial clipping, must be defined after inverting groups for VML.
- // Applies to columns etc. (#3839).
- if (options.clip !== false && !series.sharedClipKey && !hasRendered) {
- group.clip(chart.clipRect);
- }
- // Run the animation
- if (animDuration) {
- series.animate();
- }
+ // draw the mouse tracking area
+ if (
+ series.drawTracker &&
+ series.options.enableMouseTracking !== false
+ ) {
+ series.drawTracker();
+ }
- // Call the afterAnimate function on animation complete (but don't
- // overwrite the animation.complete option which should be available to
- // the user).
- if (!hasRendered) {
- series.animationTimeout = syncTimeout(function() {
- series.afterAnimate();
- }, animDuration);
- }
+ // Handle inverted series and tracker groups
+ series.invertGroups(inverted);
- series.isDirty = false; // means data is in accordance with what you see
- // (See #322) series.isDirty = series.isDirtyData = false; // means
- // data is in accordance with what you see
- series.hasRendered = true;
- },
+ // Initial clipping, must be defined after inverting groups for VML.
+ // Applies to columns etc. (#3839).
+ if (
+ options.clip !== false &&
+ !series.sharedClipKey &&
+ !hasRendered
+ ) {
+ group.clip(chart.clipRect);
+ }
- /**
- * Redraw the series. This function is called internally from `chart.redraw`
- * and normally shouldn't be called directly.
- *
- * @private
- */
- redraw: function() {
- var series = this,
- chart = series.chart,
- // cache it here as it is set to false in render, but used after
- wasDirty = series.isDirty || series.isDirtyData,
- group = series.group,
- xAxis = series.xAxis,
- yAxis = series.yAxis;
+ // Run the animation
+ if (animDuration) {
+ series.animate();
+ }
- // reposition on resize
- if (group) {
- if (chart.inverted) {
- group.attr({
- width: chart.plotWidth,
- height: chart.plotHeight
- });
+ // Call the afterAnimate function on animation complete (but don't
+ // overwrite the animation.complete option which should be available
+ // to the user).
+ if (!hasRendered) {
+ series.animationTimeout = syncTimeout(function () {
+ series.afterAnimate();
+ }, animDuration);
}
- group.animate({
- translateX: pick(xAxis && xAxis.left, chart.plotLeft),
- translateY: pick(yAxis && yAxis.top, chart.plotTop)
- });
- }
+ // Means data is in accordance with what you see
+ series.isDirty = false;
- series.translate();
- series.render();
- if (wasDirty) { // #3868, #3945
- delete this.kdTree;
- }
- },
+ // (See #322) series.isDirty = series.isDirtyData = false; // means
+ // data is in accordance with what you see
+ series.hasRendered = true;
- kdAxisArray: ['clientX', 'plotY'],
+ fireEvent(series, 'afterRender');
+ },
- searchPoint: function(e, compareX) {
- var series = this,
- xAxis = series.xAxis,
- yAxis = series.yAxis,
- inverted = series.chart.inverted;
+ /**
+ * Redraw the series. This function is called internally from
+ * `chart.redraw` and normally shouldn't be called directly.
+ *
+ * @private
+ * @function Highcharts.Series#redraw
+ */
+ redraw: function () {
+ var series = this,
+ chart = series.chart,
+ // cache it here as it is set to false in render, but used after
+ wasDirty = series.isDirty || series.isDirtyData,
+ group = series.group,
+ xAxis = series.xAxis,
+ yAxis = series.yAxis;
+
+ // reposition on resize
+ if (group) {
+ if (chart.inverted) {
+ group.attr({
+ width: chart.plotWidth,
+ height: chart.plotHeight
+ });
+ }
- return this.searchKDTree({
- clientX: inverted ?
- xAxis.len - e.chartY + xAxis.pos : e.chartX - xAxis.pos,
- plotY: inverted ?
- yAxis.len - e.chartX + yAxis.pos : e.chartY - yAxis.pos
- }, compareX);
- },
+ group.animate({
+ translateX: pick(xAxis && xAxis.left, chart.plotLeft),
+ translateY: pick(yAxis && yAxis.top, chart.plotTop)
+ });
+ }
- /**
- * Build the k-d-tree that is used by mouse and touch interaction to get the
- * closest point. Line-like series typically have a one-dimensional tree
- * where points are searched along the X axis, while scatter-like series
- * typically search in two dimensions, X and Y.
- *
- * @private
- */
- buildKDTree: function() {
+ series.translate();
+ series.render();
+ if (wasDirty) { // #3868, #3945
+ delete this.kdTree;
+ }
+ },
+
+ kdAxisArray: ['clientX', 'plotY'],
+
+ /**
+ * @private
+ * @function Highcharts.Series#searchPoint
+ *
+ * @param {object} e
+ *
+ * @param {boolean} [compareX]
+ *
+ * @return {Highcharts.Point}
+ */
+ searchPoint: function (e, compareX) {
+ var series = this,
+ xAxis = series.xAxis,
+ yAxis = series.yAxis,
+ inverted = series.chart.inverted;
+
+ return this.searchKDTree({
+ clientX: inverted ?
+ xAxis.len - e.chartY + xAxis.pos :
+ e.chartX - xAxis.pos,
+ plotY: inverted ?
+ yAxis.len - e.chartX + yAxis.pos :
+ e.chartY - yAxis.pos
+ }, compareX, e);
+ },
+
+ /**
+ * Build the k-d-tree that is used by mouse and touch interaction to get
+ * the closest point. Line-like series typically have a one-dimensional
+ * tree where points are searched along the X axis, while scatter-like
+ * series typically search in two dimensions, X and Y.
+ *
+ * @private
+ * @function Highcharts.Series#buildKDTree
+ */
+ buildKDTree: function (e) {
- // Prevent multiple k-d-trees from being built simultaneously (#6235)
- this.buildingKdTree = true;
+ // Prevent multiple k-d-trees from being built simultaneously
+ // (#6235)
+ this.buildingKdTree = true;
- var series = this,
- dimensions = series.options.findNearestPointBy.indexOf('y') > -1 ?
- 2 : 1;
+ var series = this,
+ dimensions = (
+ series.options.findNearestPointBy.indexOf('y') > -1 ? 2 : 1
+ );
- // Internal function
- function _kdtree(points, depth, dimensions) {
- var axis,
- median,
- length = points && points.length;
+ // Internal function
+ function _kdtree(points, depth, dimensions) {
+ var axis,
+ median,
+ length = points && points.length;
- if (length) {
+ if (length) {
- // alternate between the axis
- axis = series.kdAxisArray[depth % dimensions];
+ // alternate between the axis
+ axis = series.kdAxisArray[depth % dimensions];
- // sort point array
- points.sort(function(a, b) {
- return a[axis] - b[axis];
- });
+ // sort point array
+ points.sort(function (a, b) {
+ return a[axis] - b[axis];
+ });
- median = Math.floor(length / 2);
+ median = Math.floor(length / 2);
- // build and return nod
- return {
- point: points[median],
- left: _kdtree(
- points.slice(0, median), depth + 1, dimensions
- ),
- right: _kdtree(
- points.slice(median + 1), depth + 1, dimensions
- )
- };
+ // build and return nod
+ return {
+ point: points[median],
+ left: _kdtree(
+ points.slice(0, median), depth + 1, dimensions
+ ),
+ right: _kdtree(
+ points.slice(median + 1), depth + 1, dimensions
+ )
+ };
+ }
}
- }
- // Start the recursive build process with a clone of the points array
- // and null points filtered out (#3873)
- function startRecursive() {
- series.kdTree = _kdtree(
- series.getValidPoints(
- null,
- // For line-type series restrict to plot area, but
- // column-type series not (#3916, #4511)
- !series.directTouch
- ),
- dimensions,
- dimensions
+ // Start the recursive build process with a clone of the points
+ // array and null points filtered out (#3873)
+ function startRecursive() {
+ series.kdTree = _kdtree(
+ series.getValidPoints(
+ null,
+ // For line-type series restrict to plot area, but
+ // column-type series not (#3916, #4511)
+ !series.directTouch
+ ),
+ dimensions,
+ dimensions
+ );
+ series.buildingKdTree = false;
+ }
+ delete series.kdTree;
+
+ // For testing tooltips, don't build async. Also if touchstart, we
+ // may be dealing with click events on mobile, so don't delay
+ // (#6817).
+ syncTimeout(
+ startRecursive,
+ series.options.kdNow || (e && e.type === 'touchstart') ? 0 : 1
);
- series.buildingKdTree = false;
- }
- delete series.kdTree;
+ },
- // For testing tooltips, don't build async
- syncTimeout(startRecursive, series.options.kdNow ? 0 : 1);
- },
+ /**
+ * @private
+ * @function Highcharts.Series#searchKDTree
+ *
+ * @param {object} point
+ *
+ * @param {boolean} [compareX]
+ *
+ * @return {Highcharts.Point}
+ */
+ searchKDTree: function (point, compareX, e) {
+ var series = this,
+ kdX = this.kdAxisArray[0],
+ kdY = this.kdAxisArray[1],
+ kdComparer = compareX ? 'distX' : 'dist',
+ kdDimensions = series.options.findNearestPointBy
+ .indexOf('y') > -1 ? 2 : 1;
+
+ // Set the one and two dimensional distance on the point object
+ function setDistance(p1, p2) {
+ var x = (defined(p1[kdX]) && defined(p2[kdX])) ?
+ Math.pow(p1[kdX] - p2[kdX], 2) :
+ null,
+ y = (defined(p1[kdY]) && defined(p2[kdY])) ?
+ Math.pow(p1[kdY] - p2[kdY], 2) :
+ null,
+ r = (x || 0) + (y || 0);
+
+ p2.dist = defined(r) ? Math.sqrt(r) : Number.MAX_VALUE;
+ p2.distX = defined(x) ? Math.sqrt(x) : Number.MAX_VALUE;
+ }
+ function _search(search, tree, depth, dimensions) {
+ var point = tree.point,
+ axis = series.kdAxisArray[depth % dimensions],
+ tdist,
+ sideA,
+ sideB,
+ ret = point,
+ nPoint1,
+ nPoint2;
+
+ setDistance(search, point);
+
+ // Pick side based on distance to splitting point
+ tdist = search[axis] - point[axis];
+ sideA = tdist < 0 ? 'left' : 'right';
+ sideB = tdist < 0 ? 'right' : 'left';
+
+ // End of tree
+ if (tree[sideA]) {
+ nPoint1 = _search(
+ search, tree[sideA], depth + 1, dimensions
+ );
- searchKDTree: function(point, compareX) {
- var series = this,
- kdX = this.kdAxisArray[0],
- kdY = this.kdAxisArray[1],
- kdComparer = compareX ? 'distX' : 'dist',
- kdDimensions = series.options.findNearestPointBy.indexOf('y') > -1 ?
- 2 : 1;
-
- // Set the one and two dimensional distance on the point object
- function setDistance(p1, p2) {
- var x = (defined(p1[kdX]) && defined(p2[kdX])) ?
- Math.pow(p1[kdX] - p2[kdX], 2) :
- null,
- y = (defined(p1[kdY]) && defined(p2[kdY])) ?
- Math.pow(p1[kdY] - p2[kdY], 2) :
- null,
- r = (x || 0) + (y || 0);
-
- p2.dist = defined(r) ? Math.sqrt(r) : Number.MAX_VALUE;
- p2.distX = defined(x) ? Math.sqrt(x) : Number.MAX_VALUE;
- }
-
- function _search(search, tree, depth, dimensions) {
- var point = tree.point,
- axis = series.kdAxisArray[depth % dimensions],
- tdist,
- sideA,
- sideB,
- ret = point,
- nPoint1,
- nPoint2;
-
- setDistance(search, point);
-
- // Pick side based on distance to splitting point
- tdist = search[axis] - point[axis];
- sideA = tdist < 0 ? 'left' : 'right';
- sideB = tdist < 0 ? 'right' : 'left';
-
- // End of tree
- if (tree[sideA]) {
- nPoint1 = _search(search, tree[sideA], depth + 1, dimensions);
-
- ret = (nPoint1[kdComparer] < ret[kdComparer] ? nPoint1 : point);
- }
- if (tree[sideB]) {
- // compare distance to current best to splitting point to decide
- // wether to check side B or not
- if (Math.sqrt(tdist * tdist) < ret[kdComparer]) {
- nPoint2 = _search(
- search,
- tree[sideB],
- depth + 1,
- dimensions
+ ret = (
+ nPoint1[kdComparer] < ret[kdComparer] ? nPoint1 : point
);
- ret = nPoint2[kdComparer] < ret[kdComparer] ?
- nPoint2 :
- ret;
}
+ if (tree[sideB]) {
+ // compare distance to current best to splitting point to
+ // decide wether to check side B or not
+ if (Math.sqrt(tdist * tdist) < ret[kdComparer]) {
+ nPoint2 = _search(
+ search,
+ tree[sideB],
+ depth + 1,
+ dimensions
+ );
+ ret = nPoint2[kdComparer] < ret[kdComparer] ?
+ nPoint2 :
+ ret;
+ }
+ }
+
+ return ret;
}
- return ret;
- }
+ if (!this.kdTree && !this.buildingKdTree) {
+ this.buildKDTree(e);
+ }
- if (!this.kdTree && !this.buildingKdTree) {
- this.buildKDTree();
- }
+ if (this.kdTree) {
+ return _search(point, this.kdTree, kdDimensions, kdDimensions);
+ }
+ },
+
+ /**
+ * @private
+ * @function Highcharts.Series#pointPlacementToXValue
+ *
+ * @return {number}
+ */
+ pointPlacementToXValue: function () {
+
+ var series = this,
+ pointPlacement = series.options.pointPlacement;
+
+ // Point placement is relative to each series pointRange (#5889)
+ if (pointPlacement === 'between') {
+ pointPlacement = 0.5;
+ }
+ if (isNumber(pointPlacement)) {
+ pointPlacement *=
+ pick(series.options.pointRange || series.xAxis.pointRange);
+ }
- if (this.kdTree) {
- return _search(point, this.kdTree, kdDimensions, kdDimensions);
+ return pointPlacement;
}
}
-
- }); // end Series prototype
+ ); // end Series prototype
/**
* A line series displays information as a series of data points connected by
* straight line segments.
*
- * @sample {highcharts} highcharts/demo/line-basic/ Line chart
- * @sample {highstock} stock/demo/basic-line/ Line chart
- *
- * @extends plotOptions.series
- * @product highcharts highstock
+ * @sample {highcharts} highcharts/demo/line-basic/
+ * Line chart
+ * @sample {highstock} stock/demo/basic-line/
+ * Line chart
+ *
+ * @extends plotOptions.series
+ * @product highcharts highstock
* @apioption plotOptions.line
*/
+ /**
+ * The SVG value used for the `stroke-linecap` and `stroke-linejoin`
+ * of a line graph. Round means that lines are rounded in the ends and
+ * bends.
+ *
+ * @type {string}
+ * @validvalue ["round", "butt", "square"]
+ * @default round
+ * @since 3.0.7
+ * @apioption plotOptions.line.linecap
+ */
+
/**
* A `line` series. If the [type](#series.line.type) option is not
* specified, it is inherited from [chart.type](#chart.type).
- *
- * For options that apply to multiple series, it is recommended to add
- * them to the [plotOptions.series](#plotOptions.series) options structure.
- * To apply to all series of this specific type, apply it to [plotOptions.
- * line](#plotOptions.line).
- *
- * @type {Object}
- * @extends series,plotOptions.line
+ *
+ * In TypeScript instead the `type` option must always be set.
+ *
+ * @extends series,plotOptions.line
* @excluding dataParser,dataURL
- * @product highcharts highstock
+ * @product highcharts highstock
* @apioption series.line
*/
/**
* An array of data points for the series. For the `line` series type,
* points can be given in the following ways:
- *
- * 1. An array of numerical values. In this case, the numerical values
- * will be interpreted as `y` options. The `x` values will be automatically
- * calculated, either starting at 0 and incremented by 1, or from `pointStart`
- * and `pointInterval` given in the series options. If the axis has
- * categories, these will be used. Example:
- *
- * ```js
- * data: [0, 5, 3, 5]
- * ```
- *
- * 2. An array of arrays with 2 values. In this case, the values correspond
- * to `x,y`. If the first value is a string, it is applied as the name
- * of the point, and the `x` value is inferred.
- *
- * ```js
- * data: [
- * [0, 1],
- * [1, 2],
- * [2, 8]
- * ]
- * ```
- *
- * 3. An array of objects with named values. The objects are point
- * configuration objects as seen below. If the total number of data
- * points exceeds the series' [turboThreshold](#series.line.turboThreshold),
- * this option is not available.
- *
- * ```js
- * data: [{
- * x: 1,
- * y: 9,
- * name: "Point2",
- * color: "#00FF00"
- * }, {
- * x: 1,
- * y: 6,
- * name: "Point1",
- * color: "#FF00FF"
- * }]
- * ```
- *
- * @type {Array}
- * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
- * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
- * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
- * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
- * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
+ *
+ * 1. An array of numerical values. In this case, the numerical values will be
+ * interpreted as `y` options. The `x` values will be automatically
+ * calculated, either starting at 0 and incremented by 1, or from
+ * `pointStart` and `pointInterval` given in the series options. If the axis
+ * has categories, these will be used. Example:
+ * ```js
+ * data: [0, 5, 3, 5]
+ * ```
+ *
+ * 2. An array of arrays with 2 values. In this case, the values correspond to
+ * `x,y`. If the first value is a string, it is applied as the name of the
+ * point, and the `x` value is inferred.
+ * ```js
+ * data: [
+ * [0, 1],
+ * [1, 2],
+ * [2, 8]
+ * ]
+ * ```
+ *
+ * 3. An array of objects with named values. The following snippet shows only a
+ * few settings, see the complete options set below. If the total number of
+ * data points exceeds the series'
+ * [turboThreshold](#series.line.turboThreshold), this option is not
+ * available.
+ * ```js
+ * data: [{
+ * x: 1,
+ * y: 9,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * y: 6,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @sample {highcharts} highcharts/chart/reflow-true/
+ * Numerical values
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/
+ * Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
+ * Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/
+ * Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
+ *
+ * @type {Array|*>}
* @apioption series.line.data
*/
/**
* An additional, individual class name for the data point's graphic
* representation.
- *
- * @type {String}
- * @since 5.0.0
- * @product highcharts
+ *
+ * @type {string}
+ * @since 5.0.0
+ * @product highcharts gantt
* @apioption series.line.data.className
*/
@@ -26876,58 +35593,62 @@
* Individual color for the point. By default the color is pulled from
* the global `colors` array.
*
- * In styled mode, the `color` option doesn't take effect. Instead, use
+ * In styled mode, the `color` option doesn't take effect. Instead, use
* `colorIndex`.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/point/color/ Mark the highest point
- * @default undefined
- * @product highcharts highstock
+ *
+ * @sample {highcharts} highcharts/point/color/
+ * Mark the highest point
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @product highcharts highstock gantt
* @apioption series.line.data.color
*/
/**
- * Styled mode only. A specific color index to use for the point, so its
- * graphic representations are given the class name
- * `highcharts-color-{n}`.
- *
- * @type {Number}
- * @since 5.0.0
- * @product highcharts
+ * A specific color index to use for the point, so its graphic representations
+ * are given the class name `highcharts-color-{n}`. In styled mode this will
+ * change the color of the graphic. In non-styled mode, the color by is set by
+ * the `fill` attribute, so the change in class name won't have a visual effect
+ * by default.
+ *
+ * @type {number}
+ * @since 5.0.0
+ * @product highcharts gantt
* @apioption series.line.data.colorIndex
*/
/**
* Individual data label for each point. The options are the same as
- * the ones for [plotOptions.series.dataLabels](#plotOptions.series.
- * dataLabels)
- *
- * @type {Object}
- * @sample {highcharts} highcharts/point/datalabels/ Show a label for the last value
- * @sample {highstock} highcharts/point/datalabels/ Show a label for the last value
- * @product highcharts highstock
+ * the ones for [plotOptions.series.dataLabels](
+ * #plotOptions.series.dataLabels).
+ *
+ * @sample highcharts/point/datalabels/
+ * Show a label for the last value
+ *
+ * @type {Highcharts.PlotSeriesDataLabelsOptions}
+ * @product highcharts highstock gantt
* @apioption series.line.data.dataLabels
*/
/**
* A description of the point to add to the screen reader information
* about the point. Requires the Accessibility module.
- *
- * @type {String}
- * @default undefined
- * @since 5.0.0
+ *
+ * @type {string}
+ * @since 5.0.0
* @apioption series.line.data.description
*/
/**
* An id for the point. This can be used after render time to get a
* pointer to the point object through `chart.get()`.
- *
- * @type {String}
- * @sample {highcharts} highcharts/point/id/ Remove an id'd point
- * @default null
- * @since 1.2.0
- * @product highcharts highstock
+ *
+ * @sample {highcharts} highcharts/point/id/
+ * Remove an id'd point
+ *
+ * @type {string}
+ * @since 1.2.0
+ * @product highcharts highstock gantt
* @apioption series.line.data.id
*/
@@ -26935,75 +35656,79 @@
* The rank for this point's data label in case of collision. If two
* data labels are about to overlap, only the one with the highest `labelrank`
* will be drawn.
- *
- * @type {Number}
+ *
+ * @type {number}
* @apioption series.line.data.labelrank
*/
/**
* The name of the point as shown in the legend, tooltip, dataLabel
* etc.
- *
- * @type {String}
- * @sample {highcharts} highcharts/series/data-array-of-objects/ Point names
+ *
* @see [xAxis.uniqueNames](#xAxis.uniqueNames)
+ *
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Point names
+ *
+ * @type {string}
* @apioption series.line.data.name
*/
/**
* Whether the data point is selected initially.
- *
- * @type {Boolean}
- * @default false
- * @product highcharts highstock
+ *
+ * @type {boolean}
+ * @default false
+ * @product highcharts highstock gantt
* @apioption series.line.data.selected
*/
/**
* The x value of the point. For datetime axes, the X value is the timestamp
* in milliseconds since 1970.
- *
- * @type {Number}
- * @product highcharts highstock
+ *
+ * @type {number}
+ * @product highcharts highstock
* @apioption series.line.data.x
*/
/**
* The y value of the point.
- *
- * @type {Number}
- * @default null
- * @product highcharts highstock
+ *
+ * @type {number}
+ * @product highcharts highstock
* @apioption series.line.data.y
*/
/**
* Individual point events
- *
- * @extends plotOptions.series.point.events
- * @product highcharts highstock
+ *
+ * @extends plotOptions.series.point.events
+ * @product highcharts highstock gantt
* @apioption series.line.data.events
*/
/**
- * @extends plotOptions.series.marker
- * @product highcharts highstock
+ * @extends plotOptions.series.marker
+ * @product highcharts highstock
* @apioption series.line.data.marker
*/
}(Highcharts));
- (function(H) {
+ (function (H) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
+
var Axis = H.Axis,
Chart = H.Chart,
correctFloat = H.correctFloat,
defined = H.defined,
destroyObjectProperties = H.destroyObjectProperties,
- each = H.each,
format = H.format,
objectEach = H.objectEach,
pick = H.pick,
@@ -27013,9 +35738,21 @@
* The class for stacks. Each stack, on a specific X value and either negative
* or positive, has its own stack item.
*
+ * @private
* @class
+ * @name Highcharts.StackItem
+ *
+ * @param {Highcharts.Axis} axis
+ *
+ * @param {Highcharts.Options} options
+ *
+ * @param {boolean} isNegative
+ *
+ * @param {number} x
+ *
+ * @param {string|*} stackOption
*/
- H.StackItem = function(axis, options, isNegative, x, stackOption) {
+ H.StackItem = function (axis, options, isNegative, x, stackOption) {
var inverted = axis.chart.inverted;
@@ -27033,17 +35770,17 @@
// Initialize total value
this.total = null;
- // This will keep each points' extremes stored by series.index and point
+ // This will keep each points' extremes stored by series.index and point
// index
this.points = {};
- // Save the stack option on the series configuration object, and whether to
+ // Save the stack option on the series configuration object, and whether to
// treat it as percent
this.stack = stackOption;
this.leftCliff = 0;
this.rightCliff = 0;
- // The align options and text align varies on whether the stack is negative
+ // The align options and text align varies on whether the stack is negative
// and if the chart is inverted or not.
// First test the user supplied value, then use the dynamic.
this.alignOptions = {
@@ -27060,46 +35797,64 @@
};
H.StackItem.prototype = {
- destroy: function() {
+
+ /**
+ * @private
+ * @function Highcharts.StackItem#destroy
+ */
+ destroy: function () {
destroyObjectProperties(this, this.axis);
},
/**
* Renders the stack total label and adds it to the stack label group.
+ *
+ * @private
+ * @function Highcharts.StackItem#render
+ *
+ * @param {Highcharts.SVGElement} group
*/
- render: function(group) {
- var options = this.options,
+ render: function (group) {
+ var chart = this.axis.chart,
+ options = this.options,
formatOption = options.format,
str = formatOption ?
- format(formatOption, this) :
- options.formatter.call(this); // format the text in the label
+ format(formatOption, this, chart.time) :
+ options.formatter.call(this); // format the text in the label
// Change the text to reflect the new total and set visibility to hidden
// in case the serie is hidden
if (this.label) {
- this.label.attr({
- text: str,
- visibility: 'hidden'
- });
- // Create new label
+ this.label.attr({ text: str, visibility: 'hidden' });
+ // Create new label
} else {
this.label =
- this.axis.chart.renderer.text(str, null, null, options.useHTML)
- .css(options.style)
- .attr({
- align: this.textAlign,
- rotation: options.rotation,
- visibility: 'hidden' // hidden until setOffset is called
- })
- .add(group); // add to the labels-group
+ chart.renderer.text(str, null, null, options.useHTML)
+ .css(options.style)
+ .attr({
+ align: this.textAlign,
+ rotation: options.rotation,
+ visibility: 'hidden' // hidden until setOffset is called
+ })
+ .add(group); // add to the labels-group
}
+
+ // Rank it higher than data labels (#8742)
+ this.label.labelrank = chart.plotHeight;
},
/**
* Sets the offset that the stack has from the x value and repositions the
* label.
+ *
+ * @private
+ * @function Highcarts.StackItem#setOffset
+ *
+ * @param {number} xOffset
+ *
+ * @param {number} xWidth
*/
- setOffset: function(xOffset, xWidth) {
+ setOffset: function (xOffset, xWidth) {
var stackItem = this,
axis = stackItem.axis,
chart = axis.chart,
@@ -27112,13 +35867,21 @@
1
),
yZero = axis.translate(0), // stack origin
- h = Math.abs(y - yZero), // stack height
+ h = defined(y) && Math.abs(y - yZero), // stack height
x = chart.xAxis[0].translate(stackItem.x) + xOffset, // x position
- stackBox = stackItem.getStackBox(chart, stackItem, x, y, xWidth, h),
+ stackBox = defined(y) && stackItem.getStackBox(
+ chart,
+ stackItem,
+ x,
+ y,
+ xWidth,
+ h,
+ axis
+ ),
label = stackItem.label,
alignAttr;
- if (label) {
+ if (label && stackBox) {
// Align the label to the box
label.align(stackItem.alignOptions, null, stackBox);
@@ -27131,20 +35894,42 @@
) ? 'show' : 'hide'](true);
}
},
- getStackBox: function(chart, stackItem, x, y, xWidth, h) {
+
+ /**
+ * @private
+ * @function Highcharts.StackItem#getStackBox
+ *
+ * @param {Highcharts.Chart} chart
+ *
+ * @param {Highcharts.StackItem} stackItem
+ *
+ * @param {number} x
+ *
+ * @param {number} y
+ *
+ * @param {number} xWidth
+ *
+ * @param {number} h
+ *
+ * @param {Highcharts.Axis} axis
+ *
+ * @return {*}
+ */
+ getStackBox: function (chart, stackItem, x, y, xWidth, h, axis) {
var reversed = stackItem.axis.reversed,
inverted = chart.inverted,
- plotHeight = chart.plotHeight,
+ axisPos = axis.height + axis.pos - (inverted ? chart.plotLeft :
+ chart.plotTop),
neg = (stackItem.isNegative && !reversed) ||
- (!stackItem.isNegative && reversed); // #4056
+ (!stackItem.isNegative && reversed); // #4056
return { // this is the box for the complete stack
x: inverted ? (neg ? y : y - h) : x,
y: inverted ?
- plotHeight - x - xWidth :
+ axisPos - x - xWidth :
(neg ?
- (plotHeight - y - h) :
- plotHeight - y
+ (axisPos - y - h) :
+ axisPos - y
),
width: inverted ? h : xWidth,
height: inverted ? xWidth : h
@@ -27154,18 +35939,21 @@
/**
* Generate stacks for each series and calculate stacks total values
+ *
+ * @private
+ * @function Highcharts.Chart#getStacks
*/
- Chart.prototype.getStacks = function() {
+ Chart.prototype.getStacks = function () {
var chart = this;
// reset stacks for each yAxis
- each(chart.yAxis, function(axis) {
+ chart.yAxis.forEach(function (axis) {
if (axis.stacks && axis.hasVisibleSeries) {
axis.oldStacks = axis.stacks;
}
});
- each(chart.series, function(series) {
+ chart.series.forEach(function (series) {
if (series.options.stacking && (series.visible === true ||
chart.options.chart.ignoreHiddenSeries === false)) {
series.stackKey = series.type + pick(series.options.stack, '');
@@ -27178,12 +35966,16 @@
/**
* Build the stacks from top down
+ *
+ * @private
+ * @function Highcharts.Axis#buildStacks
*/
- Axis.prototype.buildStacks = function() {
+ Axis.prototype.buildStacks = function () {
var axisSeries = this.series,
reversedStacks = pick(this.options.reversedStacks, true),
len = axisSeries.length,
i;
+
if (!this.isXAxis) {
this.usePercentage = false;
i = len;
@@ -27198,7 +35990,11 @@
}
};
- Axis.prototype.renderStackTotals = function() {
+ /**
+ * @private
+ * @function Highcharts.Axis#renderStackTotals
+ */
+ Axis.prototype.renderStackTotals = function () {
var axis = this,
chart = axis.chart,
renderer = chart.renderer,
@@ -27209,11 +36005,11 @@
if (!stackTotalGroup) {
axis.stackTotalGroup = stackTotalGroup =
renderer.g('stack-labels')
- .attr({
- visibility: 'visible',
- zIndex: 6
- })
- .add();
+ .attr({
+ visibility: 'visible',
+ zIndex: 6
+ })
+ .add();
}
// plotLeft/Top will change when y axis gets wider so we need to translate
@@ -27221,8 +36017,8 @@
stackTotalGroup.translate(chart.plotLeft, chart.plotTop);
// Render each stack total
- objectEach(stacks, function(type) {
- objectEach(type, function(stack) {
+ objectEach(stacks, function (type) {
+ objectEach(type, function (stack) {
stack.render(stackTotalGroup);
});
});
@@ -27230,29 +36026,37 @@
/**
* Set all the stacks to initial states and destroy unused ones.
+ *
+ * @private
+ * @function Highcharts.Axis#resetStacks
*/
- Axis.prototype.resetStacks = function() {
+ Axis.prototype.resetStacks = function () {
var axis = this,
stacks = axis.stacks;
+
if (!axis.isXAxis) {
- objectEach(stacks, function(type) {
- objectEach(type, function(stack, key) {
+ objectEach(stacks, function (type) {
+ objectEach(type, function (stack, key) {
// Clean up memory after point deletion (#1044, #4320)
if (stack.touched < axis.stacksTouched) {
stack.destroy();
delete type[key];
- // Reset stacks
+ // Reset stacks
} else {
stack.total = null;
- stack.cum = null;
+ stack.cumulative = null;
}
});
});
}
};
- Axis.prototype.cleanStacks = function() {
+ /**
+ * @private
+ * @function Highcharts.Axis#cleanStacks
+ */
+ Axis.prototype.cleanStacks = function () {
var stacks;
if (!this.isXAxis) {
@@ -27261,9 +36065,9 @@
}
// reset stacks
- objectEach(stacks, function(type) {
- objectEach(type, function(stack) {
- stack.cum = stack.total;
+ objectEach(stacks, function (type) {
+ objectEach(type, function (stack) {
+ stack.cumulative = stack.total;
});
});
}
@@ -27274,8 +36078,11 @@
/**
* Adds series' points value to corresponding stack
+ *
+ * @private
+ * @function Highcharts.Series#setStackedPoints
*/
- Series.prototype.setStackedPoints = function() {
+ Series.prototype.setStackedPoints = function () {
if (!this.options.stacking || (this.visible !== true &&
this.chart.options.chart.ignoreHiddenSeries !== false)) {
return;
@@ -27288,7 +36095,7 @@
yDataLength = yData.length,
seriesOptions = series.options,
threshold = seriesOptions.threshold,
- stackThreshold = seriesOptions.startFromThreshold ? threshold : 0,
+ stackThreshold = pick(seriesOptions.startFromThreshold && threshold, 0),
stackOption = seriesOptions.stack,
stacking = seriesOptions.stacking,
stackKey = series.stackKey,
@@ -27350,10 +36157,11 @@
// If the StackItem doesn't exist, create it first
stack = stacks[key][x];
if (y !== null) {
- stack.points[pointKey] = stack.points[series.index] = [pick(stack.cum, stackThreshold)];
+ stack.points[pointKey] = stack.points[series.index] =
+ [pick(stack.cumulative, stackThreshold)];
// Record the base of the stack
- if (!defined(stack.cum)) {
+ if (!defined(stack.cumulative)) {
stack.base = pointKey;
}
stack.touched = yAxis.stacksTouched;
@@ -27365,6 +36173,10 @@
stack.points[pointKey][0] =
stack.points[series.index + ',' + x + ',0'][0];
}
+
+ // When updating to null, reset the point stack (#7493)
+ } else {
+ stack.points[pointKey] = stack.points[series.index] = null;
}
// Add value to the stack total
@@ -27378,7 +36190,7 @@
stack.total = other.total =
Math.max(other.total, stack.total) + Math.abs(y) || 0;
- // Percent stacked areas
+ // Percent stacked areas
} else {
stack.total = correctFloat(stack.total + (Math.abs(y) || 0));
}
@@ -27386,11 +36198,11 @@
stack.total = correctFloat(stack.total + (y || 0));
}
- stack.cum = pick(stack.cum, stackThreshold) + (y || 0);
+ stack.cumulative = pick(stack.cumulative, stackThreshold) + (y || 0);
if (y !== null) {
- stack.points[pointKey].push(stack.cum);
- stackedYData[i] = stack.cum;
+ stack.points[pointKey].push(stack.cumulative);
+ stackedYData[i] = stack.cumulative;
}
}
@@ -27407,8 +36219,11 @@
/**
* Iterate over all stacks and compute the absolute values to percent
+ *
+ * @private
+ * @function Highcharts.Series#modifyStacks
*/
- Series.prototype.modifyStacks = function() {
+ Series.prototype.modifyStacks = function () {
var series = this,
stackKey = series.stackKey,
stacks = series.yAxis.stacks,
@@ -27417,7 +36232,7 @@
stacking = series.options.stacking;
if (series[stacking + 'Stacker']) { // Modifier function exists
- each([stackKey, '-' + stackKey], function(key) {
+ [stackKey, '-' + stackKey].forEach(function (key) {
var i = processedXData.length,
x,
stack,
@@ -27443,9 +36258,19 @@
/**
* Modifier function for percent stacks. Blows up the stack to 100%.
+ *
+ * @private
+ * @function Highcharts.Series#percentStacker
+ *
+ * @param {Array} pointExtremes
+ *
+ * @param {Highcharts.StackItem} stack
+ *
+ * @param {number} i
*/
- Series.prototype.percentStacker = function(pointExtremes, stack, i) {
+ Series.prototype.percentStacker = function (pointExtremes, stack, i) {
var totalFactor = stack.total ? 100 / stack.total : 0;
+
// Y bottom value
pointExtremes[0] = correctFloat(pointExtremes[0] * totalFactor);
// Y value
@@ -27456,13 +36281,26 @@
/**
* Get stack indicator, according to it's x-value, to determine points with the
* same x-value
+ *
+ * @private
+ * @function Highcharts.Series#getStackIndicator
+ *
+ * @param {*} stackIndicator
+ *
+ * @param {number} x
+ *
+ * @param {number} index
+ *
+ * @param {string} key
+ *
+ * @return {*}
*/
- Series.prototype.getStackIndicator = function(stackIndicator, x, index, key) {
+ Series.prototype.getStackIndicator = function (stackIndicator, x, index, key) {
// Update stack indicator, when:
// first point in a stack || x changed || stack type (negative vs positive)
// changed:
if (!defined(stackIndicator) || stackIndicator.x !== x ||
- (key && stackIndicator.key !== key)) {
+ (key && stackIndicator.key !== key)) {
stackIndicator = {
x: x,
index: 0,
@@ -27478,12 +36316,15 @@
};
}(Highcharts));
- (function(H) {
+ (function (H) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
+
var addEvent = H.addEvent,
animate = H.animate,
Axis = H.Axis,
@@ -27491,11 +36332,9 @@
createElement = H.createElement,
css = H.css,
defined = H.defined,
- each = H.each,
erase = H.erase,
extend = H.extend,
fireEvent = H.fireEvent,
- inArray = H.inArray,
isNumber = H.isNumber,
isObject = H.isObject,
isArray = H.isArray,
@@ -27508,6 +36347,31 @@
setAnimation = H.setAnimation,
splat = H.splat;
+
+ // Remove settings that have not changed, to avoid unnecessary rendering or
+ // computing (#9197)
+ H.cleanRecursively = function (newer, older) {
+ var result = {};
+
+ objectEach(newer, function (val, key) {
+ var ob;
+
+ // Dive into objects
+ if (isObject(newer[key], true) && older[key]) {
+ ob = H.cleanRecursively(newer[key], older[key]);
+ if (Object.keys(ob).length) {
+ result[key] = ob;
+ }
+
+ // Arrays or primitives are copied directly
+ } else if (isObject(newer[key]) || newer[key] !== older[key]) {
+ result[key] = newer[key];
+ }
+ });
+
+ return result;
+ };
+
// Extend the Chart prototype for dynamic methods
extend(Chart.prototype, /** @lends Highcharts.Chart.prototype */ {
@@ -27518,36 +36382,44 @@
* same time as the chart is initialized, add the series as a configuration
* option instead. With multiple axes, the `offset` is dynamically adjusted.
*
- * @param {SeriesOptions} options
- * The config options for the series.
- * @param {Boolean} [redraw=true]
- * Whether to redraw the chart after adding.
- * @param {AnimationOptions} animation
- * Whether to apply animation, and optionally animation
- * configuration.
- *
- * @return {Highcharts.Series}
- * The newly created series object.
- *
* @sample highcharts/members/chart-addseries/
* Add a series from a button
* @sample stock/members/chart-addseries/
* Add a series in Highstock
+ *
+ * @function Highcharts.Chart#addSeries
+ *
+ * @param {Highcharts.SeriesOptionsType} options
+ * The config options for the series.
+ *
+ * @param {boolean} [redraw=true]
+ * Whether to redraw the chart after adding.
+ *
+ * @param {boolean|Highcharts.AnimationOptionsObject} [animation]
+ * Whether to apply animation, and optionally animation
+ * configuration.
+ *
+ * @return {Highcharts.Series}
+ * The newly created series object.
+ *
+ * @fires Highcharts.Chart#event:addSeries
+ * @fires Highcharts.Chart#event:afterAddSeries
*/
- addSeries: function(options, redraw, animation) {
+ addSeries: function (options, redraw, animation) {
var series,
chart = this;
if (options) {
redraw = pick(redraw, true); // defaults to true
- fireEvent(chart, 'addSeries', {
- options: options
- }, function() {
+ fireEvent(chart, 'addSeries', { options: options }, function () {
series = chart.initSeries(options);
- chart.isDirtyLegend = true; // the series array is out of sync with the display
+ chart.isDirtyLegend = true;
chart.linkSeries();
+
+ fireEvent(chart, 'afterAddSeries');
+
if (redraw) {
chart.redraw(animation);
}
@@ -27563,21 +36435,28 @@
* adds expense to the calculations and rendering. When adding data at the
* same time as the chart is initialized, add the axis as a configuration
* option instead.
- * @param {AxisOptions} options
- * The axis options.
- * @param {Boolean} [isX=false]
- * Whether it is an X axis or a value axis.
- * @param {Boolean} [redraw=true]
- * Whether to redraw the chart after adding.
- * @param {AnimationOptions} [animation=true]
- * Whether and how to apply animation in the redraw.
- *
- * @sample highcharts/members/chart-addaxis/ Add and remove axes
- *
- * @return {Axis}
+ *
+ * @sample highcharts/members/chart-addaxis/
+ * Add and remove axes
+ *
+ * @function Highcharts.Chart#addAxis
+ *
+ * @param {Highcharts.XAxisOptions|Highcharts.YAxisOptions|Highcharts.ZAxisOptions} options
+ * The axis options.
+ *
+ * @param {boolean} [isX=false]
+ * Whether it is an X axis or a value axis.
+ *
+ * @param {boolean} [redraw=true]
+ * Whether to redraw the chart after adding.
+ *
+ * @param {boolean|Highcharts.AnimationOptionsObject} [animation=true]
+ * Whether and how to apply animation in the redraw.
+ *
+ * @return {Highcharts.Axis}
* The newly generated Axis object.
*/
- addAxis: function(options, isX, redraw, animation) {
+ addAxis: function (options, isX, redraw, animation) {
var key = isX ? 'xAxis' : 'yAxis',
chartOptions = this.options,
userOptions = merge(options, {
@@ -27603,11 +36482,6 @@
* Dim the chart and show a loading text or symbol. Options for the loading
* screen are defined in {@link
* https://api.highcharts.com/highcharts/loading|the loading options}.
- *
- * @param {String} str
- * An optional text to show in the loading label instead of the
- * default one. The default text is set in {@link
- * http://api.highcharts.com/highcharts/lang.loading|lang.loading}.
*
* @sample highcharts/members/chart-hideloading/
* Show and hide loading from a button
@@ -27615,13 +36489,20 @@
* Apply different text labels
* @sample stock/members/chart-show-hide-loading/
* Toggle loading in Highstock
+ *
+ * @function Highcharts.Chart#showLoading
+ *
+ * @param {string} [str]
+ * An optional text to show in the loading label instead of the
+ * default one. The default text is set in
+ * [lang.loading](http://api.highcharts.com/highcharts/lang.loading).
*/
- showLoading: function(str) {
+ showLoading: function (str) {
var chart = this,
options = chart.options,
loadingDiv = chart.loadingDiv,
loadingOptions = options.loading,
- setLoadingSize = function() {
+ setLoadingSize = function () {
if (loadingDiv) {
css(loadingDiv, {
left: chart.plotLeft + 'px',
@@ -27639,9 +36520,8 @@
}, null, chart.container);
chart.loadingSpan = createElement(
- 'span', {
- className: 'highcharts-loading-inner'
- },
+ 'span',
+ { className: 'highcharts-loading-inner' },
null,
loadingDiv
);
@@ -27653,27 +36533,27 @@
// Update text
chart.loadingSpan.innerHTML = str || options.lang.loading;
+ if (!chart.styledMode) {
+ // Update visuals
+ css(loadingDiv, extend(loadingOptions.style, {
+ zIndex: 10
+ }));
+ css(chart.loadingSpan, loadingOptions.labelStyle);
- // Update visuals
- css(loadingDiv, extend(loadingOptions.style, {
- zIndex: 10
- }));
- css(chart.loadingSpan, loadingOptions.labelStyle);
-
- // Show it
- if (!chart.loadingShown) {
- css(loadingDiv, {
- opacity: 0,
- display: ''
- });
- animate(loadingDiv, {
- opacity: loadingOptions.style.opacity || 0.5
- }, {
- duration: loadingOptions.showDuration || 0
- });
+ // Show it
+ if (!chart.loadingShown) {
+ css(loadingDiv, {
+ opacity: 0,
+ display: ''
+ });
+ animate(loadingDiv, {
+ opacity: loadingOptions.style.opacity || 0.5
+ }, {
+ duration: loadingOptions.showDuration || 0
+ });
+ }
}
-
chart.loadingShown = true;
setLoadingSize();
},
@@ -27681,60 +36561,102 @@
/**
* Hide the loading layer.
*
- * @see Highcharts.Chart#showLoading
+ * @see Highcharts.Chart#showLoading
+ *
* @sample highcharts/members/chart-hideloading/
* Show and hide loading from a button
* @sample stock/members/chart-show-hide-loading/
* Toggle loading in Highstock
+ *
+ * @function Highcharts.Chart#hideLoading
*/
- hideLoading: function() {
+ hideLoading: function () {
+
var options = this.options,
loadingDiv = this.loadingDiv;
if (loadingDiv) {
- loadingDiv.className = 'highcharts-loading highcharts-loading-hidden';
-
- animate(loadingDiv, {
- opacity: 0
- }, {
- duration: options.loading.hideDuration || 100,
- complete: function() {
- css(loadingDiv, {
- display: 'none'
- });
- }
- });
-
+ loadingDiv.className =
+ 'highcharts-loading highcharts-loading-hidden';
+
+ if (!this.styledMode) {
+ animate(loadingDiv, {
+ opacity: 0
+ }, {
+ duration: options.loading.hideDuration || 100,
+ complete: function () {
+ css(loadingDiv, { display: 'none' });
+ }
+ });
+ }
}
+
this.loadingShown = false;
},
- /**
- * These properties cause isDirtyBox to be set to true when updating. Can be extended from plugins.
+ /**
+ * These properties cause isDirtyBox to be set to true when updating. Can be
+ * extended from plugins.
*/
- propsRequireDirtyBox: ['backgroundColor', 'borderColor', 'borderWidth', 'margin', 'marginTop', 'marginRight',
- 'marginBottom', 'marginLeft', 'spacing', 'spacingTop', 'spacingRight', 'spacingBottom', 'spacingLeft',
- 'borderRadius', 'plotBackgroundColor', 'plotBackgroundImage', 'plotBorderColor', 'plotBorderWidth',
- 'plotShadow', 'shadow'
+ propsRequireDirtyBox: [
+ 'backgroundColor',
+ 'borderColor',
+ 'borderWidth',
+ 'margin',
+ 'marginTop',
+ 'marginRight',
+ 'marginBottom',
+ 'marginLeft',
+ 'spacing',
+ 'spacingTop',
+ 'spacingRight',
+ 'spacingBottom',
+ 'spacingLeft',
+ 'borderRadius',
+ 'plotBackgroundColor',
+ 'plotBackgroundImage',
+ 'plotBorderColor',
+ 'plotBorderWidth',
+ 'plotShadow',
+ 'shadow'
],
- /**
+ /**
* These properties cause all series to be updated when updating. Can be
* extended from plugins.
*/
- propsRequireUpdateSeries: ['chart.inverted', 'chart.polar',
- 'chart.ignoreHiddenSeries', 'chart.type', 'colors', 'plotOptions',
+ propsRequireUpdateSeries: [
+ 'chart.inverted',
+ 'chart.polar',
+ 'chart.ignoreHiddenSeries',
+ 'chart.type',
+ 'colors',
+ 'plotOptions',
+ 'time',
'tooltip'
],
+ /**
+ * These collections (arrays) implement update() methods with support for
+ * one-to-one option.
+ */
+ collectionsWithUpdate: [
+ 'xAxis',
+ 'yAxis',
+ 'zAxis',
+ 'series',
+ 'colorAxis',
+ 'pane'
+ ],
+
/**
* A generic function to update any element of the chart. Elements can be
* enabled and disabled, moved, re-styled, re-formatted etc.
*
* A special case is configuration objects that take arrays, for example
- * {@link https://api.highcharts.com/highcharts/xAxis|xAxis},
- * {@link https://api.highcharts.com/highcharts/yAxis|yAxis} or
- * {@link https://api.highcharts.com/highcharts/series|series}. For these
+ * [xAxis](https://api.highcharts.com/highcharts/xAxis),
+ * [yAxis](https://api.highcharts.com/highcharts/yAxis) or
+ * [series](https://api.highcharts.com/highcharts/series). For these
* collections, an `id` option is used to map the new option set to an
* existing object. If an existing object of the same id is not found, the
* corresponding item is updated. So for example, running `chart.update`
@@ -27744,45 +36666,75 @@
* adding and removing items from the collection. Read more under the
* parameter description below.
*
- * See also the {@link https://api.highcharts.com/highcharts/responsive|
- * responsive option set}. Switching between `responsive.rules` basically
- * runs `chart.update` under the hood.
- *
- * @param {Options} options
- * A configuration object for the new chart options.
- * @param {Boolean} [redraw=true]
- * Whether to redraw the chart.
- * @param {Boolean} [oneToOne=false]
- * When `true`, the `series`, `xAxis` and `yAxis` collections will
- * be updated one to one, and items will be either added or removed
- * to match the new updated options. For example, if the chart has
- * two series and we call `chart.update` with a configuration
- * containing three series, one will be added. If we call
- * `chart.update` with one series, one will be removed. Setting an
- * empty `series` array will remove all series, but leaving out the
- * `series` property will leave all series untouched. If the series
- * have id's, the new series options will be matched by id, and the
- * remaining ones removed.
+ * Note that when changing series data, `chart.update` may mutate the passed
+ * data options.
+ *
+ * See also the
+ * [responsive option set](https://api.highcharts.com/highcharts/responsive).
+ * Switching between `responsive.rules` basically runs `chart.update` under
+ * the hood.
*
* @sample highcharts/members/chart-update/
- * Update chart geometry
+ * Update chart geometry
+ *
+ * @function Highcharts.Chart#update
+ *
+ * @param {Highcharts.Options} options
+ * A configuration object for the new chart options.
+ *
+ * @param {boolean} [redraw=true]
+ * Whether to redraw the chart.
+ *
+ * @param {boolean} [oneToOne=false]
+ * When `true`, the `series`, `xAxis` and `yAxis` collections will
+ * be updated one to one, and items will be either added or removed
+ * to match the new updated options. For example, if the chart has
+ * two series and we call `chart.update` with a configuration
+ * containing three series, one will be added. If we call
+ * `chart.update` with one series, one will be removed. Setting an
+ * empty `series` array will remove all series, but leaving out the
+ * `series` property will leave all series untouched. If the series
+ * have id's, the new series options will be matched by id, and the
+ * remaining ones removed.
+ *
+ * @param {boolean|Highcharts.AnimationOptionsObject} [animation=true]
+ * Whether to apply animation, and optionally animation
+ * configuration.
+ *
+ * @fires Highcharts.Chart#event:update
+ * @fires Highcharts.Chart#event:afterUpdate
*/
- update: function(options, redraw, oneToOne) {
+ update: function (options, redraw, oneToOne, animation) {
var chart = this,
adders = {
credits: 'addCredits',
title: 'setTitle',
subtitle: 'setSubtitle'
},
- optionsChart = options.chart,
+ optionsChart,
updateAllAxes,
updateAllSeries,
newWidth,
newHeight,
itemsForRemoval = [];
- // If the top-level chart option is present, some special updates are required
+ fireEvent(chart, 'update', { options: options });
+
+ // If there are responsive rules in action, undo the responsive rules
+ // before we apply the updated options and replay the responsive rules
+ // on top from the chart.redraw function (#9617).
+ if (!options.isResponsiveOptions) {
+ chart.setResponsive(false, true);
+ }
+
+ options = H.cleanRecursively(options, chart.options);
+
+ // If the top-level chart option is present, some special updates are
+ // required
+ optionsChart = options.chart;
+
if (optionsChart) {
+
merge(true, chart.options.chart, optionsChart);
// Setter function
@@ -27790,7 +36742,15 @@
chart.setClassName(optionsChart.className);
}
- if ('inverted' in optionsChart || 'polar' in optionsChart) {
+ if ('reflow' in optionsChart) {
+ chart.setReflow(optionsChart.reflow);
+ }
+
+ if (
+ 'inverted' in optionsChart ||
+ 'polar' in optionsChart ||
+ 'type' in optionsChart
+ ) {
// Parse options.chart.inverted and options.chart.polar together
// with the available series.
chart.propFromSeries();
@@ -27801,30 +36761,29 @@
updateAllAxes = true;
}
- objectEach(optionsChart, function(val, key) {
- if (inArray('chart.' + key, chart.propsRequireUpdateSeries) !== -1) {
+ objectEach(optionsChart, function (val, key) {
+ if (
+ chart.propsRequireUpdateSeries.indexOf('chart.' + key) !==
+ -1
+ ) {
updateAllSeries = true;
}
// Only dirty box
- if (inArray(key, chart.propsRequireDirtyBox) !== -1) {
+ if (chart.propsRequireDirtyBox.indexOf(key) !== -1) {
chart.isDirtyBox = true;
}
});
-
- if ('style' in optionsChart) {
+ if (!chart.styledMode && 'style' in optionsChart) {
chart.renderer.setStyle(optionsChart.style);
}
-
}
// Moved up, because tooltip needs updated plotOptions (#6218)
-
- if (options.colors) {
+ if (!chart.styledMode && options.colors) {
this.options.colors = options.colors;
}
-
if (options.plotOptions) {
merge(true, this.options.plotOptions, options.plotOptions);
}
@@ -27839,18 +36798,18 @@
// options.mapNavigation => chart.mapNavigation
// options.navigator => chart.navigator
// options.scrollbar => chart.scrollbar
- objectEach(options, function(val, key) {
+ objectEach(options, function (val, key) {
if (chart[key] && typeof chart[key].update === 'function') {
chart[key].update(val, false);
- // If a one-to-one object does not exist, look for an adder function
+ // If a one-to-one object does not exist, look for an adder function
} else if (typeof chart[adders[key]] === 'function') {
chart[adders[key]](val);
}
if (
key !== 'chart' &&
- inArray(key, chart.propsRequireUpdateSeries) !== -1
+ chart.propsRequireUpdateSeries.indexOf(key) !== -1
) {
updateAllSeries = true;
}
@@ -27862,20 +36821,30 @@
// update the first series in the chart. Setting two series without
// an id will update the first and the second respectively (#6019)
// chart.update and responsive.
- each([
- 'xAxis',
- 'yAxis',
- 'zAxis',
- 'series',
- 'colorAxis',
- 'pane'
- ], function(coll) {
+ this.collectionsWithUpdate.forEach(function (coll) {
+ var indexMap;
+
if (options[coll]) {
- each(splat(options[coll]), function(newOptions, i) {
+
+ // In stock charts, the navigator series are also part of the
+ // chart.series array, but those series should not be handled
+ // here (#8196).
+ if (coll === 'series') {
+ indexMap = [];
+ chart[coll].forEach(function (s, i) {
+ if (!s.options.isInternal) {
+ indexMap.push(pick(s.options.index, i));
+ }
+ });
+ }
+
+
+ splat(options[coll]).forEach(function (newOptions, i) {
var item = (
defined(newOptions.id) &&
chart.get(newOptions.id)
- ) || chart[coll][i];
+ ) || chart[coll][indexMap ? indexMap[i] : i];
+
if (item && item.coll === coll) {
item.update(newOptions, false);
@@ -27899,8 +36868,8 @@
// Add items for removal
if (oneToOne) {
- each(chart[coll], function(item) {
- if (!item.touched) {
+ chart[coll].forEach(function (item) {
+ if (!item.touched && !item.options.isInternal) {
itemsForRemoval.push(item);
} else {
delete item.touched;
@@ -27912,12 +36881,14 @@
}
});
- each(itemsForRemoval, function(item) {
- item.remove(false);
+ itemsForRemoval.forEach(function (item) {
+ if (item.remove) {
+ item.remove(false);
+ }
});
if (updateAllAxes) {
- each(chart.axes, function(axis) {
+ chart.axes.forEach(function (axis) {
axis.update({}, false);
});
}
@@ -27925,7 +36896,7 @@
// Certain options require the whole series structure to be thrown away
// and rebuilt
if (updateAllSeries) {
- each(chart.series, function(series) {
+ chart.series.forEach(function (series) {
series.update({}, false);
});
}
@@ -27939,22 +36910,27 @@
newWidth = optionsChart && optionsChart.width;
newHeight = optionsChart && optionsChart.height;
if ((isNumber(newWidth) && newWidth !== chart.chartWidth) ||
- (isNumber(newHeight) && newHeight !== chart.chartHeight)) {
- chart.setSize(newWidth, newHeight);
+ (isNumber(newHeight) && newHeight !== chart.chartHeight)) {
+ chart.setSize(newWidth, newHeight, animation);
} else if (pick(redraw, true)) {
- chart.redraw();
+ chart.redraw(animation);
}
+
+ fireEvent(chart, 'afterUpdate', { options: options });
+
},
/**
* Shortcut to set the subtitle options. This can also be done from {@link
* Chart#update} or {@link Chart#setTitle}.
*
- * @param {SubtitleOptions} options
- * New subtitle options. The subtitle text itself is set by the
- * `options.text` property.
+ * @function Highcharts.Chart#setSubtitle
+ *
+ * @param {Highcharts.SubtitleOptions} options
+ * New subtitle options. The subtitle text itself is set by the
+ * `options.text` property.
*/
- setSubtitle: function(options) {
+ setSubtitle: function (options) {
this.setTitle(undefined, options);
}
@@ -27967,29 +36943,35 @@
* Update point with new options (typically x/y data) and optionally redraw
* the series.
*
- * @param {Object} options
- * The point options. Point options are handled as described under
- * the `series.type.data` item for each series type. For example
- * for a line series, if options is a single number, the point will
- * be given that number as the main y value. If it is an array, it
- * will be interpreted as x and y values respectively. If it is an
- * object, advanced options are applied.
- * @param {Boolean} [redraw=true]
- * Whether to redraw the chart after the point is updated. If doing
- * more operations on the chart, it is best practice to set
- * `redraw` to false and call `chart.redraw()` after.
- * @param {AnimationOptions} [animation=true]
- * Whether to apply animation, and optionally animation
- * configuration.
- *
* @sample highcharts/members/point-update-column/
* Update column value
* @sample highcharts/members/point-update-pie/
* Update pie slice
* @sample maps/members/point-update/
* Update map area value in Highmaps
+ *
+ * @function Highcharts.Point#update
+ *
+ * @param {number|object|Array|null} options
+ * The point options. Point options are handled as described under
+ * the `series.type.data` item for each series type. For example
+ * for a line series, if options is a single number, the point will
+ * be given that number as the marin y value. If it is an array, it
+ * will be interpreted as x and y values respectively. If it is an
+ * object, advanced options are applied.
+ *
+ * @param {boolean} [redraw=true]
+ * Whether to redraw the chart after the point is updated. If doing
+ * more operations on the chart, it is best practice to set
+ * `redraw` to false and call `chart.redraw()` after.
+ *
+ * @param {boolean|Highcharts.AnimationOptionsObject} [animation=true]
+ * Whether to apply animation, and optionally animation
+ * configuration.
+ *
+ * @fires Highcharts.Point#event:update
*/
- update: function(options, redraw, animation, runEvent) {
+ update: function (options, redraw, animation, runEvent) {
var point = this,
series = point.series,
graphic = point.graphic,
@@ -28011,7 +36993,11 @@
// Destroy so we can get new elements
if (graphic && graphic.element) {
// "null" is also a valid symbol
- if (options && options.marker && options.marker.symbol !== undefined) {
+ if (
+ options &&
+ options.marker &&
+ options.marker.symbol !== undefined
+ ) {
point.graphic = graphic.destroy();
}
}
@@ -28031,11 +37017,11 @@
// is an object, use point options, otherwise use raw options
// (#4701, #4916).
seriesOptions.data[i] = (
- isObject(seriesOptions.data[i], true) ||
+ isObject(seriesOptions.data[i], true) ||
isObject(options, true)
- ) ?
+ ) ?
point.options :
- options;
+ pick(options, seriesOptions.data[i]);
// redraw
series.isDirty = series.isDirtyData = true;
@@ -28055,22 +37041,12 @@
if (runEvent === false) { // When called from setData
update();
} else {
- point.firePointEvent('update', {
- options: options
- }, update);
+ point.firePointEvent('update', { options: options }, update);
}
},
/**
* Remove a point and optionally redraw the series and if necessary the axes
- * @param {Boolean} redraw
- * Whether to redraw the chart or wait for an explicit call. When
- * doing more operations on the chart, for example running
- * `point.remove()` in a loop, it is best practice to set `redraw`
- * to false and call `chart.redraw()` after.
- * @param {AnimationOptions} [animation=false]
- * Whether to apply animation, and optionally animation
- * configuration.
*
* @sample highcharts/plotoptions/series-point-events-remove/
* Remove point and confirm
@@ -28078,9 +37054,25 @@
* Remove pie slice
* @sample maps/members/point-remove/
* Remove selected points in Highmaps
+ *
+ * @function Highcharts.Point#remove
+ *
+ * @param {boolean} redraw
+ * Whether to redraw the chart or wait for an explicit call. When
+ * doing more operations on the chart, for example running
+ * `point.remove()` in a loop, it is best practice to set `redraw`
+ * to false and call `chart.redraw()` after.
+ *
+ * @param {boolean|Highcharts.AnimationOptionsObject} [animation=false]
+ * Whether to apply animation, and optionally animation
+ * configuration.
*/
- remove: function(redraw, animation) {
- this.series.removePoint(inArray(this, this.series.data), redraw, animation);
+ remove: function (redraw, animation) {
+ this.series.removePoint(
+ this.series.data.indexOf(this),
+ redraw,
+ animation
+ );
}
});
@@ -28090,25 +37082,6 @@
* Add a point to the series after render time. The point can be added at
* the end, or by giving it an X value, to the start or in the middle of the
* series.
- *
- * @param {Number|Array|Object} options
- * The point options. If options is a single number, a point with
- * that y value is appended to the series.If it is an array, it will
- * be interpreted as x and y values respectively. If it is an
- * object, advanced options as outlined under `series.data` are
- * applied.
- * @param {Boolean} [redraw=true]
- * Whether to redraw the chart after the point is added. When adding
- * more than one point, it is highly recommended that the redraw
- * option be set to false, and instead {@link Chart#redraw}
- * is explicitly called after the adding of points is finished.
- * Otherwise, the chart will redraw after adding each point.
- * @param {Boolean} [shift=false]
- * If true, a point is shifted off the start of the series as one is
- * appended to the end.
- * @param {AnimationOptions} [animation]
- * Whether to apply animation, and optionally animation
- * configuration.
*
* @sample highcharts/members/series-addpoint-append/
* Append point
@@ -28124,8 +37097,32 @@
* Append and shift in Highstock
* @sample maps/members/series-addpoint/
* Add a point in Highmaps
+ *
+ * @function Highcharts.Series#addPoint
+ *
+ * @param {number|object|Array|null} options
+ * The point options. If options is a single number, a point with
+ * that y value is appended to the series. If it is an array, it will
+ * be interpreted as x and y values respectively. If it is an
+ * object, advanced options as outlined under `series.data` are
+ * applied.
+ *
+ * @param {boolean} [redraw=true]
+ * Whether to redraw the chart after the point is added. When adding
+ * more than one point, it is highly recommended that the redraw
+ * option be set to false, and instead {@link Chart#redraw} is
+ * explicitly called after the adding of points is finished.
+ * Otherwise, the chart will redraw after adding each point.
+ *
+ * @param {boolean} [shift=false]
+ * If true, a point is shifted off the start of the series as one is
+ * appended to the end.
+ *
+ * @param {boolean|Highcharts.AnimationOptionsObject} [animation]
+ * Whether to apply animation, and optionally animation
+ * configuration.
*/
- addPoint: function(options, redraw, shift, animation) {
+ addPoint: function (options, redraw, shift, animation) {
var series = this,
seriesOptions = series.options,
data = series.data,
@@ -28142,11 +37139,10 @@
// Optional redraw, defaults to true
redraw = pick(redraw, true);
- // Get options and push the point to xData, yData and series.options. In series.generatePoints
- // the Point instance will be created on demand and pushed to the series.data array.
- point = {
- series: series
- };
+ // Get options and push the point to xData, yData and series.options. In
+ // series.generatePoints the Point instance will be created on demand
+ // and pushed to the series.data array.
+ point = { series: series };
series.pointClass.prototype.applyOptions.apply(point, [options]);
x = point.x;
@@ -28159,8 +37155,10 @@
}
}
- series.updateParallelArrays(point, 'splice', i, 0, 0); // insert undefined item
- series.updateParallelArrays(point, i); // update it
+ // Insert undefined item
+ series.updateParallelArrays(point, 'splice', i, 0, 0);
+ // Update it
+ series.updateParallelArrays(point, i);
if (names && point.name) {
names[x] = point.name;
@@ -28199,42 +37197,52 @@
},
/**
- * Remove a point from the series. Unlike the {@link Highcharts.Point#remove}
- * method, this can also be done on a point that is not instanciated because
- * it is outside the view or subject to Highstock data grouping.
- *
- * @param {Number} i
- * The index of the point in the {@link Highcharts.Series.data|data}
- * array.
- * @param {Boolean} [redraw=true]
- * Whether to redraw the chart after the point is added. When
- * removing more than one point, it is highly recommended that the
- * `redraw` option be set to `false`, and instead {@link
- * Highcharts.Chart#redraw} is explicitly called after the adding of
- * points is finished.
- * @param {AnimationOptions} [animation]
- * Whether and optionally how the series should be animated.
+ * Remove a point from the series. Unlike the
+ * {@link Highcharts.Point#remove} method, this can also be done on a point
+ * that is not instanciated because it is outside the view or subject to
+ * Highstock data grouping.
*
* @sample highcharts/members/series-removepoint/
* Remove cropped point
+ *
+ * @function Highcharts.Series#removePoint
+ *
+ * @param {number} i
+ * The index of the point in the {@link Highcharts.Series.data|data}
+ * array.
+ *
+ * @param {boolean} [redraw=true]
+ * Whether to redraw the chart after the point is added. When
+ * removing more than one point, it is highly recommended that the
+ * `redraw` option be set to `false`, and instead {@link
+ * Highcharts.Chart#redraw} is explicitly called after the adding of
+ * points is finished.
+ *
+ * @param {boolean|Highcharts.AnimationOptionsObject} [animation]
+ * Whether and optionally how the series should be animated.
+ *
+ * @fires Highcharts.Point#event:remove
*/
- removePoint: function(i, redraw, animation) {
+ removePoint: function (i, redraw, animation) {
var series = this,
data = series.data,
point = data[i],
points = series.points,
chart = series.chart,
- remove = function() {
+ remove = function () {
if (points && points.length === data.length) { // #4935
points.splice(i, 1);
}
data.splice(i, 1);
series.options.data.splice(i, 1);
- series.updateParallelArrays(point || {
- series: series
- }, 'splice', i, 1);
+ series.updateParallelArrays(
+ point || { series: series },
+ 'splice',
+ i,
+ 1
+ );
if (point) {
point.destroy();
@@ -28262,26 +37270,33 @@
/**
* Remove a series and optionally redraw the chart.
*
- * @param {Boolean} [redraw=true]
- * Whether to redraw the chart or wait for an explicit call to
- * {@link Highcharts.Chart#redraw}.
- * @param {AnimationOptions} [animation]
- * Whether to apply animation, and optionally animation
- * configuration
- * @param {Boolean} [withEvent=true]
- * Used internally, whether to fire the series `remove` event.
- *
* @sample highcharts/members/series-remove/
* Remove first series from a button
+ *
+ * @function Highcharts.Series#remove
+ *
+ * @param {boolean} [redraw=true]
+ * Whether to redraw the chart or wait for an explicit call to
+ * {@link Highcharts.Chart#redraw}.
+ *
+ * @param {boolean|Highcharts.AnimationOptionsObject} [animation]
+ * Whether to apply animation, and optionally animation
+ * configuration.
+ *
+ * @param {boolean} [withEvent=true]
+ * Used internally, whether to fire the series `remove` event.
+ *
+ * @fires Highcharts.Series#event:remove
*/
- remove: function(redraw, animation, withEvent) {
+ remove: function (redraw, animation, withEvent, keepEvents) {
var series = this,
chart = series.chart;
function remove() {
// Destroy elements
- series.destroy();
+ series.destroy(keepEvents);
+ series.remove = null; // Prevent from doing again (#9097)
// Redraw
chart.isDirtyLegend = chart.isDirtyBox = true;
@@ -28303,34 +37318,47 @@
/**
* Update the series with a new set of options. For a clean and precise
* handling of new options, all methods and elements from the series are
- * removed, and it is initiated from scratch. Therefore, this method is more
- * performance expensive than some other utility methods like {@link
+ * removed, and it is initialized from scratch. Therefore, this method is
+ * more performance expensive than some other utility methods like {@link
* Series#setData} or {@link Series#setVisible}.
*
- * @param {SeriesOptions} options
- * New options that will be merged with the series' existing
- * options.
- * @param {Boolean} [redraw=true]
- * Whether to redraw the chart after the series is altered. If doing
- * more operations on the chart, it is a good idea to set redraw to
- * false and call {@link Chart#redraw} after.
+ * Note that `Series.update` may mutate the passed `data` options.
*
* @sample highcharts/members/series-update/
* Updating series options
* @sample maps/members/series-update/
* Update series options in Highmaps
+ *
+ * @function Highcharts.Series#update
+ *
+ * @param {Highcharts.SeriesOptionsType} options
+ * New options that will be merged with the series' existing options.
+ *
+ * @param {boolean} [redraw=true]
+ * Whether to redraw the chart after the series is altered. If doing
+ * more operations on the chart, it is a good idea to set redraw to
+ * false and call {@link Chart#redraw} after.
+ *
+ * @fires Highcharts.Series#event:afterUpdate
*/
- update: function(newOptions, redraw) {
+ update: function (options, redraw) {
+
+ options = H.cleanRecursively(options, this.userOptions);
+
var series = this,
chart = series.chart,
// must use user options when changing type because series.options
// is merged in with type specific plotOptions
oldOptions = series.userOptions,
- oldType = series.oldType || series.type,
- newType = newOptions.type || oldOptions.type || chart.options.chart.type,
- proto = seriesTypes[oldType].prototype,
+ initialType = series.initialType || series.type,
+ newType = (
+ options.type ||
+ oldOptions.type ||
+ chart.options.chart.type
+ ),
+ initialSeriesProto = seriesTypes[initialType].prototype,
n,
- preserveGroups = [
+ groups = [
'group',
'markerGroup',
'dataLabelsGroup'
@@ -28344,61 +37372,106 @@
// animation has first run. This happens when calling update
// directly after chart initialization, or when applying responsive
// rules (#6912).
- animation = series.finishedAnimating && {
- animation: false
- };
+ animation = series.finishedAnimating && { animation: false },
+ allowSoftUpdate = [
+ 'data',
+ 'name',
+ 'turboThreshold'
+ ],
+ keys = Object.keys(options),
+ doSoftUpdate = keys.length > 0;
// Running Series.update to update the data only is an intuitive usage,
// so we want to make sure that when used like this, we run the
// cheaper setData function and allow animation instead of completely
- // recreating the series instance.
- if (Object.keys && Object.keys(newOptions).toString() === 'data') {
- return this.setData(newOptions.data, redraw);
- }
+ // recreating the series instance. This includes sideways animation when
+ // adding points to the data set. The `name` should also support soft
+ // update because the data module sets name and data when setting new
+ // data by `chart.update`.
+ keys.forEach(function (key) {
+ if (allowSoftUpdate.indexOf(key) === -1) {
+ doSoftUpdate = false;
+ }
+ });
+ if (doSoftUpdate) {
+ if (options.data) {
+ this.setData(options.data, false);
+ }
+ if (options.name) {
+ this.setName(options.name, false);
+ }
+ } else {
- // If we're changing type or zIndex, create new groups (#3380, #3404)
- // Also create new groups for navigator series.
- if (
- (newType && newType !== oldType) ||
- newOptions.zIndex !== undefined
- ) {
- preserveGroups.length = 0;
- }
+ // Make sure preserved properties are not destroyed (#3094)
+ preserve = groups.concat(preserve);
+ preserve.forEach(function (prop) {
+ preserve[prop] = series[prop];
+ delete series[prop];
+ });
- // Make sure preserved properties are not destroyed (#3094)
- preserve = preserveGroups.concat(preserve);
- each(preserve, function(prop) {
- preserve[prop] = series[prop];
- delete series[prop];
- });
+ // Do the merge, with some forced options
+ options = merge(oldOptions, animation, {
+ index: series.index,
+ pointStart: pick(
+ oldOptions.pointStart, // when updating from blank (#7933)
+ series.xData[0] // when updating after addPoint
+ )
+ }, { data: series.options.data }, options);
- // Do the merge, with some forced options
- newOptions = merge(oldOptions, animation, {
- index: series.index,
- pointStart: series.xData[0] // when updating after addPoint
- }, {
- data: series.options.data
- }, newOptions);
+ // Destroy the series and delete all properties. Reinsert all
+ // methods and properties from the new type prototype (#2270,
+ // #3719).
+ series.remove(false, null, false, true);
+ for (n in initialSeriesProto) {
+ series[n] = undefined;
+ }
+ if (seriesTypes[newType || initialType]) {
+ extend(series, seriesTypes[newType || initialType].prototype);
+ } else {
+ H.error(17, true, chart);
+ }
- // Destroy the series and delete all properties. Reinsert all methods
- // and properties from the new type prototype (#2270, #3719)
- series.remove(false, null, false);
- for (n in proto) {
- series[n] = undefined;
- }
- extend(series, seriesTypes[newType || oldType].prototype);
+ // Re-register groups (#3094) and other preserved properties
+ preserve.forEach(function (prop) {
+ series[prop] = preserve[prop];
+ });
- // Re-register groups (#3094) and other preserved properties
- each(preserve, function(prop) {
- series[prop] = preserve[prop];
- });
+ series.init(chart, options);
+
+ // Update the Z index of groups (#3380, #7397)
+ if (options.zIndex !== oldOptions.zIndex) {
+ groups.forEach(function (groupName) {
+ if (series[groupName]) {
+ series[groupName].attr({
+ zIndex: options.zIndex
+ });
+ }
+ });
+ }
+
+
+ series.initialType = initialType;
+ chart.linkSeries(); // Links are lost in series.remove (#3028)
+
+ }
+ fireEvent(this, 'afterUpdate');
- series.init(chart, newOptions);
- series.oldType = oldType;
- chart.linkSeries(); // Links are lost in series.remove (#3028)
if (pick(redraw, true)) {
- chart.redraw(false);
+ chart.redraw(doSoftUpdate ? undefined : false);
}
+ },
+
+ /**
+ * Used from within series.update
+ *
+ * @private
+ * @function Highcharts.Series#setName
+ *
+ * @param {string} name
+ */
+ setName: function (name) {
+ this.name = this.options.name = this.userOptions.name = name;
+ this.chart.isDirtyLegend = true;
}
});
@@ -28410,22 +37483,45 @@
* with the existing options, so only new or altered options need to be
* specified.
*
- * @param {Object} options
- * The new options that will be merged in with existing options on
- * the axis.
- * @sample highcharts/members/axis-update/ Axis update demo
+ * @sample highcharts/members/axis-update/
+ * Axis update demo
+ *
+ * @function Highcharts.Axis#update
+ *
+ * @param {Highcharts.XAxisOptions|Highcharts.YAxisOptions|Highcharts.ZAxisOptions} options
+ * The new options that will be merged in with existing options on
+ * the axis.
+ *
+ * @param {boolean} [redraw=true]
+ * Whether to redraw the chart after the axis is altered. If doing
+ * more operations on the chart, it is a good idea to set redraw to
+ * false and call {@link Chart#redraw} after.
*/
- update: function(options, redraw) {
- var chart = this.chart;
+ update: function (options, redraw) {
+ var chart = this.chart,
+ newEvents = ((options && options.events) || {});
- options = chart.options[this.coll][this.options.index] =
- merge(this.userOptions, options);
+ options = merge(this.userOptions, options);
- this.destroy(true);
+ // Color Axis is not an array,
+ // This change is applied in the ColorAxis wrapper
+ if (chart.options[this.coll].indexOf) {
+ // Don't use this.options.index,
+ // StockChart has Axes in navigator too
+ chart.options[this.coll][
+ chart.options[this.coll].indexOf(this.userOptions)
+ ] = options;
+ }
+
+ // Remove old events, if no new exist (#8161)
+ objectEach(chart.options[this.coll].events, function (fn, ev) {
+ if (typeof newEvents[ev] === 'undefined') {
+ newEvents[ev] = undefined;
+ }
+ });
- this.init(chart, extend(options, {
- events: undefined
- }));
+ this.destroy(true);
+ this.init(chart, extend(options, { events: newEvents }));
chart.isDirtyBox = true;
if (pick(redraw, true)) {
@@ -28436,12 +37532,15 @@
/**
* Remove the axis from the chart.
*
- * @param {Boolean} [redraw=true] Whether to redraw the chart following the
- * remove.
+ * @sample highcharts/members/chart-addaxis/
+ * Add and remove axes
*
- * @sample highcharts/members/chart-addaxis/ Add and remove axes
+ * @function Highcharts.Axis#remove
+ *
+ * @param {boolean} [redraw=true]
+ * Whether to redraw the chart following the remove.
*/
- remove: function(redraw) {
+ remove: function (redraw) {
var chart = this.chart,
key = this.coll, // xAxis or yAxis
axisSeries = this.series,
@@ -28464,8 +37563,8 @@
delete chart.options[key];
}
- each(chart[key], function(axis, i) { // Re-index, #1706
- axis.options.index = i;
+ chart[key].forEach(function (axis, i) { // Re-index, #1706, #8075
+ axis.options.index = axis.userOptions.index = i;
});
this.destroy();
chart.isDirtyBox = true;
@@ -28478,776 +37577,863 @@
/**
* Update the axis title by options after render time.
*
- * @param {TitleOptions} titleOptions
- * The additional title options.
- * @param {Boolean} [redraw=true]
- * Whether to redraw the chart after setting the title.
- * @sample highcharts/members/axis-settitle/ Set a new Y axis title
+ * @sample highcharts/members/axis-settitle/
+ * Set a new Y axis title
+ *
+ * @function Highcharts.Axis#setTitle
+ *
+ * @param {Highcharts.XAxisTitleOptions|Highcharts.YAxisTitleOptions|Highcharts.ZAxisTitleOptions} titleOptions
+ * The additional title options.
+ *
+ * @param {boolean} [redraw=true]
+ * Whether to redraw the chart after setting the title.
*/
- setTitle: function(titleOptions, redraw) {
- this.update({
- title: titleOptions
- }, redraw);
+ setTitle: function (titleOptions, redraw) {
+ this.update({ title: titleOptions }, redraw);
},
/**
* Set new axis categories and optionally redraw.
- * @param {Array.} categories - The new categories.
- * @param {Boolean} [redraw=true] - Whether to redraw the chart.
- * @sample highcharts/members/axis-setcategories/ Set categories by click on
- * a button
- */
- setCategories: function(categories, redraw) {
- this.update({
- categories: categories
- }, redraw);
+ *
+ * @sample highcharts/members/axis-setcategories/
+ * Set categories by click on a button
+ *
+ * @function Highcharts.Axis#setCategories
+ *
+ * @param {Array} categories
+ * The new categories.
+ *
+ * @param {boolean} [redraw=true]
+ * Whether to redraw the chart.
+ */
+ setCategories: function (categories, redraw) {
+ this.update({ categories: categories }, redraw);
}
});
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
+
var color = H.color,
- each = H.each,
LegendSymbolMixin = H.LegendSymbolMixin,
- map = H.map,
pick = H.pick,
Series = H.Series,
seriesType = H.seriesType;
/**
* Area series type.
- * @constructor seriesTypes.area
- * @extends {Series}
- */
- /**
- * The area series type.
- * @extends {plotOptions.line}
- * @product highcharts highstock
- * @sample {highcharts} highcharts/demo/area-basic/
- * Area chart
- * @sample {highstock} stock/demo/area/
- * Area chart
- * @optionparent plotOptions.area
- */
- seriesType('area', 'line', {
-
- /**
- * Fill color or gradient for the area. When `null`, the series' `color`
- * is used with the series' `fillOpacity`.
- *
- * @type {Color}
- * @see In styled mode, the fill color can be set with the `.highcharts-area` class name.
- * @sample {highcharts} highcharts/plotoptions/area-fillcolor-default/ Null by default
- * @sample {highcharts} highcharts/plotoptions/area-fillcolor-gradient/ Gradient
- * @default null
- * @product highcharts highstock
- * @apioption plotOptions.area.fillColor
- */
+ *
+ * @private
+ * @class
+ * @name Highcharts.seriesTypes.area
+ *
+ * @augments Highcharts.Series
+ */
+ seriesType('area', 'line',
/**
- * Fill opacity for the area. When you set an explicit `fillColor`,
- * the `fillOpacity` is not applied. Instead, you should define the
- * opacity in the `fillColor` with an rgba color definition. The `fillOpacity`
- * setting, also the default setting, overrides the alpha component
- * of the `color` setting.
- *
- * @type {Number}
- * @see In styled mode, the fill opacity can be set with the `.highcharts-area` class name.
- * @sample {highcharts} highcharts/plotoptions/area-fillopacity/ Automatic fill color and fill opacity of 0.1
- * @default {highcharts} 0.75
- * @default {highstock} .75
- * @product highcharts highstock
- * @apioption plotOptions.area.fillOpacity
+ * The area series type.
+ *
+ * @sample {highcharts} highcharts/demo/area-basic/
+ * Area chart
+ * @sample {highstock} stock/demo/area/
+ * Area chart
+ *
+ * @extends plotOptions.line
+ * @excluding useOhlcData
+ * @product highcharts highstock
+ * @optionparent plotOptions.area
*/
+ {
- /**
- * A separate color for the graph line. By default the line takes the
- * `color` of the series, but the lineColor setting allows setting a
- * separate color for the line without altering the `fillColor`.
- *
- * @type {Color}
- * @see In styled mode, the line stroke can be set with the `.highcharts-graph` class name.
- * @sample {highcharts} highcharts/plotoptions/area-linecolor/ Dark gray line
- * @default null
- * @product highcharts highstock
- * @apioption plotOptions.area.lineColor
- */
+ /**
+ * Fill color or gradient for the area. When `null`, the series' `color`
+ * is used with the series' `fillOpacity`.
+ *
+ * In styled mode, the fill color can be set with the `.highcharts-area`
+ * class name.
+ *
+ * @sample {highcharts} highcharts/plotoptions/area-fillcolor-default/
+ * Null by default
+ * @sample {highcharts} highcharts/plotoptions/area-fillcolor-gradient/
+ * Gradient
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @product highcharts highstock
+ * @apioption plotOptions.area.fillColor
+ */
- /**
+ /**
+ * Fill opacity for the area. When you set an explicit `fillColor`,
+ * the `fillOpacity` is not applied. Instead, you should define the
+ * opacity in the `fillColor` with an rgba color definition. The
+ * `fillOpacity` setting, also the default setting, overrides the alpha
+ * component of the `color` setting.
+ *
+ * In styled mode, the fill opacity can be set with the
+ * `.highcharts-area` class name.
+ *
+ * @sample {highcharts} highcharts/plotoptions/area-fillopacity/
+ * Automatic fill color and fill opacity of 0.1
+ *
+ * @type {number}
+ * @default {highcharts} 0.75
+ * @default {highstock} 0.75
+ * @product highcharts highstock
+ * @apioption plotOptions.area.fillOpacity
+ */
+
+ /**
+ * A separate color for the graph line. By default the line takes the
+ * `color` of the series, but the lineColor setting allows setting a
+ * separate color for the line without altering the `fillColor`.
+ *
+ * In styled mode, the line stroke can be set with the
+ * `.highcharts-graph` class name.
+ *
+ * @sample {highcharts} highcharts/plotoptions/area-linecolor/
+ * Dark gray line
+ *
+ * @type {Highcharts.ColorString}
+ * @product highcharts highstock
+ * @apioption plotOptions.area.lineColor
+ */
+
+ /**
* A separate color for the negative part of the area.
- *
- * @type {Color}
- * @see [negativeColor](#plotOptions.area.negativeColor). In styled mode, a negative
- * color is set with the `.highcharts-negative` class name ([view live
- * demo](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/series-
- * negative-color/)).
- * @since 3.0
- * @product highcharts
+ *
+ * In styled mode, a negative color is set with the `.highcharts-negative`
+ * class name.
+ *
+ * @see [negativeColor](#plotOptions.area.negativeColor)
+ *
+ * @sample {highcharts} highcharts/css/series-negative-color/
+ * Negative color in styled mode
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @since 3.0
+ * @product highcharts
* @apioption plotOptions.area.negativeFillColor
*/
- /**
+ /**
+ * Whether the whole area or just the line should respond to mouseover
+ * tooltips and other mouse or touch events.
+ *
+ * @sample {highcharts|highstock} highcharts/plotoptions/area-trackbyarea/
+ * Display the tooltip when the area is hovered
+ *
+ * @type {boolean}
+ * @default false
+ * @since 1.1.6
+ * @product highcharts highstock
+ * @apioption plotOptions.area.trackByArea
+ */
+
+ /**
* When this is true, the series will not cause the Y axis to cross
* the zero plane (or [threshold](#plotOptions.series.threshold) option)
* unless the data actually crosses the plane.
- *
+ *
* For example, if `softThreshold` is `false`, a series of 0, 1, 2,
* 3 will make the Y axis show negative values according to the `minPadding`
* option. If `softThreshold` is `true`, the Y axis starts at 0.
- *
- * @type {Boolean}
- * @default false
- * @since 4.1.9
+ *
+ * @since 4.1.9
* @product highcharts highstock
*/
- softThreshold: false,
+ softThreshold: false,
- /**
+ /**
* The Y axis value to serve as the base for the area, for distinguishing
- * between values above and below a threshold. If `null`, the area
- * behaves like a line series with fill between the graph and the Y
- * axis minimum.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/area-threshold/ A threshold of 100
- * @default 0
- * @since 2.0
+ * between values above and below a threshold. The area between the graph
+ * and the threshold is filled.
+ *
+ * * If a number is given, the Y axis will scale to the threshold.
+ * * If `null`, the scaling behaves like a line series with fill between the
+ * graph and the Y axis minimum.
+ * * If `Infinity` or `-Infinity`, the area between the graph and the
+ * corresponing Y axis extreme is filled (since v6.1.0).
+ *
+ * @sample {highcharts} highcharts/plotoptions/area-threshold/
+ * A threshold of 100
+ * @sample {highcharts} highcharts/plotoptions/area-threshold-infinity/
+ * A threshold of Infinity
+ *
+ * @since 2.0
* @product highcharts highstock
*/
- threshold: 0
+ threshold: 0
- /**
- * Whether the whole area or just the line should respond to mouseover
- * tooltips and other mouse or touch events.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/area-trackbyarea/ Display the tooltip when the area is hovered
- * @sample {highstock} highcharts/plotoptions/area-trackbyarea/ Display the tooltip when the area is hovered
- * @default false
- * @since 1.1.6
- * @product highcharts highstock
- * @apioption plotOptions.area.trackByArea
- */
+ }, /** @lends seriesTypes.area.prototype */ {
- }, /** @lends seriesTypes.area.prototype */ {
- singleStacks: false,
- /**
- * Return an array of stacked points, where null and missing points are replaced by
- * dummy points in order for gaps to be drawn correctly in stacks.
- */
- getStackPoints: function(points) {
- var series = this,
- segment = [],
- keys = [],
- xAxis = this.xAxis,
- yAxis = this.yAxis,
- stack = yAxis.stacks[this.stackKey],
- pointMap = {},
- seriesIndex = series.index,
- yAxisSeries = yAxis.series,
- seriesLength = yAxisSeries.length,
- visibleSeries,
- upOrDown = pick(yAxis.options.reversedStacks, true) ? 1 : -1,
- i;
+ singleStacks: false,
+ // Return an array of stacked points, where null and missing points are
+ // replaced by dummy points in order for gaps to be drawn correctly in
+ // stacks.
+ getStackPoints: function (points) {
+ var series = this,
+ segment = [],
+ keys = [],
+ xAxis = this.xAxis,
+ yAxis = this.yAxis,
+ stack = yAxis.stacks[this.stackKey],
+ pointMap = {},
+ seriesIndex = series.index,
+ yAxisSeries = yAxis.series,
+ seriesLength = yAxisSeries.length,
+ visibleSeries,
+ upOrDown = pick(yAxis.options.reversedStacks, true) ? 1 : -1,
+ i;
- points = points || this.points;
- if (this.options.stacking) {
+ points = points || this.points;
- for (i = 0; i < points.length; i++) {
- // Reset after point update (#7326)
- points[i].leftNull = points[i].rightNull = null;
+ if (this.options.stacking) {
- // Create a map where we can quickly look up the points by their
- // X values.
- pointMap[points[i].x] = points[i];
- }
+ for (i = 0; i < points.length; i++) {
+ // Reset after point update (#7326)
+ points[i].leftNull = points[i].rightNull = null;
- // Sort the keys (#1651)
- H.objectEach(stack, function(stackX, x) {
- if (stackX.total !== null) { // nulled after switching between grouping and not (#1651, #2336)
- keys.push(x);
+ // Create a map where we can quickly look up the points by
+ // their X values.
+ pointMap[points[i].x] = points[i];
}
- });
- keys.sort(function(a, b) {
- return a - b;
- });
- visibleSeries = map(yAxisSeries, function() {
- return this.visible;
- });
+ // Sort the keys (#1651)
+ H.objectEach(stack, function (stackX, x) {
+ // nulled after switching between
+ // grouping and not (#1651, #2336)
+ if (stackX.total !== null) {
+ keys.push(x);
+ }
+ });
+ keys.sort(function (a, b) {
+ return a - b;
+ });
- each(keys, function(x, idx) {
- var y = 0,
- stackPoint,
- stackedValues;
-
- if (pointMap[x] && !pointMap[x].isNull) {
- segment.push(pointMap[x]);
-
- // Find left and right cliff. -1 goes left, 1 goes right.
- each([-1, 1], function(direction) {
- var nullName = direction === 1 ? 'rightNull' : 'leftNull',
- cliffName = direction === 1 ? 'rightCliff' : 'leftCliff',
- cliff = 0,
- otherStack = stack[keys[idx + direction]];
-
- // If there is a stack next to this one, to the left or to the right...
- if (otherStack) {
- i = seriesIndex;
- while (i >= 0 && i < seriesLength) { // Can go either up or down, depending on reversedStacks
- stackPoint = otherStack.points[i];
- if (!stackPoint) {
- // If the next point in this series is missing, mark the point
- // with point.leftNull or point.rightNull = true.
- if (i === seriesIndex) {
- pointMap[x][nullName] = true;
-
- // If there are missing points in the next stack in any of the
- // series below this one, we need to substract the missing values
- // and add a hiatus to the left or right.
- } else if (visibleSeries[i]) {
- stackedValues = stack[x].points[i];
- if (stackedValues) {
- cliff -= stackedValues[1] - stackedValues[0];
+ visibleSeries = yAxisSeries.map(function (s) {
+ return s.visible;
+ });
+
+ keys.forEach(function (x, idx) {
+ var y = 0,
+ stackPoint,
+ stackedValues;
+
+ if (pointMap[x] && !pointMap[x].isNull) {
+ segment.push(pointMap[x]);
+
+ // Find left and right cliff. -1 goes left, 1 goes
+ // right.
+ [-1, 1].forEach(function (direction) {
+ var nullName = direction === 1 ?
+ 'rightNull' :
+ 'leftNull',
+ cliffName = direction === 1 ?
+ 'rightCliff' :
+ 'leftCliff',
+ cliff = 0,
+ otherStack = stack[keys[idx + direction]];
+
+ // If there is a stack next to this one,
+ // to the left or to the right...
+ if (otherStack) {
+ i = seriesIndex;
+ // Can go either up or down,
+ // depending on reversedStacks
+ while (i >= 0 && i < seriesLength) {
+ stackPoint = otherStack.points[i];
+ if (!stackPoint) {
+ // If the next point in this series
+ // is missing, mark the point
+ // with point.leftNull or
+ // point.rightNull = true.
+ if (i === seriesIndex) {
+ pointMap[x][nullName] = true;
+
+ // If there are missing points in
+ // the next stack in any of the
+ // series below this one, we need
+ // to substract the missing values
+ // and add a hiatus to the left or
+ // right.
+ } else if (visibleSeries[i]) {
+ stackedValues = stack[x].points[i];
+ if (stackedValues) {
+ cliff -= stackedValues[1] -
+ stackedValues[0];
+ }
}
}
+ // When reversedStacks is true, loop up,
+ // else loop down
+ i += upOrDown;
}
- // When reversedStacks is true, loop up, else loop down
- i += upOrDown;
}
- }
- pointMap[x][cliffName] = cliff;
- });
+ pointMap[x][cliffName] = cliff;
+ });
- // There is no point for this X value in this series, so we
+ // 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 {
+ } else {
- // Loop down the stack to find the series below this one that has
- // a value (#1991)
- i = seriesIndex;
- while (i >= 0 && i < seriesLength) {
- stackPoint = stack[x].points[i];
- if (stackPoint) {
- y = stackPoint[1];
- break;
+ // Loop down the stack to find the series below this
+ // one that has a value (#1991)
+ i = seriesIndex;
+ while (i >= 0 && i < seriesLength) {
+ stackPoint = stack[x].points[i];
+ if (stackPoint) {
+ y = stackPoint[1];
+ break;
+ }
+ // When reversedStacks is true, loop up, else loop
+ // down
+ i += upOrDown;
}
- // When reversedStacks is true, loop up, else loop down
- i += upOrDown;
+ y = yAxis.translate(y, 0, 1, 0, 1); // #6272
+ segment.push({
+ isNull: true,
+ plotX: xAxis.translate(x, 0, 0, 0, 1), // #6272
+ x: x,
+ plotY: y,
+ yBottom: y
+ });
}
- y = yAxis.translate(y, 0, 1, 0, 1); // #6272
- segment.push({
- isNull: true,
- plotX: xAxis.translate(x, 0, 0, 0, 1), // #6272
- x: x,
- plotY: y,
- yBottom: y
- });
- }
- });
-
- }
+ });
- return segment;
- },
+ }
- getGraphPath: function(points) {
- var getGraphPath = Series.prototype.getGraphPath,
- graphPath,
- options = this.options,
- stacking = options.stacking,
- yAxis = this.yAxis,
- topPath,
- bottomPath,
- bottomPoints = [],
- graphPoints = [],
- seriesIndex = this.index,
- i,
- areaPath,
- plotX,
- stacks = yAxis.stacks[this.stackKey],
- threshold = options.threshold,
- translatedThreshold = yAxis.getThreshold(options.threshold),
- isNull,
- yBottom,
- connectNulls = options.connectNulls || stacking === 'percent',
- /**
- * To display null points in underlying stacked series, this series graph must be
- * broken, and the area also fall down to fill the gap left by the null point. #2069
- */
- addDummyPoints = function(i, otherI, side) {
- var point = points[i],
- stackedValues = stacking && stacks[point.x].points[seriesIndex],
- nullVal = point[side + 'Null'] || 0,
- cliffVal = point[side + 'Cliff'] || 0,
- top,
- bottom,
- isNull = true;
-
- if (cliffVal || nullVal) {
-
- top = (nullVal ? stackedValues[0] : stackedValues[1]) + cliffVal;
- bottom = stackedValues[0] + cliffVal;
- isNull = !!nullVal;
-
- } else if (!stacking && points[otherI] && points[otherI].isNull) {
- top = bottom = threshold;
- }
-
- // Add to the top and bottom line of the area
- if (top !== undefined) {
- graphPoints.push({
- plotX: plotX,
- plotY: top === null ? translatedThreshold : yAxis.getThreshold(top),
- isNull: isNull,
- isCliff: true
- });
- bottomPoints.push({
- plotX: plotX,
- plotY: bottom === null ? translatedThreshold : yAxis.getThreshold(bottom),
- doCurve: false // #1041, gaps in areaspline areas
- });
- }
- };
+ return segment;
+ },
- // Find what points to use
- points = points || this.points;
+ getGraphPath: function (points) {
+ var getGraphPath = Series.prototype.getGraphPath,
+ graphPath,
+ options = this.options,
+ stacking = options.stacking,
+ yAxis = this.yAxis,
+ topPath,
+ bottomPath,
+ bottomPoints = [],
+ graphPoints = [],
+ seriesIndex = this.index,
+ i,
+ areaPath,
+ plotX,
+ stacks = yAxis.stacks[this.stackKey],
+ threshold = options.threshold,
+ translatedThreshold = yAxis.getThreshold(options.threshold),
+ isNull,
+ yBottom,
+ connectNulls = options.connectNulls || stacking === 'percent',
+
+ // To display null points in underlying stacked series, this
+ // series graph must be broken, and the area also fall down to
+ // fill the gap left by the null point. #2069
+ addDummyPoints = function (i, otherI, side) {
+ var point = points[i],
+ stackedValues = stacking &&
+ stacks[point.x].points[seriesIndex],
+ nullVal = point[side + 'Null'] || 0,
+ cliffVal = point[side + 'Cliff'] || 0,
+ top,
+ bottom,
+ isNull = true;
+
+ if (cliffVal || nullVal) {
+
+ top = (nullVal ? stackedValues[0] : stackedValues[1]) +
+ cliffVal;
+ bottom = stackedValues[0] + cliffVal;
+ isNull = !!nullVal;
- // Fill in missing points
- if (stacking) {
- points = this.getStackPoints(points);
- }
+ } else if (
+ !stacking &&
+ points[otherI] &&
+ points[otherI].isNull
+ ) {
+ top = bottom = threshold;
+ }
+
+ // Add to the top and bottom line of the area
+ if (top !== undefined) {
+ graphPoints.push({
+ plotX: plotX,
+ plotY: top === null ?
+ translatedThreshold :
+ yAxis.getThreshold(top),
+ isNull: isNull,
+ isCliff: true
+ });
+ bottomPoints.push({
+ plotX: plotX,
+ plotY: bottom === null ?
+ translatedThreshold :
+ yAxis.getThreshold(bottom),
+ doCurve: false // #1041, gaps in areaspline areas
+ });
+ }
+ };
- for (i = 0; i < points.length; i++) {
- isNull = points[i].isNull;
- plotX = pick(points[i].rectPlotX, points[i].plotX);
- yBottom = pick(points[i].yBottom, translatedThreshold);
+ // Find what points to use
+ points = points || this.points;
- if (!isNull || connectNulls) {
+ // Fill in missing points
+ if (stacking) {
+ points = this.getStackPoints(points);
+ }
- if (!connectNulls) {
- addDummyPoints(i, i - 1, 'left');
- }
+ for (i = 0; i < points.length; i++) {
+ isNull = points[i].isNull;
+ plotX = pick(points[i].rectPlotX, points[i].plotX);
+ yBottom = pick(points[i].yBottom, translatedThreshold);
- if (!(isNull && !stacking && connectNulls)) { // Skip null point when stacking is false and connectNulls true
- graphPoints.push(points[i]);
- bottomPoints.push({
- x: i,
- plotX: plotX,
- plotY: yBottom
- });
- }
+ if (!isNull || connectNulls) {
+
+ if (!connectNulls) {
+ addDummyPoints(i, i - 1, 'left');
+ }
+ // Skip null point when stacking is false and connectNulls
+ // true
+ if (!(isNull && !stacking && connectNulls)) {
+ graphPoints.push(points[i]);
+ bottomPoints.push({
+ x: i,
+ plotX: plotX,
+ plotY: yBottom
+ });
+ }
- if (!connectNulls) {
- addDummyPoints(i, i + 1, 'right');
+ if (!connectNulls) {
+ addDummyPoints(i, i + 1, 'right');
+ }
}
}
- }
- topPath = getGraphPath.call(this, graphPoints, true, true);
+ topPath = getGraphPath.call(this, graphPoints, true, true);
- bottomPoints.reversed = true;
- bottomPath = getGraphPath.call(this, bottomPoints, true, true);
- if (bottomPath.length) {
- bottomPath[0] = 'L';
- }
-
- areaPath = topPath.concat(bottomPath);
- graphPath = getGraphPath.call(this, graphPoints, false, connectNulls); // TODO: don't set leftCliff and rightCliff when connectNulls?
+ bottomPoints.reversed = true;
+ bottomPath = getGraphPath.call(this, bottomPoints, true, true);
+ if (bottomPath.length) {
+ bottomPath[0] = 'L';
+ }
- areaPath.xMap = topPath.xMap;
- this.areaPath = areaPath;
+ areaPath = topPath.concat(bottomPath);
+ // TODO: don't set leftCliff and rightCliff when connectNulls?
+ graphPath = getGraphPath
+ .call(this, graphPoints, false, connectNulls);
+ areaPath.xMap = topPath.xMap;
+ this.areaPath = areaPath;
- return graphPath;
- },
+ return graphPath;
+ },
- /**
- * Draw the graph and the underlying area. This method calls the Series base
- * function and adds the area. The areaPath is calculated in the getSegmentPath
- * method called from Series.prototype.drawGraph.
- */
- drawGraph: function() {
+ // Draw the graph and the underlying area. This method calls the Series
+ // base function and adds the area. The areaPath is calculated in the
+ // getSegmentPath method called from Series.prototype.drawGraph.
+ drawGraph: function () {
- // Define or reset areaPath
- this.areaPath = [];
+ // Define or reset areaPath
+ this.areaPath = [];
- // Call the base method
- Series.prototype.drawGraph.apply(this);
+ // Call the base method
+ Series.prototype.drawGraph.apply(this);
- // Define local variables
- var series = this,
- areaPath = this.areaPath,
- options = this.options,
- zones = this.zones,
- props = [
- [
+ // Define local variables
+ var series = this,
+ areaPath = this.areaPath,
+ options = this.options,
+ zones = this.zones,
+ props = [[
'area',
'highcharts-area',
-
this.color,
options.fillColor
+ ]]; // area name, main color, fill color
+
+ zones.forEach(function (zone, i) {
+ props.push([
+ 'zone-area-' + i,
+ 'highcharts-area highcharts-zone-area-' + i + ' ' +
+ zone.className,
+ zone.color || series.color,
+ zone.fillColor || options.fillColor
+ ]);
+ });
- ]
- ]; // area name, main color, fill color
-
- each(zones, function(zone, i) {
- props.push([
- 'zone-area-' + i,
- 'highcharts-area highcharts-zone-area-' + i + ' ' + zone.className,
-
- zone.color || series.color,
- zone.fillColor || options.fillColor
-
- ]);
- });
+ props.forEach(function (prop) {
+ var areaKey = prop[0],
+ area = series[areaKey],
+ attribs;
- each(props, function(prop) {
- var areaKey = prop[0],
- area = series[areaKey];
+ // Create or update the area
+ if (area) { // update
+ area.endX = series.preventGraphAnimation ?
+ null :
+ areaPath.xMap;
+ area.animate({ d: areaPath });
- // Create or update the area
- if (area) { // update
- area.endX = series.preventGraphAnimation ? null : areaPath.xMap;
- area.animate({
- d: areaPath
- });
+ } else { // create
- } else { // create
- area = series[areaKey] = series.chart.renderer.path(areaPath)
- .addClass(prop[1])
- .attr({
+ attribs = {
+ zIndex: 0 // #1069
+ };
- fill: pick(
+ if (!series.chart.styledMode) {
+ attribs.fill = pick(
prop[3],
- color(prop[2]).setOpacity(pick(options.fillOpacity, 0.75)).get()
- ),
+ color(prop[2])
+ .setOpacity(pick(options.fillOpacity, 0.75))
+ .get()
+ );
+ }
- zIndex: 0 // #1069
- }).add(series.group);
- area.isArea = true;
- }
- area.startX = areaPath.xMap;
- area.shiftUnit = options.step ? 2 : 1;
- });
- },
+ area = series[areaKey] = series.chart.renderer
+ .path(areaPath)
+ .addClass(prop[1])
+ .attr(attribs)
+ .add(series.group);
+ area.isArea = true;
+ }
+ area.startX = areaPath.xMap;
+ area.shiftUnit = options.step ? 2 : 1;
+ });
+ },
- drawLegendSymbol: LegendSymbolMixin.drawRectangle
- });
+ drawLegendSymbol: LegendSymbolMixin.drawRectangle
+ });
/**
* A `area` series. If the [type](#series.area.type) option is not
* specified, it is inherited from [chart.type](#chart.type).
- *
- * For options that apply to multiple series, it is recommended to add
- * them to the [plotOptions.series](#plotOptions.series) options structure.
- * To apply to all series of this specific type, apply it to [plotOptions.
- * area](#plotOptions.area).
- *
- * @type {Object}
- * @extends series,plotOptions.area
- * @excluding dataParser,dataURL
- * @product highcharts highstock
+ *
+ * @extends series,plotOptions.area
+ * @excluding dataParser, dataURL, useOhlcData
+ * @product highcharts highstock
* @apioption series.area
*/
/**
* An array of data points for the series. For the `area` series type,
* points can be given in the following ways:
- *
- * 1. An array of numerical values. In this case, the numerical values
- * will be interpreted as `y` options. The `x` values will be automatically
- * calculated, either starting at 0 and incremented by 1, or from `pointStart`
- * and `pointInterval` given in the series options. If the axis has
- * categories, these will be used. Example:
- *
- * ```js
- * data: [0, 5, 3, 5]
- * ```
- *
- * 2. An array of arrays with 2 values. In this case, the values correspond
- * to `x,y`. If the first value is a string, it is applied as the name
- * of the point, and the `x` value is inferred.
- *
- * ```js
- * data: [
- * [0, 9],
- * [1, 7],
- * [2, 6]
- * ]
- * ```
- *
- * 3. An array of objects with named values. The objects are point
- * configuration objects as seen below. If the total number of data
- * points exceeds the series' [turboThreshold](#series.area.turboThreshold),
- * this option is not available.
- *
- * ```js
- * data: [{
- * x: 1,
- * y: 9,
- * name: "Point2",
- * color: "#00FF00"
- * }, {
- * x: 1,
- * y: 6,
- * name: "Point1",
- * color: "#FF00FF"
- * }]
- * ```
- *
- * @type {Array}
- * @extends series.line.data
- * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
- * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
- * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
- * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
- * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
- * @product highcharts highstock
+ *
+ * 1. An array of numerical values. In this case, the numerical values will be
+ * interpreted as `y` options. The `x` values will be automatically
+ * calculated, either starting at 0 and incremented by 1, or from
+ * `pointStart` * and `pointInterval` given in the series options. If the
+ * axis has categories, these will be used. Example:
+ * ```js
+ * data: [0, 5, 3, 5]
+ * ```
+ *
+ * 2. An array of arrays with 2 values. In this case, the values correspond to
+ * `x,y`. If the first value is a string, it is applied as the name of the
+ * point, and the `x` value is inferred.
+ * ```js
+ * data: [
+ * [0, 9],
+ * [1, 7],
+ * [2, 6]
+ * ]
+ * ```
+ *
+ * 3. An array of objects with named values. The following snippet shows only a
+ * few settings, see the complete options set below. If the total number of
+ * data points exceeds the series'
+ * [turboThreshold](#series.area.turboThreshold), this option is not
+ * available.
+ * ```js
+ * data: [{
+ * x: 1,
+ * y: 9,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * y: 6,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @sample {highcharts} highcharts/chart/reflow-true/
+ * Numerical values
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/
+ * Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
+ * Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/
+ * Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
+ *
+ * @type {Array|*>}
+ * @extends series.line.data
+ * @product highcharts highstock
* @apioption series.area.data
*/
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
+
var pick = H.pick,
seriesType = H.seriesType;
- /**
- * A spline series is a special type of line series, where the segments between
- * the data points are smoothed.
- *
- * @sample {highcharts} highcharts/demo/spline-irregular-time/ Spline chart
- * @sample {highstock} stock/demo/spline/ Spline chart
- *
- * @extends plotOptions.series
- * @excluding step
- * @product highcharts highstock
- * @apioption plotOptions.spline
- */
-
/**
* Spline series type.
- * @constructor seriesTypes.spline
- * @extends {Series}
+ *
+ * @private
+ * @class
+ * @name Highcharts.seriesTypes.spline
+ *
+ * @augments Highcarts.Series
*/
- seriesType('spline', 'line', {}, /** @lends seriesTypes.spline.prototype */ {
+ seriesType(
+ 'spline',
+ 'line',
/**
- * Get the spline segment from a given point's previous neighbour to the
- * given point
+ * A spline series is a special type of line series, where the segments
+ * between the data points are smoothed.
+ *
+ * @sample {highcharts} highcharts/demo/spline-irregular-time/
+ * Spline chart
+ * @sample {highstock} stock/demo/spline/
+ * Spline chart
+ *
+ * @extends plotOptions.series
+ * @excluding step
+ * @product highcharts highstock
+ * @optionparent plotOptions.spline
*/
- getPointSpline: function(points, point, i) {
- var
- // 1 means control points midway between points, 2 means 1/3 from
- // the point, 3 is 1/4 etc
- smoothing = 1.5,
- denom = smoothing + 1,
- plotX = point.plotX,
- plotY = point.plotY,
- lastPoint = points[i - 1],
- nextPoint = points[i + 1],
- leftContX,
- leftContY,
- rightContX,
- rightContY,
- ret;
-
- function doCurve(otherPoint) {
- return otherPoint &&
- !otherPoint.isNull &&
- otherPoint.doCurve !== false &&
- !point.isCliff; // #6387, area splines next to null
- }
-
- // Find control points
- if (doCurve(lastPoint) && doCurve(nextPoint)) {
- var lastX = lastPoint.plotX,
- lastY = lastPoint.plotY,
- nextX = nextPoint.plotX,
- nextY = nextPoint.plotY,
- correction = 0;
+ {
+ },
+ /** @lends seriesTypes.spline.prototype */ {
+ /**
+ * Get the spline segment from a given point's previous neighbour to the
+ * given point.
+ *
+ * @private
+ * @function Highcharts.seriesTypes.spline#getPointSpline
+ *
+ * @param {Array}
+ *
+ * @param {Highcharts.Point} point
+ *
+ * @param {number} i
+ *
+ * @return {Highcharts.SVGPathArray}
+ */
+ getPointSpline: function (points, point, i) {
+ var
+ // 1 means control points midway between points, 2 means 1/3
+ // from the point, 3 is 1/4 etc
+ smoothing = 1.5,
+ denom = smoothing + 1,
+ plotX = point.plotX,
+ plotY = point.plotY,
+ lastPoint = points[i - 1],
+ nextPoint = points[i + 1],
+ leftContX,
+ leftContY,
+ rightContX,
+ rightContY,
+ ret;
+
+ function doCurve(otherPoint) {
+ return otherPoint &&
+ !otherPoint.isNull &&
+ otherPoint.doCurve !== false &&
+ !point.isCliff; // #6387, area splines next to null
+ }
+
+ // Find control points
+ if (doCurve(lastPoint) && doCurve(nextPoint)) {
+ var lastX = lastPoint.plotX,
+ lastY = lastPoint.plotY,
+ nextX = nextPoint.plotX,
+ nextY = nextPoint.plotY,
+ correction = 0;
+
+ leftContX = (smoothing * plotX + lastX) / denom;
+ leftContY = (smoothing * plotY + lastY) / denom;
+ rightContX = (smoothing * plotX + nextX) / denom;
+ rightContY = (smoothing * plotY + nextY) / denom;
+
+ // Have the two control points make a straight line through main
+ // point
+ if (rightContX !== leftContX) { // #5016, division by zero
+ correction = (
+ ((rightContY - leftContY) * (rightContX - plotX)) /
+ (rightContX - leftContX) + plotY - rightContY
+ );
+ }
- leftContX = (smoothing * plotX + lastX) / denom;
- leftContY = (smoothing * plotY + lastY) / denom;
- rightContX = (smoothing * plotX + nextX) / denom;
- rightContY = (smoothing * plotY + nextY) / denom;
+ leftContY += correction;
+ rightContY += correction;
+
+ // to prevent false extremes, check that control points are
+ // between neighbouring points' y values
+ if (leftContY > lastY && leftContY > plotY) {
+ leftContY = Math.max(lastY, plotY);
+ // mirror of left control point
+ rightContY = 2 * plotY - leftContY;
+ } else if (leftContY < lastY && leftContY < plotY) {
+ leftContY = Math.min(lastY, plotY);
+ rightContY = 2 * plotY - leftContY;
+ }
+ if (rightContY > nextY && rightContY > plotY) {
+ rightContY = Math.max(nextY, plotY);
+ leftContY = 2 * plotY - rightContY;
+ } else if (rightContY < nextY && rightContY < plotY) {
+ rightContY = Math.min(nextY, plotY);
+ leftContY = 2 * plotY - rightContY;
+ }
- // Have the two control points make a straight line through main
- // point
- if (rightContX !== leftContX) { // #5016, division by zero
- correction = ((rightContY - leftContY) * (rightContX - plotX)) /
- (rightContX - leftContX) + plotY - rightContY;
- }
+ // record for drawing in next point
+ point.rightContX = rightContX;
+ point.rightContY = rightContY;
- leftContY += correction;
- rightContY += correction;
- // to prevent false extremes, check that control points are between
- // neighbouring points' y values
- if (leftContY > lastY && leftContY > plotY) {
- leftContY = Math.max(lastY, plotY);
- // mirror of left control point
- rightContY = 2 * plotY - leftContY;
- } else if (leftContY < lastY && leftContY < plotY) {
- leftContY = Math.min(lastY, plotY);
- rightContY = 2 * plotY - leftContY;
}
- if (rightContY > nextY && rightContY > plotY) {
- rightContY = Math.max(nextY, plotY);
- leftContY = 2 * plotY - rightContY;
- } else if (rightContY < nextY && rightContY < plotY) {
- rightContY = Math.min(nextY, plotY);
- leftContY = 2 * plotY - rightContY;
- }
-
- // record for drawing in next point
- point.rightContX = rightContX;
- point.rightContY = rightContY;
-
-
- }
- // Visualize control points for debugging
- /*
+ // Visualize control points for debugging
+ /*
if (leftContX) {
- this.chart.renderer.circle(
- leftContX + this.chart.plotLeft,
- leftContY + this.chart.plotTop,
- 2
- )
- .attr({
- stroke: 'red',
- 'stroke-width': 2,
- fill: 'none',
- zIndex: 9
- })
- .add();
- this.chart.renderer.path(['M', leftContX + this.chart.plotLeft,
- leftContY + this.chart.plotTop,
- 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
- .attr({
- stroke: 'red',
- 'stroke-width': 2,
- zIndex: 9
- })
- .add();
+ this.chart.renderer.circle(
+ leftContX + this.chart.plotLeft,
+ leftContY + this.chart.plotTop,
+ 2
+ )
+ .attr({
+ stroke: 'red',
+ 'stroke-width': 2,
+ fill: 'none',
+ zIndex: 9
+ })
+ .add();
+ this.chart.renderer.path(['M', leftContX + this.chart.plotLeft,
+ leftContY + this.chart.plotTop,
+ 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
+ .attr({
+ stroke: 'red',
+ 'stroke-width': 2,
+ zIndex: 9
+ })
+ .add();
}
if (rightContX) {
- this.chart.renderer.circle(
- rightContX + this.chart.plotLeft,
- rightContY + this.chart.plotTop,
- 2
- )
- .attr({
- stroke: 'green',
- 'stroke-width': 2,
- fill: 'none',
- zIndex: 9
- })
- .add();
- this.chart.renderer.path(['M', rightContX + this.chart.plotLeft,
- rightContY + this.chart.plotTop,
- 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
- .attr({
- stroke: 'green',
- 'stroke-width': 2,
- zIndex: 9
- })
- .add();
- }
- // */
- ret = [
- 'C',
- pick(lastPoint.rightContX, lastPoint.plotX),
- pick(lastPoint.rightContY, lastPoint.plotY),
- pick(leftContX, plotX),
- pick(leftContY, plotY),
- plotX,
- plotY
- ];
- // reset for updating series later
- lastPoint.rightContX = lastPoint.rightContY = null;
- return ret;
+ this.chart.renderer.circle(
+ rightContX + this.chart.plotLeft,
+ rightContY + this.chart.plotTop,
+ 2
+ )
+ .attr({
+ stroke: 'green',
+ 'stroke-width': 2,
+ fill: 'none',
+ zIndex: 9
+ })
+ .add();
+ this.chart.renderer.path(['M', rightContX + this.chart.plotLeft,
+ rightContY + this.chart.plotTop,
+ 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
+ .attr({
+ stroke: 'green',
+ 'stroke-width': 2,
+ zIndex: 9
+ })
+ .add();
+ }
+ // */
+ ret = [
+ 'C',
+ pick(lastPoint.rightContX, lastPoint.plotX),
+ pick(lastPoint.rightContY, lastPoint.plotY),
+ pick(leftContX, plotX),
+ pick(leftContY, plotY),
+ plotX,
+ plotY
+ ];
+ // reset for updating series later
+ lastPoint.rightContX = lastPoint.rightContY = null;
+ return ret;
+ }
}
- });
+ );
/**
* A `spline` series. If the [type](#series.spline.type) option is
* not specified, it is inherited from [chart.type](#chart.type).
- *
- * For options that apply to multiple series, it is recommended to add
- * them to the [plotOptions.series](#plotOptions.series) options structure.
- * To apply to all series of this specific type, apply it to [plotOptions.
- * spline](#plotOptions.spline).
- *
- * @type {Object}
- * @extends series,plotOptions.spline
- * @excluding dataParser,dataURL
- * @product highcharts highstock
+ *
+ * @extends series,plotOptions.spline
+ * @excluding dataParser, dataURL, step
+ * @product highcharts highstock
* @apioption series.spline
*/
/**
* An array of data points for the series. For the `spline` series type,
* points can be given in the following ways:
- *
- * 1. An array of numerical values. In this case, the numerical values
- * will be interpreted as `y` options. The `x` values will be automatically
- * calculated, either starting at 0 and incremented by 1, or from `pointStart`
- * and `pointInterval` given in the series options. If the axis has
- * categories, these will be used. Example:
- *
- * ```js
- * data: [0, 5, 3, 5]
- * ```
- *
- * 2. An array of arrays with 2 values. In this case, the values correspond
- * to `x,y`. If the first value is a string, it is applied as the name
- * of the point, and the `x` value is inferred.
- *
- * ```js
- * data: [
- * [0, 9],
- * [1, 2],
- * [2, 8]
- * ]
- * ```
- *
- * 3. An array of objects with named values. The objects are point
- * configuration objects as seen below. If the total number of data
- * points exceeds the series' [turboThreshold](#series.spline.turboThreshold),
- * this option is not available.
- *
- * ```js
- * data: [{
- * x: 1,
- * y: 9,
- * name: "Point2",
- * color: "#00FF00"
- * }, {
- * x: 1,
- * y: 0,
- * name: "Point1",
- * color: "#FF00FF"
- * }]
- * ```
- *
- * @type {Array}
- * @extends series.line.data
+ *
+ * 1. An array of numerical values. In this case, the numerical values will be
+ * interpreted as `y` options. The `x` values will be automatically
+ * calculated, either starting at 0 and incremented by 1, or from
+ * `pointStart` and `pointInterval` given in the series options. If the axis
+ * has categories, these will be used. Example:
+ * ```js
+ * data: [0, 5, 3, 5]
+ * ```
+ *
+ * 2. An array of arrays with 2 values. In this case, the values correspond to
+ * `x,y`. If the first value is a string, it is applied as the name of the
+ * point, and the `x` value is inferred.
+ * ```js
+ * data: [
+ * [0, 9],
+ * [1, 2],
+ * [2, 8]
+ * ]
+ * ```
+ *
+ * 3. An array of objects with named values. The following snippet shows only a
+ * few settings, see the complete options set below. If the total number of
+ * data points exceeds the series'
+ * [turboThreshold](#series.spline.turboThreshold), this option is not
+ * available.
+ * ```js
+ * data: [{
+ * x: 1,
+ * y: 9,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * y: 0,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
* @sample {highcharts} highcharts/chart/reflow-true/
* Numerical values
* @sample {highcharts} highcharts/series/data-array-of-arrays/
@@ -29258,128 +38444,163 @@
* Arrays of point.name and y
* @sample {highcharts} highcharts/series/data-array-of-objects/
* Config objects
- * @product highcharts highstock
+ *
+ * @type {Array|*>}
+ * @extends series.line.data
+ * @product highcharts highstock
* @apioption series.spline.data
*/
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
+
var areaProto = H.seriesTypes.area.prototype,
defaultPlotOptions = H.defaultPlotOptions,
LegendSymbolMixin = H.LegendSymbolMixin,
seriesType = H.seriesType;
+
/**
- * AreaSplineSeries object
- */
- /**
- * The area spline series is an area series where the graph between the points
- * is smoothed into a spline.
- *
- * @extends plotOptions.area
- * @excluding step
- * @sample {highcharts} highcharts/demo/areaspline/ Area spline chart
- * @sample {highstock} stock/demo/areaspline/ Area spline chart
- * @product highcharts highstock
- * @apioption plotOptions.areaspline
+ * AreaSpline series type.
+ *
+ * @private
+ * @class
+ * @name Highcharts.seriesTypes.areaspline
+ *
+ * @augments Highcharts.Series
*/
- seriesType('areaspline', 'spline', defaultPlotOptions.area, {
- getStackPoints: areaProto.getStackPoints,
- getGraphPath: areaProto.getGraphPath,
- drawGraph: areaProto.drawGraph,
- drawLegendSymbol: LegendSymbolMixin.drawRectangle
- });
+ seriesType('areaspline', 'spline',
+
+ /**
+ * The area spline series is an area series where the graph between the
+ * points is smoothed into a spline.
+ *
+ * @sample {highcharts} highcharts/demo/areaspline/
+ * Area spline chart
+ * @sample {highstock} stock/demo/areaspline/
+ * Area spline chart
+ *
+ * @extends plotOptions.area
+ * @excluding step
+ * @product highcharts highstock
+ * @apioption plotOptions.areaspline
+ */
+ defaultPlotOptions.area
+ , {
+ getStackPoints: areaProto.getStackPoints,
+ getGraphPath: areaProto.getGraphPath,
+ drawGraph: areaProto.drawGraph,
+ drawLegendSymbol: LegendSymbolMixin.drawRectangle
+ });
+
/**
* A `areaspline` series. If the [type](#series.areaspline.type) option
* is not specified, it is inherited from [chart.type](#chart.type).
- *
- *
- * For options that apply to multiple series, it is recommended to add
- * them to the [plotOptions.series](#plotOptions.series) options structure.
- * To apply to all series of this specific type, apply it to [plotOptions.
- * areaspline](#plotOptions.areaspline).
- *
- * @type {Object}
- * @extends series,plotOptions.areaspline
- * @excluding dataParser,dataURL
- * @product highcharts highstock
+ *
+ *
+ * @extends series,plotOptions.areaspline
+ * @excluding dataParser, dataURL
+ * @product highcharts highstock
* @apioption series.areaspline
*/
-
/**
* An array of data points for the series. For the `areaspline` series
* type, points can be given in the following ways:
- *
- * 1. An array of numerical values. In this case, the numerical values
- * will be interpreted as `y` options. The `x` values will be automatically
- * calculated, either starting at 0 and incremented by 1, or from `pointStart`
- * and `pointInterval` given in the series options. If the axis has
- * categories, these will be used. Example:
- *
- * ```js
- * data: [0, 5, 3, 5]
- * ```
- *
- * 2. An array of arrays with 2 values. In this case, the values correspond
- * to `x,y`. If the first value is a string, it is applied as the name
- * of the point, and the `x` value is inferred.
- *
- * ```js
- * data: [
- * [0, 10],
- * [1, 9],
- * [2, 3]
- * ]
- * ```
- *
- * 3. An array of objects with named values. The objects are point
- * configuration objects as seen below. If the total number of data
- * points exceeds the series' [turboThreshold](#series.areaspline.turboThreshold),
- * this option is not available.
- *
- * ```js
- * data: [{
- * x: 1,
- * y: 4,
- * name: "Point2",
- * color: "#00FF00"
- * }, {
- * x: 1,
- * y: 4,
- * name: "Point1",
- * color: "#FF00FF"
- * }]
- * ```
- *
- * @type {Array}
- * @extends series.line.data
- * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
- * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
- * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
- * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
- * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
- * @product highcharts highstock
+ *
+ * 1. An array of numerical values. In this case, the numerical values will be
+ * interpreted as `y` options. The `x` values will be automatically
+ * calculated, either starting at 0 and incremented by 1, or from
+ * `pointStart` and `pointInterval` given in the series options. If the axis
+ * has categories, these will be used. Example:
+ * ```js
+ * data: [0, 5, 3, 5]
+ * ```
+ *
+ * 2. An array of arrays with 2 values. In this case, the values correspond to
+ * `x,y`. If the first value is a string, it is applied as the name of the
+ * point, and the `x` value is inferred.
+ * ```js
+ * data: [
+ * [0, 10],
+ * [1, 9],
+ * [2, 3]
+ * ]
+ * ```
+ *
+ * 3. An array of objects with named values. The following snippet shows only a
+ * few settings, see the complete options set below. If the total number of
+ * data points exceeds the series'
+ * [turboThreshold](#series.areaspline.turboThreshold), this option is not
+ * available.
+ * ```js
+ * data: [{
+ * x: 1,
+ * y: 4,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * y: 4,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @sample {highcharts} highcharts/chart/reflow-true/
+ * Numerical values
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/
+ * Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
+ * Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/
+ * Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
+ *
+ * @type {Array|*>}
+ * @extends series.line.data
+ * @product highcharts highstock
* @apioption series.areaspline.data
*/
-
-
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+ /**
+ * Adjusted width and x offset of the columns for grouping.
+ *
+ * @private
+ * @interface Highcharts.ColumnMetricsObject
+ *//**
+ * Width of the columns.
+ *
+ * @name Highcharts.ColumnMetricsObject#width
+ * @type {number}
+ *//**
+ * Offset of the columns.
+ *
+ * @name Highcharts.ColumnMetricsObject#offset
+ * @type {number}
+ */
+
+
+
var animObject = H.animObject,
color = H.color,
- each = H.each,
extend = H.extend,
+ defined = H.defined,
isNumber = H.isNumber,
LegendSymbolMixin = H.LegendSymbolMixin,
merge = H.merge,
@@ -29388,846 +38609,984 @@
Series = H.Series,
seriesType = H.seriesType,
svg = H.svg;
- /**
- * The column series type.
- *
- * @constructor seriesTypes.column
- * @augments Series
- */
/**
- * Column series display one column per value along an X axis.
+ * The column series type.
*
- * @sample {highcharts} highcharts/demo/column-basic/ Column chart
- * @sample {highstock} stock/demo/column/ Column chart
+ * @private
+ * @class
+ * @name Highcharts.seriesTypes.column
*
- * @extends {plotOptions.line}
- * @product highcharts highstock
- * @excluding connectNulls,dashStyle,gapSize,gapUnit,linecap,lineWidth,marker,
- * connectEnds,step
- * @optionparent plotOptions.column
- */
- seriesType('column', 'line', {
-
- /**
- * The corner radius of the border surrounding each column or bar.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/column-borderradius/
- * Rounded columns
- * @default 0
- * @product highcharts highstock
- */
- borderRadius: 0,
-
- /**
- * The width of the border surrounding each column or bar.
- *
- * In styled mode, the stroke width can be set with the `.highcharts-point`
- * rule.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/column-borderwidth/
- * 2px black border
- * @default 1
- * @product highcharts highstock
- * @apioption plotOptions.column.borderWidth
- */
-
- /**
- * When using automatic point colors pulled from the `options.colors`
- * collection, this option determines whether the chart should receive
- * one color per series or one color per point.
- *
- * @type {Boolean}
- * @see [series colors](#plotOptions.column.colors)
- * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-false/
- * False by default
- * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-true/
- * True
- * @default false
- * @since 2.0
- * @product highcharts highstock
- * @apioption plotOptions.column.colorByPoint
- */
-
- /**
- * A series specific or series type specific color set to apply instead
- * of the global [colors](#colors) when [colorByPoint](#plotOptions.
- * column.colorByPoint) is true.
- *
- * @type {Array}
- * @since 3.0
- * @product highcharts highstock
- * @apioption plotOptions.column.colors
- */
+ * @augments Highcharts.Series
+ */
+ seriesType('column', 'line'
/**
- * When true, each column edge is rounded to its nearest pixel in order
- * to render sharp on screen. In some cases, when there are a lot of
- * densely packed columns, this leads to visible difference in column
- * widths or distance between columns. In these cases, setting `crisp`
- * to `false` may look better, even though each column is rendered
- * blurry.
+ * Column series display one column per value along an X axis.
*
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/column-crisp-false/
- * Crisp is false
- * @default true
- * @since 5.0.10
- * @product highcharts highstock
- */
- crisp: true,
-
- /**
- * Padding between each value groups, in x axis units.
+ * @sample {highcharts} highcharts/demo/column-basic/
+ * Column chart
+ * @sample {highstock} stock/demo/column/
+ * Column chart
*
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/column-grouppadding-default/
- * 0.2 by default
- * @sample {highcharts} highcharts/plotoptions/column-grouppadding-none/
- * No group padding - all columns are evenly spaced
- * @default 0.2
- * @product highcharts highstock
+ * @extends plotOptions.line
+ * @excluding connectNulls, dashStyle, gapSize, gapUnit, linecap,
+ * lineWidth, marker, connectEnds, step, useOhlcData
+ * @product highcharts highstock
+ * @optionparent plotOptions.column
*/
- groupPadding: 0.2,
+ , {
- /**
- * Whether to group non-stacked columns or to let them render independent
- * of each other. Non-grouped columns will be laid out individually
- * and overlap each other.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/column-grouping-false/
- * Grouping disabled
- * @sample {highstock} highcharts/plotoptions/column-grouping-false/
- * Grouping disabled
- * @default true
- * @since 2.3.0
- * @product highcharts highstock
- * @apioption plotOptions.column.grouping
- */
+ /**
+ * The corner radius of the border surrounding each column or bar.
+ *
+ * @sample {highcharts} highcharts/plotoptions/column-borderradius/
+ * Rounded columns
+ *
+ * @product highcharts highstock gantt
+ */
+ borderRadius: 0,
- marker: null, // point options are specified in the base options
+ /**
+ * When using automatic point colors pulled from the global
+ * [colors](colors) or series-specific
+ * [plotOptions.column.colors](series.colors) collections, this option
+ * determines whether the chart should receive one color per series or
+ * one color per point.
+ *
+ * In styled mode, the `colors` or `series.colors` arrays are not
+ * supported, and instead this option gives the points individual color
+ * class names on the form `highcharts-color-{n}`.
+ *
+ * @see [series colors](#plotOptions.column.colors)
+ *
+ * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-false/
+ * False by default
+ * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-true/
+ * True
+ *
+ * @type {boolean}
+ * @default false
+ * @since 2.0
+ * @product highcharts highstock gantt
+ * @apioption plotOptions.column.colorByPoint
+ */
- /**
- * The maximum allowed pixel width for a column, translated to the height
- * of a bar in a bar chart. This prevents the columns from becoming
- * too wide when there is a small number of points in the chart.
- *
- * @type {Number}
- * @see [pointWidth](#plotOptions.column.pointWidth)
- * @sample {highcharts} highcharts/plotoptions/column-maxpointwidth-20/
- * Limited to 50
- * @sample {highstock} highcharts/plotoptions/column-maxpointwidth-20/
- * Limited to 50
- * @default null
- * @since 4.1.8
- * @product highcharts highstock
- * @apioption plotOptions.column.maxPointWidth
- */
+ /**
+ * A series specific or series type specific color set to apply instead
+ * of the global [colors](#colors) when [colorByPoint](
+ * #plotOptions.column.colorByPoint) is true.
+ *
+ * @type {Array}
+ * @since 3.0
+ * @product highcharts highstock gantt
+ * @apioption plotOptions.column.colors
+ */
- /**
- * Padding between each column or bar, in x axis units.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/column-pointpadding-default/
- * 0.1 by default
- * @sample {highcharts} highcharts/plotoptions/column-pointpadding-025/
- * 0.25
- * @sample {highcharts} highcharts/plotoptions/column-pointpadding-none/
- * 0 for tightly packed columns
- * @default 0.1
- * @product highcharts highstock
- */
- pointPadding: 0.1,
+ /**
+ * When true, each column edge is rounded to its nearest pixel in order
+ * to render sharp on screen. In some cases, when there are a lot of
+ * densely packed columns, this leads to visible difference in column
+ * widths or distance between columns. In these cases, setting `crisp`
+ * to `false` may look better, even though each column is rendered
+ * blurry.
+ *
+ * @sample {highcharts} highcharts/plotoptions/column-crisp-false/
+ * Crisp is false
+ *
+ * @since 5.0.10
+ * @product highcharts highstock gantt
+ */
+ crisp: true,
- /**
- * A pixel value specifying a fixed width for each column or bar. When
- * `null`, the width is calculated from the `pointPadding` and
- * `groupPadding`.
- *
- * @type {Number}
- * @see [maxPointWidth](#plotOptions.column.maxPointWidth)
- * @sample {highcharts} highcharts/plotoptions/column-pointwidth-20/
- * 20px wide columns regardless of chart width or the amount of data
- * points
- * @default null
- * @since 1.2.5
- * @product highcharts highstock
- * @apioption plotOptions.column.pointWidth
- */
+ /**
+ * Padding between each value groups, in x axis units.
+ *
+ * @sample {highcharts} highcharts/plotoptions/column-grouppadding-default/
+ * 0.2 by default
+ * @sample {highcharts} highcharts/plotoptions/column-grouppadding-none/
+ * No group padding - all columns are evenly spaced
+ *
+ * @product highcharts highstock gantt
+ */
+ groupPadding: 0.2,
- /**
- * The minimal height for a column or width for a bar. By default,
- * 0 values are not shown. To visualize a 0 (or close to zero) point,
- * set the minimal point length to a pixel value like 3\. In stacked
- * column charts, minPointLength might not be respected for tightly
- * packed values.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/column-minpointlength/
- * Zero base value
- * @sample {highcharts} highcharts/plotoptions/column-minpointlength-pos-and-neg/
- * Positive and negative close to zero values
- * @default 0
- * @product highcharts highstock
- */
- minPointLength: 0,
+ /**
+ * Whether to group non-stacked columns or to let them render
+ * independent of each other. Non-grouped columns will be laid out
+ * individually and overlap each other.
+ *
+ * @sample {highcharts} highcharts/plotoptions/column-grouping-false/
+ * Grouping disabled
+ * @sample {highstock} highcharts/plotoptions/column-grouping-false/
+ * Grouping disabled
+ *
+ * @type {boolean}
+ * @default true
+ * @since 2.3.0
+ * @product highcharts highstock gantt
+ * @apioption plotOptions.column.grouping
+ */
- /**
- * When the series contains less points than the crop threshold, all
- * points are drawn, event if the points fall outside the visible plot
- * area at the current zoom. The advantage of drawing all points (including
- * markers and columns), is that animation is performed on updates.
- * On the other hand, when the series contains more points than the
- * crop threshold, the series data is cropped to only contain points
- * that fall within the plot area. The advantage of cropping away invisible
- * points is to increase performance on large series. .
- *
- * @type {Number}
- * @default 50
- * @product highcharts highstock
- */
- cropThreshold: 50,
+ /**
+ * @ignore-option
+ */
+ marker: null, // point options are specified in the base options
- /**
- * The X axis range that each point is valid for. This determines the
- * width of the column. On a categorized axis, the range will be 1
- * by default (one category unit). On linear and datetime axes, the
- * range will be computed as the distance between the two closest data
- * points.
- *
- * The default `null` means it is computed automatically, but this option
- * can be used to override the automatic value.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/column-pointrange/
- * Set the point range to one day on a data set with one week
- * between the points
- * @default null
- * @since 2.3
- * @product highcharts highstock
- */
- pointRange: null,
+ /**
+ * The maximum allowed pixel width for a column, translated to the
+ * height of a bar in a bar chart. This prevents the columns from
+ * becoming too wide when there is a small number of points in the
+ * chart.
+ *
+ * @see [pointWidth](#plotOptions.column.pointWidth)
+ *
+ * @sample {highcharts} highcharts/plotoptions/column-maxpointwidth-20/
+ * Limited to 50
+ * @sample {highstock} highcharts/plotoptions/column-maxpointwidth-20/
+ * Limited to 50
+ *
+ * @type {number}
+ * @since 4.1.8
+ * @product highcharts highstock gantt
+ * @apioption plotOptions.column.maxPointWidth
+ */
- states: {
+ /**
+ * Padding between each column or bar, in x axis units.
+ *
+ * @sample {highcharts} highcharts/plotoptions/column-pointpadding-default/
+ * 0.1 by default
+ * @sample {highcharts} highcharts/plotoptions/column-pointpadding-025/
+ * 0.25
+ * @sample {highcharts} highcharts/plotoptions/column-pointpadding-none/
+ * 0 for tightly packed columns
+ *
+ * @product highcharts highstock gantt
+ */
+ pointPadding: 0.1,
/**
- * @extends plotOptions.series.states.hover
- * @excluding halo,lineWidth,lineWidthPlus,marker
- * @product highcharts highstock
+ * A pixel value specifying a fixed width for each column or bar. When
+ * `null`, the width is calculated from the `pointPadding` and
+ * `groupPadding`.
+ *
+ * @see [maxPointWidth](#plotOptions.column.maxPointWidth)
+ *
+ * @sample {highcharts} highcharts/plotoptions/column-pointwidth-20/
+ * 20px wide columns regardless of chart width or the amount of
+ * data points
+ *
+ * @type {number}
+ * @since 1.2.5
+ * @product highcharts highstock gantt
+ * @apioption plotOptions.column.pointWidth
*/
- hover: {
- /**
- * @ignore-option
- */
- halo: false,
- /**
- * A specific border color for the hovered point. Defaults to
- * inherit the normal state border color.
- *
- * @type {Color}
- * @product highcharts
- * @apioption plotOptions.column.states.hover.borderColor
- */
+ /**
+ * A pixel value specifying a fixed width for the column or bar.
+ * Overrides pointWidth on the series.
+ *
+ * @see [series.pointWidth](#plotOptions.column.pointWidth)
+ *
+ * @type {number}
+ * @default undefined
+ * @since 7.0.0
+ * @product highcharts highstock gantt
+ * @apioption series.column.data.pointWidth
+ */
+
+ /**
+ * The minimal height for a column or width for a bar. By default,
+ * 0 values are not shown. To visualize a 0 (or close to zero) point,
+ * set the minimal point length to a pixel value like 3\. In stacked
+ * column charts, minPointLength might not be respected for tightly
+ * packed values.
+ *
+ * @sample {highcharts} highcharts/plotoptions/column-minpointlength/
+ * Zero base value
+ * @sample {highcharts} highcharts/plotoptions/column-minpointlength-pos-and-neg/
+ * Positive and negative close to zero values
+ *
+ * @product highcharts highstock gantt
+ */
+ minPointLength: 0,
+
+ /**
+ * When the series contains less points than the crop threshold, all
+ * points are drawn, event if the points fall outside the visible plot
+ * area at the current zoom. The advantage of drawing all points
+ * (including markers and columns), is that animation is performed on
+ * updates. On the other hand, when the series contains more points than
+ * the crop threshold, the series data is cropped to only contain points
+ * that fall within the plot area. The advantage of cropping away
+ * invisible points is to increase performance on large series.
+ *
+ * @product highcharts highstock gantt
+ */
+ cropThreshold: 50,
+
+ /**
+ * The X axis range that each point is valid for. This determines the
+ * width of the column. On a categorized axis, the range will be 1
+ * by default (one category unit). On linear and datetime axes, the
+ * range will be computed as the distance between the two closest data
+ * points.
+ *
+ * The default `null` means it is computed automatically, but this
+ * option can be used to override the automatic value.
+ *
+ * @sample {highcharts} highcharts/plotoptions/column-pointrange/
+ * Set the point range to one day on a data set with one week
+ * between the points
+ *
+ * @type {number|null}
+ * @since 2.3
+ * @product highcharts highstock gantt
+ */
+ pointRange: null,
+
+ states: {
/**
- * A specific color for the hovered point.
+ * Options for the hovered point. These settings override the normal
+ * state options when a point is moused over or touched.
*
- * @type {Color}
- * @default undefined
- * @product highcharts
- * @apioption plotOptions.column.states.hover.color
+ * @extends plotOptions.series.states.hover
+ * @excluding halo, lineWidth, lineWidthPlus, marker
+ * @product highcharts highstock gantt
*/
+ hover: {
+ /** @ignore-option */
+ halo: false,
+ /**
+ * A specific border color for the hovered point. Defaults to
+ * inherit the normal state border color.
+ *
+ * @type {Highcharts.ColorString}
+ * @product highcharts gantt
+ * @apioption plotOptions.column.states.hover.borderColor
+ */
+
+ /**
+ * A specific color for the hovered point.
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @product highcharts gantt
+ * @apioption plotOptions.column.states.hover.color
+ */
+
+ /**
+ * How much to brighten the point on interaction. Requires the
+ * main color to be defined in hex or rgb(a) format.
+ *
+ * In styled mode, the hover brightening is by default replaced
+ * with a fill-opacity set in the `.highcharts-point:hover`
+ * rule.
+ *
+ * @sample {highcharts} highcharts/plotoptions/column-states-hover-brightness/
+ * Brighten by 0.5
+ *
+ * @product highcharts highstock gantt
+ */
+ brightness: 0.1
+ },
/**
- * How much to brighten the point on interaction. Requires the main
- * color to be defined in hex or rgb(a) format.
+ * Options for the selected point. These settings override the
+ * normal state options when a point is selected.
*
- * In styled mode, the hover brightening is by default replaced
- * with a fill-opacity set in the `.highcharts-point:hover` rule.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/column-states-hover-brightness/
- * Brighten by 0.5
- * @default 0.1
- * @product highcharts highstock
+ * @extends plotOptions.series.states.select
+ * @excluding halo, lineWidth, lineWidthPlus, marker
+ * @product highcharts highstock gantt
*/
- brightness: 0.1
+ select: {
+ /**
+ * A specific color for the selected point.
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @default #cccccc
+ * @product highcharts highstock gantt
+ */
+ color: '#cccccc',
+ /**
+ * A specific border color for the selected point.
+ *
+ * @type {Highcharts.ColorString}
+ * @default #000000
+ * @product highcharts highstock gantt
+ */
+ borderColor: '#000000'
+ }
},
+ dataLabels: {
- select: {
- color: '#cccccc',
- borderColor: '#000000'
- }
-
- },
+ /**
+ * @type {Highcharts.AlignType|null}
+ */
+ align: null, // auto
- dataLabels: {
- align: null, // auto
- verticalAlign: null, // auto
- y: null
- },
+ /**
+ * @type {Highcharts.VerticalAlignType|null}
+ */
+ verticalAlign: null, // auto
- /**
- * When this is true, the series will not cause the Y axis to cross
- * the zero plane (or [threshold](#plotOptions.series.threshold) option)
- * unless the data actually crosses the plane.
- *
- * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
- * 3 will make the Y axis show negative values according to the `minPadding`
- * option. If `softThreshold` is `true`, the Y axis starts at 0.
- *
- * @type {Boolean}
- * @default {highcharts} true
- * @default {highstock} false
- * @since 4.1.9
- * @product highcharts highstock
- */
- softThreshold: false,
+ /**
+ * @type {number|null}
+ */
+ y: null
+ },
- // false doesn't work well: http://jsfiddle.net/highcharts/hz8fopan/14/
- /** @ignore */
- startFromThreshold: true,
+ /**
+ * When this is true, the series will not cause the Y axis to cross
+ * the zero plane (or [threshold](#plotOptions.series.threshold) option)
+ * unless the data actually crosses the plane.
+ *
+ * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
+ * 3 will make the Y axis show negative values according to the
+ * `minPadding` option. If `softThreshold` is `true`, the Y axis starts
+ * at 0.
+ *
+ * @since 4.1.9
+ * @product highcharts highstock
+ */
+ softThreshold: false,
- stickyTracking: false,
+ // false doesn't work well: https://jsfiddle.net/highcharts/hz8fopan/14/
+ /** @ignore-option */
+ startFromThreshold: true,
- tooltip: {
- distance: 6
- },
+ stickyTracking: false,
- /**
- * The Y axis value to serve as the base for the columns, for distinguishing
- * between values above and below a threshold. If `null`, the columns
- * extend from the padding Y axis minimum.
- *
- * @type {Number}
- * @default 0
- * @since 2.0
- * @product highcharts
- */
- threshold: 0,
+ tooltip: {
+ distance: 6
+ },
+ /**
+ * The Y axis value to serve as the base for the columns, for
+ * distinguishing between values above and below a threshold. If `null`,
+ * the columns extend from the padding Y axis minimum.
+ *
+ * @since 2.0
+ * @product highcharts
+ */
+ threshold: 0,
- /**
- * The color of the border surrounding each column or bar.
- *
- * In styled mode, the border stroke can be set with the `.highcharts-point`
- * rule.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/plotoptions/column-bordercolor/
- * Dark gray border
- * @default #ffffff
- * @product highcharts highstock
- */
- borderColor: '#ffffff'
- // borderWidth: 1
+ /**
+ * The width of the border surrounding each column or bar. Defaults to
+ * `1` when there is room for a border, but to `0` when the columns are
+ * so dense that a border would cover the next column.
+ *
+ * In styled mode, the stroke width can be set with the
+ * `.highcharts-point` rule.
+ *
+ * @sample {highcharts} highcharts/plotoptions/column-borderwidth/
+ * 2px black border
+ *
+ * @type {number}
+ * @default undefined
+ * @product highcharts highstock gantt
+ * @apioption plotOptions.column.borderWidth
+ */
+ /**
+ * The color of the border surrounding each column or bar.
+ *
+ * In styled mode, the border stroke can be set with the
+ * `.highcharts-point` rule.
+ *
+ * @sample {highcharts} highcharts/plotoptions/column-bordercolor/
+ * Dark gray border
+ *
+ * @type {Highcharts.ColorString}
+ * @default #ffffff
+ * @product highcharts highstock gantt
+ */
+ borderColor: '#ffffff'
- }, /** @lends seriesTypes.column.prototype */ {
- cropShoulder: 0,
- // When tooltip is not shared, this series (and derivatives) requires direct
- // touch/hover. KD-tree does not apply.
- directTouch: true,
- trackerGroups: ['group', 'dataLabelsGroup'],
- // use separate negative stacks, unlike area stacks where a negative point
- // is substracted from previous (#1910)
- negStacks: true,
+ }, /** @lends seriesTypes.column.prototype */ {
+ cropShoulder: 0,
+ // When tooltip is not shared, this series (and derivatives) requires
+ // direct touch/hover. KD-tree does not apply.
+ directTouch: true,
+ trackerGroups: ['group', 'dataLabelsGroup'],
+ // use separate negative stacks, unlike area stacks where a negative
+ // point is substracted from previous (#1910)
+ negStacks: true,
- /**
- * Initialize the series. Extends the basic Series.init method by
- * marking other series of the same type as dirty.
- *
- * @function #init
- * @memberOf seriesTypes.column
- *
- */
- init: function() {
- Series.prototype.init.apply(this, arguments);
+ /**
+ * Initialize the series. Extends the basic Series.init method by
+ * marking other series of the same type as dirty.
+ *
+ * @private
+ * @function Highcharts.seriesTypes.column#init
+ */
+ init: function () {
+ Series.prototype.init.apply(this, arguments);
- var series = this,
- chart = series.chart;
+ var series = this,
+ chart = series.chart;
- // if the series is added dynamically, force redraw of other
- // series affected by a new column
- if (chart.hasRendered) {
- each(chart.series, function(otherSeries) {
- if (otherSeries.type === series.type) {
- otherSeries.isDirty = true;
- }
- });
- }
- },
+ // if the series is added dynamically, force redraw of other
+ // series affected by a new column
+ if (chart.hasRendered) {
+ chart.series.forEach(function (otherSeries) {
+ if (otherSeries.type === series.type) {
+ otherSeries.isDirty = true;
+ }
+ });
+ }
+ },
- /**
- * Return the width and x offset of the columns adjusted for grouping,
- * groupPadding, pointPadding, pointWidth etc.
- */
- getColumnMetrics: function() {
+ /**
+ * Return the width and x offset of the columns adjusted for grouping,
+ * groupPadding, pointPadding, pointWidth etc.
+ *
+ * @private
+ * @function Highcharts.seriesTypes.column#getColumnMetrics
+ *
+ * @return {Highcharts.ColumnMetricsObject}
+ */
+ getColumnMetrics: function () {
+
+ var series = this,
+ options = series.options,
+ xAxis = series.xAxis,
+ yAxis = series.yAxis,
+ reversedStacks = xAxis.options.reversedStacks,
+ // Keep backward compatibility: reversed xAxis had reversed
+ // stacks
+ reverseStacks = (xAxis.reversed && !reversedStacks) ||
+ (!xAxis.reversed && reversedStacks),
+ stackKey,
+ stackGroups = {},
+ columnCount = 0;
+
+ // Get the total number of column type series. This is called on
+ // every series. Consider moving this logic to a chart.orderStacks()
+ // function and call it on init, addSeries and removeSeries
+ if (options.grouping === false) {
+ columnCount = 1;
+ } else {
+ series.chart.series.forEach(function (otherSeries) {
+ var otherOptions = otherSeries.options,
+ otherYAxis = otherSeries.yAxis,
+ columnIndex;
- var series = this,
- options = series.options,
- xAxis = series.xAxis,
- yAxis = series.yAxis,
- reversedXAxis = xAxis.reversed,
- stackKey,
- stackGroups = {},
- columnCount = 0;
-
- // Get the total number of column type series. This is called on every
- // series. Consider moving this logic to a chart.orderStacks() function
- // and call it on init, addSeries and removeSeries
- if (options.grouping === false) {
- columnCount = 1;
- } else {
- each(series.chart.series, function(otherSeries) {
- var otherOptions = otherSeries.options,
- otherYAxis = otherSeries.yAxis,
- columnIndex;
- if (
- otherSeries.type === series.type &&
+ if (
+ otherSeries.type === series.type &&
(
otherSeries.visible ||
!series.chart.options.chart.ignoreHiddenSeries
) &&
yAxis.len === otherYAxis.len &&
yAxis.pos === otherYAxis.pos
- ) { // #642, #2086
- if (otherOptions.stacking) {
- stackKey = otherSeries.stackKey;
- if (stackGroups[stackKey] === undefined) {
- stackGroups[stackKey] = columnCount++;
+ ) { // #642, #2086
+ if (otherOptions.stacking) {
+ stackKey = otherSeries.stackKey;
+ if (stackGroups[stackKey] === undefined) {
+ stackGroups[stackKey] = columnCount++;
+ }
+ columnIndex = stackGroups[stackKey];
+ } else if (otherOptions.grouping !== false) { // #1162
+ columnIndex = columnCount++;
}
- columnIndex = stackGroups[stackKey];
- } else if (otherOptions.grouping !== false) { // #1162
- columnIndex = columnCount++;
+ otherSeries.columnIndex = columnIndex;
}
- otherSeries.columnIndex = columnIndex;
- }
- });
- }
+ });
+ }
- var categoryWidth = Math.min(
- Math.abs(xAxis.transA) * (
- xAxis.ordinalSlope ||
+ var categoryWidth = Math.min(
+ Math.abs(xAxis.transA) * (
+ xAxis.ordinalSlope ||
options.pointRange ||
xAxis.closestPointRange ||
xAxis.tickInterval ||
1
- ), // #2610
- xAxis.len // #1535
- ),
- groupPadding = categoryWidth * options.groupPadding,
- groupWidth = categoryWidth - 2 * groupPadding,
- pointOffsetWidth = groupWidth / (columnCount || 1),
- pointWidth = Math.min(
- options.maxPointWidth || xAxis.len,
- pick(
- options.pointWidth,
- pointOffsetWidth * (1 - 2 * options.pointPadding)
- )
- ),
- pointPadding = (pointOffsetWidth - pointWidth) / 2,
- // #1251, #3737
- colIndex = (series.columnIndex || 0) + (reversedXAxis ? 1 : 0),
- pointXOffset =
- pointPadding +
- (
- groupPadding +
- colIndex * pointOffsetWidth -
- (categoryWidth / 2)
- ) * (reversedXAxis ? -1 : 1);
-
- // Save it for reading in linked series (Error bars particularly)
- series.columnMetrics = {
- width: pointWidth,
- offset: pointXOffset
- };
- return series.columnMetrics;
+ ), // #2610
+ xAxis.len // #1535
+ ),
+ groupPadding = categoryWidth * options.groupPadding,
+ groupWidth = categoryWidth - 2 * groupPadding,
+ pointOffsetWidth = groupWidth / (columnCount || 1),
+ pointWidth = Math.min(
+ options.maxPointWidth || xAxis.len,
+ pick(
+ options.pointWidth,
+ pointOffsetWidth * (1 - 2 * options.pointPadding)
+ )
+ ),
+ pointPadding = (pointOffsetWidth - pointWidth) / 2,
+ // #1251, #3737
+ colIndex = (series.columnIndex || 0) + (reverseStacks ? 1 : 0),
+ pointXOffset =
+ pointPadding +
+ (
+ groupPadding +
+ colIndex * pointOffsetWidth -
+ (categoryWidth / 2)
+ ) * (reverseStacks ? -1 : 1);
+
+ // Save it for reading in linked series (Error bars particularly)
+ series.columnMetrics = {
+ width: pointWidth,
+ offset: pointXOffset
+ };
+ return series.columnMetrics;
- },
+ },
+
+ /**
+ * Make the columns crisp. The edges are rounded to the nearest full
+ * pixel.
+ *
+ * @private
+ * @function Highcharts.seriesTypes.column#crispCol
+ *
+ * @param {number} x
+ *
+ * @param {number} y
+ *
+ * @param {number} w
+ *
+ * @param {number} h
+ *
+ * @return {*}
+ */
+ crispCol: function (x, y, w, h) {
+ var chart = this.chart,
+ borderWidth = this.borderWidth,
+ xCrisp = -(borderWidth % 2 ? 0.5 : 0),
+ yCrisp = borderWidth % 2 ? 0.5 : 1,
+ right,
+ bottom,
+ fromTop;
+
+ if (chart.inverted && chart.renderer.isVML) {
+ yCrisp += 1;
+ }
+
+ // Horizontal. We need to first compute the exact right edge, then
+ // round it and compute the width from there.
+ if (this.options.crisp) {
+ right = Math.round(x + w) + xCrisp;
+ x = Math.round(x) + xCrisp;
+ w = right - x;
+ }
+
+ // Vertical
+ bottom = Math.round(y + h) + yCrisp;
+ fromTop = Math.abs(y) <= 0.5 && bottom > 0.5; // #4504, #4656
+ y = Math.round(y) + yCrisp;
+ h = bottom - y;
- /**
- * Make the columns crisp. The edges are rounded to the nearest full pixel.
- */
- crispCol: function(x, y, w, h) {
- var chart = this.chart,
- borderWidth = this.borderWidth,
- xCrisp = -(borderWidth % 2 ? 0.5 : 0),
- yCrisp = borderWidth % 2 ? 0.5 : 1,
- right,
- bottom,
- fromTop;
+ // Top edges are exceptions
+ if (fromTop && h) { // #5146
+ y -= 1;
+ h += 1;
+ }
- if (chart.inverted && chart.renderer.isVML) {
- yCrisp += 1;
- }
+ return {
+ x: x,
+ y: y,
+ width: w,
+ height: h
+ };
+ },
- // Horizontal. We need to first compute the exact right edge, then round
- // it and compute the width from there.
- if (this.options.crisp) {
- right = Math.round(x + w) + xCrisp;
- x = Math.round(x) + xCrisp;
- w = right - x;
- }
+ /**
+ * Translate each point to the plot area coordinate system and find
+ * shape positions
+ *
+ * @private
+ * @function Highcharts.seriesTypes.column#translate
+ */
+ translate: function () {
+ var series = this,
+ chart = series.chart,
+ options = series.options,
+ dense = series.dense =
+ series.closestPointRange * series.xAxis.transA < 2,
+ borderWidth = series.borderWidth = pick(
+ options.borderWidth,
+ dense ? 0 : 1 // #3635
+ ),
+ yAxis = series.yAxis,
+ threshold = options.threshold,
+ translatedThreshold = series.translatedThreshold =
+ yAxis.getThreshold(threshold),
+ minPointLength = pick(options.minPointLength, 5),
+ metrics = series.getColumnMetrics(),
+ seriesPointWidth = metrics.width,
+ // postprocessed for border width
+ seriesBarW = series.barW =
+ Math.max(seriesPointWidth, 1 + 2 * borderWidth),
+ seriesXOffset = series.pointXOffset = metrics.offset;
- // Vertical
- bottom = Math.round(y + h) + yCrisp;
- fromTop = Math.abs(y) <= 0.5 && bottom > 0.5; // #4504, #4656
- y = Math.round(y) + yCrisp;
- h = bottom - y;
+ if (chart.inverted) {
+ translatedThreshold -= 0.5; // #3355
+ }
- // Top edges are exceptions
- if (fromTop && h) { // #5146
- y -= 1;
- h += 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 = Math.ceil(seriesBarW);
+ }
- return {
- x: x,
- y: y,
- width: w,
- height: h
- };
- },
+ Series.prototype.translate.apply(series);
- /**
- * Translate each point to the plot area coordinate system and find shape
- * positions
- */
- translate: function() {
- var series = this,
- chart = series.chart,
- options = series.options,
- dense = series.dense =
- series.closestPointRange * series.xAxis.transA < 2,
- borderWidth = series.borderWidth = pick(
- options.borderWidth,
- dense ? 0 : 1 // #3635
- ),
- yAxis = series.yAxis,
- threshold = options.threshold,
- translatedThreshold = series.translatedThreshold =
- yAxis.getThreshold(threshold),
- minPointLength = pick(options.minPointLength, 5),
- metrics = series.getColumnMetrics(),
- pointWidth = metrics.width,
- // postprocessed for border width
- seriesBarW = series.barW =
- Math.max(pointWidth, 1 + 2 * borderWidth),
- pointXOffset = series.pointXOffset = metrics.offset;
-
- if (chart.inverted) {
- translatedThreshold -= 0.5; // #3355
- }
-
- // 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 = Math.ceil(seriesBarW);
- }
-
- Series.prototype.translate.apply(series);
-
- // Record the new values
- each(series.points, function(point) {
- var yBottom = pick(point.yBottom, translatedThreshold),
- safeDistance = 999 + Math.abs(yBottom),
- plotY = Math.min(
- Math.max(-safeDistance, point.plotY),
- yAxis.len + safeDistance
- ), // Don't draw too far outside plot area (#1303, #2241, #4264)
- barX = point.plotX + pointXOffset,
- barW = seriesBarW,
- barY = Math.min(plotY, yBottom),
- up,
- barH = Math.max(plotY, yBottom) - barY;
-
- // Handle options.minPointLength
- if (minPointLength && Math.abs(barH) < minPointLength) {
- barH = minPointLength;
- up = (!yAxis.reversed && !point.negative) ||
+ // Record the new values
+ series.points.forEach(function (point) {
+ var yBottom = pick(point.yBottom, translatedThreshold),
+ safeDistance = 999 + Math.abs(yBottom),
+ pointWidth = seriesPointWidth,
+ // Don't draw too far outside plot area (#1303, #2241,
+ // #4264)
+ plotY = Math.min(
+ Math.max(-safeDistance, point.plotY),
+ yAxis.len + safeDistance
+ ),
+ barX = point.plotX + seriesXOffset,
+ barW = seriesBarW,
+ barY = Math.min(plotY, yBottom),
+ up,
+ barH = Math.max(plotY, yBottom) - barY;
+
+ // Handle options.minPointLength
+ if (minPointLength && Math.abs(barH) < minPointLength) {
+ barH = minPointLength;
+ up = (!yAxis.reversed && !point.negative) ||
(yAxis.reversed && point.negative);
- // Reverse zeros if there's no positive value in the series
- // in visible range (#7046)
- if (
- point.y === threshold &&
+ // Reverse zeros if there's no positive value in the series
+ // in visible range (#7046)
+ if (
+ point.y === threshold &&
series.dataMax <= threshold &&
yAxis.min < threshold // and if there's room for it (#7311)
- ) {
- up = !up;
- }
-
- // If stacked...
- barY = Math.abs(barY - translatedThreshold) > minPointLength ?
- // ...keep position
- yBottom - minPointLength :
- // #1485, #4051
- translatedThreshold - (up ? minPointLength : 0);
- }
+ ) {
+ up = !up;
+ }
- // Cache for access in polar
- point.barX = barX;
- point.pointWidth = pointWidth;
+ // If stacked...
+ barY = (
+ Math.abs(barY - translatedThreshold) > minPointLength ?
+ // ...keep position
+ yBottom - minPointLength :
+ // #1485, #4051
+ translatedThreshold - (up ? minPointLength : 0)
+ );
+ }
- // Fix the tooltip on center of grouped columns (#1216, #424, #3648)
- point.tooltipPos = chart.inverted ? [
- yAxis.len + yAxis.pos - chart.plotLeft - plotY,
- series.xAxis.len - barX - barW / 2, barH
- ] : [barX + barW / 2, plotY + yAxis.pos - chart.plotTop, barH];
+ // Handle point.options.pointWidth
+ // @todo Handle grouping/stacking too. Calculate offset properly
+ if (defined(point.options.pointWidth)) {
+ pointWidth = barW = Math.ceil(point.options.pointWidth);
+ barX -= Math.round((pointWidth - seriesPointWidth) / 2);
+ }
- // Register shape type and arguments to be used in drawPoints
- point.shapeType = 'rect';
- point.shapeArgs = series.crispCol.apply(
- series,
- point.isNull ?
- // #3169, drilldown from null must have a position to work
- // from #6585, dataLabel should be placed on xAxis, not
- // floating in the middle of the chart
- [barX, translatedThreshold, barW, 0] : [barX, barY, barW, barH]
- );
- });
+ // Cache for access in polar
+ point.barX = barX;
+ point.pointWidth = pointWidth;
+
+ // Fix the tooltip on center of grouped columns (#1216, #424,
+ // #3648)
+ point.tooltipPos = chart.inverted ?
+ [
+ yAxis.len + yAxis.pos - chart.plotLeft - plotY,
+ series.xAxis.len - barX - barW / 2, barH
+ ] :
+ [barX + barW / 2, plotY + yAxis.pos - chart.plotTop, barH];
+
+ // Register shape type and arguments to be used in drawPoints
+ // Allow shapeType defined on pointClass level
+ point.shapeType = point.shapeType || 'rect';
+ point.shapeArgs = series.crispCol.apply(
+ series,
+ point.isNull ?
+ // #3169, drilldown from null must have a position to work
+ // from #6585, dataLabel should be placed on xAxis, not
+ // floating in the middle of the chart
+ [barX, translatedThreshold, barW, 0] :
+ [barX, barY, barW, barH]
+ );
+ });
- },
+ },
- getSymbol: noop,
+ getSymbol: noop,
- /**
- * Use a solid rectangle like the area series types
- */
- drawLegendSymbol: LegendSymbolMixin.drawRectangle,
+ /**
+ * Use a solid rectangle like the area series types
+ *
+ * @private
+ * @function Highcharts.seriesTypes.column#drawLegendSymbol
+ *
+ * @param {Highcharts.Legend} legend
+ * The legend object
+ *
+ * @param {Highcharts.Series|Highcharts.Point} item
+ * The series (this) or point
+ */
+ drawLegendSymbol: LegendSymbolMixin.drawRectangle,
- /**
- * Columns have no graph
- */
- drawGraph: function() {
- this.group[
- this.dense ? 'addClass' : 'removeClass'
- ]('highcharts-dense-data');
- },
+ /**
+ * Columns have no graph
+ *
+ * @private
+ * @function Highcharts.seriesTypes.column#drawGraph
+ */
+ drawGraph: function () {
+ this.group[
+ this.dense ? 'addClass' : 'removeClass'
+ ]('highcharts-dense-data');
+ },
+ /**
+ * Get presentational attributes
+ *
+ * @private
+ * @function Highcharts.seriesTypes.column#pointAttribs
+ *
+ * @param {Highcharts.Point} point
+ *
+ * @param {string} state
+ *
+ * @return {Highcharts.Dictionary}
+ */
+ pointAttribs: function (point, state) {
+ var options = this.options,
+ stateOptions,
+ ret,
+ p2o = this.pointAttrToOptions || {},
+ strokeOption = p2o.stroke || 'borderColor',
+ strokeWidthOption = p2o['stroke-width'] || 'borderWidth',
+ fill = (point && point.color) || this.color,
+ // set to fill when borderColor null:
+ stroke = (
+ (point && point[strokeOption]) ||
+ options[strokeOption] ||
+ this.color ||
+ fill
+ ),
+ strokeWidth = (point && point[strokeWidthOption]) ||
+ options[strokeWidthOption] || this[strokeWidthOption] || 0,
+ dashstyle = options.dashStyle,
+ zone,
+ brightness;
+
+ // Handle zone colors
+ if (point && this.zones.length) {
+ zone = point.getZone();
+ // When zones are present, don't use point.color (#4267).
+ // Changed order (#6527)
+ fill = (
+ point.options.color || (zone && zone.color) || this.color
+ );
+ }
- /**
- * Get presentational attributes
- */
- pointAttribs: function(point, state) {
- var options = this.options,
- stateOptions,
- ret,
- p2o = this.pointAttrToOptions || {},
- strokeOption = p2o.stroke || 'borderColor',
- strokeWidthOption = p2o['stroke-width'] || 'borderWidth',
- fill = (point && point.color) || this.color,
- stroke = (point && point[strokeOption]) || options[strokeOption] ||
- this.color || fill, // set to fill when borderColor null
- strokeWidth = (point && point[strokeWidthOption]) ||
- options[strokeWidthOption] || this[strokeWidthOption] || 0,
- dashstyle = options.dashStyle,
- zone,
- brightness;
-
- // Handle zone colors
- if (point && this.zones.length) {
- zone = point.getZone();
- // When zones are present, don't use point.color (#4267). Changed
- // order (#6527)
- fill = point.options.color || (zone && zone.color) || this.color;
- }
-
- // Select or hover states
- if (state) {
- stateOptions = merge(
- options.states[state],
- // #6401
- point.options.states && point.options.states[state] || {}
- );
- brightness = stateOptions.brightness;
- fill = stateOptions.color ||
+ // Select or hover states
+ if (state) {
+ stateOptions = merge(
+ options.states[state],
+ // #6401
+ point.options.states && point.options.states[state] || {}
+ );
+ brightness = stateOptions.brightness;
+ fill = stateOptions.color ||
(
brightness !== undefined &&
color(fill).brighten(stateOptions.brightness).get()
) ||
fill;
- stroke = stateOptions[strokeOption] || stroke;
- strokeWidth = stateOptions[strokeWidthOption] || strokeWidth;
- dashstyle = stateOptions.dashStyle || dashstyle;
- }
-
- ret = {
- 'fill': fill,
- 'stroke': stroke,
- 'stroke-width': strokeWidth
- };
-
- if (dashstyle) {
- ret.dashstyle = dashstyle;
- }
-
- return ret;
- },
+ stroke = stateOptions[strokeOption] || stroke;
+ strokeWidth = stateOptions[strokeWidthOption] || strokeWidth;
+ dashstyle = stateOptions.dashStyle || dashstyle;
+ }
+ ret = {
+ 'fill': fill,
+ 'stroke': stroke,
+ 'stroke-width': strokeWidth
+ };
- /**
- * Draw the columns. For bars, the series.group is rotated, so the same
- * coordinates apply for columns and bars. This method is inherited by
- * scatter series.
- */
- drawPoints: function() {
- var series = this,
- chart = this.chart,
- options = series.options,
- renderer = chart.renderer,
- animationLimit = options.animationLimit || 250,
- shapeArgs;
+ if (dashstyle) {
+ ret.dashstyle = dashstyle;
+ }
- // draw the columns
- each(series.points, function(point) {
- var plotY = point.plotY,
- graphic = point.graphic;
+ return ret;
+ },
- if (isNumber(plotY) && point.y !== null) {
- shapeArgs = point.shapeArgs;
+ /**
+ * Draw the columns. For bars, the series.group is rotated, so the same
+ * coordinates apply for columns and bars. This method is inherited by
+ * scatter series.
+ *
+ * @private
+ * @function Highcharts.seriesTypes.column#drawPoints
+ */
+ drawPoints: function () {
+ var series = this,
+ chart = this.chart,
+ options = series.options,
+ renderer = chart.renderer,
+ animationLimit = options.animationLimit || 250,
+ shapeArgs;
+
+ // draw the columns
+ series.points.forEach(function (point) {
+ var plotY = point.plotY,
+ graphic = point.graphic,
+ verb = graphic && chart.pointCount < animationLimit ?
+ 'animate' : 'attr';
+
+ if (isNumber(plotY) && point.y !== null) {
+ shapeArgs = point.shapeArgs;
- if (graphic) { // update
- graphic[
- chart.pointCount < animationLimit ? 'animate' : 'attr'
- ](
- merge(shapeArgs)
- );
+ if (graphic) { // update
+ graphic[verb](
+ merge(shapeArgs)
+ );
- } else {
- point.graphic = graphic =
+ } else {
+ point.graphic = graphic =
renderer[point.shapeType](shapeArgs)
- .add(point.group || series.group);
- }
+ .add(point.group || series.group);
+ }
- // Border radius is not stylable (#6900)
- if (options.borderRadius) {
- graphic.attr({
- r: options.borderRadius
- });
- }
+ // Border radius is not stylable (#6900)
+ if (options.borderRadius) {
+ graphic.attr({
+ r: options.borderRadius
+ });
+ }
+ // Presentational
+ if (!chart.styledMode) {
+ graphic[verb](series.pointAttribs(
+ point,
+ point.selected && 'select'
+ ))
+ .shadow(
+ options.shadow,
+ null,
+ options.stacking && !options.borderRadius
+ );
+ }
- // Presentational
- graphic
- .attr(series.pointAttribs(
- point,
- point.selected && 'select'
- ))
- .shadow(
- options.shadow,
- null,
- options.stacking && !options.borderRadius
- );
+ graphic.addClass(point.getClassName(), true);
- graphic.addClass(point.getClassName(), true);
+ } else if (graphic) {
+ point.graphic = graphic.destroy(); // #1269
+ }
+ });
+ },
+ /**
+ * Animate the column heights one by one from zero.
+ *
+ * @private
+ * @function Highcharts.seriesTypes.column#animate
+ *
+ * @param {boolean} init
+ * Whether to initialize the animation or run it
+ */
+ animate: function (init) {
+ var series = this,
+ yAxis = this.yAxis,
+ options = series.options,
+ inverted = this.chart.inverted,
+ attr = {},
+ translateProp = inverted ? 'translateX' : 'translateY',
+ translateStart,
+ translatedThreshold;
+
+ if (svg) { // VML is too slow anyway
+ if (init) {
+ attr.scaleY = 0.001;
+ translatedThreshold = Math.min(
+ yAxis.pos + yAxis.len,
+ Math.max(yAxis.pos, yAxis.toPixels(options.threshold))
+ );
+ if (inverted) {
+ attr.translateX = translatedThreshold - yAxis.len;
+ } else {
+ attr.translateY = translatedThreshold;
+ }
- } else if (graphic) {
- point.graphic = graphic.destroy(); // #1269
- }
- });
- },
+ // apply finnal clipping (used in Highstock) (#7083)
+ // animation is done by scaleY, so cliping is for panes
+ if (series.clipBox) {
+ series.setClip();
+ }
- /**
- * Animate the column heights one by one from zero
- * @param {Boolean} init Whether to initialize the animation or run it
- */
- animate: function(init) {
- var series = this,
- yAxis = this.yAxis,
- options = series.options,
- inverted = this.chart.inverted,
- attr = {},
- translateProp = inverted ? 'translateX' : 'translateY',
- translateStart,
- translatedThreshold;
+ series.group.attr(attr);
+
+ } else { // run the animation
+ translateStart = series.group.attr(translateProp);
+ series.group.animate(
+ { scaleY: 1 },
+ extend(animObject(series.options.animation), {
+ // Do the scale synchronously to ensure smooth
+ // updating (#5030, #7228)
+ step: function (val, fx) {
+
+ attr[translateProp] =
+ translateStart +
+ fx.pos * (yAxis.pos - translateStart);
+ series.group.attr(attr);
+ }
+ })
+ );
- if (svg) { // VML is too slow anyway
- if (init) {
- attr.scaleY = 0.001;
- translatedThreshold = Math.min(
- yAxis.pos + yAxis.len,
- Math.max(yAxis.pos, yAxis.toPixels(options.threshold))
- );
- if (inverted) {
- attr.translateX = translatedThreshold - yAxis.len;
- } else {
- attr.translateY = translatedThreshold;
+ // delete this function to allow it only once
+ series.animate = null;
}
- series.group.attr(attr);
-
- } else { // run the animation
- translateStart = series.group.attr(translateProp);
- series.group.animate({
- scaleY: 1
- },
- extend(animObject(series.options.animation), {
- // Do the scale synchronously to ensure smooth updating
- // (#5030, #7228)
- step: function(val, fx) {
-
- attr[translateProp] =
- translateStart +
- fx.pos * (yAxis.pos - translateStart);
- series.group.attr(attr);
- }
- }));
-
- // delete this function to allow it only once
- series.animate = null;
}
- }
- },
+ },
- /**
- * Remove this series from the chart
- */
- remove: function() {
- var series = this,
- chart = series.chart;
+ /**
+ * Remove this series from the chart
+ *
+ * @private
+ * @function Highcharts.seriesTypes.column#remove
+ */
+ remove: function () {
+ var series = this,
+ chart = series.chart;
+
+ // column and bar series affects other series of the same type
+ // as they are either stacked or grouped
+ if (chart.hasRendered) {
+ chart.series.forEach(function (otherSeries) {
+ if (otherSeries.type === series.type) {
+ otherSeries.isDirty = true;
+ }
+ });
+ }
- // column and bar series affects other series of the same type
- // as they are either stacked or grouped
- if (chart.hasRendered) {
- each(chart.series, function(otherSeries) {
- if (otherSeries.type === series.type) {
- otherSeries.isDirty = true;
- }
- });
+ Series.prototype.remove.apply(series, arguments);
}
-
- Series.prototype.remove.apply(series, arguments);
- }
- });
+ });
/**
* A `column` series. If the [type](#series.column.type) option is
* not specified, it is inherited from [chart.type](#chart.type).
*
- * For options that apply to multiple series, it is recommended to add
- * them to the [plotOptions.series](#plotOptions.series) options structure.
- * To apply to all series of this specific type, apply it to [plotOptions.
- * column](#plotOptions.column).
- *
- * @type {Object}
- * @extends series,plotOptions.column
- * @excluding dataParser,dataURL,marker
- * @product highcharts highstock
+ * @extends series,plotOptions.column
+ * @excluding connectNulls, dashStyle, dataParser, dataURL, gapSize, gapUnit,
+ * linecap, lineWidth, marker, connectEnds, step
+ * @product highcharts highstock
* @apioption series.column
*/
@@ -30235,51 +39594,47 @@
* An array of data points for the series. For the `column` series type,
* points can be given in the following ways:
*
- * 1. An array of numerical values. In this case, the numerical values
- * will be interpreted as `y` options. The `x` values will be automatically
- * calculated, either starting at 0 and incremented by 1, or from `pointStart`
- * and `pointInterval` given in the series options. If the axis has
- * categories, these will be used. Example:
- *
- * ```js
- * data: [0, 5, 3, 5]
- * ```
- *
- * 2. An array of arrays with 2 values. In this case, the values correspond
- * to `x,y`. If the first value is a string, it is applied as the name
- * of the point, and the `x` value is inferred.
- *
- * ```js
- * data: [
- * [0, 6],
- * [1, 2],
- * [2, 6]
- * ]
- * ```
+ * 1. An array of numerical values. In this case, the numerical values will be
+ * interpreted as `y` options. The `x` values will be automatically
+ * calculated, either starting at 0 and incremented by 1, or from
+ * `pointStart` and `pointInterval` given in the series options. If the axis
+ * has categories, these will be used. Example:
+ * ```js
+ * data: [0, 5, 3, 5]
+ * ```
*
- * 3. An array of objects with named values. The objects are point
- * configuration objects as seen below. If the total number of data
- * points exceeds the series' [turboThreshold](#series.column.turboThreshold),
- * this option is not available.
+ * 2. An array of arrays with 2 values. In this case, the values correspond to
+ * `x,y`. If the first value is a string, it is applied as the name of the
+ * point, and the `x` value is inferred.
+ * ```js
+ * data: [
+ * [0, 6],
+ * [1, 2],
+ * [2, 6]
+ * ]
+ * ```
*
- * ```js
- * data: [{
- * x: 1,
- * y: 9,
- * name: "Point2",
- * color: "#00FF00"
- * }, {
- * x: 1,
- * y: 6,
- * name: "Point1",
- * color: "#FF00FF"
- * }]
- * ```
+ * 3. An array of objects with named values. The following snippet shows only a
+ * few settings, see the complete options set below. If the total number of
+ * data points exceeds the series'
+ * [turboThreshold](#series.column.turboThreshold), this option is not
+ * available.
+ * ```js
+ * data: [{
+ * x: 1,
+ * y: 9,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * y: 6,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
*
- * @type {Array}
- * @extends series.line.data
- * @excluding marker
- * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
+ * @sample {highcharts} highcharts/chart/reflow-true/
+ * Numerical values
* @sample {highcharts} highcharts/series/data-array-of-arrays/
* Arrays of numeric x and y
* @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
@@ -30288,283 +39643,465 @@
* Arrays of point.name and y
* @sample {highcharts} highcharts/series/data-array-of-objects/
* Config objects
- * @product highcharts highstock
+ *
+ * @type {Array|*>}
+ * @extends series.line.data
+ * @excluding marker
+ * @product highcharts highstock
* @apioption series.column.data
*/
+ /**
+ * The color of the border surrounding the column or bar.
+ *
+ * In styled mode, the border stroke can be set with the `.highcharts-point`
+ * rule.
+ *
+ * @sample {highcharts} highcharts/plotoptions/column-bordercolor/
+ * Dark gray border
+ *
+ * @type {Highcharts.ColorString}
+ * @product highcharts highstock
+ * @apioption series.column.data.borderColor
+ */
+
+ /**
+ * The width of the border surrounding the column or bar.
+ *
+ * In styled mode, the stroke width can be set with the `.highcharts-point`
+ * rule.
+ *
+ * @sample {highcharts} highcharts/plotoptions/column-borderwidth/
+ * 2px black border
+ *
+ * @type {number}
+ * @product highcharts highstock
+ * @apioption series.column.data.borderWidth
+ */
+
+ /**
+ * @excluding halo, lineWidth, lineWidthPlus, marker
+ * @product highcharts highstock
+ * @apioption series.column.states.hover
+ */
- }(Highcharts));
- (function(H) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * @excluding halo, lineWidth, lineWidthPlus, marker
+ * @product highcharts highstock
+ * @apioption series.column.states.select
+ */
+
+ }(Highcharts));
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
var seriesType = H.seriesType;
/**
- * The Bar series class
- */
- seriesType('bar', 'column', null, {
- inverted: true
- });
- /**
- * A bar series is a special type of column series where the columns are
- * horizontal.
+ * Bar series type.
+ *
+ * @private
+ * @class
+ * @name Highcharts.seriesTypes.bar
*
- * @sample highcharts/demo/bar-basic/ Bar chart
- * @extends {plotOptions.column}
- * @product highcharts
- * @optionparent plotOptions.bar
+ * @augments Highcharts.Series
*/
+ seriesType('bar', 'column',
+
+ /**
+ * A bar series is a special type of column series where the columns are
+ * horizontal.
+ *
+ * @sample highcharts/demo/bar-basic/
+ * Bar chart
+ *
+ * @extends plotOptions.column
+ * @product highcharts
+ * @apioption plotOptions.bar
+ */
+
+ /**
+ * Alignment of the data label relative to the data point.
+ *
+ * @sample {highcharts} highcharts/plotoptions/bar-datalabels-align-inside-bar/
+ * Data labels inside the bar
+ *
+ * @type {string}
+ * @default left
+ * @product highcharts
+ * @apioption plotOptions.bar.dataLabels.align
+ */
+
+ /**
+ * The x position of the data label relative to the data point.
+ *
+ * @sample {highcharts} highcharts/plotoptions/bar-datalabels-align-inside-bar/
+ * Data labels inside the bar
+ *
+ * @type {number}
+ * @default 5
+ * @product highcharts
+ * @apioption plotOptions.bar.dataLabels.x
+ */
+
+ /**
+ * @ignore
+ */
+ null
+
+ , {
+ inverted: true
+ });
/**
* A `bar` series. If the [type](#series.bar.type) option is not specified,
* it is inherited from [chart.type](#chart.type).
- *
- * For options that apply to multiple series, it is recommended to add
- * them to the [plotOptions.series](#plotOptions.series) options structure.
- * To apply to all series of this specific type, apply it to [plotOptions.
- * bar](#plotOptions.bar).
- *
- * @type {Object}
- * @extends series,plotOptions.bar
- * @excluding dataParser,dataURL
- * @product highcharts
+ *
+ * @extends series,plotOptions.bar
+ * @excluding connectNulls, dashStyle, dataParser, dataURL, gapSize, gapUnit,
+ * linecap, lineWidth, marker, connectEnds, step
+ * @product highcharts
* @apioption series.bar
*/
/**
* An array of data points for the series. For the `bar` series type,
* points can be given in the following ways:
- *
- * 1. An array of numerical values. In this case, the numerical values
- * will be interpreted as `y` options. The `x` values will be automatically
- * calculated, either starting at 0 and incremented by 1, or from `pointStart`
- * and `pointInterval` given in the series options. If the axis has
- * categories, these will be used. Example:
- *
- * ```js
- * data: [0, 5, 3, 5]
- * ```
- *
- * 2. An array of arrays with 2 values. In this case, the values correspond
- * to `x,y`. If the first value is a string, it is applied as the name
- * of the point, and the `x` value is inferred.
- *
- * ```js
- * data: [
- * [0, 5],
- * [1, 10],
- * [2, 3]
- * ]
- * ```
- *
- * 3. An array of objects with named values. The objects are point
- * configuration objects as seen below. If the total number of data
- * points exceeds the series' [turboThreshold](#series.bar.turboThreshold),
- * this option is not available.
- *
- * ```js
- * data: [{
- * x: 1,
- * y: 1,
- * name: "Point2",
- * color: "#00FF00"
- * }, {
- * x: 1,
- * y: 10,
- * name: "Point1",
- * color: "#FF00FF"
- * }]
- * ```
- *
- * @type {Array}
- * @extends series.column.data
- * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
- * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
- * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
- * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
- * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
- * @product highcharts
+ *
+ * 1. An array of numerical values. In this case, the numerical values will be
+ * interpreted as `y` options. The `x` values will be automatically
+ * calculated, either starting at 0 and incremented by 1, or from
+ * `pointStart` and `pointInterval` given in the series options. If the axis
+ * has categories, these will be used. Example:
+ * ```js
+ * data: [0, 5, 3, 5]
+ * ```
+ *
+ * 2. An array of arrays with 2 values. In this case, the values correspond to
+ * `x,y`. If the first value is a string, it is applied as the name of the
+ * point, and the `x` value is inferred.
+ * ```js
+ * data: [
+ * [0, 5],
+ * [1, 10],
+ * [2, 3]
+ * ]
+ * ```
+ *
+ * 3. An array of objects with named values. The following snippet shows only a
+ * few settings, see the complete options set below. If the total number of
+ * data points exceeds the series'
+ * [turboThreshold](#series.bar.turboThreshold), this option is not
+ * available.
+ * ```js
+ * data: [{
+ * x: 1,
+ * y: 1,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * y: 10,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @sample {highcharts} highcharts/chart/reflow-true/
+ * Numerical values
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/
+ * Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
+ * Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/
+ * Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
+ *
+ * @type {Array|*>}
+ * @extends series.column.data
+ * @product highcharts
* @apioption series.bar.data
*/
/**
- * Alignment of the data label relative to the data point.
- *
- * @type {String}
- * @sample {highcharts} highcharts/plotoptions/bar-datalabels-align-inside-bar/
- * Data labels inside the bar
- * @default left
- * @product highcharts
- * @apioption plotOptions.bar.dataLabels.align
+ * @excluding halo,lineWidth,lineWidthPlus,marker
+ * @product highcharts highstock
+ * @apioption series.bar.states.hover
*/
/**
- * The x position of the data label relative to the data point.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/bar-datalabels-align-inside-bar/
- * Data labels inside the bar
- * @default 5
- * @product highcharts
- * @apioption plotOptions.bar.dataLabels.x
+ * @excluding halo,lineWidth,lineWidthPlus,marker
+ * @product highcharts highstock
+ * @apioption series.bar.states.select
*/
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
+
var Series = H.Series,
seriesType = H.seriesType;
/**
- * A scatter plot uses cartesian coordinates to display values for two variables
- * for a set of data.
+ * Scatter series type.
+ *
+ * @private
+ * @class
+ * @name Highcharts.seriesTypes.scatter
*
- * @sample {highcharts} highcharts/demo/scatter/ Scatter plot
- *
- * @extends {plotOptions.line}
- * @product highcharts highstock
- * @optionparent plotOptions.scatter
+ * @augments Highcharts.Series
*/
- seriesType('scatter', 'line', {
+ seriesType(
+ 'scatter',
+ 'line',
/**
- * The width of the line connecting the data points.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-none/
- * 0 by default
- * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-1/
- * 1px
- * @default 0
- * @product highcharts highstock
+ * A scatter plot uses cartesian coordinates to display values for two
+ * variables for a set of data.
+ *
+ * @sample {highcharts} highcharts/demo/scatter/
+ * Scatter plot
+ *
+ * @extends plotOptions.line
+ * @excluding pointPlacement, shadow, useOhlcData
+ * @product highcharts highstock
+ * @optionparent plotOptions.scatter
*/
- lineWidth: 0,
+ {
- findNearestPointBy: 'xy',
- marker: {
- enabled: true // Overrides auto-enabling in line series (#3647)
- },
+ /**
+ * The width of the line connecting the data points.
+ *
+ * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-none/
+ * 0 by default
+ * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-1/
+ * 1px
+ *
+ * @product highcharts highstock
+ */
+ lineWidth: 0,
- /**
- * Sticky tracking of mouse events. When true, the `mouseOut` event
- * on a series isn't triggered until the mouse moves over another series,
- * or out of the plot area. When false, the `mouseOut` event on a series
- * is triggered when the mouse leaves the area around the series' graph
- * or markers. This also implies the tooltip. When `stickyTracking`
- * is false and `tooltip.shared` is false, the tooltip will be hidden
- * when moving the mouse between series.
- *
- * @type {Boolean}
- * @default false
- * @product highcharts highstock
- * @apioption plotOptions.scatter.stickyTracking
- */
+ findNearestPointBy: 'xy',
- /**
- * A configuration object for the tooltip rendering of each single
- * series. Properties are inherited from #tooltip .
- * Overridable properties are `headerFormat`, `pointFormat`, `yDecimals`,
- * `xDateFormat`, `yPrefix` and `ySuffix`. Unlike other series, in
- * a scatter plot the series.name by default shows in the headerFormat
- * and point.x and point.y in the pointFormat.
- *
- * @product highcharts highstock
- */
- tooltip: {
+ /**
+ * Apply a jitter effect for the rendered markers. When plotting
+ * discrete values, a little random noise may help telling the points
+ * apart. The jitter setting applies a random displacement of up to `n`
+ * axis units in either direction. So for example on a horizontal X
+ * axis, setting the `jitter.x` to 0.24 will render the point in a
+ * random position between 0.24 units to the left and 0.24 units to the
+ * right of the true axis position. On a category axis, setting it to
+ * 0.5 will fill up the bin and make the data appear continuous.
+ *
+ * When rendered on top of a box plot or a column series, a jitter value
+ * of 0.24 will correspond to the underlying series' default
+ * [groupPadding](
+ * https://api.highcharts.com/highcharts/plotOptions.column.groupPadding)
+ * and [pointPadding](
+ * https://api.highcharts.com/highcharts/plotOptions.column.pointPadding)
+ * settings.
+ *
+ * @sample {highcharts} highcharts/series-scatter/jitter
+ * Jitter on a scatter plot
+ *
+ * @sample {highcharts} highcharts/series-scatter/jitter-boxplot
+ * Jittered scatter plot on top of a box plot
+ *
+ * @product highcharts highstock
+ * @since 7.0.2
+ */
+ jitter: {
+ /**
+ * The maximal X offset for the random jitter effect.
+ */
+ x: 0,
+ /**
+ * The maximal Y offset for the random jitter effect.
+ */
+ y: 0
+ },
- headerFormat: '\u25CF ' +
- ' {series.name} ',
+ marker: {
+ enabled: true // Overrides auto-enabling in line series (#3647)
+ },
- pointFormat: 'x: {point.x} y: {point.y} '
- }
+ /**
+ * Sticky tracking of mouse events. When true, the `mouseOut` event
+ * on a series isn't triggered until the mouse moves over another
+ * series, or out of the plot area. When false, the `mouseOut` event on
+ * a series is triggered when the mouse leaves the area around the
+ * series' graph or markers. This also implies the tooltip. When
+ * `stickyTracking` is false and `tooltip.shared` is false, the tooltip
+ * will be hidden when moving the mouse between series.
+ *
+ * @type {boolean}
+ * @default false
+ * @product highcharts highstock
+ * @apioption plotOptions.scatter.stickyTracking
+ */
+
+ /**
+ * A configuration object for the tooltip rendering of each single
+ * series. Properties are inherited from [tooltip](#tooltip).
+ * Overridable properties are `headerFormat`, `pointFormat`,
+ * `yDecimals`, `xDateFormat`, `yPrefix` and `ySuffix`. Unlike other
+ * series, in a scatter plot the series.name by default shows in the
+ * headerFormat and point.x and point.y in the pointFormat.
+ *
+ * @product highcharts highstock
+ */
+ tooltip: {
+ headerFormat:
+ '\u25CF ' +
+ ' {series.name} ',
+ pointFormat: 'x: {point.x} y: {point.y} '
+ }
+
+ // Prototype members
+ }, {
+ sorted: false,
+ requireSorting: false,
+ noSharedTooltip: true,
+ trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'],
+ takeOrdinalPosition: false, // #2342
- // Prototype members
- }, {
- sorted: false,
- requireSorting: false,
- noSharedTooltip: true,
- trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'],
- takeOrdinalPosition: false, // #2342
- drawGraph: function() {
- if (this.options.lineWidth) {
- Series.prototype.drawGraph.call(this);
+ /**
+ * @private
+ * @function Highcharts.seriesTypes.scatter#drawGraph
+ */
+ drawGraph: function () {
+ if (this.options.lineWidth) {
+ Series.prototype.drawGraph.call(this);
+ }
+ },
+
+ // Optionally add the jitter effect
+ applyJitter: function () {
+ var series = this,
+ jitter = this.options.jitter,
+ len = this.points.length;
+
+ // Return a repeatable, pseudo-random number based on an integer
+ // seed
+ function unrandom(seed) {
+ var rand = Math.sin(seed) * 10000;
+ return rand - Math.floor(rand);
+ }
+
+ if (jitter) {
+ this.points.forEach(function (point, i) {
+ ['x', 'y'].forEach(function (dim, j) {
+ var axis,
+ plotProp = 'plot' + dim.toUpperCase(),
+ min,
+ max,
+ translatedJitter;
+ if (jitter[dim] && !point.isNull) {
+ axis = series[dim + 'Axis'];
+ translatedJitter = jitter[dim] * axis.transA;
+ if (axis && !axis.isLog) {
+
+ // Identify the outer bounds of the jitter range
+ min = Math.max(
+ 0,
+ point[plotProp] - translatedJitter
+ );
+ max = Math.min(
+ axis.len,
+ point[plotProp] + translatedJitter
+ );
+
+ // Find a random position within this range
+ point[plotProp] = min +
+ (max - min) * unrandom(i + j * len);
+
+ // Update clientX for the tooltip k-d-tree
+ if (dim === 'x') {
+ point.clientX = point.plotX;
+ }
+ }
+ }
+ });
+ });
+ }
}
}
+ );
+
+ H.addEvent(Series, 'afterTranslate', function () {
+ if (this.applyJitter) {
+ this.applyJitter();
+ }
});
/**
* A `scatter` series. If the [type](#series.scatter.type) option is
* not specified, it is inherited from [chart.type](#chart.type).
- *
- * For options that apply to multiple series, it is recommended to add
- * them to the [plotOptions.series](#plotOptions.series) options structure.
- * To apply to all series of this specific type, apply it to [plotOptions.
- * scatter](#plotOptions.scatter).
- *
- * @type {Object}
- * @extends series,plotOptions.scatter
- * @excluding dataParser,dataURL,stack
- * @product highcharts highstock
+ *
+ * @extends series,plotOptions.scatter
+ * @excluding dataParser, dataURL, useOhlcData
+ * @product highcharts highstock
* @apioption series.scatter
*/
/**
* An array of data points for the series. For the `scatter` series
* type, points can be given in the following ways:
- *
- * 1. An array of numerical values. In this case, the numerical values
- * will be interpreted as `y` options. The `x` values will be automatically
- * calculated, either starting at 0 and incremented by 1, or from `pointStart`
- * and `pointInterval` given in the series options. If the axis has
- * categories, these will be used. Example:
- *
- * ```js
- * data: [0, 5, 3, 5]
- * ```
- *
- * 2. An array of arrays with 2 values. In this case, the values correspond
- * to `x,y`. If the first value is a string, it is applied as the name
- * of the point, and the `x` value is inferred.
- *
- * ```js
- * data: [
- * [0, 0],
- * [1, 8],
- * [2, 9]
- * ]
- * ```
- *
- * 3. An array of objects with named values. The objects are point
- * configuration objects as seen below. If the total number of data
- * points exceeds the series' [turboThreshold](#series.scatter.turboThreshold),
- * this option is not available.
- *
- * ```js
- * data: [{
- * x: 1,
- * y: 2,
- * name: "Point2",
- * color: "#00FF00"
- * }, {
- * x: 1,
- * y: 4,
- * name: "Point1",
- * color: "#FF00FF"
- * }]
- * ```
- *
- * @type {Array}
- * @extends series.line.data
+ *
+ * 1. An array of numerical values. In this case, the numerical values will be
+ * interpreted as `y` options. The `x` values will be automatically
+ * calculated, either starting at 0 and incremented by 1, or from
+ * `pointStart` and `pointInterval` given in the series options. If the axis
+ * has categories, these will be used. Example:
+ * ```js
+ * data: [0, 5, 3, 5]
+ * ```
+ *
+ * 2. An array of arrays with 2 values. In this case, the values correspond to
+ * `x,y`. If the first value is a string, it is applied as the name of the
+ * point, and the `x` value is inferred.
+ * ```js
+ * data: [
+ * [0, 0],
+ * [1, 8],
+ * [2, 9]
+ * ]
+ * ```
+ *
+ * 3. An array of objects with named values. The following snippet shows only a
+ * few settings, see the complete options set below. If the total number of
+ * data points exceeds the series'
+ * [turboThreshold](#series.scatter.turboThreshold), this option is not
+ * available.
+ * ```js
+ * data: [{
+ * x: 1,
+ * y: 2,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * y: 4,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
* @sample {highcharts} highcharts/chart/reflow-true/
* Numerical values
* @sample {highcharts} highcharts/series/data-array-of-arrays/
@@ -30575,28 +40112,53 @@
* Arrays of point.name and y
* @sample {highcharts} highcharts/series/data-array-of-objects/
* Config objects
- * @product highcharts highstock
+ *
+ * @type {Array|*>}
+ * @extends series.line.data
+ * @product highcharts highstock
* @apioption series.scatter.data
*/
-
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+ /**
+ * @private
+ * @typedef Highcharts.RadianAngles
+ *
+ * @property {number} start
+ *
+ * @property {number} end
+ */
+
+
+
var deg2rad = H.deg2rad,
isNumber = H.isNumber,
pick = H.pick,
relativeLength = H.relativeLength;
+
+ /**
+ * @private
+ * @mixin Highcharts.CenteredSeriesMixin
+ */
H.CenteredSeriesMixin = {
+
/**
- * Get the center of the pie based on the size and center options relative to the
- * plot area. Borrowed by the polar and gauge series types.
+ * Get the center of the pie based on the size and center options relative
+ * to the plot area. Borrowed by the polar and gauge series types.
+ *
+ * @private
+ * @function Highcharts.CenteredSeriesMixin.getCenter
+ *
+ * @return {Array}
*/
- getCenter: function() {
+ getCenter: function () {
var options = this.options,
chart = this.chart,
@@ -30605,7 +40167,12 @@
plotWidth = chart.plotWidth - 2 * slicingRoom,
plotHeight = chart.plotHeight - 2 * slicingRoom,
centerOption = options.center,
- positions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0],
+ positions = [
+ pick(centerOption[0], '50%'),
+ pick(centerOption[1], '50%'),
+ options.size || '100%',
+ options.innerSize || 0
+ ],
smallestSize = Math.min(plotWidth, plotHeight),
i,
value;
@@ -30618,8 +40185,10 @@
// i == 1: centerY, relative to height
// i == 2: size, relative to smallestSize
// i == 3: innerSize, relative to size
- positions[i] = relativeLength(value, [plotWidth, plotHeight, smallestSize, positions[2]][i]) +
- (handleSlicingRoom ? slicingRoom : 0);
+ positions[i] = relativeLength(
+ value,
+ [plotWidth, plotHeight, smallestSize, positions[2]][i]
+ ) + (handleSlicingRoom ? slicingRoom : 0);
}
// innerSize cannot be larger than size (#3632)
@@ -30628,14 +40197,22 @@
}
return positions;
},
+
/**
* getStartAndEndRadians - Calculates start and end angles in radians.
* Used in series types such as pie and sunburst.
*
- * @param {Number} start Start angle in degrees.
- * @param {Number} end Start angle in degrees.
- * @return {object} Returns an object containing start and end angles as
- * radians.
+ * @private
+ * @function Highcharts.CenteredSeriesMixin.getStartAndEndRadians
+ *
+ * @param {number} start
+ * Start angle in degrees.
+ *
+ * @param {number} end
+ * Start angle in degrees.
+ *
+ * @return {Highcharts.RadianAngles}
+ * Returns an object containing start and end angles as radians.
*/
getStartAndEndRadians: function getStartAndEndRadians(start, end) {
var startAngle = isNumber(start) ? start : 0, // must be a number
@@ -30646,10 +40223,11 @@
// difference must be less than 360 degrees
(end - startAngle) < 360
) ?
- end :
- startAngle + 360
+ end :
+ startAngle + 360
),
correction = -90;
+
return {
start: deg2rad * (startAngle + correction),
end: deg2rad * (endAngle + correction)
@@ -30658,19 +40236,20 @@
};
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
+
var addEvent = H.addEvent,
CenteredSeriesMixin = H.CenteredSeriesMixin,
defined = H.defined,
- each = H.each,
extend = H.extend,
getStartAndEndRadians = CenteredSeriesMixin.getStartAndEndRadians,
- inArray = H.inArray,
LegendSymbolMixin = H.LegendSymbolMixin,
noop = H.noop,
pick = H.pick,
@@ -30681,824 +40260,1234 @@
setAnimation = H.setAnimation;
/**
- * The pie series type.
+ * Pie series type.
*
- * @constructor seriesTypes.pie
- * @augments Series
- */
-
- /**
- * A pie chart is a circular graphic which is divided into slices to illustrate
- * numerical proportion.
+ * @private
+ * @class
+ * @name Highcharts.seriesTypes.pie
*
- * @sample highcharts/demo/pie-basic/ Pie chart
- *
- * @extends {plotOptions.line}
- * @excluding animationLimit,boostThreshold,connectEnds,connectNulls,
- * cropThreshold,dashStyle,findNearestPointBy,getExtremesFromAll,
- * lineWidth,marker,negativeColor,pointInterval,pointIntervalUnit,
- * pointPlacement,pointStart,softThreshold,stacking,step,threshold,
- * turboThreshold,zoneAxis,zones
- * @product highcharts
- * @optionparent plotOptions.pie
+ * @augments Highcharts.Series
*/
- seriesType('pie', 'line', {
+ seriesType('pie', 'line',
+
+ /**
+ * A pie chart is a circular graphic which is divided into slices to
+ * illustrate numerical proportion.
+ *
+ * @sample highcharts/demo/pie-basic/
+ * Pie chart
+ *
+ * @extends plotOptions.line
+ * @excluding animationLimit, boostThreshold, connectEnds, connectNulls,
+ * cropThreshold, dashStyle, findNearestPointBy,
+ * getExtremesFromAll, lineWidth, marker, negativeColor,
+ * pointInterval, pointIntervalUnit, pointPlacement,
+ * pointStart, softThreshold, stacking, step, threshold,
+ * turboThreshold, zoneAxis, zones
+ * @product highcharts
+ * @optionparent plotOptions.pie
+ */
+ {
+
+ /**
+ * @excluding legendItemClick
+ * @apioption plotOptions.pie.events
+ */
+
+ /**
+ * Fires when the checkbox next to the point name in the legend is
+ * clicked. One parameter, event, is passed to the function. The state
+ * of the checkbox is found by event.checked. The checked item is found
+ * by event.item. Return false to prevent the default action which is to
+ * toggle the select state of the series.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-events-checkboxclick/
+ * Alert checkbox status
+ *
+ * @type {Function}
+ * @since 1.2.0
+ * @product highcharts
+ * @context Highcharts.Point
+ * @apioption plotOptions.pie.events.checkboxClick
+ */
+
+ /**
+ * The center of the pie chart relative to the plot area. Can be
+ * percentages or pixel values. The default behaviour (as of 3.0) is to
+ * center the pie so that all slices and data labels are within the plot
+ * area. As a consequence, the pie may actually jump around in a chart
+ * with dynamic values, as the data labels move. In that case, the
+ * center should be explicitly set, for example to `["50%", "50%"]`.
+ *
+ * @sample {highcharts} highcharts/plotoptions/pie-center/
+ * Centered at 100, 100
+ *
+ * @type {Array}
+ * @default [null, null]
+ * @product highcharts
+ */
+ center: [null, null],
+
+ /**
+ * @product highcharts
+ */
+ clip: false,
+
+ /** @ignore */
+ colorByPoint: true, // always true for pies
+
+ /**
+ * A series specific or series type specific color set to use instead
+ * of the global [colors](#colors).
+ *
+ * @sample {highcharts} highcharts/demo/pie-monochrome/
+ * Set default colors for all pies
+ *
+ * @type {Array}
+ * @since 3.0
+ * @product highcharts
+ * @apioption plotOptions.pie.colors
+ */
+
+ /**
+ * @extends plotOptions.series.dataLabels
+ * @excluding align, allowOverlap, inside, staggerLines, step,
+ * verticalAlign
+ * @product highcharts
+ */
+ dataLabels: {
+
+ allowOverlap: true,
+
+ /**
+ * The color of the line connecting the data label to the pie slice.
+ * The default color is the same as the point's color.
+ *
+ * In styled mode, the connector stroke is given in the
+ * `.highcharts-data-label-connector` class.
+ *
+ * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorcolor/
+ * Blue connectors
+ * @sample {highcharts} highcharts/css/pie-point/
+ * Styled connectors
+ *
+ * @type {Highcharts.ColorString}
+ * @since 2.1
+ * @product highcharts
+ * @apioption plotOptions.pie.dataLabels.connectorColor
+ */
+
+ /**
+ * The distance from the data label to the connector. Note that data
+ * labels also have a default `padding`, so in order for the
+ * connector to touch the text, the `padding` must also be 0.
+ *
+ * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorpadding/
+ * No padding
+ *
+ * @since 2.1
+ * @product highcharts
+ * @apioption plotOptions.pie.dataLabels.connectorPadding
+ */
+ connectorPadding: 5,
+
+ /**
+ * The width of the line connecting the data label to the pie slice.
+ *
+ *
+ * In styled mode, the connector stroke width is given in the
+ * `.highcharts-data-label-connector` class.
+ *
+ * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorwidth-disabled/
+ * Disable the connector
+ * @sample {highcharts} highcharts/css/pie-point/
+ * Styled connectors
+ *
+ * @type {number}
+ * @default 1
+ * @since 2.1
+ * @product highcharts
+ * @apioption plotOptions.pie.dataLabels.connectorWidth
+ */
+
+ /**
+ * @sample {highcharts} highcharts/plotOptions/pie-datalabels-overflow
+ * Long labels truncated with an ellipsis
+ * @sample {highcharts} highcharts/plotOptions/pie-datalabels-overflow-wrap
+ * Long labels are wrapped
+ *
+ * @type {Highcharts.CSSObject}
+ * @apioption plotOptions.pie.dataLabels.style
+ */
+
+ /**
+ * The distance of the data label from the pie's edge. Negative
+ * numbers put the data label on top of the pie slices. Connectors
+ * are only shown for data labels outside the pie.
+ *
+ * @sample {highcharts} highcharts/plotoptions/pie-datalabels-distance/
+ * Data labels on top of the pie
+ *
+ * @since 2.1
+ * @product highcharts
+ */
+ distance: 30,
+
+ /**
+ * Enable or disable the data labels.
+ *
+ * @since 2.1
+ * @product highcharts
+ */
+ enabled: true,
+
+ /**
+ * @type {Highcharts.FormatterCallbackFunction}
+ * @default function () { return this.point.name; }
+ * @apioption plotOptions.pie.dataLabels.formatter
+ */
+ formatter: function () { // #2945
+ return this.point.isNull ? undefined : this.point.name;
+ },
+
+ /**
+ * Whether to render the connector as a soft arc or a line with
+ * sharp break. Works only if `connectorShape` equals to
+ * `fixedOffset`.
+ *
+ * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-true/
+ * Soft
+ * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-false/
+ * Non soft
+ *
+ * @type {number}
+ * @since 2.1.7
+ * @product highcharts
+ * @apioption plotOptions.pie.dataLabels.softConnector
+ */
+ softConnector: true,
+
+ /**
+ * Alignment method for data labels. Possible values are:
+ * `'toPlotEdges'` (each label touches the nearest vertical edge of
+ * the plot area) or `'connectors'` (connectors have the same x
+ * position and the widest label of each half (left & right) touches
+ * the nearest vertical edge of the plot area).
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/plotoptions/pie-datalabels-alignto-connectors/ alignTo: connectors
+ * @sample {highcharts} highcharts/plotoptions/pie-datalabels-alignto-plotedges/ alignTo: plotEdges
+ * @since 7.0.0
+ * @default undefined
+ * @product highcharts
+ * @apioption plotOptions.pie.dataLabels.alignTo
+ */
+
+ x: 0,
+
+ /**
+ * Specifies the method that is used to generate the connector path.
+ * Highcharts provides 3 built-in connector shapes: `'fixedOffset'`
+ * (default), `'straight'` and `'crookedLine'`. Using
+ * `'crookedLine'` has the most sense (in most of the cases) when
+ * `'alignTo'` is set.
+ *
+ * Users can provide their own method by passing a function instead
+ * of a String. 3 arguments are passed to the callback:
+ *
+ * - Object that holds the information about the coordinates of the
+ * label (`x` & `y` properties) and how the label is located in
+ * relation to the pie (`alignment` property). `alignment` can by
+ * one of the following:
+ * `'left'` (pie on the left side of the data label),
+ * `'right'` (pie on the right side of the data label) or
+ * `'center'` (data label overlaps the pie).
+ * - Object that holds the information about the position of the
+ * connector. Its `touchingSliceAt` porperty tells the position
+ * of the place where the connector touches the slice.
+ * - Data label options
+ *
+ * The function has to return an SVG path definition in array form
+ * (see the example).
+ *
+ * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorshape-string/
+ * connectorShape is a String
+ * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorshape-function/
+ * connectorShape is a function
+ *
+ * @type {string|Function}
+ * @since 7.0.0
+ * @product highcharts
+ * @apioption plotOptions.pie.dataLabels.connectorShape
+ */
+ connectorShape: 'fixedOffset',
+
+ /**
+ * Works only if `connectorShape` is `'crookedLine'`. It defines how
+ * far from the vertical plot edge the coonnector path should be
+ * crooked.
+ *
+ * @sample {highcharts} highcharts/plotoptions/pie-datalabels-crookdistance/
+ * crookDistance set to 90%
+ *
+ * @type {string}
+ * @since 7.0.0
+ * @product highcharts
+ * @apioption plotOptions.pie.dataLabels.crookDistance
+ */
+ crookDistance: '70%'
+ },
+
+ /**
+ * The end angle of the pie in degrees where 0 is top and 90 is right.
+ * Defaults to `startAngle` plus 360.
+ *
+ * @sample {highcharts} highcharts/demo/pie-semi-circle/
+ * Semi-circle donut
+ *
+ * @type {number}
+ * @since 1.3.6
+ * @product highcharts
+ * @apioption plotOptions.pie.endAngle
+ */
+
+ /**
+ * Equivalent to [chart.ignoreHiddenSeries](#chart.ignoreHiddenSeries),
+ * this option tells whether the series shall be redrawn as if the
+ * hidden point were `null`.
+ *
+ * The default value changed from `false` to `true` with Highcharts
+ * 3.0.
+ *
+ * @sample {highcharts} highcharts/plotoptions/pie-ignorehiddenpoint/
+ * True, the hiddden point is ignored
+ *
+ * @since 2.3.0
+ * @product highcharts
+ */
+ ignoreHiddenPoint: true,
- /**
- * The center of the pie chart relative to the plot area. Can be percentages
- * or pixel values. The default behaviour (as of 3.0) is to center
- * the pie so that all slices and data labels are within the plot area.
- * As a consequence, the pie may actually jump around in a chart with
- * dynamic values, as the data labels move. In that case, the center
- * should be explicitly set, for example to `["50%", "50%"]`.
- *
- * @type {Array}
- * @sample {highcharts} highcharts/plotoptions/pie-center/ Centered at 100, 100
- * @default [null, null]
- * @product highcharts
- */
- center: [null, null],
+ /**
+ * The size of the inner diameter for the pie. A size greater than 0
+ * renders a donut chart. Can be a percentage or pixel value.
+ * Percentages are relative to the pie size. Pixel values are given as
+ * integers.
+ *
+ *
+ * Note: in Highcharts < 4.1.2, the percentage was relative to the plot
+ * area, not the pie size.
+ *
+ * @sample {highcharts} highcharts/plotoptions/pie-innersize-80px/
+ * 80px inner size
+ * @sample {highcharts} highcharts/plotoptions/pie-innersize-50percent/
+ * 50% of the plot area
+ * @sample {highcharts} highcharts/demo/3d-pie-donut/
+ * 3D donut
+ *
+ * @type {number|string}
+ * @default 0
+ * @since 2.0
+ * @product highcharts
+ * @apioption plotOptions.pie.innerSize
+ */
- clip: false,
+ /** @ignore-option */
+ legendType: 'point',
- /** @ignore */
- colorByPoint: true, // always true for pies
+ /** @ignore-option */
+ marker: null, // point options are specified in the base options
- /**
- * A series specific or series type specific color set to use instead
- * of the global [colors](#colors).
- *
- * @type {Array}
- * @sample {highcharts} highcharts/demo/pie-monochrome/ Set default colors for all pies
- * @since 3.0
- * @product highcharts
- * @apioption plotOptions.pie.colors
- */
+ /**
+ * The minimum size for a pie in response to auto margins. The pie will
+ * try to shrink to make room for data labels in side the plot area,
+ * but only to this size.
+ *
+ * @type {number}
+ * @default 80
+ * @since 3.0
+ * @product highcharts
+ * @apioption plotOptions.pie.minSize
+ */
- /**
- * @extends plotOptions.series.dataLabels
- * @excluding align,allowOverlap,staggerLines,step
- * @product highcharts
- */
- dataLabels: {
/**
- * The color of the line connecting the data label to the pie slice.
- * The default color is the same as the point's color.
- *
- * In styled mode, the connector stroke is given in the
- * `.highcharts-data-label-connector` class.
- *
- * @type {String}
- * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorcolor/ Blue connectors
- * @sample {highcharts} highcharts/css/pie-point/ Styled connectors
- * @default {point.color}
- * @since 2.1
+ * The diameter of the pie relative to the plot area. Can be a
+ * percentage or pixel value. Pixel values are given as integers. The
+ * default behaviour (as of 3.0) is to scale to the plot area and give
+ * room for data labels within the plot area.
+ * [slicedOffset](#plotOptions.pie.slicedOffset) is also included in the
+ * default size calculation. As a consequence, the size of the pie may
+ * vary when points are updated and data labels more around. In that
+ * case it is best to set a fixed value, for example `"75%"`.
+ *
+ * @sample {highcharts} highcharts/plotoptions/pie-size/
+ * Smaller pie
+ *
+ * @type {number|string|null}
* @product highcharts
- * @apioption plotOptions.pie.dataLabels.connectorColor
*/
+ size: null,
/**
- * The distance from the data label to the connector.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorpadding/ No padding
- * @default 5
- * @since 2.1
+ * Whether to display this particular series or series type in the
+ * legend. Since 2.1, pies are not shown in the legend by default.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-showinlegend/
+ * One series in the legend, one hidden
+ *
* @product highcharts
- * @apioption plotOptions.pie.dataLabels.connectorPadding
*/
+ showInLegend: false,
/**
- * The width of the line connecting the data label to the pie slice.
- *
- *
- * In styled mode, the connector stroke width is given in the
- * `.highcharts-data-label-connector` class.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorwidth-disabled/ Disable the connector
- * @sample {highcharts} highcharts/css/pie-point/ Styled connectors
- * @default 1
- * @since 2.1
+ * If a point is sliced, moved out from the center, how many pixels
+ * should it be moved?.
+ *
+ * @sample {highcharts} highcharts/plotoptions/pie-slicedoffset-20/
+ * 20px offset
+ *
* @product highcharts
- * @apioption plotOptions.pie.dataLabels.connectorWidth
*/
+ slicedOffset: 10,
/**
- * The distance of the data label from the pie's edge. Negative numbers
- * put the data label on top of the pie slices. Connectors are only
- * shown for data labels outside the pie.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/pie-datalabels-distance/ Data labels on top of the pie
- * @default 30
- * @since 2.1
- * @product highcharts
+ * The start angle of the pie slices in degrees where 0 is top and 90
+ * right.
+ *
+ * @sample {highcharts} highcharts/plotoptions/pie-startangle-90/
+ * Start from right
+ *
+ * @type {number}
+ * @default 0
+ * @since 2.3.4
+ * @product highcharts
+ * @apioption plotOptions.pie.startAngle
*/
- distance: 30,
/**
- * Enable or disable the data labels.
- *
- * @type {Boolean}
- * @since 2.1
+ * Sticky tracking of mouse events. When true, the `mouseOut` event
+ * on a series isn't triggered until the mouse moves over another
+ * series, or out of the plot area. When false, the `mouseOut` event on
+ * a series is triggered when the mouse leaves the area around the
+ * series' graph or markers. This also implies the tooltip. When
+ * `stickyTracking` is false and `tooltip.shared` is false, the tooltip
+ * will be hidden when moving the mouse between series.
+ *
* @product highcharts
*/
- enabled: true,
+ stickyTracking: false,
- formatter: function() { // #2945
- return this.point.isNull ? undefined : this.point.name;
+ tooltip: {
+ followPointer: true
},
/**
- * Whether to render the connector as a soft arc or a line with sharp
- * break.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-true/ Soft
- * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-false/ Non soft
- * @since 2.1.7
+ * The color of the border surrounding each slice. When `null`, the
+ * border takes the same color as the slice fill. This can be used
+ * together with a `borderWidth` to fill drawing gaps created by
+ * antialiazing artefacts in borderless pies.
+ *
+ * In styled mode, the border stroke is given in the `.highcharts-point`
+ * class.
+ *
+ * @sample {highcharts} highcharts/plotoptions/pie-bordercolor-black/
+ * Black border
+ *
+ * @type {Highcharts.ColorString}
+ * @default #ffffff
* @product highcharts
- * @apioption plotOptions.pie.dataLabels.softConnector
*/
-
- x: 0
- // y: 0
- },
-
- /**
- * The end angle of the pie in degrees where 0 is top and 90 is right.
- * Defaults to `startAngle` plus 360.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/demo/pie-semi-circle/ Semi-circle donut
- * @default null
- * @since 1.3.6
- * @product highcharts
- * @apioption plotOptions.pie.endAngle
- */
-
- /**
- * Equivalent to [chart.ignoreHiddenSeries](#chart.ignoreHiddenSeries),
- * this option tells whether the series shall be redrawn as if the
- * hidden point were `null`.
- *
- * The default value changed from `false` to `true` with Highcharts
- * 3.0.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/pie-ignorehiddenpoint/ True, the hiddden point is ignored
- * @default true
- * @since 2.3.0
- * @product highcharts
- */
- ignoreHiddenPoint: true,
-
- /**
- * The size of the inner diameter for the pie. A size greater than 0
- * renders a donut chart. Can be a percentage or pixel value. Percentages
- * are relative to the pie size. Pixel values are given as integers.
- *
- *
- * Note: in Highcharts < 4.1.2, the percentage was relative to the plot
- * area, not the pie size.
- *
- * @type {String|Number}
- * @sample {highcharts} highcharts/plotoptions/pie-innersize-80px/ 80px inner size
- * @sample {highcharts} highcharts/plotoptions/pie-innersize-50percent/ 50% of the plot area
- * @sample {highcharts} highcharts/demo/3d-pie-donut/ 3D donut
- * @default 0
- * @since 2.0
- * @product highcharts
- * @apioption plotOptions.pie.innerSize
- */
-
- /** @ignore */
- legendType: 'point',
-
- /** @ignore */
- marker: null, // point options are specified in the base options
-
- /**
- * The minimum size for a pie in response to auto margins. The pie will
- * try to shrink to make room for data labels in side the plot area,
- * but only to this size.
- *
- * @type {Number}
- * @default 80
- * @since 3.0
- * @product highcharts
- * @apioption plotOptions.pie.minSize
- */
-
- /**
- * The diameter of the pie relative to the plot area. Can be a percentage
- * or pixel value. Pixel values are given as integers. The default
- * behaviour (as of 3.0) is to scale to the plot area and give room
- * for data labels within the plot area. As a consequence, the size
- * of the pie may vary when points are updated and data labels more
- * around. In that case it is best to set a fixed value, for example
- * `"75%"`.
- *
- * @type {String|Number}
- * @sample {highcharts} highcharts/plotoptions/pie-size/ Smaller pie
- * @default
- * @product highcharts
- */
- size: null,
-
- /**
- * Whether to display this particular series or series type in the
- * legend. Since 2.1, pies are not shown in the legend by default.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/series-showinlegend/ One series in the legend, one hidden
- * @product highcharts
- */
- showInLegend: false,
-
- /**
- * If a point is sliced, moved out from the center, how many pixels
- * should it be moved?.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/pie-slicedoffset-20/ 20px offset
- * @default 10
- * @product highcharts
- */
- slicedOffset: 10,
-
- /**
- * The start angle of the pie slices in degrees where 0 is top and 90
- * right.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/pie-startangle-90/ Start from right
- * @default 0
- * @since 2.3.4
- * @product highcharts
- * @apioption plotOptions.pie.startAngle
- */
-
- /**
- * Sticky tracking of mouse events. When true, the `mouseOut` event
- * on a series isn't triggered until the mouse moves over another series,
- * or out of the plot area. When false, the `mouseOut` event on a
- * series is triggered when the mouse leaves the area around the series'
- * graph or markers. This also implies the tooltip. When `stickyTracking`
- * is false and `tooltip.shared` is false, the tooltip will be hidden
- * when moving the mouse between series.
- *
- * @product highcharts
- */
- stickyTracking: false,
-
- tooltip: {
- followPointer: true
- },
-
-
- /**
- * The color of the border surrounding each slice. When `null`, the
- * border takes the same color as the slice fill. This can be used
- * together with a `borderWidth` to fill drawing gaps created by antialiazing
- * artefacts in borderless pies.
- *
- * In styled mode, the border stroke is given in the `.highcharts-point` class.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/plotoptions/pie-bordercolor-black/ Black border
- * @default #ffffff
- * @product highcharts
- */
- borderColor: '#ffffff',
-
- /**
- * The width of the border surrounding each slice.
- *
- * When setting the border width to 0, there may be small gaps between
- * the slices due to SVG antialiasing artefacts. To work around this,
- * keep the border width at 0.5 or 1, but set the `borderColor` to
- * `null` instead.
- *
- * In styled mode, the border stroke width is given in the `.highcharts-point` class.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/pie-borderwidth/ 3px border
- * @default 1
- * @product highcharts
- */
- borderWidth: 1,
-
- states: {
+ borderColor: '#ffffff',
/**
- * @extends plotOptions.series.states.hover
+ * The width of the border surrounding each slice.
+ *
+ * When setting the border width to 0, there may be small gaps between
+ * the slices due to SVG antialiasing artefacts. To work around this,
+ * keep the border width at 0.5 or 1, but set the `borderColor` to
+ * `null` instead.
+ *
+ * In styled mode, the border stroke width is given in the
+ * `.highcharts-point` class.
+ *
+ * @sample {highcharts} highcharts/plotoptions/pie-borderwidth/
+ * 3px border
+ *
* @product highcharts
*/
- hover: {
+ borderWidth: 1,
+
+ states: {
/**
+ * @extends plotOptions.series.states.hover
+ * @excluding marker, lineWidth, lineWidthPlus
+ * @product highcharts
+ */
+ hover: {
+
+ /**
* How much to brighten the point on interaction. Requires the main
* color to be defined in hex or rgb(a) format.
- *
+ *
* In styled mode, the hover brightness is by default replaced
* by a fill-opacity given in the `.highcharts-point-hover` class.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/pie-states-hover-brightness/ Brightened by 0.5
- * @default 0.1
+ *
+ * @sample {highcharts} highcharts/plotoptions/pie-states-hover-brightness/
+ * Brightened by 0.5
+ *
* @product highcharts
*/
- brightness: 0.1,
-
- shadow: false
- }
- }
-
-
- }, /** @lends seriesTypes.pie.prototype */ {
- isCartesian: false,
- requireSorting: false,
- directTouch: true,
- noSharedTooltip: true,
- trackerGroups: ['group', 'dataLabelsGroup'],
- axisTypes: [],
- pointAttribs: seriesTypes.column.prototype.pointAttribs,
- /**
- * Animate the pies in
- */
- animate: function(init) {
- var series = this,
- points = series.points,
- startAngleRad = series.startAngleRad;
-
- if (!init) {
- each(points, function(point) {
- var graphic = point.graphic,
- args = point.shapeArgs;
-
- if (graphic) {
- // start values
- graphic.attr({
- r: point.startR || (series.center[3] / 2), // animate from inner radius (#779)
- start: startAngleRad,
- end: startAngleRad
- });
-
- // animate
- graphic.animate({
- r: args.r,
- start: args.start,
- end: args.end
- }, series.options.animation);
- }
- });
-
- // delete this function to allow it only once
- series.animate = null;
- }
- },
-
- /**
- * Recompute total chart sum and update percentages of points.
- */
- updateTotals: function() {
- var i,
- total = 0,
- points = this.points,
- len = points.length,
- point,
- ignoreHiddenPoint = this.options.ignoreHiddenPoint;
-
- // Get the total sum
- for (i = 0; i < len; i++) {
- point = points[i];
- total += (ignoreHiddenPoint && !point.visible) ?
- 0 :
- point.isNull ? 0 : point.y;
+ brightness: 0.1
+ }
}
- this.total = total;
- // Set each point's properties
- for (i = 0; i < len; i++) {
- point = points[i];
- point.percentage = (total > 0 && (point.visible || !ignoreHiddenPoint)) ? point.y / total * 100 : 0;
- point.total = total;
- }
- },
+ }, /** @lends seriesTypes.pie.prototype */ {
- /**
- * Extend the generatePoints method by adding total and percentage properties to each point
- */
- generatePoints: function() {
- Series.prototype.generatePoints.call(this);
- this.updateTotals();
- },
+ isCartesian: false,
+ requireSorting: false,
+ directTouch: true,
+ noSharedTooltip: true,
+ trackerGroups: ['group', 'dataLabelsGroup'],
+ axisTypes: [],
+ pointAttribs: seriesTypes.column.prototype.pointAttribs,
- /**
- * Do translation for pie slices
- */
- translate: function(positions) {
- this.generatePoints();
+ /**
+ * Animate the pies in
+ *
+ * @private
+ * @function Highcharts.seriesTypes.pie#animate
+ *
+ * @param {boolean} [init=false]
+ */
+ animate: function (init) {
+ var series = this,
+ points = series.points,
+ startAngleRad = series.startAngleRad;
- var series = this,
- cumulative = 0,
- precision = 1000, // issue #172
- options = series.options,
- slicedOffset = options.slicedOffset,
- connectorOffset = slicedOffset + (options.borderWidth || 0),
- finalConnectorOffset,
- start,
- end,
- angle,
- radians = getStartAndEndRadians(options.startAngle, options.endAngle),
- startAngleRad = series.startAngleRad = radians.start,
- endAngleRad = series.endAngleRad = radians.end,
- circ = endAngleRad - startAngleRad, // 2 * Math.PI,
- points = series.points,
- radiusX, // the x component of the radius vector for a given point
- radiusY,
- labelDistance = options.dataLabels.distance,
- ignoreHiddenPoint = options.ignoreHiddenPoint,
- i,
- len = points.length,
- point;
+ if (!init) {
+ points.forEach(function (point) {
+ var graphic = point.graphic,
+ args = point.shapeArgs;
- // Get positions - either an integer or a percentage string must be given.
- // If positions are passed as a parameter, we're in a recursive loop for adjusting
- // space for data labels.
- if (!positions) {
- series.center = positions = series.getCenter();
- }
-
- // Utility for getting the x value from a given y, used for anticollision
- // logic in data labels.
- // Added point for using specific points' label distance.
- series.getX = function(y, left, point) {
- angle = Math.asin(Math.min((y - positions[1]) / (positions[2] / 2 + point.labelDistance), 1));
- return positions[0] +
- (left ? -1 : 1) *
- (Math.cos(angle) * (positions[2] / 2 + point.labelDistance));
- };
+ if (graphic) {
+ // start values
+ graphic.attr({
+ // animate from inner radius (#779)
+ r: point.startR || (series.center[3] / 2),
+ start: startAngleRad,
+ end: startAngleRad
+ });
- // Calculate the geometry for each point
- for (i = 0; i < len; i++) {
+ // animate
+ graphic.animate({
+ r: args.r,
+ start: args.start,
+ end: args.end
+ }, series.options.animation);
+ }
+ });
- point = points[i];
+ // delete this function to allow it only once
+ series.animate = null;
+ }
+ },
- // Used for distance calculation for specific point.
- point.labelDistance = pick(
- point.options.dataLabels && point.options.dataLabels.distance,
- labelDistance
- );
+ /**
+ * Recompute total chart sum and update percentages of points.
+ *
+ * @private
+ * @function Highcharts.seriesTypes.pie#updateTotals
+ */
+ updateTotals: function () {
+ var i,
+ total = 0,
+ points = this.points,
+ len = points.length,
+ point,
+ ignoreHiddenPoint = this.options.ignoreHiddenPoint;
- // Saved for later dataLabels distance calculation.
- series.maxLabelDistance = Math.max(series.maxLabelDistance || 0, point.labelDistance);
-
- // set start and end angle
- start = startAngleRad + (cumulative * circ);
- if (!ignoreHiddenPoint || point.visible) {
- cumulative += point.percentage / 100;
- }
- end = startAngleRad + (cumulative * circ);
-
- // set the shape
- point.shapeType = 'arc';
- point.shapeArgs = {
- x: positions[0],
- y: positions[1],
- r: positions[2] / 2,
- innerR: positions[3] / 2,
- start: Math.round(start * precision) / precision,
- end: Math.round(end * precision) / precision
- };
+ // Get the total sum
+ for (i = 0; i < len; i++) {
+ point = points[i];
+ total += (ignoreHiddenPoint && !point.visible) ?
+ 0 :
+ point.isNull ? 0 : point.y;
+ }
+ this.total = total;
- // The angle must stay within -90 and 270 (#2645)
- angle = (end + start) / 2;
- if (angle > 1.5 * Math.PI) {
- angle -= 2 * Math.PI;
- } else if (angle < -Math.PI / 2) {
- angle += 2 * Math.PI;
+ // Set each point's properties
+ for (i = 0; i < len; i++) {
+ point = points[i];
+ point.percentage =
+ (total > 0 && (point.visible || !ignoreHiddenPoint)) ?
+ point.y / total * 100 :
+ 0;
+ point.total = total;
}
+ },
- // Center for the sliced out slice
- point.slicedTranslation = {
- translateX: Math.round(Math.cos(angle) * slicedOffset),
- translateY: Math.round(Math.sin(angle) * slicedOffset)
- };
+ /**
+ * Extend the generatePoints method by adding total and percentage
+ * properties to each point
+ *
+ * @private
+ * @function Highcharts.seriesTypes.pie#generatePoints
+ */
+ generatePoints: function () {
+ Series.prototype.generatePoints.call(this);
+ this.updateTotals();
+ },
- // set the anchor point for tooltips
- radiusX = Math.cos(angle) * positions[2] / 2;
- radiusY = Math.sin(angle) * positions[2] / 2;
- point.tooltipPos = [
- positions[0] + radiusX * 0.7,
- positions[1] + radiusY * 0.7
- ];
+ // Utility for getting the x value from a given y, used for
+ // anticollision logic in data labels. Added point for using specific
+ // points' label distance.
+ getX: function (y, left, point) {
+ var center = this.center,
+ // Variable pie has individual radius
+ radius = this.radii ? this.radii[point.index] : center[2] / 2,
+ angle,
+ x;
+
+ angle = Math.asin(
+ Math.max(
+ Math.min(
+ (
+ (y - center[1]) /
+ (radius + point.labelDistance)
+ ),
+ 1
+ ),
+ -1
+ )
+ );
+ x = center[0] +
+ (left ? -1 : 1) *
+ (Math.cos(angle) * (radius + point.labelDistance)) +
+ (
+ point.labelDistance > 0 ?
+ (left ? -1 : 1) * this.options.dataLabels.padding :
+ 0
+ );
- point.half = angle < -Math.PI / 2 || angle > Math.PI / 2 ? 1 : 0;
- point.angle = angle;
-
- // Set the anchor point for data labels. Use point.labelDistance
- // instead of labelDistance // #1174
- // finalConnectorOffset - not override connectorOffset value.
- finalConnectorOffset = Math.min(connectorOffset, point.labelDistance / 5); // #1678
- point.labelPos = [
- positions[0] + radiusX + Math.cos(angle) * point.labelDistance, // first break of connector
- positions[1] + radiusY + Math.sin(angle) * point.labelDistance, // a/a
- positions[0] + radiusX + Math.cos(angle) * finalConnectorOffset, // second break, right outside pie
- positions[1] + radiusY + Math.sin(angle) * finalConnectorOffset, // a/a
- positions[0] + radiusX, // landing point for connector
- positions[1] + radiusY, // a/a
- point.labelDistance < 0 ? // alignment
- 'center' :
- point.half ? 'right' : 'left', // alignment
- angle // center angle
- ];
+ return x;
+ },
- }
- },
+ /**
+ * Do translation for pie slices
+ *
+ * @private
+ * @function Highcharts.seriesTypes.pie#translate
+ *
+ * @param {Array} positions
+ */
+ translate: function (positions) {
+ this.generatePoints();
- drawGraph: null,
+ var series = this,
+ cumulative = 0,
+ precision = 1000, // issue #172
+ options = series.options,
+ slicedOffset = options.slicedOffset,
+ connectorOffset = slicedOffset + (options.borderWidth || 0),
+ finalConnectorOffset,
+ start,
+ end,
+ angle,
+ radians = getStartAndEndRadians(
+ options.startAngle,
+ options.endAngle
+ ),
+ startAngleRad = series.startAngleRad = radians.start,
+ endAngleRad = series.endAngleRad = radians.end,
+ circ = endAngleRad - startAngleRad, // 2 * Math.PI,
+ points = series.points,
+ // the x component of the radius vector for a given point
+ radiusX,
+ radiusY,
+ labelDistance = options.dataLabels.distance,
+ ignoreHiddenPoint = options.ignoreHiddenPoint,
+ i,
+ len = points.length,
+ point;
+
+ // Get positions - either an integer or a percentage string must be
+ // given. If positions are passed as a parameter, we're in a
+ // recursive loop for adjusting space for data labels.
+ if (!positions) {
+ series.center = positions = series.getCenter();
+ }
+
+ // Calculate the geometry for each point
+ for (i = 0; i < len; i++) {
- /**
- * Draw the data points
- */
- drawPoints: function() {
- var series = this,
- chart = series.chart,
- renderer = chart.renderer,
- groupTranslation,
- graphic,
- pointAttr,
- shapeArgs;
+ point = points[i];
+ // Used for distance calculation for specific point.
+ point.labelDistance = pick(
+ (
+ point.options.dataLabels &&
+ point.options.dataLabels.distance
+ ),
+ labelDistance
+ );
- var shadow = series.options.shadow;
- if (shadow && !series.shadowGroup) {
- series.shadowGroup = renderer.g('shadow')
- .add(series.group);
- }
+ // Saved for later dataLabels distance calculation.
+ series.maxLabelDistance = Math.max(
+ series.maxLabelDistance || 0,
+ point.labelDistance
+ );
+ // set start and end angle
+ start = startAngleRad + (cumulative * circ);
+ if (!ignoreHiddenPoint || point.visible) {
+ cumulative += point.percentage / 100;
+ }
+ end = startAngleRad + (cumulative * circ);
+
+ // set the shape
+ point.shapeType = 'arc';
+ point.shapeArgs = {
+ x: positions[0],
+ y: positions[1],
+ r: positions[2] / 2,
+ innerR: positions[3] / 2,
+ start: Math.round(start * precision) / precision,
+ end: Math.round(end * precision) / precision
+ };
- // draw the slices
- each(series.points, function(point) {
- graphic = point.graphic;
- if (!point.isNull) {
- shapeArgs = point.shapeArgs;
+ // The angle must stay within -90 and 270 (#2645)
+ angle = (end + start) / 2;
+ if (angle > 1.5 * Math.PI) {
+ angle -= 2 * Math.PI;
+ } else if (angle < -Math.PI / 2) {
+ angle += 2 * Math.PI;
+ }
+ // Center for the sliced out slice
+ point.slicedTranslation = {
+ translateX: Math.round(Math.cos(angle) * slicedOffset),
+ translateY: Math.round(Math.sin(angle) * slicedOffset)
+ };
- // If the point is sliced, use special translation, else use
- // plot area traslation
- groupTranslation = point.getTranslate();
+ // set the anchor point for tooltips
+ radiusX = Math.cos(angle) * positions[2] / 2;
+ radiusY = Math.sin(angle) * positions[2] / 2;
+ point.tooltipPos = [
+ positions[0] + radiusX * 0.7,
+ positions[1] + radiusY * 0.7
+ ];
+ point.half = angle < -Math.PI / 2 || angle > Math.PI / 2 ?
+ 1 :
+ 0;
+ point.angle = angle;
+
+ // Set the anchor point for data labels. Use point.labelDistance
+ // instead of labelDistance // #1174
+ // finalConnectorOffset - not override connectorOffset value.
+
+ finalConnectorOffset = Math.min(
+ connectorOffset,
+ point.labelDistance / 5
+ ); // #1678
+
+ point.labelPosition = {
+ natural: {
+ // initial position of the data label - it's utilized for
+ // finding the final position for the label
+ x: positions[0] + radiusX + Math.cos(angle) *
+ point.labelDistance,
+ y: positions[1] + radiusY + Math.sin(angle) *
+ point.labelDistance
+ },
+ 'final': {
+ // used for generating connector path -
+ // initialized later in drawDataLabels function
+ // x: undefined,
+ // y: undefined
+ },
+ // left - pie on the left side of the data label
+ // right - pie on the right side of the data label
+ // center - data label overlaps the pie
+ alignment: point.labelDistance < 0 ?
+ 'center' : point.half ? 'right' : 'left',
+ connectorPosition: {
+ breakAt: { // used in connectorShapes.fixedOffset
+ x: positions[0] + radiusX + Math.cos(angle) *
+ finalConnectorOffset,
+ y: positions[1] + radiusY + Math.sin(angle) *
+ finalConnectorOffset
+ },
+ touchingSliceAt: { // middle of the arc
+ x: positions[0] + radiusX,
+ y: positions[1] + radiusY
+ }
+ }
+ };
+ }
+ },
- // Put the shadow behind all points
- var shadowGroup = point.shadowGroup;
- if (shadow && !shadowGroup) {
- shadowGroup = point.shadowGroup = renderer.g('shadow')
- .add(series.shadowGroup);
- }
+ /**
+ * @private
+ * @deprecated
+ * @name Highcharts.seriesTypes.pie#drawGraph
+ * @type {null}
+ */
+ drawGraph: null,
- if (shadowGroup) {
- shadowGroup.attr(groupTranslation);
- }
- pointAttr = series.pointAttribs(point, point.selected && 'select');
+ /**
+ * Draw the data points
+ *
+ * @private
+ * @function Highcharts.seriesTypes.pie#drawPoints
+ */
+ drawPoints: function () {
+ var series = this,
+ chart = series.chart,
+ renderer = chart.renderer,
+ groupTranslation,
+ graphic,
+ pointAttr,
+ shapeArgs,
+ shadow = series.options.shadow;
+
+ if (shadow && !series.shadowGroup && !chart.styledMode) {
+ series.shadowGroup = renderer.g('shadow')
+ .add(series.group);
+ }
+ // draw the slices
+ series.points.forEach(function (point) {
+ graphic = point.graphic;
+ if (!point.isNull) {
+ shapeArgs = point.shapeArgs;
- // Draw the slice
- if (graphic) {
- graphic
- .setRadialReference(series.center)
- .attr(pointAttr)
+ // If the point is sliced, use special translation, else use
+ // plot area traslation
+ groupTranslation = point.getTranslate();
- .animate(extend(shapeArgs, groupTranslation));
- } else {
+ if (!chart.styledMode) {
+ // Put the shadow behind all points
+ var shadowGroup = point.shadowGroup;
- point.graphic = graphic = renderer[point.shapeType](shapeArgs)
- .setRadialReference(series.center)
- .attr(groupTranslation)
- .add(series.group);
+ if (shadow && !shadowGroup) {
+ shadowGroup = point.shadowGroup = renderer
+ .g('shadow')
+ .add(series.shadowGroup);
+ }
- if (!point.visible) {
- graphic.attr({
- visibility: 'hidden'
- });
+ if (shadowGroup) {
+ shadowGroup.attr(groupTranslation);
+ }
+ pointAttr = series.pointAttribs(
+ point,
+ point.selected && 'select'
+ );
}
+ // Draw the slice
+ if (graphic) {
+ graphic
+ .setRadialReference(series.center);
- graphic
- .attr(pointAttr)
- .attr({
- 'stroke-linejoin': 'round'
- })
- .shadow(shadow, shadowGroup);
+ if (!chart.styledMode) {
+ graphic.attr(pointAttr);
+ }
+ graphic.animate(extend(shapeArgs, groupTranslation));
+ } else {
- }
+ point.graphic = graphic = renderer[point.shapeType](
+ shapeArgs
+ )
+ .setRadialReference(series.center)
+ .attr(groupTranslation)
+ .add(series.group);
+
+ if (!chart.styledMode) {
+ graphic
+ .attr(pointAttr)
+ .attr({ 'stroke-linejoin': 'round' })
+ .shadow(shadow, shadowGroup);
+ }
+ }
- graphic.addClass(point.getClassName());
+ graphic.attr({
+ visibility: point.visible ? 'inherit' : 'hidden'
+ });
- } else if (graphic) {
- point.graphic = graphic.destroy();
- }
- });
+ graphic.addClass(point.getClassName());
- },
+ } else if (graphic) {
+ point.graphic = graphic.destroy();
+ }
+ });
+ },
- searchPoint: noop,
+ /**
+ * @private
+ * @deprecated
+ * @function Highcharts.seriesTypes.pie#searchPoint
+ */
+ searchPoint: noop,
- /**
+ /**
* Utility for sorting data labels
+ *
+ * @private
+ * @function Highcharts.seriesTypes.pie#sortByAngle
+ *
+ * @param {Array} points
+ *
+ * @param {number} sign
*/
- sortByAngle: function(points, sign) {
- points.sort(function(a, b) {
- return a.angle !== undefined && (b.angle - a.angle) * sign;
- });
- },
+ sortByAngle: function (points, sign) {
+ points.sort(function (a, b) {
+ return a.angle !== undefined && (b.angle - a.angle) * sign;
+ });
+ },
- /**
- * Use a simple symbol from LegendSymbolMixin
+ /**
+ * Use a simple symbol from LegendSymbolMixin.
+ *
+ * @private
+ * @borrows Highcharts.LegendSymbolMixin.drawRectangle as Highcharts.seriesTypes.pie#drawLegendSymbol
*/
- drawLegendSymbol: LegendSymbolMixin.drawRectangle,
+ drawLegendSymbol: LegendSymbolMixin.drawRectangle,
- /**
- * Use the getCenter method from drawLegendSymbol
+ /**
+ * Use the getCenter method from drawLegendSymbol.
+ *
+ * @private
+ * @borrows Highcharts.CenteredSeriesMixin.getCenter as Highcharts.seriesTypes.pie#getCenter
*/
- getCenter: CenteredSeriesMixin.getCenter,
+ getCenter: CenteredSeriesMixin.getCenter,
- /**
- * Pies don't have point marker symbols
+ /**
+ * Pies don't have point marker symbols.
+ *
+ * @deprecated
+ * @private
+ * @function Highcharts.seriesTypes.pie#getSymbol
*/
- getSymbol: noop
+ getSymbol: noop
- /**
- * @constructor seriesTypes.pie.prototype.pointClass
- * @extends {Point}
- */
- }, /** @lends seriesTypes.pie.prototype.pointClass.prototype */ {
- /**
- * Initiate the pie slice
+ }, /** @lends seriesTypes.pie.prototype.pointClass.prototype */ {
+
+ /**
+ * Initialize the pie slice
+ *
+ * @private
+ * @function Highcharts.seriesTypes.pie#pointClass#init
+ *
+ * @return {Highcharts.Point}
*/
- init: function() {
+ init: function () {
- Point.prototype.init.apply(this, arguments);
+ Point.prototype.init.apply(this, arguments);
- var point = this,
- toggleSlice;
+ var point = this,
+ toggleSlice;
- point.name = pick(point.name, 'Slice');
+ point.name = pick(point.name, 'Slice');
- // add event listener for select
- toggleSlice = function(e) {
- point.slice(e.type === 'select');
- };
- addEvent(point, 'select', toggleSlice);
- addEvent(point, 'unselect', toggleSlice);
+ // add event listener for select
+ toggleSlice = function (e) {
+ point.slice(e.type === 'select');
+ };
+ addEvent(point, 'select', toggleSlice);
+ addEvent(point, 'unselect', toggleSlice);
- return point;
- },
+ return point;
+ },
- /**
+ /**
* Negative points are not valid (#1530, #3623, #5322)
+ *
+ * @private
+ * @function Highcharts.seriesTypes.pie#pointClass#isValid
+ *
+ * @return {boolean}
*/
- isValid: function() {
- return H.isNumber(this.y, true) && this.y >= 0;
- },
+ isValid: function () {
+ return H.isNumber(this.y, true) && this.y >= 0;
+ },
- /**
+ /**
* Toggle the visibility of the pie slice
- * @param {Boolean} vis Whether to show the slice or not. If undefined, the
- * visibility is toggled
+ *
+ * @private
+ * @function Highcharts.seriesTypes.pie#pointClass#setVisible
+ *
+ * @param {boolean} vis
+ * Whether to show the slice or not. If undefined, the visibility is
+ * toggled.
+ *
+ * @param {boolean} [redraw=false]
*/
- setVisible: function(vis, redraw) {
- var point = this,
- series = point.series,
- chart = series.chart,
- ignoreHiddenPoint = series.options.ignoreHiddenPoint;
-
- redraw = pick(redraw, ignoreHiddenPoint);
+ setVisible: function (vis, redraw) {
+ var point = this,
+ series = point.series,
+ chart = series.chart,
+ ignoreHiddenPoint = series.options.ignoreHiddenPoint;
+
+ redraw = pick(redraw, ignoreHiddenPoint);
+
+ if (vis !== point.visible) {
+
+ // If called without an argument, toggle visibility
+ point.visible = point.options.visible = vis =
+ vis === undefined ? !point.visible : vis;
+ // update userOptions.data
+ series.options.data[series.data.indexOf(point)] = point.options;
+
+ // Show and hide associated elements. This is performed
+ // regardless of redraw or not, because chart.redraw only
+ // handles full series.
+ ['graphic', 'dataLabel', 'connector', 'shadowGroup'].forEach(
+ function (key) {
+ if (point[key]) {
+ point[key][vis ? 'show' : 'hide'](true);
+ }
+ }
+ );
- if (vis !== point.visible) {
+ if (point.legendItem) {
+ chart.legend.colorizeItem(point, vis);
+ }
- // If called without an argument, toggle visibility
- point.visible = point.options.visible = vis = vis === undefined ? !point.visible : vis;
- series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data
+ // #4170, hide halo after hiding point
+ if (!vis && point.state === 'hover') {
+ point.setState('');
+ }
- // Show and hide associated elements. This is performed regardless of redraw or not,
- // because chart.redraw only handles full series.
- each(['graphic', 'dataLabel', 'connector', 'shadowGroup'], function(key) {
- if (point[key]) {
- point[key][vis ? 'show' : 'hide'](true);
+ // Handle ignore hidden slices
+ if (ignoreHiddenPoint) {
+ series.isDirty = true;
}
- });
- if (point.legendItem) {
- chart.legend.colorizeItem(point, vis);
+ if (redraw) {
+ chart.redraw();
+ }
}
+ },
- // #4170, hide halo after hiding point
- if (!vis && point.state === 'hover') {
- point.setState('');
- }
+ /**
+ * Set or toggle whether the slice is cut out from the pie
+ *
+ * @private
+ * @function Highcharts.seriesTypes.pie#pointClass#slice
+ *
+ * @param {boolean} sliced
+ * When undefined, the slice state is toggled.
+ *
+ * @param {boolean} redraw
+ * Whether to redraw the chart. True by default.
+ */
+ slice: function (sliced, redraw, animation) {
+ var point = this,
+ series = point.series,
+ chart = series.chart;
- // Handle ignore hidden slices
- if (ignoreHiddenPoint) {
- series.isDirty = true;
- }
+ setAnimation(animation, chart);
- if (redraw) {
- chart.redraw();
+ // redraw is true by default
+ redraw = pick(redraw, true);
+
+ // if called without an argument, toggle
+ point.sliced = point.options.sliced = sliced =
+ defined(sliced) ? sliced : !point.sliced;
+ // update userOptions.data
+ series.options.data[series.data.indexOf(point)] = point.options;
+
+ point.graphic.animate(this.getTranslate());
+
+ if (point.shadowGroup) {
+ point.shadowGroup.animate(this.getTranslate());
}
- }
- },
+ },
- /**
- * Set or toggle whether the slice is cut out from the pie
- * @param {Boolean} sliced When undefined, the slice state is toggled
- * @param {Boolean} redraw Whether to redraw the chart. True by default.
+ /**
+ * @private
+ * @function Highcharts.seriesTypes.pie#pointClass#getTranslate
+ *
+ * @return {*}
*/
- slice: function(sliced, redraw, animation) {
- var point = this,
- series = point.series,
- chart = series.chart;
+ getTranslate: function () {
+ return this.sliced ? this.slicedTranslation : {
+ translateX: 0,
+ translateY: 0
+ };
+ },
- setAnimation(animation, chart);
+ /**
+ * @private
+ * @function Highcharts.seriesTypes.pie#pointClass#haloPath
+ *
+ * @param {number} size
+ *
+ * @return {Highcharts.SVGPathArray}
+ */
+ haloPath: function (size) {
+ var shapeArgs = this.shapeArgs;
+
+ return this.sliced || !this.visible ?
+ [] :
+ this.series.chart.renderer.symbols.arc(
+ shapeArgs.x,
+ shapeArgs.y,
+ shapeArgs.r + size,
+ shapeArgs.r + size, {
+ // Substract 1px to ensure the background is not bleeding
+ // through between the halo and the slice (#7495).
+ innerR: this.shapeArgs.r - 1,
+ start: shapeArgs.start,
+ end: shapeArgs.end
+ }
+ );
+ },
- // redraw is true by default
- redraw = pick(redraw, true);
+ connectorShapes: {
+ // only one available before v7.0.0
+ fixedOffset: function (labelPosition, connectorPosition, options) {
+ var breakAt = connectorPosition.breakAt,
+ touchingSliceAt = connectorPosition.touchingSliceAt,
+ linePath = options.softConnector ? [
+ 'C', // soft break
+ // 1st control point (of the curve)
+ labelPosition.x +
+ // 5 gives the connector a little horizontal bend
+ (labelPosition.alignment === 'left' ? -5 : 5),
+ labelPosition.y, //
+ 2 * breakAt.x - touchingSliceAt.x, // 2nd control point
+ 2 * breakAt.y - touchingSliceAt.y, //
+ breakAt.x, // end of the curve
+ breakAt.y //
+ ] : [
+ 'L', // pointy break
+ breakAt.x,
+ breakAt.y
+ ];
- // if called without an argument, toggle
- point.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced;
- series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data
+ // assemble the path
+ return [
+ 'M',
+ labelPosition.x,
+ labelPosition.y
+ ].concat(linePath).concat([
+ 'L',
+ touchingSliceAt.x,
+ touchingSliceAt.y
+ ]);
+ },
- point.graphic.animate(this.getTranslate());
+ straight: function (labelPosition, connectorPosition) {
+ var touchingSliceAt = connectorPosition.touchingSliceAt;
+ // direct line to the slice
+ return [
+ 'M',
+ labelPosition.x,
+ labelPosition.y,
+ 'L',
+ touchingSliceAt.x,
+ touchingSliceAt.y
+ ];
+ },
- if (point.shadowGroup) {
- point.shadowGroup.animate(this.getTranslate());
- }
+ crookedLine: function (labelPosition, connectorPosition,
+ options) {
+
+ var touchingSliceAt = connectorPosition.touchingSliceAt,
+ series = this.series,
+ pieCenterX = series.center[0],
+ plotWidth = series.chart.plotWidth,
+ plotLeft = series.chart.plotLeft,
+ alignment = labelPosition.alignment,
+ radius = this.shapeArgs.r,
+ crookDistance =
+ H.relativeLength(options.crookDistance, 1), // % to fraction
+ crookX = alignment === 'left' ?
+ pieCenterX + radius + (plotWidth + plotLeft -
+ pieCenterX - radius) * (1 - crookDistance) :
+ plotLeft + (pieCenterX - radius) * crookDistance,
+ segmentWithCrook = ['L',
+ crookX,
+ labelPosition.y];
+
+ // crookedLine formula doesn't make sense if the path overlaps
+ // the label - use straight line instead in that case
+ if (alignment === 'left' ?
+ (crookX > labelPosition.x || crookX < touchingSliceAt.x) :
+ (crookX < labelPosition.x || crookX > touchingSliceAt.x)) {
+ segmentWithCrook = []; // remove the crook
+ }
- },
+ // assemble the path
+ return [
+ 'M',
+ labelPosition.x,
+ labelPosition.y
+ ].concat(segmentWithCrook).concat(['L',
+ touchingSliceAt.x,
+ touchingSliceAt.y
+ ]);
- getTranslate: function() {
- return this.sliced ? this.slicedTranslation : {
- translateX: 0,
- translateY: 0
- };
- },
+ }
+ },
- haloPath: function(size) {
- var shapeArgs = this.shapeArgs;
+ /**
+ * Extendable method for getting the path of the connector between the data
+ * label and the pie slice.
+ */
+ getConnectorPath: function () {
+ var labelPosition = this.labelPosition,
+ options = this.series.options.dataLabels,
+ connectorShape = options.connectorShape,
+ predefinedShapes = this.connectorShapes;
- return this.sliced || !this.visible ? [] :
- this.series.chart.renderer.symbols.arc(
- shapeArgs.x,
- shapeArgs.y,
- shapeArgs.r + size,
- shapeArgs.r + size, {
- innerR: this.shapeArgs.r,
- start: shapeArgs.start,
- end: shapeArgs.end
- }
- );
- }
- });
+ // find out whether to use the predefined shape
+ if (predefinedShapes[connectorShape]) {
+ connectorShape = predefinedShapes[connectorShape];
+ }
+
+ return connectorShape.call(this, {
+ // pass simplified label position object for user's convenience
+ x: labelPosition.final.x,
+ y: labelPosition.final.y,
+ alignment: labelPosition.alignment
+ }, labelPosition.connectorPosition, options);
+ }
+ });
/**
* A `pie` series. If the [type](#series.pie.type) option is not specified,
* it is inherited from [chart.type](#chart.type).
- *
- * For options that apply to multiple series, it is recommended to add
- * them to the [plotOptions.series](#plotOptions.series) options structure.
- * To apply to all series of this specific type, apply it to [plotOptions.
- * pie](#plotOptions.pie).
- *
- * @type {Object}
- * @extends series,plotOptions.pie
- * @excluding dataParser,dataURL,stack,xAxis,yAxis
- * @product highcharts
+ *
+ * @extends series,plotOptions.pie
+ * @excluding dataParser, dataURL, stack, xAxis, yAxis
+ * @product highcharts
* @apioption series.pie
*/
/**
* An array of data points for the series. For the `pie` series type,
* points can be given in the following ways:
- *
+ *
* 1. An array of numerical values. In this case, the numerical values
* will be interpreted as `y` options. Example:
- *
+ *
* ```js
* data: [0, 5, 3, 5]
* ```
- *
- * 2. An array of objects with named values. The objects are point
- * configuration objects as seen below. If the total number of data
+ *
+ * 2. An array of objects with named values. The following snippet shows only a
+ * few settings, see the complete options set below. If the total number of data
* points exceeds the series' [turboThreshold](#series.pie.turboThreshold),
* this option is not available.
- *
+ *
* ```js
* data: [{
* y: 1,
@@ -31509,108 +41498,107 @@
* name: "Point1",
* color: "#FF00FF"
* }]
- *
- * @type {Array}
- * @extends series.line.data
- * @excluding marker,x
- * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
- * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
- * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
- * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
- * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
- * @product highcharts
+ *
+ * @sample {highcharts} highcharts/chart/reflow-true/
+ * Numerical values
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/
+ * Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
+ * Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/
+ * Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
+ *
+ * @type {Array|*>}
+ * @extends series.line.data
+ * @excluding marker, x
+ * @product highcharts
* @apioption series.pie.data
*/
/**
* The sequential index of the data point in the legend.
- *
- * @type {Number}
- * @product highcharts
+ *
+ * @type {number}
+ * @product highcharts
* @apioption series.pie.data.legendIndex
*/
- /**
- * Whether to display a slice offset from the center.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/point/sliced/ One sliced point
- * @product highcharts
- * @apioption series.pie.data.sliced
- */
-
- /**
- * Fires when the checkbox next to the point name in the legend is clicked.
- * One parameter, event, is passed to the function. The state of the
- * checkbox is found by event.checked. The checked item is found by
- * event.item. Return false to prevent the default action which is to
- * toggle the select state of the series.
- *
- * @type {Function}
- * @context Point
- * @sample {highcharts} highcharts/plotoptions/series-events-checkboxclick/
- * Alert checkbox status
- * @since 1.2.0
- * @product highcharts
- * @apioption plotOptions.pie.events.checkboxClick
- */
-
- /**
- * Not applicable to pies, as the legend item is per point. See point.
- * events.
- *
- * @type {Function}
- * @since 1.2.0
- * @product highcharts
- * @apioption plotOptions.pie.events.legendItemClick
- */
-
/**
* Fires when the legend item belonging to the pie point (slice) is
* clicked. The `this` keyword refers to the point itself. One parameter,
* `event`, is passed to the function, containing common event information. The
* default action is to toggle the visibility of the point. This can be
* prevented by calling `event.preventDefault()`.
- *
- * @type {Function}
+ *
* @sample {highcharts} highcharts/plotoptions/pie-point-events-legenditemclick/
* Confirm toggle visibility
- * @since 1.2.0
- * @product highcharts
+ *
+ * @type {Function}
+ * @since 1.2.0
+ * @product highcharts
* @apioption plotOptions.pie.point.events.legendItemClick
*/
+ /**
+ * Whether to display a slice offset from the center.
+ *
+ * @sample {highcharts} highcharts/point/sliced/
+ * One sliced point
+ *
+ * @type {boolean}
+ * @product highcharts
+ * @apioption series.pie.data.sliced
+ */
+
+ /**
+ * @excluding legendItemClick
+ * @apioption series.pie.events
+ */
+
}(Highcharts));
- (function(H) {
+ (function (H) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
+
var addEvent = H.addEvent,
arrayMax = H.arrayMax,
defined = H.defined,
- each = H.each,
extend = H.extend,
format = H.format,
- map = H.map,
merge = H.merge,
noop = H.noop,
pick = H.pick,
relativeLength = H.relativeLength,
Series = H.Series,
seriesTypes = H.seriesTypes,
- stableSort = H.stableSort;
+ stableSort = H.stableSort,
+ isArray = H.isArray,
+ splat = H.splat;
- /* eslint max-len: ["warn", 80, 4] */
/**
* General distribution algorithm for distributing labels of differing size
* along a confined length in two dimensions. The algorithm takes an array of
* objects containing a size, a target and a rank. It will place the labels as
* close as possible to their targets, skipping the lowest ranked labels if
* necessary.
+ *
+ * @private
+ * @function Highcharts.distribute
+ *
+ * @param {Array} boxes
+ *
+ * @param {number} len
+ *
+ * @param {number} maxDistance
*/
- H.distribute = function(boxes, len) {
+ H.distribute = function (boxes, len, maxDistance) {
var i,
overlapping = true,
@@ -31618,7 +41606,8 @@
restBoxes = [], // The outranked overshoot
box,
target,
- total = 0;
+ total = 0,
+ reducedLen = origBoxes.reducedLen || len;
function sortByTarget(a, b) {
return a.target - b.target;
@@ -31632,13 +41621,13 @@
}
// Sort by rank, then slice away overshoot
- if (total > len) {
- stableSort(boxes, function(a, b) {
+ if (total > reducedLen) {
+ stableSort(boxes, function (a, b) {
return (b.rank || 0) - (a.rank || 0);
});
i = 0;
total = 0;
- while (total <= len) {
+ while (total <= reducedLen) {
total += boxes[i].size;
i++;
}
@@ -31651,7 +41640,7 @@
// So far we have been mutating the original array. Now
// create a copy with target arrays
- boxes = map(boxes, function(box) {
+ boxes = boxes.map(function (box) {
return {
size: box.size,
targets: [box.target],
@@ -31698,39 +41687,78 @@
}
}
+ // Add the rest (hidden boxes)
+ origBoxes.push.apply(origBoxes, restBoxes);
+
+
// Now the composite boxes are placed, we need to put the original boxes
// within them
i = 0;
- each(boxes, function(box) {
+ boxes.some(function (box) {
var posInCompositeBox = 0;
- each(box.targets, function() {
+
+ if (box.targets.some(function () {
origBoxes[i].pos = box.pos + posInCompositeBox;
+
+ // If the distance between the position and the target exceeds
+ // maxDistance, abort the loop and decrease the length in increments
+ // of 10% to recursively reduce the number of visible boxes by
+ // rank. Once all boxes are within the maxDistance, we're good.
+ if (
+ Math.abs(origBoxes[i].pos - origBoxes[i].target) >
+ maxDistance
+ ) {
+ // Reset the positions that are already set
+ origBoxes.slice(0, i + 1).forEach(function (box) {
+ delete box.pos;
+ });
+
+ // Try with a smaller length
+ origBoxes.reducedLen =
+ (origBoxes.reducedLen || len) - (len * 0.1);
+
+ // Recurse
+ if (origBoxes.reducedLen > len * 0.1) {
+ H.distribute(origBoxes, len, maxDistance);
+ }
+
+ // Exceeded maxDistance => abort
+ return true;
+ }
+
posInCompositeBox += origBoxes[i].size;
i++;
- });
+
+ })) {
+ // Exceeded maxDistance => abort
+ return true;
+ }
});
// Add the rest (hidden) boxes and sort by target
- origBoxes.push.apply(origBoxes, restBoxes);
stableSort(origBoxes, sortByTarget);
};
/**
* Draw the data labels
+ *
+ * @private
+ * @function Highcharts.Series#drawDataLabels
+ *
+ * @fires Highcharts.Series#event:afterDrawDataLabels
*/
- Series.prototype.drawDataLabels = function() {
+ Series.prototype.drawDataLabels = function () {
var series = this,
+ chart = series.chart,
seriesOptions = series.options,
- options = seriesOptions.dataLabels,
+ seriesDlOptions = seriesOptions.dataLabels,
points = series.points,
pointOptions,
- generalOptions,
hasRendered = series.hasRendered || 0,
- str,
dataLabelsGroup,
- defer = pick(options.defer, !!seriesOptions.animation),
- renderer = series.chart.renderer;
+ defer = pick(seriesDlOptions.defer, !!seriesOptions.animation),
+ renderer = chart.renderer;
/*
* Handle the dataLabels.filter option.
@@ -31740,6 +41768,7 @@
op,
prop,
val;
+
if (filter) {
op = filter.operator;
prop = point[filter.property];
@@ -31759,182 +41788,294 @@
return true;
}
- if (options.enabled || series._hasPointLabels) {
+ /*
+ * Merge two objects that can be arrays. If one of them is an array, the
+ * other is merged into each element. If both are arrays, each element is
+ * merged by index. If neither are arrays, we use normal merge.
+ */
+ function mergeArrays(one, two) {
+ var res = [],
+ i;
- // Process default alignment of data labels for columns
- if (series.dlProcessOptions) {
- series.dlProcessOptions(options);
+ if (isArray(one) && !isArray(two)) {
+ res = one.map(function (el) {
+ return merge(el, two);
+ });
+ } else if (isArray(two) && !isArray(one)) {
+ res = two.map(function (el) {
+ return merge(one, el);
+ });
+ } else if (!isArray(one) && !isArray(two)) {
+ res = merge(one, two);
+ } else {
+ i = Math.max(one.length, two.length);
+ while (i--) {
+ res[i] = merge(one[i], two[i]);
+ }
}
+ return res;
+ }
+
+
+ // Merge in plotOptions.dataLabels for series
+ seriesDlOptions = mergeArrays(
+ mergeArrays(
+ chart.options.plotOptions &&
+ chart.options.plotOptions.series &&
+ chart.options.plotOptions.series.dataLabels,
+ chart.options.plotOptions &&
+ chart.options.plotOptions[series.type] &&
+ chart.options.plotOptions[series.type].dataLabels
+ ),
+ seriesDlOptions
+ );
+
+ H.fireEvent(this, 'drawDataLabels');
+
+ if (
+ isArray(seriesDlOptions) ||
+ seriesDlOptions.enabled ||
+ series._hasPointLabels
+ ) {
// Create a separate group for the data labels to avoid rotation
dataLabelsGroup = series.plotGroup(
'dataLabelsGroup',
'data-labels',
defer && !hasRendered ? 'hidden' : 'visible', // #5133
- options.zIndex || 6
+ seriesDlOptions.zIndex || 6
);
if (defer) {
- dataLabelsGroup.attr({
- opacity: +hasRendered
- }); // #3300
+ dataLabelsGroup.attr({ opacity: +hasRendered }); // #3300
if (!hasRendered) {
- addEvent(series, 'afterAnimate', function() {
+ addEvent(series, 'afterAnimate', function () {
if (series.visible) { // #2597, #3023, #3024
dataLabelsGroup.show(true);
}
dataLabelsGroup[
seriesOptions.animation ? 'animate' : 'attr'
- ]({
- opacity: 1
- }, {
- duration: 200
- });
+ ]({ opacity: 1 }, { duration: 200 });
});
}
}
// Make the labels for each point
- generalOptions = options;
- each(points, function(point) {
- var enabled,
- dataLabel = point.dataLabel,
- labelConfig,
- attr,
- rotation,
- connector = point.connector,
- isNew = !dataLabel,
- style,
- formatString;
-
- // Determine if each data label is enabled
+ points.forEach(function (point) {
+
+ // Merge in series options for the point.
// @note dataLabelAttribs (like pointAttribs) would eradicate
// the need for dlOptions, and simplify the section below.
- pointOptions = point.dlOptions || // dlOptions is used in treemaps
- (point.options && point.options.dataLabels);
- enabled = pick(
- pointOptions && pointOptions.enabled,
- generalOptions.enabled
- ) && !point.isNull; // #2282, #4641, #7112
-
- if (enabled) {
- enabled = applyFilter(point, pointOptions || options) === true;
- }
-
- if (enabled) {
- // Create individual options structure that can be extended
- // without affecting others
- options = merge(generalOptions, pointOptions);
- labelConfig = point.getLabelConfig();
- formatString = (
- options[point.formatPrefix + 'Format'] ||
- options.format
- );
-
- str = defined(formatString) ?
- format(formatString, labelConfig) :
- (
- options[point.formatPrefix + 'Formatter'] ||
- options.formatter
- ).call(labelConfig, options);
-
- style = options.style;
- rotation = options.rotation;
-
- // Determine the color
- style.color = pick(
- options.color,
- style.color,
- series.color,
- '#000000'
- );
- // Get automated contrast color
- if (style.color === 'contrast') {
- point.contrastColor =
- renderer.getContrast(point.color || series.color);
- style.color = options.inside ||
- pick(point.labelDistance, options.distance) < 0 ||
- !!seriesOptions.stacking ?
- point.contrastColor :
- '#000000';
- }
- if (seriesOptions.cursor) {
- style.cursor = seriesOptions.cursor;
- }
-
+ pointOptions = splat(
+ mergeArrays(
+ seriesDlOptions,
+ point.dlOptions || // dlOptions is used in treemaps
+ (point.options && point.options.dataLabels)
+ )
+ );
- attr = {
+ // Handle each individual data label for this point
+ pointOptions.forEach(function (labelOptions, i) {
+ // Options for one datalabel
+ var labelEnabled = labelOptions.enabled &&
+ !point.isNull && // #2282, #4641, #7112
+ applyFilter(point, labelOptions),
+ labelConfig,
+ formatString,
+ labelText,
+ style,
+ rotation,
+ attr,
+ dataLabel = point.dataLabels ? point.dataLabels[i] :
+ point.dataLabel,
+ connector = point.connectors ? point.connectors[i] :
+ point.connector,
+ isNew = !dataLabel;
+
+ if (labelEnabled) {
+ // Create individual options structure that can be extended
+ // without affecting others
+ labelConfig = point.getLabelConfig();
+ formatString = (
+ labelOptions[point.formatPrefix + 'Format'] ||
+ labelOptions.format
+ );
- fill: options.backgroundColor,
- stroke: options.borderColor,
- 'stroke-width': options.borderWidth,
+ labelText = defined(formatString) ?
+ format(formatString, labelConfig, chart.time) :
+ (
+ labelOptions[point.formatPrefix + 'Formatter'] ||
+ labelOptions.formatter
+ ).call(labelConfig, labelOptions);
+
+ style = labelOptions.style;
+ rotation = labelOptions.rotation;
+
+ if (!chart.styledMode) {
+ // Determine the color
+ style.color = pick(
+ labelOptions.color,
+ style.color,
+ series.color,
+ '#000000'
+ );
+ // Get automated contrast color
+ if (style.color === 'contrast') {
+ point.contrastColor = renderer.getContrast(
+ point.color || series.color
+ );
+ style.color = labelOptions.inside ||
+ pick(
+ labelOptions.distance,
+ point.labelDistance
+ ) < 0 ||
+ !!seriesOptions.stacking ?
+ point.contrastColor :
+ '#000000';
+ }
+ if (seriesOptions.cursor) {
+ style.cursor = seriesOptions.cursor;
+ }
+ }
- r: options.borderRadius || 0,
- rotation: rotation,
- padding: options.padding,
- zIndex: 1
- };
+ attr = {
+ r: labelOptions.borderRadius || 0,
+ rotation: rotation,
+ padding: labelOptions.padding,
+ zIndex: 1
+ };
- // Remove unused attributes (#947)
- H.objectEach(attr, function(val, name) {
- if (val === undefined) {
- delete attr[name];
+ if (!chart.styledMode) {
+ attr.fill = labelOptions.backgroundColor;
+ attr.stroke = labelOptions.borderColor;
+ attr['stroke-width'] = labelOptions.borderWidth;
}
- });
- }
- // If the point is outside the plot area, destroy it. #678, #820
- if (dataLabel && (!enabled || !defined(str))) {
- point.dataLabel = dataLabel = dataLabel.destroy();
- if (connector) {
- point.connector = connector.destroy();
+
+ // Remove unused attributes (#947)
+ H.objectEach(attr, function (val, name) {
+ if (val === undefined) {
+ delete attr[name];
+ }
+ });
}
+
+ // If the point is outside the plot area, destroy it. #678, #820
+ if (dataLabel && (!labelEnabled || !defined(labelText))) {
+ point.dataLabel =
+ point.dataLabel && point.dataLabel.destroy();
+ if (point.dataLabels) {
+ // Remove point.dataLabels if this was the last one
+ if (point.dataLabels.length === 1) {
+ delete point.dataLabels;
+ } else {
+ delete point.dataLabels[i];
+ }
+ }
+ if (!i) {
+ delete point.dataLabel;
+ }
+ if (connector) {
+ point.connector = point.connector.destroy();
+ if (point.connectors) {
+ // Remove point.connectors if this was the last one
+ if (point.connectors.length === 1) {
+ delete point.connectors;
+ } else {
+ delete point.connectors[i];
+ }
+ }
+ }
+
// Individual labels are disabled if the are explicitly disabled
// in the point options, or if they fall outside the plot area.
- } else if (enabled && defined(str)) {
- // create new label
- if (!dataLabel) {
- dataLabel = point.dataLabel = renderer[
- rotation ? 'text' : 'label' // labels don't rotate
- ](
- str,
- 0, -9999,
- options.shape,
- null,
- null,
- options.useHTML,
- null,
- 'data-label'
- );
- dataLabel.addClass(
- 'highcharts-data-label-color-' + point.colorIndex +
- ' ' + (options.className || '') +
- (options.useHTML ? 'highcharts-tracker' : '') // #3398
- );
- } else {
- attr.text = str;
- }
- dataLabel.attr(attr);
+ } else if (labelEnabled && defined(labelText)) {
+
+ if (!dataLabel) {
+ // Create new label element
+ point.dataLabels = point.dataLabels || [];
+ dataLabel = point.dataLabels[i] = rotation ?
+
+ // Labels don't rotate, use text element
+ renderer.text(labelText, 0, -9999)
+ .addClass('highcharts-data-label') :
+
+ // We can use label
+ renderer.label(
+ labelText,
+ 0,
+ -9999,
+ labelOptions.shape,
+ null,
+ null,
+ labelOptions.useHTML,
+ null,
+ 'data-label'
+ );
+
+ // Store for backwards compatibility
+ if (!i) {
+ point.dataLabel = dataLabel;
+ }
+
+ dataLabel.addClass(
+ ' highcharts-data-label-color-' + point.colorIndex +
+ ' ' + (labelOptions.className || '') +
+ ( // #3398
+ labelOptions.useHTML ?
+ ' highcharts-tracker' :
+ ''
+ )
+ );
+ } else {
+ // Use old element and just update text
+ attr.text = labelText;
+ }
+
+ // Store data label options for later access
+ dataLabel.options = labelOptions;
- // Styles must be applied before add in order to read text
- // bounding box
- dataLabel.css(style).shadow(options.shadow);
+ dataLabel.attr(attr);
+ if (!chart.styledMode) {
+ // Styles must be applied before add in order to read
+ // text bounding box
+ dataLabel.css(style).shadow(labelOptions.shadow);
+ }
+
+ if (!dataLabel.added) {
+ dataLabel.add(dataLabelsGroup);
+ }
- if (!dataLabel.added) {
- dataLabel.add(dataLabelsGroup);
+ // Now the data label is created and placed at 0,0, so we
+ // need to align it
+ series.alignDataLabel(
+ point, dataLabel, labelOptions, null, isNew
+ );
}
- // Now the data label is created and placed at 0,0, so we need
- // to align it
- series.alignDataLabel(point, dataLabel, options, null, isNew);
- }
+ });
});
}
+
+ H.fireEvent(this, 'afterDrawDataLabels');
};
/**
- * Align each individual data label
+ * Align each individual data label.
+ *
+ * @private
+ * @function Highcharts.Series#alignDataLabel
+ *
+ * @param {Highcharts.Point} point
+ *
+ * @param {Highcharts.SVGElement} dataLabel
+ *
+ * @param {Highcharts.PlotSeriesDataLabelsOptions} options
+ *
+ * @param {Highcharts.BBoxObject} alignTo
+ *
+ * @param {boolean} isNew
*/
- Series.prototype.alignDataLabel = function(
+ Series.prototype.alignDataLabel = function (
point,
dataLabel,
options,
@@ -31942,11 +42083,10 @@
isNew
) {
var chart = this.chart,
- inverted = chart.inverted,
+ inverted = this.isCartesian && chart.inverted,
plotX = pick(point.dlBox && point.dlBox.centerX, point.plotX, -9999),
plotY = pick(point.plotY, -9999),
bBox = dataLabel.getBBox(),
- fontSize,
baseline,
rotation = options.rotation,
normRotation,
@@ -31956,30 +42096,29 @@
// Math.round for rounding errors (#2683), alignTo to allow column
// labels (#2700)
visible =
- this.visible &&
- (
- point.series.forceDL ||
- chart.isInsidePlot(plotX, Math.round(plotY), inverted) ||
+ this.visible &&
(
- alignTo && chart.isInsidePlot(
- plotX,
- inverted ?
- alignTo.x + 1 :
- alignTo.y + alignTo.height - 1,
- inverted
+ point.series.forceDL ||
+ chart.isInsidePlot(plotX, Math.round(plotY), inverted) ||
+ (
+ alignTo && chart.isInsidePlot(
+ plotX,
+ inverted ?
+ alignTo.x + 1 :
+ alignTo.y + alignTo.height - 1,
+ inverted
+ )
)
- )
- ),
+ ),
alignAttr, // the final position;
justify = pick(options.overflow, 'justify') === 'justify';
if (visible) {
-
- fontSize = options.style.fontSize;
-
-
- baseline = chart.renderer.fontMetrics(fontSize, dataLabel).b;
+ baseline = chart.renderer.fontMetrics(
+ chart.styledMode ? undefined : options.style.fontSize,
+ dataLabel
+ ).b;
// The alignment box is a singular point
alignTo = extend({
@@ -32004,12 +42143,9 @@
x: alignTo.x + options.x + alignTo.width / 2 + rotCorr.x,
y: (
alignTo.y +
- options.y + {
- top: 0,
- middle: 0.5,
- bottom: 1
- }[options.verticalAlign] *
- alignTo.height
+ options.y +
+ { top: 0, middle: 0.5, bottom: 1 }[options.verticalAlign] *
+ alignTo.height
)
};
dataLabel[isNew ? 'attr' : 'animate'](alignAttr)
@@ -32030,7 +42166,8 @@
alignAttr.x -= bBox.width;
alignAttr.y -= negRotation ? 0 : bBox.height;
}
-
+ dataLabel.placed = true;
+ dataLabel.alignAttr = alignAttr;
} else {
dataLabel.align(options, null, alignTo);
@@ -32038,7 +42175,7 @@
}
// Handle justify or crop
- if (justify) {
+ if (justify && alignTo.height >= 0) { // #8830
point.isLabelJustified = this.justifyDataLabel(
dataLabel,
options,
@@ -32048,7 +42185,7 @@
isNew
);
- // Now check that the data label is within the plot area
+ // Now check that the data label is within the plot area
} else if (pick(options.crop, true)) {
visible =
chart.isInsidePlot(
@@ -32073,9 +42210,7 @@
// Show or hide based on the final aligned position
if (!visible) {
- dataLabel.attr({
- y: -9999
- });
+ dataLabel.attr({ y: -9999 });
dataLabel.placed = false; // don't animate back in
}
@@ -32084,8 +42219,23 @@
/**
* If data labels fall partly outside the plot area, align them back in, in a
* way that doesn't hide the point.
+ *
+ * @private
+ * @function Highcharts.Series#justifyDataLabel
+ *
+ * @param {Highcharts.SVGElement} dataLabel
+ *
+ * @param {Highcharts.PlotSeriesDataLabelsOptions} options
+ *
+ * @param {*} alignAttr
+ *
+ * @param {Highcharts.BBoxObject} bBox
+ *
+ * @param {boolean} isNew
+ *
+ * @return {boolean}
*/
- Series.prototype.justifyDataLabel = function(
+ Series.prototype.justifyDataLabel = function (
dataLabel,
options,
alignAttr,
@@ -32152,27 +42302,100 @@
return justified;
};
- /**
- * Override the base drawDataLabels method by pie specific functionality
- */
if (seriesTypes.pie) {
- seriesTypes.pie.prototype.drawDataLabels = function() {
+ seriesTypes.pie.prototype.dataLabelPositioners = {
+
+ // Based on the value computed in Highcharts' distribute algorithm.
+ radialDistributionY: function (point) {
+ return point.top + point.distributeBox.pos;
+ },
+ // get the x - use the natural x position for labels near the
+ // top and bottom, to prevent the top and botton slice
+ // connectors from touching each other on either side
+
+ // Based on the value computed in Highcharts' distribute algorithm.
+ radialDistributionX: function (series, point, y, naturalY) {
+ return series.getX(
+ y < point.top + 2 || y > point.bottom - 2 ?
+ naturalY :
+ y,
+ point.half,
+ point
+ );
+ },
+
+ // dataLabels.distance determines the x position of the label
+ justify: function (point, radius, seriesCenter) {
+ return seriesCenter[0] + (point.half ? -1 : 1) *
+ (radius + point.labelDistance);
+ },
+
+ // Left edges of the left-half labels touch the left edge of the plot
+ // area. Right edges of the right-half labels touch the right edge of
+ // the plot area.
+ alignToPlotEdges: function (
+ dataLabel,
+ half,
+ plotWidth,
+ plotLeft
+ ) {
+ var dataLabelWidth = dataLabel.getBBox().width;
+
+ return half ? dataLabelWidth + plotLeft :
+ plotWidth - dataLabelWidth - plotLeft;
+ },
+
+ // Connectors of each side end in the same x position. Labels are
+ // aligned to them. Left edge of the widest left-half label touches the
+ // left edge of the plot area. Right edge of the widest right-half label
+ // touches the right edge of the plot area.
+ alignToConnectors: function (
+ points,
+ half,
+ plotWidth,
+ plotLeft
+ ) {
+ var maxDataLabelWidth = 0,
+ dataLabelWidth;
+
+ // find widest data label
+ points.forEach(function (point) {
+ dataLabelWidth = point.dataLabel.getBBox().width;
+ if (dataLabelWidth > maxDataLabelWidth) {
+ maxDataLabelWidth = dataLabelWidth;
+ }
+ });
+ return half ? maxDataLabelWidth + plotLeft :
+ plotWidth - maxDataLabelWidth - plotLeft;
+ }
+ };
+
+ /**
+ * Override the base drawDataLabels method by pie specific functionality
+ *
+ * @private
+ * @function Highcharts.seriesTypes.pie#drawDataLabels
+ */
+ seriesTypes.pie.prototype.drawDataLabels = function () {
var series = this,
data = series.data,
point,
chart = series.chart,
options = series.options.dataLabels,
- connectorPadding = pick(options.connectorPadding, 10),
+ connectorPadding = options.connectorPadding,
connectorWidth = pick(options.connectorWidth, 1),
plotWidth = chart.plotWidth,
plotHeight = chart.plotHeight,
+ plotLeft = chart.plotLeft,
+ maxWidth = Math.round(chart.chartWidth / 3),
connector,
seriesCenter = series.center,
radius = seriesCenter[2] / 2,
centerY = seriesCenter[1],
dataLabel,
dataLabelWidth,
- labelPos,
+ // labelPos,
+ labelPosition,
labelHeight,
// divide the points into right and left halves for anti collision
halves = [
@@ -32183,7 +42406,8 @@
y,
visibility,
j,
- overflow = [0, 0, 0, 0]; // top, right, bottom, left
+ overflow = [0, 0, 0, 0], // top, right, bottom, left
+ dataLabelPositioners = series.dataLabelPositioners;
// get out if not enabled
if (!series.visible || (!options.enabled && !series._hasPointLabels)) {
@@ -32191,7 +42415,7 @@
}
// Reset all labels that have been shortened
- each(data, function(point) {
+ data.forEach(function (point) {
if (point.dataLabel && point.visible && point.dataLabel.shortened) {
point.dataLabel
.attr({
@@ -32208,21 +42432,51 @@
// run parent method
Series.prototype.drawDataLabels.apply(series);
- each(data, function(point) {
- if (point.dataLabel && point.visible) { // #407, #2510
+ data.forEach(function (point) {
+ if (point.dataLabel) {
+
+ if (point.visible) { // #407, #2510
- // Arrange points for detection collision
- halves[point.half].push(point);
+ // Arrange points for detection collision
+ halves[point.half].push(point);
- // Reset positions (#4905)
- point.dataLabel._pos = null;
+ // Reset positions (#4905)
+ point.dataLabel._pos = null;
+
+ // Avoid long labels squeezing the pie size too far down
+ if (
+ !defined(options.style.width) &&
+ !defined(
+ point.options.dataLabels &&
+ point.options.dataLabels.style &&
+ point.options.dataLabels.style.width
+ )
+ ) {
+ if (point.dataLabel.getBBox().width > maxWidth) {
+ point.dataLabel.css({
+ // Use a fraction of the maxWidth to avoid
+ // wrapping close to the end of the string.
+ width: maxWidth * 0.7
+ });
+ point.dataLabel.shortened = true;
+ }
+ }
+ } else {
+ point.dataLabel = point.dataLabel.destroy();
+ // Workaround to make pies destroy multiple datalabels
+ // correctly. This logic needs rewriting to support multiple
+ // datalabels fully.
+ if (point.dataLabels && point.dataLabels.length === 1) {
+ delete point.dataLabels;
+ }
+ }
}
});
/* Loop over the points in each half, starting from the top and bottom
* of the pie to detect overlapping labels.
*/
- each(halves, function(points, i) {
+ halves.forEach(function (points, i) {
var top,
bottom,
@@ -32230,8 +42484,8 @@
positions = [],
naturalY,
sideOverflow,
- positionsIndex, // Point index in positions array.
- size;
+ size,
+ distributionLength;
if (!length) {
return;
@@ -32239,7 +42493,7 @@
// Sort by angle
series.sortByAngle(points, i - 0.5);
- // Only do anti-collision when we have dataLabels outside the pie
+ // Only do anti-collision when we have dataLabels outside the pie
// and have connectors. (#856)
if (series.maxLabelDistance > 0) {
top = Math.max(
@@ -32250,11 +42504,11 @@
centerY + radius + series.maxLabelDistance,
chart.plotHeight
);
- each(points, function(point) {
+ points.forEach(function (point) {
// check if specific points' label is outside the pie
if (point.labelDistance > 0 && point.dataLabel) {
// point.top depends on point.labelDistance value
- // Used for calculation of y value in getX method
+ // Used for calculation of y value in getX method
point.top = Math.max(
0,
centerY - radius - point.labelDistance
@@ -32265,66 +42519,79 @@
);
size = point.dataLabel.getBBox().height || 21;
- // point.positionsIndex is needed for getting index of
- // parameter related to specific point inside positions
+ // point.positionsIndex is needed for getting index of
+ // parameter related to specific point inside positions
// array - not every point is in positions array.
- point.positionsIndex = positions.push({
- target: point.labelPos[1] - point.top + size / 2,
+ point.distributeBox = {
+ target: point.labelPosition.natural.y -
+ point.top + size / 2,
size: size,
rank: point.y
- }) - 1;
+ };
+ positions.push(point.distributeBox);
}
});
- H.distribute(positions, bottom + size - top);
+ distributionLength = bottom + size - top;
+ H.distribute(
+ positions,
+ distributionLength,
+ distributionLength / 5
+ );
}
// Now the used slots are sorted, fill them up sequentially
for (j = 0; j < length; j++) {
point = points[j];
- positionsIndex = point.positionsIndex;
- labelPos = point.labelPos;
+ // labelPos = point.labelPos;
+ labelPosition = point.labelPosition;
dataLabel = point.dataLabel;
visibility = point.visible === false ? 'hidden' : 'inherit';
- naturalY = labelPos[1];
+ naturalY = labelPosition.natural.y;
y = naturalY;
- if (positions && defined(positions[positionsIndex])) {
- if (positions[positionsIndex].pos === undefined) {
+ if (positions && defined(point.distributeBox)) {
+ if (point.distributeBox.pos === undefined) {
visibility = 'hidden';
} else {
- labelHeight = positions[positionsIndex].size;
- y = point.top + positions[positionsIndex].pos;
+ labelHeight = point.distributeBox.size;
+ // Find label's y position
+ y = dataLabelPositioners.radialDistributionY(point);
}
}
- // It is needed to delete point.positionIndex for
+ // It is needed to delete point.positionIndex for
// dynamically added points etc.
delete point.positionIndex;
- // get the x - use the natural x position for labels near the
- // top and bottom, to prevent the top and botton slice
- // connectors from touching each other on either side
+ // Find label's x position
+ // justify is undocumented in the API - preserve support for it
if (options.justify) {
- x = seriesCenter[0] +
- (i ? -1 : 1) * (radius + point.labelDistance);
+ x = dataLabelPositioners.justify(point, radius,
+ seriesCenter);
} else {
- x = series.getX(
- y < point.top + 2 || y > point.bottom - 2 ?
- naturalY :
- y,
- i,
- point
- );
+ switch (options.alignTo) {
+ case 'connectors':
+ x = dataLabelPositioners.alignToConnectors(points,
+ i, plotWidth, plotLeft);
+ break;
+ case 'plotEdges':
+ x = dataLabelPositioners.alignToPlotEdges(dataLabel,
+ i, plotWidth, plotLeft);
+ break;
+ default:
+ x = dataLabelPositioners.radialDistributionX(series,
+ point, y, naturalY);
+ }
}
-
// Record the placement and visibility
dataLabel._attr = {
visibility: visibility,
- align: labelPos[6]
+ align: labelPosition.alignment
};
+
dataLabel._pos = {
x: (
x +
@@ -32332,15 +42599,16 @@
({
left: connectorPadding,
right: -connectorPadding
- }[labelPos[6]] || 0)
+ }[labelPosition.alignment] || 0)
),
// 10 is for the baseline (label vs text)
y: y + options.y - 10
};
- labelPos.x = x;
- labelPos.y = y;
-
+ // labelPos.x = x;
+ // labelPos.y = y;
+ labelPosition.final.x = x;
+ labelPosition.final.y = y;
// Detect overflowing data labels
if (pick(options.crop, true)) {
@@ -32348,16 +42616,19 @@
sideOverflow = null;
// Overflow left
- if (x - dataLabelWidth < connectorPadding) {
+ if (
+ x - dataLabelWidth < connectorPadding &&
+ i === 1 // left half
+ ) {
sideOverflow = Math.round(
dataLabelWidth - x + connectorPadding
);
overflow[3] = Math.max(sideOverflow, overflow[3]);
- // Overflow right
+ // Overflow right
} else if (
- x + dataLabelWidth >
- plotWidth - connectorPadding
+ x + dataLabelWidth > plotWidth - connectorPadding &&
+ i === 0 // right half
) {
sideOverflow = Math.round(
x + dataLabelWidth - plotWidth + connectorPadding
@@ -32372,7 +42643,7 @@
overflow[0]
);
- // Overflow left
+ // Overflow left
} else if (y + labelHeight / 2 > plotHeight) {
overflow[2] = Math.max(
Math.round(y + labelHeight / 2 - plotHeight),
@@ -32396,7 +42667,7 @@
// Draw the connectors
if (connectorWidth) {
- each(this.points, function(point) {
+ this.points.forEach(function (point) {
var isNew;
connector = point.connector;
@@ -32414,23 +42685,30 @@
if (isNew) {
point.connector = connector = chart.renderer.path()
- .addClass('highcharts-data-label-connector ' +
- ' highcharts-color-' + point.colorIndex)
- .add(series.dataLabelsGroup);
-
-
- connector.attr({
- 'stroke-width': connectorWidth,
- 'stroke': (
- options.connectorColor ||
- point.color ||
- '#666666'
+ .addClass(
+ 'highcharts-data-label-connector ' +
+ ' highcharts-color-' + point.colorIndex +
+ (
+ point.className ?
+ ' ' + point.className :
+ ''
+ )
)
- });
+ .add(series.dataLabelsGroup);
+ if (!chart.styledMode) {
+ connector.attr({
+ 'stroke-width': connectorWidth,
+ 'stroke': (
+ options.connectorColor ||
+ point.color ||
+ '#666666'
+ )
+ });
+ }
}
connector[isNew ? 'attr' : 'animate']({
- d: series.connectorPath(point.labelPos)
+ d: point.getConnectorPath()
});
connector.attr('visibility', visibility);
@@ -32445,8 +42723,17 @@
/**
* Extendable method for getting the path of the connector between the data
* label and the pie slice.
+ *
+ * @private
+ * @function Highcharts.seriesTypes.pie#connectorPath
+ *
+ * @param {*} labelPos
+ *
+ * @return {Highcharts.PathObject}
*/
- seriesTypes.pie.prototype.connectorPath = function(labelPos) {
+ // TODO: depracated - remove it
+ /*
+ seriesTypes.pie.prototype.connectorPath = function (labelPos) {
var x = labelPos.x,
y = labelPos.y;
return pick(this.options.dataLabels.softConnector, true) ? [
@@ -32469,15 +42756,21 @@
labelPos[4], labelPos[5] // base
];
};
+ */
+
/**
* Perform the final placement of the data labels after we have verified
* that they fall within the plot area.
+ *
+ * @private
+ * @function Highcharts.seriesTypes.pie#placeDataLabels
*/
- seriesTypes.pie.prototype.placeDataLabels = function() {
- each(this.points, function(point) {
+ seriesTypes.pie.prototype.placeDataLabels = function () {
+ this.points.forEach(function (point) {
var dataLabel = point.dataLabel,
_pos;
+
if (dataLabel && point.visible) {
_pos = dataLabel._pos;
if (_pos) {
@@ -32487,9 +42780,14 @@
if (dataLabel.sideOverflow) {
dataLabel._attr.width =
dataLabel.getBBox().width - dataLabel.sideOverflow;
+
dataLabel.css({
width: dataLabel._attr.width + 'px',
- textOverflow: 'ellipsis'
+ textOverflow: (
+ (this.options.dataLabels.style || {})
+ .textOverflow ||
+ 'ellipsis'
+ )
});
dataLabel.shortened = true;
}
@@ -32498,9 +42796,7 @@
dataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos);
dataLabel.moved = true;
} else if (dataLabel) {
- dataLabel.attr({
- y: -9999
- });
+ dataLabel.attr({ y: -9999 });
}
}
}, this);
@@ -32512,8 +42808,15 @@
* Verify whether the data labels are allowed to draw, or we should run more
* translation and data label positioning to keep them inside the plot area.
* Returns true when data labels are ready to draw.
+ *
+ * @private
+ * @function Highcharts.seriesTypes.pie#verifyDataLabelOverflow
+ *
+ * @param {boolean} overflow
+ *
+ * @return {boolean}
*/
- seriesTypes.pie.prototype.verifyDataLabelOverflow = function(overflow) {
+ seriesTypes.pie.prototype.verifyDataLabelOverflow = function (overflow) {
var center = this.center,
options = this.options,
@@ -32571,8 +42874,8 @@
if (this.drawDataLabels) {
this.drawDataLabels();
}
- // Else, return true to indicate that the pie and its labels is
- // within the plot area
+ // Else, return true to indicate that the pie and its labels is
+ // within the plot area
} else {
ret = true;
}
@@ -32585,9 +42888,22 @@
/**
* Override the basic data label alignment by adjusting for the position of
- * the column
+ * the column.
+ *
+ * @private
+ * @function Highcharts.seriesTypes.column#alignDataLabel
+ *
+ * @param {Highcharts.Point} point
+ *
+ * @param {Highcharts.SVGElement} dataLabel
+ *
+ * @param {Highcharts.PlotSeriesDataLabelsOptions} options
+ *
+ * @param {Highcharts.BBoxObject} alignTo
+ *
+ * @param {boolean} isNew
*/
- seriesTypes.column.prototype.alignDataLabel = function(
+ seriesTypes.column.prototype.alignDataLabel = function (
point,
dataLabel,
options,
@@ -32644,7 +42960,8 @@
// When alignment is undefined (typically columns and bars), display the
// individual point below or above the point depending on the threshold
options.align = pick(
- options.align, !inverted || inside ? 'center' : below ? 'right' : 'left'
+ options.align,
+ !inverted || inside ? 'center' : below ? 'right' : 'left'
);
options.verticalAlign = pick(
options.verticalAlign,
@@ -32663,7 +42980,7 @@
// If label was justified and we have contrast, set it:
if (point.isLabelJustified && point.contrastColor) {
- point.dataLabel.css({
+ dataLabel.css({
color: point.contrastColor
});
}
@@ -32671,98 +42988,156 @@
}
}(Highcharts));
- (function(H) {
- /**
- * (c) 2009-2017 Torstein Honsi
- *
- * License: www.highcharts.com/license
- */
- /**
+ (function (H) {
+ /* *
* Highcharts module to hide overlapping data labels. This module is included in
* Highcharts.
+ *
+ * (c) 2009-2019 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
*/
+
+
+
var Chart = H.Chart,
- each = H.each,
+ isArray = H.isArray,
objectEach = H.objectEach,
pick = H.pick,
- addEvent = H.addEvent;
+ addEvent = H.addEvent,
+ fireEvent = H.fireEvent;
// Collect potensial overlapping data labels. Stack labels probably don't need
// to be considered because they are usually accompanied by data labels that lie
// inside the columns.
- addEvent(Chart.prototype, 'render', function collectAndHide() {
+ addEvent(Chart, 'render', function collectAndHide() {
var labels = [];
// Consider external label collectors
- each(this.labelCollectors || [], function(collector) {
+ (this.labelCollectors || []).forEach(function (collector) {
labels = labels.concat(collector());
});
- each(this.yAxis || [], function(yAxis) {
+ (this.yAxis || []).forEach(function (yAxis) {
if (
yAxis.options.stackLabels &&
!yAxis.options.stackLabels.allowOverlap
) {
- objectEach(yAxis.stacks, function(stack) {
- objectEach(stack, function(stackItem) {
+ objectEach(yAxis.stacks, function (stack) {
+ objectEach(stack, function (stackItem) {
labels.push(stackItem.label);
});
});
}
});
- each(this.series || [], function(series) {
- var dlOptions = series.options.dataLabels,
- // Range series have two collections
- collections = series.dataLabelCollections || ['dataLabel'];
+ (this.series || []).forEach(function (series) {
+ var dlOptions = series.options.dataLabels;
if (
- (dlOptions.enabled || series._hasPointLabels) &&
- !dlOptions.allowOverlap &&
- series.visible
+ series.visible &&
+ !(dlOptions.enabled === false && !series._hasPointLabels)
) { // #3866
- each(collections, function(coll) {
- each(series.points, function(point) {
- if (point[coll]) {
- point[coll].labelrank = pick(
+ series.points.forEach(function (point) {
+ if (point.visible) {
+ var dataLabels = (
+ isArray(point.dataLabels) ?
+ point.dataLabels :
+ (point.dataLabel ? [point.dataLabel] : [])
+ );
+
+ dataLabels.forEach(function (label) {
+ var options = label.options;
+
+ label.labelrank = pick(
+ options.labelrank,
point.labelrank,
point.shapeArgs && point.shapeArgs.height
); // #4118
- labels.push(point[coll]);
- }
- });
+
+ if (!options.allowOverlap) {
+ labels.push(label);
+ }
+ });
+ }
});
}
});
+
this.hideOverlappingLabels(labels);
});
/**
* Hide overlapping labels. Labels are moved and faded in and out on zoom to
* provide a smooth visual imression.
+ *
+ * @private
+ * @function Highcharts.Chart#hideOverlappingLabels
+ *
+ * @param {Array} labels
*/
- Chart.prototype.hideOverlappingLabels = function(labels) {
+ Chart.prototype.hideOverlappingLabels = function (labels) {
- var len = labels.length,
+ var chart = this,
+ len = labels.length,
+ ren = chart.renderer,
label,
i,
j,
label1,
label2,
isIntersecting,
- pos1,
- pos2,
- parent1,
- parent2,
- padding,
- bBox,
- intersectRect = function(x1, y1, w1, h1, x2, y2, w2, h2) {
+ box1,
+ box2,
+ intersectRect = function (x1, y1, w1, h1, x2, y2, w2, h2) {
return !(
x2 > x1 + w1 ||
x2 + w2 < x1 ||
y2 > y1 + h1 ||
y2 + h2 < y1
);
+ },
+
+ // Get the box with its position inside the chart, as opposed to getBBox
+ // that only reports the position relative to the parent.
+ getAbsoluteBox = function (label) {
+ var pos,
+ parent,
+ bBox,
+ // Substract the padding if no background or border (#4333)
+ padding = label.box ? 0 : (label.padding || 0),
+ lineHeightCorrection = 0;
+
+ if (
+ label &&
+ (!label.alignAttr || label.placed)
+ ) {
+ pos = label.alignAttr || {
+ x: label.attr('x'),
+ y: label.attr('y')
+ };
+ parent = label.parentGroup;
+
+ // Get width and height if pure text nodes (stack labels)
+ if (!label.width) {
+ bBox = label.getBBox();
+ label.width = bBox.width;
+ label.height = bBox.height;
+
+ // Labels positions are computed from top left corner, so
+ // we need to substract the text height from text nodes too.
+ lineHeightCorrection = ren
+ .fontMetrics(null, label.element).h;
+ }
+ return {
+ x: pos.x + (parent.translateX || 0) + padding,
+ y: pos.y + (parent.translateY || 0) + padding -
+ lineHeightCorrection,
+ width: label.width - 2 * padding,
+ height: label.height - 2 * padding
+ };
+
+ }
};
for (i = 0; i < len; i++) {
@@ -32773,86 +43148,87 @@
label.oldOpacity = label.opacity;
label.newOpacity = 1;
- // Get width and height if pure text nodes (stack labels)
- if (!label.width) {
- bBox = label.getBBox();
- label.width = bBox.width;
- label.height = bBox.height;
- }
+ label.absoluteBox = getAbsoluteBox(label);
+
}
}
// Prevent a situation in a gradually rising slope, that each label will
// hide the previous one because the previous one always has lower rank.
- labels.sort(function(a, b) {
+ labels.sort(function (a, b) {
return (b.labelrank || 0) - (a.labelrank || 0);
});
// Detect overlapping labels
for (i = 0; i < len; i++) {
label1 = labels[i];
+ box1 = label1 && label1.absoluteBox;
for (j = i + 1; j < len; ++j) {
label2 = labels[j];
+ box2 = label2 && label2.absoluteBox;
+
if (
- label1 && label2 &&
+ box1 &&
+ box2 &&
label1 !== label2 && // #6465, polar chart with connectEnds
- label1.placed && label2.placed &&
- label1.newOpacity !== 0 && label2.newOpacity !== 0
+ label1.newOpacity !== 0 &&
+ label2.newOpacity !== 0
) {
- pos1 = label1.alignAttr;
- pos2 = label2.alignAttr;
- // Different panes have different positions
- parent1 = label1.parentGroup;
- parent2 = label2.parentGroup;
- // Substract the padding if no background or border (#4333)
- padding = 2 * (label1.box ? 0 : (label1.padding || 0));
isIntersecting = intersectRect(
- pos1.x + parent1.translateX,
- pos1.y + parent1.translateY,
- label1.width - padding,
- label1.height - padding,
- pos2.x + parent2.translateX,
- pos2.y + parent2.translateY,
- label2.width - padding,
- label2.height - padding
+ box1.x,
+ box1.y,
+ box1.width,
+ box1.height,
+ box2.x,
+ box2.y,
+ box2.width,
+ box2.height
);
+
if (isIntersecting) {
(label1.labelrank < label2.labelrank ? label1 : label2)
- .newOpacity = 0;
+ .newOpacity = 0;
}
}
}
}
// Hide or show
- each(labels, function(label) {
+ labels.forEach(function (label) {
var complete,
newOpacity;
if (label) {
newOpacity = label.newOpacity;
- if (label.oldOpacity !== newOpacity && label.placed) {
+ if (label.oldOpacity !== newOpacity) {
// Make sure the label is completely hidden to avoid catching
// clicks (#4362)
- if (newOpacity) {
- label.show(true);
- } else {
- complete = function() {
- label.hide();
- };
- }
+ if (label.alignAttr && label.placed) { // data labels
+ if (newOpacity) {
+ label.show(true);
+ } else {
+ complete = function () {
+ label.hide();
+ };
+ }
- // Animate or set the opacity
- label.alignAttr.opacity = newOpacity;
- label[label.isOld ? 'animate' : 'attr'](
- label.alignAttr,
- null,
- complete
- );
+ // Animate or set the opacity
+ label.alignAttr.opacity = newOpacity;
+ label[label.isOld ? 'animate' : 'attr'](
+ label.alignAttr,
+ null,
+ complete
+ );
+ fireEvent(chart, 'afterHideOverlappingLabels');
+ } else { // other labels, tick labels
+ label.attr({
+ opacity: newOpacity
+ });
+ }
}
label.isOld = true;
@@ -32861,23 +43237,24 @@
};
}(Highcharts));
- (function(H) {
+ (function (H) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
+
var addEvent = H.addEvent,
Chart = H.Chart,
createElement = H.createElement,
css = H.css,
defaultOptions = H.defaultOptions,
defaultPlotOptions = H.defaultPlotOptions,
- each = H.each,
extend = H.extend,
fireEvent = H.fireEvent,
hasTouch = H.hasTouch,
- inArray = H.inArray,
isObject = H.isObject,
Legend = H.Legend,
merge = H.merge,
@@ -32890,18 +43267,27 @@
/**
* TrackerMixin for points and graphs.
+ *
+ * @private
+ * @mixin Highcharts.TrackerMixin
*/
TrackerMixin = H.TrackerMixin = {
/**
* Draw the tracker for a point.
+ *
+ * @private
+ * @function Highcharts.TrackerMixin.drawTrackerPoint
+ *
+ * @fires Highcharts.Series#event:afterDrawTracker
*/
- drawTrackerPoint: function() {
+ drawTrackerPoint: function () {
var series = this,
chart = series.chart,
pointer = chart.pointer,
- onMouseOver = function(e) {
+ onMouseOver = function (e) {
var point = pointer.getPointFromEvent(e);
+
// undefined on graph in scatterchart
if (point !== undefined) {
pointer.isDirectTouch = true;
@@ -32910,7 +43296,7 @@
};
// Add reference to the point
- each(series.points, function(point) {
+ series.points.forEach(function (point) {
if (point.graphic) {
point.graphic.element.point = point;
}
@@ -32925,31 +43311,29 @@
// Add the event listeners, we need to do this only once
if (!series._hasTracking) {
- each(series.trackerGroups, function(key) {
+ series.trackerGroups.forEach(function (key) {
if (series[key]) { // we don't always have dataLabelsGroup
series[key]
.addClass('highcharts-tracker')
.on('mouseover', onMouseOver)
- .on('mouseout', function(e) {
+ .on('mouseout', function (e) {
pointer.onTrackerMouseOut(e);
});
if (hasTouch) {
series[key].on('touchstart', onMouseOver);
}
-
- if (series.options.cursor) {
+ if (!chart.styledMode && series.options.cursor) {
series[key]
.css(css)
- .css({
- cursor: series.options.cursor
- });
+ .css({ cursor: series.options.cursor });
}
-
}
});
series._hasTracking = true;
}
+
+ fireEvent(this, 'afterDrawTracker');
},
/**
@@ -32957,12 +43341,19 @@
* track mouse events on the graph or points. For the line type charts
* the tracker uses the same graphPath, but with a greater stroke width
* for better control.
+ *
+ * @private
+ * @function Highcharts.TrackerMixin.drawTrackerGraph
+ *
+ * @fires Highcharts.Series#event:afterDrawTracker
*/
- drawTrackerGraph: function() {
+ drawTrackerGraph: function () {
var series = this,
options = series.options,
trackByArea = options.trackByArea,
- trackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath),
+ trackerPath = [].concat(
+ trackByArea ? series.areaPath : series.graphPath
+ ),
trackerPathLength = trackerPath.length,
chart = series.chart,
pointer = chart.pointer,
@@ -32970,13 +43361,14 @@
snap = chart.options.tooltip.snap,
tracker = series.tracker,
i,
- onMouseOver = function() {
+ onMouseOver = function () {
if (chart.hoverSeries !== series) {
series.onMouseOver();
}
},
/*
- * Empirical lowest possible opacities for TRACKER_FILL for an element to stay invisible but clickable
+ * Empirical lowest possible opacities for TRACKER_FILL for an
+ * element to stay invisible but clickable
* IE6: 0.002
* IE7: 0.002
* IE8: 0.002
@@ -32995,115 +43387,173 @@
i = trackerPathLength + 1;
while (i--) {
if (trackerPath[i] === 'M') { // extend left side
- trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], 'L');
+ trackerPath.splice(
+ i + 1, 0,
+ trackerPath[i + 1] - snap,
+ trackerPath[i + 2],
+ 'L'
+ );
}
- if ((i && trackerPath[i] === 'M') || i === trackerPathLength) { // extend right side
- trackerPath.splice(i, 0, 'L', trackerPath[i - 2] + snap, trackerPath[i - 1]);
+ if (
+ (i && trackerPath[i] === 'M') ||
+ i === trackerPathLength
+ ) { // extend right side
+ trackerPath.splice(
+ i,
+ 0,
+ 'L',
+ trackerPath[i - 2] + snap,
+ trackerPath[i - 1]
+ );
}
}
}
// draw the tracker
if (tracker) {
- tracker.attr({
- d: trackerPath
- });
+ tracker.attr({ d: trackerPath });
} else if (series.graph) { // create
series.tracker = renderer.path(trackerPath)
.attr({
- 'stroke-linejoin': 'round', // #1225
visibility: series.visible ? 'visible' : 'hidden',
- stroke: TRACKER_FILL,
- fill: trackByArea ? TRACKER_FILL : 'none',
- 'stroke-width': series.graph.strokeWidth() + (trackByArea ? 0 : 2 * snap),
zIndex: 2
})
+ .addClass(
+ trackByArea ?
+ 'highcharts-tracker-area' :
+ 'highcharts-tracker-line'
+ )
.add(series.group);
- // The tracker is added to the series group, which is clipped, but is covered
- // by the marker group. So the marker group also needs to capture events.
- each([series.tracker, series.markerGroup], function(tracker) {
+ if (!chart.styledMode) {
+ series.tracker.attr({
+ 'stroke-linejoin': 'round', // #1225
+ stroke: TRACKER_FILL,
+ fill: trackByArea ? TRACKER_FILL : 'none',
+ 'stroke-width': series.graph.strokeWidth() +
+ (trackByArea ? 0 : 2 * snap)
+ });
+ }
+
+ // The tracker is added to the series group, which is clipped, but
+ // is covered by the marker group. So the marker group also needs to
+ // capture events.
+ [series.tracker, series.markerGroup].forEach(function (tracker) {
tracker.addClass('highcharts-tracker')
.on('mouseover', onMouseOver)
- .on('mouseout', function(e) {
+ .on('mouseout', function (e) {
pointer.onTrackerMouseOut(e);
});
-
- if (options.cursor) {
- tracker.css({
- cursor: options.cursor
- });
+ if (options.cursor && !chart.styledMode) {
+ tracker.css({ cursor: options.cursor });
}
-
if (hasTouch) {
tracker.on('touchstart', onMouseOver);
}
});
}
+ fireEvent(this, 'afterDrawTracker');
}
};
/* End TrackerMixin */
- /**
+ /*
* Add tracking event listener to the series group, so the point graphics
* themselves act as trackers
*/
if (seriesTypes.column) {
+ /**
+ * @private
+ * @borrows Highcharts.TrackerMixin.drawTrackerPoint as Highcharts.seriesTypes.column#drawTracker
+ */
seriesTypes.column.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
}
if (seriesTypes.pie) {
+ /**
+ * @private
+ * @borrows Highcharts.TrackerMixin.drawTrackerPoint as Highcharts.seriesTypes.pie#drawTracker
+ */
seriesTypes.pie.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
}
if (seriesTypes.scatter) {
+ /**
+ * @private
+ * @borrows Highcharts.TrackerMixin.drawTrackerPoint as Highcharts.seriesTypes.scatter#drawTracker
+ */
seriesTypes.scatter.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
}
- /*
- * Extend Legend for item events
- */
+ // Extend Legend for item events.
extend(Legend.prototype, {
- setItemEvents: function(item, legendItem, useHTML) {
+ /**
+ * @private
+ * @function Highcharts.Legend#setItemEvents
+ *
+ * @param {Highcharts.Point|Highcharts.Series} item
+ *
+ * @param {Highcharts.SVGElement} legendItem
+ *
+ * @param {boolean} [useHTML=false]
+ *
+ * @fires Highcharts.Point#event:legendItemClick
+ * @fires Highcharts.Series#event:legendItemClick
+ */
+ setItemEvents: function (item, legendItem, useHTML) {
var legend = this,
boxWrapper = legend.chart.renderer.boxWrapper,
- activeClass = 'highcharts-legend-' + (item.series ? 'point' : 'series') + '-active';
-
- // Set the events on the item group, or in case of useHTML, the item itself (#1249)
- (useHTML ? legendItem : item.legendGroup).on('mouseover', function() {
- item.setState('hover');
+ activeClass = 'highcharts-legend-' +
+ (item instanceof Point ? 'point' : 'series') + '-active',
+ styledMode = legend.chart.styledMode;
- // A CSS class to dim or hide other than the hovered series
- boxWrapper.addClass(activeClass);
+ // Set the events on the item group, or in case of useHTML, the item
+ // itself (#1249)
+ (useHTML ? legendItem : item.legendGroup).on('mouseover', function () {
+ item.setState('hover');
+ // A CSS class to dim or hide other than the hovered series
+ boxWrapper.addClass(activeClass);
+ if (!styledMode) {
legendItem.css(legend.options.itemHoverStyle);
-
- })
- .on('mouseout', function() {
-
- legendItem.css(merge(item.visible ? legend.itemStyle : legend.itemHiddenStyle));
-
+ }
+ })
+ .on('mouseout', function () {
+ if (!legend.styledMode) {
+ legendItem.css(
+ merge(
+ item.visible ?
+ legend.itemStyle :
+ legend.itemHiddenStyle
+ )
+ );
+ }
// A CSS class to dim or hide other than the hovered series
boxWrapper.removeClass(activeClass);
item.setState();
})
- .on('click', function(event) {
+ .on('click', function (event) {
var strLegendItemClick = 'legendItemClick',
- fnLegendItemClick = function() {
+ fnLegendItemClick = function () {
if (item.setVisible) {
item.setVisible();
}
};
+ // A CSS class to dim or hide other than the hovered series.
+ // Event handling in iOS causes the activeClass to be added
+ // prior to click in some cases (#7418).
+ boxWrapper.removeClass(activeClass);
+
// Pass over the click/touch event. #4.
event = {
browserEvent: event
@@ -33111,31 +43561,48 @@
// click the name or symbol
if (item.firePointEvent) { // point
- item.firePointEvent(strLegendItemClick, event, fnLegendItemClick);
+ item.firePointEvent(
+ strLegendItemClick,
+ event,
+ fnLegendItemClick
+ );
} else {
- fireEvent(item, strLegendItemClick, event, fnLegendItemClick);
+ fireEvent(
+ item, strLegendItemClick, event, fnLegendItemClick
+ );
}
});
},
- createCheckboxForItem: function(item) {
+ /**
+ * @private
+ * @function Highcharts.Legend#createCheckboxForItem
+ *
+ * @param {Highcharts.Point|Highcharts.Series} item
+ *
+ * @fires Highcharts.Series#event:checkboxClick
+ */
+ createCheckboxForItem: function (item) {
var legend = this;
item.checkbox = createElement('input', {
type: 'checkbox',
+ className: 'highcharts-legend-checkbox',
checked: item.selected,
defaultChecked: item.selected // required by IE7
}, legend.options.itemCheckboxStyle, legend.chart.container);
- addEvent(item.checkbox, 'click', function(event) {
+ addEvent(item.checkbox, 'click', function (event) {
var target = event.target;
+
fireEvent(
item.series || item,
- 'checkboxClick', { // #3712
+ 'checkboxClick',
+ { // #3712
checked: target.checked,
item: item
},
- function() {
+ function () {
item.select();
}
);
@@ -33143,24 +43610,21 @@
}
});
-
-
- // Add pointer cursor to legend itemstyle in defaultOptions
- defaultOptions.legend.itemStyle.cursor = 'pointer';
-
-
-
/*
* Extend the Chart object with interaction
*/
extend(Chart.prototype, /** @lends Chart.prototype */ {
+
/**
* Display the zoom button.
*
* @private
+ * @function Highcharts.Chart#showResetZoom
+ *
+ * @fires Highcharts.Chart#event:beforeShowResetZoom
*/
- showResetZoom: function() {
+ showResetZoom: function () {
var chart = this,
lang = defaultOptions.lang,
btnOptions = chart.options.chart.resetZoomButton,
@@ -33172,38 +43636,47 @@
chart.zoomOut();
}
- this.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, zoomOut, theme, states && states.hover)
- .attr({
- align: btnOptions.position.align,
- title: lang.resetZoomTitle
- })
- .addClass('highcharts-reset-zoom')
- .add()
- .align(btnOptions.position, false, alignTo);
+ fireEvent(this, 'beforeShowResetZoom', null, function () {
+ chart.resetZoomButton = chart.renderer.button(
+ lang.resetZoom,
+ null,
+ null,
+ zoomOut,
+ theme,
+ states && states.hover
+ )
+ .attr({
+ align: btnOptions.position.align,
+ title: lang.resetZoomTitle
+ })
+ .addClass('highcharts-reset-zoom')
+ .add()
+ .align(btnOptions.position, false, alignTo);
+ });
},
/**
- * Zoom out to 1:1.
+ * Zoom the chart out after a user has zoomed in. See also
+ * [Axis.setExtremes](/class-reference/Highcharts.Axis#setExtremes).
*
- * @private
+ * @function Highcharts.Chart#zoomOut
+ *
+ * @fires Highcharts.Chart#event:selection
*/
- zoomOut: function() {
- var chart = this;
- fireEvent(chart, 'selection', {
- resetSelection: true
- }, function() {
- chart.zoom();
- });
+ zoomOut: function () {
+ fireEvent(this, 'selection', { resetSelection: true }, this.zoom);
},
/**
* Zoom into a given portion of the chart given by axis coordinates.
- * @param {Object} event
*
* @private
+ * @function Highcharts.Chart#zoom
+ *
+ * @param {Highcharts.SelectEventObject} event
*/
- zoom: function(event) {
+ zoom: function (event) {
var chart = this,
hasZoomed,
pointer = chart.pointer,
@@ -33212,13 +43685,13 @@
// If zoom is called with no arguments, reset the axes
if (!event || event.resetSelection) {
- each(chart.axes, function(axis) {
+ chart.axes.forEach(function (axis) {
hasZoomed = axis.zoom();
});
pointer.initiated = false; // #6804
} else { // else, zoom in on all axes
- each(event.xAxis.concat(event.yAxis), function(axisData) {
+ event.xAxis.concat(event.yAxis).forEach(function (axisData) {
var axis = axisData.axis,
isXAxis = axis.isXAxis;
@@ -33244,7 +43717,11 @@
// Redraw
if (hasZoomed) {
chart.redraw(
- pick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100) // animation
+ pick(
+ chart.options.chart.animation,
+ event && event.animation,
+ chart.pointCount < 100
+ )
);
}
},
@@ -33255,105 +43732,118 @@
* compared to the first chartX position in the dragging operation.
*
* @private
+ * @function Highcharts.Chart#pan
+ *
+ * @param {Highcharts.PointerEventObject} e
+ *
+ * @param {string} panning
*/
- pan: function(e, panning) {
+ pan: function (e, panning) {
var chart = this,
hoverPoints = chart.hoverPoints,
doRedraw;
- // remove active points for shared tooltip
- if (hoverPoints) {
- each(hoverPoints, function(point) {
- point.setState();
- });
- }
-
- each(panning === 'xy' ? [1, 0] : [1], function(isX) { // xy is used in maps
- var axis = chart[isX ? 'xAxis' : 'yAxis'][0],
- horiz = axis.horiz,
- mousePos = e[horiz ? 'chartX' : 'chartY'],
- mouseDown = horiz ? 'mouseDownX' : 'mouseDownY',
- startPos = chart[mouseDown],
- halfPointRange = (axis.pointRange || 0) / 2,
- extremes = axis.getExtremes(),
- panMin = axis.toValue(startPos - mousePos, true) +
- halfPointRange,
- panMax = axis.toValue(startPos + axis.len - mousePos, true) -
- halfPointRange,
- flipped = panMax < panMin,
- newMin = flipped ? panMax : panMin,
- newMax = flipped ? panMin : panMax,
- paddedMin = Math.min(
- extremes.dataMin,
- axis.toValue(
- axis.toPixels(extremes.min) - axis.minPixelPadding
- )
- ),
- paddedMax = Math.max(
- extremes.dataMax,
- axis.toValue(
- axis.toPixels(extremes.max) + axis.minPixelPadding
- )
- ),
- spill;
+ fireEvent(this, 'pan', { originalEvent: e }, function () {
- // If the new range spills over, either to the min or max, adjust
- // the new range.
- spill = paddedMin - newMin;
- if (spill > 0) {
- newMax += spill;
- newMin = paddedMin;
- }
- spill = newMax - paddedMax;
- if (spill > 0) {
- newMax = paddedMax;
- newMin -= spill;
+ // remove active points for shared tooltip
+ if (hoverPoints) {
+ hoverPoints.forEach(function (point) {
+ point.setState();
+ });
}
- // Set new extremes if they are actually new
- if (axis.series.length && newMin !== extremes.min && newMax !== extremes.max) {
- axis.setExtremes(
- newMin,
- newMax,
- false,
- false, {
- trigger: 'pan'
- }
- );
- doRedraw = true;
- }
+ // xy is used in maps
+ (panning === 'xy' ? [1, 0] : [1]).forEach(function (isX) {
+ var axis = chart[isX ? 'xAxis' : 'yAxis'][0],
+ horiz = axis.horiz,
+ mousePos = e[horiz ? 'chartX' : 'chartY'],
+ mouseDown = horiz ? 'mouseDownX' : 'mouseDownY',
+ startPos = chart[mouseDown],
+ halfPointRange = (axis.pointRange || 0) / 2,
+ pointRangeDirection =
+ (axis.reversed && !chart.inverted) ||
+ (!axis.reversed && chart.inverted) ?
+ -1 :
+ 1,
+ extremes = axis.getExtremes(),
+ panMin = axis.toValue(startPos - mousePos, true) +
+ halfPointRange * pointRangeDirection,
+ panMax =
+ axis.toValue(
+ startPos + axis.len - mousePos, true
+ ) -
+ halfPointRange * pointRangeDirection,
+ flipped = panMax < panMin,
+ newMin = flipped ? panMax : panMin,
+ newMax = flipped ? panMin : panMax,
+ paddedMin = Math.min(
+ extremes.dataMin,
+ halfPointRange ?
+ extremes.min :
+ axis.toValue(
+ axis.toPixels(extremes.min) -
+ axis.minPixelPadding
+ )
+ ),
+ paddedMax = Math.max(
+ extremes.dataMax,
+ halfPointRange ?
+ extremes.max :
+ axis.toValue(
+ axis.toPixels(extremes.max) +
+ axis.minPixelPadding
+ )
+ ),
+ spill;
+
+ // If the new range spills over, either to the min or max,
+ // adjust the new range.
+ spill = paddedMin - newMin;
+ if (spill > 0) {
+ newMax += spill;
+ newMin = paddedMin;
+ }
+ spill = newMax - paddedMax;
+ if (spill > 0) {
+ newMax = paddedMax;
+ newMin -= spill;
+ }
- chart[mouseDown] = mousePos; // set new reference for next run
- });
+ // Set new extremes if they are actually new
+ if (
+ axis.series.length &&
+ newMin !== extremes.min &&
+ newMax !== extremes.max
+ ) {
+ axis.setExtremes(
+ newMin,
+ newMax,
+ false,
+ false,
+ { trigger: 'pan' }
+ );
+ doRedraw = true;
+ }
- if (doRedraw) {
- chart.redraw(false);
- }
- css(chart.container, {
- cursor: 'move'
+ chart[mouseDown] = mousePos; // set new reference for next run
+ });
+
+ if (doRedraw) {
+ chart.redraw(false);
+ }
+ css(chart.container, { cursor: 'move' });
});
}
});
- /*
- * Extend the Point object with interaction
- */
+ // Extend the Point object with interaction
extend(Point.prototype, /** @lends Highcharts.Point.prototype */ {
+
/**
* Toggle the selection status of a point.
- * @param {Boolean} [selected]
- * When `true`, the point is selected. When `false`, the point is
- * unselected. When `null` or `undefined`, the selection state is
- * toggled.
- * @param {Boolean} [accumulate=false]
- * When `true`, the selection is added to other selected points.
- * When `false`, other selected points are deselected. Internally in
- * Highcharts, when {@link http://api.highcharts.com/highcharts/plotOptions.series.allowPointSelect|allowPointSelect}
- * is `true`, selected points are accumulated on Control, Shift or
- * Cmd clicking the point.
- *
- * @see Highcharts.Chart#getSelectedPoints
+ *
+ * @see Highcharts.Chart#getSelectedPoints
*
* @sample highcharts/members/point-select/
* Select a point from a button
@@ -33361,8 +43851,26 @@
* Select a range of points through a drag selection
* @sample maps/series/data-id/
* Select a point in Highmaps
+ *
+ * @function Highcharts.Point#select
+ *
+ * @param {boolean} [selected]
+ * When `true`, the point is selected. When `false`, the point is
+ * unselected. When `null` or `undefined`, the selection state is
+ * toggled.
+ *
+ * @param {boolean} [accumulate=false]
+ * When `true`, the selection is added to other selected points.
+ * When `false`, other selected points are deselected. Internally in
+ * Highcharts, when
+ * [allowPointSelect](http://api.highcharts.com/highcharts/plotOptions.series.allowPointSelect)
+ * is `true`, selected points are accumulated on Control, Shift or
+ * Cmd clicking the point.
+ *
+ * @fires Highcharts.Point#event:select
+ * @fires Highcharts.Point#event:unselect
*/
- select: function(selected, accumulate) {
+ select: function (selected, accumulate) {
var point = this,
series = point.series,
chart = series.chart;
@@ -33370,48 +43878,59 @@
selected = pick(selected, !point.selected);
// fire the event with the default handler
- point.firePointEvent(selected ? 'select' : 'unselect', {
- accumulate: accumulate
- }, function() {
+ point.firePointEvent(
+ selected ? 'select' : 'unselect',
+ { accumulate: accumulate },
+ function () {
- /**
- * Whether the point is selected or not.
- * @see Point#select
- * @see Chart#getSelectedPoints
- * @memberof Point
- * @name selected
- * @type {Boolean}
- */
- point.selected = point.options.selected = selected;
- series.options.data[inArray(point, series.data)] = point.options;
-
- point.setState(selected && 'select');
-
- // unselect all other points unless Ctrl or Cmd + click
- if (!accumulate) {
- each(chart.getSelectedPoints(), function(loopPoint) {
- if (loopPoint.selected && loopPoint !== point) {
- loopPoint.selected = loopPoint.options.selected = false;
- series.options.data[inArray(loopPoint, series.data)] = loopPoint.options;
- loopPoint.setState('');
- loopPoint.firePointEvent('unselect');
- }
- });
+ /**
+ * Whether the point is selected or not.
+ *
+ * @see Point#select
+ * @see Chart#getSelectedPoints
+ *
+ * @name Highcharts.Point#selected
+ * @type {boolean}
+ */
+ point.selected = point.options.selected = selected;
+ series.options.data[series.data.indexOf(point)] =
+ point.options;
+
+ point.setState(selected && 'select');
+
+ // unselect all other points unless Ctrl or Cmd + click
+ if (!accumulate) {
+ chart.getSelectedPoints().forEach(function (loopPoint) {
+ if (loopPoint.selected && loopPoint !== point) {
+ loopPoint.selected = loopPoint.options.selected =
+ false;
+ series.options.data[
+ series.data.indexOf(loopPoint)
+ ] = loopPoint.options;
+ loopPoint.setState('');
+ loopPoint.firePointEvent('unselect');
+ }
+ });
+ }
}
- });
+ );
},
/**
* Runs on mouse over the point. Called internally from mouse and touch
* events.
- *
- * @param {Object} e The event arguments
+ *
+ * @function Highcharts.Point#onMouseOver
+ *
+ * @param {Highcharts.PointerEventObject} e
+ * The event arguments.
*/
- onMouseOver: function(e) {
+ onMouseOver: function (e) {
var point = this,
series = point.series,
chart = series.chart,
pointer = chart.pointer;
+
e = e ?
pointer.normalize(e) :
// In cases where onMouseOver is called directly without an event
@@ -33422,12 +43941,17 @@
/**
* Runs on mouse out from the point. Called internally from mouse and touch
* events.
+ *
+ * @function Highcharts.Point#onMouseOut
+ *
+ * @fires Highcharts.Point#event:mouseOut
*/
- onMouseOut: function() {
+ onMouseOut: function () {
var point = this,
chart = point.series.chart;
+
point.firePointEvent('mouseOut');
- each(chart.hoverPoints || [], function(p) {
+ (chart.hoverPoints || []).forEach(function (p) {
p.setState();
});
chart.hoverPoints = chart.hoverPoint = null;
@@ -33438,8 +43962,9 @@
* demand, to save processing time on hovering.
*
* @private
+ * @function Highcharts.Point#importEvents
*/
- importEvents: function() {
+ importEvents: function () {
if (!this.hasImportedEvents) {
var point = this,
options = merge(point.series.options.point, point.options),
@@ -33447,7 +43972,7 @@
point.events = events;
- H.objectEach(events, function(event, eventType) {
+ H.objectEach(events, function (event, eventType) {
addEvent(point, eventType, event);
});
this.hasImportedEvents = true;
@@ -33457,21 +43982,32 @@
/**
* Set the point's state.
- * @param {String} [state]
- * The new state, can be one of `''` (an empty string), `hover` or
- * `select`.
+ *
+ * @function Highcharts.Point#setState
+ *
+ * @param {string} [state]
+ * The new state, can be one of `''` (an empty string), `hover` or
+ * `select`.
+ *
+ * @param {boolean} [move]
+ * State for animation.
+ *
+ * @fires Highcharts.Point#event:afterSetState
*/
- setState: function(state, move) {
+ setState: function (state, move) {
var point = this,
plotX = Math.floor(point.plotX), // #4586
plotY = point.plotY,
series = point.series,
- stateOptions = series.options.states[state] || {},
+ stateOptions = series.options.states[state || 'normal'] || {},
markerOptions = defaultPlotOptions[series.type].marker &&
- series.options.marker,
+ series.options.marker,
normalDisabled = markerOptions && markerOptions.enabled === false,
- markerStateOptions = (markerOptions && markerOptions.states &&
- markerOptions.states[state]) || {},
+ markerStateOptions = (
+ markerOptions &&
+ markerOptions.states &&
+ markerOptions.states[state || 'normal']
+ ) || {},
stateDisabled = markerStateOptions.enabled === false,
stateMarkerGraphic = series.stateMarkerGraphic,
pointMarker = point.marker || {},
@@ -33526,15 +44062,15 @@
point.graphic.addClass('highcharts-point-' + state);
}
-
- point.graphic.animate(
- series.pointAttribs(point, state),
- pick(
- chart.options.chart.animation,
- stateOptions.animation
- )
- );
-
+ if (!chart.styledMode) {
+ point.graphic.animate(
+ series.pointAttribs(point, state),
+ pick(
+ chart.options.chart.animation,
+ stateOptions.animation
+ )
+ );
+ }
if (markerAttribs) {
point.graphic.animate(
@@ -33552,32 +44088,36 @@
stateMarkerGraphic.hide();
}
} else {
- // if a graphic is not applied to each point in the normal state, create a shared
- // graphic for the hover state
+ // if a graphic is not applied to each point in the normal state,
+ // create a shared graphic for the hover state
if (state && markerStateOptions) {
newSymbol = pointMarker.symbol || series.symbol;
- // If the point has another symbol than the previous one, throw away the
- // state marker graphic and force a new one (#1459)
- if (stateMarkerGraphic && stateMarkerGraphic.currentSymbol !== newSymbol) {
+ // If the point has another symbol than the previous one, throw
+ // away the state marker graphic and force a new one (#1459)
+ if (
+ stateMarkerGraphic &&
+ stateMarkerGraphic.currentSymbol !== newSymbol
+ ) {
stateMarkerGraphic = stateMarkerGraphic.destroy();
}
// Add a new state marker graphic
if (!stateMarkerGraphic) {
if (newSymbol) {
- series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(
+ series.stateMarkerGraphic = stateMarkerGraphic =
+ chart.renderer.symbol(
newSymbol,
markerAttribs.x,
markerAttribs.y,
markerAttribs.width,
markerAttribs.height
)
- .add(series.markerGroup);
+ .add(series.markerGroup);
stateMarkerGraphic.currentSymbol = newSymbol;
}
- // Move the existing graphic
+ // Move the existing graphic
} else {
stateMarkerGraphic[move ? 'animate' : 'attr']({ // #1054
x: markerAttribs.x,
@@ -33585,14 +44125,17 @@
});
}
- if (stateMarkerGraphic) {
+ if (!chart.styledMode && stateMarkerGraphic) {
stateMarkerGraphic.attr(series.pointAttribs(point, state));
}
-
}
if (stateMarkerGraphic) {
- stateMarkerGraphic[state && chart.isInsidePlot(plotX, plotY, chart.inverted) ? 'show' : 'hide'](); // #2450
+ stateMarkerGraphic[
+ state && chart.isInsidePlot(plotX, plotY, chart.inverted) ?
+ 'show' :
+ 'hide'
+ ](); // #2450
stateMarkerGraphic.element.point = point; // #4310
}
}
@@ -33605,41 +44148,53 @@
// #5818, #5903, #6705
.add((point.graphic || stateMarkerGraphic).parentGroup);
}
- halo[move ? 'animate' : 'attr']({
+ halo.show()[move ? 'animate' : 'attr']({
d: point.haloPath(haloOptions.size)
});
halo.attr({
'class': 'highcharts-halo highcharts-color-' +
- pick(point.colorIndex, series.colorIndex)
+ pick(point.colorIndex, series.colorIndex) +
+ (point.className ? ' ' + point.className : ''),
+ 'zIndex': -1 // #4929, #8276
});
halo.point = point; // #6055
-
- halo.attr(extend({
- 'fill': point.color || series.color,
- 'fill-opacity': haloOptions.opacity,
- 'zIndex': -1 // #4929, IE8 added halo above everything
- }, haloOptions.attributes));
-
+ if (!chart.styledMode) {
+ halo.attr(extend({
+ 'fill': point.color || series.color,
+ 'fill-opacity': haloOptions.opacity
+ }, haloOptions.attributes));
+ }
} else if (halo && halo.point && halo.point.haloPath) {
// Animate back to 0 on the current halo point (#6055)
- halo.animate({
- d: halo.point.haloPath(0)
- });
+ halo.animate(
+ { d: halo.point.haloPath(0) },
+ null,
+ // Hide after unhovering. The `complete` callback runs in the
+ // halo's context (#7681).
+ halo.hide
+ );
}
point.state = state;
+
+ fireEvent(point, 'afterSetState');
},
/**
* Get the path definition for the halo, which is usually a shadow-like
* circle around the currently hovered point.
- * @param {Number} size
- * The radius of the circular halo.
- * @return {Array} The path definition
+ *
+ * @function Highcharts.Point#haloPath
+ *
+ * @param {number} size
+ * The radius of the circular halo.
+ *
+ * @return {Highcharts.SVGPathArray}
+ * The path definition.
*/
- haloPath: function(size) {
+ haloPath: function (size) {
var series = this.series,
chart = series.chart;
@@ -33652,15 +44207,17 @@
}
});
- /*
- * Extend the Series object with interaction
- */
-
+ // Extend the Series object with interaction
extend(Series.prototype, /** @lends Highcharts.Series.prototype */ {
+
/**
* Runs on mouse over the series graphical items.
+ *
+ * @function Highcharts.Series#onMouseOver
+ *
+ * @fires Highcharts.Series#event:mouseOver
*/
- onMouseOver: function() {
+ onMouseOver: function () {
var series = this,
chart = series.chart,
hoverSeries = chart.hoverSeries;
@@ -33683,8 +44240,12 @@
/**
* Runs on mouse out of the series graphical items.
+ *
+ * @function Highcharts.Series#onMouseOut
+ *
+ * @fires Highcharts.Series#event:mouseOut
*/
- onMouseOut: function() {
+ onMouseOut: function () {
// trigger the event only if listeners exist
var series = this,
options = series.options,
@@ -33692,7 +44253,8 @@
tooltip = chart.tooltip,
hoverPoint = chart.hoverPoint;
- chart.hoverSeries = null; // #182, set to null before the mouseOut event fires
+ // #182, set to null before the mouseOut event fires
+ chart.hoverSeries = null;
// trigger mouse out on the point, which must be in this series
if (hoverPoint) {
@@ -33706,7 +44268,11 @@
// hide the tooltip
- if (tooltip && !series.stickyTracking && (!tooltip.shared || series.noSharedTooltip)) {
+ if (
+ tooltip &&
+ !series.stickyTracking &&
+ (!tooltip.shared || series.noSharedTooltip)
+ ) {
tooltip.hide();
}
@@ -33715,15 +44281,16 @@
},
/**
- * Set the state of the series. Called internally on mouse interaction and
- * select operations, but it can also be called directly to visually
+ * Set the state of the series. Called internally on mouse interaction
+ * operations, but it can also be called directly to visually
* highlight a series.
*
- * @param {String} [state]
- * Can be either `hover`, `select` or undefined to set to normal
- * state.
+ * @function Highcharts.Series#setState
+ *
+ * @param {string} [state]
+ * Can be either `hover` or undefined to set to normal state.
*/
- setState: function(state) {
+ setState: function (state) {
var series = this,
options = series.options,
graph = series.graph,
@@ -33737,11 +44304,11 @@
if (series.state !== state) {
// Toggle class names
- each([
+ [
series.group,
series.markerGroup,
series.dataLabelsGroup
- ], function(group) {
+ ].forEach(function (group) {
if (group) {
// Old state
if (series.state) {
@@ -33756,51 +44323,66 @@
series.state = state;
+ if (!series.chart.styledMode) {
+ if (
+ stateOptions[state] &&
+ stateOptions[state].enabled === false
+ ) {
+ return;
+ }
- if (stateOptions[state] && stateOptions[state].enabled === false) {
- return;
- }
-
- if (state) {
- lineWidth = stateOptions[state].lineWidth || lineWidth + (stateOptions[state].lineWidthPlus || 0); // #4035
- }
+ if (state) {
+ lineWidth = (
+ stateOptions[state].lineWidth ||
+ lineWidth + (stateOptions[state].lineWidthPlus || 0)
+ ); // #4035
+ }
- if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
- attribs = {
- 'stroke-width': lineWidth
- };
+ if (graph && !graph.dashstyle) {
+ attribs = {
+ 'stroke-width': lineWidth
+ };
- // Animate the graph stroke-width. By default a quick animation
- // to hover, slower to un-hover.
- graph.animate(
- attribs,
- pick(
- series.chart.options.chart.animation,
- stateOptions[state] && stateOptions[state].animation
- )
- );
- while (series['zone-graph-' + i]) {
- series['zone-graph-' + i].attr(attribs);
- i = i + 1;
+ // Animate the graph stroke-width. By default a quick
+ // animation to hover, slower to un-hover.
+ graph.animate(
+ attribs,
+ pick(
+ (
+ stateOptions[state || 'normal'] &&
+ stateOptions[state || 'normal'].animation
+ ),
+ series.chart.options.chart.animation
+ )
+ );
+ while (series['zone-graph-' + i]) {
+ series['zone-graph-' + i].attr(attribs);
+ i = i + 1;
+ }
}
}
-
}
},
/**
* Show or hide the series.
*
- * @param {Boolean} [visible]
- * True to show the series, false to hide. If undefined, the
- * visibility is toggled.
- * @param {Boolean} [redraw=true]
- * Whether to redraw the chart after the series is altered. If doing
- * more operations on the chart, it is a good idea to set redraw to
- * false and call {@link Chart#redraw|chart.redraw()} after.
+ * @function Highcharts.Series#setVisible
+ *
+ * @param {boolean} [visible]
+ * True to show the series, false to hide. If undefined, the
+ * visibility is toggled.
+ *
+ * @param {boolean} [redraw=true]
+ * Whether to redraw the chart after the series is altered. If doing
+ * more operations on the chart, it is a good idea to set redraw to
+ * false and call {@link Chart#redraw|chart.redraw()} after.
+ *
+ * @fires Highcharts.Series#event:hide
+ * @fires Highcharts.Series#event:show
*/
- setVisible: function(vis, redraw) {
+ setVisible: function (vis, redraw) {
var series = this,
chart = series.chart,
legendItem = series.legendItem,
@@ -33809,11 +44391,21 @@
oldVisibility = series.visible;
// if called without an argument, toggle visibility
- series.visible = vis = series.options.visible = series.userOptions.visible = vis === undefined ? !oldVisibility : vis; // #5618
+ series.visible =
+ vis =
+ series.options.visible =
+ series.userOptions.visible =
+ vis === undefined ? !oldVisibility : vis; // #5618
showOrHide = vis ? 'show' : 'hide';
// show or hide elements
- each(['group', 'dataLabelsGroup', 'markerGroup', 'tracker', 'tt'], function(key) {
+ [
+ 'group',
+ 'dataLabelsGroup',
+ 'markerGroup',
+ 'tracker',
+ 'tt'
+ ].forEach(function (key) {
if (series[key]) {
series[key][showOrHide]();
}
@@ -33821,7 +44413,10 @@
// hide tooltip (#1361)
- if (chart.hoverSeries === series || (chart.hoverPoint && chart.hoverPoint.series) === series) {
+ if (
+ chart.hoverSeries === series ||
+ (chart.hoverPoint && chart.hoverPoint.series) === series
+ ) {
series.onMouseOut();
}
@@ -33835,7 +44430,7 @@
series.isDirty = true;
// in a stack, all other series are affected
if (series.options.stacking) {
- each(chart.series, function(otherSeries) {
+ chart.series.forEach(function (otherSeries) {
if (otherSeries.options.stacking && otherSeries.visible) {
otherSeries.isDirty = true;
}
@@ -33843,18 +44438,19 @@
}
// show or hide linked series
- each(series.linkedSeries, function(otherSeries) {
+ series.linkedSeries.forEach(function (otherSeries) {
otherSeries.setVisible(vis, false);
});
if (ignoreHiddenSeries) {
chart.isDirtyBox = true;
}
+
+ fireEvent(series, showOrHide);
+
if (redraw !== false) {
chart.redraw();
}
-
- fireEvent(series, showOrHide);
},
/**
@@ -33862,8 +44458,12 @@
*
* @sample highcharts/members/series-hide/
* Toggle visibility from a button
+ *
+ * @function Highcharts.Series#show
+ *
+ * @fires Highcharts.Series#event:show
*/
- show: function() {
+ show: function () {
this.setVisible(true);
},
@@ -33875,31 +44475,46 @@
*
* @sample highcharts/members/series-hide/
* Toggle visibility from a button
+ *
+ * @function Highcharts.Series#hide
+ *
+ * @fires Highcharts.Series#event:hide
*/
- hide: function() {
+ hide: function () {
this.setVisible(false);
},
/**
- * Select or unselect the series. This means its {@link
- * Highcharts.Series.selected|selected} property is set, the checkbox in the
- * legend is toggled and when selected, the series is returned by the
- * {@link Highcharts.Chart#getSelectedSeries} function.
- *
- * @param {Boolean} [selected]
- * True to select the series, false to unselect. If undefined, the
- * selection state is toggled.
+ * Select or unselect the series. This means its
+ * {@link Highcharts.Series.selected|selected}
+ * property is set, the checkbox in the legend is toggled and when selected,
+ * the series is returned by the
+ * {@link Highcharts.Chart#getSelectedSeries}
+ * function.
*
* @sample highcharts/members/series-select/
* Select a series from a button
+ *
+ * @function Highcharts.Series#select
+ *
+ * @param {boolean} [selected]
+ * True to select the series, false to unselect. If undefined, the
+ * selection state is toggled.
+ *
+ * @fires Highcharts.Series#event:select
+ * @fires Highcharts.Series#event:unselect
*/
- select: function(selected) {
+ select: function (selected) {
var series = this;
- series.selected = selected = (selected === undefined) ?
- !series.selected :
- selected;
+ series.selected =
+ selected =
+ this.options.selected = (
+ selected === undefined ?
+ !series.selected :
+ selected
+ );
if (series.checkbox) {
series.checkbox.checked = selected;
@@ -33908,46 +44523,69 @@
fireEvent(series, selected ? 'select' : 'unselect');
},
+ /**
+ * @private
+ * @borrows Highcharts.TrackerMixin.drawTrackerGraph as Highcharts.Series#drawTracker
+ */
drawTracker: TrackerMixin.drawTrackerGraph
});
}(Highcharts));
- (function(H) {
+ (function (H) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+ /**
+ * A callback function to gain complete control on when the responsive rule
+ * applies.
+ *
+ * @callback Highcharts.ResponsiveCallbackFunction
+ *
+ * @return {boolean}
+ * Return `true` if it applies.
+ */
+
+
+
var Chart = H.Chart,
- each = H.each,
- inArray = H.inArray,
isArray = H.isArray,
isObject = H.isObject,
pick = H.pick,
splat = H.splat;
-
/**
* Allows setting a set of rules to apply for different screen or chart
* sizes. Each rule specifies additional chart options.
- *
- * @sample {highstock} stock/demo/responsive/ Stock chart
- * @sample highcharts/responsive/axis/ Axis
- * @sample highcharts/responsive/legend/ Legend
- * @sample highcharts/responsive/classname/ Class name
- * @since 5.0.0
+ *
+ * @sample {highstock} stock/demo/responsive/
+ * Stock chart
+ * @sample highcharts/responsive/axis/
+ * Axis
+ * @sample highcharts/responsive/legend/
+ * Legend
+ * @sample highcharts/responsive/classname/
+ * Class name
+ *
+ * @since 5.0.0
* @apioption responsive
*/
/**
* A set of rules for responsive settings. The rules are executed from
* the top down.
- *
- * @type {Array}
- * @sample {highcharts} highcharts/responsive/axis/ Axis changes
- * @sample {highstock} highcharts/responsive/axis/ Axis changes
- * @sample {highmaps} highcharts/responsive/axis/ Axis changes
- * @since 5.0.0
+ *
+ * @sample {highcharts} highcharts/responsive/axis/
+ * Axis changes
+ * @sample {highstock} highcharts/responsive/axis/
+ * Axis changes
+ * @sample {highmaps} highcharts/responsive/axis/
+ * Axis changes
+ *
+ * @type {Array<*>}
+ * @since 5.0.0
* @apioption responsive.rules
*/
@@ -33955,7 +44593,7 @@
* A full set of chart options to apply as overrides to the general
* chart options. The chart options are applied when the given rule
* is active.
- *
+ *
* A special case is configuration objects that take arrays, for example
* [xAxis](#xAxis), [yAxis](#yAxis) or [series](#series). For these
* collections, an `id` option is used to map the new option set to
@@ -33963,21 +44601,25 @@
* the item of the same indexupdated. So for example, setting `chartOptions`
* with two series items without an `id`, will cause the existing chart's
* two series to be updated with respective options.
- *
- * @type {Object}
- * @sample {highstock} stock/demo/responsive/ Stock chart
- * @sample highcharts/responsive/axis/ Axis
- * @sample highcharts/responsive/legend/ Legend
- * @sample highcharts/responsive/classname/ Class name
- * @since 5.0.0
+ *
+ * @sample {highstock} stock/demo/responsive/
+ * Stock chart
+ * @sample highcharts/responsive/axis/
+ * Axis
+ * @sample highcharts/responsive/legend/
+ * Legend
+ * @sample highcharts/responsive/classname/
+ * Class name
+ *
+ * @type {Highcharts.Options}
+ * @since 5.0.0
* @apioption responsive.rules.chartOptions
*/
/**
* Under which conditions the rule applies.
- *
- * @type {Object}
- * @since 5.0.0
+ *
+ * @since 5.0.0
* @apioption responsive.rules.condition
*/
@@ -33986,60 +44628,71 @@
* rule applies. Return `true` if it applies. This opens for checking
* against other metrics than the chart size, or example the document
* size or other elements.
- *
- * @type {Function}
- * @context Chart
- * @since 5.0.0
+ *
+ * @type {Highcharts.ResponsiveCallbackFunction}
+ * @since 5.0.0
+ * @context Highcharts.Chart
* @apioption responsive.rules.condition.callback
*/
/**
* The responsive rule applies if the chart height is less than this.
- *
- * @type {Number}
- * @since 5.0.0
+ *
+ * @type {number}
+ * @since 5.0.0
* @apioption responsive.rules.condition.maxHeight
*/
/**
* The responsive rule applies if the chart width is less than this.
- *
- * @type {Number}
- * @sample highcharts/responsive/axis/ Max width is 500
- * @since 5.0.0
+ *
+ * @sample highcharts/responsive/axis/
+ * Max width is 500
+ *
+ * @type {number}
+ * @since 5.0.0
* @apioption responsive.rules.condition.maxWidth
*/
/**
* The responsive rule applies if the chart height is greater than this.
- *
- * @type {Number}
- * @default 0
- * @since 5.0.0
+ *
+ * @type {number}
+ * @default 0
+ * @since 5.0.0
* @apioption responsive.rules.condition.minHeight
*/
/**
* The responsive rule applies if the chart width is greater than this.
- *
- * @type {Number}
- * @default 0
- * @since 5.0.0
+ *
+ * @type {number}
+ * @default 0
+ * @since 5.0.0
* @apioption responsive.rules.condition.minWidth
*/
/**
* Update the chart based on the current chart/document size and options for
* responsiveness.
+ *
+ * @private
+ * @function Highcharts.Chart#setResponsive
+ *
+ * @param {boolean} [redraw=true]
+ * @param {Array} [reset=false]
+ * Reset by un-applying all rules. Chart.update resets all rules before
+ * applying updated options.
*/
- Chart.prototype.setResponsive = function(redraw) {
+ Chart.prototype.setResponsive = function (redraw, reset) {
var options = this.options.responsive,
ruleIds = [],
currentResponsive = this.currentResponsive,
- currentRuleIds;
+ currentRuleIds,
+ undoOptions;
- if (options && options.rules) {
- each(options.rules, function(rule) {
+ if (!reset && options && options.rules) {
+ options.rules.forEach(function (rule) {
if (rule._id === undefined) {
rule._id = H.uniqueKey();
}
@@ -34049,17 +44702,18 @@
}
// Merge matching rules
- var mergedOptions = H.merge.apply(0, H.map(ruleIds, function(ruleId) {
- return H.find(options.rules, function(rule) {
+ var mergedOptions = H.merge.apply(0, ruleIds.map(function (ruleId) {
+ return H.find(options.rules, function (rule) {
return rule._id === ruleId;
}).chartOptions;
}));
+ mergedOptions.isResponsiveOptions = true;
+
// Stringified key for the rules that currently apply.
ruleIds = ruleIds.toString() || undefined;
currentRuleIds = currentResponsive && currentResponsive.ruleIds;
-
// Changes in what rules apply
if (ruleIds !== currentRuleIds) {
@@ -34071,10 +44725,12 @@
if (ruleIds) {
// Get undo-options for matching rules
+ undoOptions = this.currentOptions(mergedOptions);
+ undoOptions.isResponsiveOptions = true;
this.currentResponsive = {
ruleIds: ruleIds,
mergedOptions: mergedOptions,
- undoOptions: this.currentOptions(mergedOptions)
+ undoOptions: undoOptions
};
this.update(mergedOptions, redraw);
@@ -34086,29 +44742,46 @@
};
/**
- * Handle a single responsiveness rule
+ * Handle a single responsiveness rule.
+ *
+ * @private
+ * @function Highcharts.Chart#matchResponsiveRule
+ *
+ * @param {Highcharts.ResponsiveRulesConditionOptions} rule
+ *
+ * @param {Array} matches
*/
- Chart.prototype.matchResponsiveRule = function(rule, matches) {
+ Chart.prototype.matchResponsiveRule = function (rule, matches) {
+
var condition = rule.condition,
- fn = condition.callback || function() {
- return this.chartWidth <= pick(condition.maxWidth, Number.MAX_VALUE) &&
- this.chartHeight <= pick(condition.maxHeight, Number.MAX_VALUE) &&
+ fn = condition.callback || function () {
+ return (
+ this.chartWidth <= pick(condition.maxWidth, Number.MAX_VALUE) &&
+ this.chartHeight <=
+ pick(condition.maxHeight, Number.MAX_VALUE) &&
this.chartWidth >= pick(condition.minWidth, 0) &&
- this.chartHeight >= pick(condition.minHeight, 0);
+ this.chartHeight >= pick(condition.minHeight, 0)
+ );
};
if (fn.call(this)) {
matches.push(rule._id);
}
-
};
/**
* Get the current values for a given set of options. Used before we update
* the chart with a new responsiveness rule.
* TODO: Restore axis options (by id?)
+ *
+ * @private
+ * @function Highcharts.Chart#currentOptions
+ *
+ * @param {Highcharts.Options} options
+ *
+ * @return {Highcharts.Options}
*/
- Chart.prototype.currentOptions = function(options) {
+ Chart.prototype.currentOptions = function (options) {
var ret = {};
@@ -34118,8 +44791,9 @@
*/
function getCurrent(options, curr, ret, depth) {
var i;
- H.objectEach(options, function(val, key) {
- if (!depth && inArray(key, ['series', 'xAxis', 'yAxis']) > -1) {
+
+ H.objectEach(options, function (val, key) {
+ if (!depth && ['series', 'xAxis', 'yAxis'].indexOf(key) > -1) {
val = splat(val);
ret[key] = [];
@@ -34151,5 +44825,9 @@
};
}(Highcharts));
- return Highcharts
+ return (function (Highcharts) {
+
+
+ return Highcharts;
+ }(Highcharts));
}));
diff --git a/app/assets/javascripts/highcharts/highcharts-3d.js b/app/assets/javascripts/highcharts/highcharts-3d.js
index d52fd05..4e0c257 100644
--- a/app/assets/javascripts/highcharts/highcharts-3d.js
+++ b/app/assets/javascripts/highcharts/highcharts-3d.js
@@ -1,74 +1,107 @@
/**
- * @license Highcharts JS v6.0.3 (2017-11-14)
+ * @license Highcharts JS v7.0.3 (2019-02-06)
*
* 3D features for Highcharts JS
*
* @license: www.highcharts.com/license
*/
'use strict';
-(function(factory) {
+(function (factory) {
if (typeof module === 'object' && module.exports) {
+ factory['default'] = factory;
module.exports = factory;
+ } else if (typeof define === 'function' && define.amd) {
+ define(function () {
+ return factory;
+ });
} else {
- factory(Highcharts);
+ factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
}
-}(function(Highcharts) {
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+}(function (Highcharts) {
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
- /**
- * Mathematical Functionility
- */
+
+
+
+ // Mathematical Functionility
var deg2rad = H.deg2rad,
pick = H.pick;
+
+ /* eslint-disable max-len */
/**
* Apply 3-D rotation
- * Euler Angles (XYZ): cosA = cos(Alfa|Roll), cosB = cos(Beta|Pitch), cosG = cos(Gamma|Yaw)
- *
+ * Euler Angles (XYZ):
+ * cosA = cos(Alfa|Roll)
+ * cosB = cos(Beta|Pitch)
+ * cosG = cos(Gamma|Yaw)
+ *
* Composite rotation:
* | cosB * cosG | cosB * sinG | -sinB |
* | sinA * sinB * cosG - cosA * sinG | sinA * sinB * sinG + cosA * cosG | sinA * cosB |
* | cosA * sinB * cosG + sinA * sinG | cosA * sinB * sinG - sinA * cosG | cosA * cosB |
- *
- * Now, Gamma/Yaw is not used (angle=0), so we assume cosG = 1 and sinG = 0, so we get:
+ *
+ * Now, Gamma/Yaw is not used (angle=0), so we assume cosG = 1 and sinG = 0, so
+ * we get:
* | cosB | 0 | - sinB |
* | sinA * sinB | cosA | sinA * cosB |
* | cosA * sinB | - sinA | cosA * cosB |
- *
- * But in browsers, y is reversed, so we get sinA => -sinA. The general result is:
+ *
+ * But in browsers, y is reversed, so we get sinA => -sinA. The general result
+ * is:
* | cosB | 0 | - sinB | | x | | px |
- * | - sinA * sinB | cosA | - sinA * cosB | x | y | = | py |
+ * | - sinA * sinB | cosA | - sinA * cosB | x | y | = | py |
* | cosA * sinB | sinA | cosA * cosB | | z | | pz |
+ *
+ * @private
+ * @function rotate3D
*/
+ /* eslint-enable max-len */
function rotate3D(x, y, z, angles) {
return {
x: angles.cosB * x - angles.sinB * z,
- y: -angles.sinA * angles.sinB * x + angles.cosA * y - angles.cosB * angles.sinA * z,
- z: angles.cosA * angles.sinB * x + angles.sinA * y + angles.cosA * angles.cosB * z
+ y: -angles.sinA * angles.sinB * x + angles.cosA * y -
+ angles.cosB * angles.sinA * z,
+ z: angles.cosA * angles.sinB * x + angles.sinA * y +
+ angles.cosA * angles.cosB * z
};
}
- function perspective3D(coordinate, origin, distance) {
- var projection = ((distance > 0) && (distance < Number.POSITIVE_INFINITY)) ? distance / (coordinate.z + origin.z + distance) : 1;
+ // Perspective3D function is available in global Highcharts scope because is
+ // needed also outside of perspective() function (#8042).
+ H.perspective3D = function (coordinate, origin, distance) {
+ var projection = ((distance > 0) && (distance < Number.POSITIVE_INFINITY)) ?
+ distance / (coordinate.z + origin.z + distance) :
+ 1;
+
return {
x: coordinate.x * projection,
y: coordinate.y * projection
};
- }
+ };
/**
* Transforms a given array of points according to the angles in chart.options.
- * Parameters:
- * - points: the array of points
- * - chart: the chart
- * - insidePlotArea: wether to verifiy the points are inside the plotArea
- * Returns:
- * - an array of transformed points
+ *
+ * @private
+ * @function Highcharts.perspective
+ *
+ * @param {Array} points
+ * The array of points
+ *
+ * @param {Highcharts.Chart} chart
+ * The chart
+ *
+ * @param {boolean} [insidePlotArea]
+ * Wether to verifiy the points are inside the plotArea
+ *
+ * @return {Array}
+ * An array of transformed points
*/
- H.perspective = function(points, chart, insidePlotArea) {
+ H.perspective = function (points, chart, insidePlotArea) {
var options3d = chart.options.chart.options3d,
inverted = insidePlotArea ? chart.inverted : false,
origin = {
@@ -93,14 +126,15 @@
}
// Transform each point
- return H.map(points, function(point) {
+ return points.map(function (point) {
var rotated = rotate3D(
(inverted ? point.y : point.x) - origin.x,
(inverted ? point.x : point.y) - origin.y,
(point.z || 0) - origin.z,
angles
),
- coordinate = perspective3D(rotated, origin, origin.vd); // Apply perspective
+ // Apply perspective
+ coordinate = H.perspective3D(rotated, origin, origin.vd);
// Apply translation
coordinate.x = coordinate.x * scale + origin.x;
@@ -116,32 +150,54 @@
};
/**
- * Calculate a distance from camera to points - made for calculating zIndex of scatter points.
- * Parameters:
- * - coordinates: The coordinates of the specific point
- * - chart: the chart
- * Returns:
- * - a distance from camera to point
+ * Calculate a distance from camera to points - made for calculating zIndex of
+ * scatter points.
+ *
+ * @private
+ * @function Highcharts.pointCameraDistance
+ *
+ * @param {object} coordinates
+ * The coordinates of the specific point
+ *
+ * @param {Highcharts.Chart} chart
+ * The chart
+ *
+ * @return {number}
+ * A distance from camera to point
*/
- H.pointCameraDistance = function(coordinates, chart) {
+ H.pointCameraDistance = function (coordinates, chart) {
var options3d = chart.options.chart.options3d,
cameraPosition = {
x: chart.plotWidth / 2,
y: chart.plotHeight / 2,
- z: pick(options3d.depth, 1) * pick(options3d.viewDistance, 0) + options3d.depth
+ z: pick(options3d.depth, 1) * pick(options3d.viewDistance, 0) +
+ options3d.depth
},
- distance = Math.sqrt(Math.pow(cameraPosition.x - coordinates.plotX, 2) + Math.pow(cameraPosition.y - coordinates.plotY, 2) + Math.pow(cameraPosition.z - coordinates.plotZ, 2));
+ distance = Math.sqrt(
+ Math.pow(cameraPosition.x - coordinates.plotX, 2) +
+ Math.pow(cameraPosition.y - coordinates.plotY, 2) +
+ Math.pow(cameraPosition.z - coordinates.plotZ, 2)
+ );
+
return distance;
};
/**
* Calculate area of a 2D polygon using Shoelace algorithm
* http://en.wikipedia.org/wiki/Shoelace_formula
+ *
+ * @private
+ * @function Highcharts.shapeArea
+ *
+ * @param {Array} vertexes
+ *
+ * @return {number}
*/
- H.shapeArea = function(vertexes) {
+ H.shapeArea = function (vertexes) {
var area = 0,
i,
j;
+
for (i = 0; i < vertexes.length; i++) {
j = (i + 1) % vertexes.length;
area += vertexes[i].x * vertexes[j].y - vertexes[j].x * vertexes[i].y;
@@ -151,81 +207,104 @@
/**
* Calculate area of a 3D polygon after perspective projection
+ *
+ * @private
+ * @function Highcharts.shapeArea3d
+ *
+ * @param {Array} vertexes
+ *
+ * @param {Highcharts.Chart} chart
+ *
+ * @param {boolean} [insidePlotArea]
+ *
+ * @return {number}
*/
- H.shapeArea3d = function(vertexes, chart, insidePlotArea) {
+ H.shapeArea3d = function (vertexes, chart, insidePlotArea) {
return H.shapeArea(H.perspective(vertexes, chart, insidePlotArea));
};
-
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
+ *
+ * Extensions to the SVGRenderer class to enable 3D shapes
*
* License: www.highcharts.com/license
*/
+
+
+
var cos = Math.cos,
PI = Math.PI,
sin = Math.sin;
-
var animObject = H.animObject,
charts = H.charts,
color = H.color,
defined = H.defined,
deg2rad = H.deg2rad,
- each = H.each,
extend = H.extend,
- inArray = H.inArray,
- map = H.map,
merge = H.merge,
perspective = H.perspective,
pick = H.pick,
SVGElement = H.SVGElement,
SVGRenderer = H.SVGRenderer,
- wrap = H.wrap;
+
+ dFactor,
+ element3dMethods,
+ cuboidMethods;
+
/*
- EXTENSION TO THE SVG-RENDERER TO ENABLE 3D SHAPES
+ EXTENSION TO THE SVG-RENDERER TO ENABLE 3D SHAPES
*/
- // HELPER METHODS //
+ // HELPER METHODS
+ dFactor = (4 * (Math.sqrt(2) - 1) / 3) / (PI / 2);
- var dFactor = (4 * (Math.sqrt(2) - 1) / 3) / (PI / 2);
-
- /** Method to construct a curved path
- * Can 'wrap' around more then 180 degrees
- */
+ // Method to construct a curved path. Can 'wrap' around more then 180 degrees
function curveTo(cx, cy, rx, ry, start, end, dx, dy) {
var result = [],
arcAngle = end - start;
+
if ((end > start) && (end - start > Math.PI / 2 + 0.0001)) {
- result = result.concat(curveTo(cx, cy, rx, ry, start, start + (Math.PI / 2), dx, dy));
- result = result.concat(curveTo(cx, cy, rx, ry, start + (Math.PI / 2), end, dx, dy));
+ result = result.concat(
+ curveTo(cx, cy, rx, ry, start, start + (Math.PI / 2), dx, dy)
+ );
+ result = result.concat(
+ curveTo(cx, cy, rx, ry, start + (Math.PI / 2), end, dx, dy)
+ );
return result;
}
if ((end < start) && (start - end > Math.PI / 2 + 0.0001)) {
- result = result.concat(curveTo(cx, cy, rx, ry, start, start - (Math.PI / 2), dx, dy));
- result = result.concat(curveTo(cx, cy, rx, ry, start - (Math.PI / 2), end, dx, dy));
+ result = result.concat(
+ curveTo(cx, cy, rx, ry, start, start - (Math.PI / 2), dx, dy)
+ );
+ result = result.concat(
+ curveTo(cx, cy, rx, ry, start - (Math.PI / 2), end, dx, dy)
+ );
return result;
}
return [
'C',
- cx + (rx * Math.cos(start)) - ((rx * dFactor * arcAngle) * Math.sin(start)) + dx,
- cy + (ry * Math.sin(start)) + ((ry * dFactor * arcAngle) * Math.cos(start)) + dy,
- cx + (rx * Math.cos(end)) + ((rx * dFactor * arcAngle) * Math.sin(end)) + dx,
- cy + (ry * Math.sin(end)) - ((ry * dFactor * arcAngle) * Math.cos(end)) + dy,
+ cx + (rx * Math.cos(start)) -
+ ((rx * dFactor * arcAngle) * Math.sin(start)) + dx,
+ cy + (ry * Math.sin(start)) +
+ ((ry * dFactor * arcAngle) * Math.cos(start)) + dy,
+ cx + (rx * Math.cos(end)) +
+ ((rx * dFactor * arcAngle) * Math.sin(end)) + dx,
+ cy + (ry * Math.sin(end)) -
+ ((ry * dFactor * arcAngle) * Math.cos(end)) + dy,
cx + (rx * Math.cos(end)) + dx,
cy + (ry * Math.sin(end)) + dy
];
}
-
-
- SVGRenderer.prototype.toLinePath = function(points, closed) {
+ SVGRenderer.prototype.toLinePath = function (points, closed) {
var result = [];
// Put "L x y" for each point
- each(points, function(point) {
+ points.forEach(function (point) {
result.push('L', point.x, point.y);
});
@@ -242,11 +321,11 @@
return result;
};
- SVGRenderer.prototype.toLineSegments = function(points) {
- var result = [];
+ SVGRenderer.prototype.toLineSegments = function (points) {
+ var result = [],
+ m = true;
- var m = true;
- each(points, function(point) {
+ points.forEach(function (point) {
result.push(m ? 'M' : 'L', point.x, point.y);
m = !m;
});
@@ -254,30 +333,42 @@
return result;
};
- /**
- * A 3-D Face is defined by it's 3D vertexes, and is only
- * visible if it's vertexes are counter-clockwise (Back-face culling).
- * It is used as a polyhedron Element
- */
- SVGRenderer.prototype.face3d = function(args) {
+ // A 3-D Face is defined by it's 3D vertexes, and is only visible if it's
+ // vertexes are counter-clockwise (Back-face culling). It is used as a
+ // polyhedron Element
+ SVGRenderer.prototype.face3d = function (args) {
var renderer = this,
ret = this.createElement('path');
+
ret.vertexes = [];
ret.insidePlotArea = false;
ret.enabled = true;
- wrap(ret, 'attr', function(proceed, hash) {
- if (typeof hash === 'object' &&
- (defined(hash.enabled) || defined(hash.vertexes) || defined(hash.insidePlotArea))) {
+ ret.attr = function (hash) {
+ if (
+ typeof hash === 'object' &&
+ (
+ defined(hash.enabled) ||
+ defined(hash.vertexes) ||
+ defined(hash.insidePlotArea)
+ )
+ ) {
this.enabled = pick(hash.enabled, this.enabled);
this.vertexes = pick(hash.vertexes, this.vertexes);
- this.insidePlotArea = pick(hash.insidePlotArea, this.insidePlotArea);
+ this.insidePlotArea = pick(
+ hash.insidePlotArea,
+ this.insidePlotArea
+ );
delete hash.enabled;
delete hash.vertexes;
delete hash.insidePlotArea;
var chart = charts[renderer.chartIndex],
- vertexes2d = perspective(this.vertexes, chart, this.insidePlotArea),
+ vertexes2d = perspective(
+ this.vertexes,
+ chart,
+ this.insidePlotArea
+ ),
path = renderer.toLinePath(vertexes2d, true),
area = H.shapeArea(vertexes2d),
visibility = (this.enabled && area > 0) ? 'visible' : 'hidden';
@@ -285,21 +376,34 @@
hash.d = path;
hash.visibility = visibility;
}
- return proceed.apply(this, [].slice.call(arguments, 1));
- });
+ return SVGElement.prototype.attr.apply(this, arguments);
+ };
- wrap(ret, 'animate', function(proceed, params) {
- if (typeof params === 'object' &&
- (defined(params.enabled) || defined(params.vertexes) || defined(params.insidePlotArea))) {
+ ret.animate = function (params) {
+ if (
+ typeof params === 'object' &&
+ (
+ defined(params.enabled) ||
+ defined(params.vertexes) ||
+ defined(params.insidePlotArea)
+ )
+ ) {
this.enabled = pick(params.enabled, this.enabled);
this.vertexes = pick(params.vertexes, this.vertexes);
- this.insidePlotArea = pick(params.insidePlotArea, this.insidePlotArea);
+ this.insidePlotArea = pick(
+ params.insidePlotArea,
+ this.insidePlotArea
+ );
delete params.enabled;
delete params.vertexes;
delete params.insidePlotArea;
var chart = charts[renderer.chartIndex],
- vertexes2d = perspective(this.vertexes, chart, this.insidePlotArea),
+ vertexes2d = perspective(
+ this.vertexes,
+ chart,
+ this.insidePlotArea
+ ),
path = renderer.toLinePath(vertexes2d, true),
area = H.shapeArea(vertexes2d),
visibility = (this.enabled && area > 0) ? 'visible' : 'hidden';
@@ -308,39 +412,38 @@
this.attr('visibility', visibility);
}
- return proceed.apply(this, [].slice.call(arguments, 1));
- });
+ return SVGElement.prototype.animate.apply(this, arguments);
+ };
return ret.attr(args);
};
- /**
- * A Polyhedron is a handy way of defining a group of 3-D faces.
- * It's only attribute is `faces`, an array of attributes of each one of it's Face3D instances.
- */
- SVGRenderer.prototype.polyhedron = function(args) {
+ // A Polyhedron is a handy way of defining a group of 3-D faces. It's only
+ // attribute is `faces`, an array of attributes of each one of it's Face3D
+ // instances.
+ SVGRenderer.prototype.polyhedron = function (args) {
var renderer = this,
result = this.g(),
destroy = result.destroy;
-
- result.attr({
- 'stroke-linejoin': 'round'
- });
-
+ if (!this.styledMode) {
+ result.attr({
+ 'stroke-linejoin': 'round'
+ });
+ }
result.faces = [];
// destroy all children
- result.destroy = function() {
+ result.destroy = function () {
for (var i = 0; i < result.faces.length; i++) {
result.faces[i].destroy();
}
return destroy.call(this);
};
- wrap(result, 'attr', function(proceed, hash, val, complete, continueAnimation) {
+ result.attr = function (hash, val, complete, continueAnimation) {
if (typeof hash === 'object' && defined(hash.faces)) {
while (result.faces.length > hash.faces.length) {
result.faces.pop().destroy();
@@ -349,14 +452,22 @@
result.faces.push(renderer.face3d().add(result));
}
for (var i = 0; i < hash.faces.length; i++) {
- result.faces[i].attr(hash.faces[i], null, complete, continueAnimation);
+ if (renderer.styledMode) {
+ delete hash.faces[i].fill;
+ }
+ result.faces[i].attr(
+ hash.faces[i],
+ null,
+ complete,
+ continueAnimation
+ );
}
delete hash.faces;
}
- return proceed.apply(this, [].slice.call(arguments, 1));
- });
+ return SVGElement.prototype.attr.apply(this, arguments);
+ };
- wrap(result, 'animate', function(proceed, params, duration, complete) {
+ result.animate = function (params, duration, complete) {
if (params && params.faces) {
while (result.faces.length > params.faces.length) {
result.faces.pop().destroy();
@@ -369,146 +480,181 @@
}
delete params.faces;
}
- return proceed.apply(this, [].slice.call(arguments, 1));
- });
+ return SVGElement.prototype.animate.apply(this, arguments);
+ };
return result.attr(args);
};
- // CUBOIDS //
- SVGRenderer.prototype.cuboid = function(shapeArgs) {
+ // Base, abstract prototype member for 3D elements
+ element3dMethods = {
+ // The init is used by base - renderer.Element
+ initArgs: function (args) {
+ var elem3d = this,
+ renderer = elem3d.renderer,
+ paths = renderer[elem3d.pathType + 'Path'](args),
+ zIndexes = paths.zIndexes;
+
+ // build parts
+ elem3d.parts.forEach(function (part) {
+ elem3d[part] = renderer.path(paths[part]).attr({
+ 'class': 'highcharts-3d-' + part,
+ zIndex: zIndexes[part] || 0
+ }).add(elem3d);
+ });
- var result = this.g(),
- destroy = result.destroy,
- paths = this.cuboidPath(shapeArgs);
+ elem3d.attr({
+ 'stroke-linejoin': 'round',
+ zIndex: zIndexes.group
+ });
+ // store original destroy
+ elem3d.originalDestroy = elem3d.destroy;
+ elem3d.destroy = elem3d.destroyParts;
+ },
- result.attr({
- 'stroke-linejoin': 'round'
- });
+ // Single property setter that applies options to each part
+ singleSetterForParts: function (
+ prop, val, values, verb, duration, complete
+ ) {
+ var elem3d = this,
+ newAttr = {},
+ optionsToApply = [null, null, (verb || 'attr'), duration, complete],
+ hasZIndexes = values && values.zIndexes;
+
+ if (!values) {
+ newAttr[prop] = val;
+ optionsToApply[0] = newAttr;
+ } else {
+ H.objectEach(values, function (partVal, part) {
+ newAttr[part] = {};
+ newAttr[part][prop] = partVal;
+ // include zIndexes if provided
+ if (hasZIndexes) {
+ newAttr[part].zIndex = values.zIndexes[part] || 0;
+ }
+ });
+ optionsToApply[1] = newAttr;
+ }
- // create the 3 sides
- result.front = this.path(paths[0]).attr({
- 'class': 'highcharts-3d-front'
- }).add(result); // Front, top and side are never overlapping in our case so it is redundant to set zIndex of every element.
- result.top = this.path(paths[1]).attr({
- 'class': 'highcharts-3d-top'
- }).add(result);
- result.side = this.path(paths[2]).attr({
- 'class': 'highcharts-3d-side'
- }).add(result);
-
- // apply the fill everywhere, the top a bit brighter, the side a bit darker
- result.fillSetter = function(fill) {
- this.front.attr({
- fill: fill
- });
- this.top.attr({
- fill: color(fill).brighten(0.1).get()
- });
- this.side.attr({
- fill: color(fill).brighten(-0.1).get()
- });
- this.color = fill;
+ return elem3d.processParts.apply(elem3d, optionsToApply);
+ },
- // for animation getter (#6776)
- result.fill = fill;
+ // Calls function for each part. Used for attr, animate and destroy.
+ processParts: function (props, partsProps, verb, duration, complete) {
+ var elem3d = this;
- return this;
- };
+ elem3d.parts.forEach(function (part) {
+ // if different props for different parts
+ if (partsProps) {
+ props = H.pick(partsProps[part], false);
+ }
- // apply opacaity everywhere
- result.opacitySetter = function(opacity) {
- this.front.attr({
- opacity: opacity
- });
- this.top.attr({
- opacity: opacity
- });
- this.side.attr({
- opacity: opacity
+ // only if something to set, but allow undefined
+ if (props !== false) {
+ elem3d[part][verb](props, duration, complete);
+ }
});
- return this;
- };
+ return elem3d;
+ },
+
+ // Destroy all parts
+ destroyParts: function () {
+ this.processParts(null, null, 'destroy');
+ return this.originalDestroy();
+ }
+ };
- result.attr = function(args, val, complete, continueAnimation) {
+ // CUBOID
+ cuboidMethods = H.merge(element3dMethods, {
+ parts: ['front', 'top', 'side'],
+ pathType: 'cuboid',
+ attr: function (args, val, complete, continueAnimation) {
// Resolve setting attributes by string name
if (typeof args === 'string' && typeof val !== 'undefined') {
var key = args;
+
args = {};
args[key] = val;
}
if (args.shapeArgs || defined(args.x)) {
- var shapeArgs = args.shapeArgs || args;
- var paths = this.renderer.cuboidPath(shapeArgs);
- this.front.attr({
- d: paths[0]
- });
- this.top.attr({
- d: paths[1]
- });
- this.side.attr({
- d: paths[2]
- });
- } else {
- // getter returns value
- return SVGElement.prototype.attr.call(
- this, args, undefined, complete, continueAnimation
+ return this.singleSetterForParts(
+ 'd',
+ null,
+ this.renderer[this.pathType + 'Path'](args.shapeArgs || args)
);
}
- return this;
- };
-
- result.animate = function(args, duration, complete) {
+ return SVGElement.prototype.attr.call(
+ this, args, undefined, complete, continueAnimation
+ );
+ },
+ animate: function (args, duration, complete) {
if (defined(args.x) && defined(args.y)) {
- var paths = this.renderer.cuboidPath(args);
- this.front.animate({
- d: paths[0]
- }, duration, complete);
- this.top.animate({
- d: paths[1]
- }, duration, complete);
- this.side.animate({
- d: paths[2]
- }, duration, complete);
+ var paths = this.renderer[this.pathType + 'Path'](args);
+
+ this.singleSetterForParts(
+ 'd', null, paths, 'animate', duration, complete
+ );
+
this.attr({
- zIndex: -paths[3] // #4774
+ zIndex: paths.zIndexes.group
});
} else if (args.opacity) {
- this.front.animate(args, duration, complete);
- this.top.animate(args, duration, complete);
- this.side.animate(args, duration, complete);
+ this.processParts(args, null, 'animate', duration, complete);
} else {
SVGElement.prototype.animate.call(this, args, duration, complete);
}
return this;
- };
+ },
+ fillSetter: function (fill) {
+ this.singleSetterForParts('fill', null, {
+ front: fill,
+ top: color(fill).brighten(0.1).get(),
+ side: color(fill).brighten(-0.1).get()
+ });
- // destroy all children
- result.destroy = function() {
- this.front.destroy();
- this.top.destroy();
- this.side.destroy();
+ // fill for animation getter (#6776)
+ this.color = this.fill = fill;
- return destroy.call(this);
- };
+ return this;
+ },
+ opacitySetter: function (opacity) {
+ return this.singleSetterForParts('opacity', opacity);
+ }
+ });
- // Apply the Z index to the cuboid group
- result.attr({
- zIndex: -paths[3]
- });
+ // set them up
+ SVGRenderer.prototype.elements3d = {
+ base: element3dMethods,
+ cuboid: cuboidMethods
+ };
- return result;
+ // return result, generalization
+ SVGRenderer.prototype.element3d = function (type, shapeArgs) {
+ // base
+ var ret = this.g();
+
+ // extend
+ H.extend(ret, this.elements3d[type]);
+
+ // init
+ ret.initArgs(shapeArgs);
+
+ // return
+ return ret;
};
- /**
- * Generates a cuboid
- */
- H.SVGRenderer.prototype.cuboidPath = function(shapeArgs) {
+ // generelized, so now use simply
+ SVGRenderer.prototype.cuboid = function (shapeArgs) {
+ return this.element3d('cuboid', shapeArgs);
+ };
+
+ // Generates a cuboid path and zIndexes
+ H.SVGRenderer.prototype.cuboidPath = function (shapeArgs) {
var x = shapeArgs.x,
y = shapeArgs.y,
z = shapeArgs.z,
@@ -531,49 +677,51 @@
isRight,
options3d = chart.options.chart.options3d,
alpha = options3d.alpha,
- // Priority for x axis is the biggest,
+ // Priority for x axis is the biggest,
// because of x direction has biggest influence on zIndex
incrementX = 10000,
- // y axis has the smallest priority in case of our charts
+ // y axis has the smallest priority in case of our charts
// (needs to be set because of stacking)
incrementY = 10,
incrementZ = 100,
- zIndex = 0;
+ zIndex = 0,
- // The 8 corners of the cube
- var pArr = [{
- x: x,
- y: y,
- z: z
- }, {
- x: x + w,
- y: y,
- z: z
- }, {
- x: x + w,
- y: y + h,
- z: z
- }, {
- x: x,
- y: y + h,
- z: z
- }, {
- x: x,
- y: y + h,
- z: z + d
- }, {
- x: x + w,
- y: y + h,
- z: z + d
- }, {
- x: x + w,
- y: y,
- z: z + d
- }, {
- x: x,
- y: y,
- z: z + d
- }];
+ // The 8 corners of the cube
+ pArr = [{
+ x: x,
+ y: y,
+ z: z
+ }, {
+ x: x + w,
+ y: y,
+ z: z
+ }, {
+ x: x + w,
+ y: y + h,
+ z: z
+ }, {
+ x: x,
+ y: y + h,
+ z: z
+ }, {
+ x: x,
+ y: y + h,
+ z: z + d
+ }, {
+ x: x + w,
+ y: y + h,
+ z: z + d
+ }, {
+ x: x + w,
+ y: y,
+ z: z + d
+ }, {
+ x: x,
+ y: y,
+ z: z + d
+ }],
+
+ pickShape;
// apply perspective
pArr = perspective(pArr, chart, shapeArgs.insidePlotArea);
@@ -583,17 +731,19 @@
return pArr[i];
}
- /*
+ /* *
* First value - path with specific side
- * Second value - added information about side for later calculations.
- * Possible second values are 0 for path1, 1 for path2 and -1 for no path choosed.
+ * Second value - added information about side for later calculations.
+ * Possible second values are 0 for path1, 1 for path2 and -1 for no path
+ * chosen.
*/
- var pickShape = function(path1, path2) {
+ pickShape = function (path1, path2) {
var ret = [
[], -1
];
- path1 = map(path1, mapPath);
- path2 = map(path2, mapPath);
+
+ path1 = path1.map(mapPath);
+ path2 = path2.map(mapPath);
if (H.shapeArea(path1) < 0) {
ret = [path1, 0];
} else if (H.shapeArea(path2) < 0) {
@@ -624,13 +774,14 @@
path3 = shape[0];
isRight = shape[1];
- /*
- * New block used for calculating zIndex. It is basing on X, Y and Z position of specific columns.
- * All zIndexes (for X, Y and Z values) are added to the final zIndex, where every value has different priority.
- * The biggest priority is in X and Z directions, the lowest index is for stacked columns (Y direction and the same X and Z positions).
- * Big differents between priorities is made because we need to ensure that even for big changes in Y and Z parameters
- * all columns will be drawn correctly.
- */
+ /* New block used for calculating zIndex. It is basing on X, Y and Z
+ position of specific columns. All zIndexes (for X, Y and Z values) are
+ added to the final zIndex, where every value has different priority. The
+ biggest priority is in X and Z directions, the lowest index is for
+ stacked columns (Y direction and the same X and Z positions). Big
+ differences between priorities is made because we need to ensure that
+ even for big changes in Y and Z parameters all columns will be drawn
+ correctly. */
if (isRight === 1) {
zIndex += incrementX * (1000 - x);
@@ -638,9 +789,11 @@
zIndex += incrementX * x;
}
- zIndex += incrementY * (!isTop ||
- (alpha >= 0 && alpha <= 180 || alpha < 360 && alpha > 357.5) ? // Numbers checked empirically
- chart.plotHeight - y : 10 + y
+ zIndex += incrementY * (
+ !isTop ||
+ // Numbers checked empirically
+ (alpha >= 0 && alpha <= 180 || alpha < 360 && alpha > 357.5) ?
+ chart.plotHeight - y : 10 + y
);
if (isFront === 1) {
@@ -649,34 +802,38 @@
zIndex += incrementZ * (1000 - z);
}
- zIndex = -Math.round(zIndex);
+ return {
+ front: this.toLinePath(path1, true),
+ top: this.toLinePath(path2, true),
+ side: this.toLinePath(path3, true),
+ zIndexes: {
+ group: Math.round(zIndex)
+ },
- return [
- this.toLinePath(path1, true),
- this.toLinePath(path2, true),
- this.toLinePath(path3, true),
- zIndex
- ]; // #4774
+ // additional info about zIndexes
+ isFront: isFront,
+ isTop: isTop
+ }; // #4774
};
// SECTORS //
- H.SVGRenderer.prototype.arc3d = function(attribs) {
+ H.SVGRenderer.prototype.arc3d = function (attribs) {
var wrapper = this.g(),
renderer = wrapper.renderer,
customAttribs = ['x', 'y', 'r', 'innerR', 'start', 'end'];
- /**
- * Get custom attributes. Don't mutate the original object and return an object with only custom attr.
- */
+ // Get custom attributes. Don't mutate the original object and return an
+ // object with only custom attr.
function suckOutCustom(params) {
var hasCA = false,
- ca = {};
+ ca = {},
+ key;
params = merge(params); // Don't mutate the original object
- for (var key in params) {
- if (inArray(key, customAttribs) !== -1) {
+ for (key in params) {
+ if (customAttribs.indexOf(key) !== -1) {
ca[key] = params[key];
delete params[key];
hasCA = true;
@@ -687,8 +844,8 @@
attribs = merge(attribs);
- attribs.alpha *= deg2rad;
- attribs.beta *= deg2rad;
+ attribs.alpha = (attribs.alpha || 0) * deg2rad;
+ attribs.beta = (attribs.beta || 0) * deg2rad;
// Create the different sub sections of the shape
wrapper.top = renderer.path();
@@ -697,17 +854,16 @@
wrapper.inn = renderer.path();
wrapper.out = renderer.path();
- /**
- * Add all faces
- */
- wrapper.onAdd = function() {
+ // Add all faces
+ wrapper.onAdd = function () {
var parent = wrapper.parentGroup,
className = wrapper.attr('class');
+
wrapper.top.add(wrapper);
// These faces are added outside the wrapper group because the z index
// relates to neighbour elements as well
- each(['out', 'inn', 'side1', 'side2'], function(face) {
+ ['out', 'inn', 'side1', 'side2'].forEach(function (face) {
wrapper[face]
.attr({
'class': className + ' highcharts-3d-side'
@@ -717,52 +873,34 @@
};
// Cascade to faces
- each(['addClass', 'removeClass'], function(fn) {
- wrapper[fn] = function() {
+ ['addClass', 'removeClass'].forEach(function (fn) {
+ wrapper[fn] = function () {
var args = arguments;
- each(['top', 'out', 'inn', 'side1', 'side2'], function(face) {
+
+ ['top', 'out', 'inn', 'side1', 'side2'].forEach(function (face) {
wrapper[face][fn].apply(wrapper[face], args);
});
};
});
- /**
- * Compute the transformed paths and set them to the composite shapes
- */
- wrapper.setPaths = function(attribs) {
+ // Compute the transformed paths and set them to the composite shapes
+ wrapper.setPaths = function (attribs) {
var paths = wrapper.renderer.arc3dPath(attribs),
zIndex = paths.zTop * 100;
wrapper.attribs = attribs;
- wrapper.top.attr({
- d: paths.top,
- zIndex: paths.zTop
- });
- wrapper.inn.attr({
- d: paths.inn,
- zIndex: paths.zInn
- });
- wrapper.out.attr({
- d: paths.out,
- zIndex: paths.zOut
- });
- wrapper.side1.attr({
- d: paths.side1,
- zIndex: paths.zSide1
- });
- wrapper.side2.attr({
- d: paths.side2,
- zIndex: paths.zSide2
- });
+ wrapper.top.attr({ d: paths.top, zIndex: paths.zTop });
+ wrapper.inn.attr({ d: paths.inn, zIndex: paths.zInn });
+ wrapper.out.attr({ d: paths.out, zIndex: paths.zOut });
+ wrapper.side1.attr({ d: paths.side1, zIndex: paths.zSide1 });
+ wrapper.side2.attr({ d: paths.side2, zIndex: paths.zSide2 });
// show all children
wrapper.zIndex = zIndex;
- wrapper.attr({
- zIndex: zIndex
- });
+ wrapper.attr({ zIndex: zIndex });
// Set the radial gradient center the first time
if (attribs.center) {
@@ -773,45 +911,36 @@
wrapper.setPaths(attribs);
// Apply the fill to the top and a darker shade to the sides
- wrapper.fillSetter = function(value) {
+ wrapper.fillSetter = function (value) {
var darker = color(value).brighten(-0.1).get();
this.fill = value;
- this.side1.attr({
- fill: darker
- });
- this.side2.attr({
- fill: darker
- });
- this.inn.attr({
- fill: darker
- });
- this.out.attr({
- fill: darker
- });
- this.top.attr({
- fill: value
- });
+ this.side1.attr({ fill: darker });
+ this.side2.attr({ fill: darker });
+ this.inn.attr({ fill: darker });
+ this.out.attr({ fill: darker });
+ this.top.attr({ fill: value });
return this;
};
- // Apply the same value to all. These properties cascade down to the children
- // when set to the composite arc3d.
- each(['opacity', 'translateX', 'translateY', 'visibility'], function(setter) {
- wrapper[setter + 'Setter'] = function(value, key) {
- wrapper[key] = value;
- each(['out', 'inn', 'side1', 'side2', 'top'], function(el) {
- wrapper[el].attr(key, value);
- });
- };
- });
+ // Apply the same value to all. These properties cascade down to the
+ // children when set to the composite arc3d.
+ ['opacity', 'translateX', 'translateY', 'visibility'].forEach(
+ function (setter) {
+ wrapper[setter + 'Setter'] = function (value, key) {
+ wrapper[key] = value;
+ ['out', 'inn', 'side1', 'side2', 'top'].forEach(function (el) {
+ wrapper[el].attr(key, value);
+ });
+ };
+ }
+ );
- /**
- * Override attr to remove shape attributes and use those to set child paths
- */
- wrap(wrapper, 'attr', function(proceed, params) {
+ // Override attr to remove shape attributes and use those to set child paths
+ wrapper.attr = function (params) {
var ca;
+
if (typeof params === 'object') {
ca = suckOutCustom(params);
if (ca) {
@@ -819,21 +948,20 @@
wrapper.setPaths(wrapper.attribs);
}
}
- return proceed.apply(this, [].slice.call(arguments, 1));
- });
+ return SVGElement.prototype.attr.apply(wrapper, arguments);
+ };
- /**
- * Override the animate function by sucking out custom parameters related to the shapes directly,
- * and update the shapes from the animation step.
- */
- wrap(wrapper, 'animate', function(proceed, params, animation, complete) {
+ // Override the animate function by sucking out custom parameters related to
+ // the shapes directly, and update the shapes from the animation step.
+ wrapper.animate = function (params, animation, complete) {
var ca,
from = this.attribs,
to,
- anim;
+ anim,
+ randomProp = 'data-' + Math.random().toString(26).substring(2, 9);
- // Attribute-line properties connected to 3D. These shouldn't have been in the
- // attribs collection in the first place.
+ // Attribute-line properties connected to 3D. These shouldn't have been
+ // in the attribs collection in the first place.
delete params.center;
delete params.z;
delete params.depth;
@@ -844,16 +972,21 @@
if (anim.duration) {
ca = suckOutCustom(params);
- params.dummy = 1; // Params need to have a property in order for the step to run (#5765)
+ // Params need to have a property in order for the step to run
+ // (#5765, #7097, #7437)
+ wrapper[randomProp] = 0;
+ params[randomProp] = 1;
+ wrapper[randomProp + 'Setter'] = H.noop;
if (ca) {
to = ca;
- anim.step = function(a, fx) {
+ anim.step = function (a, fx) {
function interpolate(key) {
- return from[key] + (pick(to[key], from[key]) - from[key]) * fx.pos;
+ return from[key] +
+ (pick(to[key], from[key]) - from[key]) * fx.pos;
}
- if (fx.prop === 'dummy') {
+ if (fx.prop === randomProp) {
fx.elem.setPaths(merge(from, {
x: interpolate('x'),
y: interpolate('y'),
@@ -867,11 +1000,16 @@
}
animation = anim; // Only when duration (#5572)
}
- return proceed.call(this, params, animation, complete);
- });
+ return SVGElement.prototype.animate.call(
+ this,
+ params,
+ animation,
+ complete
+ );
+ };
// destroy all children
- wrapper.destroy = function() {
+ wrapper.destroy = function () {
this.top.destroy();
this.out.destroy();
this.inn.destroy();
@@ -881,34 +1019,32 @@
SVGElement.prototype.destroy.call(this);
};
// hide all children
- wrapper.hide = function() {
+ wrapper.hide = function () {
this.top.hide();
this.out.hide();
this.inn.hide();
this.side1.hide();
this.side2.hide();
};
- wrapper.show = function() {
- this.top.show();
- this.out.show();
- this.inn.show();
- this.side1.show();
- this.side2.show();
+ wrapper.show = function (inherit) {
+ this.top.show(inherit);
+ this.out.show(inherit);
+ this.inn.show(inherit);
+ this.side1.show(inherit);
+ this.side2.show(inherit);
};
return wrapper;
};
- /**
- * Generate the paths required to draw a 3D arc
- */
- SVGRenderer.prototype.arc3dPath = function(shapeArgs) {
+ // Generate the paths required to draw a 3D arc
+ SVGRenderer.prototype.arc3dPath = function (shapeArgs) {
var cx = shapeArgs.x, // x coordinate of the center
cy = shapeArgs.y, // y coordinate of the center
start = shapeArgs.start, // start angle
end = shapeArgs.end - 0.00001, // end angle
r = shapeArgs.r, // radius
- ir = shapeArgs.innerR, // inner radius
- d = shapeArgs.depth, // depth
+ ir = shapeArgs.innerR || 0, // inner radius
+ d = shapeArgs.depth || 0, // depth
alpha = shapeArgs.alpha, // alpha rotation of the chart
beta = shapeArgs.beta; // beta rotation of the chart
@@ -917,7 +1053,7 @@
ss = Math.sin(start), // sinus of the start angle
ce = Math.cos(end), // cosinus of the end angle
se = Math.sin(end), // sinus of the end angle
- rx = r * Math.cos(beta), // x-radius
+ rx = r * Math.cos(beta), // x-radius
ry = r * Math.cos(alpha), // y-radius
irx = ir * Math.cos(beta), // x-radius (inner)
iry = ir * Math.cos(alpha), // y-radius (inner)
@@ -926,12 +1062,14 @@
// TOP
var top = ['M', cx + (rx * cs), cy + (ry * ss)];
+
top = top.concat(curveTo(cx, cy, rx, ry, start, end, 0, 0));
top = top.concat([
'L', cx + (irx * ce), cy + (iry * se)
]);
top = top.concat(curveTo(cx, cy, irx, iry, end, start, 0, 0));
top = top.concat(['Z']);
+
// OUTSIDE
var b = (beta > 0 ? Math.PI / 2 : 0),
a = (alpha > 0 ? 0 : Math.PI / 2);
@@ -940,15 +1078,16 @@
end2 = end < PI - a ? end : (start < PI - a ? PI - a : end),
midEnd = 2 * PI - a;
- // When slice goes over bottom middle, need to add both, left and right outer side.
- // Additionally, when we cross right hand edge, create sharp edge. Outer shape/wall:
+ // When slice goes over bottom middle, need to add both, left and right
+ // outer side. Additionally, when we cross right hand edge, create sharp
+ // edge. Outer shape/wall:
//
// -------
// / ^ \
// 4) / / \ \ 1)
// / / \ \
// / / \ \
- // (c)=> ==== ==== <=(d)
+ // (c)=> ==== ==== <=(d)
// \ \ / /
// \ \<=(a)/ /
// \ \ / / <=(b)
@@ -959,12 +1098,17 @@
// (b) - outer side
// (c) - left edge (sharp)
// (d) - right edge (sharp)
- // 1..n - rendering order for startAngle = 0, when set to e.g 90, order changes clockwise (1->2, 2->3, n->1) and counterclockwise for negative startAngle
+ // 1..n - rendering order for startAngle = 0, when set to e.g 90, order
+ // changes clockwise (1->2, 2->3, n->1) and counterclockwise for negative
+ // startAngle
var out = ['M', cx + (rx * cos(start2)), cy + (ry * sin(start2))];
+
out = out.concat(curveTo(cx, cy, rx, ry, start2, end2, 0, 0));
- if (end > midEnd && start < midEnd) { // When shape is wide, it can cross both, (c) and (d) edges, when using startAngle
+ // When shape is wide, it can cross both, (c) and (d) edges, when using
+ // startAngle
+ if (end > midEnd && start < midEnd) {
// Go to outer side
out = out.concat([
'L', cx + (rx * cos(end2)) + dx, cy + (ry * sin(end2)) + dy
@@ -988,10 +1132,14 @@
]);
// Go back to the left edge
out = out.concat(curveTo(cx, cy, rx, ry, midEnd, end2, 0, 0));
- } else if (end > PI - a && start < PI - a) { // But shape can cross also only (c) edge:
+
+ // But shape can cross also only (c) edge:
+ } else if (end > PI - a && start < PI - a) {
// Go to outer side
out = out.concat([
- 'L', cx + (rx * Math.cos(end2)) + dx, cy + (ry * Math.sin(end2)) + dy
+ 'L',
+ cx + (rx * Math.cos(end2)) + dx,
+ cy + (ry * Math.sin(end2)) + dy
]);
// Curve to the true end of the slice
out = out.concat(curveTo(cx, cy, rx, ry, end2, end, dx, dy));
@@ -1011,6 +1159,7 @@
// INSIDE
var inn = ['M', cx + (irx * cs), cy + (iry * ss)];
+
inn = inn.concat(curveTo(cx, cy, irx, iry, start, end, 0, 0));
inn = inn.concat([
'L', cx + (irx * Math.cos(end)) + dx, cy + (iry * Math.sin(end)) + dy
@@ -1034,7 +1183,8 @@
'Z'
];
- // correction for changed position of vanishing point caused by alpha and beta rotations
+ // correction for changed position of vanishing point caused by alpha and
+ // beta rotations
var angleCorr = Math.atan2(dy, -dx),
angleEnd = Math.abs(end + angleCorr),
angleStart = Math.abs(start + angleCorr),
@@ -1060,7 +1210,8 @@
return {
top: top,
- zTop: Math.PI * incPrecision + 1, // max angle is PI, so this is allways higher
+ // max angle is PI, so this is always higher
+ zTop: Math.PI * incPrecision + 1,
out: out,
zOut: Math.max(a1, a2, a3),
inn: inn,
@@ -1073,54 +1224,80 @@
};
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* Extension for 3D charts
*
* License: www.highcharts.com/license
*/
- var Chart = H.Chart,
- each = H.each,
+
+
+
+ var addEvent = H.addEvent,
+ Chart = H.Chart,
merge = H.merge,
perspective = H.perspective,
pick = H.pick,
wrap = H.wrap;
// Shorthand to check the is3d flag
- Chart.prototype.is3d = function() {
- return this.options.chart.options3d && this.options.chart.options3d.enabled; // #4280
+ Chart.prototype.is3d = function () {
+ return (
+ this.options.chart.options3d &&
+ this.options.chart.options3d.enabled
+ ); // #4280
};
Chart.prototype.propsRequireDirtyBox.push('chart.options3d');
Chart.prototype.propsRequireUpdateSeries.push('chart.options3d');
// Legacy support for HC < 6 to make 'scatter' series in a 3D chart route to the
- // real 'scatter3d' series type.
- wrap(Chart.prototype, 'initSeries', function(proceed, options) {
- var type = options.type ||
- this.options.chart.type ||
- this.options.chart.defaultSeriesType;
- if (this.is3d() && type === 'scatter') {
- options.type = 'scatter3d';
+ // real 'scatter3d' series type.
+ addEvent(Chart, 'afterInit', function () {
+ var options = this.options;
+
+ if (this.is3d()) {
+ (options.series || []).forEach(function (s) {
+ var type = s.type ||
+ options.chart.type ||
+ options.chart.defaultSeriesType;
+
+ if (type === 'scatter') {
+ s.type = 'scatter3d';
+ }
+ });
+ }
+ });
+ // And do it on dynamic add (#8407)
+ addEvent(Chart, 'addSeries', function (e) {
+ if (this.is3d()) {
+ if (e.options.type === 'scatter') {
+ e.options.type = 'scatter3d';
+ }
}
- return proceed.call(this, options);
});
/**
* Calculate scale of the 3D view. That is required to
* fit chart's 3D projection into the actual plotting area. Reported as #4933.
- * @notice This function should ideally take the plot values instead of a chart object,
- * but since the chart object is needed for perspective it is not practical.
- * Possible to make both getScale and perspective more logical and also immutable.
- * @param {Object} chart Chart object
- * @param {Number} chart.plotLeft
- * @param {Number} chart.plotWidth
- * @param {Number} chart.plotTop
- * @param {Number} chart.plotHeight
- * @param {Number} depth The depth of the chart
- * @return {Number} The scale to fit the 3D chart into the plotting area.
+ * @notice This function should ideally take the plot values instead of a chart
+ * object, but since the chart object is needed for perspective it is
+ * not practical. Possible to make both getScale and perspective more
+ * logical and also immutable.
+ *
+ * @private
+ * @function getScale
+ *
+ * @param {Highcharts.Chart} chart
+ * Chart object
+ *
+ * @param {number} depth
+ * The depth of the chart
+ *
+ * @return {number}
+ * The scale to fit the 3D chart into the plotting area.
*/
function getScale(chart, depth) {
var plotLeft = chart.plotLeft,
@@ -1150,7 +1327,7 @@
}];
// Top right corners:
- each([0, 1], function(i) {
+ [0, 1].forEach(function (i) {
corners.push({
x: plotRight,
y: corners[i].y,
@@ -1159,7 +1336,7 @@
});
// All bottom corners:
- each([0, 1, 2, 3], function(i) {
+ [0, 1, 2, 3].forEach(function (i) {
corners.push({
x: corners[i].x,
y: plotBottom,
@@ -1171,7 +1348,7 @@
corners = perspective(corners, chart, false);
// Get bounding box of 3D element:
- each(corners, function(corner) {
+ corners.forEach(function (corner) {
bbox3d.minX = Math.min(bbox3d.minX, corner.x);
bbox3d.maxX = Math.max(bbox3d.maxX, corner.x);
bbox3d.minY = Math.min(bbox3d.minY, corner.y);
@@ -1180,101 +1357,98 @@
// Left edge:
if (plotLeft > bbox3d.minX) {
- scale = Math.min(scale, 1 - Math.abs((plotLeft + originX) / (bbox3d.minX + originX)) % 1);
+ scale = Math.min(
+ scale,
+ 1 - Math.abs((plotLeft + originX) / (bbox3d.minX + originX)) % 1
+ );
}
// Right edge:
if (plotRight < bbox3d.maxX) {
- scale = Math.min(scale, (plotRight - originX) / (bbox3d.maxX - originX));
+ scale = Math.min(
+ scale,
+ (plotRight - originX) / (bbox3d.maxX - originX)
+ );
}
// Top edge:
if (plotTop > bbox3d.minY) {
if (bbox3d.minY < 0) {
- scale = Math.min(scale, (plotTop + originY) / (-bbox3d.minY + plotTop + originY));
+ scale = Math.min(
+ scale,
+ (plotTop + originY) / (-bbox3d.minY + plotTop + originY)
+ );
} else {
- scale = Math.min(scale, 1 - (plotTop + originY) / (bbox3d.minY + originY) % 1);
+ scale = Math.min(
+ scale,
+ 1 - (plotTop + originY) / (bbox3d.minY + originY) % 1
+ );
}
}
// Bottom edge:
if (plotBottom < bbox3d.maxY) {
- scale = Math.min(scale, Math.abs((plotBottom - originY) / (bbox3d.maxY - originY)));
+ scale = Math.min(
+ scale,
+ Math.abs((plotBottom - originY) / (bbox3d.maxY - originY))
+ );
}
return scale;
}
-
- H.wrap(H.Chart.prototype, 'isInsidePlot', function(proceed) {
+ H.wrap(H.Chart.prototype, 'isInsidePlot', function (proceed) {
return this.is3d() || proceed.apply(this, [].slice.call(arguments, 1));
});
var defaultOptions = H.getOptions();
/**
- * Options to render charts in 3 dimensions.
- * This feature requires highcharts-3d.js, found in the download package,
- * or online at code.highcharts.com/highcharts-3d.js.
* @optionparent
*/
var extendedOptions = {
- /**
- * Options regarding the chart area and plot area as well as general
- * chart options.
- *
- */
chart: {
/**
* Options to render charts in 3 dimensions. This feature requires
* `highcharts-3d.js`, found in the download package or online at
- * [code.highcharts.com/highcharts-3d.js](http://code.highcharts.com/highcharts-
- * 3d.js).
- *
- * @since 4.0
+ * [code.highcharts.com/highcharts-3d.js](http://code.highcharts.com/highcharts-3d.js).
+ *
+ * @since 4.0
* @product highcharts
*/
options3d: {
/**
* Wether to render the chart using the 3D functionality.
- *
- * @type {Boolean}
- * @default false
- * @since 4.0
+ *
+ * @since 4.0
* @product highcharts
*/
enabled: false,
/**
* One of the two rotation angles for the chart.
- *
- * @type {Number}
- * @default 0
- * @since 4.0
+ *
+ * @since 4.0
* @product highcharts
*/
alpha: 0,
/**
* One of the two rotation angles for the chart.
- *
- * @type {Number}
- * @default 0
- * @since 4.0
+ *
+ * @since 4.0
* @product highcharts
*/
beta: 0,
/**
* The total depth of the chart.
- *
- * @type {Number}
- * @default 100
- * @since 4.0
+ *
+ * @since 4.0
* @product highcharts
*/
depth: 100,
@@ -1282,22 +1456,19 @@
/**
* Whether the 3d box should automatically adjust to the chart plot
* area.
- *
- * @type {Boolean}
- * @default true
- * @since 4.2.4
+ *
+ * @since 4.2.4
* @product highcharts
*/
fitToPlot: true,
/**
- * Defines the distance the viewer is standing in front of the chart,
- * this setting is important to calculate the perspective effect
- * in column and scatter charts. It is not used for 3D pie charts.
- *
- * @type {Number}
- * @default 100
- * @since 4.0
+ * Defines the distance the viewer is standing in front of the
+ * chart, this setting is important to calculate the perspective
+ * effect in column and scatter charts. It is not used for 3D pie
+ * charts.
+ *
+ * @since 4.0
* @product highcharts
*/
viewDistance: 25,
@@ -1305,20 +1476,18 @@
/**
* Set it to `"auto"` to automatically move the labels to the best
* edge.
- *
- * @validvalue [null, "auto"]
- * @type {String}
- * @default null
- * @since 5.0.12
+ *
+ * @type {"auto"|null}
+ * @since 5.0.12
* @product highcharts
*/
- axisLabelPosition: 'default',
+ axisLabelPosition: null,
/**
* Provides the option to draw a frame around the charts by defining
* a bottom, front and back panel.
- *
- * @since 4.0
+ *
+ * @since 4.0
* @product highcharts
*/
frame: {
@@ -1335,79 +1504,84 @@
/**
* The bottom of the frame around a 3D chart.
- *
- * @since 4.0
+ *
+ * @since 4.0
* @product highcharts
*/
- bottom: {
- /**
- * The color of the panel.
- *
- * @type {Color}
- * @default transparent
- * @since 4.0
- * @product highcharts
- * @apioption chart.options3d.frame.bottom.color
- */
-
- /**
- * The thickness of the panel.
- *
- * @type {Number}
- * @default 1
- * @since 4.0
- * @product highcharts
- * @apioption chart.options3d.frame.bottom.size
- */
-
- /**
- * Whether to display the frame. Possible values are `true`, `false`,
- * `"auto"` to display only the frames behind the data, and `"default"`
- * to display faces behind the data based on the axis layout, ignoring
- * the point of view.
- *
- * @validvalue ["default", "auto", true, false]
- * @type {Boolean|String}
- * @sample {highcharts} highcharts/3d/scatter-frame/ Auto frames
- * @default default
- * @since 5.0.12
- * @product highcharts
- * @apioption chart.options3d.frame.bottom.visible
- */
- },
+
+ /**
+ * The color of the panel.
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @default transparent
+ * @since 4.0
+ * @product highcharts
+ * @apioption chart.options3d.frame.bottom.color
+ */
+
+ /**
+ * The thickness of the panel.
+ *
+ * @type {number}
+ * @default 1
+ * @since 4.0
+ * @product highcharts
+ * @apioption chart.options3d.frame.bottom.size
+ */
+
+ /**
+ * Whether to display the frame. Possible values are `true`,
+ * `false`, `"auto"` to display only the frames behind the data,
+ * and `"default"` to display faces behind the data based on the
+ * axis layout, ignoring the point of view.
+ *
+ * @sample {highcharts} highcharts/3d/scatter-frame/
+ * Auto frames
+ *
+ * @type {boolean|"default"|"auto"}
+ * @default default
+ * @since 5.0.12
+ * @product highcharts
+ * @apioption chart.options3d.frame.bottom.visible
+ */
+
+ /**
+ * The bottom of the frame around a 3D chart.
+ */
+ bottom: {},
/**
* The top of the frame around a 3D chart.
*
- * @extends {chart.options3d.frame.bottom}
+ * @extends chart.options3d.frame.bottom
*/
top: {},
/**
* The left side of the frame around a 3D chart.
*
- * @extends {chart.options3d.frame.bottom}
+ * @extends chart.options3d.frame.bottom
*/
left: {},
/**
* The right of the frame around a 3D chart.
*
- * @extends {chart.options3d.frame.bottom}
+ * @extends chart.options3d.frame.bottom
*/
right: {},
/**
* The back side of the frame around a 3D chart.
*
- * @extends {chart.options3d.frame.bottom}
+ * @extends chart.options3d.frame.bottom
*/
back: {},
/**
* The front of the frame around a 3D chart.
*
- * @extends {chart.options3d.frame.bottom}
+ * @extends chart.options3d.frame.bottom
*/
front: {}
}
@@ -1417,9 +1591,53 @@
merge(true, defaultOptions, extendedOptions);
+ // Add the required CSS classes for column sides (#6018)
+ addEvent(Chart, 'afterGetContainer', function () {
+ if (this.styledMode) {
+ this.renderer.definition({
+ tagName: 'style',
+ textContent:
+ '.highcharts-3d-top{' +
+ 'filter: url(#highcharts-brighter)' +
+ '}\n' +
+ '.highcharts-3d-side{' +
+ 'filter: url(#highcharts-darker)' +
+ '}\n'
+ });
+ // Add add definitions used by brighter and darker faces of the cuboids.
+ [{
+ name: 'darker',
+ slope: 0.6
+ }, {
+ name: 'brighter',
+ slope: 1.4
+ }].forEach(function (cfg) {
+ this.renderer.definition({
+ tagName: 'filter',
+ id: 'highcharts-' + cfg.name,
+ children: [{
+ tagName: 'feComponentTransfer',
+ children: [{
+ tagName: 'feFuncR',
+ type: 'linear',
+ slope: cfg.slope
+ }, {
+ tagName: 'feFuncG',
+ type: 'linear',
+ slope: cfg.slope
+ }, {
+ tagName: 'feFuncB',
+ type: 'linear',
+ slope: cfg.slope
+ }]
+ }]
+ });
+ }, this);
+ }
+ });
- wrap(Chart.prototype, 'setClassName', function(proceed) {
+ wrap(Chart.prototype, 'setClassName', function (proceed) {
proceed.apply(this, [].slice.call(arguments, 1));
if (this.is3d()) {
@@ -1427,12 +1645,10 @@
}
});
- H.wrap(H.Chart.prototype, 'setChartSize', function(proceed) {
+ addEvent(H.Chart, 'afterSetChartSize', function () {
var chart = this,
options3d = chart.options.chart.options3d;
- proceed.apply(chart, [].slice.call(arguments, 1));
-
if (chart.is3d()) {
var inverted = chart.inverted,
clipBox = chart.clipBox,
@@ -1448,31 +1664,33 @@
clipBox[h] = chart.chartHeight + (margin[0] || 0) + (margin[2] || 0);
// Set scale, used later in perspective method():
- chart.scale3d = 1; // @notice getScale uses perspective, so scale3d has to be reset.
+ // getScale uses perspective, so scale3d has to be reset.
+ chart.scale3d = 1;
if (options3d.fitToPlot === true) {
chart.scale3d = getScale(chart, options3d.depth);
}
+ // Recalculate the 3d frame with every call of setChartSize,
+ // instead of doing it after every redraw(). It avoids ticks
+ // and axis title outside of chart.
+ chart.frame3d = this.get3dFrame(); // #7942
}
});
- wrap(Chart.prototype, 'redraw', function(proceed) {
+ addEvent(Chart, 'beforeRedraw', function () {
if (this.is3d()) {
// Set to force a redraw of all elements
this.isDirtyBox = true;
- this.frame3d = this.get3dFrame();
}
- proceed.apply(this, [].slice.call(arguments, 1));
});
- wrap(Chart.prototype, 'render', function(proceed) {
+ addEvent(Chart, 'beforeRender', function () {
if (this.is3d()) {
this.frame3d = this.get3dFrame();
}
- proceed.apply(this, [].slice.call(arguments, 1));
});
// Draw the series in the reverse order (#3803, #3917)
- wrap(Chart.prototype, 'renderSeries', function(proceed) {
+ wrap(Chart.prototype, 'renderSeries', function (proceed) {
var series,
i = this.series.length;
@@ -1487,7 +1705,7 @@
}
});
- wrap(Chart.prototype, 'drawChartBox', function(proceed) {
+ addEvent(Chart, 'afterDrawChartBox', function () {
if (this.is3d()) {
var chart = this,
renderer = chart.renderer,
@@ -1519,811 +1737,802 @@
front: renderer.polyhedron().add()
};
}
-
this.frameShapes.bottom[verb]({
'class': 'highcharts-3d-frame highcharts-3d-frame-bottom',
zIndex: frame.bottom.frontFacing ? -1000 : 1000,
faces: [{ // bottom
- fill: H.color(frame.bottom.color).brighten(0.1).get(),
- vertexes: [{
- x: xmm,
- y: ypp,
- z: zmm
- }, {
- x: xpp,
- y: ypp,
- z: zmm
- }, {
- x: xpp,
- y: ypp,
- z: zpp
- }, {
- x: xmm,
- y: ypp,
- z: zpp
- }],
- enabled: frame.bottom.visible
- },
- { // top
- fill: H.color(frame.bottom.color).brighten(0.1).get(),
- vertexes: [{
- x: xm,
- y: yp,
- z: zp
- }, {
- x: xp,
- y: yp,
- z: zp
- }, {
- x: xp,
- y: yp,
- z: zm
- }, {
- x: xm,
- y: yp,
- z: zm
- }],
- enabled: frame.bottom.visible
- },
- { // left
- fill: H.color(frame.bottom.color).brighten(-0.1).get(),
- vertexes: [{
- x: xmm,
- y: ypp,
- z: zmm
- }, {
- x: xmm,
- y: ypp,
- z: zpp
- }, {
- x: xm,
- y: yp,
- z: zp
- }, {
- x: xm,
- y: yp,
- z: zm
- }],
- enabled: frame.bottom.visible && !frame.left.visible
- },
- { // right
- fill: H.color(frame.bottom.color).brighten(-0.1).get(),
- vertexes: [{
- x: xpp,
- y: ypp,
- z: zpp
- }, {
- x: xpp,
- y: ypp,
- z: zmm
- }, {
- x: xp,
- y: yp,
- z: zm
- }, {
- x: xp,
- y: yp,
- z: zp
- }],
- enabled: frame.bottom.visible && !frame.right.visible
- },
- { // front
- fill: H.color(frame.bottom.color).get(),
- vertexes: [{
- x: xpp,
- y: ypp,
- z: zmm
- }, {
- x: xmm,
- y: ypp,
- z: zmm
- }, {
- x: xm,
- y: yp,
- z: zm
- }, {
- x: xp,
- y: yp,
- z: zm
- }],
- enabled: frame.bottom.visible && !frame.front.visible
- },
- { // back
- fill: H.color(frame.bottom.color).get(),
- vertexes: [{
- x: xmm,
- y: ypp,
- z: zpp
- }, {
- x: xpp,
- y: ypp,
- z: zpp
- }, {
- x: xp,
- y: yp,
- z: zp
- }, {
- x: xm,
- y: yp,
- z: zp
- }],
- enabled: frame.bottom.visible && !frame.back.visible
- }
- ]
+ fill: H.color(frame.bottom.color).brighten(0.1).get(),
+ vertexes: [{
+ x: xmm,
+ y: ypp,
+ z: zmm
+ }, {
+ x: xpp,
+ y: ypp,
+ z: zmm
+ }, {
+ x: xpp,
+ y: ypp,
+ z: zpp
+ }, {
+ x: xmm,
+ y: ypp,
+ z: zpp
+ }],
+ enabled: frame.bottom.visible
+ },
+ { // top
+ fill: H.color(frame.bottom.color).brighten(0.1).get(),
+ vertexes: [{
+ x: xm,
+ y: yp,
+ z: zp
+ }, {
+ x: xp,
+ y: yp,
+ z: zp
+ }, {
+ x: xp,
+ y: yp,
+ z: zm
+ }, {
+ x: xm,
+ y: yp,
+ z: zm
+ }],
+ enabled: frame.bottom.visible
+ },
+ { // left
+ fill: H.color(frame.bottom.color).brighten(-0.1).get(),
+ vertexes: [{
+ x: xmm,
+ y: ypp,
+ z: zmm
+ }, {
+ x: xmm,
+ y: ypp,
+ z: zpp
+ }, {
+ x: xm,
+ y: yp,
+ z: zp
+ }, {
+ x: xm,
+ y: yp,
+ z: zm
+ }],
+ enabled: frame.bottom.visible && !frame.left.visible
+ },
+ { // right
+ fill: H.color(frame.bottom.color).brighten(-0.1).get(),
+ vertexes: [{
+ x: xpp,
+ y: ypp,
+ z: zpp
+ }, {
+ x: xpp,
+ y: ypp,
+ z: zmm
+ }, {
+ x: xp,
+ y: yp,
+ z: zm
+ }, {
+ x: xp,
+ y: yp,
+ z: zp
+ }],
+ enabled: frame.bottom.visible && !frame.right.visible
+ },
+ { // front
+ fill: H.color(frame.bottom.color).get(),
+ vertexes: [{
+ x: xpp,
+ y: ypp,
+ z: zmm
+ }, {
+ x: xmm,
+ y: ypp,
+ z: zmm
+ }, {
+ x: xm,
+ y: yp,
+ z: zm
+ }, {
+ x: xp,
+ y: yp,
+ z: zm
+ }],
+ enabled: frame.bottom.visible && !frame.front.visible
+ },
+ { // back
+ fill: H.color(frame.bottom.color).get(),
+ vertexes: [{
+ x: xmm,
+ y: ypp,
+ z: zpp
+ }, {
+ x: xpp,
+ y: ypp,
+ z: zpp
+ }, {
+ x: xp,
+ y: yp,
+ z: zp
+ }, {
+ x: xm,
+ y: yp,
+ z: zp
+ }],
+ enabled: frame.bottom.visible && !frame.back.visible
+ }]
});
this.frameShapes.top[verb]({
'class': 'highcharts-3d-frame highcharts-3d-frame-top',
zIndex: frame.top.frontFacing ? -1000 : 1000,
faces: [{ // bottom
- fill: H.color(frame.top.color).brighten(0.1).get(),
- vertexes: [{
- x: xmm,
- y: ymm,
- z: zpp
- }, {
- x: xpp,
- y: ymm,
- z: zpp
- }, {
- x: xpp,
- y: ymm,
- z: zmm
- }, {
- x: xmm,
- y: ymm,
- z: zmm
- }],
- enabled: frame.top.visible
- },
- { // top
- fill: H.color(frame.top.color).brighten(0.1).get(),
- vertexes: [{
- x: xm,
- y: ym,
- z: zm
- }, {
- x: xp,
- y: ym,
- z: zm
- }, {
- x: xp,
- y: ym,
- z: zp
- }, {
- x: xm,
- y: ym,
- z: zp
- }],
- enabled: frame.top.visible
- },
- { // left
- fill: H.color(frame.top.color).brighten(-0.1).get(),
- vertexes: [{
- x: xmm,
- y: ymm,
- z: zpp
- }, {
- x: xmm,
- y: ymm,
- z: zmm
- }, {
- x: xm,
- y: ym,
- z: zm
- }, {
- x: xm,
- y: ym,
- z: zp
- }],
- enabled: frame.top.visible && !frame.left.visible
- },
- { // right
- fill: H.color(frame.top.color).brighten(-0.1).get(),
- vertexes: [{
- x: xpp,
- y: ymm,
- z: zmm
- }, {
- x: xpp,
- y: ymm,
- z: zpp
- }, {
- x: xp,
- y: ym,
- z: zp
- }, {
- x: xp,
- y: ym,
- z: zm
- }],
- enabled: frame.top.visible && !frame.right.visible
- },
- { // front
- fill: H.color(frame.top.color).get(),
- vertexes: [{
- x: xmm,
- y: ymm,
- z: zmm
- }, {
- x: xpp,
- y: ymm,
- z: zmm
- }, {
- x: xp,
- y: ym,
- z: zm
- }, {
- x: xm,
- y: ym,
- z: zm
- }],
- enabled: frame.top.visible && !frame.front.visible
- },
- { // back
- fill: H.color(frame.top.color).get(),
- vertexes: [{
- x: xpp,
- y: ymm,
- z: zpp
- }, {
- x: xmm,
- y: ymm,
- z: zpp
- }, {
- x: xm,
- y: ym,
- z: zp
- }, {
- x: xp,
- y: ym,
- z: zp
- }],
- enabled: frame.top.visible && !frame.back.visible
- }
- ]
+ fill: H.color(frame.top.color).brighten(0.1).get(),
+ vertexes: [{
+ x: xmm,
+ y: ymm,
+ z: zpp
+ }, {
+ x: xpp,
+ y: ymm,
+ z: zpp
+ }, {
+ x: xpp,
+ y: ymm,
+ z: zmm
+ }, {
+ x: xmm,
+ y: ymm,
+ z: zmm
+ }],
+ enabled: frame.top.visible
+ },
+ { // top
+ fill: H.color(frame.top.color).brighten(0.1).get(),
+ vertexes: [{
+ x: xm,
+ y: ym,
+ z: zm
+ }, {
+ x: xp,
+ y: ym,
+ z: zm
+ }, {
+ x: xp,
+ y: ym,
+ z: zp
+ }, {
+ x: xm,
+ y: ym,
+ z: zp
+ }],
+ enabled: frame.top.visible
+ },
+ { // left
+ fill: H.color(frame.top.color).brighten(-0.1).get(),
+ vertexes: [{
+ x: xmm,
+ y: ymm,
+ z: zpp
+ }, {
+ x: xmm,
+ y: ymm,
+ z: zmm
+ }, {
+ x: xm,
+ y: ym,
+ z: zm
+ }, {
+ x: xm,
+ y: ym,
+ z: zp
+ }],
+ enabled: frame.top.visible && !frame.left.visible
+ },
+ { // right
+ fill: H.color(frame.top.color).brighten(-0.1).get(),
+ vertexes: [{
+ x: xpp,
+ y: ymm,
+ z: zmm
+ }, {
+ x: xpp,
+ y: ymm,
+ z: zpp
+ }, {
+ x: xp,
+ y: ym,
+ z: zp
+ }, {
+ x: xp,
+ y: ym,
+ z: zm
+ }],
+ enabled: frame.top.visible && !frame.right.visible
+ },
+ { // front
+ fill: H.color(frame.top.color).get(),
+ vertexes: [{
+ x: xmm,
+ y: ymm,
+ z: zmm
+ }, {
+ x: xpp,
+ y: ymm,
+ z: zmm
+ }, {
+ x: xp,
+ y: ym,
+ z: zm
+ }, {
+ x: xm,
+ y: ym,
+ z: zm
+ }],
+ enabled: frame.top.visible && !frame.front.visible
+ },
+ { // back
+ fill: H.color(frame.top.color).get(),
+ vertexes: [{
+ x: xpp,
+ y: ymm,
+ z: zpp
+ }, {
+ x: xmm,
+ y: ymm,
+ z: zpp
+ }, {
+ x: xm,
+ y: ym,
+ z: zp
+ }, {
+ x: xp,
+ y: ym,
+ z: zp
+ }],
+ enabled: frame.top.visible && !frame.back.visible
+ }]
});
this.frameShapes.left[verb]({
'class': 'highcharts-3d-frame highcharts-3d-frame-left',
zIndex: frame.left.frontFacing ? -1000 : 1000,
faces: [{ // bottom
- fill: H.color(frame.left.color).brighten(0.1).get(),
- vertexes: [{
- x: xmm,
- y: ypp,
- z: zmm
- }, {
- x: xm,
- y: yp,
- z: zm
- }, {
- x: xm,
- y: yp,
- z: zp
- }, {
- x: xmm,
- y: ypp,
- z: zpp
- }],
- enabled: frame.left.visible && !frame.bottom.visible
- },
- { // top
- fill: H.color(frame.left.color).brighten(0.1).get(),
- vertexes: [{
- x: xmm,
- y: ymm,
- z: zpp
- }, {
- x: xm,
- y: ym,
- z: zp
- }, {
- x: xm,
- y: ym,
- z: zm
- }, {
- x: xmm,
- y: ymm,
- z: zmm
- }],
- enabled: frame.left.visible && !frame.top.visible
- },
- { // left
- fill: H.color(frame.left.color).brighten(-0.1).get(),
- vertexes: [{
- x: xmm,
- y: ypp,
- z: zpp
- }, {
- x: xmm,
- y: ymm,
- z: zpp
- }, {
- x: xmm,
- y: ymm,
- z: zmm
- }, {
- x: xmm,
- y: ypp,
- z: zmm
- }],
- enabled: frame.left.visible
- },
- { // right
- fill: H.color(frame.left.color).brighten(-0.1).get(),
- vertexes: [{
- x: xm,
- y: ym,
- z: zp
- }, {
- x: xm,
- y: yp,
- z: zp
- }, {
- x: xm,
- y: yp,
- z: zm
- }, {
- x: xm,
- y: ym,
- z: zm
- }],
- enabled: frame.left.visible
- },
- { // front
- fill: H.color(frame.left.color).get(),
- vertexes: [{
- x: xmm,
- y: ypp,
- z: zmm
- }, {
- x: xmm,
- y: ymm,
- z: zmm
- }, {
- x: xm,
- y: ym,
- z: zm
- }, {
- x: xm,
- y: yp,
- z: zm
- }],
- enabled: frame.left.visible && !frame.front.visible
- },
- { // back
- fill: H.color(frame.left.color).get(),
- vertexes: [{
- x: xmm,
- y: ymm,
- z: zpp
- }, {
- x: xmm,
- y: ypp,
- z: zpp
- }, {
- x: xm,
- y: yp,
- z: zp
- }, {
- x: xm,
- y: ym,
- z: zp
- }],
- enabled: frame.left.visible && !frame.back.visible
- }
- ]
+ fill: H.color(frame.left.color).brighten(0.1).get(),
+ vertexes: [{
+ x: xmm,
+ y: ypp,
+ z: zmm
+ }, {
+ x: xm,
+ y: yp,
+ z: zm
+ }, {
+ x: xm,
+ y: yp,
+ z: zp
+ }, {
+ x: xmm,
+ y: ypp,
+ z: zpp
+ }],
+ enabled: frame.left.visible && !frame.bottom.visible
+ },
+ { // top
+ fill: H.color(frame.left.color).brighten(0.1).get(),
+ vertexes: [{
+ x: xmm,
+ y: ymm,
+ z: zpp
+ }, {
+ x: xm,
+ y: ym,
+ z: zp
+ }, {
+ x: xm,
+ y: ym,
+ z: zm
+ }, {
+ x: xmm,
+ y: ymm,
+ z: zmm
+ }],
+ enabled: frame.left.visible && !frame.top.visible
+ },
+ { // left
+ fill: H.color(frame.left.color).brighten(-0.1).get(),
+ vertexes: [{
+ x: xmm,
+ y: ypp,
+ z: zpp
+ }, {
+ x: xmm,
+ y: ymm,
+ z: zpp
+ }, {
+ x: xmm,
+ y: ymm,
+ z: zmm
+ }, {
+ x: xmm,
+ y: ypp,
+ z: zmm
+ }],
+ enabled: frame.left.visible
+ },
+ { // right
+ fill: H.color(frame.left.color).brighten(-0.1).get(),
+ vertexes: [{
+ x: xm,
+ y: ym,
+ z: zp
+ }, {
+ x: xm,
+ y: yp,
+ z: zp
+ }, {
+ x: xm,
+ y: yp,
+ z: zm
+ }, {
+ x: xm,
+ y: ym,
+ z: zm
+ }],
+ enabled: frame.left.visible
+ },
+ { // front
+ fill: H.color(frame.left.color).get(),
+ vertexes: [{
+ x: xmm,
+ y: ypp,
+ z: zmm
+ }, {
+ x: xmm,
+ y: ymm,
+ z: zmm
+ }, {
+ x: xm,
+ y: ym,
+ z: zm
+ }, {
+ x: xm,
+ y: yp,
+ z: zm
+ }],
+ enabled: frame.left.visible && !frame.front.visible
+ },
+ { // back
+ fill: H.color(frame.left.color).get(),
+ vertexes: [{
+ x: xmm,
+ y: ymm,
+ z: zpp
+ }, {
+ x: xmm,
+ y: ypp,
+ z: zpp
+ }, {
+ x: xm,
+ y: yp,
+ z: zp
+ }, {
+ x: xm,
+ y: ym,
+ z: zp
+ }],
+ enabled: frame.left.visible && !frame.back.visible
+ }]
});
this.frameShapes.right[verb]({
'class': 'highcharts-3d-frame highcharts-3d-frame-right',
zIndex: frame.right.frontFacing ? -1000 : 1000,
faces: [{ // bottom
- fill: H.color(frame.right.color).brighten(0.1).get(),
- vertexes: [{
- x: xpp,
- y: ypp,
- z: zpp
- }, {
- x: xp,
- y: yp,
- z: zp
- }, {
- x: xp,
- y: yp,
- z: zm
- }, {
- x: xpp,
- y: ypp,
- z: zmm
- }],
- enabled: frame.right.visible && !frame.bottom.visible
- },
- { // top
- fill: H.color(frame.right.color).brighten(0.1).get(),
- vertexes: [{
- x: xpp,
- y: ymm,
- z: zmm
- }, {
- x: xp,
- y: ym,
- z: zm
- }, {
- x: xp,
- y: ym,
- z: zp
- }, {
- x: xpp,
- y: ymm,
- z: zpp
- }],
- enabled: frame.right.visible && !frame.top.visible
- },
- { // left
- fill: H.color(frame.right.color).brighten(-0.1).get(),
- vertexes: [{
- x: xp,
- y: ym,
- z: zm
- }, {
- x: xp,
- y: yp,
- z: zm
- }, {
- x: xp,
- y: yp,
- z: zp
- }, {
- x: xp,
- y: ym,
- z: zp
- }],
- enabled: frame.right.visible
- },
- { // right
- fill: H.color(frame.right.color).brighten(-0.1).get(),
- vertexes: [{
- x: xpp,
- y: ypp,
- z: zmm
- }, {
- x: xpp,
- y: ymm,
- z: zmm
- }, {
- x: xpp,
- y: ymm,
- z: zpp
- }, {
- x: xpp,
- y: ypp,
- z: zpp
- }],
- enabled: frame.right.visible
- },
- { // front
- fill: H.color(frame.right.color).get(),
- vertexes: [{
- x: xpp,
- y: ymm,
- z: zmm
- }, {
- x: xpp,
- y: ypp,
- z: zmm
- }, {
- x: xp,
- y: yp,
- z: zm
- }, {
- x: xp,
- y: ym,
- z: zm
- }],
- enabled: frame.right.visible && !frame.front.visible
- },
- { // back
- fill: H.color(frame.right.color).get(),
- vertexes: [{
- x: xpp,
- y: ypp,
- z: zpp
- }, {
- x: xpp,
- y: ymm,
- z: zpp
- }, {
- x: xp,
- y: ym,
- z: zp
- }, {
- x: xp,
- y: yp,
- z: zp
- }],
- enabled: frame.right.visible && !frame.back.visible
- }
- ]
+ fill: H.color(frame.right.color).brighten(0.1).get(),
+ vertexes: [{
+ x: xpp,
+ y: ypp,
+ z: zpp
+ }, {
+ x: xp,
+ y: yp,
+ z: zp
+ }, {
+ x: xp,
+ y: yp,
+ z: zm
+ }, {
+ x: xpp,
+ y: ypp,
+ z: zmm
+ }],
+ enabled: frame.right.visible && !frame.bottom.visible
+ },
+ { // top
+ fill: H.color(frame.right.color).brighten(0.1).get(),
+ vertexes: [{
+ x: xpp,
+ y: ymm,
+ z: zmm
+ }, {
+ x: xp,
+ y: ym,
+ z: zm
+ }, {
+ x: xp,
+ y: ym,
+ z: zp
+ }, {
+ x: xpp,
+ y: ymm,
+ z: zpp
+ }],
+ enabled: frame.right.visible && !frame.top.visible
+ },
+ { // left
+ fill: H.color(frame.right.color).brighten(-0.1).get(),
+ vertexes: [{
+ x: xp,
+ y: ym,
+ z: zm
+ }, {
+ x: xp,
+ y: yp,
+ z: zm
+ }, {
+ x: xp,
+ y: yp,
+ z: zp
+ }, {
+ x: xp,
+ y: ym,
+ z: zp
+ }],
+ enabled: frame.right.visible
+ },
+ { // right
+ fill: H.color(frame.right.color).brighten(-0.1).get(),
+ vertexes: [{
+ x: xpp,
+ y: ypp,
+ z: zmm
+ }, {
+ x: xpp,
+ y: ymm,
+ z: zmm
+ }, {
+ x: xpp,
+ y: ymm,
+ z: zpp
+ }, {
+ x: xpp,
+ y: ypp,
+ z: zpp
+ }],
+ enabled: frame.right.visible
+ },
+ { // front
+ fill: H.color(frame.right.color).get(),
+ vertexes: [{
+ x: xpp,
+ y: ymm,
+ z: zmm
+ }, {
+ x: xpp,
+ y: ypp,
+ z: zmm
+ }, {
+ x: xp,
+ y: yp,
+ z: zm
+ }, {
+ x: xp,
+ y: ym,
+ z: zm
+ }],
+ enabled: frame.right.visible && !frame.front.visible
+ },
+ { // back
+ fill: H.color(frame.right.color).get(),
+ vertexes: [{
+ x: xpp,
+ y: ypp,
+ z: zpp
+ }, {
+ x: xpp,
+ y: ymm,
+ z: zpp
+ }, {
+ x: xp,
+ y: ym,
+ z: zp
+ }, {
+ x: xp,
+ y: yp,
+ z: zp
+ }],
+ enabled: frame.right.visible && !frame.back.visible
+ }]
});
this.frameShapes.back[verb]({
'class': 'highcharts-3d-frame highcharts-3d-frame-back',
zIndex: frame.back.frontFacing ? -1000 : 1000,
faces: [{ // bottom
- fill: H.color(frame.back.color).brighten(0.1).get(),
- vertexes: [{
- x: xpp,
- y: ypp,
- z: zpp
- }, {
- x: xmm,
- y: ypp,
- z: zpp
- }, {
- x: xm,
- y: yp,
- z: zp
- }, {
- x: xp,
- y: yp,
- z: zp
- }],
- enabled: frame.back.visible && !frame.bottom.visible
- },
- { // top
- fill: H.color(frame.back.color).brighten(0.1).get(),
- vertexes: [{
- x: xmm,
- y: ymm,
- z: zpp
- }, {
- x: xpp,
- y: ymm,
- z: zpp
- }, {
- x: xp,
- y: ym,
- z: zp
- }, {
- x: xm,
- y: ym,
- z: zp
- }],
- enabled: frame.back.visible && !frame.top.visible
- },
- { // left
- fill: H.color(frame.back.color).brighten(-0.1).get(),
- vertexes: [{
- x: xmm,
- y: ypp,
- z: zpp
- }, {
- x: xmm,
- y: ymm,
- z: zpp
- }, {
- x: xm,
- y: ym,
- z: zp
- }, {
- x: xm,
- y: yp,
- z: zp
- }],
- enabled: frame.back.visible && !frame.left.visible
- },
- { // right
- fill: H.color(frame.back.color).brighten(-0.1).get(),
- vertexes: [{
- x: xpp,
- y: ymm,
- z: zpp
- }, {
- x: xpp,
- y: ypp,
- z: zpp
- }, {
- x: xp,
- y: yp,
- z: zp
- }, {
- x: xp,
- y: ym,
- z: zp
- }],
- enabled: frame.back.visible && !frame.right.visible
- },
- { // front
- fill: H.color(frame.back.color).get(),
- vertexes: [{
- x: xm,
- y: ym,
- z: zp
- }, {
- x: xp,
- y: ym,
- z: zp
- }, {
- x: xp,
- y: yp,
- z: zp
- }, {
- x: xm,
- y: yp,
- z: zp
- }],
- enabled: frame.back.visible
- },
- { // back
- fill: H.color(frame.back.color).get(),
- vertexes: [{
- x: xmm,
- y: ypp,
- z: zpp
- }, {
- x: xpp,
- y: ypp,
- z: zpp
- }, {
- x: xpp,
- y: ymm,
- z: zpp
- }, {
- x: xmm,
- y: ymm,
- z: zpp
- }],
- enabled: frame.back.visible
- }
- ]
+ fill: H.color(frame.back.color).brighten(0.1).get(),
+ vertexes: [{
+ x: xpp,
+ y: ypp,
+ z: zpp
+ }, {
+ x: xmm,
+ y: ypp,
+ z: zpp
+ }, {
+ x: xm,
+ y: yp,
+ z: zp
+ }, {
+ x: xp,
+ y: yp,
+ z: zp
+ }],
+ enabled: frame.back.visible && !frame.bottom.visible
+ },
+ { // top
+ fill: H.color(frame.back.color).brighten(0.1).get(),
+ vertexes: [{
+ x: xmm,
+ y: ymm,
+ z: zpp
+ }, {
+ x: xpp,
+ y: ymm,
+ z: zpp
+ }, {
+ x: xp,
+ y: ym,
+ z: zp
+ }, {
+ x: xm,
+ y: ym,
+ z: zp
+ }],
+ enabled: frame.back.visible && !frame.top.visible
+ },
+ { // left
+ fill: H.color(frame.back.color).brighten(-0.1).get(),
+ vertexes: [{
+ x: xmm,
+ y: ypp,
+ z: zpp
+ }, {
+ x: xmm,
+ y: ymm,
+ z: zpp
+ }, {
+ x: xm,
+ y: ym,
+ z: zp
+ }, {
+ x: xm,
+ y: yp,
+ z: zp
+ }],
+ enabled: frame.back.visible && !frame.left.visible
+ },
+ { // right
+ fill: H.color(frame.back.color).brighten(-0.1).get(),
+ vertexes: [{
+ x: xpp,
+ y: ymm,
+ z: zpp
+ }, {
+ x: xpp,
+ y: ypp,
+ z: zpp
+ }, {
+ x: xp,
+ y: yp,
+ z: zp
+ }, {
+ x: xp,
+ y: ym,
+ z: zp
+ }],
+ enabled: frame.back.visible && !frame.right.visible
+ },
+ { // front
+ fill: H.color(frame.back.color).get(),
+ vertexes: [{
+ x: xm,
+ y: ym,
+ z: zp
+ }, {
+ x: xp,
+ y: ym,
+ z: zp
+ }, {
+ x: xp,
+ y: yp,
+ z: zp
+ }, {
+ x: xm,
+ y: yp,
+ z: zp
+ }],
+ enabled: frame.back.visible
+ },
+ { // back
+ fill: H.color(frame.back.color).get(),
+ vertexes: [{
+ x: xmm,
+ y: ypp,
+ z: zpp
+ }, {
+ x: xpp,
+ y: ypp,
+ z: zpp
+ }, {
+ x: xpp,
+ y: ymm,
+ z: zpp
+ }, {
+ x: xmm,
+ y: ymm,
+ z: zpp
+ }],
+ enabled: frame.back.visible
+ }]
});
this.frameShapes.front[verb]({
'class': 'highcharts-3d-frame highcharts-3d-frame-front',
zIndex: frame.front.frontFacing ? -1000 : 1000,
faces: [{ // bottom
- fill: H.color(frame.front.color).brighten(0.1).get(),
- vertexes: [{
- x: xmm,
- y: ypp,
- z: zmm
- }, {
- x: xpp,
- y: ypp,
- z: zmm
- }, {
- x: xp,
- y: yp,
- z: zm
- }, {
- x: xm,
- y: yp,
- z: zm
- }],
- enabled: frame.front.visible && !frame.bottom.visible
- },
- { // top
- fill: H.color(frame.front.color).brighten(0.1).get(),
- vertexes: [{
- x: xpp,
- y: ymm,
- z: zmm
- }, {
- x: xmm,
- y: ymm,
- z: zmm
- }, {
- x: xm,
- y: ym,
- z: zm
- }, {
- x: xp,
- y: ym,
- z: zm
- }],
- enabled: frame.front.visible && !frame.top.visible
- },
- { // left
- fill: H.color(frame.front.color).brighten(-0.1).get(),
- vertexes: [{
- x: xmm,
- y: ymm,
- z: zmm
- }, {
- x: xmm,
- y: ypp,
- z: zmm
- }, {
- x: xm,
- y: yp,
- z: zm
- }, {
- x: xm,
- y: ym,
- z: zm
- }],
- enabled: frame.front.visible && !frame.left.visible
- },
- { // right
- fill: H.color(frame.front.color).brighten(-0.1).get(),
- vertexes: [{
- x: xpp,
- y: ypp,
- z: zmm
- }, {
- x: xpp,
- y: ymm,
- z: zmm
- }, {
- x: xp,
- y: ym,
- z: zm
- }, {
- x: xp,
- y: yp,
- z: zm
- }],
- enabled: frame.front.visible && !frame.right.visible
- },
- { // front
- fill: H.color(frame.front.color).get(),
- vertexes: [{
- x: xp,
- y: ym,
- z: zm
- }, {
- x: xm,
- y: ym,
- z: zm
- }, {
- x: xm,
- y: yp,
- z: zm
- }, {
- x: xp,
- y: yp,
- z: zm
- }],
- enabled: frame.front.visible
- },
- { // back
- fill: H.color(frame.front.color).get(),
- vertexes: [{
- x: xpp,
- y: ypp,
- z: zmm
- }, {
- x: xmm,
- y: ypp,
- z: zmm
- }, {
- x: xmm,
- y: ymm,
- z: zmm
- }, {
- x: xpp,
- y: ymm,
- z: zmm
- }],
- enabled: frame.front.visible
- }
- ]
+ fill: H.color(frame.front.color).brighten(0.1).get(),
+ vertexes: [{
+ x: xmm,
+ y: ypp,
+ z: zmm
+ }, {
+ x: xpp,
+ y: ypp,
+ z: zmm
+ }, {
+ x: xp,
+ y: yp,
+ z: zm
+ }, {
+ x: xm,
+ y: yp,
+ z: zm
+ }],
+ enabled: frame.front.visible && !frame.bottom.visible
+ },
+ { // top
+ fill: H.color(frame.front.color).brighten(0.1).get(),
+ vertexes: [{
+ x: xpp,
+ y: ymm,
+ z: zmm
+ }, {
+ x: xmm,
+ y: ymm,
+ z: zmm
+ }, {
+ x: xm,
+ y: ym,
+ z: zm
+ }, {
+ x: xp,
+ y: ym,
+ z: zm
+ }],
+ enabled: frame.front.visible && !frame.top.visible
+ },
+ { // left
+ fill: H.color(frame.front.color).brighten(-0.1).get(),
+ vertexes: [{
+ x: xmm,
+ y: ymm,
+ z: zmm
+ }, {
+ x: xmm,
+ y: ypp,
+ z: zmm
+ }, {
+ x: xm,
+ y: yp,
+ z: zm
+ }, {
+ x: xm,
+ y: ym,
+ z: zm
+ }],
+ enabled: frame.front.visible && !frame.left.visible
+ },
+ { // right
+ fill: H.color(frame.front.color).brighten(-0.1).get(),
+ vertexes: [{
+ x: xpp,
+ y: ypp,
+ z: zmm
+ }, {
+ x: xpp,
+ y: ymm,
+ z: zmm
+ }, {
+ x: xp,
+ y: ym,
+ z: zm
+ }, {
+ x: xp,
+ y: yp,
+ z: zm
+ }],
+ enabled: frame.front.visible && !frame.right.visible
+ },
+ { // front
+ fill: H.color(frame.front.color).get(),
+ vertexes: [{
+ x: xp,
+ y: ym,
+ z: zm
+ }, {
+ x: xm,
+ y: ym,
+ z: zm
+ }, {
+ x: xm,
+ y: yp,
+ z: zm
+ }, {
+ x: xp,
+ y: yp,
+ z: zm
+ }],
+ enabled: frame.front.visible
+ },
+ { // back
+ fill: H.color(frame.front.color).get(),
+ vertexes: [{
+ x: xpp,
+ y: ypp,
+ z: zmm
+ }, {
+ x: xmm,
+ y: ypp,
+ z: zmm
+ }, {
+ x: xmm,
+ y: ymm,
+ z: zmm
+ }, {
+ x: xpp,
+ y: ymm,
+ z: zmm
+ }],
+ enabled: frame.front.visible
+ }]
});
}
-
- return proceed.apply(this, [].slice.call(arguments, 1));
});
- Chart.prototype.retrieveStacks = function(stacking) {
+ Chart.prototype.retrieveStacks = function (stacking) {
var series = this.series,
stacks = {},
stackNumber,
i = 1;
- each(this.series, function(s) {
- stackNumber = pick(s.options.stack, (stacking ? 0 : series.length - 1 - s.index)); // #3841, #4532
+ this.series.forEach(function (s) {
+ stackNumber = pick(
+ s.options.stack,
+ (stacking ? 0 : series.length - 1 - s.index)
+ ); // #3841, #4532
if (!stacks[stackNumber]) {
- stacks[stackNumber] = {
- series: [s],
- position: i
- };
+ stacks[stackNumber] = { series: [s], position: i };
i++;
} else {
stacks[stackNumber].series.push(s);
@@ -2334,7 +2543,7 @@
return stacks;
};
- Chart.prototype.get3dFrame = function() {
+ Chart.prototype.get3dFrame = function () {
var chart = this,
options3d = chart.options.chart.options3d,
frameOptions = options3d.frame,
@@ -2344,8 +2553,9 @@
yp = chart.plotTop + chart.plotHeight,
zm = 0,
zp = options3d.depth,
- faceOrientation = function(vertexes) {
+ faceOrientation = function (vertexes) {
var area = H.shapeArea3d(vertexes, chart);
+
// Give it 0.5 squared-pixel as a margin for rounding errors.
if (area > 0.5) {
return 1;
@@ -2355,108 +2565,42 @@
}
return 0;
},
- bottomOrientation = faceOrientation([{
- x: xm,
- y: yp,
- z: zp
- }, {
- x: xp,
- y: yp,
- z: zp
- }, {
- x: xp,
- y: yp,
- z: zm
- }, {
- x: xm,
- y: yp,
- z: zm
- }]),
- topOrientation = faceOrientation([{
- x: xm,
- y: ym,
- z: zm
- }, {
- x: xp,
- y: ym,
- z: zm
- }, {
- x: xp,
- y: ym,
- z: zp
- }, {
- x: xm,
- y: ym,
- z: zp
- }]),
- leftOrientation = faceOrientation([{
- x: xm,
- y: ym,
- z: zm
- }, {
- x: xm,
- y: ym,
- z: zp
- }, {
- x: xm,
- y: yp,
- z: zp
- }, {
- x: xm,
- y: yp,
- z: zm
- }]),
- rightOrientation = faceOrientation([{
- x: xp,
- y: ym,
- z: zp
- }, {
- x: xp,
- y: ym,
- z: zm
- }, {
- x: xp,
- y: yp,
- z: zm
- }, {
- x: xp,
- y: yp,
- z: zp
- }]),
- frontOrientation = faceOrientation([{
- x: xm,
- y: yp,
- z: zm
- }, {
- x: xp,
- y: yp,
- z: zm
- }, {
- x: xp,
- y: ym,
- z: zm
- }, {
- x: xm,
- y: ym,
- z: zm
- }]),
- backOrientation = faceOrientation([{
- x: xm,
- y: ym,
- z: zp
- }, {
- x: xp,
- y: ym,
- z: zp
- }, {
- x: xp,
- y: yp,
- z: zp
- }, {
- x: xm,
- y: yp,
- z: zp
- }]),
+ bottomOrientation = faceOrientation([
+ { x: xm, y: yp, z: zp },
+ { x: xp, y: yp, z: zp },
+ { x: xp, y: yp, z: zm },
+ { x: xm, y: yp, z: zm }
+ ]),
+ topOrientation = faceOrientation([
+ { x: xm, y: ym, z: zm },
+ { x: xp, y: ym, z: zm },
+ { x: xp, y: ym, z: zp },
+ { x: xm, y: ym, z: zp }
+ ]),
+ leftOrientation = faceOrientation([
+ { x: xm, y: ym, z: zm },
+ { x: xm, y: ym, z: zp },
+ { x: xm, y: yp, z: zp },
+ { x: xm, y: yp, z: zm }
+ ]),
+ rightOrientation = faceOrientation([
+ { x: xp, y: ym, z: zp },
+ { x: xp, y: ym, z: zm },
+ { x: xp, y: yp, z: zm },
+ { x: xp, y: yp, z: zp }
+ ]),
+ frontOrientation = faceOrientation([
+ { x: xm, y: yp, z: zm },
+ { x: xp, y: yp, z: zm },
+ { x: xp, y: ym, z: zm },
+ { x: xm, y: ym, z: zm }
+ ]),
+ backOrientation = faceOrientation([
+ { x: xm, y: ym, z: zp },
+ { x: xp, y: ym, z: zp },
+ { x: xp, y: yp, z: zp },
+ { x: xm, y: yp, z: zp }
+ ]),
defaultShowBottom = false,
defaultShowTop = false,
defaultShowLeft = false,
@@ -2467,7 +2611,7 @@
// The 'default' criteria to visible faces of the frame is looking up every
// axis to decide whenever the left/right//top/bottom sides of the frame
// will be shown
- each([].concat(chart.xAxis, chart.yAxis, chart.zAxis), function(axis) {
+ [].concat(chart.xAxis, chart.yAxis, chart.zAxis).forEach(function (axis) {
if (axis) {
if (axis.horiz) {
if (axis.opposite) {
@@ -2485,14 +2629,17 @@
}
});
- var getFaceOptions = function(sources, faceOrientation, defaultVisible) {
+ var getFaceOptions = function (sources, faceOrientation, defaultVisible) {
var faceAttrs = ['size', 'color', 'visible'];
var options = {};
+
for (var i = 0; i < faceAttrs.length; i++) {
var attr = faceAttrs[i];
+
for (var j = 0; j < sources.length; j++) {
if (typeof sources[j] === 'object') {
var val = sources[j][attr];
+
if (val !== undefined && val !== null) {
options[attr] = val;
break;
@@ -2501,6 +2648,7 @@
}
}
var isVisible = defaultVisible;
+
if (options.visible === true || options.visible === false) {
isVisible = options.visible;
} else if (options.visible === 'auto') {
@@ -2515,8 +2663,8 @@
};
};
- // docs @TODO: Add all frame options (left, right, top, bottom, front, back) to
- // apioptions JSDoc once the new system is up.
+ // docs @TODO: Add all frame options (left, right, top, bottom, front, back)
+ // to apioptions JSDoc once the new system is up.
var ret = {
// FIXME: Previously, left/right, top/bottom and front/back pairs shared
// size and color.
@@ -2567,26 +2715,30 @@
};
- // Decide the bast place to put axis title/labels based on the visible faces.
- // Ideally, The labels can only be on the edge between a visible face and an invisble one.
- // Also, the Y label should be one the left-most edge (right-most if opposite),
+ // Decide the bast place to put axis title/labels based on the visible
+ // faces. Ideally, The labels can only be on the edge between a visible face
+ // and an invisble one. Also, the Y label should be one the left-most edge
+ // (right-most if opposite),
if (options3d.axisLabelPosition === 'auto') {
- var isValidEdge = function(face1, face2) {
- return (face1.visible !== face2.visible) ||
- (face1.visible && face2.visible && (face1.frontFacing !== face2.frontFacing));
+ var isValidEdge = function (face1, face2) {
+ return (
+ (face1.visible !== face2.visible) ||
+ (
+ face1.visible &&
+ face2.visible &&
+ (face1.frontFacing !== face2.frontFacing)
+ )
+ );
};
var yEdges = [];
+
if (isValidEdge(ret.left, ret.front)) {
yEdges.push({
y: (ym + yp) / 2,
x: xm,
z: zm,
- xDir: {
- x: 1,
- y: 0,
- z: 0
- }
+ xDir: { x: 1, y: 0, z: 0 }
});
}
if (isValidEdge(ret.left, ret.back)) {
@@ -2594,11 +2746,7 @@
y: (ym + yp) / 2,
x: xm,
z: zp,
- xDir: {
- x: 0,
- y: 0,
- z: -1
- }
+ xDir: { x: 0, y: 0, z: -1 }
});
}
if (isValidEdge(ret.right, ret.front)) {
@@ -2606,11 +2754,7 @@
y: (ym + yp) / 2,
x: xp,
z: zm,
- xDir: {
- x: 0,
- y: 0,
- z: 1
- }
+ xDir: { x: 0, y: 0, z: 1 }
});
}
if (isValidEdge(ret.right, ret.back)) {
@@ -2618,25 +2762,18 @@
y: (ym + yp) / 2,
x: xp,
z: zp,
- xDir: {
- x: -1,
- y: 0,
- z: 0
- }
+ xDir: { x: -1, y: 0, z: 0 }
});
}
var xBottomEdges = [];
+
if (isValidEdge(ret.bottom, ret.front)) {
xBottomEdges.push({
x: (xm + xp) / 2,
y: yp,
z: zm,
- xDir: {
- x: 1,
- y: 0,
- z: 0
- }
+ xDir: { x: 1, y: 0, z: 0 }
});
}
if (isValidEdge(ret.bottom, ret.back)) {
@@ -2644,25 +2781,18 @@
x: (xm + xp) / 2,
y: yp,
z: zp,
- xDir: {
- x: -1,
- y: 0,
- z: 0
- }
+ xDir: { x: -1, y: 0, z: 0 }
});
}
var xTopEdges = [];
+
if (isValidEdge(ret.top, ret.front)) {
xTopEdges.push({
x: (xm + xp) / 2,
y: ym,
z: zm,
- xDir: {
- x: 1,
- y: 0,
- z: 0
- }
+ xDir: { x: 1, y: 0, z: 0 }
});
}
if (isValidEdge(ret.top, ret.back)) {
@@ -2670,25 +2800,18 @@
x: (xm + xp) / 2,
y: ym,
z: zp,
- xDir: {
- x: -1,
- y: 0,
- z: 0
- }
+ xDir: { x: -1, y: 0, z: 0 }
});
}
var zBottomEdges = [];
+
if (isValidEdge(ret.bottom, ret.left)) {
zBottomEdges.push({
z: (zm + zp) / 2,
y: yp,
x: xm,
- xDir: {
- x: 0,
- y: 0,
- z: -1
- }
+ xDir: { x: 0, y: 0, z: -1 }
});
}
if (isValidEdge(ret.bottom, ret.right)) {
@@ -2696,25 +2819,18 @@
z: (zm + zp) / 2,
y: yp,
x: xp,
- xDir: {
- x: 0,
- y: 0,
- z: 1
- }
+ xDir: { x: 0, y: 0, z: 1 }
});
}
var zTopEdges = [];
+
if (isValidEdge(ret.top, ret.left)) {
zTopEdges.push({
z: (zm + zp) / 2,
y: ym,
x: xm,
- xDir: {
- x: 0,
- y: 0,
- z: -1
- }
+ xDir: { x: 0, y: 0, z: -1 }
});
}
if (isValidEdge(ret.top, ret.right)) {
@@ -2722,31 +2838,39 @@
z: (zm + zp) / 2,
y: ym,
x: xp,
- xDir: {
- x: 0,
- y: 0,
- z: 1
- }
+ xDir: { x: 0, y: 0, z: 1 }
});
}
- var pickEdge = function(edges, axis, mult) {
+ var pickEdge = function (edges, axis, mult) {
if (edges.length === 0) {
return null;
- } else if (edges.length === 1) {
+ }
+ if (edges.length === 1) {
return edges[0];
}
var best = 0,
projections = perspective(edges, chart, false);
+
for (var i = 1; i < projections.length; i++) {
- if (mult * projections[i][axis] > mult * projections[best][axis]) {
+ if (
+ mult * projections[i][axis] >
+ mult * projections[best][axis]
+ ) {
best = i;
- } else if ((mult * projections[i][axis] === mult * projections[best][axis]) && (projections[i].z < projections[best].z)) {
+ } else if (
+ (
+ mult * projections[i][axis] ===
+ mult * projections[best][axis]
+ ) &&
+ (projections[i].z < projections[best].z)
+ ) {
best = i;
}
}
return edges[best];
};
+
ret.axes = {
y: {
'left': pickEdge(yEdges, 'x', -1),
@@ -2764,71 +2888,27 @@
} else {
ret.axes = {
y: {
- 'left': {
- x: xm,
- z: zm,
- xDir: {
- x: 1,
- y: 0,
- z: 0
- }
- },
- 'right': {
- x: xp,
- z: zm,
- xDir: {
- x: 0,
- y: 0,
- z: 1
- }
- }
+ 'left': { x: xm, z: zm, xDir: { x: 1, y: 0, z: 0 } },
+ 'right': { x: xp, z: zm, xDir: { x: 0, y: 0, z: 1 } }
},
x: {
- 'top': {
- y: ym,
- z: zm,
- xDir: {
- x: 1,
- y: 0,
- z: 0
- }
- },
- 'bottom': {
- y: yp,
- z: zm,
- xDir: {
- x: 1,
- y: 0,
- z: 0
- }
- }
+ 'top': { y: ym, z: zm, xDir: { x: 1, y: 0, z: 0 } },
+ 'bottom': { y: yp, z: zm, xDir: { x: 1, y: 0, z: 0 } }
},
z: {
'top': {
x: defaultShowLeft ? xp : xm,
y: ym,
- xDir: defaultShowLeft ? {
- x: 0,
- y: 0,
- z: 1
- } : {
- x: 0,
- y: 0,
- z: -1
- }
+ xDir: defaultShowLeft ?
+ { x: 0, y: 0, z: 1 } :
+ { x: 0, y: 0, z: -1 }
},
'bottom': {
x: defaultShowLeft ? xp : xm,
y: yp,
- xDir: defaultShowLeft ? {
- x: 0,
- y: 0,
- z: 1
- } : {
- x: 0,
- y: 0,
- z: -1
- }
+ xDir: defaultShowLeft ?
+ { x: 0, y: 0, z: 1 } :
+ { x: 0, y: 0, z: -1 }
}
}
};
@@ -2837,15 +2917,15 @@
return ret;
};
- /**
- * Animation setter for matrix property.
- */
- H.Fx.prototype.matrixSetter = function() {
+ // Animation setter for matrix property.
+ H.Fx.prototype.matrixSetter = function () {
var interpolated;
+
if (this.pos < 1 &&
- (H.isArray(this.start) || H.isArray(this.end))) {
+ (H.isArray(this.start) || H.isArray(this.end))) {
var start = this.start || [1, 0, 0, 1, 0, 0];
var end = this.end || [1, 0, 0, 1, 0, 0];
+
interpolated = [];
for (var i = 0; i < 6; i++) {
interpolated.push(this.pos * end[i] + (1 - this.pos) * start[i]);
@@ -2863,55 +2943,59 @@
};
/**
- * Note: As of v5.0.12, `frame.left` or `frame.right` should be used
- * instead.
- *
+ * Note: As of v5.0.12, `frame.left` or `frame.right` should be used instead.
+ *
* The side for the frame around a 3D chart.
- *
- * @since 4.0
- * @product highcharts
+ *
+ * @deprecated
+ * @since 4.0
+ * @product highcharts
* @apioption chart.options3d.frame.side
*/
/**
* The color of the panel.
- *
- * @type {Color}
- * @default transparent
- * @since 4.0
- * @product highcharts
+ *
+ * @deprecated
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @default transparent
+ * @since 4.0
+ * @product highcharts
* @apioption chart.options3d.frame.side.color
*/
/**
* The thickness of the panel.
- *
- * @type {Number}
- * @default 1
- * @since 4.0
- * @product highcharts
+ *
+ * @deprecated
+ * @type {number}
+ * @default 1
+ * @since 4.0
+ * @product highcharts
* @apioption chart.options3d.frame.side.size
*/
-
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* Extenstion for 3d axes
*
* License: www.highcharts.com/license
*/
- var ZAxis,
+
+
+ var ZAxis,
+ addEvent = H.addEvent,
Axis = H.Axis,
Chart = H.Chart,
deg2rad = H.deg2rad,
- each = H.each,
extend = H.extend,
merge = H.merge,
perspective = H.perspective,
+ perspective3D = H.perspective3D,
pick = H.pick,
shapeArea = H.shapeArea,
splat = H.splat,
@@ -2926,36 +3010,45 @@
/**
* Defines how the labels are be repositioned according to the 3D chart
* orientation.
+ *
* - `'offset'`: Maintain a fixed horizontal/vertical distance from the
- * tick marks, despite the chart orientation. This is the backwards
- * compatible behavior, and causes skewing of X and Z axes.
+ * tick marks, despite the chart orientation. This is the backwards
+ * compatible behavior, and causes skewing of X and Z axes.
+ *
* - `'chart'`: Preserve 3D position relative to the chart.
* This looks nice, but hard to read if the text isn't
* forward-facing.
+ *
* - `'flap'`: Rotated text along the axis to compensate for the chart
- * orientation. This tries to maintain text as legible as possible on
- * all orientations.
+ * orientation. This tries to maintain text as legible as possible
+ * on all orientations.
+ *
* - `'ortho'`: Rotated text along the axis direction so that the labels
- * are orthogonal to the axis. This is very similar to `'flap'`, but
- * prevents skewing the labels (X and Y scaling are still present).
- *
+ * are orthogonal to the axis. This is very similar to `'flap'`,
+ * but prevents skewing the labels (X and Y scaling are still
+ * present).
+ *
+ * @sample highcharts/3d/skewed-labels/
+ * Skewed labels
+ *
+ * @since 5.0.15
* @validvalue ['offset', 'chart', 'flap', 'ortho']
- * @sample highcharts/3d/skewed-labels/ Skewed labels
- * @since 5.0.15
- * @product highcharts
+ * @product highcharts
*/
position3d: 'offset',
/**
- * If enabled, the axis labels will skewed to follow the perspective.
- *
+ * If enabled, the axis labels will skewed to follow the perspective.
+ *
* This will fix overlapping labels and titles, but texts become less
* legible due to the distortion.
- *
+ *
* The final appearance depends heavily on `labels.position3d`.
- *
- * @since 5.0.15
- * @sample highcharts/3d/skewed-labels/ Skewed labels
+ *
+ * @sample highcharts/3d/skewed-labels/
+ * Skewed labels
+ *
+ * @since 5.0.15
* @product highcharts
*/
skew3d: false
@@ -2964,42 +3057,49 @@
/**
* Defines how the title is repositioned according to the 3D chart
* orientation.
+ *
* - `'offset'`: Maintain a fixed horizontal/vertical distance from the
* tick marks, despite the chart orientation. This is the backwards
* compatible behavior, and causes skewing of X and Z axes.
+ *
* - `'chart'`: Preserve 3D position relative to the chart.
* This looks nice, but hard to read if the text isn't
* forward-facing.
+ *
* - `'flap'`: Rotated text along the axis to compensate for the chart
* orientation. This tries to maintain text as legible as possible on
* all orientations.
+ *
* - `'ortho'`: Rotated text along the axis direction so that the labels
* are orthogonal to the axis. This is very similar to `'flap'`, but
* prevents skewing the labels (X and Y scaling are still present).
- * - `null`: Will use the config from `labels.position3d`
- *
- * @validvalue ['offset', 'chart', 'flap', 'ortho', null]
- * @type {String}
- * @since 5.0.15
- * @sample highcharts/3d/skewed-labels/ Skewed labels
- * @product highcharts
+ *
+ * - `undefined`: Will use the config from `labels.position3d`
+ *
+ * @sample highcharts/3d/skewed-labels/
+ * Skewed labels
+ *
+ * @type {"offset"|"chart"|"flap"|"ortho"|null}
+ * @since 5.0.15
+ * @product highcharts
*/
position3d: null,
/**
* If enabled, the axis title will skewed to follow the perspective.
- *
+ *
* This will fix overlapping labels and titles, but texts become less
* legible due to the distortion.
- *
+ *
* The final appearance depends heavily on `title.position3d`.
- *
+ *
* A `null` value will use the config from `labels.skew3d`.
- *
- * @validvalue [false, true, null]
- * @type {Boolean}
- * @sample highcharts/3d/skewed-labels/ Skewed labels
- * @since 5.0.15
+ *
+ * @sample highcharts/3d/skewed-labels/
+ * Skewed labels
+ *
+ * @type {boolean|null}
+ * @since 5.0.15
* @product highcharts
*/
skew3d: null
@@ -3009,9 +3109,9 @@
merge(true, Axis.prototype.defaultOptions, extendedOptions);
- wrap(Axis.prototype, 'setOptions', function(proceed, userOptions) {
+ addEvent(Axis, 'afterSetOptions', function () {
var options;
- proceed.call(this, userOptions);
+
if (this.chart.is3d && this.chart.is3d() && this.coll !== 'colorAxis') {
options = this.options;
options.tickWidth = pick(options.tickWidth, 0);
@@ -3019,7 +3119,7 @@
}
});
- wrap(Axis.prototype, 'getPlotLinePath', function(proceed) {
+ wrap(Axis.prototype, 'getPlotLinePath', function (proceed) {
var path = proceed.apply(this, [].slice.call(arguments, 1));
// Do not do this if the chart is not 3D
@@ -3037,29 +3137,14 @@
frame = chart.frame3d;
var pArr = [
- this.swapZ({
- x: path[1],
- y: path[2],
- z: 0
- }),
- this.swapZ({
- x: path[1],
- y: path[2],
- z: d
- }),
- this.swapZ({
- x: path[4],
- y: path[5],
- z: 0
- }),
- this.swapZ({
- x: path[4],
- y: path[5],
- z: d
- })
+ this.swapZ({ x: path[1], y: path[2], z: 0 }),
+ this.swapZ({ x: path[1], y: path[2], z: d }),
+ this.swapZ({ x: path[4], y: path[5], z: 0 }),
+ this.swapZ({ x: path[4], y: path[5], z: d })
];
var pathSegments = [];
+
if (!this.horiz) { // Y-Axis
if (frame.front.visible) {
pathSegments.push(pArr[0], pArr[2]);
@@ -3107,7 +3192,7 @@
});
// Do not draw axislines in 3D
- wrap(Axis.prototype, 'getLinePath', function(proceed) {
+ wrap(Axis.prototype, 'getLinePath', function (proceed) {
// Do not do this if the chart is not 3D
if (!this.chart.is3d() || this.coll === 'colorAxis') {
return proceed.apply(this, [].slice.call(arguments, 1));
@@ -3116,7 +3201,7 @@
return [];
});
- wrap(Axis.prototype, 'getPlotBandPath', function(proceed) {
+ wrap(Axis.prototype, 'getPlotBandPath', function (proceed) {
// Do not do this if the chart is not 3D
if (!this.chart.is3d() || this.coll === 'colorAxis') {
return proceed.apply(this, [].slice.call(arguments, 1));
@@ -3136,7 +3221,8 @@
'L', fromPath[i + 4], fromPath[i + 5],
'L', toPath[i + 4], toPath[i + 5],
'L', toPath[i + 1], toPath[i + 2],
- 'Z');
+ 'Z'
+ );
}
}
@@ -3171,17 +3257,9 @@
offsetX = 0,
offsetY = 0,
vecX,
- vecY = {
- x: 0,
- y: 1,
- z: 0
- };
+ vecY = { x: 0, y: 1, z: 0 };
- pos = axis.swapZ({
- x: pos.x,
- y: pos.y,
- z: 0
- });
+ pos = axis.swapZ({ x: pos.x, y: pos.y, z: 0 });
if (axis.isZAxis) { // Z Axis
@@ -3234,11 +3312,7 @@
pos.z = frame.axes.y.right.z;
vecX = frame.axes.y.right.xDir;
// Rotate 90º on opposite edge
- vecX = {
- x: vecX.z,
- y: vecX.y,
- z: -vecX.x
- };
+ vecX = { x: vecX.z, y: vecX.y, z: -vecX.x };
} else {
if (frame.axes.y.left === null) {
return {};
@@ -3257,44 +3331,30 @@
} else if (positionMode === 'flap') {
// Labels are be rotated around the axis direction to face the screen
if (!axis.horiz) { // Y Axis
- vecX = {
- x: Math.cos(beta),
- y: 0,
- z: Math.sin(beta)
- };
+ vecX = { x: Math.cos(beta), y: 0, z: Math.sin(beta) };
} else { // X and Z Axis
var sin = Math.sin(alpha);
var cos = Math.cos(alpha);
+
if (axis.opposite) {
sin = -sin;
}
if (reverseFlap) {
sin = -sin;
}
- vecY = {
- x: vecX.z * sin,
- y: cos,
- z: -vecX.x * sin
- };
+ vecY = { x: vecX.z * sin, y: cos, z: -vecX.x * sin };
}
} else if (positionMode === 'ortho') {
// Labels will be rotated to be ortogonal to the axis
if (!axis.horiz) { // Y Axis
- vecX = {
- x: Math.cos(beta),
- y: 0,
- z: Math.sin(beta)
- };
+ vecX = { x: Math.cos(beta), y: 0, z: Math.sin(beta) };
} else { // X and Z Axis
var sina = Math.sin(alpha);
var cosa = Math.cos(alpha);
var sinb = Math.sin(beta);
var cosb = Math.cos(beta);
- var vecZ = {
- x: sinb * cosa,
- y: -sina,
- z: -cosa * cosb
- };
+ var vecZ = { x: sinb * cosa, y: -sina, z: -cosa * cosb };
+
vecY = {
x: vecX.y * vecZ.z - vecX.z * vecZ.y,
y: vecX.z * vecZ.x - vecX.x * vecZ.z,
@@ -3303,24 +3363,17 @@
var scale = 1 / Math.sqrt(
vecY.x * vecY.x + vecY.y * vecY.y + vecY.z * vecY.z
);
+
if (reverseFlap) {
scale = -scale;
}
- vecY = {
- x: scale * vecY.x,
- y: scale * vecY.y,
- z: scale * vecY.z
- };
+ vecY = { x: scale * vecY.x, y: scale * vecY.y, z: scale * vecY.z };
}
} else { // positionMode == 'offset'
// Labels will be skewd to maintain vertical / horizontal offsets from
// axis
if (!axis.horiz) { // Y Axis
- vecX = {
- x: Math.cos(beta),
- y: 0,
- z: Math.sin(beta)
- };
+ vecX = { x: Math.cos(beta), y: 0, z: Math.sin(beta) };
} else { // X and Z Axis
vecY = {
x: Math.sin(beta) * Math.sin(alpha),
@@ -3339,40 +3392,18 @@
// Check if the label text would be mirrored
var isMirrored = shapeArea(perspective([
pos,
- {
- x: pos.x + vecX.x,
- y: pos.y + vecX.y,
- z: pos.z + vecX.z
- },
- {
- x: pos.x + vecY.x,
- y: pos.y + vecY.y,
- z: pos.z + vecY.z
- }
+ { x: pos.x + vecX.x, y: pos.y + vecX.y, z: pos.z + vecX.z },
+ { x: pos.x + vecY.x, y: pos.y + vecY.y, z: pos.z + vecY.z }
], axis.chart)) < 0;
+
if (isMirrored) {
- vecX = {
- x: -vecX.x,
- y: -vecX.y,
- z: -vecX.z
- };
+ vecX = { x: -vecX.x, y: -vecX.y, z: -vecX.z };
}
- var pointsProjected = perspective([{
- x: pos.x,
- y: pos.y,
- z: pos.z
- },
- {
- x: pos.x + vecX.x,
- y: pos.y + vecX.y,
- z: pos.z + vecX.z
- },
- {
- x: pos.x + vecY.x,
- y: pos.y + vecY.y,
- z: pos.z + vecY.z
- }
+ var pointsProjected = perspective([
+ { x: pos.x, y: pos.y, z: pos.z },
+ { x: pos.x + vecX.x, y: pos.y + vecX.y, z: pos.z + vecX.z },
+ { x: pos.x + vecY.x, y: pos.y + vecY.y, z: pos.z + vecY.z }
], axis.chart);
projected.matrix = [
@@ -3387,8 +3418,6 @@
projected.y * projected.matrix[2];
projected.matrix[5] -= projected.x * projected.matrix[1] +
projected.y * projected.matrix[3];
- } else {
- projected.matrix = null;
}
return projected;
@@ -3397,64 +3426,54 @@
/*
Tick extensions
*/
- wrap(Tick.prototype, 'getMarkPath', function(proceed) {
+
+ wrap(Tick.prototype, 'getMarkPath', function (proceed) {
var path = proceed.apply(this, [].slice.call(arguments, 1));
var pArr = [
- fix3dPosition(this.axis, {
- x: path[1],
- y: path[2],
- z: 0
- }),
- fix3dPosition(this.axis, {
- x: path[4],
- y: path[5],
- z: 0
- })
+ fix3dPosition(this.axis, { x: path[1], y: path[2], z: 0 }),
+ fix3dPosition(this.axis, { x: path[4], y: path[5], z: 0 })
];
return this.axis.chart.renderer.toLineSegments(pArr);
});
- wrap(Tick.prototype, 'getLabelPosition', function(proceed) {
- var pos = proceed.apply(this, [].slice.call(arguments, 1));
- return fix3dPosition(this.axis, pos);
+ addEvent(Tick, 'afterGetLabelPosition', function (e) {
+ extend(e.pos, fix3dPosition(this.axis, e.pos));
});
- wrap(Axis.prototype, 'getTitlePosition', function(proceed) {
+ wrap(Axis.prototype, 'getTitlePosition', function (proceed) {
var pos = proceed.apply(this, [].slice.call(arguments, 1));
+
return fix3dPosition(this, pos, true);
});
- wrap(Axis.prototype, 'drawCrosshair', function(proceed) {
- var args = arguments;
+ addEvent(Axis, 'drawCrosshair', function (e) {
if (this.chart.is3d() && this.coll !== 'colorAxis') {
- if (args[2]) {
- args[2] = {
- plotX: args[2].plotXold || args[2].plotX,
- plotY: args[2].plotYold || args[2].plotY
- };
+ if (e.point) {
+ e.point.crosshairPos = this.isXAxis ?
+ e.point.axisXpos :
+ this.len - (e.point.axisYpos);
}
}
- proceed.apply(this, [].slice.call(args, 1));
});
- wrap(Axis.prototype, 'destroy', function(proceed) {
- each(['backFrame', 'bottomFrame', 'sideFrame'], function(prop) {
+ addEvent(Axis, 'destroy', function () {
+ ['backFrame', 'bottomFrame', 'sideFrame'].forEach(function (prop) {
if (this[prop]) {
this[prop] = this[prop].destroy();
}
}, this);
- proceed.apply(this, [].slice.call(arguments, 1));
});
/*
Z-AXIS
- */
+ */
- Axis.prototype.swapZ = function(p, insidePlotArea) {
+ Axis.prototype.swapZ = function (p, insidePlotArea) {
if (this.isZAxis) {
var plotLeft = insidePlotArea ? 0 : this.chart.plotLeft;
+
return {
x: plotLeft + p.z,
y: p.y,
@@ -3464,13 +3483,13 @@
return p;
};
- ZAxis = H.ZAxis = function() {
+ ZAxis = H.ZAxis = function () {
this.init.apply(this, arguments);
};
extend(ZAxis.prototype, Axis.prototype);
extend(ZAxis.prototype, {
isZAxis: true,
- setOptions: function(userOptions) {
+ setOptions: function (userOptions) {
userOptions = merge({
offset: 0,
lineWidth: 0
@@ -3478,12 +3497,12 @@
Axis.prototype.setOptions.call(this, userOptions);
this.coll = 'zAxis';
},
- setAxisSize: function() {
+ setAxisSize: function () {
Axis.prototype.setAxisSize.call(this);
this.width = this.len = this.chart.options.chart.options3d.depth;
this.right = this.chart.chartWidth - this.width - this.left;
},
- getSeriesExtremes: function() {
+ getSeriesExtremes: function () {
var axis = this,
chart = axis.chart;
@@ -3500,7 +3519,7 @@
}
// loop through this axis' series
- each(axis.series, function(series) {
+ axis.series.forEach(function (series) {
if (series.visible || !chart.options.chart.ignoreHiddenSeries) {
@@ -3532,56 +3551,121 @@
});
- /**
- * Extend the chart getAxes method to also get the color axis
- */
- wrap(Chart.prototype, 'getAxes', function(proceed) {
+ // Get the Z axis in addition to the default X and Y.
+ addEvent(Chart, 'afterGetAxes', function () {
var chart = this,
options = this.options,
zAxisOptions = options.zAxis = splat(options.zAxis || {});
- proceed.call(this);
-
if (!chart.is3d()) {
return;
}
this.zAxis = [];
- each(zAxisOptions, function(axisOptions, i) {
+ zAxisOptions.forEach(function (axisOptions, i) {
axisOptions.index = i;
// Z-Axis is shown horizontally, so it's kind of a X-Axis
axisOptions.isX = true;
var zAxis = new ZAxis(chart, axisOptions);
+
zAxis.setScale();
});
});
+ // Wrap getSlotWidth function to calculate individual width value for each slot
+ // (#8042).
+ wrap(Axis.prototype, 'getSlotWidth', function (proceed, tick) {
+ if (this.chart.is3d() &&
+ tick &&
+ tick.label &&
+ this.categories &&
+ this.chart.frameShapes
+ ) {
+ var chart = this.chart,
+ ticks = this.ticks,
+ gridGroup = this.gridGroup.element.childNodes,
+ firstGridLine = gridGroup[0].getBBox(),
+ frame3DLeft = chart.frameShapes.left.getBBox(),
+ options3d = chart.options.chart.options3d,
+ origin = {
+ x: chart.plotWidth / 2,
+ y: chart.plotHeight / 2,
+ z: options3d.depth / 2,
+ vd: pick(options3d.depth, 1) * pick(options3d.viewDistance, 0)
+ },
+ labelPos,
+ prevLabelPos,
+ nextLabelPos,
+ slotWidth,
+ tickId = tick.pos,
+ prevTick = ticks[tickId - 1],
+ nextTick = ticks[tickId + 1];
+
+ // Check whether the tick is not the first one and previous tick exists,
+ // then calculate position of previous label.
+ if (tickId !== 0 && prevTick && prevTick.label.xy) { // #8621
+ prevLabelPos = perspective3D({
+ x: prevTick.label.xy.x,
+ y: prevTick.label.xy.y,
+ z: null
+ }, origin, origin.vd);
+ }
+ // If next label position is defined, then recalculate its position
+ // basing on the perspective.
+ if (nextTick && nextTick.label.xy) {
+ nextLabelPos = perspective3D({
+ x: nextTick.label.xy.x,
+ y: nextTick.label.xy.y,
+ z: null
+ }, origin, origin.vd);
+ }
+ labelPos = {
+ x: tick.label.xy.x,
+ y: tick.label.xy.y,
+ z: null
+ };
+
+ labelPos = perspective3D(labelPos, origin, origin.vd);
+
+ // If tick is first one, check whether next label position is already
+ // calculated, then return difference between the first and the second
+ // label. If there is no next label position calculated, return the
+ // difference between the first grid line and left 3d frame.
+ slotWidth = Math.abs(
+ prevLabelPos ?
+ labelPos.x - prevLabelPos.x : nextLabelPos ?
+ nextLabelPos.x - labelPos.x :
+ firstGridLine.x - frame3DLeft.x
+ );
+ return slotWidth;
+ }
+ return proceed.apply(this, [].slice.call(arguments, 1));
+ });
+
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* Extension to the Series object in 3D charts.
*
* License: www.highcharts.com/license
*/
- var perspective = H.perspective,
- pick = H.pick,
- wrap = H.wrap;
- // Wrap the translate method to post-translate points into 3D perspective
- wrap(H.Series.prototype, 'translate', function(proceed) {
- proceed.apply(this, [].slice.call(arguments, 1));
+
+ var addEvent = H.addEvent,
+ perspective = H.perspective,
+ pick = H.pick;
+
+ // Wrap the translate method to post-translate points into 3D perspective
+ addEvent(H.Series, 'afterTranslate', function () {
if (this.chart.is3d()) {
this.translate3dPoints();
}
-
});
- /**
- * Translate the plotX, plotY properties and add plotZ.
- */
- H.Series.prototype.translate3dPoints = function() {
+ // Translate the plotX, plotY properties and add plotZ.
+ H.Series.prototype.translate3dPoints = function () {
var series = this,
chart = series.chart,
zAxis = pick(series.zAxis, chart.options.zAxis[0]),
@@ -3603,12 +3687,18 @@
rawPoint.isInside = rawPoint.isInside ?
(zValue >= zAxis.min && zValue <= zAxis.max) :
false;
+ } else {
+ rawPoint.plotZ = 0;
}
+ rawPoint.axisXpos = rawPoint.plotX;
+ rawPoint.axisYpos = rawPoint.plotY;
+ rawPoint.axisZpos = rawPoint.plotZ;
+
rawPoints.push({
- x: pick(rawPoint.plotXold, rawPoint.plotX),
- y: pick(rawPoint.plotYold, rawPoint.plotY),
- z: pick(rawPoint.plotZold, rawPoint.plotZ)
+ x: rawPoint.plotX,
+ y: rawPoint.plotY,
+ z: rawPoint.plotZ
});
}
@@ -3618,76 +3708,70 @@
rawPoint = series.data[i];
projectedPoint = projectedPoints[i];
- rawPoint.plotXold = rawPoint.plotX;
- rawPoint.plotYold = rawPoint.plotY;
- rawPoint.plotZold = rawPoint.plotZ;
-
rawPoint.plotX = projectedPoint.x;
rawPoint.plotY = projectedPoint.y;
rawPoint.plotZ = projectedPoint.z;
}
};
-
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
- var each = H.each,
+
+
+
+ var addEvent = H.addEvent,
perspective = H.perspective,
pick = H.pick,
Series = H.Series,
seriesTypes = H.seriesTypes,
- inArray = H.inArray,
svg = H.svg,
wrap = H.wrap;
-
-
/**
- * Depth of the columns in a 3D column chart. Requires `highcharts-3d.
- * js`.
- *
- * @type {Number}
- * @default 25
- * @since 4.0
- * @product highcharts
+ * Depth of the columns in a 3D column chart. Requires `highcharts-3d.js`.
+ *
+ * @type {number}
+ * @default 25
+ * @since 4.0
+ * @product highcharts
* @apioption plotOptions.column.depth
*/
/**
* 3D columns only. The color of the edges. Similar to `borderColor`,
* except it defaults to the same color as the column.
- *
- * @type {Color}
- * @product highcharts
+ *
+ * @type {Highcharts.ColorString}
+ * @product highcharts
* @apioption plotOptions.column.edgeColor
*/
/**
* 3D columns only. The width of the colored edges.
- *
- * @type {Number}
- * @default 1
- * @product highcharts
+ *
+ * @type {number}
+ * @default 1
+ * @product highcharts
* @apioption plotOptions.column.edgeWidth
*/
/**
* The spacing between columns on the Z Axis in a 3D chart. Requires
* `highcharts-3d.js`.
- *
- * @type {Number}
- * @default 1
- * @since 4.0
- * @product highcharts
+ *
+ * @type {number}
+ * @default 1
+ * @since 4.0
+ * @product highcharts
* @apioption plotOptions.column.groupZPadding
*/
- wrap(seriesTypes.column.prototype, 'translate', function(proceed) {
+ wrap(seriesTypes.column.prototype, 'translate', function (proceed) {
proceed.apply(this, [].slice.call(arguments, 1));
// Do not do this if the chart is not 3D
@@ -3696,16 +3780,30 @@
}
});
- seriesTypes.column.prototype.translate3dPoints = function() {};
- seriesTypes.column.prototype.translate3dShapes = function() {
+ // In 3D we need to pass point.outsidePlot option to the justifyDataLabel
+ // method for disabling justifying dataLabels in columns outside plot
+ wrap(H.Series.prototype, 'alignDataLabel', function (proceed) {
+ arguments[3].outside3dPlot = arguments[1].outside3dPlot;
+ proceed.apply(this, [].slice.call(arguments, 1));
+ });
+
+ // Don't use justifyDataLabel when point is outsidePlot
+ wrap(H.Series.prototype, 'justifyDataLabel', function (proceed) {
+ return !(arguments[2].outside3dPlot) ?
+ proceed.apply(this, [].slice.call(arguments, 1)) :
+ false;
+ });
+
+ seriesTypes.column.prototype.translate3dPoints = function () {};
+ seriesTypes.column.prototype.translate3dShapes = function () {
var series = this,
chart = series.chart,
seriesOptions = series.options,
depth = seriesOptions.depth || 25,
stack = seriesOptions.stacking ?
- (seriesOptions.stack || 0) :
- series.index, // #4743
+ (seriesOptions.stack || 0) :
+ series.index, // #4743
z = stack * (depth + (seriesOptions.groupZPadding || 1)),
borderCrisp = series.borderWidth % 2 ? 0.5 : 0;
@@ -3718,20 +3816,19 @@
}
z += (seriesOptions.groupZPadding || 1);
- each(series.data, function(point) {
+ series.data.forEach(function (point) {
+ // #7103 Reset outside3dPlot flag
+ point.outside3dPlot = null;
if (point.y !== null) {
var shapeArgs = point.shapeArgs,
tooltipPos = point.tooltipPos,
// Array for final shapeArgs calculation.
// We are checking two dimensions (x and y).
- dimensions = [
- ['x', 'width'],
- ['y', 'height']
- ],
+ dimensions = [['x', 'width'], ['y', 'height']],
borderlessBase; // Crisped rects can have +/- 0.5 pixels offset.
// #3131 We need to check if column is inside plotArea.
- each(dimensions, function(d) {
+ dimensions.forEach(function (d) {
borderlessBase = shapeArgs[d[0]] - borderCrisp;
if (borderlessBase < 0) {
// If borderLessBase is smaller than 0, it is needed to set
@@ -3742,36 +3839,49 @@
borderlessBase = 0;
}
if (
- borderlessBase + shapeArgs[d[1]] > series[d[0] + 'Axis'].len &&
- shapeArgs[d[1]] !== 0 // Do not change height/width of column if 0.
- // #6708
+ (
+ borderlessBase + shapeArgs[d[1]] >
+ series[d[0] + 'Axis'].len
+ ) &&
+ // Do not change height/width of column if 0 (#6708)
+ shapeArgs[d[1]] !== 0
) {
- shapeArgs[d[1]] = series[d[0] + 'Axis'].len - shapeArgs[d[0]];
+ shapeArgs[d[1]] =
+ series[d[0] + 'Axis'].len - shapeArgs[d[0]];
}
if (
- (shapeArgs[d[1]] !== 0) && // Do not remove columns with zero height/width.
+ // Do not remove columns with zero height/width.
+ (shapeArgs[d[1]] !== 0) &&
(
shapeArgs[d[0]] >= series[d[0] + 'Axis'].len ||
shapeArgs[d[0]] + shapeArgs[d[1]] <= borderCrisp
)
) {
- for (var key in shapeArgs) { // Set args to 0 if column is outside the chart.
+ // Set args to 0 if column is outside the chart.
+ for (var key in shapeArgs) {
shapeArgs[key] = 0;
}
+ // #7103 outside3dPlot flag is set on Points which are
+ // currently outside of plot.
+ point.outside3dPlot = true;
}
});
- point.shapeType = 'cuboid';
+ // Change from 2d to 3d
+ if (point.shapeType === 'rect') {
+ point.shapeType = 'cuboid';
+ }
+
shapeArgs.z = z;
shapeArgs.depth = depth;
shapeArgs.insidePlotArea = true;
// Translate the tooltip position in 3d space
- tooltipPos = perspective([{
- x: tooltipPos[0],
- y: tooltipPos[1],
- z: z
- }], chart, true)[0];
+ tooltipPos = perspective(
+ [{ x: tooltipPos[0], y: tooltipPos[1], z: z }],
+ chart,
+ true
+ )[0];
point.tooltipPos = [tooltipPos.x, tooltipPos.y];
}
});
@@ -3779,7 +3889,7 @@
series.z = z;
};
- wrap(seriesTypes.column.prototype, 'animate', function(proceed) {
+ wrap(seriesTypes.column.prototype, 'animate', function (proceed) {
if (!this.chart.is3d()) {
proceed.apply(this, [].slice.call(arguments, 1));
} else {
@@ -3791,29 +3901,39 @@
if (svg) { // VML is too slow anyway
if (init) {
- each(series.data, function(point) {
+ series.data.forEach(function (point) {
if (point.y !== null) {
point.height = point.shapeArgs.height;
point.shapey = point.shapeArgs.y; // #2968
point.shapeArgs.height = 1;
if (!reversed) {
if (point.stackY) {
- point.shapeArgs.y = point.plotY + yAxis.translate(point.stackY);
+ point.shapeArgs.y =
+ point.plotY + yAxis.translate(point.stackY);
} else {
- point.shapeArgs.y = point.plotY + (point.negative ? -point.height : point.height);
+ point.shapeArgs.y =
+ point.plotY +
+ (
+ point.negative ?
+ -point.height :
+ point.height
+ );
}
}
}
});
- } else { // run the animation
- each(series.data, function(point) {
+ } else { // run the animation
+ series.data.forEach(function (point) {
if (point.y !== null) {
point.shapeArgs.height = point.height;
point.shapeArgs.y = point.shapey; // #2968
// null value do not have a graphic
if (point.graphic) {
- point.graphic.animate(point.shapeArgs, series.options.animation);
+ point.graphic.animate(
+ point.shapeArgs,
+ series.options.animation
+ );
}
}
});
@@ -3828,51 +3948,56 @@
}
});
- /*
- * In case of 3d columns there is no sense to add this columns
- * to a specific series group - if series is added to a group
- * all columns will have the same zIndex in comparison with different series
- */
-
- wrap(seriesTypes.column.prototype, 'plotGroup', function(proceed, prop, name, visibility, zIndex, parent) {
- if (this.chart.is3d() && parent && !this[prop]) {
- if (!this.chart.columnGroup) {
- this.chart.columnGroup = this.chart.renderer.g('columnGroup').add(parent);
+ // In case of 3d columns there is no sense to add this columns to a specific
+ // series group - if series is added to a group all columns will have the same
+ // zIndex in comparison with different series.
+ wrap(
+ seriesTypes.column.prototype,
+ 'plotGroup',
+ function (proceed, prop, name, visibility, zIndex, parent) {
+ if (this.chart.is3d() && parent && !this[prop]) {
+ if (!this.chart.columnGroup) {
+ this.chart.columnGroup =
+ this.chart.renderer.g('columnGroup').add(parent);
+ }
+ this[prop] = this.chart.columnGroup;
+ this.chart.columnGroup.attr(this.getPlotBox());
+ this[prop].survive = true;
}
- this[prop] = this.chart.columnGroup;
- this.chart.columnGroup.attr(this.getPlotBox());
- this[prop].survive = true;
+ return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
}
- return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
- });
-
- /*
- * When series is not added to group it is needed to change
- * setVisible method to allow correct Legend funcionality
- * This wrap is basing on pie chart series
- */
- wrap(seriesTypes.column.prototype, 'setVisible', function(proceed, vis) {
- var series = this,
- pointVis;
- if (series.chart.is3d()) {
- each(series.data, function(point) {
- point.visible = point.options.visible = vis = vis === undefined ? !point.visible : vis;
- pointVis = vis ? 'visible' : 'hidden';
- series.options.data[inArray(point, series.data)] = point.options;
- if (point.graphic) {
- point.graphic.attr({
- visibility: pointVis
- });
- }
- });
+ );
+
+ // When series is not added to group it is needed to change setVisible method to
+ // allow correct Legend funcionality. This wrap is basing on pie chart series.
+ wrap(
+ seriesTypes.column.prototype,
+ 'setVisible',
+ function (proceed, vis) {
+ var series = this,
+ pointVis;
+
+ if (series.chart.is3d()) {
+ series.data.forEach(function (point) {
+ point.visible = point.options.visible = vis =
+ vis === undefined ? !point.visible : vis;
+ pointVis = vis ? 'visible' : 'hidden';
+ series.options.data[series.data.indexOf(point)] =
+ point.options;
+ if (point.graphic) {
+ point.graphic.attr({
+ visibility: pointVis
+ });
+ }
+ });
+ }
+ proceed.apply(this, Array.prototype.slice.call(arguments, 1));
}
- proceed.apply(this, Array.prototype.slice.call(arguments, 1));
- });
-
- wrap(seriesTypes.column.prototype, 'init', function(proceed) {
- proceed.apply(this, [].slice.call(arguments, 1));
+ );
- if (this.chart.is3d()) {
+ seriesTypes.column.prototype.handle3dGrouping = true;
+ addEvent(Series, 'afterInit', function () {
+ if (this.chart.is3d() && this.handle3dGrouping) {
var seriesOptions = this.options,
grouping = seriesOptions.grouping,
stacking = seriesOptions.stacking,
@@ -3883,14 +4008,17 @@
var stacks = this.chart.retrieveStacks(stacking),
stack = seriesOptions.stack || 0,
i; // position within the stack
+
for (i = 0; i < stacks[stack].series.length; i++) {
if (stacks[stack].series[i] === this) {
break;
}
}
- z = (10 * (stacks.totalStacks - stacks[stack].position)) + (reversedStacks ? i : -i); // #4369
+ z = (10 * (stacks.totalStacks - stacks[stack].position)) +
+ (reversedStacks ? i : -i); // #4369
- // In case when axis is reversed, columns are also reversed inside the group (#3737)
+ // In case when axis is reversed, columns are also reversed inside
+ // the group (#3737)
if (!this.xAxis.reversed) {
z = (stacks.totalStacks * 10) - z;
}
@@ -3900,7 +4028,6 @@
}
});
-
function pointAttribs(proceed) {
var attr = proceed.apply(this, [].slice.call(arguments, 1));
@@ -3916,36 +4043,39 @@
wrap(seriesTypes.column.prototype, 'pointAttribs', pointAttribs);
if (seriesTypes.columnrange) {
wrap(seriesTypes.columnrange.prototype, 'pointAttribs', pointAttribs);
- seriesTypes.columnrange.prototype.plotGroup = seriesTypes.column.prototype.plotGroup;
- seriesTypes.columnrange.prototype.setVisible = seriesTypes.column.prototype.setVisible;
+ seriesTypes.columnrange.prototype.plotGroup =
+ seriesTypes.column.prototype.plotGroup;
+ seriesTypes.columnrange.prototype.setVisible =
+ seriesTypes.column.prototype.setVisible;
}
+ wrap(Series.prototype, 'alignDataLabel', function (proceed) {
- wrap(Series.prototype, 'alignDataLabel', function(proceed) {
-
- // Only do this for 3D columns and columnranges
- if (this.chart.is3d() && (this.type === 'column' || this.type === 'columnrange')) {
+ // Only do this for 3D columns and it's derived series
+ if (
+ this.chart.is3d() &&
+ this instanceof seriesTypes.column
+ ) {
var series = this,
chart = series.chart;
var args = arguments,
- alignTo = args[4];
+ alignTo = args[4],
+ point = args[1];
+
+ var pos = ({ x: alignTo.x, y: alignTo.y, z: series.z });
- var pos = ({
- x: alignTo.x,
- y: alignTo.y,
- z: series.z
- });
pos = perspective([pos], chart, true)[0];
alignTo.x = pos.x;
- alignTo.y = pos.y;
+ // #7103 If point is outside of plotArea, hide data label.
+ alignTo.y = point.outside3dPlot ? -9e9 : pos.y;
}
proceed.apply(this, [].slice.call(arguments, 1));
});
// Added stackLabels position calculation for 3D charts.
- wrap(H.StackItem.prototype, 'getStackBox', function(proceed, chart) { // #3946
+ wrap(H.StackItem.prototype, 'getStackBox', function (proceed, chart) { // #3946
var stackBox = proceed.apply(this, [].slice.call(arguments, 1));
// Only do this for 3D chart.
@@ -3955,6 +4085,7 @@
y: stackBox.y,
z: 0
});
+
pos = H.perspective([pos], chart, true)[0];
stackBox.x = pos.x;
stackBox.y = pos.y;
@@ -3964,83 +4095,90 @@
});
/*
- EXTENSION FOR 3D CYLINDRICAL COLUMNS
- Not supported
+ @merge v6.2
+ @todo
+ EXTENSION FOR 3D CYLINDRICAL COLUMNS
+ Not supported
*/
/*
var defaultOptions = H.getOptions();
- defaultOptions.plotOptions.cylinder = H.merge(defaultOptions.plotOptions.column);
+ defaultOptions.plotOptions.cylinder =
+ H.merge(defaultOptions.plotOptions.column);
var CylinderSeries = H.extendClass(seriesTypes.column, {
- type: 'cylinder'
+ type: 'cylinder'
});
seriesTypes.cylinder = CylinderSeries;
wrap(seriesTypes.cylinder.prototype, 'translate', function (proceed) {
- proceed.apply(this, [].slice.call(arguments, 1));
-
- // Do not do this if the chart is not 3D
- if (!this.chart.is3d()) {
- return;
- }
-
- var series = this,
- chart = series.chart,
- options = chart.options,
- cylOptions = options.plotOptions.cylinder,
- options3d = options.chart.options3d,
- depth = cylOptions.depth || 0,
- alpha = chart.alpha3d;
-
- var z = cylOptions.stacking ? (this.options.stack || 0) * depth : series._i * depth;
- z += depth / 2;
-
- if (cylOptions.grouping !== false) { z = 0; }
-
- each(series.data, function (point) {
- var shapeArgs = point.shapeArgs,
- deg2rad = H.deg2rad;
- point.shapeType = 'arc3d';
- shapeArgs.x += depth / 2;
- shapeArgs.z = z;
- shapeArgs.start = 0;
- shapeArgs.end = 2 * PI;
- shapeArgs.r = depth * 0.95;
- shapeArgs.innerR = 0;
- shapeArgs.depth = shapeArgs.height * (1 / sin((90 - alpha) * deg2rad)) - z;
- shapeArgs.alpha = 90 - alpha;
- shapeArgs.beta = 0;
- });
+ proceed.apply(this, [].slice.call(arguments, 1));
+
+ // Do not do this if the chart is not 3D
+ if (!this.chart.is3d()) {
+ return;
+ }
+
+ var series = this,
+ chart = series.chart,
+ options = chart.options,
+ cylOptions = options.plotOptions.cylinder,
+ options3d = options.chart.options3d,
+ depth = cylOptions.depth || 0,
+ alpha = chart.alpha3d;
+
+ var z = cylOptions.stacking ?
+ (this.options.stack || 0) * depth :
+ series._i * depth;
+ z += depth / 2;
+
+ if (cylOptions.grouping !== false) { z = 0; }
+
+ each(series.data, function (point) {
+ var shapeArgs = point.shapeArgs,
+ deg2rad = H.deg2rad;
+ point.shapeType = 'arc3d';
+ shapeArgs.x += depth / 2;
+ shapeArgs.z = z;
+ shapeArgs.start = 0;
+ shapeArgs.end = 2 * PI;
+ shapeArgs.r = depth * 0.95;
+ shapeArgs.innerR = 0;
+ shapeArgs.depth =
+ shapeArgs.height * (1 / sin((90 - alpha) * deg2rad)) - z;
+ shapeArgs.alpha = 90 - alpha;
+ shapeArgs.beta = 0;
+ });
});
*/
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* 3D pie series
- *
+ *
* License: www.highcharts.com/license
*/
+
+
+
var deg2rad = H.deg2rad,
- each = H.each,
pick = H.pick,
seriesTypes = H.seriesTypes,
svg = H.svg,
wrap = H.wrap;
-
/**
* The thickness of a 3D pie. Requires `highcharts-3d.js`
- *
- * @type {Number}
- * @default 0
- * @since 4.0
- * @product highcharts
+ *
+ * @type {number}
+ * @default 0
+ * @since 4.0
+ * @product highcharts
* @apioption plotOptions.pie.depth
*/
- wrap(seriesTypes.pie.prototype, 'translate', function(proceed) {
+ wrap(seriesTypes.pie.prototype, 'translate', function (proceed) {
proceed.apply(this, [].slice.call(arguments, 1));
// Do not do this if the chart is not 3D
@@ -4054,7 +4192,9 @@
options3d = series.chart.options.chart.options3d,
alpha = options3d.alpha,
beta = options3d.beta,
- z = seriesOptions.stacking ? (seriesOptions.stack || 0) * depth : series._i * depth;
+ z = seriesOptions.stacking ?
+ (seriesOptions.stack || 0) * depth :
+ series._i * depth;
z += depth / 2;
@@ -4062,7 +4202,7 @@
z = 0;
}
- each(series.data, function(point) {
+ series.data.forEach(function (point) {
var shapeArgs = point.shapeArgs,
angle;
@@ -4078,67 +4218,72 @@
angle = (shapeArgs.end + shapeArgs.start) / 2;
point.slicedTranslation = {
- translateX: Math.round(Math.cos(angle) * seriesOptions.slicedOffset * Math.cos(alpha * deg2rad)),
- translateY: Math.round(Math.sin(angle) * seriesOptions.slicedOffset * Math.cos(alpha * deg2rad))
+ translateX: Math.round(
+ Math.cos(angle) *
+ seriesOptions.slicedOffset *
+ Math.cos(alpha * deg2rad)
+ ),
+ translateY: Math.round(
+ Math.sin(angle) *
+ seriesOptions.slicedOffset *
+ Math.cos(alpha * deg2rad)
+ )
};
});
});
- wrap(seriesTypes.pie.prototype.pointClass.prototype, 'haloPath', function(proceed) {
- var args = arguments;
- return this.series.chart.is3d() ? [] : proceed.call(this, args[1]);
- });
-
-
- wrap(seriesTypes.pie.prototype, 'pointAttribs', function(proceed, point, state) {
- var attr = proceed.call(this, point, state),
- options = this.options;
+ wrap(
+ seriesTypes.pie.prototype.pointClass.prototype,
+ 'haloPath',
+ function (proceed) {
+ var args = arguments;
- if (this.chart.is3d()) {
- attr.stroke = options.edgeColor || point.color || this.color;
- attr['stroke-width'] = pick(options.edgeWidth, 1);
+ return this.series.chart.is3d() ? [] : proceed.call(this, args[1]);
}
+ );
+
+ wrap(
+ seriesTypes.pie.prototype,
+ 'pointAttribs',
+ function (proceed, point, state) {
+ var attr = proceed.call(this, point, state),
+ options = this.options;
+
+ if (this.chart.is3d() && !this.chart.styledMode) {
+ attr.stroke = options.edgeColor || point.color || this.color;
+ attr['stroke-width'] = pick(options.edgeWidth, 1);
+ }
- return attr;
- });
-
-
- wrap(seriesTypes.pie.prototype, 'drawPoints', function(proceed) {
- proceed.apply(this, [].slice.call(arguments, 1));
-
- if (this.chart.is3d()) {
- each(this.points, function(point) {
- var graphic = point.graphic;
-
- // #4584 Check if has graphic - null points don't have it
- if (graphic) {
- // Hide null or 0 points (#3006, 3650)
- graphic[point.y && point.visible ? 'show' : 'hide']();
- }
- });
+ return attr;
}
- });
+ );
- wrap(seriesTypes.pie.prototype, 'drawDataLabels', function(proceed) {
+ wrap(seriesTypes.pie.prototype, 'drawDataLabels', function (proceed) {
if (this.chart.is3d()) {
var series = this,
chart = series.chart,
options3d = chart.options.chart.options3d;
- each(series.data, function(point) {
+
+ series.data.forEach(function (point) {
var shapeArgs = point.shapeArgs,
r = shapeArgs.r,
- a1 = (shapeArgs.alpha || options3d.alpha) * deg2rad, // #3240 issue with datalabels for 0 and null values
+ // #3240 issue with datalabels for 0 and null values
+ a1 = (shapeArgs.alpha || options3d.alpha) * deg2rad,
b1 = (shapeArgs.beta || options3d.beta) * deg2rad,
a2 = (shapeArgs.start + shapeArgs.end) / 2,
- labelPos = point.labelPos,
- labelIndexes = [0, 2, 4], // [x1, y1, x2, y2, x3, y3]
- yOffset = (-r * (1 - Math.cos(a1)) * Math.sin(a2)), // + (sin(a2) > 0 ? sin(a1) * d : 0)
+ labelPosition = point.labelPosition,
+ connectorPosition = labelPosition.connectorPosition,
+ yOffset = (-r * (1 - Math.cos(a1)) * Math.sin(a2)),
xOffset = r * (Math.cos(b1) - 1) * Math.cos(a2);
// Apply perspective on label positions
- each(labelIndexes, function(index) {
- labelPos[index] += xOffset;
- labelPos[index + 1] += yOffset;
+ [
+ labelPosition.natural,
+ connectorPosition.breakAt,
+ connectorPosition.touchingSliceAt
+ ].forEach(function (coordinates) {
+ coordinates.x += xOffset;
+ coordinates.y += yOffset;
});
});
}
@@ -4146,7 +4291,7 @@
proceed.apply(this, [].slice.call(arguments, 1));
});
- wrap(seriesTypes.pie.prototype, 'addPoint', function(proceed) {
+ wrap(seriesTypes.pie.prototype, 'addPoint', function (proceed) {
proceed.apply(this, [].slice.call(arguments, 1));
if (this.chart.is3d()) {
// destroy (and rebuild) everything!!!
@@ -4154,7 +4299,7 @@
}
});
- wrap(seriesTypes.pie.prototype, 'animate', function(proceed) {
+ wrap(seriesTypes.pie.prototype, 'animate', function (proceed) {
if (!this.chart.is3d()) {
proceed.apply(this, [].slice.call(arguments, 1));
} else {
@@ -4190,7 +4335,7 @@
markerGroup.attr(attribs);
}
- // Run the animation
+ // Run the animation
} else {
attribs = {
translateX: group.oldtranslateX,
@@ -4213,93 +4358,104 @@
});
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* Scatter 3D series.
*
* License: www.highcharts.com/license
*/
+
+
+
var Point = H.Point,
seriesType = H.seriesType,
seriesTypes = H.seriesTypes;
/**
- * A 3D scatter plot uses x, y and z coordinates to display values for three
- * variables for a set of data.
+ * @private
+ * @class
+ * @name Highcharts.seriesTypes.scatter3d
*
- * @sample {highcharts} highcharts/3d/scatter/
- * Simple 3D scatter
- * @sample {highcharts} highcharts/demo/3d-scatter-draggable
- * Draggable 3d scatter
- *
- * @extends {plotOptions.scatter}
- * @product highcharts
- * @optionparent plotOptions.scatter3d
+ * @augments Highcharts.Series
*/
- seriesType('scatter3d', 'scatter', {
- tooltip: {
- pointFormat: 'x: {point.x} y: {point.y} z: {point.z} '
- }
+ seriesType(
+ 'scatter3d',
+ 'scatter',
+ /**
+ * A 3D scatter plot uses x, y and z coordinates to display values for three
+ * variables for a set of data.
+ *
+ * @sample {highcharts} highcharts/3d/scatter/
+ * Simple 3D scatter
+ * @sample {highcharts} highcharts/demo/3d-scatter-draggable
+ * Draggable 3d scatter
+ *
+ * @extends plotOptions.scatter
+ * @product highcharts
+ * @optionparent plotOptions.scatter3d
+ */
+ {
+ tooltip: {
+ pointFormat: 'x: {point.x} y: {point.y} z: {point.z} '
+ }
// Series class
- }, {
- pointAttribs: function(point) {
- var attribs = seriesTypes.scatter.prototype.pointAttribs
- .apply(this, arguments);
+ }, {
+ pointAttribs: function (point) {
+ var attribs = seriesTypes.scatter.prototype.pointAttribs
+ .apply(this, arguments);
- if (this.chart.is3d() && point) {
- attribs.zIndex = H.pointCameraDistance(point, this.chart);
- }
+ if (this.chart.is3d() && point) {
+ attribs.zIndex = H.pointCameraDistance(point, this.chart);
+ }
- return attribs;
- },
- axisTypes: ['xAxis', 'yAxis', 'zAxis'],
- pointArrayMap: ['x', 'y', 'z'],
- parallelArrays: ['x', 'y', 'z'],
+ return attribs;
+ },
+ axisTypes: ['xAxis', 'yAxis', 'zAxis'],
+ pointArrayMap: ['x', 'y', 'z'],
+ parallelArrays: ['x', 'y', 'z'],
- // Require direct touch rather than using the k-d-tree, because the k-d-tree
- // currently doesn't take the xyz coordinate system into account (#4552)
- directTouch: true
+ // Require direct touch rather than using the k-d-tree, because the
+ // k-d-tree currently doesn't take the xyz coordinate system into
+ // account (#4552)
+ directTouch: true
// Point class
- }, {
- applyOptions: function() {
- Point.prototype.applyOptions.apply(this, arguments);
- if (this.z === undefined) {
- this.z = 0;
+ }, {
+ applyOptions: function () {
+ Point.prototype.applyOptions.apply(this, arguments);
+ if (this.z === undefined) {
+ this.z = 0;
+ }
+
+ return this;
}
- return this;
}
-
- });
+ );
/**
* A `scatter3d` series. If the [type](#series.scatter3d.type) option is
* not specified, it is inherited from [chart.type](#chart.type).
- *
- * For options that apply to multiple series, it is recommended to add
- * them to the [plotOptions.series](#plotOptions.series) options structure.
- * To apply to all series of this specific type, apply it to [plotOptions.
+ *
* scatter3d](#plotOptions.scatter3d).
- *
- * @type {Object}
- * @extends series,plotOptions.scatter3d
- * @product highcharts
+ *
+ * @extends series,plotOptions.scatter3d
+ * @product highcharts
* @apioption series.scatter3d
*/
/**
* An array of data points for the series. For the `scatter3d` series
* type, points can be given in the following ways:
- *
+ *
* 1. An array of arrays with 3 values. In this case, the values correspond
* to `x,y,z`. If the first value is a string, it is applied as the name
* of the point, and the `x` value is inferred.
- *
+ *
* ```js
* data: [
* [0, 0, 1],
@@ -4307,12 +4463,13 @@
* [2, 9, 2]
* ]
* ```
- *
- * 3. An array of objects with named values. The objects are point
- * configuration objects as seen below. If the total number of data
- * points exceeds the series' [turboThreshold](#series.scatter3d.turboThreshold),
- * this option is not available.
- *
+ *
+ * 3. An array of objects with named values. The following snippet shows only a
+ * few settings, see the complete options set below. If the total number of data
+ * points exceeds the series'
+ * [turboThreshold](#series.scatter3d.turboThreshold), this option is not
+ * available.
+ *
* ```js
* data: [{
* x: 1,
@@ -4328,99 +4485,95 @@
* color: "#FF00FF"
* }]
* ```
- *
- * @type {Array}
- * @extends series.scatter.data
- * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
- * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
- * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
- * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
- * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
- * @product highcharts
+ *
+ * @sample {highcharts} highcharts/chart/reflow-true/
+ * Numerical values
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/
+ * Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
+ * Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/
+ * Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
+ *
+ * @type {Array|*>}
+ * @extends series.scatter.data
+ * @product highcharts
* @apioption series.scatter3d.data
*/
/**
* The z value for each data point.
- *
- * @type {Number}
- * @product highcharts
+ *
+ * @type {number}
+ * @product highcharts
* @apioption series.scatter3d.data.z
*/
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
+ *
+ * Extension to the VML Renderer
*
* License: www.highcharts.com/license
*/
- var Axis = H.Axis,
+
+
+ var addEvent = H.addEvent,
+ Axis = H.Axis,
SVGRenderer = H.SVGRenderer,
VMLRenderer = H.VMLRenderer;
- /**
- * Extension to the VML Renderer
- */
if (VMLRenderer) {
- H.setOptions({
- animate: false
- });
+ H.setOptions({ animate: false });
VMLRenderer.prototype.face3d = SVGRenderer.prototype.face3d;
VMLRenderer.prototype.polyhedron = SVGRenderer.prototype.polyhedron;
+
+ VMLRenderer.prototype.elements3d = SVGRenderer.prototype.elements3d;
+ VMLRenderer.prototype.element3d = SVGRenderer.prototype.element3d;
VMLRenderer.prototype.cuboid = SVGRenderer.prototype.cuboid;
VMLRenderer.prototype.cuboidPath = SVGRenderer.prototype.cuboidPath;
VMLRenderer.prototype.toLinePath = SVGRenderer.prototype.toLinePath;
VMLRenderer.prototype.toLineSegments = SVGRenderer.prototype.toLineSegments;
- VMLRenderer.prototype.createElement3D =
- SVGRenderer.prototype.createElement3D;
-
- VMLRenderer.prototype.arc3d = function(shapeArgs) {
+ VMLRenderer.prototype.arc3d = function (shapeArgs) {
var result = SVGRenderer.prototype.arc3d.call(this, shapeArgs);
- result.css({
- zIndex: result.zIndex
- });
+
+ result.css({ zIndex: result.zIndex });
return result;
};
H.VMLRenderer.prototype.arc3dPath = H.SVGRenderer.prototype.arc3dPath;
- H.wrap(Axis.prototype, 'render', function(proceed) {
- proceed.apply(this, [].slice.call(arguments, 1));
+ addEvent(Axis, 'render', function () {
+
// VML doesn't support a negative z-index
if (this.sideFrame) {
- this.sideFrame.css({
- zIndex: 0
- });
- this.sideFrame.front.attr({
- fill: this.sideFrame.color
- });
+ this.sideFrame.css({ zIndex: 0 });
+ this.sideFrame.front.attr({ fill: this.sideFrame.color });
}
if (this.bottomFrame) {
- this.bottomFrame.css({
- zIndex: 1
- });
- this.bottomFrame.front.attr({
- fill: this.bottomFrame.color
- });
+ this.bottomFrame.css({ zIndex: 1 });
+ this.bottomFrame.front.attr({ fill: this.bottomFrame.color });
}
if (this.backFrame) {
- this.backFrame.css({
- zIndex: 0
- });
- this.backFrame.front.attr({
- fill: this.backFrame.color
- });
+ this.backFrame.css({ zIndex: 0 });
+ this.backFrame.front.attr({ fill: this.backFrame.color });
}
});
}
-
}(Highcharts));
+ return (function () {
+
+
+ }());
}));
diff --git a/app/assets/javascripts/highcharts/highcharts-more.js b/app/assets/javascripts/highcharts/highcharts-more.js
index 270968d..a2a019f 100644
--- a/app/assets/javascripts/highcharts/highcharts-more.js
+++ b/app/assets/javascripts/highcharts/highcharts-more.js
@@ -1,111 +1,49 @@
/**
- * @license Highcharts JS v6.0.3 (2017-11-14)
+ * @license Highcharts JS v7.0.3 (2019-02-06)
*
- * (c) 2009-2016 Torstein Honsi
+ * (c) 2009-2018 Torstein Honsi
*
* License: www.highcharts.com/license
*/
'use strict';
-(function(factory) {
+(function (factory) {
if (typeof module === 'object' && module.exports) {
+ factory['default'] = factory;
module.exports = factory;
+ } else if (typeof define === 'function' && define.amd) {
+ define(function () {
+ return factory;
+ });
} else {
- factory(Highcharts);
+ factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
}
-}(function(Highcharts) {
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+}(function (Highcharts) {
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
- var deg2rad = H.deg2rad,
- isNumber = H.isNumber,
- pick = H.pick,
- relativeLength = H.relativeLength;
- H.CenteredSeriesMixin = {
- /**
- * Get the center of the pie based on the size and center options relative to the
- * plot area. Borrowed by the polar and gauge series types.
- */
- getCenter: function() {
-
- var options = this.options,
- chart = this.chart,
- slicingRoom = 2 * (options.slicedOffset || 0),
- handleSlicingRoom,
- plotWidth = chart.plotWidth - 2 * slicingRoom,
- plotHeight = chart.plotHeight - 2 * slicingRoom,
- centerOption = options.center,
- positions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0],
- smallestSize = Math.min(plotWidth, plotHeight),
- i,
- value;
-
- for (i = 0; i < 4; ++i) {
- value = positions[i];
- handleSlicingRoom = i < 2 || (i === 2 && /%$/.test(value));
- // i == 0: centerX, relative to width
- // i == 1: centerY, relative to height
- // i == 2: size, relative to smallestSize
- // i == 3: innerSize, relative to size
- positions[i] = relativeLength(value, [plotWidth, plotHeight, smallestSize, positions[2]][i]) +
- (handleSlicingRoom ? slicingRoom : 0);
- }
- // innerSize cannot be larger than size (#3632)
- if (positions[3] > positions[2]) {
- positions[3] = positions[2];
- }
- return positions;
- },
- /**
- * getStartAndEndRadians - Calculates start and end angles in radians.
- * Used in series types such as pie and sunburst.
- *
- * @param {Number} start Start angle in degrees.
- * @param {Number} end Start angle in degrees.
- * @return {object} Returns an object containing start and end angles as
- * radians.
- */
- getStartAndEndRadians: function getStartAndEndRadians(start, end) {
- var startAngle = isNumber(start) ? start : 0, // must be a number
- endAngle = (
- (
- isNumber(end) && // must be a number
- end > startAngle && // must be larger than the start angle
- // difference must be less than 360 degrees
- (end - startAngle) < 360
- ) ?
- end :
- startAngle + 360
- ),
- correction = -90;
- return {
- start: deg2rad * (startAngle + correction),
- end: deg2rad * (endAngle + correction)
- };
- }
- };
- }(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
- *
- * License: www.highcharts.com/license
- */
var CenteredSeriesMixin = H.CenteredSeriesMixin,
- each = H.each,
extend = H.extend,
merge = H.merge,
splat = H.splat;
+
/**
* The Pane object allows options that are common to a set of X and Y axes.
*
* In the future, this can be extended to basic Highcharts and Highstock.
*
+ * @private
+ * @class
+ * @name Highcharts.Pane
+ *
+ * @param {Highcharts.PaneOptions} options
+ *
+ * @param {Highcharts.Chart} chart
*/
function Pane(options, chart) {
this.init(options, chart);
@@ -117,9 +55,16 @@
coll: 'pane', // Member of chart.pane
/**
- * Initiate the Pane object
+ * Initialize the Pane object
+ *
+ * @private
+ * @function Highcharts.Pane#init
+ *
+ * @param {Highcharts.PaneOptions} options
+ *
+ * @param {Highcharts.Chart} chart
*/
- init: function(options, chart) {
+ init: function (options, chart) {
this.chart = chart;
this.background = [];
@@ -128,22 +73,29 @@
this.setOptions(options);
},
- setOptions: function(options) {
+ /**
+ * @private
+ * @function Highcharts.Pane#setOptions
+ *
+ * @param {Highcharts.PaneOptions} options
+ */
+ setOptions: function (options) {
// Set options. Angular charts have a default background (#3318)
this.options = options = merge(
this.defaultOptions,
- this.chart.angular ? {
- background: {}
- } : undefined,
+ this.chart.angular ? { background: {} } : undefined,
options
);
},
/**
* Render the pane with its backgrounds.
+ *
+ * @private
+ * @function Highcharts.Pane#render
*/
- render: function() {
+ render: function () {
var options = this.options,
backgroundOption = this.options.background,
@@ -153,9 +105,7 @@
if (!this.group) {
this.group = renderer.g('pane-group')
- .attr({
- zIndex: options.zIndex || 0
- })
+ .attr({ zIndex: options.zIndex || 0 })
.add();
}
@@ -171,7 +121,9 @@
);
for (i = 0; i < len; i++) {
- if (backgroundOption[i] && this.axis) { // #6641 - if axis exists, chart is circular and apply background
+ // #6641 - if axis exists, chart is circular and apply
+ // background
+ if (backgroundOption[i] && this.axis) {
this.renderBackground(
merge(
this.defaultBackgroundOptions,
@@ -189,11 +141,30 @@
/**
* Render an individual pane background.
- * @param {Object} backgroundOptions Background options
- * @param {number} i The index of the background in this.backgrounds
+ *
+ * @private
+ * @function Highcharts.Pane#renderBackground
+ *
+ * @param {Highcharts.PaneBackgroundOptions} backgroundOptions
+ * Background options
+ *
+ * @param {number} i
+ * The index of the background in this.backgrounds
*/
- renderBackground: function(backgroundOptions, i) {
- var method = 'animate';
+ renderBackground: function (backgroundOptions, i) {
+ var method = 'animate',
+ attribs = {
+ 'class':
+ 'highcharts-pane ' + (backgroundOptions.className || '')
+ };
+
+ if (!this.chart.styledMode) {
+ extend(attribs, {
+ 'fill': backgroundOptions.backgroundColor,
+ 'stroke': backgroundOptions.borderColor,
+ 'stroke-width': backgroundOptions.borderWidth
+ });
+ }
if (!this.background[i]) {
this.background[i] = this.chart.renderer.path()
@@ -207,34 +178,45 @@
backgroundOptions.to,
backgroundOptions
)
- }).attr({
-
- 'fill': backgroundOptions.backgroundColor,
- 'stroke': backgroundOptions.borderColor,
- 'stroke-width': backgroundOptions.borderWidth,
-
- 'class': 'highcharts-pane ' + (backgroundOptions.className || '')
- });
+ }).attr(attribs);
},
/**
- * The pane serves as a container for axes and backgrounds for circular
+ * The pane serves as a container for axes and backgrounds for circular
* gauges and polar charts.
- * @since 2.3.0
+ *
+ * @since 2.3.0
+ * @product highcharts
* @optionparent pane
*/
defaultOptions: {
+
+ /**
+ * The end angle of the polar X axis or gauge value axis, given in
+ * degrees where 0 is north. Defaults to [startAngle](#pane.startAngle)
+ * + 360.
+ *
+ * @sample {highcharts} highcharts/demo/gauge-vu-meter/
+ * VU-meter with custom start and end angle
+ *
+ * @type {number}
+ * @since 2.3.0
+ * @product highcharts
+ * @apioption pane.endAngle
+ */
+
/**
* The center of a polar chart or angular gauge, given as an array
- * of [x, y] positions. Positions can be given as integers that transform
- * to pixels, or as percentages of the plot area size.
- *
- * @type {Array}
+ * of [x, y] positions. Positions can be given as integers that
+ * transform to pixels, or as percentages of the plot area size.
+ *
* @sample {highcharts} highcharts/demo/gauge-vu-meter/
* Two gauges with different center
+ *
+ * @type {Array}
* @default ["50%", "50%"]
- * @since 2.3.0
+ * @since 2.3.0
* @product highcharts
*/
center: ['50%', '50%'],
@@ -242,10 +224,11 @@
/**
* The size of the pane, either as a number defining pixels, or a
* percentage defining a percentage of the plot are.
- *
- * @type {Number|String}
- * @sample {highcharts} highcharts/demo/gauge-vu-meter/ Smaller size
- * @default 85%
+ *
+ * @sample {highcharts} highcharts/demo/gauge-vu-meter/
+ * Smaller size
+ *
+ * @type {number|string}
* @product highcharts
*/
size: '85%',
@@ -253,67 +236,58 @@
/**
* The start angle of the polar X axis or gauge axis, given in degrees
* where 0 is north. Defaults to 0.
- *
- * @type {Number}
+ *
* @sample {highcharts} highcharts/demo/gauge-vu-meter/
* VU-meter with custom start and end angle
- * @since 2.3.0
+ *
+ * @since 2.3.0
* @product highcharts
*/
startAngle: 0
-
- /**
- * The end angle of the polar X axis or gauge value axis, given in degrees
- * where 0 is north. Defaults to [startAngle](#pane.startAngle) + 360.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/demo/gauge-vu-meter/
- * VU-meter with custom start and end angle
- * @since 2.3.0
- * @product highcharts
- * @apioption pane.endAngle
- */
},
/**
* An array of background items for the pane.
- * @type Array.
+ *
* @sample {highcharts} highcharts/demo/gauge-speedometer/
* Speedometer gauge with multiple backgrounds
+ *
+ * @type {Array<*>}
* @optionparent pane.background
*/
defaultBackgroundOptions: {
+
/**
* The class name for this background.
- *
- * @type {String}
- * @sample {highcharts} highcharts/css/pane/ Panes styled by CSS
- * @sample {highstock} highcharts/css/pane/ Panes styled by CSS
- * @sample {highmaps} highcharts/css/pane/ Panes styled by CSS
- * @default highcharts-pane
- * @since 5.0.0
+ *
+ * @sample {highcharts} highcharts/css/pane/
+ * Panes styled by CSS
+ * @sample {highstock} highcharts/css/pane/
+ * Panes styled by CSS
+ * @sample {highmaps} highcharts/css/pane/
+ * Panes styled by CSS
+ *
+ * @type {string}
+ * @default highcharts-pane
+ * @since 5.0.0
* @apioption pane.background.className
*/
/**
- * Tha shape of the pane background. When `solid`, the background
+ * The shape of the pane background. When `solid`, the background
* is circular. When `arc`, the background extends only from the min
* to the max of the value axis.
- *
- * @validvalue ["solid", "arc"]
- * @type {String}
- * @default solid
- * @since 2.3.0
- * @product highcharts
+ *
+ * @type {string}
+ * @since 2.3.0
+ * @validvalue ["arc", "circle", "solid"]
+ * @product highcharts
*/
shape: 'circle',
-
/**
* The pixel border width of the pane background.
- *
- * @type {Number}
- * @default 1
+ *
* @since 2.3.0
* @product highcharts
*/
@@ -321,111 +295,115 @@
/**
* The pane background border color.
- *
- * @type {Color}
- * @default #cccccc
- * @since 2.3.0
+ *
+ * @type {Highcharts.ColorString}
+ * @since 2.3.0
* @product highcharts
*/
borderColor: '#cccccc',
/**
* The background color or gradient for the pane.
- *
- * @type {Color}
- * @since 2.3.0
+ *
+ * @type {Highcharts.GradientColorObject}
+ * @default { linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, stops: [[0, #ffffff], [1, #e6e6e6]] }
+ * @since 2.3.0
* @product highcharts
*/
backgroundColor: {
+
/**
- * Definition of the gradient, similar to SVG: object literal holds
- * start position (x1, y1) and the end position (x2, y2) relative
- * to the shape, where 0 means top/left and 1 is bottom/right.
- * All positions are floats between 0 and 1.
- *
- * @type {Object}
+ * @ignore
*/
- linearGradient: {
- x1: 0,
- y1: 0,
- x2: 0,
- y2: 1
- },
+ linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
+
/**
- * The stops is an array of tuples, where the first
- * item is a float between 0 and 1 assigning the relative position in
- * the gradient, and the second item is the color.
- *
- * @default [[0, #ffffff], [1, #e6e6e6]]
- * @type {Array}
+ * @ignore
*/
stops: [
[0, '#ffffff'],
[1, '#e6e6e6']
]
- },
+ },
- /** @ignore */
+ /** @ignore-option */
from: -Number.MAX_VALUE, // corrected to axis min
/**
* The inner radius of the pane background. Can be either numeric
* (pixels) or a percentage string.
- *
- * @type {Number|String}
- * @default 0
- * @since 2.3.0
+ *
+ * @type {number|string}
+ * @since 2.3.0
* @product highcharts
*/
innerRadius: 0,
- /** @ignore */
+ /**
+ * @ignore-option
+ */
to: Number.MAX_VALUE, // corrected to axis max
/**
* The outer radius of the circular pane background. Can be either
* numeric (pixels) or a percentage string.
- *
- * @type {Number|String}
- * @default 105%
- * @since 2.3.0
- * @product highcharts
+ *
+ * @type {number|string}
+ * @since 2.3.0
+ * @product highcharts
*/
outerRadius: '105%'
+
},
/**
* Gets the center for the pane and its axis.
+ *
+ * @private
+ * @function Highcharts.Pane#updateCenter
+ *
+ * @param {Highcharts.RadialAxis} axis
*/
- updateCenter: function(axis) {
+ updateCenter: function (axis) {
this.center = (axis || this.axis || {}).center =
CenteredSeriesMixin.getCenter.call(this);
},
/**
* Destroy the pane item
+ *
+ * @ignore
+ * @private
+ * @function Highcharts.Pane#destroy
* /
destroy: function () {
- H.erase(this.chart.pane, this);
- each(this.background, function (background) {
- background.destroy();
- });
- this.background.length = 0;
- this.group = this.group.destroy();
+ H.erase(this.chart.pane, this);
+ this.background.forEach(function (background) {
+ background.destroy();
+ });
+ this.background.length = 0;
+ this.group = this.group.destroy();
},
*/
/**
* Update the pane item with new options
- * @param {Object} options New pane options
+ *
+ * @private
+ * @function Highcharts.Pane#update
+ *
+ * @param {Highcharts.PaneOptions} options
+ * New pane options
+ *
+ * @param {boolean} redraw
*/
- update: function(options, redraw) {
+ update: function (options, redraw) {
merge(true, this.options, options);
this.setOptions(this.options);
this.render();
- each(this.chart.axes, function(axis) {
+ this.chart.axes.forEach(function (axis) {
if (axis.pane === this) {
axis.pane = null;
axis.update({}, redraw);
@@ -438,22 +416,25 @@
H.Pane = Pane;
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
- var Axis = H.Axis,
- each = H.each,
+
+
+
+ var addEvent = H.addEvent,
+ Axis = H.Axis,
extend = H.extend,
- map = H.map,
merge = H.merge,
noop = H.noop,
pick = H.pick,
pInt = H.pInt,
Tick = H.Tick,
wrap = H.wrap,
+ correctFloat = H.correctFloat,
hiddenAxisMixin, // @todo Extract this to a new file
@@ -461,570 +442,675 @@
axisProto = Axis.prototype,
tickProto = Tick.prototype;
- /**
- * Augmented methods for the x axis in order to hide it completely, used for the X axis in gauges
- */
- hiddenAxisMixin = {
- getOffset: noop,
- redraw: function() {
- this.isDirty = false; // prevent setting Y axis dirty
- },
- render: function() {
- this.isDirty = false; // prevent setting Y axis dirty
- },
- setScale: noop,
- setCategories: noop,
- setTitle: noop
- };
-
- /**
- * Augmented methods for the value axis
- */
- radialAxisMixin = {
+ if (!H.radialAxisExtended) {
+ H.radialAxisExtended = true;
- /**
- * The default options extend defaultYAxisOptions
- */
- defaultRadialGaugeOptions: {
- labels: {
- align: 'center',
- x: 0,
- y: null // auto
+ // Augmented methods for the x axis in order to hide it completely, used for
+ // the X axis in gauges
+ hiddenAxisMixin = {
+ getOffset: noop,
+ redraw: function () {
+ this.isDirty = false; // prevent setting Y axis dirty
},
- minorGridLineWidth: 0,
- minorTickInterval: 'auto',
- minorTickLength: 10,
- minorTickPosition: 'inside',
- minorTickWidth: 1,
- tickLength: 10,
- tickPosition: 'inside',
- tickWidth: 2,
- title: {
- rotation: 0
+ render: function () {
+ this.isDirty = false; // prevent setting Y axis dirty
},
- zIndex: 2 // behind dials, points in the series group
- },
+ setScale: noop,
+ setCategories: noop,
+ setTitle: noop
+ };
- // Circular axis around the perimeter of a polar chart
- defaultRadialXOptions: {
- gridLineWidth: 1, // spokes
- labels: {
- align: null, // auto
- distance: 15,
- x: 0,
- y: null // auto
+ // Augmented methods for the value axis
+ radialAxisMixin = {
+
+ // The default options extend defaultYAxisOptions
+ defaultRadialGaugeOptions: {
+ labels: {
+ align: 'center',
+ x: 0,
+ y: null // auto
+ },
+ minorGridLineWidth: 0,
+ minorTickInterval: 'auto',
+ minorTickLength: 10,
+ minorTickPosition: 'inside',
+ minorTickWidth: 1,
+ tickLength: 10,
+ tickPosition: 'inside',
+ tickWidth: 2,
+ title: {
+ rotation: 0
+ },
+ zIndex: 2 // behind dials, points in the series group
},
- maxPadding: 0,
- minPadding: 0,
- showLastLabel: false,
- tickLength: 0
- },
- // Radial axis, like a spoke in a polar chart
- defaultRadialYOptions: {
- gridLineInterpolation: 'circle',
- labels: {
- align: 'right',
- x: -3,
- y: -2
+ // Circular axis around the perimeter of a polar chart
+ defaultRadialXOptions: {
+ gridLineWidth: 1, // spokes
+ labels: {
+ align: null, // auto
+ distance: 15,
+ x: 0,
+ y: null, // auto
+ style: {
+ textOverflow: 'none' // wrap lines by default (#7248)
+ }
+ },
+ maxPadding: 0,
+ minPadding: 0,
+ showLastLabel: false,
+ tickLength: 0
},
- showLastLabel: false,
- title: {
- x: 4,
- text: null,
- rotation: 90
- }
- },
- /**
- * Merge and set options
- */
- setOptions: function(userOptions) {
+ // Radial axis, like a spoke in a polar chart
+ defaultRadialYOptions: {
+ gridLineInterpolation: 'circle',
+ labels: {
+ align: 'right',
+ x: -3,
+ y: -2
+ },
+ showLastLabel: false,
+ title: {
+ x: 4,
+ text: null,
+ rotation: 90
+ }
+ },
- var options = this.options = merge(
- this.defaultOptions,
- this.defaultRadialOptions,
- userOptions
- );
+ // Merge and set options
+ setOptions: function (userOptions) {
- // Make sure the plotBands array is instanciated for each Axis (#2649)
- if (!options.plotBands) {
- options.plotBands = [];
- }
+ var options = this.options = merge(
+ this.defaultOptions,
+ this.defaultRadialOptions,
+ userOptions
+ );
- },
+ // Make sure the plotBands array is instanciated for each Axis
+ // (#2649)
+ if (!options.plotBands) {
+ options.plotBands = [];
+ }
- /**
- * Wrap the getOffset method to return zero offset for title or labels in a radial
- * axis
- */
- getOffset: function() {
- // Call the Axis prototype method (the method we're in now is on the instance)
- axisProto.getOffset.call(this);
+ H.fireEvent(this, 'afterSetOptions');
- // Title or label offsets are not counted
- this.chart.axisOffset[this.side] = 0;
+ },
- },
+ // Wrap the getOffset method to return zero offset for title or labels
+ // in a radial axis
+ getOffset: function () {
+ // Call the Axis prototype method (the method we're in now is on the
+ // instance)
+ axisProto.getOffset.call(this);
+ // Title or label offsets are not counted
+ this.chart.axisOffset[this.side] = 0;
- /**
- * Get the path for the axis line. This method is also referenced in the getPlotLinePath
- * method.
- */
- getLinePath: function(lineWidth, radius) {
- var center = this.center,
- end,
- chart = this.chart,
- r = pick(radius, center[2] / 2 - this.offset),
- path;
-
- if (this.isCircular || radius !== undefined) {
- path = this.chart.renderer.symbols.arc(
- this.left + center[0],
- this.top + center[1],
- r,
- r, {
- start: this.startAngleRad,
- end: this.endAngleRad,
- open: true,
- innerR: 0
- }
- );
+ },
- // Bounds used to position the plotLine label next to the line
- // (#7117)
- path.xBounds = [this.left + center[0]];
- path.yBounds = [this.top + center[1] - r];
- } else {
- end = this.postTranslate(this.angleRad, r);
- path = ['M', center[0] + chart.plotLeft, center[1] + chart.plotTop, 'L', end.x, end.y];
- }
- return path;
- },
+ // Get the path for the axis line. This method is also referenced in the
+ // getPlotLinePath method.
+ getLinePath: function (lineWidth, radius) {
+ var center = this.center,
+ end,
+ chart = this.chart,
+ r = pick(radius, center[2] / 2 - this.offset),
+ path;
+
+ if (this.isCircular || radius !== undefined) {
+ path = this.chart.renderer.symbols.arc(
+ this.left + center[0],
+ this.top + center[1],
+ r,
+ r,
+ {
+ start: this.startAngleRad,
+ end: this.endAngleRad,
+ open: true,
+ innerR: 0
+ }
+ );
- /**
- * Override setAxisTranslation by setting the translation to the difference
- * in rotation. This allows the translate method to return angle for
- * any given value.
- */
- setAxisTranslation: function() {
+ // Bounds used to position the plotLine label next to the line
+ // (#7117)
+ path.xBounds = [this.left + center[0]];
+ path.yBounds = [this.top + center[1] - r];
- // Call uber method
- axisProto.setAxisTranslation.call(this);
+ } else {
+ end = this.postTranslate(this.angleRad, r);
+ path = [
+ 'M',
+ center[0] + chart.plotLeft,
+ center[1] + chart.plotTop,
+ 'L',
+ end.x,
+ end.y
+ ];
+ }
+ return path;
+ },
- // Set transA and minPixelPadding
- if (this.center) { // it's not defined the first time
- if (this.isCircular) {
+ /* *
+ * Override setAxisTranslation by setting the translation to the
+ * difference in rotation. This allows the translate method to return
+ * angle for any given value.
+ */
+ setAxisTranslation: function () {
- this.transA = (this.endAngleRad - this.startAngleRad) /
- ((this.max - this.min) || 1);
+ // Call uber method
+ axisProto.setAxisTranslation.call(this);
+ // Set transA and minPixelPadding
+ if (this.center) { // it's not defined the first time
+ if (this.isCircular) {
- } else {
- this.transA = (this.center[2] / 2) / ((this.max - this.min) || 1);
- }
+ this.transA = (this.endAngleRad - this.startAngleRad) /
+ ((this.max - this.min) || 1);
- if (this.isXAxis) {
- this.minPixelPadding = this.transA * this.minPointOffset;
- } else {
- // This is a workaround for regression #2593, but categories still don't position correctly.
- this.minPixelPadding = 0;
- }
- }
- },
- /**
- * In case of auto connect, add one closestPointRange to the max value right before
- * tickPositions are computed, so that ticks will extend passed the real max.
- */
- beforeSetTickPositions: function() {
- // If autoConnect is true, polygonal grid lines are connected, and one closestPointRange
- // is added to the X axis to prevent the last point from overlapping the first.
- this.autoConnect = this.isCircular && pick(this.userMax, this.options.max) === undefined &&
- this.endAngleRad - this.startAngleRad === 2 * Math.PI;
-
- if (this.autoConnect) {
- this.max += (this.categories && 1) || this.pointRange || this.closestPointRange || 0; // #1197, #2260
- }
- },
+ } else {
+ this.transA = (
+ (this.center[2] / 2) /
+ ((this.max - this.min) || 1)
+ );
+ }
- /**
- * Override the setAxisSize method to use the arc's circumference as length. This
- * allows tickPixelInterval to apply to pixel lengths along the perimeter
- */
- setAxisSize: function() {
+ if (this.isXAxis) {
+ this.minPixelPadding = this.transA * this.minPointOffset;
+ } else {
+ // This is a workaround for regression #2593, but categories
+ // still don't position correctly.
+ this.minPixelPadding = 0;
+ }
+ }
+ },
- axisProto.setAxisSize.call(this);
+ /* *
+ * In case of auto connect, add one closestPointRange to the max value
+ * right before tickPositions are computed, so that ticks will extend
+ * passed the real max.
+ */
+ beforeSetTickPositions: function () {
+ // If autoConnect is true, polygonal grid lines are connected, and
+ // one closestPointRange is added to the X axis to prevent the last
+ // point from overlapping the first.
+ this.autoConnect = (
+ this.isCircular &&
+ pick(this.userMax, this.options.max) === undefined &&
+ correctFloat(this.endAngleRad - this.startAngleRad) ===
+ correctFloat(2 * Math.PI)
+ );
- if (this.isRadial) {
+ if (this.autoConnect) {
+ this.max += (
+ (this.categories && 1) ||
+ this.pointRange ||
+ this.closestPointRange ||
+ 0
+ ); // #1197, #2260
+ }
+ },
- // Set the center array
- this.pane.updateCenter(this);
+ /* *
+ * Override the setAxisSize method to use the arc's circumference as
+ * length. This allows tickPixelInterval to apply to pixel lengths along
+ * the perimeter
+ */
+ setAxisSize: function () {
- // The sector is used in Axis.translate to compute the translation of reversed axis points (#2570)
- if (this.isCircular) {
- this.sector = this.endAngleRad - this.startAngleRad;
- }
+ axisProto.setAxisSize.call(this);
- // Axis len is used to lay out the ticks
- this.len = this.width = this.height = this.center[2] * pick(this.sector, 1) / 2;
+ if (this.isRadial) {
+ // Set the center array
+ this.pane.updateCenter(this);
- }
- },
+ // The sector is used in Axis.translate to compute the
+ // translation of reversed axis points (#2570)
+ if (this.isCircular) {
+ this.sector = this.endAngleRad - this.startAngleRad;
+ }
- /**
- * Returns the x, y coordinate of a point given by a value and a pixel distance
- * from center
- */
- getPosition: function(value, length) {
- return this.postTranslate(
- this.isCircular ? this.translate(value) : this.angleRad, // #2848
- pick(this.isCircular ? length : this.translate(value), this.center[2] / 2) - this.offset
- );
- },
+ // Axis len is used to lay out the ticks
+ this.len = this.width = this.height =
+ this.center[2] * pick(this.sector, 1) / 2;
- /**
- * Translate from intermediate plotX (angle), plotY (axis.len - radius) to final chart coordinates.
- */
- postTranslate: function(angle, radius) {
+ }
+ },
- var chart = this.chart,
- center = this.center;
+ /* *
+ * Returns the x, y coordinate of a point given by a value and a pixel
+ * distance from center
+ */
+ getPosition: function (value, length) {
+ return this.postTranslate(
+ this.isCircular ?
+ this.translate(value) :
+ this.angleRad, // #2848
+ pick(
+ this.isCircular ? length : this.translate(value),
+ this.center[2] / 2
+ ) - this.offset
+ );
+ },
- angle = this.startAngleRad + angle;
+ /* *
+ * Translate from intermediate plotX (angle), plotY (axis.len - radius)
+ * to final chart coordinates.
+ */
+ postTranslate: function (angle, radius) {
- return {
- x: chart.plotLeft + center[0] + Math.cos(angle) * radius,
- y: chart.plotTop + center[1] + Math.sin(angle) * radius
- };
+ var chart = this.chart,
+ center = this.center;
- },
+ angle = this.startAngleRad + angle;
- /**
- * Find the path for plot bands along the radial axis
- */
- getPlotBandPath: function(from, to, options) {
- var center = this.center,
- startAngleRad = this.startAngleRad,
- fullRadius = center[2] / 2,
- radii = [
- pick(options.outerRadius, '100%'),
- options.innerRadius,
- pick(options.thickness, 10)
- ],
- offset = Math.min(this.offset, 0),
- percentRegex = /%$/,
- start,
- end,
- open,
- isCircular = this.isCircular, // X axis in a polar chart
- ret;
+ return {
+ x: chart.plotLeft + center[0] + Math.cos(angle) * radius,
+ y: chart.plotTop + center[1] + Math.sin(angle) * radius
+ };
- // Polygonal plot bands
- if (this.options.gridLineInterpolation === 'polygon') {
- ret = this.getPlotLinePath(from).concat(this.getPlotLinePath(to, true));
+ },
- // Circular grid bands
- } else {
+ /* *
+ * Find the path for plot bands along the radial axis
+ */
+ getPlotBandPath: function (from, to, options) {
+ var center = this.center,
+ startAngleRad = this.startAngleRad,
+ fullRadius = center[2] / 2,
+ radii = [
+ pick(options.outerRadius, '100%'),
+ options.innerRadius,
+ pick(options.thickness, 10)
+ ],
+ offset = Math.min(this.offset, 0),
+ percentRegex = /%$/,
+ start,
+ end,
+ open,
+ isCircular = this.isCircular, // X axis in a polar chart
+ ret;
+
+ // Polygonal plot bands
+ if (this.options.gridLineInterpolation === 'polygon') {
+ ret = this.getPlotLinePath(from).concat(
+ this.getPlotLinePath(to, true)
+ );
- // Keep within bounds
- from = Math.max(from, this.min);
- to = Math.min(to, this.max);
+ // Circular grid bands
+ } else {
- // Plot bands on Y axis (radial axis) - inner and outer radius depend on to and from
- if (!isCircular) {
- radii[0] = this.translate(from);
- radii[1] = this.translate(to);
- }
+ // Keep within bounds
+ from = Math.max(from, this.min);
+ to = Math.min(to, this.max);
- // Convert percentages to pixel values
- radii = map(radii, function(radius) {
- if (percentRegex.test(radius)) {
- radius = (pInt(radius, 10) * fullRadius) / 100;
+ // Plot bands on Y axis (radial axis) - inner and outer radius
+ // depend on to and from
+ if (!isCircular) {
+ radii[0] = this.translate(from);
+ radii[1] = this.translate(to);
}
- return radius;
- });
- // Handle full circle
- if (options.shape === 'circle' || !isCircular) {
- start = -Math.PI / 2;
- end = Math.PI * 1.5;
- open = true;
- } else {
- start = startAngleRad + this.translate(from);
- end = startAngleRad + this.translate(to);
- }
+ // Convert percentages to pixel values
+ radii = radii.map(function (radius) {
+ if (percentRegex.test(radius)) {
+ radius = (pInt(radius, 10) * fullRadius) / 100;
+ }
+ return radius;
+ });
- radii[0] -= offset; // #5283
- radii[2] -= offset; // #5283
-
- ret = this.chart.renderer.symbols.arc(
- this.left + center[0],
- this.top + center[1],
- radii[0],
- radii[0], {
- start: Math.min(start, end), // Math is for reversed yAxis (#3606)
- end: Math.max(start, end),
- innerR: pick(radii[1], radii[0] - radii[2]),
- open: open
+ // Handle full circle
+ if (options.shape === 'circle' || !isCircular) {
+ start = -Math.PI / 2;
+ end = Math.PI * 1.5;
+ open = true;
+ } else {
+ start = startAngleRad + this.translate(from);
+ end = startAngleRad + this.translate(to);
}
- );
- }
- return ret;
- },
+ radii[0] -= offset; // #5283
+ radii[2] -= offset; // #5283
+
+ ret = this.chart.renderer.symbols.arc(
+ this.left + center[0],
+ this.top + center[1],
+ radii[0],
+ radii[0],
+ {
+ // Math is for reversed yAxis (#3606)
+ start: Math.min(start, end),
+ end: Math.max(start, end),
+ innerR: pick(radii[1], radii[0] - radii[2]),
+ open: open
+ }
+ );
+ }
- /**
- * Find the path for plot lines perpendicular to the radial axis.
- */
- getPlotLinePath: function(value, reverse) {
- var axis = this,
- center = axis.center,
- chart = axis.chart,
- end = axis.getPosition(value),
- xAxis,
- xy,
- tickPositions,
- ret;
+ return ret;
+ },
- // Spokes
- if (axis.isCircular) {
- ret = ['M', center[0] + chart.plotLeft, center[1] + chart.plotTop, 'L', end.x, end.y];
+ /* *
+ * Find the path for plot lines perpendicular to the radial axis.
+ */
+ getPlotLinePath: function (value, reverse) {
+ var axis = this,
+ center = axis.center,
+ chart = axis.chart,
+ end = axis.getPosition(value),
+ xAxis,
+ xy,
+ tickPositions,
+ ret;
+
+ // Spokes
+ if (axis.isCircular) {
+ ret = [
+ 'M',
+ center[0] + chart.plotLeft,
+ center[1] + chart.plotTop,
+ 'L',
+ end.x,
+ end.y
+ ];
// Concentric circles
- } else if (axis.options.gridLineInterpolation === 'circle') {
- value = axis.translate(value);
- if (value) { // a value of 0 is in the center
+ } else if (axis.options.gridLineInterpolation === 'circle') {
+ value = axis.translate(value);
+
+ // a value of 0 is in the center, so it won't be visible,
+ // but draw it anyway for update and animation (#2366)
ret = axis.getLinePath(0, value);
- }
// Concentric polygons
- } else {
- // Find the X axis in the same pane
- each(chart.xAxis, function(a) {
- if (a.pane === axis.pane) {
- xAxis = a;
+ } else {
+ // Find the X axis in the same pane
+ chart.xAxis.forEach(function (a) {
+ if (a.pane === axis.pane) {
+ xAxis = a;
+ }
+ });
+ ret = [];
+ value = axis.translate(value);
+ tickPositions = xAxis.tickPositions;
+ if (xAxis.autoConnect) {
+ tickPositions = tickPositions.concat([tickPositions[0]]);
+ }
+ // Reverse the positions for concatenation of polygonal plot
+ // bands
+ if (reverse) {
+ tickPositions = [].concat(tickPositions).reverse();
}
- });
- ret = [];
- value = axis.translate(value);
- tickPositions = xAxis.tickPositions;
- if (xAxis.autoConnect) {
- tickPositions = tickPositions.concat([tickPositions[0]]);
- }
- // Reverse the positions for concatenation of polygonal plot bands
- if (reverse) {
- tickPositions = [].concat(tickPositions).reverse();
- }
- each(tickPositions, function(pos, i) {
- xy = xAxis.getPosition(pos, value);
- ret.push(i ? 'L' : 'M', xy.x, xy.y);
- });
+ tickPositions.forEach(function (pos, i) {
+ xy = xAxis.getPosition(pos, value);
+ ret.push(i ? 'L' : 'M', xy.x, xy.y);
+ });
- }
- return ret;
- },
+ }
+ return ret;
+ },
- /**
- * Find the position for the axis title, by default inside the gauge
- */
- getTitlePosition: function() {
- var center = this.center,
+ /* *
+ * Find the position for the axis title, by default inside the gauge
+ */
+ getTitlePosition: function () {
+ var center = this.center,
+ chart = this.chart,
+ titleOptions = this.options.title;
+
+ return {
+ x: chart.plotLeft + center[0] + (titleOptions.x || 0),
+ y: (
+ chart.plotTop +
+ center[1] -
+ (
+ {
+ high: 0.5,
+ middle: 0.25,
+ low: 0
+ }[titleOptions.align] * center[2]
+ ) +
+ (titleOptions.y || 0)
+ )
+ };
+ }
+
+ };
+
+ // Actions before axis init.
+ addEvent(Axis, 'init', function (e) {
+ var axis = this,
chart = this.chart,
- titleOptions = this.options.title;
-
- return {
- x: chart.plotLeft + center[0] + (titleOptions.x || 0),
- y: chart.plotTop + center[1] - ({
- high: 0.5,
- middle: 0.25,
- low: 0
- }[titleOptions.align] *
- center[2]) + (titleOptions.y || 0)
- };
- }
+ angular = chart.angular,
+ polar = chart.polar,
+ isX = this.isXAxis,
+ isHidden = angular && isX,
+ isCircular,
+ chartOptions = chart.options,
+ paneIndex = e.userOptions.pane || 0,
+ pane = this.pane = chart.pane && chart.pane[paneIndex];
+
+ // Before prototype.init
+ if (angular) {
+ extend(this, isHidden ? hiddenAxisMixin : radialAxisMixin);
+ isCircular = !isX;
+ if (isCircular) {
+ this.defaultRadialOptions = this.defaultRadialGaugeOptions;
+ }
- };
+ } else if (polar) {
+ extend(this, radialAxisMixin);
+ isCircular = isX;
+ this.defaultRadialOptions = isX ?
+ this.defaultRadialXOptions :
+ merge(this.defaultYAxisOptions, this.defaultRadialYOptions);
- /**
- * Override axisProto.init to mix in special axis instance functions and function overrides
- */
- wrap(axisProto, 'init', function(proceed, chart, userOptions) {
- var angular = chart.angular,
- polar = chart.polar,
- isX = userOptions.isX,
- isHidden = angular && isX,
- isCircular,
- options,
- chartOptions = chart.options,
- paneIndex = userOptions.pane || 0,
- pane = this.pane = chart.pane && chart.pane[paneIndex],
- paneOptions = pane && pane.options;
-
- // Before prototype.init
- if (angular) {
- extend(this, isHidden ? hiddenAxisMixin : radialAxisMixin);
- isCircular = !isX;
- if (isCircular) {
- this.defaultRadialOptions = this.defaultRadialGaugeOptions;
}
- } else if (polar) {
- extend(this, radialAxisMixin);
- isCircular = isX;
- this.defaultRadialOptions = isX ? this.defaultRadialXOptions : merge(this.defaultYAxisOptions, this.defaultRadialYOptions);
+ // Disable certain features on angular and polar axes
+ if (angular || polar) {
+ this.isRadial = true;
+ chart.inverted = false;
+ chartOptions.chart.zoomType = null;
+
+ // Prevent overlapping axis labels (#9761)
+ chart.labelCollectors.push(function () {
+ if (
+ axis.isRadial &&
+ axis.tickPositions &&
+ // undocumented option for now, but working
+ axis.options.labels.allowOverlap !== true
+ ) {
+ return axis.tickPositions
+ .map(function (pos) {
+ return axis.ticks[pos] && axis.ticks[pos].label;
+ })
+ .filter(function (label) {
+ return Boolean(label);
+ });
+ }
+ });
+ } else {
+ this.isRadial = false;
+ }
- }
+ // A pointer back to this axis to borrow geometry
+ if (pane && isCircular) {
+ pane.axis = this;
+ }
- // Disable certain features on angular and polar axes
- if (angular || polar) {
- this.isRadial = true;
- chart.inverted = false;
- chartOptions.chart.zoomType = null;
- } else {
- this.isRadial = false;
- }
+ this.isCircular = isCircular;
- // A pointer back to this axis to borrow geometry
- if (pane && isCircular) {
- pane.axis = this;
- }
+ });
- // Run prototype.init
- proceed.call(this, chart, userOptions);
+ addEvent(Axis, 'afterInit', function () {
- if (!isHidden && pane && (angular || polar)) {
- options = this.options;
+ var chart = this.chart,
+ options = this.options,
+ isHidden = chart.angular && this.isXAxis,
+ pane = this.pane,
+ paneOptions = pane && pane.options;
- // Start and end angle options are
- // given in degrees relative to top, while internal computations are
- // in radians relative to right (like SVG).
- this.angleRad = (options.angle || 0) * Math.PI / 180; // Y axis in polar charts
- this.startAngleRad = (paneOptions.startAngle - 90) * Math.PI / 180; // Gauges
- this.endAngleRad = (pick(paneOptions.endAngle, paneOptions.startAngle + 360) - 90) * Math.PI / 180; // Gauges
- this.offset = options.offset || 0;
+ if (!isHidden && pane && (chart.angular || chart.polar)) {
- this.isCircular = isCircular;
+ // Start and end angle options are
+ // given in degrees relative to top, while internal computations are
+ // in radians relative to right (like SVG).
- }
+ // Y axis in polar charts
+ this.angleRad = (options.angle || 0) * Math.PI / 180;
+ // Gauges
+ this.startAngleRad = (paneOptions.startAngle - 90) * Math.PI / 180;
+ this.endAngleRad = (
+ pick(paneOptions.endAngle, paneOptions.startAngle + 360) - 90
+ ) * Math.PI / 180; // Gauges
+ this.offset = options.offset || 0;
- });
+ }
- /**
- * Wrap auto label align to avoid setting axis-wide rotation on radial axes (#4920)
- * @param {Function} proceed
- * @returns {String} Alignment
- */
- wrap(axisProto, 'autoLabelAlign', function(proceed) {
- if (!this.isRadial) {
- return proceed.apply(this, [].slice.call(arguments, 1));
- } // else return undefined
- });
+ });
- /**
- * Add special cases within the Tick class' methods for radial axes.
- */
- wrap(tickProto, 'getPosition', function(proceed, horiz, pos, tickmarkOffset, old) {
- var axis = this.axis;
+ // Wrap auto label align to avoid setting axis-wide rotation on radial axes
+ // (#4920)
+ addEvent(Axis, 'autoLabelAlign', function (e) {
+ if (this.isRadial) {
+ e.align = undefined;
+ e.preventDefault();
+ }
+ });
- return axis.getPosition ?
- axis.getPosition(pos) :
- proceed.call(this, horiz, pos, tickmarkOffset, old);
- });
+ // Add special cases within the Tick class' methods for radial axes.
+ addEvent(Tick, 'afterGetPosition', function (e) {
+ if (this.axis.getPosition) {
+ extend(e.pos, this.axis.getPosition(this.pos));
+ }
+ });
- /**
- * Wrap the getLabelPosition function to find the center position of the label
- * based on the distance option
- */
- wrap(tickProto, 'getLabelPosition', function(proceed, x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
- var axis = this.axis,
- optionsY = labelOptions.y,
- ret,
- centerSlot = 20, // 20 degrees to each side at the top and bottom
- align = labelOptions.align,
- angle = ((axis.translate(this.pos) + axis.startAngleRad + Math.PI / 2) / Math.PI * 180) % 360;
-
- if (axis.isRadial) { // Both X and Y axes in a polar chart
- ret = axis.getPosition(this.pos, (axis.center[2] / 2) + pick(labelOptions.distance, -25));
-
- // Automatically rotated
- if (labelOptions.rotation === 'auto') {
- label.attr({
- rotation: angle
- });
+ // Find the center position of the label based on the distance option.
+ addEvent(Tick, 'afterGetLabelPosition', function (e) {
+ var axis = this.axis,
+ label = this.label,
+ labelOptions = axis.options.labels,
+ optionsY = labelOptions.y,
+ ret,
+ centerSlot = 20, // 20 degrees to each side at the top and bottom
+ align = labelOptions.align,
+ angle = (
+ (axis.translate(this.pos) + axis.startAngleRad + Math.PI / 2) /
+ Math.PI * 180
+ ) % 360;
+
+ if (axis.isRadial) { // Both X and Y axes in a polar chart
+ ret = axis.getPosition(this.pos, (axis.center[2] / 2) +
+ pick(labelOptions.distance, -25));
+
+ // Automatically rotated
+ if (labelOptions.rotation === 'auto') {
+ label.attr({
+ rotation: angle
+ });
// Vertically centered
- } else if (optionsY === null) {
- optionsY = axis.chart.renderer.fontMetrics(label.styles.fontSize).b - label.getBBox().height / 2;
- }
+ } else if (optionsY === null) {
+ optionsY = (
+ axis.chart.renderer
+ .fontMetrics(label.styles && label.styles.fontSize).b -
+ label.getBBox().height / 2
+ );
+ }
- // Automatic alignment
- if (align === null) {
- if (axis.isCircular) { // Y axis
- if (this.label.getBBox().width > axis.len * axis.tickInterval / (axis.max - axis.min)) { // #3506
- centerSlot = 0;
- }
- if (angle > centerSlot && angle < 180 - centerSlot) {
- align = 'left'; // right hemisphere
- } else if (angle > 180 + centerSlot && angle < 360 - centerSlot) {
- align = 'right'; // left hemisphere
+ // Automatic alignment
+ if (align === null) {
+ if (axis.isCircular) { // Y axis
+ if (
+ this.label.getBBox().width >
+ axis.len * axis.tickInterval / (axis.max - axis.min)
+ ) { // #3506
+ centerSlot = 0;
+ }
+ if (angle > centerSlot && angle < 180 - centerSlot) {
+ align = 'left'; // right hemisphere
+ } else if (
+ angle > 180 + centerSlot &&
+ angle < 360 - centerSlot
+ ) {
+ align = 'right'; // left hemisphere
+ } else {
+ align = 'center'; // top or bottom
+ }
} else {
- align = 'center'; // top or bottom
+ align = 'center';
}
- } else {
- align = 'center';
+ label.attr({
+ align: align
+ });
}
- label.attr({
- align: align
- });
- }
- ret.x += labelOptions.x;
- ret.y += optionsY;
+ e.pos.x = ret.x + labelOptions.x;
+ e.pos.y = ret.y + optionsY;
- } else {
- ret = proceed.call(this, x, y, label, horiz, labelOptions, tickmarkOffset, index, step);
- }
- return ret;
- });
+ }
+ });
- /**
- * Wrap the getMarkPath function to return the path of the radial marker
- */
- wrap(tickProto, 'getMarkPath', function(proceed, x, y, tickLength, tickWidth, horiz, renderer) {
- var axis = this.axis,
- endPoint,
- ret;
-
- if (axis.isRadial) {
- endPoint = axis.getPosition(this.pos, axis.center[2] / 2 + tickLength);
- ret = [
- 'M',
- x,
- y,
- 'L',
- endPoint.x,
- endPoint.y
- ];
- } else {
- ret = proceed.call(this, x, y, tickLength, tickWidth, horiz, renderer);
- }
- return ret;
- });
+ // Wrap the getMarkPath function to return the path of the radial marker
+ wrap(tickProto, 'getMarkPath', function (
+ proceed,
+ x,
+ y,
+ tickLength,
+ tickWidth,
+ horiz,
+ renderer
+ ) {
+ var axis = this.axis,
+ endPoint,
+ ret;
+
+ if (axis.isRadial) {
+ endPoint = axis.getPosition(
+ this.pos,
+ axis.center[2] / 2 + tickLength
+ );
+ ret = [
+ 'M',
+ x,
+ y,
+ 'L',
+ endPoint.x,
+ endPoint.y
+ ];
+ } else {
+ ret = proceed.call(
+ this,
+ x,
+ y,
+ tickLength,
+ tickWidth,
+ horiz,
+ renderer
+ );
+ }
+ return ret;
+ });
+ }
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
- var each = H.each,
- noop = H.noop,
+
+
+
+ var noop = H.noop,
pick = H.pick,
+ extend = H.extend,
+ isArray = H.isArray,
defined = H.defined,
Series = H.Series,
seriesType = H.seriesType,
@@ -1033,157 +1119,153 @@
pointProto = H.Point.prototype;
/**
- * The area range series is a carteseian series with higher and lower values
- * for each point along an X axis, where the area between the values is shaded.
+ * The area range series is a carteseian series with higher and lower values for
+ * each point along an X axis, where the area between the values is shaded.
* Requires `highcharts-more.js`.
- *
- * @extends plotOptions.area
- * @product highcharts highstock
- * @sample {highcharts} highcharts/demo/arearange/ Area range chart
- * @sample {highstock} stock/demo/arearange/ Area range chart
+ *
+ * @sample {highcharts} highcharts/demo/arearange/
+ * Area range chart
+ * @sample {highstock} stock/demo/arearange/
+ * Area range chart
+ *
+ * @extends plotOptions.area
+ * @product highcharts highstock
+ * @excluding stack, stacking
* @optionparent plotOptions.arearange
*/
seriesType('arearange', 'area', {
+ /**
+ * Whether to apply a drop shadow to the graph line. Since 2.3 the shadow
+ * can be an object configuration containing `color`, `offsetX`, `offsetY`,
+ * `opacity` and `width`.
+ *
+ * @type {boolean|Highcharts.ShadowOptionsObject}
+ * @product highcharts
+ * @apioption plotOptions.arearange.shadow
+ */
/**
* Pixel width of the arearange graph line.
- *
- * @type {Number}
- * @default 1
- * @since 2.3.0
+ *
+ * @since 2.3.0
* @product highcharts highstock
*/
lineWidth: 1,
-
- /**
- * @default null
- */
threshold: null,
tooltip: {
-
-
- pointFormat: '\u25CF {series.name}: {point.low} - {point.high} ' // eslint-disable-line no-dupe-keys
-
+ pointFormat: '\u25CF ' +
+ '{series.name}: {point.low} - {point.high} '
},
/**
* Whether the whole area or just the line should respond to mouseover
* tooltips and other mouse or touch events.
- *
- * @type {Boolean}
- * @default true
- * @since 2.3.0
+ *
+ * @since 2.3.0
* @product highcharts highstock
*/
trackByArea: true,
/**
* Extended data labels for range series types. Range series data labels
- * have no `x` and `y` options. Instead, they have `xLow`, `xHigh`,
- * `yLow` and `yHigh` options to allow the higher and lower data label
- * sets individually.
- *
- * @type {Object}
- * @extends plotOptions.series.dataLabels
- * @excluding x,y
- * @since 2.3.0
- * @product highcharts highstock
+ * have no `x` and `y` options. Instead, they have `xLow`, `xHigh`, `yLow`
+ * and `yHigh` options to allow the higher and lower data label sets
+ * individually.
+ *
+ * @extends plotOptions.series.dataLabels
+ * @since 2.3.0
+ * @excluding x, y
+ * @product highcharts highstock
*/
dataLabels: {
+ /**
+ * @type {Highcharts.AlignType|null}
+ */
align: null,
+ /**
+ * @type {Highcharts.VerticalAlignType|null}
+ */
verticalAlign: null,
/**
* X offset of the lower data labels relative to the point value.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/arearange-datalabels/ Data labels on range series
- * @sample {highstock} highcharts/plotoptions/arearange-datalabels/ Data labels on range series
- * @default 0
- * @since 2.3.0
+ *
+ * @sample {highcharts} highcharts/plotoptions/arearange-datalabels/
+ * Data labels on range series
+ * @sample {highstock} highcharts/plotoptions/arearange-datalabels/
+ * Data labels on range series
+ *
+ * @since 2.3.0
* @product highcharts highstock
*/
xLow: 0,
/**
* X offset of the higher data labels relative to the point value.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/arearange-datalabels/ Data labels on range series
- * @sample {highstock} highcharts/plotoptions/arearange-datalabels/ Data labels on range series
- * @default 0
- * @since 2.3.0
+ *
+ * @sample {highcharts|highstock} highcharts/plotoptions/arearange-datalabels/
+ * Data labels on range series
+ *
+ * @since 2.3.0
* @product highcharts highstock
*/
xHigh: 0,
/**
* Y offset of the lower data labels relative to the point value.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/arearange-datalabels/ Data labels on range series
- * @sample {highstock} highcharts/plotoptions/arearange-datalabels/ Data labels on range series
- * @default 16
- * @since 2.3.0
+ *
+ * @sample {highcharts|highstock} highcharts/plotoptions/arearange-datalabels/
+ * Data labels on range series
+ *
+ * @since 2.3.0
* @product highcharts highstock
*/
yLow: 0,
/**
* Y offset of the higher data labels relative to the point value.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/arearange-datalabels/ Data labels on range series
- * @sample {highstock} highcharts/plotoptions/arearange-datalabels/ Data labels on range series
- * @default -6
- * @since 2.3.0
+ *
+ * @sample {highcharts|highstock} highcharts/plotoptions/arearange-datalabels/
+ * Data labels on range series
+ *
+ * @since 2.3.0
* @product highcharts highstock
*/
yHigh: 0
}
- /**
- * Whether to apply a drop shadow to the graph line. Since 2.3 the shadow
- * can be an object configuration containing `color`, `offsetX`, `offsetY`,
- * `opacity` and `width`.
- *
- * @type {Boolean|Object}
- * @product highcharts
- * @apioption plotOptions.arearange.shadow
- */
-
- // Prototype members
+ // Prototype members
}, {
pointArrayMap: ['low', 'high'],
- dataLabelCollections: ['dataLabel', 'dataLabelUpper'],
- toYData: function(point) {
+ toYData: function (point) {
return [point.low, point.high];
},
pointValKey: 'low',
deferTranslatePolar: true,
- /**
- * Translate a point's plotHigh from the internal angle and radius measures to
- * true plotHigh coordinates. This is an addition of the toXY method found in
- * Polar.js, because it runs too early for arearanges to be considered (#3419).
- */
- highToXY: function(point) {
+ // Translate a point's plotHigh from the internal angle and radius measures
+ // to true plotHigh coordinates. This is an addition of the toXY method
+ // found in Polar.js, because it runs too early for arearanges to be
+ // considered (#3419).
+ highToXY: function (point) {
// Find the polar plotX and plotY
var chart = this.chart,
- xy = this.xAxis.postTranslate(point.rectPlotX, this.yAxis.len - point.plotHigh);
+ xy = this.xAxis.postTranslate(
+ point.rectPlotX,
+ this.yAxis.len - point.plotHigh
+ );
+
point.plotHighX = xy.x - chart.plotLeft;
point.plotHigh = xy.y - chart.plotTop;
point.plotLowX = point.plotX;
},
- /**
- * Translate data points from raw values x and y to plotX and plotY
- */
- translate: function() {
+ // Translate data points from raw values x and y to plotX and plotY.
+ translate: function () {
var series = this,
yAxis = series.yAxis,
hasModifyValue = !!series.modifyValue;
@@ -1191,7 +1273,7 @@
seriesTypes.area.prototype.translate.apply(series);
// Set plotLow and plotHigh
- each(series.points, function(point) {
+ series.points.forEach(function (point) {
var low = point.low,
high = point.high,
@@ -1217,7 +1299,7 @@
// Postprocess plotHigh
if (this.chart.polar) {
- each(this.points, function(point) {
+ this.points.forEach(function (point) {
series.highToXY(point);
point.tooltipPos = [
(point.plotHighX + point.plotLowX) / 2,
@@ -1227,11 +1309,9 @@
}
},
- /**
- * Extend the line series' getSegmentPath method by applying the segment
- * path to both lower and higher values of the range
- */
- getGraphPath: function(points) {
+ // Extend the line series' getSegmentPath method by applying the segment
+ // path to both lower and higher values of the range.
+ getGraphPath: function (points) {
var highPoints = [],
highAreaPoints = [],
@@ -1251,14 +1331,15 @@
points = points || this.points;
i = points.length;
- // Create the top line and the top part of the area fill. The area fill compensates for
- // null points by drawing down to the lower graph, moving across the null gap and
- // starting again at the lower graph.
+ // Create the top line and the top part of the area fill. The area fill
+ // compensates for null points by drawing down to the lower graph,
+ // moving across the null gap and starting again at the lower graph.
i = points.length;
while (i--) {
point = points[i];
- if (!point.isNull &&
+ if (
+ !point.isNull &&
!connectEnds &&
!connectNulls &&
(!points[i + 1] || points[i + 1].isNull)
@@ -1274,7 +1355,8 @@
polarPlotY: point.polarPlotY,
rectPlotX: point.rectPlotX,
yBottom: point.yBottom,
- plotX: pick(point.plotHighX, point.plotX), // plotHighX is for polar charts
+ // plotHighX is for polar charts
+ plotX: pick(point.plotHighX, point.plotX),
plotY: point.plotHigh,
isNull: point.isNull
};
@@ -1283,7 +1365,8 @@
highPoints.push(pointShim);
- if (!point.isNull &&
+ if (
+ !point.isNull &&
!connectEnds &&
!connectNulls &&
(!points[i - 1] || points[i - 1].isNull)
@@ -1315,9 +1398,10 @@
// Create a line on both top and bottom of the range
linePath = [].concat(lowerPath, higherPath);
- // For the area path, we need to change the 'move' statement into 'lineTo' or 'curveTo'
+ // For the area path, we need to change the 'move' statement
+ // into 'lineTo' or 'curveTo'
if (!this.chart.polar && higherAreaPath[0] === 'M') {
- higherAreaPath[0] = 'L'; // this probably doesn't work for spline
+ higherAreaPath[0] = 'L'; // this probably doesn't work for spline
}
this.graphPath = linePath;
@@ -1331,110 +1415,162 @@
return linePath;
},
- /**
- * Extend the basic drawDataLabels method by running it for both lower and higher
- * values.
- */
- drawDataLabels: function() {
+ // Extend the basic drawDataLabels method by running it for both lower and
+ // higher values.
+ drawDataLabels: function () {
- var data = this.data,
+ var data = this.points,
length = data.length,
i,
originalDataLabels = [],
dataLabelOptions = this.options.dataLabels,
- align = dataLabelOptions.align,
- verticalAlign = dataLabelOptions.verticalAlign,
- inside = dataLabelOptions.inside,
point,
up,
- inverted = this.chart.inverted;
-
- if (dataLabelOptions.enabled || this._hasPointLabels) {
+ inverted = this.chart.inverted,
+ upperDataLabelOptions,
+ lowerDataLabelOptions;
+
+ // Split into upper and lower options. If data labels is an array, the
+ // first element is the upper label, the second is the lower.
+ //
+ // TODO: We want to change this and allow multiple labels for both upper
+ // and lower values in the future - introducing some options for which
+ // point value to use as Y for the dataLabel, so that this could be
+ // handled in Series.drawDataLabels. This would also improve performance
+ // since we now have to loop over all the points multiple times to work
+ // around the data label logic.
+ if (isArray(dataLabelOptions)) {
+ if (dataLabelOptions.length > 1) {
+ upperDataLabelOptions = dataLabelOptions[0];
+ lowerDataLabelOptions = dataLabelOptions[1];
+ } else {
+ upperDataLabelOptions = dataLabelOptions[0];
+ lowerDataLabelOptions = { enabled: false };
+ }
+ } else {
+ upperDataLabelOptions = extend({}, dataLabelOptions); // Make copy;
+ upperDataLabelOptions.x = dataLabelOptions.xHigh;
+ upperDataLabelOptions.y = dataLabelOptions.yHigh;
+ lowerDataLabelOptions = extend({}, dataLabelOptions); // Make copy
+ lowerDataLabelOptions.x = dataLabelOptions.xLow;
+ lowerDataLabelOptions.y = dataLabelOptions.yLow;
+ }
- // Step 1: set preliminary values for plotY and dataLabel and draw the upper labels
+ // Draw upper labels
+ if (upperDataLabelOptions.enabled || this._hasPointLabels) {
+ // Set preliminary values for plotY and dataLabel
+ // and draw the upper labels
i = length;
while (i--) {
point = data[i];
if (point) {
- up = inside ? point.plotHigh < point.plotLow : point.plotHigh > point.plotLow;
+ up = upperDataLabelOptions.inside ?
+ point.plotHigh < point.plotLow :
+ point.plotHigh > point.plotLow;
- // Set preliminary values
point.y = point.high;
point._plotY = point.plotY;
point.plotY = point.plotHigh;
- // Store original data labels and set preliminary label objects to be picked up
- // in the uber method
+ // Store original data labels and set preliminary label
+ // objects to be picked up in the uber method
originalDataLabels[i] = point.dataLabel;
point.dataLabel = point.dataLabelUpper;
// Set the default offset
point.below = up;
if (inverted) {
- if (!align) {
- dataLabelOptions.align = up ? 'right' : 'left';
+ if (!upperDataLabelOptions.align) {
+ upperDataLabelOptions.align = up ? 'right' : 'left';
}
} else {
- if (!verticalAlign) {
- dataLabelOptions.verticalAlign = up ? 'top' : 'bottom';
+ if (!upperDataLabelOptions.verticalAlign) {
+ upperDataLabelOptions.verticalAlign = up ?
+ 'top' :
+ 'bottom';
}
}
-
- dataLabelOptions.x = dataLabelOptions.xHigh;
- dataLabelOptions.y = dataLabelOptions.yHigh;
}
}
+ this.options.dataLabels = upperDataLabelOptions;
+
if (seriesProto.drawDataLabels) {
seriesProto.drawDataLabels.apply(this, arguments); // #1209
}
- // Step 2: reorganize and handle data labels for the lower values
+ // Reset state after the upper labels were created. Move
+ // it to point.dataLabelUpper and reassign the originals.
+ // We do this here to support not drawing a lower label.
i = length;
while (i--) {
point = data[i];
if (point) {
- up = inside ? point.plotHigh < point.plotLow : point.plotHigh > point.plotLow;
-
- // Move the generated labels from step 1, and reassign the original data labels
point.dataLabelUpper = point.dataLabel;
point.dataLabel = originalDataLabels[i];
-
- // Reset values
+ delete point.dataLabels;
point.y = point.low;
point.plotY = point._plotY;
+ }
+ }
+ }
+
+ // Draw lower labels
+ if (lowerDataLabelOptions.enabled || this._hasPointLabels) {
+ i = length;
+ while (i--) {
+ point = data[i];
+ if (point) {
+ up = lowerDataLabelOptions.inside ?
+ point.plotHigh < point.plotLow :
+ point.plotHigh > point.plotLow;
// Set the default offset
point.below = !up;
if (inverted) {
- if (!align) {
- dataLabelOptions.align = up ? 'left' : 'right';
+ if (!lowerDataLabelOptions.align) {
+ lowerDataLabelOptions.align = up ? 'left' : 'right';
}
} else {
- if (!verticalAlign) {
- dataLabelOptions.verticalAlign = up ? 'bottom' : 'top';
+ if (!lowerDataLabelOptions.verticalAlign) {
+ lowerDataLabelOptions.verticalAlign = up ?
+ 'bottom' :
+ 'top';
}
-
}
-
- dataLabelOptions.x = dataLabelOptions.xLow;
- dataLabelOptions.y = dataLabelOptions.yLow;
}
}
+
+ this.options.dataLabels = lowerDataLabelOptions;
+
if (seriesProto.drawDataLabels) {
seriesProto.drawDataLabels.apply(this, arguments);
}
}
- dataLabelOptions.align = align;
- dataLabelOptions.verticalAlign = verticalAlign;
+ // Merge upper and lower into point.dataLabels for later destroying
+ if (upperDataLabelOptions.enabled) {
+ i = length;
+ while (i--) {
+ point = data[i];
+ if (point) {
+ point.dataLabels = [point.dataLabelUpper, point.dataLabel]
+ .filter(function (label) {
+ return !!label;
+ });
+ }
+ }
+ }
+
+ // Reset options
+ this.options.dataLabels = dataLabelOptions;
},
- alignDataLabel: function() {
+ alignDataLabel: function () {
seriesTypes.column.prototype.alignDataLabel.apply(this, arguments);
},
- drawPoints: function() {
+ drawPoints: function () {
var series = this,
pointLength = series.points.length,
point,
@@ -1443,37 +1579,62 @@
// Draw bottom points
seriesProto.drawPoints.apply(series, arguments);
+ // Prepare drawing top points
i = 0;
while (i < pointLength) {
point = series.points[i];
+
+ // Save original props to be overridden by temporary props for top
+ // points
+ point.origProps = {
+ plotY: point.plotY,
+ plotX: point.plotX,
+ isInside: point.isInside,
+ negative: point.negative,
+ zone: point.zone,
+ y: point.y
+ };
+
point.lowerGraphic = point.graphic;
point.graphic = point.upperGraphic;
- point._plotY = point.plotY;
- point._plotX = point.plotX;
point.plotY = point.plotHigh;
if (defined(point.plotHighX)) {
point.plotX = point.plotHighX;
}
+ point.y = point.high;
+ point.negative = point.high < (series.options.threshold || 0);
+ point.zone = series.zones.length && point.getZone();
+
+ if (!series.chart.polar) {
+ point.isInside = point.isTopInside = (
+ point.plotY !== undefined &&
+ point.plotY >= 0 &&
+ point.plotY <= series.yAxis.len && // #3519
+ point.plotX >= 0 &&
+ point.plotX <= series.xAxis.len
+ );
+ }
i++;
}
// Draw top points
seriesProto.drawPoints.apply(series, arguments);
+ // Reset top points preliminary modifications
i = 0;
while (i < pointLength) {
point = series.points[i];
point.upperGraphic = point.graphic;
point.graphic = point.lowerGraphic;
- point.plotY = point._plotY;
- point.plotX = point._plotX;
+ H.extend(point, point.origProps);
+ delete point.origProps;
i++;
}
},
setStackedPoints: noop
}, {
- setState: function() {
+ setState: function () {
var prevState = this.state,
series = this.series,
isPolar = series.chart.polar;
@@ -1489,8 +1650,10 @@
this.plotLow = this.plotY = series.yAxis.toPixels(this.low, true);
}
- // Bottom state:
- pointProto.setState.apply(this, arguments);
+ if (series.stateMarkerGraphic) {
+ series.lowerStateMarkerGraphic = series.stateMarkerGraphic;
+ series.stateMarkerGraphic = series.upperStateMarkerGraphic;
+ }
// Change state also for the top marker
this.graphic = this.upperGraphic;
@@ -1500,15 +1663,11 @@
this.plotX = this.plotHighX;
}
- this.state = prevState;
-
- if (series.stateMarkerGraphic) {
- series.lowerStateMarkerGraphic = series.stateMarkerGraphic;
- series.stateMarkerGraphic = series.upperStateMarkerGraphic;
- }
-
+ // Top state:
pointProto.setState.apply(this, arguments);
+ this.state = prevState;
+
// Now restore defaults
this.plotY = this.plotLow;
this.graphic = this.lowerGraphic;
@@ -1524,8 +1683,11 @@
// to avoid reference duplication (#7021)
series.lowerStateMarkerGraphic = undefined;
}
+
+ pointProto.setState.apply(this, arguments);
+
},
- haloPath: function() {
+ haloPath: function () {
var isPolar = this.series.chart.polar,
path = [];
@@ -1535,71 +1697,76 @@
this.plotX = this.plotLowX;
}
- path = pointProto.haloPath.apply(this, arguments);
+ if (this.isInside) {
+ path = pointProto.haloPath.apply(this, arguments);
+ }
// Top halo
this.plotY = this.plotHigh;
if (isPolar) {
this.plotX = this.plotHighX;
}
- path = path.concat(
- pointProto.haloPath.apply(this, arguments)
- );
+ if (this.isTopInside) {
+ path = path.concat(
+ pointProto.haloPath.apply(this, arguments)
+ );
+ }
return path;
},
- destroy: function() {
- if (this.upperGraphic) {
- this.upperGraphic = this.upperGraphic.destroy();
- }
- return pointProto.destroy.apply(this, arguments);
+ destroyElements: function () {
+ var graphics = ['lowerGraphic', 'upperGraphic'];
+
+ graphics.forEach(function (graphicName) {
+ if (this[graphicName]) {
+ this[graphicName] = this[graphicName].destroy();
+ }
+ }, this);
+
+ // Clear graphic for states, removed in the above each:
+ this.graphic = null;
+
+ return pointProto.destroyElements.apply(this, arguments);
}
});
/**
- * A `arearange` series. If the [type](#series.arearange.type) option
- * is not specified, it is inherited from [chart.type](#chart.type).
- *
- *
- * For options that apply to multiple series, it is recommended to add
- * them to the [plotOptions.series](#plotOptions.series) options structure.
- * To apply to all series of this specific type, apply it to [plotOptions.
- * arearange](#plotOptions.arearange).
- *
- * @type {Object}
- * @extends series,plotOptions.arearange
- * @excluding dataParser,dataURL,stack
- * @product highcharts highstock
+ * A `arearange` series. If the [type](#series.arearange.type) option is not
+ * specified, it is inherited from [chart.type](#chart.type).
+ *
+ *
+ * @extends series,plotOptions.arearange
+ * @excluding dataParser, dataURL, stack, stacking
+ * @product highcharts highstock
* @apioption series.arearange
*/
/**
- * An array of data points for the series. For the `arearange` series
- * type, points can be given in the following ways:
- *
+ * An array of data points for the series. For the `arearange` series type,
+ * points can be given in the following ways:
+ *
* 1. An array of arrays with 3 or 2 values. In this case, the values
- * correspond to `x,low,high`. If the first value is a string, it is
- * applied as the name of the point, and the `x` value is inferred.
- * The `x` value can also be omitted, in which case the inner arrays
- * should be of length 2\. Then the `x` value is automatically calculated,
- * either starting at 0 and incremented by 1, or from `pointStart`
- * and `pointInterval` given in the series options.
- *
- * ```js
+ * correspond to `x,low,high`. If the first value is a string, it is
+ * applied as the name of the point, and the `x` value is inferred.
+ * The `x` value can also be omitted, in which case the inner arrays
+ * should be of length 2\. Then the `x` value is automatically calculated,
+ * either starting at 0 and incremented by 1, or from `pointStart`
+ * and `pointInterval` given in the series options.
+ * ```js
* data: [
* [0, 8, 3],
* [1, 1, 1],
* [2, 6, 8]
* ]
- * ```
- *
- * 2. An array of objects with named values. The objects are point
- * configuration objects as seen below. If the total number of data
- * points exceeds the series' [turboThreshold](#series.arearange.turboThreshold),
- * this option is not available.
- *
- * ```js
+ * ```
+ *
+ * 2. An array of objects with named values. The following snippet shows only a
+ * few settings, see the complete options set below. If the total number of
+ * data points exceeds the series'
+ * [turboThreshold](#series.arearange.turboThreshold),
+ * this option is not available.
+ * ```js
* data: [{
* x: 1,
* low: 9,
@@ -1613,44 +1780,56 @@
* name: "Point1",
* color: "#FF00FF"
* }]
- * ```
- *
- * @type {Array}
- * @extends series.line.data
- * @excluding marker,y
- * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
- * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
- * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
- * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
- * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
- * @product highcharts highstock
+ * ```
+ *
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/
+ * Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
+ * Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/
+ * Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
+ *
+ * @type {Array|Array<(number|string),number,number>|*>}
+ * @extends series.line.data
+ * @excluding marker, y
+ * @product highcharts highstock
* @apioption series.arearange.data
*/
/**
* The high or maximum value for each data point.
- *
- * @type {Number}
- * @product highcharts highstock
+ *
+ * @type {number}
+ * @product highcharts highstock
* @apioption series.arearange.data.high
*/
/**
* The low or minimum value for each data point.
- *
- * @type {Number}
- * @product highcharts highstock
+ *
+ * @type {number}
+ * @product highcharts highstock
* @apioption series.arearange.data.low
*/
- }(Highcharts));
- (function(H) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * @excluding x, y
+ * @product highcharts highstock
+ * @apioption series.arearange.dataLabels
+ */
+
+ }(Highcharts));
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
var seriesType = H.seriesType,
seriesTypes = H.seriesTypes;
@@ -1659,13 +1838,14 @@
* lower Y values along an X axis. The area inside the range is colored, and
* the graph outlining the area is a smoothed spline. Requires
* `highcharts-more.js`.
- *
- * @extends plotOptions.arearange
+ *
+ * @sample {highstock|highstock} stock/demo/areasplinerange/
+ * Area spline range
+ *
+ * @extends plotOptions.arearange
+ * @since 2.3.0
* @excluding step
- * @since 2.3.0
- * @sample {highstock} stock/demo/areasplinerange/ Area spline range
- * @sample {highstock} stock/demo/areasplinerange/ Area spline range
- * @product highcharts highstock
+ * @product highcharts highstock
* @apioption plotOptions.areasplinerange
*/
seriesType('areasplinerange', 'arearange', null, {
@@ -1673,83 +1853,81 @@
});
/**
- * A `areasplinerange` series. If the [type](#series.areasplinerange.
- * type) option is not specified, it is inherited from [chart.type](#chart.
- * type).
- *
- * For options that apply to multiple series, it is recommended to add
- * them to the [plotOptions.series](#plotOptions.series) options structure.
- * To apply to all series of this specific type, apply it to [plotOptions.
- * areasplinerange](#plotOptions.areasplinerange).
- *
- * @type {Object}
- * @extends series,plotOptions.areasplinerange
- * @excluding dataParser,dataURL,stack
- * @product highcharts highstock
+ * A `areasplinerange` series. If the [type](#series.areasplinerange.type)
+ * option is not specified, it is inherited from [chart.type](#chart.type).
+ *
+ * @extends series,plotOptions.areasplinerange
+ * @excluding dataParser, dataURL, stack
+ * @product highcharts highstock
* @apioption series.areasplinerange
*/
/**
* An array of data points for the series. For the `areasplinerange`
* series type, points can be given in the following ways:
- *
- * 1. An array of arrays with 3 or 2 values. In this case, the values
- * correspond to `x,low,high`. If the first value is a string, it is
- * applied as the name of the point, and the `x` value is inferred.
- * The `x` value can also be omitted, in which case the inner arrays
- * should be of length 2\. Then the `x` value is automatically calculated,
- * either starting at 0 and incremented by 1, or from `pointStart`
- * and `pointInterval` given in the series options.
- *
- * ```js
- * data: [
- * [0, 0, 5],
- * [1, 9, 1],
- * [2, 5, 2]
- * ]
- * ```
- *
- * 2. An array of objects with named values. The objects are point
- * configuration objects as seen below. If the total number of data
- * points exceeds the series' [turboThreshold](#series.areasplinerange.
- * turboThreshold), this option is not available.
- *
- * ```js
- * data: [{
- * x: 1,
- * low: 5,
- * high: 0,
- * name: "Point2",
- * color: "#00FF00"
- * }, {
- * x: 1,
- * low: 4,
- * high: 1,
- * name: "Point1",
- * color: "#FF00FF"
- * }]
- * ```
- *
- * @type {Array}
- * @extends series.arearange.data
- * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
- * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
- * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
- * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
- * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
- * @product highcharts highstock
+ *
+ * 1. An array of arrays with 3 or 2 values. In this case, the values correspond
+ * to `x,low,high`. If the first value is a string, it is applied as the name
+ * of the point, and the `x` value is inferred. The `x` value can also be
+ * omitted, in which case the inner arrays should be of length 2\. Then the
+ * `x` value is automatically calculated, either starting at 0 and
+ * incremented by 1, or from `pointStart` and `pointInterval` given in the
+ * series options.
+ * ```js
+ * data: [
+ * [0, 0, 5],
+ * [1, 9, 1],
+ * [2, 5, 2]
+ * ]
+ * ```
+ *
+ * 2. An array of objects with named values. The following snippet shows only a
+ * few settings, see the complete options set below. If the total number of
+ * data points exceeds the series'
+ * [turboThreshold](#series.areasplinerange.turboThreshold), this option is
+ * not available.
+ * ```js
+ * data: [{
+ * x: 1,
+ * low: 5,
+ * high: 0,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * low: 4,
+ * high: 1,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/
+ * Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
+ * Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/
+ * Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
+ *
+ * @type {Array|Array<(number|string),number,number>|*>}
+ * @extends series.arearange.data
+ * @product highcharts highstock
* @apioption series.areasplinerange.data
*/
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
+
var defaultPlotOptions = H.defaultPlotOptions,
- each = H.each,
merge = H.merge,
noop = H.noop,
pick = H.pick,
@@ -1757,50 +1935,57 @@
seriesTypes = H.seriesTypes;
var colProto = seriesTypes.column.prototype;
+
/**
* The column range is a cartesian series type with higher and lower
* Y values along an X axis. Requires `highcharts-more.js`. To display
* horizontal bars, set [chart.inverted](#chart.inverted) to `true`.
*
- * @type {Object}
- * @extends plotOptions.column
- * @excluding negativeColor,stacking,softThreshold,threshold
- * @sample {highcharts} highcharts/demo/columnrange/
- * Inverted column range
- * @sample {highstock} highcharts/demo/columnrange/
+ * @sample {highcharts|highstock} highcharts/demo/columnrange/
* Inverted column range
- * @since 2.3.0
- * @product highcharts highstock
+ *
+ * @extends plotOptions.column
+ * @since 2.3.0
+ * @excluding negativeColor, stacking, softThreshold, threshold
+ * @product highcharts highstock
* @optionparent plotOptions.columnrange
*/
var columnRangeOptions = {
- pointRange: null,
- marker: null,
- states: {
- hover: {
- /**
- * @ignore-option
- */
- halo: false
- }
- }
-
/**
* Extended data labels for range series types. Range series data labels
* have no `x` and `y` options. Instead, they have `xLow`, `xHigh`,
* `yLow` and `yHigh` options to allow the higher and lower data label
* sets individually.
*
- * @type {Object}
- * @extends plotOptions.arearange.dataLabels
- * @since 2.3.0
- * @product highcharts highstock
+ * @extends plotOptions.arearange.dataLabels
+ * @since 2.3.0
+ * @excluding x, y
+ * @product highcharts highstock
* @apioption plotOptions.columnrange.dataLabels
*/
+
+ pointRange: null,
+
+ /** @ignore-option */
+ marker: null,
+
+ states: {
+ hover: {
+ /** @ignore-option */
+ halo: false
+ }
+ }
};
+
/**
* The ColumnRangeSeries class
+ *
+ * @private
+ * @class
+ * @name Highcharts.seriesTypes.columnrange
+ *
+ * @augments Highcharts.Series
*/
seriesType('columnrange', 'arearange', merge(
defaultPlotOptions.column,
@@ -1808,10 +1993,9 @@
columnRangeOptions
), {
- /**
- * Translate data points from raw values x and y to plotX and plotY
- */
- translate: function() {
+
+ // Translate data points from raw values x and y to plotX and plotY
+ translate: function () {
var series = this,
yAxis = series.yAxis,
xAxis = series.xAxis,
@@ -1824,7 +2008,8 @@
// Don't draw too far outside plot area (#6835)
function safeBounds(pixelPos) {
- return Math.min(Math.max(-safeDistance,
+ return Math.min(Math.max(
+ -safeDistance,
pixelPos
), safeDistance);
}
@@ -1833,7 +2018,7 @@
colProto.translate.apply(series);
// Set plotLow and plotHigh
- each(series.points, function(point) {
+ series.points.forEach(function (point) {
var shapeArgs = point.shapeArgs,
minPointLength = series.options.minPointLength,
heightDifference,
@@ -1855,7 +2040,7 @@
height += heightDifference;
y -= heightDifference / 2;
- // Adjust for negative ranges or reversed Y axis (#1457)
+ // Adjust for negative ranges or reversed Y axis (#1457)
} else if (height < 0) {
height *= -1;
y -= height;
@@ -1866,24 +2051,30 @@
start = point.barX + startAngleRad;
point.shapeType = 'path';
point.shapeArgs = {
- d: series.polarArc(y + height, y, start, start + point.pointWidth)
+ d: series.polarArc(
+ y + height,
+ y,
+ start,
+ start + point.pointWidth
+ )
};
} else {
shapeArgs.height = height;
shapeArgs.y = y;
- point.tooltipPos = chart.inverted ? [
- yAxis.len + yAxis.pos - chart.plotLeft - y - height / 2,
- xAxis.len + xAxis.pos - chart.plotTop - shapeArgs.x -
- shapeArgs.width / 2,
- height
- ] : [
- xAxis.left - chart.plotLeft + shapeArgs.x +
- shapeArgs.width / 2,
- yAxis.pos - chart.plotTop + y + height / 2,
- height
- ]; // don't inherit from column tooltip position - #3372
+ point.tooltipPos = chart.inverted ?
+ [
+ yAxis.len + yAxis.pos - chart.plotLeft - y - height / 2,
+ xAxis.len + xAxis.pos - chart.plotTop - shapeArgs.x -
+ shapeArgs.width / 2,
+ height
+ ] : [
+ xAxis.left - chart.plotLeft + shapeArgs.x +
+ shapeArgs.width / 2,
+ yAxis.pos - chart.plotTop + y + height / 2,
+ height
+ ]; // don't inherit from column tooltip position - #3372
}
});
},
@@ -1891,23 +2082,33 @@
trackerGroups: ['group', 'dataLabelsGroup'],
drawGraph: noop,
getSymbol: noop,
- crispCol: colProto.crispCol,
- drawPoints: colProto.drawPoints,
- drawTracker: colProto.drawTracker,
- getColumnMetrics: colProto.getColumnMetrics,
- pointAttribs: colProto.pointAttribs,
// Overrides from modules that may be loaded after this module
- animate: function() {
+ crispCol: function () {
+ return colProto.crispCol.apply(this, arguments);
+ },
+ drawPoints: function () {
+ return colProto.drawPoints.apply(this, arguments);
+ },
+ drawTracker: function () {
+ return colProto.drawTracker.apply(this, arguments);
+ },
+ getColumnMetrics: function () {
+ return colProto.getColumnMetrics.apply(this, arguments);
+ },
+ pointAttribs: function () {
+ return colProto.pointAttribs.apply(this, arguments);
+ },
+ animate: function () {
return colProto.animate.apply(this, arguments);
},
- polarArc: function() {
+ polarArc: function () {
return colProto.polarArc.apply(this, arguments);
},
- translate3dPoints: function() {
+ translate3dPoints: function () {
return colProto.translate3dPoints.apply(this, arguments);
},
- translate3dShapes: function() {
+ translate3dShapes: function () {
return colProto.translate3dShapes.apply(this, arguments);
}
}, {
@@ -1917,18 +2118,12 @@
/**
* A `columnrange` series. If the [type](#series.columnrange.type)
- * option is not specified, it is inherited from [chart.type](#chart.
- * type).
- *
- * For options that apply to multiple series, it is recommended to add
- * them to the [plotOptions.series](#plotOptions.series) options structure.
- * To apply to all series of this specific type, apply it to [plotOptions.
- * columnrange](#plotOptions.columnrange).
- *
- * @type {Object}
- * @extends series,plotOptions.columnrange
- * @excluding dataParser,dataURL,stack
- * @product highcharts highstock
+ * option is not specified, it is inherited from
+ * [chart.type](#chart.type).
+ *
+ * @extends series,plotOptions.columnrange
+ * @excluding dataParser, dataURL, stack, stacking
+ * @product highcharts highstock
* @apioption series.columnrange
*/
@@ -1936,65 +2131,369 @@
* An array of data points for the series. For the `columnrange` series
* type, points can be given in the following ways:
*
- * 1. An array of arrays with 3 or 2 values. In this case, the values
- * correspond to `x,low,high`. If the first value is a string, it is
- * applied as the name of the point, and the `x` value is inferred.
- * The `x` value can also be omitted, in which case the inner arrays
- * should be of length 2\. Then the `x` value is automatically calculated,
- * either starting at 0 and incremented by 1, or from `pointStart`
- * and `pointInterval` given in the series options.
- *
- * ```js
- * data: [
- * [0, 4, 2],
- * [1, 2, 1],
- * [2, 9, 10]
- * ]
- * ```
+ * 1. An array of arrays with 3 or 2 values. In this case, the values correspond
+ * to `x,low,high`. If the first value is a string, it is applied as the name
+ * of the point, and the `x` value is inferred. The `x` value can also be
+ * omitted, in which case the inner arrays should be of length 2\. Then the
+ * `x` value is automatically calculated, either starting at 0 and
+ * incremented by 1, or from `pointStart` and `pointInterval` given in the
+ * series options.
+ * ```js
+ * data: [
+ * [0, 4, 2],
+ * [1, 2, 1],
+ * [2, 9, 10]
+ * ]
+ * ```
*
- * 2. An array of objects with named values. The objects are point
- * configuration objects as seen below. If the total number of data
- * points exceeds the series' [turboThreshold](#series.columnrange.
- * turboThreshold), this option is not available.
+ * 2. An array of objects with named values. The following snippet shows only a
+ * few settings, see the complete options set below. If the total number of
+ * data points exceeds the series'
+ * [turboThreshold](#series.columnrange.turboThreshold), this option is not
+ * available.
+ * ```js
+ * data: [{
+ * x: 1,
+ * low: 0,
+ * high: 4,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * low: 5,
+ * high: 3,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
*
- * ```js
- * data: [{
- * x: 1,
- * low: 0,
- * high: 4,
- * name: "Point2",
- * color: "#00FF00"
- * }, {
- * x: 1,
- * low: 5,
- * high: 3,
- * name: "Point1",
- * color: "#FF00FF"
- * }]
- * ```
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/
+ * Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
+ * Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/
+ * Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
*
- * @type {Array}
- * @extends series.arearange.data
+ * @type {Array|Array<(number|string),number,number>|*>}
+ * @extends series.arearange.data
* @excluding marker
- * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
- * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
- * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
- * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
- * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
- * @product highcharts highstock
+ * @product highcharts highstock
* @apioption series.columnrange.data
*/
+ /**
+ * @excluding halo, lineWidth, lineWidthPlus, marker
+ * @product highcharts highstock
+ * @apioption series.columnrange.states.hover
+ */
+
+ /**
+ * @excluding halo, lineWidth, lineWidthPlus, marker
+ * @product highcharts highstock
+ * @apioption series.columnrange.states.select
+ */
}(Highcharts));
- (function(H) {
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Sebastian Bochan
+ *
+ * License: www.highcharts.com/license
+ */
+
+
+
+ var pick = H.pick,
+ seriesType = H.seriesType,
+ seriesTypes = H.seriesTypes;
+
+ var colProto = seriesTypes.column.prototype;
+
+ /**
+ * The ColumnPyramidSeries class
+ *
+ * @private
+ * @class
+ * @name Highcharts.seriesTypes.columnpyramid
+ *
+ * @augments Highcharts.Series
+ */
+ seriesType('columnpyramid', 'column'
+
+ /**
+ * Column pyramid series display one pyramid per value along an X axis.
+ * Requires `highcharts-more.js`. To display horizontal pyramids,
+ * set [chart.inverted](#chart.inverted) to `true`.
+ *
+ * @sample {highcharts|highstock} highcharts/demo/column-pyramid/
+ * Column pyramid
+ * @sample {highcharts|highstock} highcharts/plotoptions/columnpyramid-stacked/
+ * Column pyramid stacked
+ * @sample {highcharts|highstock} highcharts/plotoptions/columnpyramid-inverted/
+ * Column pyramid inverted
+ *
+ * @extends plotOptions.column
+ * @since 7.0.0
+ * @product highcharts highstock
+ * @excluding boostThreshold, borderRadius, crisp, depth, edgeColor,
+ * edgeWidth, groupZPadding, negativeColor, softThreshold,
+ * threshold, zoneAxis, zones
+ * @optionparent plotOptions.columnpyramid
+ */
+ , {}, {
+ // Overrides the column translate method
+ translate: function () {
+ var series = this,
+ chart = series.chart,
+ options = series.options,
+ dense = series.dense =
+ series.closestPointRange * series.xAxis.transA < 2,
+ borderWidth = series.borderWidth = pick(
+ options.borderWidth,
+ dense ? 0 : 1 // #3635
+ ),
+ yAxis = series.yAxis,
+ threshold = options.threshold,
+ translatedThreshold = series.translatedThreshold =
+ yAxis.getThreshold(threshold),
+ minPointLength = pick(options.minPointLength, 5),
+ metrics = series.getColumnMetrics(),
+ pointWidth = metrics.width,
+ // postprocessed for border width
+ seriesBarW = series.barW =
+ Math.max(pointWidth, 1 + 2 * borderWidth),
+ pointXOffset = series.pointXOffset = metrics.offset;
+
+ if (chart.inverted) {
+ translatedThreshold -= 0.5; // #3355
+ }
+
+ // When the pointPadding is 0,
+ // we want the pyramids to be packed tightly,
+ // so we allow individual pyramids to have individual sizes.
+ // When pointPadding is greater,
+ // we strive for equal-width columns (#2694).
+ if (options.pointPadding) {
+ seriesBarW = Math.ceil(seriesBarW);
+ }
+
+ colProto.translate.apply(series);
+
+ // Record the new values
+ series.points.forEach(function (point) {
+ var yBottom = pick(point.yBottom, translatedThreshold),
+ safeDistance = 999 + Math.abs(yBottom),
+ plotY = Math.min(
+ Math.max(-safeDistance, point.plotY),
+ yAxis.len + safeDistance
+ ),
+ // Don't draw too far outside plot area
+ // (#1303, #2241, #4264)
+ barX = point.plotX + pointXOffset,
+ barW = seriesBarW / 2,
+ barY = Math.min(plotY, yBottom),
+ barH = Math.max(plotY, yBottom) - barY,
+ stackTotal, stackHeight, topPointY, topXwidth, bottomXwidth,
+ invBarPos,
+ x1, x2, x3, x4, y1, y2;
+
+
+ point.barX = barX;
+ point.pointWidth = pointWidth;
+
+ // Fix the tooltip on center of grouped pyramids
+ // (#1216, #424, #3648)
+ point.tooltipPos = chart.inverted ? [
+ yAxis.len + yAxis.pos - chart.plotLeft - plotY,
+ series.xAxis.len - barX - barW, barH
+ ] : [barX + barW, plotY + yAxis.pos - chart.plotTop, barH];
+
+ stackTotal = threshold + (point.total || point.y);
+
+ // overwrite stacktotal (always 100 / -100)
+ if (options.stacking === 'percent') {
+ stackTotal = threshold + (point.y < 0) ? -100 : 100;
+ }
+
+ // get the highest point (if stack, extract from total)
+ topPointY = yAxis.toPixels((stackTotal), true);
+
+ // calculate height of stack (in pixels)
+ stackHeight = chart.plotHeight - topPointY -
+ (chart.plotHeight - translatedThreshold);
+
+ // topXwidth and bottomXwidth = width of lines from the center
+ // calculated from tanges proportion.
+ topXwidth = (barW * (barY - topPointY)) / stackHeight;
+ // like topXwidth, but with height of point
+ bottomXwidth = (barW * (barY + barH - topPointY)) / stackHeight;
+
+ /*
+ /\
+ / \
+ x1,y1,------ x2,y1
+ / \
+ ----------
+ x4,y2 x3,y2
+ */
+
+ x1 = barX - topXwidth + barW;
+ x2 = barX + topXwidth + barW;
+ x3 = barX + bottomXwidth + barW;
+ x4 = barX - bottomXwidth + barW;
+
+ y1 = barY - minPointLength;
+ y2 = barY + barH;
+
+ if (point.y < 0) {
+ y1 = barY;
+ y2 = barY + barH + minPointLength;
+ }
+
+ // inverted chart
+ if (chart.inverted) {
+ invBarPos = chart.plotWidth - barY;
+ stackHeight = (topPointY -
+ (chart.plotWidth - translatedThreshold));
+
+ // proportion tanges
+ topXwidth = (barW *
+ (topPointY - invBarPos)) / stackHeight;
+ bottomXwidth = (barW *
+ (topPointY - (invBarPos - barH))) / stackHeight;
+
+ x1 = barX + barW + topXwidth; // top bottom
+ x2 = x1 - 2 * topXwidth; // top top
+ x3 = barX - bottomXwidth + barW; // bottom top
+ x4 = barX + bottomXwidth + barW; // bottom bottom
+
+ y1 = barY;
+ y2 = barY + barH - minPointLength;
+
+ if (point.y < 0) {
+ y2 = barY + barH + minPointLength;
+ }
+ }
+
+ // Register shape type and arguments to be used in drawPoints
+ point.shapeType = 'path';
+ point.shapeArgs = {
+ // args for datalabels positioning
+ x: x1,
+ y: y1,
+ width: x2 - x1,
+ height: barH,
+ // path of pyramid
+ d: ['M',
+ x1, y1,
+ 'L',
+ x2, y1,
+ x3, y2,
+ x4, y2,
+ 'Z'
+ ]
+ };
+ });
+ }
+ });
+
+
+ /**
+ * A `columnpyramid` series. If the [type](#series.columnpyramid.type) option is
+ * not specified, it is inherited from [chart.type](#chart.type).
+ *
+ * @extends series,plotOptions.columnpyramid
+ * @excluding connectEnds, connectNulls, dashStyle, dataParser, dataURL,
+ * gapSize, gapUnit, linecap, lineWidth, marker, step
+ * @product highcharts highstock
+ * @apioption series.columnpyramid
+ */
+
+ /**
+ * @excluding halo, lineWidth, lineWidthPlus, marker
+ * @product highcharts highstock
+ * @apioption series.columnpyramid.states.hover
+ */
+
+ /**
+ * @excluding halo, lineWidth, lineWidthPlus, marker
+ * @product highcharts highstock
+ * @apioption series.columnpyramid.states.select
+ */
+
/**
- * (c) 2010-2017 Torstein Honsi
+ * An array of data points for the series. For the `columnpyramid` series type,
+ * points can be given in the following ways:
+ *
+ * 1. An array of numerical values. In this case, the numerical values will be
+ * interpreted as `y` options. The `x` values will be automatically
+ * calculated, either starting at 0 and incremented by 1, or from
+ * `pointStart` and `pointInterval` given in the series options. If the axis
+ * has categories, these will be used. Example:
+ * ```js
+ * data: [0, 5, 3, 5]
+ * ```
+ *
+ * 2. An array of arrays with 2 values. In this case, the values correspond to
+ * `x,y`. If the first value is a string, it is applied as the name of the
+ * point, and the `x` value is inferred.
+ * ```js
+ * data: [
+ * [0, 6],
+ * [1, 2],
+ * [2, 6]
+ * ]
+ * ```
+ *
+ * 3. An array of objects with named values. The objects are point configuration
+ * objects as seen below. If the total number of data points exceeds the
+ * series' [turboThreshold](#series.columnpyramid.turboThreshold), this
+ * option is not available.
+ * ```js
+ * data: [{
+ * x: 1,
+ * y: 9,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * y: 6,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @sample {highcharts} highcharts/chart/reflow-true/
+ * Numerical values
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/
+ * Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
+ * Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/
+ * Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
+ *
+ * @type {Array|*>}
+ * @extends series.line.data
+ * @excluding marker
+ * @product highcharts highstock
+ * @apioption series.columnpyramid.data
+ */
+
+ }(Highcharts));
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
- var each = H.each,
- isNumber = H.isNumber,
+
+
+
+ var isNumber = H.isNumber,
merge = H.merge,
noop = H.noop,
pick = H.pick,
@@ -2003,35 +2502,52 @@
seriesType = H.seriesType,
TrackerMixin = H.TrackerMixin;
-
- /**
+ /**
* Gauges are circular plots displaying one or more values with a dial pointing
* to values along the perimeter.
*
- * @sample highcharts/demo/gauge-speedometer/ Gauge chart
- * @extends {plotOptions.line}
- * @excluding animationLimit,boostThreshold,connectEnds,connectNulls,cropThreshold,dashStyle,findNearestPointBy,getExtremesFromAll,marker,pointPlacement,softThreshold,stacking,step,threshold,turboThreshold,zoneAxis,zones
- * @product highcharts
+ * @sample highcharts/demo/gauge-speedometer/
+ * Gauge chart
+ *
+ * @extends plotOptions.line
+ * @excluding animationLimit, boostThreshold, connectEnds, connectNulls,
+ * cropThreshold, dashStyle, findNearestPointBy,
+ * getExtremesFromAll, marker, negativeColor, pointPlacement,
+ * shadow, softThreshold, stacking, states, step, threshold,
+ * turboThreshold, xAxis, zoneAxis, zones
+ * @product highcharts
* @optionparent plotOptions.gauge
*/
seriesType('gauge', 'line', {
+ /**
+ * When this option is `true`, the dial will wrap around the axes. For
+ * instance, in a full-range gauge going from 0 to 360, a value of 400
+ * will point to 40\. When `wrap` is `false`, the dial stops at 360.
+ *
+ * @see [overshoot](#plotOptions.gauge.overshoot)
+ *
+ * @type {boolean}
+ * @default true
+ * @since 3.0
+ * @product highcharts
+ * @apioption plotOptions.gauge.wrap
+ */
+
/**
* Data labels for the gauge. For gauges, the data labels are enabled
* by default and shown in a bordered box below the point.
- *
- * @type {Object}
+ *
* @extends plotOptions.series.dataLabels
- * @since 2.3.0
+ * @since 2.3.0
* @product highcharts
*/
dataLabels: {
/**
* Enable or disable the data labels.
- *
- * @type {Boolean}
- * @since 2.3.0
+ *
+ * @since 2.3.0
* @product highcharts highmaps
*/
enabled: true,
@@ -2041,20 +2557,16 @@
/**
* The y position offset of the label relative to the center of the
* gauge.
- *
- * @type {Number}
- * @default 15
- * @since 2.3.0
+ *
+ * @since 2.3.0
* @product highcharts highmaps
*/
y: 15,
/**
* The border radius in pixels for the gauge's data label.
- *
- * @type {Number}
- * @default 3
- * @since 2.3.0
+ *
+ * @since 2.3.0
* @product highcharts highmaps
*/
borderRadius: 3,
@@ -2063,9 +2575,7 @@
/**
* The vertical alignment of the data label.
- *
- * @type {String}
- * @default top
+ *
* @product highcharts highmaps
*/
verticalAlign: 'top',
@@ -2073,32 +2583,26 @@
/**
* The Z index of the data labels. A value of 2 display them behind
* the dial.
- *
- * @type {Number}
- * @default 2
- * @since 2.1.5
+ *
+ * @since 2.1.5
* @product highcharts highmaps
*/
zIndex: 2,
- // Presentational
-
/**
* The border width in pixels for the gauge data label.
- *
- * @type {Number}
- * @default 1
- * @since 2.3.0
+ *
+ * @since 2.3.0
* @product highcharts highmaps
*/
borderWidth: 1,
/**
* The border color for the data label.
- *
- * @type {Color}
+ *
+ * @type {Highcharts.ColorString}
* @default #cccccc
- * @since 2.3.0
+ * @since 2.3.0
* @product highcharts highmaps
*/
borderColor: '#cccccc'
@@ -2107,232 +2611,232 @@
/**
* Options for the dial or arrow pointer of the gauge.
- *
- * In styled mode, the dial is styled with the `.highcharts-gauge-
- * series .highcharts-dial` rule.
- *
- * @type {Object}
- * @sample {highcharts} highcharts/css/gauge/ Styled mode
- * @since 2.3.0
+ *
+ * In styled mode, the dial is styled with the
+ * `.highcharts-gauge-series .highcharts-dial` rule.
+ *
+ * @sample {highcharts} highcharts/css/gauge/
+ * Styled mode
+ *
+ * @since 2.3.0
* @product highcharts
*/
+ dial: {},
+ /**
+ * The length of the dial's base part, relative to the total radius
+ * or length of the dial.
+ *
+ * @sample {highcharts} highcharts/plotoptions/gauge-dial/
+ * Dial options demonstrated
+ *
+ * @type {string}
+ * @default 70%
+ * @since 2.3.0
+ * @product highcharts
+ * @apioption plotOptions.gauge.dial.baseLength
+ */
- dial: {
-
- /**
- * The length of the dial's base part, relative to the total radius
- * or length of the dial.
- *
- * @type {String}
- * @sample {highcharts} highcharts/plotoptions/gauge-dial/
- * Dial options demonstrated
- * @default 70%
- * @since 2.3.0
- * @product highcharts
- * @apioption plotOptions.gauge.dial.baseLength
- */
-
- /**
- * The pixel width of the base of the gauge dial. The base is the part
- * closest to the pivot, defined by baseLength.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/gauge-dial/
- * Dial options demonstrated
- * @default 3
- * @since 2.3.0
- * @product highcharts
- * @apioption plotOptions.gauge.dial.baseWidth
- */
-
- /**
- * The radius or length of the dial, in percentages relative to the
- * radius of the gauge itself.
- *
- * @type {String}
- * @sample {highcharts} highcharts/plotoptions/gauge-dial/
- * Dial options demonstrated
- * @default 80%
- * @since 2.3.0
- * @product highcharts
- * @apioption plotOptions.gauge.dial.radius
- */
-
- /**
- * The length of the dial's rear end, the part that extends out on the
- * other side of the pivot. Relative to the dial's length.
- *
- * @type {String}
- * @sample {highcharts} highcharts/plotoptions/gauge-dial/ Dial options demonstrated
- * @default 10%
- * @since 2.3.0
- * @product highcharts
- * @apioption plotOptions.gauge.dial.rearLength
- */
-
- /**
- * The width of the top of the dial, closest to the perimeter. The pivot
- * narrows in from the base to the top.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/gauge-dial/ Dial options demonstrated
- * @default 1
- * @since 2.3.0
- * @product highcharts
- * @apioption plotOptions.gauge.dial.topWidth
- */
-
+ /**
+ * The pixel width of the base of the gauge dial. The base is the part
+ * closest to the pivot, defined by baseLength.
+ *
+ * @sample {highcharts} highcharts/plotoptions/gauge-dial/
+ * Dial options demonstrated
+ *
+ * @type {number}
+ * @default 3
+ * @since 2.3.0
+ * @product highcharts
+ * @apioption plotOptions.gauge.dial.baseWidth
+ */
+ /**
+ * The radius or length of the dial, in percentages relative to the
+ * radius of the gauge itself.
+ *
+ * @sample {highcharts} highcharts/plotoptions/gauge-dial/
+ * Dial options demonstrated
+ *
+ * @type {string}
+ * @default 80%
+ * @since 2.3.0
+ * @product highcharts
+ * @apioption plotOptions.gauge.dial.radius
+ */
- /**
- * The background or fill color of the gauge's dial.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/plotoptions/gauge-dial/ Dial options demonstrated
- * @default #000000
- * @since 2.3.0
- * @product highcharts
- * @apioption plotOptions.gauge.dial.backgroundColor
- */
+ /**
+ * The length of the dial's rear end, the part that extends out on the
+ * other side of the pivot. Relative to the dial's length.
+ *
+ * @sample {highcharts} highcharts/plotoptions/gauge-dial/
+ * Dial options demonstrated
+ *
+ * @type {string}
+ * @default 10%
+ * @since 2.3.0
+ * @product highcharts
+ * @apioption plotOptions.gauge.dial.rearLength
+ */
- /**
- * The border color or stroke of the gauge's dial. By default, the borderWidth
- * is 0, so this must be set in addition to a custom border color.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/plotoptions/gauge-dial/ Dial options demonstrated
- * @default #cccccc
- * @since 2.3.0
- * @product highcharts
- * @apioption plotOptions.gauge.dial.borderColor
- */
+ /**
+ * The width of the top of the dial, closest to the perimeter. The pivot
+ * narrows in from the base to the top.
+ *
+ * @sample {highcharts} highcharts/plotoptions/gauge-dial/
+ * Dial options demonstrated
+ *
+ * @type {number}
+ * @default 1
+ * @since 2.3.0
+ * @product highcharts
+ * @apioption plotOptions.gauge.dial.topWidth
+ */
- /**
- * The width of the gauge dial border in pixels.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/gauge-dial/ Dial options demonstrated
- * @default 0
- * @since 2.3.0
- * @product highcharts
- * @apioption plotOptions.gauge.dial.borderWidth
- */
+ /**
+ * The background or fill color of the gauge's dial.
+ *
+ * @sample {highcharts} highcharts/plotoptions/gauge-dial/
+ * Dial options demonstrated
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @default #000000
+ * @since 2.3.0
+ * @product highcharts
+ * @apioption plotOptions.gauge.dial.backgroundColor
+ */
+ /**
+ * The border color or stroke of the gauge's dial. By default, the
+ * borderWidth is 0, so this must be set in addition to a custom border
+ * color.
+ *
+ * @sample {highcharts} highcharts/plotoptions/gauge-dial/
+ * Dial options demonstrated
+ *
+ * @type {Highcharts.ColorString}
+ * @default #cccccc
+ * @since 2.3.0
+ * @product highcharts
+ * @apioption plotOptions.gauge.dial.borderColor
+ */
- },
+ /**
+ * The width of the gauge dial border in pixels.
+ *
+ * @sample {highcharts} highcharts/plotoptions/gauge-dial/
+ * Dial options demonstrated
+ *
+ * @type {number}
+ * @default 0
+ * @since 2.3.0
+ * @product highcharts
+ * @apioption plotOptions.gauge.dial.borderWidth
+ */
/**
* Allow the dial to overshoot the end of the perimeter axis by this
* many degrees. Say if the gauge axis goes from 0 to 60, a value of
- * 100, or 1000, will show 5 degrees beyond the end of the axis.
- *
- * @type {Number}
+ * 100, or 1000, will show 5 degrees beyond the end of the axis when this
+ * option is set to 5.
+ *
* @see [wrap](#plotOptions.gauge.wrap)
+ *
* @sample {highcharts} highcharts/plotoptions/gauge-overshoot/
* Allow 5 degrees overshoot
- * @default 0
- * @since 3.0.10
- * @product highcharts
+ *
+ * @type {number}
+ * @default 0
+ * @since 3.0.10
+ * @product highcharts
* @apioption plotOptions.gauge.overshoot
*/
/**
* Options for the pivot or the center point of the gauge.
- *
- * In styled mode, the pivot is styled with the `.highcharts-gauge-
- * series .highcharts-pivot` rule.
- *
- * @type {Object}
- * @sample {highcharts} highcharts/css/gauge/ Styled mode
- * @since 2.3.0
+ *
+ * In styled mode, the pivot is styled with the
+ * `.highcharts-gauge-series .highcharts-pivot` rule.
+ *
+ * @sample {highcharts} highcharts/css/gauge/
+ * Styled mode
+ *
+ * @since 2.3.0
* @product highcharts
*/
- pivot: {
+ pivot: {},
- /**
- * The pixel radius of the pivot.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/gauge-pivot/ Pivot options demonstrated
- * @default 5
- * @since 2.3.0
- * @product highcharts
- * @apioption plotOptions.gauge.pivot.radius
- */
+ /**
+ * The pixel radius of the pivot.
+ *
+ * @sample {highcharts} highcharts/plotoptions/gauge-pivot/
+ * Pivot options demonstrated
+ *
+ * @type {number}
+ * @default 5
+ * @since 2.3.0
+ * @product highcharts
+ * @apioption plotOptions.gauge.pivot.radius
+ */
+ /**
+ * The border or stroke width of the pivot.
+ *
+ * @sample {highcharts} highcharts/plotoptions/gauge-pivot/
+ * Pivot options demonstrated
+ *
+ * @type {number}
+ * @default 0
+ * @since 2.3.0
+ * @product highcharts
+ * @apioption plotOptions.gauge.pivot.borderWidth
+ */
+ /**
+ * The border or stroke color of the pivot. In able to change this,
+ * the borderWidth must also be set to something other than the default
+ * 0.
+ *
+ * @sample {highcharts} highcharts/plotoptions/gauge-pivot/
+ * Pivot options demonstrated
+ *
+ * @type {Highcharts.ColorString}
+ * @default #cccccc
+ * @since 2.3.0
+ * @product highcharts
+ * @apioption plotOptions.gauge.pivot.borderColor
+ */
- /**
- * The border or stroke width of the pivot.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/gauge-pivot/ Pivot options demonstrated
- * @default 0
- * @since 2.3.0
- * @product highcharts
- * @apioption plotOptions.gauge.pivot.borderWidth
- */
+ /**
+ * The background color or fill of the pivot.
+ *
+ * @sample {highcharts} highcharts/plotoptions/gauge-pivot/
+ * Pivot options demonstrated
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @default #000000
+ * @since 2.3.0
+ * @product highcharts
+ * @apioption plotOptions.gauge.pivot.backgroundColor
+ */
- /**
- * The border or stroke color of the pivot. In able to change this,
- * the borderWidth must also be set to something other than the default
- * 0.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/plotoptions/gauge-pivot/ Pivot options demonstrated
- * @default #cccccc
- * @since 2.3.0
- * @product highcharts
- * @apioption plotOptions.gauge.pivot.borderColor
- */
-
- /**
- * The background color or fill of the pivot.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/plotoptions/gauge-pivot/ Pivot options demonstrated
- * @default #000000
- * @since 2.3.0
- * @product highcharts
- * @apioption plotOptions.gauge.pivot.backgroundColor
- */
-
- },
-
- tooltip: {
- headerFormat: ''
- },
+ tooltip: {
+ headerFormat: ''
+ },
/**
* Whether to display this particular series or series type in the
* legend. Defaults to false for gauge series.
- *
- * @type {Boolean}
- * @since 2.3.0
+ *
+ * @since 2.3.0
* @product highcharts
*/
showInLegend: false
- /**
- * When this option is `true`, the dial will wrap around the axes. For
- * instance, in a full-range gauge going from 0 to 360, a value of 400
- * will point to 40\. When `wrap` is `false`, the dial stops at 360.
- *
- * @type {Boolean}
- * @see [overshoot](#plotOptions.gauge.overshoot)
- * @default true
- * @since 3.0
- * @product highcharts
- * @apioption plotOptions.gauge.wrap
- */
-
-
-
- // Prototype members
+ // Prototype members
}, {
- // chart.angular will be set to true when a gauge series is present, and this will
- // be used on the axes
+ // chart.angular will be set to true when a gauge series is present,
+ // and this will be used on the axes
angular: true,
directTouch: true, // #5063
drawGraph: noop,
@@ -2341,10 +2845,8 @@
noSharedTooltip: true,
trackerGroups: ['group', 'dataLabelsGroup'],
- /**
- * Calculate paths etc
- */
- translate: function() {
+ // Calculate paths etc
+ translate: function () {
var series = this,
yAxis = series.yAxis,
@@ -2353,24 +2855,34 @@
series.generatePoints();
- each(series.points, function(point) {
+ series.points.forEach(function (point) {
var dialOptions = merge(options.dial, point.dial),
- radius = (pInt(pick(dialOptions.radius, 80)) * center[2]) / 200,
- baseLength = (pInt(pick(dialOptions.baseLength, 70)) * radius) / 100,
- rearLength = (pInt(pick(dialOptions.rearLength, 10)) * radius) / 100,
+ radius = (pInt(pick(dialOptions.radius, 80)) * center[2]) /
+ 200,
+ baseLength = (pInt(pick(dialOptions.baseLength, 70)) * radius) /
+ 100,
+ rearLength = (pInt(pick(dialOptions.rearLength, 10)) * radius) /
+ 100,
baseWidth = dialOptions.baseWidth || 3,
topWidth = dialOptions.topWidth || 1,
overshoot = options.overshoot,
- rotation = yAxis.startAngleRad + yAxis.translate(point.y, null, null, null, true);
+ rotation = yAxis.startAngleRad +
+ yAxis.translate(point.y, null, null, null, true);
// Handle the wrap and overshoot options
if (isNumber(overshoot)) {
overshoot = overshoot / 180 * Math.PI;
- rotation = Math.max(yAxis.startAngleRad - overshoot, Math.min(yAxis.endAngleRad + overshoot, rotation));
+ rotation = Math.max(
+ yAxis.startAngleRad - overshoot,
+ Math.min(yAxis.endAngleRad + overshoot, rotation)
+ );
} else if (options.wrap === false) {
- rotation = Math.max(yAxis.startAngleRad, Math.min(yAxis.endAngleRad, rotation));
+ rotation = Math.max(
+ yAxis.startAngleRad,
+ Math.min(yAxis.endAngleRad, rotation)
+ );
}
rotation = rotation * 180 / Math.PI;
@@ -2378,12 +2890,14 @@
point.shapeType = 'path';
point.shapeArgs = {
d: dialOptions.path || [
- 'M', -rearLength, -baseWidth / 2,
+ 'M',
+ -rearLength, -baseWidth / 2,
'L',
baseLength, -baseWidth / 2,
radius, -topWidth / 2,
radius, topWidth / 2,
- baseLength, baseWidth / 2, -rearLength, baseWidth / 2,
+ baseLength, baseWidth / 2,
+ -rearLength, baseWidth / 2,
'z'
],
translateX: center[0],
@@ -2397,19 +2911,18 @@
});
},
- /**
- * Draw the points where each point is one needle
- */
- drawPoints: function() {
+ // Draw the points where each point is one needle
+ drawPoints: function () {
var series = this,
+ chart = series.chart,
center = series.yAxis.center,
pivot = series.pivot,
options = series.options,
pivotOptions = options.pivot,
- renderer = series.chart.renderer;
+ renderer = chart.renderer;
- each(series.points, function(point) {
+ series.points.forEach(function (point) {
var graphic = point.graphic,
shapeArgs = point.shapeArgs,
@@ -2422,20 +2935,22 @@
} else {
point.graphic = renderer[point.shapeType](shapeArgs)
.attr({
- rotation: shapeArgs.rotation, // required by VML when animation is false
+ // required by VML when animation is false
+ rotation: shapeArgs.rotation,
zIndex: 1
})
.addClass('highcharts-dial')
.add(series.group);
-
// Presentational attributes
- point.graphic.attr({
- stroke: dialOptions.borderColor || 'none',
- 'stroke-width': dialOptions.borderWidth || 0,
- fill: dialOptions.backgroundColor || '#000000'
- });
-
+ if (!chart.styledMode) {
+ point.graphic.attr({
+ stroke: dialOptions.borderColor || 'none',
+ 'stroke-width': dialOptions.borderWidth || 0,
+ fill: dialOptions.backgroundColor ||
+ '#000000'
+ });
+ }
}
});
@@ -2454,25 +2969,25 @@
.translate(center[0], center[1])
.add(series.group);
-
// Presentational attributes
- series.pivot.attr({
- 'stroke-width': pivotOptions.borderWidth || 0,
- stroke: pivotOptions.borderColor || '#cccccc',
- fill: pivotOptions.backgroundColor || '#000000'
- });
-
+ if (!chart.styledMode) {
+ series.pivot.attr({
+ 'stroke-width': pivotOptions.borderWidth || 0,
+ stroke: pivotOptions.borderColor ||
+ '#cccccc',
+ fill: pivotOptions.backgroundColor ||
+ '#000000'
+ });
+ }
}
},
- /**
- * Animate the arrow up from startAngle
- */
- animate: function(init) {
+ // Animate the arrow up from startAngle
+ animate: function (init) {
var series = this;
if (!init) {
- each(series.points, function(point) {
+ series.points.forEach(function (point) {
var graphic = point.graphic;
if (graphic) {
@@ -2493,7 +3008,7 @@
}
},
- render: function() {
+ render: function () {
this.group = this.plotGroup(
'group',
'series',
@@ -2505,11 +3020,9 @@
this.group.clip(this.chart.clipRect);
},
- /**
- * Extend the basic setData method by running processData and generatePoints immediately,
- * in order to access the points from the legend.
- */
- setData: function(data, redraw) {
+ // Extend the basic setData method by running processData and generatePoints
+ // immediately, in order to access the points from the legend.
+ setData: function (data, redraw) {
Series.prototype.setData.call(this, data, false);
this.processData();
this.generatePoints();
@@ -2518,17 +3031,13 @@
}
},
- /**
- * If the tracking module is loaded, add the point tracker
- */
+ // If the tracking module is loaded, add the point tracker
drawTracker: TrackerMixin && TrackerMixin.drawTrackerPoint
- // Point members
+ // Point members
}, {
- /**
- * Don't do any hover colors or anything
- */
- setState: function(state) {
+ // Don't do any hover colors or anything
+ setState: function (state) {
this.state = state;
}
});
@@ -2536,69 +3045,69 @@
/**
* A `gauge` series. If the [type](#series.gauge.type) option is not
* specified, it is inherited from [chart.type](#chart.type).
- *
- * For options that apply to multiple series, it is recommended to add
- * them to the [plotOptions.series](#plotOptions.series) options structure.
- * To apply to all series of this specific type, apply it to [plotOptions.
- * gauge](#plotOptions.gauge).
- *
- * @type {Object}
- * @extends series,plotOptions.gauge
- * @excluding dataParser,dataURL,stack
- * @product highcharts
+ *
+ * @extends series,plotOptions.gauge
+ * @excluding animationLimit, boostThreshold, connectEnds, connectNulls,
+ * cropThreshold, dashStyle, dataParser, dataURL, findNearestPointBy,
+ * getExtremesFromAll, marker, negativeColor, pointPlacement, shadow,
+ * softThreshold, stack, stacking, states, step, threshold,
+ * turboThreshold, zoneAxis, zones
+ * @product highcharts
* @apioption series.gauge
*/
/**
* An array of data points for the series. For the `gauge` series type,
* points can be given in the following ways:
- *
- * 1. An array of numerical values. In this case, the numerical values
- * will be interpreted as `y` options. Example:
- *
- * ```js
- * data: [0, 5, 3, 5]
- * ```
- *
- * 2. An array of objects with named values. The objects are point
- * configuration objects as seen below. If the total number of data
- * points exceeds the series' [turboThreshold](#series.gauge.turboThreshold),
- * this option is not available.
- *
- * ```js
- * data: [{
- * y: 6,
- * name: "Point2",
- * color: "#00FF00"
- * }, {
- * y: 8,
- * name: "Point1",
- * color: "#FF00FF"
- * }]
- *
+ *
+ * 1. An array of numerical values. In this case, the numerical values will be
+ * interpreted as `y` options. Example:
+ * ```js
+ * data: [0, 5, 3, 5]
+ * ```
+ *
+ * 2. An array of objects with named values. The following snippet shows only a
+ * few settings, see the complete options set below. If the total number of
+ * data points exceeds the series'
+ * [turboThreshold](#series.gauge.turboThreshold), this option is not
+ * available.
+ * ```js
+ * data: [{
+ * y: 6,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * y: 8,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
* The typical gauge only contains a single data value.
- *
- * @type {Array}
- * @extends series.line.data
- * @excluding drilldown,marker,x
- * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
- * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
- * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
- * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
- * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
- * @product highcharts
+ *
+ * @sample {highcharts} highcharts/chart/reflow-true/
+ * Numerical values
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
+ *
+ * @type {Array}
+ * @extends series.line.data
+ * @excluding drilldown, marker, x
+ * @product highcharts
* @apioption series.gauge.data
*/
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
- var each = H.each,
- noop = H.noop,
+
+
+
+ var noop = H.noop,
pick = H.pick,
seriesType = H.seriesType,
seriesTypes = H.seriesTypes;
@@ -2606,8 +3115,11 @@
/**
* The boxplot series type.
*
- * @constructor seriesTypes.boxplot
- * @augments seriesTypes.column
+ * @private
+ * @class
+ * @name Highcharts.seriesTypes#boxplot
+ *
+ * @augments Highcharts.Series
*/
/**
@@ -2615,10 +3127,13 @@
* five-number summaries: the smallest observation (sample minimum), lower
* quartile (Q1), median (Q2), upper quartile (Q3), and largest observation
* (sample maximum).
- *
- * @sample highcharts/demo/box-plot/ Box plot
- * @extends {plotOptions.column}
- * @product highcharts
+ *
+ * @sample highcharts/demo/box-plot/
+ * Box plot
+ *
+ * @extends plotOptions.column
+ * @excluding borderColor, borderRadius, borderWidth, groupZPadding, states
+ * @product highcharts
* @optionparent plotOptions.boxplot
*/
seriesType('boxplot', 'column', {
@@ -2626,230 +3141,226 @@
threshold: null,
tooltip: {
-
-
- pointFormat: '\u25CF {series.name} ' + // eslint-disable-line no-dupe-keys
+ pointFormat:
+ '\u25CF ' +
+ '{series.name} ' +
'Maximum: {point.high} ' +
'Upper quartile: {point.q3} ' +
'Median: {point.median} ' +
'Lower quartile: {point.q1} ' +
'Minimum: {point.low} '
-
},
/**
* The length of the whiskers, the horizontal lines marking low and
* high values. It can be a numerical pixel value, or a percentage
* value of the box width. Set `0` to disable whiskers.
- *
- * @type {Number|String}
+ *
* @sample {highcharts} highcharts/plotoptions/box-plot-styling/
* True by default
- * @default 50%
- * @since 3.0
+ *
+ * @type {number|string}
+ * @since 3.0
* @product highcharts
*/
whiskerLength: '50%',
-
/**
* The fill color of the box.
- *
- * @type {Color}
- * @see In styled mode, the fill color can be set with the
+ *
+ * In styled mode, the fill color can be set with the
* `.highcharts-boxplot-box` class.
+ *
* @sample {highcharts} highcharts/plotoptions/box-plot-styling/
* Box plot styling
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
* @default #ffffff
- * @since 3.0
+ * @since 3.0
* @product highcharts
*/
fillColor: '#ffffff',
/**
- * The width of the line surrounding the box. If any of [stemWidth](#plotOptions.
- * boxplot.stemWidth), [medianWidth](#plotOptions.boxplot.medianWidth)
+ * The width of the line surrounding the box. If any of
+ * [stemWidth](#plotOptions.boxplot.stemWidth),
+ * [medianWidth](#plotOptions.boxplot.medianWidth)
* or [whiskerWidth](#plotOptions.boxplot.whiskerWidth) are `null`,
- * the lineWidth also applies to these lines.
- *
- * @type {Number}
+ * the lineWidth also applies to these lines.
+ *
* @sample {highcharts} highcharts/plotoptions/box-plot-styling/
* Box plot styling
* @sample {highcharts} highcharts/plotoptions/error-bar-styling/
* Error bar styling
- * @default 1
- * @since 3.0
+ *
+ * @since 3.0
* @product highcharts
*/
lineWidth: 1,
/**
- * The color of the median line. If `null`, the general series color
+ * The color of the median line. If `undefined`, the general series color
* applies.
- *
- * @type {Color}
- * @see In styled mode, the median stroke width can be set with the
+ *
+ * In styled mode, the median stroke width can be set with the
* `.highcharts-boxplot-median` class.
- *
+ *
* @sample {highcharts} highcharts/plotoptions/box-plot-styling/
* Box plot styling
* @sample {highcharts} highcharts/css/boxplot/
* Box plot in styled mode
* @sample {highcharts} highcharts/plotoptions/error-bar-styling/
* Error bar styling
- * @default null
- * @since 3.0
- * @product highcharts
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject}
+ * @since 3.0
+ * @product highcharts
* @apioption plotOptions.boxplot.medianColor
*/
/**
- * The pixel width of the median line. If `null`, the [lineWidth](#plotOptions.
- * boxplot.lineWidth) is used.
- *
- * @type {Number}
- * @see In styled mode, the median stroke width can be set with the
+ * The pixel width of the median line. If `null`, the
+ * [lineWidth](#plotOptions.boxplot.lineWidth) is used.
+ *
+ * In styled mode, the median stroke width can be set with the
* `.highcharts-boxplot-median` class.
+ *
* @sample {highcharts} highcharts/plotoptions/box-plot-styling/
* Box plot styling
* @sample {highcharts} highcharts/css/boxplot/
* Box plot in styled mode
- * @default 2
- * @since 3.0
+ *
+ * @since 3.0
* @product highcharts
*/
medianWidth: 2,
+ /*
+ // States are not working and are removed from docs.
+ // Refer to: #2340
states: {
hover: {
brightness: -0.3
}
},
+
/**
* The color of the stem, the vertical line extending from the box to
- * the whiskers. If `null`, the series color is used.
- *
- * @type {Color}
- * @see In styled mode, the stem stroke can be set with the `.highcharts-boxplot-stem` class.
+ * the whiskers. If `undefined`, the series color is used.
+ *
+ * In styled mode, the stem stroke can be set with the
+ * `.highcharts-boxplot-stem` class.
+ *
* @sample {highcharts} highcharts/plotoptions/box-plot-styling/
* Box plot styling
* @sample {highcharts} highcharts/css/boxplot/
* Box plot in styled mode
* @sample {highcharts} highcharts/plotoptions/error-bar-styling/
* Error bar styling
- * @default null
- * @since 3.0
- * @product highcharts
+ *
+ * @type {Highcharts.ColorString}
+ * @since 3.0
+ * @product highcharts
* @apioption plotOptions.boxplot.stemColor
*/
/**
* The dash style of the stem, the vertical line extending from the
* box to the whiskers.
- *
- * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
- * "ShortDashDotDot", "Dot", "Dash" ,"LongDash", "DashDot",
- * "LongDashDot", "LongDashDotDot"]
- * @type {String}
+ *
* @sample {highcharts} highcharts/plotoptions/box-plot-styling/
* Box plot styling
* @sample {highcharts} highcharts/css/boxplot/
* Box plot in styled mode
* @sample {highcharts} highcharts/plotoptions/error-bar-styling/
* Error bar styling
- * @default Solid
- * @since 3.0
- * @product highcharts
+ *
+ * @type {Highcharts.DashStyleType}
+ * @default Solid
+ * @since 3.0
+ * @product highcharts
* @apioption plotOptions.boxplot.stemDashStyle
*/
/**
* The width of the stem, the vertical line extending from the box to
- * the whiskers. If `null`, the width is inherited from the [lineWidth](#plotOptions.
- * boxplot.lineWidth) option.
- *
- * @type {Number}
- * @see In styled mode, the stem stroke width can be set with the `.
- * highcharts-boxplot-stem` class.
+ * the whiskers. If `undefined`, the width is inherited from the
+ * [lineWidth](#plotOptions.boxplot.lineWidth) option.
+ *
+ * In styled mode, the stem stroke width can be set with the
+ * `.highcharts-boxplot-stem` class.
+ *
* @sample {highcharts} highcharts/plotoptions/box-plot-styling/
* Box plot styling
* @sample {highcharts} highcharts/css/boxplot/
* Box plot in styled mode
* @sample {highcharts} highcharts/plotoptions/error-bar-styling/
* Error bar styling
- * @default null
- * @since 3.0
- * @product highcharts
+ *
+ * @type {number}
+ * @since 3.0
+ * @product highcharts
* @apioption plotOptions.boxplot.stemWidth
*/
/**
* The color of the whiskers, the horizontal lines marking low and high
- * values. When `null`, the general series color is used.
- *
- * @type {Color}
- * @see In styled mode, the whisker stroke can be set with the `.highcharts-boxplot-whisker` class .
+ * values. When `undefined`, the general series color is used.
+ *
+ * In styled mode, the whisker stroke can be set with the
+ * `.highcharts-boxplot-whisker` class .
+ *
* @sample {highcharts} highcharts/plotoptions/box-plot-styling/
* Box plot styling
* @sample {highcharts} highcharts/css/boxplot/
* Box plot in styled mode
- * @default null
- * @since 3.0
- * @product highcharts
+ *
+ * @type {Highcharts.ColorString}
+ * @since 3.0
+ * @product highcharts
* @apioption plotOptions.boxplot.whiskerColor
*/
/**
- * The line width of the whiskers, the horizontal lines marking low
- * and high values. When `null`, the general [lineWidth](#plotOptions.
- * boxplot.lineWidth) applies.
- *
- * @type {Number}
- * @see In styled mode, the whisker stroke width can be set with the
+ * The line width of the whiskers, the horizontal lines marking low and
+ * high values. When `undefined`, the general
+ * [lineWidth](#plotOptions.boxplot.lineWidth) applies.
+ *
+ * In styled mode, the whisker stroke width can be set with the
* `.highcharts-boxplot-whisker` class.
- *
+ *
* @sample {highcharts} highcharts/plotoptions/box-plot-styling/
* Box plot styling
* @sample {highcharts} highcharts/css/boxplot/
* Box plot in styled mode
- * @since 3.0
+ *
+ * @since 3.0
* @product highcharts
*/
whiskerWidth: 2
+ }, /** @lends Highcharts.seriesTypes.boxplot */ {
- }, /** @lends seriesTypes.boxplot */ {
- pointArrayMap: ['low', 'q1', 'median', 'q3', 'high'], // array point configs are mapped to this
- toYData: function(point) { // return a plain array for speedy calculation
+ // array point configs are mapped to this
+ pointArrayMap: ['low', 'q1', 'median', 'q3', 'high'],
+ toYData: function (point) { // return a plain array for speedy calculation
return [point.low, point.q1, point.median, point.q3, point.high];
},
- pointValKey: 'high', // defines the top of the tracker
-
- /**
- * Get presentational attributes
- */
- pointAttribs: function(point) {
- var options = this.options,
- color = (point && point.color) || this.color;
+ // defines the top of the tracker
+ pointValKey: 'high',
- return {
- 'fill': point.fillColor || options.fillColor || color,
- 'stroke': options.lineColor || color,
- 'stroke-width': options.lineWidth || 0
- };
+ // Get presentational attributes
+ pointAttribs: function () {
+ // No attributes should be set on point.graphic which is the group
+ return {};
},
-
- /**
- * Disable data labels for box plot
- */
+ // Disable data labels for box plot
drawDataLabels: noop,
- /**
- * Translate data points from raw values x and y to plotX and plotY
- */
- translate: function() {
+ // Translate data points from raw values x and y to plotX and plotY
+ translate: function () {
var series = this,
yAxis = series.yAxis,
pointArrayMap = series.pointArrayMap;
@@ -2857,19 +3368,19 @@
seriesTypes.column.prototype.translate.apply(series);
// do the translation on each point dimension
- each(series.points, function(point) {
- each(pointArrayMap, function(key) {
+ series.points.forEach(function (point) {
+ pointArrayMap.forEach(function (key) {
if (point[key] !== null) {
- point[key + 'Plot'] = yAxis.translate(point[key], 0, 1, 0, 1);
+ point[key + 'Plot'] = yAxis.translate(
+ point[key], 0, 1, 0, 1
+ );
}
});
});
},
- /**
- * Draw the data points
- */
- drawPoints: function() {
+ // Draw the data points
+ drawPoints: function () {
var series = this,
points = series.points,
options = series.options,
@@ -2888,25 +3399,23 @@
left,
right,
halfWidth,
- doQuartiles = series.doQuartiles !== false, // error bar inherits this series type but doesn't do quartiles
+ // error bar inherits this series type but doesn't do quartiles
+ doQuartiles = series.doQuartiles !== false,
pointWiskerLength,
whiskerLength = series.options.whiskerLength;
- each(points, function(point) {
+ points.forEach(function (point) {
var graphic = point.graphic,
verb = graphic ? 'animate' : 'attr',
- shapeArgs = point.shapeArgs; // the box
-
-
- var boxAttr,
+ shapeArgs = point.shapeArgs,
+ boxAttr = {},
stemAttr = {},
whiskersAttr = {},
medianAttr = {},
color = point.color || series.color;
-
if (point.plotY !== undefined) {
// crisp vector coordinates
@@ -2942,45 +3451,63 @@
.add(graphic);
}
+ if (!chart.styledMode) {
+ // Stem attributes
+ stemAttr.stroke =
+ point.stemColor || options.stemColor || color;
+ stemAttr['stroke-width'] = pick(
+ point.stemWidth,
+ options.stemWidth,
+ options.lineWidth
+ );
+ stemAttr.dashstyle =
+ point.stemDashStyle || options.stemDashStyle;
+ point.stem.attr(stemAttr);
+ // Whiskers attributes
+ if (whiskerLength) {
+ whiskersAttr.stroke =
+ point.whiskerColor || options.whiskerColor || color;
+ whiskersAttr['stroke-width'] = pick(
+ point.whiskerWidth,
+ options.whiskerWidth,
+ options.lineWidth
+ );
+ point.whiskers.attr(whiskersAttr);
+ }
+ if (doQuartiles) {
+ boxAttr.fill = (
+ point.fillColor ||
+ options.fillColor ||
+ color
+ );
+ boxAttr.stroke = options.lineColor || color;
+ boxAttr['stroke-width'] = options.lineWidth || 0;
+ point.box.attr(boxAttr);
+ }
- // Stem attributes
- stemAttr.stroke = point.stemColor || options.stemColor || color;
- stemAttr['stroke-width'] = pick(point.stemWidth, options.stemWidth, options.lineWidth);
- stemAttr.dashstyle = point.stemDashStyle || options.stemDashStyle;
- point.stem.attr(stemAttr);
-
- // Whiskers attributes
- if (whiskerLength) {
- whiskersAttr.stroke = point.whiskerColor || options.whiskerColor || color;
- whiskersAttr['stroke-width'] = pick(point.whiskerWidth, options.whiskerWidth, options.lineWidth);
- point.whiskers.attr(whiskersAttr);
- }
+ // Median attributes
+ medianAttr.stroke =
+ point.medianColor || options.medianColor || color;
+ medianAttr['stroke-width'] = pick(
+ point.medianWidth,
+ options.medianWidth,
+ options.lineWidth
+ );
+ point.medianShape.attr(medianAttr);
- if (doQuartiles) {
- boxAttr = series.pointAttribs(point);
- point.box.attr(boxAttr);
}
- // Median attributes
- medianAttr.stroke = point.medianColor || options.medianColor || color;
- medianAttr['stroke-width'] = pick(point.medianWidth, options.medianWidth, options.lineWidth);
- point.medianShape.attr(medianAttr);
-
-
-
-
-
// The stem
crispCorr = (point.stem.strokeWidth() % 2) / 2;
crispX = left + halfWidth + crispCorr;
point.stem[verb]({
d: [
- // stem up
+ // stem up
'M',
crispX, q3Plot,
'L',
@@ -3023,10 +3550,12 @@
crispCorr = (point.whiskers.strokeWidth() % 2) / 2;
highPlot = highPlot + crispCorr;
lowPlot = lowPlot + crispCorr;
- pointWiskerLength = (/%$/).test(whiskerLength) ? halfWidth * parseFloat(whiskerLength) / 100 : whiskerLength / 2;
+ pointWiskerLength = (/%$/).test(whiskerLength) ?
+ halfWidth * parseFloat(whiskerLength) / 100 :
+ whiskerLength / 2;
point.whiskers[verb]({
d: [
- // High whisker
+ // High whisker
'M',
crispX - pointWiskerLength,
highPlot,
@@ -3066,177 +3595,176 @@
},
setStackedPoints: noop // #3890
-
});
/**
* A `boxplot` series. If the [type](#series.boxplot.type) option is
* not specified, it is inherited from [chart.type](#chart.type).
- *
- * For options that apply to multiple series, it is recommended to add
- * them to the [plotOptions.series](#plotOptions.series) options structure.
- * To apply to all series of this specific type, apply it to [plotOptions.
- * boxplot](#plotOptions.boxplot).
- *
- * @type {Object}
- * @extends series,plotOptions.boxplot
- * @excluding dataParser,dataURL,stack
- * @product highcharts
+ *
+ * @extends series,plotOptions.boxplot
+ * @excluding dataParser, dataURL, marker, stack, stacking, states
+ * @product highcharts
* @apioption series.boxplot
*/
/**
* An array of data points for the series. For the `boxplot` series
* type, points can be given in the following ways:
- *
- * 1. An array of arrays with 6 or 5 values. In this case, the values
- * correspond to `x,low,q1,median,q3,high`. If the first value is a
- * string, it is applied as the name of the point, and the `x` value
- * is inferred. The `x` value can also be omitted, in which case the
- * inner arrays should be of length 5\. Then the `x` value is automatically
- * calculated, either starting at 0 and incremented by 1, or from `pointStart`
- * and `pointInterval` given in the series options.
- *
- * ```js
- * data: [
- * [0, 3, 0, 10, 3, 5],
- * [1, 7, 8, 7, 2, 9],
- * [2, 6, 9, 5, 1, 3]
- * ]
- * ```
- *
- * 2. An array of objects with named values. The objects are point
- * configuration objects as seen below. If the total number of data
- * points exceeds the series' [turboThreshold](#series.boxplot.turboThreshold),
- * this option is not available.
- *
- * ```js
- * data: [{
- * x: 1,
- * low: 4,
- * q1: 9,
- * median: 9,
- * q3: 1,
- * high: 10,
- * name: "Point2",
- * color: "#00FF00"
- * }, {
- * x: 1,
- * low: 5,
- * q1: 7,
- * median: 3,
- * q3: 6,
- * high: 2,
- * name: "Point1",
- * color: "#FF00FF"
- * }]
- * ```
- *
- * @type {Array}
- * @extends series.line.data
+ *
+ * 1. An array of arrays with 6 or 5 values. In this case, the values correspond
+ * to `x,low,q1,median,q3,high`. If the first value is a string, it is
+ * applied as the name of the point, and the `x` value is inferred. The `x`
+ * value can also be omitted, in which case the inner arrays should be of
+ * length 5. Then the `x` value is automatically calculated, either starting
+ * at 0 and incremented by 1, or from `pointStart` and `pointInterval` given
+ * in the series options.
+ * ```js
+ * data: [
+ * [0, 3, 0, 10, 3, 5],
+ * [1, 7, 8, 7, 2, 9],
+ * [2, 6, 9, 5, 1, 3]
+ * ]
+ * ```
+ *
+ * 2. An array of objects with named values. The following snippet shows only a
+ * few settings, see the complete options set below. If the total number of
+ * data points exceeds the series'
+ * [turboThreshold](#series.boxplot.turboThreshold), this option is not
+ * available.
+ * ```js
+ * data: [{
+ * x: 1,
+ * low: 4,
+ * q1: 9,
+ * median: 9,
+ * q3: 1,
+ * high: 10,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * low: 5,
+ * q1: 7,
+ * median: 3,
+ * q3: 6,
+ * high: 2,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/
+ * Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
+ * Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/
+ * Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
+ *
+ * @type {Array|Array<(number|string),number,number,number,number,number>|*>}
+ * @extends series.line.data
* @excluding marker
- * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
- * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
- * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
- * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
- * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
- * @product highcharts
+ * @product highcharts
* @apioption series.boxplot.data
*/
/**
* The `high` value for each data point, signifying the highest value
* in the sample set. The top whisker is drawn here.
- *
- * @type {Number}
- * @product highcharts
+ *
+ * @type {number}
+ * @product highcharts
* @apioption series.boxplot.data.high
*/
/**
* The `low` value for each data point, signifying the lowest value
* in the sample set. The bottom whisker is drawn here.
- *
- * @type {Number}
- * @product highcharts
+ *
+ * @type {number}
+ * @product highcharts
* @apioption series.boxplot.data.low
*/
/**
* The median for each data point. This is drawn as a line through the
* middle area of the box.
- *
- * @type {Number}
- * @product highcharts
+ *
+ * @type {number}
+ * @product highcharts
* @apioption series.boxplot.data.median
*/
/**
* The lower quartile for each data point. This is the bottom of the
* box.
- *
- * @type {Number}
- * @product highcharts
+ *
+ * @type {number}
+ * @product highcharts
* @apioption series.boxplot.data.q1
*/
/**
* The higher quartile for each data point. This is the top of the box.
- *
- * @type {Number}
- * @product highcharts
+ *
+ * @type {number}
+ * @product highcharts
* @apioption series.boxplot.data.q3
*/
-
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
- var each = H.each,
- noop = H.noop,
+
+
+
+ var noop = H.noop,
seriesType = H.seriesType,
seriesTypes = H.seriesTypes;
- /**
+ /**
* Error bars are a graphical representation of the variability of data and are
* used on graphs to indicate the error, or uncertainty in a reported
* measurement.
*
- * @sample highcharts/demo/error-bar/ Error bars
- * @extends {plotOptions.boxplot}
- * @product highcharts highstock
+ * @sample highcharts/demo/error-bar/
+ * Error bars on a column series
+ * @sample highcharts/series-errorbar/on-scatter/
+ * Error bars on a scatter series
+ *
+ * @extends plotOptions.boxplot
+ * @product highcharts highstock
* @optionparent plotOptions.errorbar
*/
seriesType('errorbar', 'boxplot', {
-
/**
- * The main color of the bars. This can be overridden by [stemColor](#plotOptions.
- * errorbar.stemColor) and [whiskerColor](#plotOptions.errorbar.whiskerColor)
- * individually.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/plotoptions/error-bar-styling/ Error bar styling
+ * The main color of the bars. This can be overridden by
+ * [stemColor](#plotOptions.errorbar.stemColor) and
+ * [whiskerColor](#plotOptions.errorbar.whiskerColor) individually.
+ *
+ * @sample {highcharts} highcharts/plotoptions/error-bar-styling/
+ * Error bar styling
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
* @default #000000
- * @since 3.0
+ * @since 3.0
* @product highcharts
*/
color: '#000000',
-
grouping: false,
/**
* The parent series of the error bar. The default value links it to
* the previous series. Otherwise, use the id of the parent series.
- *
- * @type {String}
- * @default :previous
- * @since 3.0
+ *
+ * @since 3.0
* @product highcharts
*/
linkedTo: ':previous',
@@ -3247,40 +3775,43 @@
/**
* The line width of the whiskers, the horizontal lines marking low
- * and high values. When `null`, the general [lineWidth](#plotOptions.
- * errorbar.lineWidth) applies.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/error-bar-styling/ Error bar styling
- * @default null
- * @since 3.0
+ * and high values. When `null`, the general
+ * [lineWidth](#plotOptions.errorbar.lineWidth) applies.
+ *
+ * @sample {highcharts} highcharts/plotoptions/error-bar-styling/
+ * Error bar styling
+ *
+ * @type {number}
+ * @since 3.0
* @product highcharts
*/
whiskerWidth: null
- // Prototype members
+ // Prototype members
}, {
type: 'errorbar',
pointArrayMap: ['low', 'high'], // array point configs are mapped to this
- toYData: function(point) { // return a plain array for speedy calculation
+ toYData: function (point) { // return a plain array for speedy calculation
return [point.low, point.high];
},
pointValKey: 'high', // defines the top of the tracker
doQuartiles: false,
- drawDataLabels: seriesTypes.arearange ? function() {
- var valKey = this.pointValKey;
- seriesTypes.arearange.prototype.drawDataLabels.call(this);
- // Arearange drawDataLabels does not reset point.y to high, but to low after drawing. #4133
- each(this.data, function(point) {
- point.y = point[valKey];
- });
- } : noop,
+ drawDataLabels: seriesTypes.arearange ?
+ function () {
+ var valKey = this.pointValKey;
+
+ seriesTypes.arearange.prototype.drawDataLabels.call(this);
+ // Arearange drawDataLabels does not reset point.y to high,
+ // but to low after drawing (#4133)
+ this.data.forEach(function (point) {
+ point.y = point[valKey];
+ });
+ } :
+ noop,
- /**
- * Get the width and X offset, either on top of the linked series column
- * or standalone
- */
- getColumnMetrics: function() {
+ // Get the width and X offset, either on top of the linked series column or
+ // standalone
+ getColumnMetrics: function () {
return (this.linkedParent && this.linkedParent.columnMetrics) ||
seriesTypes.column.prototype.getColumnMetrics.call(this);
}
@@ -3289,160 +3820,199 @@
/**
* A `errorbar` series. If the [type](#series.errorbar.type) option
* is not specified, it is inherited from [chart.type](#chart.type).
- *
- *
- * For options that apply to multiple series, it is recommended to add
- * them to the [plotOptions.series](#plotOptions.series) options structure.
- * To apply to all series of this specific type, apply it to [plotOptions.
- * errorbar](#plotOptions.errorbar).
- *
- * @type {Object}
- * @extends series,plotOptions.errorbar
- * @excluding dataParser,dataURL,stack
- * @product highcharts
+ *
+ * @extends series,plotOptions.errorbar
+ * @excluding dataParser, dataURL, stack, stacking
+ * @product highcharts
* @apioption series.errorbar
*/
/**
* An array of data points for the series. For the `errorbar` series
* type, points can be given in the following ways:
- *
- * 1. An array of arrays with 3 or 2 values. In this case, the values
- * correspond to `x,low,high`. If the first value is a string, it is
- * applied as the name of the point, and the `x` value is inferred.
- * The `x` value can also be omitted, in which case the inner arrays
- * should be of length 2\. Then the `x` value is automatically calculated,
- * either starting at 0 and incremented by 1, or from `pointStart`
- * and `pointInterval` given in the series options.
- *
- * ```js
- * data: [
- * [0, 10, 2],
- * [1, 1, 8],
- * [2, 4, 5]
- * ]
- * ```
- *
- * 2. An array of objects with named values. The objects are point
- * configuration objects as seen below. If the total number of data
- * points exceeds the series' [turboThreshold](#series.errorbar.turboThreshold),
- * this option is not available.
- *
- * ```js
- * data: [{
- * x: 1,
- * low: 0,
- * high: 0,
- * name: "Point2",
- * color: "#00FF00"
- * }, {
- * x: 1,
- * low: 5,
- * high: 5,
- * name: "Point1",
- * color: "#FF00FF"
- * }]
- * ```
- *
- * @type {Array}
- * @extends series.arearange.data
- * @excluding dataLabels,drilldown,marker
- * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
- * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
- * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
- * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
- * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
- * @product highcharts
+ *
+ * 1. An array of arrays with 3 or 2 values. In this case, the values correspond
+ * to `x,low,high`. If the first value is a string, it is applied as the name
+ * of the point, and the `x` value is inferred. The `x` value can also be
+ * omitted, in which case the inner arrays should be of length 2\. Then the
+ * `x` value is automatically calculated, either starting at 0 and
+ * incremented by 1, or from `pointStart` and `pointInterval` given in the
+ * series options.
+ * ```js
+ * data: [
+ * [0, 10, 2],
+ * [1, 1, 8],
+ * [2, 4, 5]
+ * ]
+ * ```
+ *
+ * 2. An array of objects with named values. The following snippet shows only a
+ * few settings, see the complete options set below. If the total number of
+ * data points exceeds the series'
+ * [turboThreshold](#series.errorbar.turboThreshold), this option is not
+ * available.
+ * ```js
+ * data: [{
+ * x: 1,
+ * low: 0,
+ * high: 0,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * low: 5,
+ * high: 5,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/
+ * Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
+ * Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/
+ * Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
+ *
+ * @type {Array|Array<(number|string),number,number>|*>}
+ * @extends series.arearange.data
+ * @excluding dataLabels, drilldown, marker, states
+ * @product highcharts
* @apioption series.errorbar.data
*/
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
+
var correctFloat = H.correctFloat,
isNumber = H.isNumber,
pick = H.pick,
+ objectEach = H.objectEach,
+ arrayMin = H.arrayMin,
+ arrayMax = H.arrayMax,
+ addEvent = H.addEvent,
+ Axis = H.Axis,
+ Chart = H.Chart,
Point = H.Point,
Series = H.Series,
seriesType = H.seriesType,
seriesTypes = H.seriesTypes;
+ addEvent(Axis, 'afterInit', function () {
+ if (!this.isXAxis) {
+ this.waterfallStacks = {};
+ }
+ });
+
+ addEvent(Chart, 'beforeRedraw', function () {
+ var axes = this.axes,
+ series = this.series,
+ i = series.length;
+
+ while (i--) {
+ if (series[i].options.stacking) {
+ axes.forEach(function (axis) {
+ if (!axis.isXAxis) {
+ axis.waterfallStacks = {};
+ }
+ });
+ i = 0;
+ }
+ }
+ });
+
/**
* A waterfall chart displays sequentially introduced positive or negative
* values in cumulative columns.
*
- * @sample highcharts/demo/waterfall/ Waterfall chart
- * @extends {plotOptions.column}
- * @product highcharts
+ * @sample highcharts/demo/waterfall/
+ * Waterfall chart
+ * @sample highcharts/plotoptions/waterfall-inverted/
+ * Horizontal (inverted) waterfall
+ * @sample highcharts/plotoptions/waterfall-stacked/
+ * Stacked waterfall chart
+ *
+ * @extends plotOptions.column
+ * @product highcharts
* @optionparent plotOptions.waterfall
*/
seriesType('waterfall', 'column', {
+ /**
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @apioption plotOptions.waterfall.color
+ */
+
+ /**
+ * The color used specifically for positive point columns. When not
+ * specified, the general series color is used.
+ *
+ * In styled mode, the waterfall colors can be set with the
+ * `.highcharts-point-negative`, `.highcharts-sum` and
+ * `.highcharts-intermediate-sum` classes.
+ *
+ * @sample {highcharts} highcharts/demo/waterfall/
+ * Waterfall
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @product highcharts
+ * @apioption plotOptions.waterfall.upColor
+ */
+
dataLabels: {
inside: true
},
-
/**
* The width of the line connecting waterfall columns.
- *
- * @type {Number}
- * @default 1
+ *
* @product highcharts
*/
lineWidth: 1,
/**
* The color of the line that connects columns in a waterfall series.
- *
- *
+ *
* In styled mode, the stroke can be set with the `.highcharts-graph` class.
- *
- * @type {Color}
- * @default #333333
- * @since 3.0
+ *
+ * @type {Highcharts.ColorString}
+ * @since 3.0
* @product highcharts
*/
lineColor: '#333333',
/**
* A name for the dash style to use for the line connecting the columns
- * of the waterfall series. Possible values:
- *
- * * Solid
- * * ShortDash
- * * ShortDot
- * * ShortDashDot
- * * ShortDashDotDot
- * * Dot
- * * Dash
- * * LongDash
- * * DashDot
- * * LongDashDot
- * * LongDashDotDot
- *
- * In styled mode, the stroke dash-array can be set with the `.
- * highcharts-graph` class.
- *
- * @type {String}
- * @default Dot
- * @since 3.0
+ * of the waterfall series. Possible values: Dash, DashDot, Dot, LongDash,
+ * LongDashDot, LongDashDotDot, ShortDash, ShortDashDot, ShortDashDotDot,
+ * ShortDot, Solid
+ *
+ * In styled mode, the stroke dash-array can be set with the
+ * `.highcharts-graph` class.
+ *
+ * @type {Highcharts.DashStyleType}
+ * @since 3.0
* @product highcharts
*/
- dashStyle: 'dot',
+ dashStyle: 'Dot',
/**
* The color of the border of each waterfall column.
- *
- * In styled mode, the border stroke can be set with the `.highcharts-point` class.
- *
- * @type {Color}
- * @default #333333
- * @since 3.0
+ *
+ * In styled mode, the border stroke can be set with the
+ * `.highcharts-point` class.
+ *
+ * @type {Highcharts.ColorString}
+ * @since 3.0
* @product highcharts
*/
borderColor: '#333333',
@@ -3453,39 +4023,46 @@
}
}
-
- /**
- * The color used specifically for positive point columns. When not
- * specified, the general series color is used.
- *
- * In styled mode, the waterfall colors can be set with the
- * `.highcharts-point-negative`, `.highcharts-sum` and
- * `.highcharts-intermediate-sum` classes.
- *
- * @type {Color}
- * @sample {highcharts} highcharts/demo/waterfall/ Waterfall
- * @product highcharts
- * @apioption plotOptions.waterfall.upColor
- */
-
- // Prototype members
+ // Prototype members
}, {
pointValKey: 'y',
- /**
- * Translate data points from raw values
- */
- translate: function() {
- var series = this,
- options = series.options,
- yAxis = series.yAxis,
+ // Property needed to prevent lines between the columns from disappearing
+ // when negativeColor is used.
+ showLine: true,
+
+ // After generating points, set y-values for all sums.
+ generatePoints: function () {
+ var point,
len,
i,
- points,
- point,
- shapeArgs,
- stack,
- y,
+ y;
+
+ // Parent call:
+ seriesTypes.column.prototype.generatePoints.apply(this);
+
+ for (i = 0, len = this.points.length; i < len; i++) {
+ point = this.points[i];
+ y = this.processedYData[i];
+ // override point value for sums
+ // #3710 Update point does not propagate to sum
+ if (point.isIntermediateSum || point.isSum) {
+ point.y = correctFloat(y);
+ }
+ }
+ },
+
+ // Translate data points from raw values
+ translate: function () {
+ var series = this,
+ options = series.options,
+ yAxis = series.yAxis,
+ len,
+ i,
+ points,
+ point,
+ shapeArgs,
+ y,
yValue,
previousY,
previousIntermediate,
@@ -3494,8 +4071,13 @@
halfMinPointLength = minPointLength / 2,
threshold = options.threshold,
stacking = options.stacking,
- stackIndicator,
- tooltipY;
+ tooltipY,
+ actualStack = yAxis.waterfallStacks[series.stackKey],
+ actualStackX,
+ total,
+ pointY,
+ yPos,
+ hPos;
// run column series translate
seriesTypes.column.prototype.translate.apply(series);
@@ -3509,57 +4091,118 @@
yValue = series.processedYData[i];
shapeArgs = point.shapeArgs;
- // get current stack
- stack = stacking && yAxis.stacks[(series.negStacks && yValue < threshold ? '-' : '') + series.stackKey];
- stackIndicator = series.getStackIndicator(
- stackIndicator,
- point.x,
- series.index
- );
- range = stack ?
- stack[point.x].points[stackIndicator.key] : [0, yValue];
+ range = [0, yValue];
+ pointY = point.y;
- // override point value for sums
- // #3710 Update point does not propagate to sum
- if (point.isSum) {
- point.y = correctFloat(yValue);
- } else if (point.isIntermediateSum) {
- point.y = correctFloat(yValue - previousIntermediate); // #3840
- }
- // up points
- y = Math.max(previousY, previousY + point.y) + range[0];
- shapeArgs.y = yAxis.translate(y, 0, 1, 0, 1);
-
- // sum points
- if (point.isSum) {
- shapeArgs.y = yAxis.translate(range[1], 0, 1, 0, 1);
- shapeArgs.height = Math.min(yAxis.translate(range[0], 0, 1, 0, 1), yAxis.len) -
- shapeArgs.y; // #4256
-
- } else if (point.isIntermediateSum) {
- shapeArgs.y = yAxis.translate(range[1], 0, 1, 0, 1);
- shapeArgs.height = Math.min(yAxis.translate(previousIntermediate, 0, 1, 0, 1), yAxis.len) -
- shapeArgs.y;
- previousIntermediate = range[1];
-
- // If it's not the sum point, update previous stack end position and get
- // shape height (#3886)
+ // code responsible for correct positions of stacked points
+ // starts here
+ if (stacking) {
+ if (actualStack) {
+ actualStackX = actualStack[i];
+
+ if (stacking === 'overlap') {
+ total = actualStackX.threshold + actualStackX.total;
+ actualStackX.total -= pointY;
+
+ y = pointY >= 0 ? total : total - pointY;
+ } else {
+ if (pointY >= 0) {
+ total = actualStackX.threshold +
+ actualStackX.posTotal;
+
+ actualStackX.posTotal -= pointY;
+ y = total;
+ } else {
+ total = actualStackX.threshold +
+ actualStackX.negTotal;
+
+ actualStackX.negTotal -= pointY;
+ y = total - pointY;
+ }
+ }
+
+ if (!point.isSum) {
+ // the connectorThreshold property is later used in
+ // getCrispPath function to draw a connector line in a
+ // correct place
+ actualStackX.connectorThreshold =
+ actualStackX.threshold + actualStackX.stackTotal;
+ }
+
+ if (yAxis.reversed) {
+ yPos = (pointY >= 0) ? (y - pointY) : (y + pointY);
+ hPos = y;
+ } else {
+ yPos = y;
+ hPos = y - pointY;
+ }
+
+ point.below = yPos <= pick(threshold, 0);
+
+ shapeArgs.y = yAxis.translate(yPos, 0, 1, 0, 1);
+ shapeArgs.height = Math.abs(shapeArgs.y -
+ yAxis.translate(hPos, 0, 1, 0, 1));
+ }
} else {
- shapeArgs.height = yValue > 0 ?
- yAxis.translate(previousY, 0, 1, 0, 1) - shapeArgs.y :
- yAxis.translate(previousY, 0, 1, 0, 1) - yAxis.translate(previousY - yValue, 0, 1, 0, 1);
+ // up points
+ y = Math.max(previousY, previousY + pointY) + range[0];
+ shapeArgs.y = yAxis.translate(y, 0, 1, 0, 1);
+
+ // sum points
+ if (point.isSum) {
+ shapeArgs.y = yAxis.translate(range[1], 0, 1, 0, 1);
+ shapeArgs.height = Math.min(
+ yAxis.translate(range[0], 0, 1, 0, 1),
+ yAxis.len
+ ) - shapeArgs.y; // #4256
+
+ } else if (point.isIntermediateSum) {
+ if (pointY >= 0) {
+ yPos = range[1] + previousIntermediate;
+ hPos = previousIntermediate;
+ } else {
+ yPos = previousIntermediate;
+ hPos = range[1] + previousIntermediate;
+ }
- previousY += stack && stack[point.x] ? stack[point.x].total : yValue;
- }
+ if (yAxis.reversed) {
+ // swapping values
+ yPos ^= hPos;
+ hPos ^= yPos;
+ yPos ^= hPos;
+ }
+
+ shapeArgs.y = yAxis.translate(yPos, 0, 1, 0, 1);
+ shapeArgs.height = Math.abs(shapeArgs.y - Math.min(
+ yAxis.translate(hPos, 0, 1, 0, 1),
+ yAxis.len
+ ));
- // #3952 Negative sum or intermediate sum not rendered correctly
- if (shapeArgs.height < 0) {
- shapeArgs.y += shapeArgs.height;
- shapeArgs.height *= -1;
+ previousIntermediate += range[1];
+
+ // If it's not the sum point, update previous stack end position
+ // and get shape height (#3886)
+ } else {
+ shapeArgs.height = yValue > 0 ?
+ yAxis.translate(previousY, 0, 1, 0, 1) - shapeArgs.y :
+ yAxis.translate(previousY, 0, 1, 0, 1) -
+ yAxis.translate(previousY - yValue, 0, 1, 0, 1);
+
+ previousY += yValue;
+ point.below = previousY < pick(threshold, 0);
+ }
+
+ // #3952 Negative sum or intermediate sum not rendered correctly
+ if (shapeArgs.height < 0) {
+ shapeArgs.y += shapeArgs.height;
+ shapeArgs.height *= -1;
+ }
}
- point.plotY = shapeArgs.y = Math.round(shapeArgs.y) - (series.borderWidth % 2) / 2;
- shapeArgs.height = Math.max(Math.round(shapeArgs.height), 0.001); // #3151
+ point.plotY = shapeArgs.y = Math.round(shapeArgs.y) -
+ (series.borderWidth % 2) / 2;
+ // #3151
+ shapeArgs.height = Math.max(Math.round(shapeArgs.height), 0.001);
point.yBottom = shapeArgs.y + shapeArgs.height;
if (shapeArgs.height <= minPointLength && !point.isNull) {
@@ -3572,6 +4215,9 @@
point.minPointLengthOffset = halfMinPointLength;
}
} else {
+ if (point.isNull) {
+ shapeArgs.width = 0;
+ }
point.minPointLengthOffset = 0;
}
@@ -3586,14 +4232,14 @@
}
},
- /**
- * Call default processData then override yData to reflect waterfall's extremes on yAxis
- */
- processData: function(force) {
+ // Call default processData then override yData to reflect waterfall's
+ // extremes on yAxis
+ processData: function (force) {
var series = this,
options = series.options,
yData = series.yData,
- points = series.options.data, // #3710 Update point does not propagate to sum
+ // #3710 Update point does not propagate to sum
+ points = options.data,
point,
dataLength = yData.length,
threshold = options.threshold || 0,
@@ -3604,7 +4250,7 @@
y,
i;
- sum = subSum = dataMin = dataMax = threshold;
+ sum = subSum = dataMin = dataMax = 0;
for (i = 0; i < dataLength; i++) {
y = yData[i];
@@ -3614,6 +4260,7 @@
yData[i] = correctFloat(sum);
} else if (y === 'intermediateSum' || point.isIntermediateSum) {
yData[i] = correctFloat(subSum);
+ subSum = 0;
} else {
sum += y;
subSum += y;
@@ -3625,18 +4272,17 @@
Series.prototype.processData.call(this, force);
// Record extremes only if stacking was not set:
- if (!series.options.stacking) {
- series.dataMin = dataMin;
+ if (!options.stacking) {
+ series.dataMin = dataMin + threshold;
series.dataMax = dataMax;
}
},
- /**
- * Return y value or string if point is sum
- */
- toYData: function(pt) {
+ // Return y value or string if point is sum
+ toYData: function (pt) {
if (pt.isSum) {
- return (pt.x === 0 ? null : 'sum'); // #3245 Error when first element is Sum or Intermediate Sum
+ // #3245 Error when first element is Sum or Intermediate Sum
+ return (pt.x === 0 ? null : 'sum');
}
if (pt.isIntermediateSum) {
return (pt.x === 0 ? null : 'intermediateSum'); // #3245
@@ -3644,11 +4290,8 @@
return pt.y;
},
-
- /**
- * Postprocess mapping between options and SVG attributes
- */
- pointAttribs: function(point, state) {
+ // Postprocess mapping between options and SVG attributes
+ pointAttribs: function (point, state) {
var upColor = this.options.upColor,
attr;
@@ -3658,7 +4301,11 @@
point.color = point.y > 0 ? upColor : null;
}
- attr = seriesTypes.column.prototype.pointAttribs.call(this, point, state);
+ attr = seriesTypes.column.prototype.pointAttribs.call(
+ this,
+ point,
+ state
+ );
// The dashStyle option in waterfall applies to the graph, not
// the points
@@ -3667,26 +4314,30 @@
return attr;
},
-
- /**
- * Return an empty path initially, because we need to know the stroke-width in order
- * to set the final path.
- */
- getGraphPath: function() {
+ // Return an empty path initially, because we need to know the stroke-width
+ // in order to set the final path.
+ getGraphPath: function () {
return ['M', 0, 0];
},
- /**
- * Draw columns' connector lines
- */
- getCrispPath: function() {
+ // Draw columns' connector lines
+ getCrispPath: function () {
var data = this.data,
+ yAxis = this.yAxis,
length = data.length,
- lineWidth = this.graph.strokeWidth() + this.borderWidth,
- normalizer = Math.round(lineWidth) % 2 / 2,
+ graphNormalizer = Math.round(this.graph.strokeWidth()) % 2 / 2,
+ borderNormalizer = Math.round(this.borderWidth) % 2 / 2,
+ reversedXAxis = this.xAxis.reversed,
reversedYAxis = this.yAxis.reversed,
+ stacking = this.options.stacking,
path = [],
+ connectorThreshold,
+ prevStack,
+ prevStackX,
+ prevPoint,
+ yPos,
+ isPos,
prevArgs,
pointArgs,
i,
@@ -3694,20 +4345,43 @@
for (i = 1; i < length; i++) {
pointArgs = data[i].shapeArgs;
+ prevPoint = data[i - 1];
prevArgs = data[i - 1].shapeArgs;
+ prevStack = yAxis.waterfallStacks[this.stackKey];
+ isPos = prevPoint.y > 0 ? -prevArgs.height : 0;
+
+ if (prevStack) {
+ prevStackX = prevStack[i - 1];
+
+ // y position of the connector is different when series are
+ // stacked, yAxis is reversed and it also depends on point's
+ // value
+ if (stacking) {
+ connectorThreshold = prevStackX.connectorThreshold;
+
+ yPos = Math.round(
+ (yAxis.translate(connectorThreshold, 0, 1, 0, 1) +
+ (reversedYAxis ? isPos : 0))
+ ) - graphNormalizer;
+ } else {
+ yPos = prevArgs.y + prevPoint.minPointLengthOffset +
+ borderNormalizer - graphNormalizer;
+ }
- d = [
- 'M',
- prevArgs.x + prevArgs.width,
- prevArgs.y + data[i - 1].minPointLengthOffset + normalizer,
- 'L',
- pointArgs.x,
- prevArgs.y + data[i - 1].minPointLengthOffset + normalizer
- ];
+ d = [
+ 'M',
+ prevArgs.x + (reversedXAxis ? 0 : prevArgs.width),
+ yPos,
+ 'L',
+ pointArgs.x + (reversedXAxis ? pointArgs.width : 0),
+ yPos
+ ];
+ }
if (
- (data[i - 1].y < 0 && !reversedYAxis) ||
- (data[i - 1].y > 0 && reversedYAxis)
+ !stacking &&
+ (prevPoint.y < 0 && !reversedYAxis) ||
+ (prevPoint.y > 0 && reversedYAxis)
) {
d[2] += prevArgs.height;
d[5] += prevArgs.height;
@@ -3719,55 +4393,143 @@
return path;
},
- /**
- * The graph is initally drawn with an empty definition, then updated with
- * crisp rendering.
- */
- drawGraph: function() {
+ // The graph is initially drawn with an empty definition, then updated with
+ // crisp rendering.
+ drawGraph: function () {
Series.prototype.drawGraph.call(this);
this.graph.attr({
d: this.getCrispPath()
});
},
- /**
- * Waterfall has stacking along the x-values too.
- */
- setStackedPoints: function() {
+ // Waterfall has stacking along the x-values too.
+ setStackedPoints: function () {
var series = this,
options = series.options,
- stackedYLength,
- i;
+ waterfallStacks = series.yAxis.waterfallStacks,
+ seriesThreshold = options.threshold,
+ stackThreshold = seriesThreshold || 0,
+ interSum = seriesThreshold || 0,
+ stackKey = series.stackKey,
+ xData = series.xData,
+ xLength = xData.length,
+ actualStack,
+ actualStackX,
+ posTotal,
+ negTotal,
+ xPoint,
+ yVal,
+ x;
+
+ // code responsible for creating stacks for waterfall series
+ if (series.visible || !series.chart.options.chart.ignoreHiddenSeries) {
+ if (!waterfallStacks[stackKey]) {
+ waterfallStacks[stackKey] = {};
+ }
- Series.prototype.setStackedPoints.apply(series, arguments);
+ actualStack = waterfallStacks[stackKey];
- stackedYLength = series.stackedYData ? series.stackedYData.length : 0;
+ for (var i = 0; i < xLength; i++) {
+ x = xData[i];
- // Start from the second point:
- for (i = 1; i < stackedYLength; i++) {
- if (!options.data[i].isSum &&
- !options.data[i].isIntermediateSum
- ) {
- // Sum previous stacked data as waterfall can grow up/down:
- series.stackedYData[i] += series.stackedYData[i - 1];
+ if (!actualStack[x]) {
+ actualStack[x] = {
+ negTotal: 0,
+ posTotal: 0,
+ total: 0,
+ stackTotal: 0,
+ threshold: 0,
+ stackState: [stackThreshold]
+ };
+ }
+
+ actualStackX = actualStack[x];
+ yVal = series.yData[i];
+
+ if (yVal >= 0) {
+ actualStackX.posTotal += yVal;
+ } else {
+ actualStackX.negTotal += yVal;
+ }
+
+ // points do not exist yet, so raw data is used
+ xPoint = options.data[i];
+ posTotal = actualStackX.posTotal;
+ negTotal = actualStackX.negTotal;
+
+ if (xPoint && xPoint.isIntermediateSum) {
+ // swapping values
+ stackThreshold ^= interSum;
+ interSum ^= stackThreshold;
+ stackThreshold ^= interSum;
+ } else if (xPoint && xPoint.isSum) {
+ stackThreshold = seriesThreshold;
+ }
+
+ actualStackX.stackTotal = posTotal + negTotal;
+ actualStackX.total = actualStackX.stackTotal;
+ actualStackX.threshold = stackThreshold;
+
+ actualStackX.stackState[0] = stackThreshold;
+ actualStackX.stackState.push(actualStackX.stackTotal);
+
+ stackThreshold += actualStackX.stackTotal;
}
}
},
- /**
- * Extremes for a non-stacked series are recorded in processData.
- * In case of stacking, use Series.stackedYData to calculate extremes.
- */
- getExtremes: function() {
- if (this.options.stacking) {
- return Series.prototype.getExtremes.apply(this, arguments);
+ // Extremes for a non-stacked series are recorded in processData.
+ // In case of stacking, use Series.stackedYData to calculate extremes.
+ getExtremes: function () {
+ var stacking = this.options.stacking,
+ yAxis,
+ waterfallStacks,
+ stackedYNeg,
+ stackedYPos,
+ states,
+ firstState;
+
+ if (stacking) {
+ yAxis = this.yAxis;
+ waterfallStacks = yAxis.waterfallStacks;
+ stackedYNeg = this.stackedYNeg = [];
+ stackedYPos = this.stackedYPos = [];
+
+ // the visible y range can be different when stacking is set to
+ // overlap and different when it's set to normal
+ if (stacking === 'overlap') {
+ objectEach(waterfallStacks[this.stackKey], function (stackX) {
+
+ states = [];
+ stackX.stackState.forEach(function (state, stateIndex) {
+ firstState = stackX.stackState[0];
+
+ if (stateIndex) {
+ states.push(state + firstState);
+ } else {
+ states.push(firstState);
+ }
+ });
+
+ stackedYNeg.push(arrayMin(states));
+ stackedYPos.push(arrayMax(states));
+ });
+ } else {
+ objectEach(waterfallStacks[this.stackKey], function (stackX) {
+ stackedYNeg.push(stackX.negTotal + stackX.threshold);
+ stackedYPos.push(stackX.posTotal + stackX.threshold);
+ });
+ }
+
+ this.dataMin = arrayMin(stackedYNeg);
+ this.dataMax = arrayMax(stackedYPos);
}
}
- // Point members
+ // Point members
}, {
- getClassName: function() {
+ getClassName: function () {
var className = Point.prototype.getClassName.call(this);
if (this.isSum) {
@@ -3777,10 +4539,8 @@
}
return className;
},
- /**
- * Pass the null test in ColumnSeries.translate.
- */
- isValid: function() {
+ // Pass the null test in ColumnSeries.translate.
+ isValid: function () {
return isNumber(this.y, true) || this.isSum || this.isIntermediateSum;
}
@@ -3789,74 +4549,71 @@
/**
* A `waterfall` series. If the [type](#series.waterfall.type) option
* is not specified, it is inherited from [chart.type](#chart.type).
- *
- *
- * For options that apply to multiple series, it is recommended to add
- * them to the [plotOptions.series](#plotOptions.series) options structure.
- * To apply to all series of this specific type, apply it to [plotOptions.
- * waterfall](#plotOptions.waterfall).
- *
- * @type {Object}
- * @extends series,plotOptions.waterfall
- * @excluding dataParser,dataURL
- * @product highcharts
+ *
+ * @extends series,plotOptions.waterfall
+ * @excluding dataParser, dataURL
+ * @product highcharts
* @apioption series.waterfall
*/
/**
* An array of data points for the series. For the `waterfall` series
* type, points can be given in the following ways:
- *
- * 1. An array of numerical values. In this case, the numerical values
- * will be interpreted as `y` options. The `x` values will be automatically
- * calculated, either starting at 0 and incremented by 1, or from `pointStart`
- * and `pointInterval` given in the series options. If the axis has
- * categories, these will be used. Example:
- *
- * ```js
- * data: [0, 5, 3, 5]
- * ```
- *
- * 2. An array of arrays with 2 values. In this case, the values correspond
- * to `x,y`. If the first value is a string, it is applied as the name
- * of the point, and the `x` value is inferred.
- *
- * ```js
- * data: [
- * [0, 7],
- * [1, 8],
- * [2, 3]
- * ]
- * ```
- *
- * 3. An array of objects with named values. The objects are point
- * configuration objects as seen below. If the total number of data
- * points exceeds the series' [turboThreshold](#series.waterfall.turboThreshold),
- * this option is not available.
- *
- * ```js
- * data: [{
- * x: 1,
- * y: 8,
- * name: "Point2",
- * color: "#00FF00"
- * }, {
- * x: 1,
- * y: 8,
- * name: "Point1",
- * color: "#FF00FF"
- * }]
- * ```
- *
- * @type {Array}
- * @extends series.line.data
+ *
+ * 1. An array of numerical values. In this case, the numerical values will be
+ * interpreted as `y` options. The `x` values will be automatically
+ * calculated, either starting at 0 and incremented by 1, or from
+ * `pointStart` and `pointInterval` given in the series options. If the axis
+ * has categories, these will be used. Example:
+ * ```js
+ * data: [0, 5, 3, 5]
+ * ```
+ *
+ * 2. An array of arrays with 2 values. In this case, the values correspond to
+ * `x,y`. If the first value is a string, it is applied as the name of the
+ * point, and the `x` value is inferred.
+ * ```js
+ * data: [
+ * [0, 7],
+ * [1, 8],
+ * [2, 3]
+ * ]
+ * ```
+ *
+ * 3. An array of objects with named values. The following snippet shows only a
+ * few settings, see the complete options set below. If the total number of
+ * data points exceeds the series'
+ * [turboThreshold](#series.waterfall.turboThreshold), this option is not
+ * available.
+ * ```js
+ * data: [{
+ * x: 1,
+ * y: 8,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * y: 8,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @sample {highcharts} highcharts/chart/reflow-true/
+ * Numerical values
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/
+ * Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
+ * Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/
+ * Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
+ *
+ * @type {Array|*>}
+ * @extends series.line.data
* @excluding marker
- * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
- * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
- * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
- * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
- * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
- * @product highcharts
+ * @product highcharts
* @apioption series.waterfall.data
*/
@@ -3865,32 +4622,39 @@
* When this property is true, the points acts as a summary column for
* the values added or substracted since the last intermediate sum,
* or since the start of the series. The `y` value is ignored.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/demo/waterfall/ Waterfall
- * @default false
- * @product highcharts
+ *
+ * @sample {highcharts} highcharts/demo/waterfall/
+ * Waterfall
+ *
+ * @type {boolean}
+ * @default false
+ * @product highcharts
* @apioption series.waterfall.data.isIntermediateSum
*/
/**
* When this property is true, the point display the total sum across
* the entire series. The `y` value is ignored.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/demo/waterfall/ Waterfall
- * @default false
- * @product highcharts
+ *
+ * @sample {highcharts} highcharts/demo/waterfall/
+ * Waterfall
+ *
+ * @type {boolean}
+ * @default false
+ * @product highcharts
* @apioption series.waterfall.data.isSum
*/
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
* License: www.highcharts.com/license
*/
+
+
+
var LegendSymbolMixin = H.LegendSymbolMixin,
noop = H.noop,
Series = H.Series,
@@ -3902,14 +4666,16 @@
* coordinate system. A fill is applied with the `color` option, and
* stroke is applied through `lineWidth` and `lineColor` options. Requires
* the `highcharts-more.js` file.
- *
- * @type {Object}
- * @extends plotOptions.scatter
- * @excluding softThreshold,threshold
- * @sample {highcharts} highcharts/demo/polygon/ Polygon
- * @sample {highstock} highcharts/demo/polygon/ Polygon
- * @since 4.1.0
- * @product highcharts highstock
+ *
+ * @sample {highcharts} highcharts/demo/polygon/
+ * Polygon
+ * @sample {highstock} highcharts/demo/polygon/
+ * Polygon
+ *
+ * @extends plotOptions.scatter
+ * @since 4.1.0
+ * @excluding jitter, softThreshold, threshold
+ * @product highcharts highstock
* @optionparent plotOptions.polygon
*/
seriesType('polygon', 'scatter', {
@@ -3928,10 +4694,10 @@
},
trackByArea: true
- // Prototype members
+ // Prototype members
}, {
type: 'polygon',
- getGraphPath: function() {
+ getGraphPath: function () {
var graphPath = Series.prototype.getGraphPath.call(this),
i = graphPath.length + 1;
@@ -3945,10 +4711,9 @@
this.areaPath = graphPath;
return graphPath;
},
- drawGraph: function() {
-
- this.options.fillColor = this.color; // Hack into the fill logic in area.drawGraph
-
+ drawGraph: function () {
+ // Hack into the fill logic in area.drawGraph
+ this.options.fillColor = this.color;
seriesTypes.area.prototype.drawGraph.call(this);
},
drawLegendSymbol: LegendSymbolMixin.drawRectangle,
@@ -3957,167 +4722,1338 @@
});
-
/**
* A `polygon` series. If the [type](#series.polygon.type) option is
* not specified, it is inherited from [chart.type](#chart.type).
- *
- * For options that apply to multiple series, it is recommended to add
- * them to the [plotOptions.series](#plotOptions.series) options structure.
- * To apply to all series of this specific type, apply it to [plotOptions.
- * polygon](#plotOptions.polygon).
- *
- * @type {Object}
- * @extends series,plotOptions.polygon
- * @excluding dataParser,dataURL,stack
- * @product highcharts highstock
+ *
+ * @extends series,plotOptions.polygon
+ * @excluding dataParser, dataURL, stack
+ * @product highcharts highstock
* @apioption series.polygon
*/
/**
* An array of data points for the series. For the `polygon` series
* type, points can be given in the following ways:
- *
- * 1. An array of numerical values. In this case, the numerical values
- * will be interpreted as `y` options. The `x` values will be automatically
- * calculated, either starting at 0 and incremented by 1, or from `pointStart`
- * and `pointInterval` given in the series options. If the axis has
- * categories, these will be used. Example:
- *
- * ```js
- * data: [0, 5, 3, 5]
- * ```
- *
- * 2. An array of arrays with 2 values. In this case, the values correspond
- * to `x,y`. If the first value is a string, it is applied as the name
- * of the point, and the `x` value is inferred.
- *
- * ```js
- * data: [
- * [0, 10],
- * [1, 3],
- * [2, 1]
- * ]
- * ```
- *
- * 3. An array of objects with named values. The objects are point
- * configuration objects as seen below. If the total number of data
- * points exceeds the series' [turboThreshold](#series.polygon.turboThreshold),
- * this option is not available.
- *
- * ```js
- * data: [{
- * x: 1,
- * y: 1,
- * name: "Point2",
- * color: "#00FF00"
- * }, {
- * x: 1,
- * y: 8,
- * name: "Point1",
- * color: "#FF00FF"
- * }]
- * ```
- *
- * @type {Array}
- * @extends series.line.data
- * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
- * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
- * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
- * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
- * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
- * @product highcharts highstock
+ *
+ * 1. An array of numerical values. In this case, the numerical values will be
+ * interpreted as `y` options. The `x` values will be automatically
+ * calculated, either starting at 0 and incremented by 1, or from
+ * `pointStart` and `pointInterval` given in the series options. If the axis
+ * has categories, these will be used. Example:
+ * ```js
+ * data: [0, 5, 3, 5]
+ * ```
+ *
+ * 2. An array of arrays with 2 values. In this case, the values correspond to
+ * `x,y`. If the first value is a string, it is applied as the name of the
+ * point, and the `x` value is inferred.
+ * ```js
+ * data: [
+ * [0, 10],
+ * [1, 3],
+ * [2, 1]
+ * ]
+ * ```
+ *
+ * 3. An array of objects with named values. The following snippet shows only a
+ * few settings, see the complete options set below. If the total number of
+ * data points exceeds the series'
+ * [turboThreshold](#series.polygon.turboThreshold), this option is not
+ * available.
+ * ```js
+ * data: [{
+ * x: 1,
+ * y: 1,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * y: 8,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @sample {highcharts} highcharts/chart/reflow-true/
+ * Numerical values
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/
+ * Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
+ * Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/
+ * Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
+ *
+ * @type {Array|*>}
+ * @extends series.line.data
+ * @product highcharts highstock
* @apioption series.polygon.data
*/
}(Highcharts));
- (function(H) {
- /**
- * (c) 2010-2017 Torstein Honsi
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Highsoft AS
+ *
+ * Author: Paweł Potaczek
*
* License: www.highcharts.com/license
*/
- var arrayMax = H.arrayMax,
- arrayMin = H.arrayMin,
- Axis = H.Axis,
+
+ /**
+ * @interface Highcharts.LegendBubbleLegendFormatterContextObject
+ *//**
+ * The center y position of the range.
+ * @name Highcharts.LegendBubbleLegendFormatterContextObject#center
+ * @type {number}
+ *//**
+ * The radius of the bubble range.
+ * @name Highcharts.LegendBubbleLegendFormatterContextObject#radius
+ * @type {number}
+ *//**
+ * The bubble value.
+ * @name Highcharts.LegendBubbleLegendFormatterContextObject#value
+ * @type {number}
+ */
+
+
+
+ var Series = H.Series,
+ Legend = H.Legend,
+ Chart = H.Chart,
+
+ addEvent = H.addEvent,
+ wrap = H.wrap,
color = H.color,
- each = H.each,
isNumber = H.isNumber,
+ numberFormat = H.numberFormat,
+ objectEach = H.objectEach,
+ merge = H.merge,
noop = H.noop,
pick = H.pick,
- pInt = H.pInt,
- Point = H.Point,
- Series = H.Series,
- seriesType = H.seriesType,
- seriesTypes = H.seriesTypes;
+ stableSort = H.stableSort,
+ setOptions = H.setOptions,
+ arrayMin = H.arrayMin,
+ arrayMax = H.arrayMax;
+ setOptions({ // Set default bubble legend options
+ legend: {
+ /**
+ * The bubble legend is an additional element in legend which presents
+ * the scale of the bubble series. Individual bubble ranges can be
+ * defined by user or calculated from series. In the case of
+ * automatically calculated ranges, a 1px margin of error is permitted.
+ * Requires `highcharts-more.js`.
+ *
+ * @since 7.0.0
+ * @product highcharts highstock highmaps
+ * @optionparent legend.bubbleLegend
+ */
+ bubbleLegend: {
+ /**
+ * The color of the ranges borders, can be also defined for an
+ * individual range.
+ *
+ * @sample highcharts/bubble-legend/similartoseries/
+ * Similat look to the bubble series
+ * @sample highcharts/bubble-legend/bordercolor/
+ * Individual bubble border color
+ *
+ * @type {Highcharts.ColorString}
+ */
+ borderColor: undefined,
+ /**
+ * The width of the ranges borders in pixels, can be also defined
+ * for an individual range.
+ */
+ borderWidth: 2,
+ /**
+ * An additional class name to apply to the bubble legend' circle
+ * graphical elements. This option does not replace default class
+ * names of the graphical element.
+ *
+ * @sample {highcharts} highcharts/css/bubble-legend/
+ * Styling by CSS
+ *
+ * @type {string}
+ */
+ className: undefined,
+ /**
+ * The main color of the bubble legend. Applies to ranges, if
+ * individual color is not defined.
+ *
+ * @sample highcharts/bubble-legend/similartoseries/
+ * Similat look to the bubble series
+ * @sample highcharts/bubble-legend/color/
+ * Individual bubble color
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ */
+ color: undefined,
+ /**
+ * An additional class name to apply to the bubble legend's
+ * connector graphical elements. This option does not replace
+ * default class names of the graphical element.
+ *
+ * @sample {highcharts} highcharts/css/bubble-legend/
+ * Styling by CSS
+ *
+ * @type {string}
+ */
+ connectorClassName: undefined,
+ /**
+ * The color of the connector, can be also defined
+ * for an individual range.
+ *
+ * @type {Highcharts.ColorString}
+ */
+ connectorColor: undefined,
+ /**
+ * The length of the connectors in pixels. If labels are centered,
+ * the distance is reduced to 0.
+ *
+ * @sample highcharts/bubble-legend/connectorandlabels/
+ * Increased connector length
+ */
+ connectorDistance: 60,
+ /**
+ * The width of the connectors in pixels.
+ *
+ * @sample highcharts/bubble-legend/connectorandlabels/
+ * Increased connector width
+ */
+ connectorWidth: 1,
+ /**
+ * Enable or disable the bubble legend.
+ */
+ enabled: false,
+ /**
+ * Options for the bubble legend labels.
+ */
+ labels: {
+ /**
+ * An additional class name to apply to the bubble legend
+ * label graphical elements. This option does not replace
+ * default class names of the graphical element.
+ *
+ * @sample {highcharts} highcharts/css/bubble-legend/
+ * Styling by CSS
+ *
+ * @type {string}
+ */
+ className: undefined,
+ /**
+ * Whether to allow data labels to overlap.
+ */
+ allowOverlap: false,
+ /**
+ * A [format string](http://docs.highcharts.com/#formatting)
+ * for the bubble legend labels. Available variables are the
+ * same as for `formatter`.
+ *
+ * @sample highcharts/bubble-legend/format/
+ * Add a unit
+ *
+ * @type {string}
+ */
+ format: '',
+ /**
+ * Available `this` properties are:
+ *
+ * - `this.value`: The bubble value.
+ *
+ * - `this.radius`: The radius of the bubble range.
+ *
+ * - `this.center`: The center y position of the range.
+ *
+ * @type {Highcharts.FormatterCallbackFunction}
+ */
+ formatter: undefined,
+ /**
+ * The alignment of the labels compared to the bubble legend.
+ * Can be one of `left`, `center` or `right`.
+ * @validvalue ["left", "center", "right"]
+ *
+ * @sample highcharts/bubble-legend/connectorandlabels/
+ * Labels on left
+ *
+ * @validvalue ["left", "center", "right"]
+ */
+ align: 'right',
+ /**
+ * CSS styles for the labels.
+ *
+ * @type {Highcharts.CSSObject}
+ */
+ style: {
+ /** @ignore-option */
+ fontSize: 10,
+ /** @ignore-option */
+ color: undefined
+ },
+ /**
+ * The x position offset of the label relative to the
+ * connector.
+ */
+ x: 0,
+ /**
+ * The y position offset of the label relative to the
+ * connector.
+ */
+ y: 0
+ },
+ /**
+ * Miximum bubble legend range size. If values for ranges are not
+ * specified, the `minSize` and the `maxSize` are calculated from
+ * bubble series.
+ */
+ maxSize: 60, // Number
+ /**
+ * Minimum bubble legend range size. If values for ranges are not
+ * specified, the `minSize` and the `maxSize` are calculated from
+ * bubble series.
+ */
+ minSize: 10, // Number
+ /**
+ * The position of the bubble legend in the legend.
+ * @sample highcharts/bubble-legend/connectorandlabels/
+ * Bubble legend as last item in legend
+ */
+ legendIndex: 0, // Number
+ /**
+ * Options for specific range. One range consists of bubble, label
+ * and connector.
+ *
+ * @sample highcharts/bubble-legend/ranges/
+ * Manually defined ranges
+ * @sample highcharts/bubble-legend/autoranges/
+ * Auto calculated ranges
+ *
+ * @type {Array<*>}
+ */
+ ranges: {
+ /**
+ * Range size value, similar to bubble Z data.
+ */
+ value: undefined,
+ /**
+ * The color of the border for individual range.
+ * @type {Highcharts.ColorString}
+ */
+ borderColor: undefined,
+ /**
+ * The color of the bubble for individual range.
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ */
+ color: undefined,
+ /**
+ * The color of the connector for individual range.
+ * @type {Highcharts.ColorString}
+ */
+ connectorColor: undefined
+ },
+ /**
+ * Whether the bubble legend range value should be represented by
+ * the area or the width of the bubble. The default, area,
+ * corresponds best to the human perception of the size of each
+ * bubble.
+ *
+ * @sample highcharts/bubble-legend/ranges/
+ * Size by width
+ *
+ * @validvalue ["area", "width"]
+ */
+ sizeBy: 'area',
+ /**
+ * When this is true, the absolute value of z determines the size of
+ * the bubble. This means that with the default zThreshold of 0, a
+ * bubble of value -1 will have the same size as a bubble of value
+ * 1, while a bubble of value 0 will have a smaller size according
+ * to minSize.
+ */
+ sizeByAbsoluteValue: false,
+ /**
+ * Define the visual z index of the bubble legend.
+ */
+ zIndex: 1,
+ /**
+ * Ranges with with lower value than zThreshold, are skipped.
+ */
+ zThreshold: 0
+ }
+ }
+ });
/**
- * A bubble series is a three dimensional series type where each point renders
- * an X, Y and Z value. Each points is drawn as a bubble where the position
- * along the X and Y axes mark the X and Y values, and the size of the bubble
- * relates to the Z value. Requires `highcharts-more.js`.
+ * BubbleLegend class.
*
- * @sample {highcharts} highcharts/demo/bubble/ Bubble chart
- * @extends plotOptions.scatter
- * @product highcharts highstock
- * @optionparent plotOptions.bubble
+ * @private
+ * @class
+ * @name Highcharts.BubbleLegend
+ *
+ * @param {Highcharts.LegendBubbleLegendOptions} config
+ * Bubble legend options
+ *
+ * @param {Highcharts.LegendOptions} config
+ * Legend options
*/
- seriesType('bubble', 'scatter', {
+ H.BubbleLegend = function (options, legend) {
+ this.init(options, legend);
+ };
- dataLabels: {
- formatter: function() { // #2945
- return this.point.z;
- },
- inside: true,
- verticalAlign: 'middle'
+ H.BubbleLegend.prototype = {
+ /**
+ * Create basic bubbleLegend properties similar to item in legend.
+ *
+ * @private
+ * @function Highcharts.BubbleLegend#init
+ *
+ * @param {Highcharts.LegendBubbleLegendOptions} config
+ * Bubble legend options
+ *
+ * @param {Highcharts.LegendOptions} config
+ * Legend options
+ */
+ init: function (options, legend) {
+ this.options = options;
+ this.visible = true;
+ this.chart = legend.chart;
+ this.legend = legend;
},
+ setState: noop,
+
/**
- * Whether to display negative sized bubbles. The threshold is given
- * by the [zThreshold](#plotOptions.bubble.zThreshold) option, and negative
- * bubbles can be visualized by setting [negativeColor](#plotOptions.
- * bubble.negativeColor).
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/bubble-negative/
- * Negative bubbles
- * @default true
- * @since 3.0
- * @product highcharts
- * @apioption plotOptions.bubble.displayNegative
+ * Depending on the position option, add bubbleLegend to legend items.
+ *
+ * @private
+ * @function Highcharts.BubbleLegend#addToLegend
+ *
+ * @param {Array<*>}
+ * All legend items
*/
+ addToLegend: function (items) {
+ // Insert bubbleLegend into legend items
+ items.splice(this.options.legendIndex, 0, this);
+ },
/**
- * Options for the point markers of line-like series. Properties like
- * `fillColor`, `lineColor` and `lineWidth` define the visual appearance
- * of the markers. Other series types, like column series, don't have
- * markers, but have visual options on the series level instead.
- *
- * In styled mode, the markers can be styled with the `.highcharts-point`, `.highcharts-point-hover` and `.highcharts-point-select`
- * class names.
- *
- * @type {Object}
- * @extends plotOptions.series.marker
- * @excluding enabled,height,radius,width
- * @product highcharts
+ * Calculate ranges, sizes and call the next steps of bubbleLegend creation.
+ *
+ * @private
+ * @function Highcharts.BubbleLegend#drawLegendSymbol
+ *
+ * @param {Highcharts.Legend} legend
+ * Legend instance
+ */
+ drawLegendSymbol: function (legend) {
+ var bubbleLegend = this,
+ chart = bubbleLegend.chart,
+ options = bubbleLegend.options,
+ size,
+ itemDistance = pick(legend.options.itemDistance, 20),
+ connectorSpace,
+ ranges = options.ranges,
+ radius,
+ maxLabel,
+ connectorDistance = options.connectorDistance;
+
+ // Predict label dimensions
+ bubbleLegend.fontMetrics = chart.renderer.fontMetrics(
+ options.labels.style.fontSize.toString() + 'px'
+ );
+
+ // Do not create bubbleLegend now if ranges or ranges valeus are not
+ // specified or if are empty array.
+ if (!ranges || !ranges.length || !isNumber(ranges[0].value)) {
+ legend.options.bubbleLegend.autoRanges = true;
+ return;
+ }
+
+ // Sort ranges to right render order
+ stableSort(ranges, function (a, b) {
+ return b.value - a.value;
+ });
+
+ bubbleLegend.ranges = ranges;
+
+ bubbleLegend.setOptions();
+ bubbleLegend.render();
+
+ // Get max label size
+ maxLabel = bubbleLegend.getMaxLabelSize();
+ radius = bubbleLegend.ranges[0].radius;
+ size = radius * 2;
+
+ // Space for connectors and labels.
+ connectorSpace = connectorDistance - radius + maxLabel.width;
+ connectorSpace = connectorSpace > 0 ? connectorSpace : 0;
+
+ bubbleLegend.maxLabel = maxLabel;
+ bubbleLegend.movementX = options.labels.align === 'left' ?
+ connectorSpace : 0;
+
+ bubbleLegend.legendItemWidth = size + connectorSpace + itemDistance;
+ bubbleLegend.legendItemHeight = size + bubbleLegend.fontMetrics.h / 2;
+ },
+
+ /**
+ * Set style options for each bubbleLegend range.
+ *
+ * @private
+ * @function Highcharts.BubbleLegend#setOptions
+ */
+ setOptions: function () {
+ var bubbleLegend = this,
+ ranges = bubbleLegend.ranges,
+ options = bubbleLegend.options,
+ series = bubbleLegend.chart.series[options.seriesIndex],
+ baseline = bubbleLegend.legend.baseline,
+ bubbleStyle = {
+ 'z-index': options.zIndex,
+ 'stroke-width': options.borderWidth
+ },
+ connectorStyle = {
+ 'z-index': options.zIndex,
+ 'stroke-width': options.connectorWidth
+ },
+ labelStyle = bubbleLegend.getLabelStyles(),
+ fillOpacity = series.options.marker.fillOpacity,
+ styledMode = bubbleLegend.chart.styledMode;
+
+ // Allow to parts of styles be used individually for range
+ ranges.forEach(function (range, i) {
+ if (!styledMode) {
+ bubbleStyle.stroke = pick(
+ range.borderColor,
+ options.borderColor,
+ series.color
+ );
+ bubbleStyle.fill = pick(
+ range.color,
+ options.color,
+ fillOpacity !== 1 ?
+ color(series.color).setOpacity(fillOpacity)
+ .get('rgba') :
+ series.color
+ );
+ connectorStyle.stroke = pick(
+ range.connectorColor,
+ options.connectorColor,
+ series.color
+ );
+ }
+
+ // Set options needed for rendering each range
+ ranges[i].radius = bubbleLegend.getRangeRadius(range.value);
+ ranges[i] = merge(ranges[i], {
+ center: ranges[0].radius - ranges[i].radius + baseline
+ });
+
+ if (!styledMode) {
+ merge(true, ranges[i], {
+ bubbleStyle: merge(false, bubbleStyle),
+ connectorStyle: merge(false, connectorStyle),
+ labelStyle: labelStyle
+ });
+ }
+ });
+ },
+
+ /**
+ * Merge options for bubbleLegend labels.
+ *
+ * @private
+ * @function Highcharts.BubbleLegend#getLabelStyles
+ */
+ getLabelStyles: function () {
+ var options = this.options,
+ additionalLabelsStyle = {},
+ labelsOnLeft = options.labels.align === 'left',
+ rtl = this.legend.options.rtl;
+
+ // To separate additional style options
+ objectEach(options.labels.style, function (value, key) {
+ if (key !== 'color' && key !== 'fontSize' && key !== 'z-index') {
+ additionalLabelsStyle[key] = value;
+ }
+ });
+
+ return merge(false, additionalLabelsStyle, {
+ 'font-size': options.labels.style.fontSize,
+ fill: pick(
+ options.labels.style.color,
+ '#000000'
+ ),
+ 'z-index': options.zIndex,
+ align: rtl || labelsOnLeft ? 'right' : 'left'
+ });
+ },
+
+
+ /**
+ * Calculate radius for each bubble range,
+ * used code from BubbleSeries.js 'getRadius' method.
+ *
+ * @private
+ * @function Highcharts.BubbleLegend#getRangeRadius
+ *
+ * @param {number} value
+ * Range value
+ *
+ * @return {number}
+ * Radius for one range
+ */
+ getRangeRadius: function (value) {
+ var bubbleLegend = this,
+ options = bubbleLegend.options,
+ seriesIndex = bubbleLegend.options.seriesIndex,
+ bubbleSeries = bubbleLegend.chart.series[seriesIndex],
+ zMax = options.ranges[0].value,
+ zMin = options.ranges[options.ranges.length - 1].value,
+ minSize = options.minSize,
+ maxSize = options.maxSize;
+
+ return bubbleSeries.getRadius.call(
+ this,
+ zMin,
+ zMax,
+ minSize,
+ maxSize,
+ value
+ );
+ },
+
+ /**
+ * Render the legendSymbol group.
+ *
+ * @private
+ * @function Highcharts.BubbleLegend#render
+ */
+ render: function () {
+ var bubbleLegend = this,
+ renderer = bubbleLegend.chart.renderer,
+ zThreshold = bubbleLegend.options.zThreshold;
+
+
+ if (!bubbleLegend.symbols) {
+ bubbleLegend.symbols = {
+ connectors: [],
+ bubbleItems: [],
+ labels: []
+ };
+ }
+ // Nesting SVG groups to enable handleOverflow
+ bubbleLegend.legendSymbol = renderer.g('bubble-legend');
+ bubbleLegend.legendItem = renderer.g('bubble-legend-item');
+
+ // To enable default 'hideOverlappingLabels' method
+ bubbleLegend.legendSymbol.translateX = 0;
+ bubbleLegend.legendSymbol.translateY = 0;
+
+ bubbleLegend.ranges.forEach(function (range) {
+ if (range.value >= zThreshold) {
+ bubbleLegend.renderRange(range);
+ }
+ });
+ // To use handleOverflow method
+ bubbleLegend.legendSymbol.add(bubbleLegend.legendItem);
+ bubbleLegend.legendItem.add(bubbleLegend.legendGroup);
+
+ bubbleLegend.hideOverlappingLabels();
+ },
+
+ /**
+ * Render one range, consisting of bubble symbol, connector and label.
+ *
+ * @private
+ * @function Highcharts.BubbleLegend#renderRange
+ *
+ * @param {Highcharts.LegendBubbleLegendRangesOptions} config
+ * Range options
+ *
+ * @private
+ */
+ renderRange: function (range) {
+ var bubbleLegend = this,
+ mainRange = bubbleLegend.ranges[0],
+ legend = bubbleLegend.legend,
+ options = bubbleLegend.options,
+ labelsOptions = options.labels,
+ chart = bubbleLegend.chart,
+ renderer = chart.renderer,
+ symbols = bubbleLegend.symbols,
+ labels = symbols.labels,
+ label,
+ elementCenter = range.center,
+ absoluteRadius = Math.abs(range.radius),
+ connectorDistance = options.connectorDistance,
+ labelsAlign = labelsOptions.align,
+ rtl = legend.options.rtl,
+ fontSize = labelsOptions.style.fontSize,
+ connectorLength = rtl || labelsAlign === 'left' ?
+ -connectorDistance : connectorDistance,
+ borderWidth = options.borderWidth,
+ connectorWidth = options.connectorWidth,
+ posX = mainRange.radius,
+ posY = elementCenter - absoluteRadius - borderWidth / 2 +
+ connectorWidth / 2,
+ labelY,
+ labelX,
+ fontMetrics = bubbleLegend.fontMetrics,
+ labelMovement = fontSize / 2 - (fontMetrics.h - fontSize) / 2,
+ crispMovement = (posY % 1 ? 1 : 0.5) -
+ (connectorWidth % 2 ? 0 : 0.5),
+ styledMode = renderer.styledMode;
+
+ // Set options for centered labels
+ if (labelsAlign === 'center') {
+ connectorLength = 0; // do not use connector
+ options.connectorDistance = 0;
+ range.labelStyle.align = 'center';
+ }
+
+ labelY = posY + options.labels.y;
+ labelX = posX + connectorLength + options.labels.x;
+
+ // Render bubble symbol
+ symbols.bubbleItems.push(
+ renderer
+ .circle(
+ posX,
+ elementCenter + crispMovement,
+ absoluteRadius
+ )
+ .attr(
+ styledMode ? {} : range.bubbleStyle
+ )
+ .addClass(
+ (
+ styledMode ?
+ 'highcharts-color-' +
+ bubbleLegend.options.seriesIndex + ' ' :
+ ''
+ ) +
+ 'highcharts-bubble-legend-symbol ' +
+ (options.className || '')
+ ).add(
+ bubbleLegend.legendSymbol
+ )
+ );
+
+ // Render connector
+ symbols.connectors.push(
+ renderer
+ .path(renderer.crispLine(
+ ['M', posX, posY, 'L', posX + connectorLength, posY],
+ options.connectorWidth
+ ))
+ .attr(
+ styledMode ? {} : range.connectorStyle
+ )
+ .addClass(
+ (
+ styledMode ?
+ 'highcharts-color-' +
+ bubbleLegend.options.seriesIndex + ' ' :
+ ''
+ ) +
+ 'highcharts-bubble-legend-connectors ' +
+ (options.connectorClassName || '')
+ ).add(
+ bubbleLegend.legendSymbol
+ )
+ );
+
+ // Render label
+ label = renderer
+ .text(
+ bubbleLegend.formatLabel(range),
+ labelX,
+ labelY + labelMovement
+ )
+ .attr(
+ styledMode ? {} : range.labelStyle
+ )
+ .addClass(
+ 'highcharts-bubble-legend-labels ' +
+ (options.labels.className || '')
+ ).add(
+ bubbleLegend.legendSymbol
+ );
+
+ labels.push(label);
+ // To enable default 'hideOverlappingLabels' method
+ label.placed = true;
+ label.alignAttr = {
+ x: labelX,
+ y: labelY + labelMovement
+ };
+ },
+
+ /**
+ * Get the label which takes up the most space.
+ *
+ * @private
+ * @function Highcharts.BubbleLegend#getMaxLabelSize
+ */
+ getMaxLabelSize: function () {
+ var labels = this.symbols.labels,
+ maxLabel,
+ labelSize;
+
+ labels.forEach(function (label) {
+ labelSize = label.getBBox(true);
+
+ if (maxLabel) {
+ maxLabel = labelSize.width > maxLabel.width ?
+ labelSize : maxLabel;
+
+ } else {
+ maxLabel = labelSize;
+ }
+ });
+ return maxLabel || {};
+ },
+
+ /**
+ * Get formatted label for range.
+ *
+ * @private
+ * @function Highcharts.BubbleLegend#formatLabel
+ *
+ * @param {Highcharts.LegendBubbleLegendRangesOptions} range
+ * Range options
+ *
+ * @return {string}
+ * Range label text
+ */
+ formatLabel: function (range) {
+ var options = this.options,
+ formatter = options.labels.formatter,
+ format = options.labels.format;
+
+ return format ? H.format(format, range) :
+ formatter ? formatter.call(range) :
+ numberFormat(range.value, 1);
+ },
+
+ /**
+ * By using default chart 'hideOverlappingLabels' method, hide or show
+ * labels and connectors.
+ *
+ * @private
+ * @function Highcharts.BubbleLegend#hideOverlappingLabels
+ */
+ hideOverlappingLabels: function () {
+ var bubbleLegend = this,
+ chart = this.chart,
+ allowOverlap = bubbleLegend.options.labels.allowOverlap,
+ symbols = bubbleLegend.symbols;
+
+ if (!allowOverlap && symbols) {
+ chart.hideOverlappingLabels(symbols.labels);
+
+ // Hide or show connectors
+ symbols.labels.forEach(function (label, index) {
+ if (!label.newOpacity) {
+ symbols.connectors[index].hide();
+ } else if (label.newOpacity !== label.oldOpacity) {
+ symbols.connectors[index].show();
+ }
+ });
+ }
+ },
+
+ /**
+ * Calculate ranges from created series.
+ *
+ * @private
+ * @function Highcharts.BubbleLegend#getRanges
+ *
+ * @return {Array}
+ * Array of range objects
+ */
+ getRanges: function () {
+ var bubbleLegend = this.legend.bubbleLegend,
+ series = bubbleLegend.chart.series,
+ ranges,
+ rangesOptions = bubbleLegend.options.ranges,
+ zData,
+ minZ = Number.MAX_VALUE,
+ maxZ = -Number.MAX_VALUE;
+
+ series.forEach(function (s) {
+ // Find the min and max Z, like in bubble series
+ if (s.isBubble && !s.ignoreSeries) {
+ zData = s.zData.filter(isNumber);
+
+ if (zData.length) {
+ minZ = pick(s.options.zMin, Math.min(
+ minZ,
+ Math.max(
+ arrayMin(zData),
+ s.options.displayNegative === false ?
+ s.options.zThreshold :
+ -Number.MAX_VALUE
+ )
+ ));
+ maxZ = pick(
+ s.options.zMax,
+ Math.max(maxZ, arrayMax(zData))
+ );
+ }
+ }
+ });
+
+ // Set values for ranges
+ if (minZ === maxZ) {
+ // Only one range if min and max values are the same.
+ ranges = [{ value: maxZ }];
+ } else {
+ ranges = [
+ { value: minZ },
+ { value: (minZ + maxZ) / 2 },
+ { value: maxZ, autoRanges: true }
+ ];
+ }
+ // Prevent reverse order of ranges after redraw
+ if (rangesOptions.length && rangesOptions[0].radius) {
+ ranges.reverse();
+ }
+
+ // Merge ranges values with user options
+ ranges.forEach(function (range, i) {
+ if (rangesOptions && rangesOptions[i]) {
+ ranges[i] = merge(false, rangesOptions[i], range);
+ }
+ });
+
+ return ranges;
+ },
+
+ /**
+ * Calculate bubble legend sizes from rendered series.
+ *
+ * @private
+ * @function Highcharts.BubbleLegend#predictBubbleSizes
+ *
+ * @return {Array}
+ * Calculated min and max bubble sizes
+ */
+ predictBubbleSizes: function () {
+ var chart = this.chart,
+ fontMetrics = this.fontMetrics,
+ legendOptions = chart.legend.options,
+ floating = legendOptions.floating,
+ horizontal = legendOptions.layout === 'horizontal',
+ lastLineHeight = horizontal ? chart.legend.lastLineHeight : 0,
+ plotSizeX = chart.plotSizeX,
+ plotSizeY = chart.plotSizeY,
+ bubbleSeries = chart.series[this.options.seriesIndex],
+ minSize = Math.ceil(bubbleSeries.minPxSize),
+ maxPxSize = Math.ceil(bubbleSeries.maxPxSize),
+ maxSize = bubbleSeries.options.maxSize,
+ plotSize = Math.min(plotSizeY, plotSizeX),
+ calculatedSize;
+
+ // Calculate prediceted max size of bubble
+ if (floating || !(/%$/.test(maxSize))) {
+ calculatedSize = maxPxSize;
+
+ } else {
+ maxSize = parseFloat(maxSize);
+
+ calculatedSize = ((plotSize + lastLineHeight - fontMetrics.h / 2) *
+ maxSize / 100) / (maxSize / 100 + 1);
+
+ // Get maxPxSize from bubble series if calculated bubble legend
+ // size will not affect to bubbles series.
+ if (
+ (horizontal && plotSizeY - calculatedSize >=
+ plotSizeX) || (!horizontal && plotSizeX -
+ calculatedSize >= plotSizeY)
+ ) {
+ calculatedSize = maxPxSize;
+ }
+ }
+
+ return [minSize, Math.ceil(calculatedSize)];
+ },
+
+ /**
+ * Correct ranges with calculated sizes.
+ *
+ * @private
+ * @function Highcharts.BubbleLegend#updateRanges
+ *
+ * @param {number} min
+ *
+ * @param {number} max
+ */
+ updateRanges: function (min, max) {
+ var bubbleLegendOptions = this.legend.options.bubbleLegend;
+
+ bubbleLegendOptions.minSize = min;
+ bubbleLegendOptions.maxSize = max;
+ bubbleLegendOptions.ranges = this.getRanges();
+ },
+
+ /**
+ * Because of the possibility of creating another legend line, predicted
+ * bubble legend sizes may differ by a few pixels, so it is necessary to
+ * correct them.
+ *
+ * @private
+ * @function Highcharts.BubbleLegend#correctSizes
+ */
+ correctSizes: function () {
+ var legend = this.legend,
+ chart = this.chart,
+ bubbleSeries = chart.series[this.options.seriesIndex],
+ bubbleSeriesSize = bubbleSeries.maxPxSize,
+ bubbleLegendSize = this.options.maxSize;
+
+ if (Math.abs(Math.ceil(bubbleSeriesSize) - bubbleLegendSize) > 1) {
+ this.updateRanges(this.options.minSize, bubbleSeries.maxPxSize);
+ legend.render();
+ }
+ }
+ };
+
+ // Start the bubble legend creation process.
+ addEvent(H.Legend, 'afterGetAllItems', function (e) {
+ var legend = this,
+ bubbleLegend = legend.bubbleLegend,
+ legendOptions = legend.options,
+ options = legendOptions.bubbleLegend,
+ bubbleSeriesIndex = legend.chart.getVisibleBubbleSeriesIndex();
+
+ // Remove unnecessary element
+ if (bubbleLegend && bubbleLegend.ranges && bubbleLegend.ranges.length) {
+ // Allow change the way of calculating ranges in update
+ if (options.ranges.length) {
+ options.autoRanges = !!options.ranges[0].autoRanges;
+ }
+ // Update bubbleLegend dimensions in each redraw
+ legend.destroyItem(bubbleLegend);
+ }
+ // Create bubble legend
+ if (bubbleSeriesIndex >= 0 &&
+ legendOptions.enabled &&
+ options.enabled
+ ) {
+ options.seriesIndex = bubbleSeriesIndex;
+ legend.bubbleLegend = new H.BubbleLegend(options, legend);
+ legend.bubbleLegend.addToLegend(e.allItems);
+ }
+ });
+
+ /**
+ * Check if there is at least one visible bubble series.
+ *
+ * @private
+ * @function Highcharts.Chart#getVisibleBubbleSeriesIndex
+ *
+ * @return {number}
+ * First visible bubble series index
+ */
+ Chart.prototype.getVisibleBubbleSeriesIndex = function () {
+ var series = this.series,
+ i = 0;
+
+ while (i < series.length) {
+ if (
+ series[i] &&
+ series[i].isBubble &&
+ series[i].visible &&
+ series[i].zData.length
+ ) {
+ return i;
+ }
+ i++;
+ }
+ return -1;
+ };
+
+ /**
+ * Calculate height for each row in legend.
+ *
+ * @private
+ * @function Highcharts.Legend#getLinesHeights
+ *
+ * @return {Array}
+ * Informations about line height and items amount
+ */
+ Legend.prototype.getLinesHeights = function () {
+ var items = this.allItems,
+ lines = [],
+ lastLine,
+ length = items.length,
+ i = 0,
+ j = 0;
+
+ for (i = 0; i < length; i++) {
+ if (items[i].legendItemHeight) {
+ // for bubbleLegend
+ items[i].itemHeight = items[i].legendItemHeight;
+ }
+ if ( // Line break
+ items[i] === items[length - 1] ||
+ items[i + 1] &&
+ items[i]._legendItemPos[1] !==
+ items[i + 1]._legendItemPos[1]
+ ) {
+ lines.push({ height: 0 });
+ lastLine = lines[lines.length - 1];
+ // Find the highest item in line
+ for (j; j <= i; j++) {
+ if (items[j].itemHeight > lastLine.height) {
+ lastLine.height = items[j].itemHeight;
+ }
+ }
+ lastLine.step = i;
+ }
+ }
+ return lines;
+ };
+
+ /**
+ * Correct legend items translation in case of different elements heights.
+ *
+ * @private
+ * @function Highcharts.Legend#retranslateItems
+ *
+ * @param {Array} lines
+ * Informations about line height and items amount
+ */
+ Legend.prototype.retranslateItems = function (lines) {
+ var items = this.allItems,
+ orgTranslateX,
+ orgTranslateY,
+ movementX,
+ rtl = this.options.rtl,
+ actualLine = 0;
+
+ items.forEach(function (item, index) {
+ orgTranslateX = item.legendGroup.translateX;
+ orgTranslateY = item._legendItemPos[1];
+
+ movementX = item.movementX;
+
+ if (movementX || (rtl && item.ranges)) {
+ movementX = rtl ? orgTranslateX - item.options.maxSize / 2 :
+ orgTranslateX + movementX;
+
+ item.legendGroup.attr({ translateX: movementX });
+ }
+ if (index > lines[actualLine].step) {
+ actualLine++;
+ }
+
+ item.legendGroup.attr({
+ translateY: Math.round(
+ orgTranslateY + lines[actualLine].height / 2
+ )
+ });
+ item._legendItemPos[1] = orgTranslateY + lines[actualLine].height / 2;
+ });
+ };
+
+ // Hide or show bubble legend depending on the visible status of bubble series.
+ addEvent(Series, 'legendItemClick', function () {
+ var series = this,
+ chart = series.chart,
+ visible = series.visible,
+ legend = series.chart.legend,
+ status;
+
+ if (legend && legend.bubbleLegend) {
+ // Visible property is not set correctly yet, so temporary correct it
+ series.visible = !visible;
+ // Save future status for getRanges method
+ series.ignoreSeries = visible;
+ // Check if at lest one bubble series is visible
+ status = chart.getVisibleBubbleSeriesIndex() >= 0;
+
+ // Hide bubble legend if all bubble series are disabled
+ if (legend.bubbleLegend.visible !== status) {
+ // Show or hide bubble legend
+ legend.update({
+ bubbleLegend: { enabled: status }
+ });
+
+ legend.bubbleLegend.visible = status; // Restore default status
+ }
+ series.visible = visible;
+ }
+ });
+
+ // If ranges are not specified, determine ranges from rendered bubble series and
+ // render legend again.
+ wrap(Chart.prototype, 'drawChartBox', function (proceed, options, callback) {
+ var chart = this,
+ legend = chart.legend,
+ bubbleSeries = chart.getVisibleBubbleSeriesIndex() >= 0,
+ bubbleLegendOptions,
+ bubbleSizes;
+
+ if (
+ legend && legend.options.enabled && legend.bubbleLegend &&
+ legend.options.bubbleLegend.autoRanges && bubbleSeries
+ ) {
+ bubbleLegendOptions = legend.bubbleLegend.options;
+ bubbleSizes = legend.bubbleLegend.predictBubbleSizes();
+
+ legend.bubbleLegend.updateRanges(bubbleSizes[0], bubbleSizes[1]);
+ // Disable animation on init
+ if (!bubbleLegendOptions.placed) {
+ legend.group.placed = false;
+
+ legend.allItems.forEach(function (item) {
+ item.legendGroup.translateY = null;
+ });
+ }
+
+ // Create legend with bubbleLegend
+ legend.render();
+
+ chart.getMargins();
+
+ chart.axes.forEach(function (axis) {
+ axis.render();
+
+ if (!bubbleLegendOptions.placed) {
+ axis.setScale();
+ axis.updateNames();
+ // Disable axis animation on init
+ objectEach(axis.ticks, function (tick) {
+ tick.isNew = true;
+ tick.isNewLabel = true;
+ });
+ }
+ });
+ bubbleLegendOptions.placed = true;
+
+ // After recalculate axes, calculate margins again.
+ chart.getMargins();
+
+ // Call default 'drawChartBox' method.
+ proceed.call(chart, options, callback);
+
+ // Check bubble legend sizes and correct them if necessary.
+ legend.bubbleLegend.correctSizes();
+
+ // Correct items positions with different dimensions in legend.
+ legend.retranslateItems(legend.getLinesHeights());
+
+ } else {
+ proceed.call(chart, options, callback);
+
+ if (legend && legend.options.enabled && legend.bubbleLegend) {
+ // Allow color change after click in legend on static bubble legend
+ legend.render();
+ legend.retranslateItems(legend.getLinesHeights());
+ }
+ }
+ });
+
+ }(Highcharts));
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+
+
+
+ var arrayMax = H.arrayMax,
+ arrayMin = H.arrayMin,
+ Axis = H.Axis,
+ color = H.color,
+ isNumber = H.isNumber,
+ noop = H.noop,
+ pick = H.pick,
+ pInt = H.pInt,
+ Point = H.Point,
+ Series = H.Series,
+ seriesType = H.seriesType,
+ seriesTypes = H.seriesTypes;
+
+
+ /**
+ * A bubble series is a three dimensional series type where each point renders
+ * an X, Y and Z value. Each points is drawn as a bubble where the position
+ * along the X and Y axes mark the X and Y values, and the size of the bubble
+ * relates to the Z value. Requires `highcharts-more.js`.
+ *
+ * @sample {highcharts} highcharts/demo/bubble/
+ * Bubble chart
+ *
+ * @extends plotOptions.scatter
+ * @product highcharts highstock
+ * @optionparent plotOptions.bubble
+ */
+ seriesType('bubble', 'scatter', {
+
+ dataLabels: {
+ formatter: function () { // #2945
+ return this.point.z;
+ },
+ inside: true,
+ verticalAlign: 'middle'
+ },
+
+ /**
+ * If there are more points in the series than the `animationLimit`, the
+ * animation won't run. Animation affects overall performance and doesn't
+ * work well with heavy data series.
+ *
+ * @since 6.1.0
+ */
+ animationLimit: 250,
+
+ /**
+ * Whether to display negative sized bubbles. The threshold is given
+ * by the [zThreshold](#plotOptions.bubble.zThreshold) option, and negative
+ * bubbles can be visualized by setting
+ * [negativeColor](#plotOptions.bubble.negativeColor).
+ *
+ * @sample {highcharts} highcharts/plotoptions/bubble-negative/
+ * Negative bubbles
+ *
+ * @type {boolean}
+ * @default true
+ * @since 3.0
+ * @apioption plotOptions.bubble.displayNegative
+ */
+
+ /**
+ * @extends plotOptions.series.marker
+ * @excluding enabled, enabledThreshold, height, radius, width
*/
marker: {
lineColor: null, // inherit from series.color
+
lineWidth: 1,
+
/**
* The fill opacity of the bubble markers.
- * @type {Number}
- * @default 0.5
- * @product highcharts
*/
+ fillOpacity: 0.5,
/**
- * In bubble charts, the radius is overridden and determined based on
+ * In bubble charts, the radius is overridden and determined based on
* the point's data value.
+ *
+ * @ignore
*/
radius: null,
+
states: {
hover: {
radiusPlus: 0
@@ -4127,26 +6063,26 @@
/**
* A predefined shape or symbol for the marker. Possible values are
* "circle", "square", "diamond", "triangle" and "triangle-down".
- *
+ *
* Additionally, the URL to a graphic can be given on the form
* `url(graphic.png)`. Note that for the image to be applied to exported
* charts, its URL needs to be accessible by the export server.
- *
+ *
* Custom callbacks for symbol path generation can also be added to
* `Highcharts.SVGRenderer.prototype.symbols`. The callback is then
* used by its method name, as shown in the demo.
- *
- * @validvalue ["circle", "square", "diamond", "triangle", "triangle-down"]
- * @type {String}
- * @sample {highcharts} highcharts/plotoptions/bubble-symbol/
- * Bubble chart with various symbols
- * @sample {highcharts} highcharts/plotoptions/series-marker-symbol/
- * General chart with predefined, graphic and custom markers
- * @default circle
- * @since 5.0.11
- * @product highcharts
+ *
+ * @sample {highcharts} highcharts/plotoptions/bubble-symbol/
+ * Bubble chart with various symbols
+ * @sample {highcharts} highcharts/plotoptions/series-marker-symbol/
+ * General chart with predefined, graphic and custom markers
+ *
+ * @since 5.0.11
+ * @validvalue ["circle", "square", "diamond", "triangle",
+ * "triangle-down"]
*/
symbol: 'circle'
+
},
/**
@@ -4154,12 +6090,13 @@
* `minSize` and `maxSize` to reflect the `z` value of each bubble.
* Can be either pixels (when no unit is given), or a percentage of
* the smallest one of the plot width and height.
- *
- * @type {String}
- * @sample {highcharts} highcharts/plotoptions/bubble-size/ Bubble size
- * @default 8
- * @since 3.0
- * @product highcharts
+ *
+ * @sample {highcharts} highcharts/plotoptions/bubble-size/
+ * Bubble size
+ *
+ * @type {number|string}
+ * @since 3.0
+ * @product highcharts highstock
*/
minSize: 8,
@@ -4168,25 +6105,26 @@
* `minSize` and `maxSize` to reflect the `z` value of each bubble.
* Can be either pixels (when no unit is given), or a percentage of
* the smallest one of the plot width and height.
- *
- * @type {String}
- * @sample {highcharts} highcharts/plotoptions/bubble-size/ Bubble size
- * @default 20%
- * @since 3.0
- * @product highcharts
+ *
+ * @sample {highcharts} highcharts/plotoptions/bubble-size/
+ * Bubble size
+ *
+ * @type {number|string}
+ * @since 3.0
+ * @product highcharts highstock
*/
maxSize: '20%',
/**
- * When a point's Z value is below the [zThreshold](#plotOptions.bubble.
- * zThreshold) setting, this color is used.
- *
- * @type {Color}
+ * When a point's Z value is below the
+ * [zThreshold](#plotOptions.bubble.zThreshold) setting, this color is used.
+ *
* @sample {highcharts} highcharts/plotoptions/bubble-negative/
* Negative bubbles
- * @default null
- * @since 3.0
- * @product highcharts
+ *
+ * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
+ * @since 3.0
+ * @product highcharts
* @apioption plotOptions.bubble.negativeColor
*/
@@ -4194,15 +6132,15 @@
* Whether the bubble's value should be represented by the area or the
* width of the bubble. The default, `area`, corresponds best to the
* human perception of the size of each bubble.
- *
- * @validvalue ["area", "width"]
- * @type {String}
+ *
* @sample {highcharts} highcharts/plotoptions/bubble-sizeby/
* Comparison of area and size
- * @default area
- * @since 3.0.7
- * @product highcharts
- * @apioption plotOptions.bubble.sizeBy
+ *
+ * @type {string}
+ * @default area
+ * @since 3.0.7
+ * @validvalue ["area", "width"]
+ * @apioption plotOptions.bubble.sizeBy
*/
/**
@@ -4211,13 +6149,14 @@
* bubble of value -1 will have the same size as a bubble of value 1,
* while a bubble of value 0 will have a smaller size according to
* `minSize`.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/bubble-sizebyabsolutevalue/
- * Size by absolute value, various thresholds
- * @default false
- * @since 4.1.9
- * @product highcharts
+ *
+ * @sample {highcharts} highcharts/plotoptions/bubble-sizebyabsolutevalue/
+ * Size by absolute value, various thresholds
+ *
+ * @type {boolean}
+ * @default false
+ * @since 4.1.9
+ * @product highcharts
* @apioption plotOptions.bubble.sizeByAbsoluteValue
*/
@@ -4225,14 +6164,12 @@
* When this is true, the series will not cause the Y axis to cross
* the zero plane (or [threshold](#plotOptions.series.threshold) option)
* unless the data actually crosses the plane.
- *
+ *
* For example, if `softThreshold` is `false`, a series of 0, 1, 2,
* 3 will make the Y axis show negative values according to the `minPadding`
* option. If `softThreshold` is `true`, the Y axis starts at 0.
- *
- * @type {Boolean}
- * @default false
- * @since 4.1.9
+ *
+ * @since 4.1.9
* @product highcharts
*/
softThreshold: false,
@@ -4251,52 +6188,53 @@
turboThreshold: 0,
- /**
- * When [displayNegative](#plotOptions.bubble.displayNegative) is `false`,
- * bubbles with lower Z values are skipped. When `displayNegative`
- * is `true` and a [negativeColor](#plotOptions.bubble.negativeColor)
- * is given, points with lower Z is colored.
- *
- * @type {Number}
- * @sample {highcharts} highcharts/plotoptions/bubble-negative/
- * Negative bubbles
- * @default 0
- * @since 3.0
- * @product highcharts
- */
- zThreshold: 0,
-
- zoneAxis: 'z'
-
/**
* The minimum for the Z value range. Defaults to the highest Z value
* in the data.
- *
- * @type {Number}
- * @see [zMax](#plotOptions.bubble.zMin)
+ *
+ * @see [zMin](#plotOptions.bubble.zMin)
+ *
* @sample {highcharts} highcharts/plotoptions/bubble-zmin-zmax/
* Z has a possible range of 0-100
- * @default null
- * @since 4.0.3
- * @product highcharts
+ *
+ * @type {number}
+ * @since 4.0.3
+ * @product highcharts
* @apioption plotOptions.bubble.zMax
*/
/**
* The minimum for the Z value range. Defaults to the lowest Z value
* in the data.
- *
- * @type {Number}
+ *
* @see [zMax](#plotOptions.bubble.zMax)
+ *
* @sample {highcharts} highcharts/plotoptions/bubble-zmin-zmax/
* Z has a possible range of 0-100
- * @default null
- * @since 4.0.3
- * @product highcharts
+ *
+ * @type {number}
+ * @since 4.0.3
+ * @product highcharts
* @apioption plotOptions.bubble.zMin
*/
- // Prototype members
+ /**
+ * When [displayNegative](#plotOptions.bubble.displayNegative) is `false`,
+ * bubbles with lower Z values are skipped. When `displayNegative`
+ * is `true` and a [negativeColor](#plotOptions.bubble.negativeColor)
+ * is given, points with lower Z is colored.
+ *
+ * @sample {highcharts} highcharts/plotoptions/bubble-negative/
+ * Negative bubbles
+ *
+ * @since 3.0
+ * @product highcharts
+ */
+ zThreshold: 0,
+
+ zoneAxis: 'z'
+
+ // Prototype members
}, {
pointArrayMap: ['y', 'z'],
parallelArrays: ['x', 'y', 'z'],
@@ -4305,11 +6243,11 @@
bubblePadding: true,
zoneAxis: 'z',
directTouch: true,
+ isBubble: true,
-
- pointAttribs: function(point, state) {
+ pointAttribs: function (point, state) {
var markerOptions = this.options.marker,
- fillOpacity = pick(markerOptions.fillOpacity, 0.5),
+ fillOpacity = markerOptions.fillOpacity,
attr = Series.prototype.pointAttribs.call(this, point, state);
if (fillOpacity !== 1) {
@@ -4319,65 +6257,72 @@
return attr;
},
-
- /**
- * Get the radius for each point based on the minSize, maxSize and each point's Z value. This
- * must be done prior to Series.translate because the axis needs to add padding in
- * accordance with the point sizes.
- */
- getRadii: function(zMin, zMax, minSize, maxSize) {
+ // Get the radius for each point based on the minSize, maxSize and each
+ // point's Z value. This must be done prior to Series.translate because
+ // the axis needs to add padding in accordance with the point sizes.
+ getRadii: function (zMin, zMax, series) {
var len,
i,
- pos,
zData = this.zData,
+ minSize = series.minPxSize,
+ maxSize = series.maxPxSize,
radii = [],
- options = this.options,
- sizeByArea = options.sizeBy !== 'width',
- zThreshold = options.zThreshold,
- zRange = zMax - zMin,
- value,
- radius;
+ value;
// Set the shape type and arguments to be picked up in drawPoints
for (i = 0, len = zData.length; i < len; i++) {
-
value = zData[i];
+ // Separate method to get individual radius for bubbleLegend
+ radii.push(this.getRadius(zMin, zMax, minSize, maxSize, value));
+ }
+ this.radii = radii;
+ },
- // When sizing by threshold, the absolute value of z determines the size
- // of the bubble.
- if (options.sizeByAbsoluteValue && value !== null) {
- value = Math.abs(value - zThreshold);
- zMax = Math.max(zMax - zThreshold, Math.abs(zMin - zThreshold));
- zMin = 0;
- }
+ // Get the individual radius for one point.
+ getRadius: function (zMin, zMax, minSize, maxSize, value) {
+ var options = this.options,
+ sizeByArea = options.sizeBy !== 'width',
+ zThreshold = options.zThreshold,
+ pos,
+ zRange = zMax - zMin,
+ radius;
- if (value === null) {
- radius = null;
- // Issue #4419 - if value is less than zMin, push a radius that's always smaller than the minimum size
- } else if (value < zMin) {
- radius = minSize / 2 - 1;
- } else {
- // Relative size, a number between 0 and 1
- pos = zRange > 0 ? (value - zMin) / zRange : 0.5;
+ // When sizing by threshold, the absolute value of z determines
+ // the size of the bubble.
+ if (options.sizeByAbsoluteValue && value !== null) {
+ value = Math.abs(value - zThreshold);
+ zMax = zRange = Math.max(
+ zMax - zThreshold,
+ Math.abs(zMin - zThreshold)
+ );
+ zMin = 0;
+ }
- if (sizeByArea && pos >= 0) {
- pos = Math.sqrt(pos);
- }
- radius = Math.ceil(minSize + pos * (maxSize - minSize)) / 2;
+ if (!isNumber(value)) {
+ radius = null;
+ // Issue #4419 - if value is less than zMin, push a radius that's
+ // always smaller than the minimum size
+ } else if (value < zMin) {
+ radius = minSize / 2 - 1;
+ } else {
+ // Relative size, a number between 0 and 1
+ pos = zRange > 0 ? (value - zMin) / zRange : 0.5;
+
+ if (sizeByArea && pos >= 0) {
+ pos = Math.sqrt(pos);
}
- radii.push(radius);
+ radius = Math.ceil(minSize + pos * (maxSize - minSize)) / 2;
}
- this.radii = radii;
+ return radius;
},
- /**
- * Perform animation on the bubbles
- */
- animate: function(init) {
- var animation = this.options.animation;
-
- if (!init) { // run the animation
- each(this.points, function(point) {
+ // Perform animation on the bubbles
+ animate: function (init) {
+ if (
+ !init &&
+ this.points.length < this.options.animationLimit // #8099
+ ) {
+ this.points.forEach(function (point) {
var graphic = point.graphic,
animationTarget;
@@ -4398,19 +6343,17 @@
});
// Run animation
- graphic.animate(animationTarget, animation);
+ graphic.animate(animationTarget, this.options.animation);
}
- });
+ }, this);
// delete this function to allow it only once
this.animate = null;
}
},
- /**
- * Extend the base translate method to handle bubble size
- */
- translate: function() {
+ // Extend the base translate method to handle bubble size
+ translate: function () {
var i,
data = this.data,
@@ -4444,7 +6387,8 @@
height: 2 * radius
};
} else { // below zThreshold
- point.shapeArgs = point.plotY = point.dlBox = undefined; // #1691
+ // #1691
+ point.shapeArgs = point.plotY = point.dlBox = undefined;
}
}
},
@@ -4453,22 +6397,21 @@
buildKDTree: noop,
applyZones: noop
- // Point class
+ // Point class
}, {
- haloPath: function(size) {
+ haloPath: function (size) {
return Point.prototype.haloPath.call(
this,
- size === 0 ? 0 : (this.marker ? this.marker.radius || 0 : 0) + size // #6067
+ // #6067
+ size === 0 ? 0 : (this.marker ? this.marker.radius || 0 : 0) + size
);
},
ttBelow: false
});
- /**
- * Add logic to pad each axis with the amount of pixels
- * necessary to avoid the bubbles to overflow.
- */
- Axis.prototype.beforePadding = function() {
+ // Add logic to pad each axis with the amount of pixels necessary to avoid the
+ // bubbles to overflow.
+ Axis.prototype.beforePadding = function () {
var axis = this,
axisLength = this.len,
chart = this.chart,
@@ -4486,12 +6429,15 @@
activeSeries = [];
// Handle padding on the second pass, or on redraw
- each(this.series, function(series) {
+ this.series.forEach(function (series) {
var seriesOptions = series.options,
zData;
- if (series.bubblePadding && (series.visible || !chart.options.chart.ignoreHiddenSeries)) {
+ if (
+ series.bubblePadding &&
+ (series.visible || !chart.options.chart.ignoreHiddenSeries)
+ ) {
// Correction for #1673
axis.allowZoomOutside = true;
@@ -4502,7 +6448,7 @@
if (isXAxis) { // because X axis is evaluated first
// For each series, translate the size extremes to pixel values
- each(['minSize', 'maxSize'], function(prop) {
+ ['minSize', 'maxSize'].forEach(function (prop) {
var length = seriesOptions[prop],
isPercent = /%$/.test(length);
@@ -4518,53 +6464,72 @@
series.maxPxSize = Math.max(extremes.maxSize, extremes.minSize);
// Find the min and max Z
- zData = series.zData;
+ zData = series.zData.filter(H.isNumber);
if (zData.length) { // #1735
zMin = pick(seriesOptions.zMin, Math.min(
zMin,
Math.max(
arrayMin(zData),
- seriesOptions.displayNegative === false ? seriesOptions.zThreshold : -Number.MAX_VALUE
+ seriesOptions.displayNegative === false ?
+ seriesOptions.zThreshold :
+ -Number.MAX_VALUE
)
));
- zMax = pick(seriesOptions.zMax, Math.max(zMax, arrayMax(zData)));
+ zMax = pick(
+ seriesOptions.zMax,
+ Math.max(zMax, arrayMax(zData))
+ );
}
}
}
});
- each(activeSeries, function(series) {
+ activeSeries.forEach(function (series) {
var data = series[dataKey],
i = data.length,
radius;
if (isXAxis) {
- series.getRadii(zMin, zMax, series.minPxSize, series.maxPxSize);
+ series.getRadii(zMin, zMax, series);
}
if (range > 0) {
while (i--) {
- if (isNumber(data[i]) && axis.dataMin <= data[i] && data[i] <= axis.dataMax) {
+ if (
+ isNumber(data[i]) &&
+ axis.dataMin <= data[i] &&
+ data[i] <= axis.dataMax
+ ) {
radius = series.radii[i];
- pxMin = Math.min(((data[i] - min) * transA) - radius, pxMin);
- pxMax = Math.max(((data[i] - min) * transA) + radius, pxMax);
+ pxMin = Math.min(
+ ((data[i] - min) * transA) - radius,
+ pxMin
+ );
+ pxMax = Math.max(
+ ((data[i] - min) * transA) + radius,
+ pxMax
+ );
}
}
}
});
+ // Apply the padding to the min and max properties
if (activeSeries.length && range > 0 && !this.isLog) {
pxMax -= axisLength;
- transA *= (axisLength + pxMin - pxMax) / axisLength;
- each([
- ['min', 'userMin', pxMin],
- ['max', 'userMax', pxMax]
- ], function(keys) {
- if (pick(axis.options[keys[0]], axis[keys[1]]) === undefined) {
- axis[keys[0]] += keys[2] / transA;
+ transA *= (
+ axisLength +
+ Math.max(0, pxMin) - // #8901
+ Math.min(pxMax, axisLength)
+ ) / axisLength;
+ [['min', 'userMin', pxMin], ['max', 'userMax', pxMax]].forEach(
+ function (keys) {
+ if (pick(axis.options[keys[0]], axis[keys[1]]) === undefined) {
+ axis[keys[0]] += keys[2] / transA;
+ }
}
- });
+ );
}
};
@@ -4572,98 +6537,661 @@
/**
* A `bubble` series. If the [type](#series.bubble.type) option is
* not specified, it is inherited from [chart.type](#chart.type).
- *
- * For options that apply to multiple series, it is recommended to add
- * them to the [plotOptions.series](#plotOptions.series) options structure.
- * To apply to all series of this specific type, apply it to [plotOptions.
- * bubble](#plotOptions.bubble).
- *
- * @type {Object}
- * @extends series,plotOptions.bubble
- * @excluding dataParser,dataURL,stack
- * @product highcharts
+ *
+ * @extends series,plotOptions.bubble
+ * @excluding dataParser, dataURL, stack
+ * @product highcharts highstock
* @apioption series.bubble
*/
/**
* An array of data points for the series. For the `bubble` series type,
* points can be given in the following ways:
- *
- * 1. An array of arrays with 3 or 2 values. In this case, the values
- * correspond to `x,y,z`. If the first value is a string, it is applied
- * as the name of the point, and the `x` value is inferred. The `x`
- * value can also be omitted, in which case the inner arrays should
- * be of length 2\. Then the `x` value is automatically calculated,
- * either starting at 0 and incremented by 1, or from `pointStart` and
- * `pointInterval` given in the series options.
- *
- * ```js
- * data: [
- * [0, 1, 2],
- * [1, 5, 5],
- * [2, 0, 2]
- * ]
- * ```
- *
- * 2. An array of objects with named values. The objects are point
- * configuration objects as seen below. If the total number of data
- * points exceeds the series' [turboThreshold](#series.bubble.turboThreshold),
- * this option is not available.
- *
- * ```js
- * data: [{
- * x: 1,
- * y: 1,
- * z: 1,
- * name: "Point2",
- * color: "#00FF00"
- * }, {
- * x: 1,
- * y: 5,
- * z: 4,
- * name: "Point1",
- * color: "#FF00FF"
- * }]
- * ```
- *
- * @type {Array}
- * @extends series.line.data
+ *
+ * 1. An array of arrays with 3 or 2 values. In this case, the values correspond
+ * to `x,y,z`. If the first value is a string, it is applied as the name of
+ * the point, and the `x` value is inferred. The `x` value can also be
+ * omitted, in which case the inner arrays should be of length 2\. Then the
+ * `x` value is automatically calculated, either starting at 0 and
+ * incremented by 1, or from `pointStart` and `pointInterval` given in the
+ * series options.
+ * ```js
+ * data: [
+ * [0, 1, 2],
+ * [1, 5, 5],
+ * [2, 0, 2]
+ * ]
+ * ```
+ *
+ * 2. An array of objects with named values. The following snippet shows only a
+ * few settings, see the complete options set below. If the total number of
+ * data points exceeds the series'
+ * [turboThreshold](#series.bubble.turboThreshold), this option is not
+ * available.
+ * ```js
+ * data: [{
+ * x: 1,
+ * y: 1,
+ * z: 1,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * y: 5,
+ * z: 4,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/
+ * Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
+ * Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/
+ * Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
+ *
+ * @type {Array|Array<(number|string),number,number>|*>}
+ * @extends series.line.data
* @excluding marker
- * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
- * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
- * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
- * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
- * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
- * @product highcharts
+ * @product highcharts
* @apioption series.bubble.data
*/
/**
- * The size value for each bubble. The bubbles' diameters are computed
- * based on the `z`, and controlled by series options like `minSize`,
- * `maxSize`, `sizeBy`, `zMin` and `zMax`.
- *
- * @type {Number}
- * @product highcharts
- * @apioption series.bubble.data.z
+ * The size value for each bubble. The bubbles' diameters are computed
+ * based on the `z`, and controlled by series options like `minSize`,
+ * `maxSize`, `sizeBy`, `zMin` and `zMax`.
+ *
+ * @type {number}
+ * @product highcharts
+ * @apioption series.bubble.data.z
+ */
+
+ /**
+ * @excluding enabled, enabledThreshold, height, radius, width
+ * @apioption series.bubble.marker
+ */
+
+ }(Highcharts));
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Grzegorz Blachlinski, Sebastian Bochan
+ *
+ * License: www.highcharts.com/license
+ */
+
+
+
+
+ var seriesType = H.seriesType,
+ defined = H.defined;
+
+ /**
+ * Packed bubble series
+ *
+ * @private
+ * @class
+ * @name Highcharts.seriesTypes.packedbubble
+ *
+ * @augments Highcharts.Series
+ *
+ * @requires modules:highcharts-more
+ */
+ seriesType(
+ 'packedbubble',
+ 'bubble',
+
+ /**
+ * A packed bubble series is a two dimensional series type, where each point
+ * renders a value in X, Y position. Each point is drawn as a bubble where
+ * the bubbles don't overlap with each other and the radius of the bubble
+ * related to the value. Requires `highcharts-more.js`.
+ *
+ * @sample {highcharts} highcharts/demo/packed-bubble/
+ * Packed-bubble chart
+ *
+ * @extends plotOptions.bubble
+ * @since 7.0.0
+ * @product highcharts
+ * @excluding connectEnds, connectNulls, jitter, keys,
+ * sizeByAbsoluteValue, step, zMax, zMin
+ * @optionparent plotOptions.packedbubble
+ */
+ {
+ /**
+ * Minimum bubble size. Bubbles will automatically size between the
+ * `minSize` and `maxSize` to reflect the value of each bubble.
+ * Can be either pixels (when no unit is given), or a percentage of
+ * the smallest one of the plot width and height.
+ *
+ * @sample {highcharts} highcharts/plotoptions/bubble-size/
+ * Bubble size
+ *
+ * @type {number|string}
+ */
+ minSize: '10%',
+ /**
+ * Maximum bubble size. Bubbles will automatically size between the
+ * `minSize` and `maxSize` to reflect the value of each bubble.
+ * Can be either pixels (when no unit is given), or a percentage of
+ * the smallest one of the plot width and height.
+ *
+ * @sample {highcharts} highcharts/plotoptions/bubble-size/
+ * Bubble size
+ *
+ * @type {number|string}
+ */
+ maxSize: '100%',
+ sizeBy: 'radius',
+ zoneAxis: 'y',
+ tooltip: {
+ pointFormat: 'Value: {point.value}'
+ }
+ }, {
+ pointArrayMap: ['value'],
+ pointValKey: 'value',
+ isCartesian: false,
+ axisTypes: [],
+ /**
+ * Create a single array of all points from all series
+ * @private
+ * @param {Array} Array of all series objects
+ * @return {Array} Returns the array of all points.
+ */
+ accumulateAllPoints: function (series) {
+
+ var chart = series.chart,
+ allDataPoints = [],
+ i, j;
+
+ for (i = 0; i < chart.series.length; i++) {
+
+ series = chart.series[i];
+
+ if (series.visible || !chart.options.chart.ignoreHiddenSeries) {
+
+ // add data to array only if series is visible
+ for (j = 0; j < series.yData.length; j++) {
+ allDataPoints.push([
+ null, null,
+ series.yData[j],
+ series.index,
+ j
+ ]);
+ }
+ }
+ }
+
+ return allDataPoints;
+ },
+ // Extend the base translate method to handle bubble size, and correct
+ // positioning them.
+ translate: function () {
+
+ var positions, // calculated positions of bubbles in bubble array
+ series = this,
+ chart = series.chart,
+ data = series.data,
+ index = series.index,
+ point,
+ radius,
+ i;
+
+ this.processedXData = this.xData;
+ this.generatePoints();
+
+ // merged data is an array with all of the data from all series
+ if (!defined(chart.allDataPoints)) {
+ chart.allDataPoints = series.accumulateAllPoints(series);
+
+ // calculate radius for all added data
+ series.getPointRadius();
+ }
+
+ // after getting initial radius, calculate bubble positions
+ positions = this.placeBubbles(chart.allDataPoints);
+
+ // Set the shape type and arguments to be picked up in drawPoints
+ for (i = 0; i < positions.length; i++) {
+
+ if (positions[i][3] === index) {
+
+ // update the series points with the values from positions
+ // array
+ point = data[positions[i][4]];
+ radius = positions[i][2];
+ point.plotX = positions[i][0] - chart.plotLeft +
+ chart.diffX;
+ point.plotY = positions[i][1] - chart.plotTop +
+ chart.diffY;
+
+ point.marker = H.extend(point.marker, {
+ radius: radius,
+ width: 2 * radius,
+ height: 2 * radius
+ });
+ }
+ }
+ },
+ /**
+ * Check if two bubbles overlaps.
+ * @private
+ * @param {Array} bubble1 first bubble
+ * @param {Array} bubble2 second bubble
+ * @return {boolean} overlap or not
+ */
+ checkOverlap: function (bubble1, bubble2) {
+ var diffX = bubble1[0] - bubble2[0], // diff of X center values
+ diffY = bubble1[1] - bubble2[1], // diff of Y center values
+ sumRad = bubble1[2] + bubble2[2]; // sum of bubble radius
+
+ return (
+ Math.sqrt(diffX * diffX + diffY * diffY) -
+ Math.abs(sumRad)
+ ) < -0.001;
+ },
+ /**
+ * Function that is adding one bubble based on positions and sizes
+ * of two other bubbles, lastBubble is the last added bubble,
+ * newOrigin is the bubble for positioning new bubbles.
+ * nextBubble is the curently added bubble for which we are
+ * calculating positions
+ * @private
+ * @param {Array} lastBubble The closest last bubble
+ * @param {Array} newOrigin New bubble
+ * @param {Array} nextBubble The closest next bubble
+ * @return {Array} Bubble with correct positions
+ */
+ positionBubble: function (lastBubble, newOrigin, nextBubble) {
+ var sqrt = Math.sqrt,
+ asin = Math.asin,
+ acos = Math.acos,
+ pow = Math.pow,
+ abs = Math.abs,
+ distance = sqrt( // dist between lastBubble and newOrigin
+ pow((lastBubble[0] - newOrigin[0]), 2) +
+ pow((lastBubble[1] - newOrigin[1]), 2)
+ ),
+ alfa = acos(
+ // from cosinus theorem: alfa is an angle used for
+ // calculating correct position
+ (
+ pow(distance, 2) +
+ pow(nextBubble[2] + newOrigin[2], 2) -
+ pow(nextBubble[2] + lastBubble[2], 2)
+ ) / (2 * (nextBubble[2] + newOrigin[2]) * distance)
+ ),
+
+ beta = asin( // from sinus theorem.
+ abs(lastBubble[0] - newOrigin[0]) /
+ distance
+ ),
+ // providing helping variables, related to angle between
+ // lastBubble and newOrigin
+ gamma = (lastBubble[1] - newOrigin[1]) < 0 ? 0 : Math.PI,
+ // if new origin y is smaller than last bubble y value
+ // (2 and 3 quarter),
+ // add Math.PI to final angle
+
+ delta = (lastBubble[0] - newOrigin[0]) *
+ (lastBubble[1] - newOrigin[1]) < 0 ?
+ 1 : -1, // (1st and 3rd quarter)
+ finalAngle = gamma + alfa + beta * delta,
+ cosA = Math.cos(finalAngle),
+ sinA = Math.sin(finalAngle),
+ posX = newOrigin[0] + (newOrigin[2] + nextBubble[2]) * sinA,
+ // center of new origin + (radius1 + radius2) * sinus A
+ posY = newOrigin[1] - (newOrigin[2] + nextBubble[2]) * cosA;
+
+ return [
+ posX,
+ posY,
+ nextBubble[2],
+ nextBubble[3],
+ nextBubble[4]
+ ]; // the same as described before
+ },
+ /**
+ * This is the main function responsible for positioning all of the
+ * bubbles.
+ * allDataPoints - bubble array, in format [pixel x value,
+ * pixel y value, radius, related series index, related point index]
+ * @private
+ * @param {Array} allDataPoints All points from all series
+ * @return {Array} Positions of all bubbles
+ */
+ placeBubbles: function (allDataPoints) {
+
+ var series = this,
+ checkOverlap = series.checkOverlap,
+ positionBubble = series.positionBubble,
+ bubblePos = [],
+ stage = 1,
+ j = 0,
+ k = 0,
+ calculatedBubble,
+ sortedArr,
+ i;
+
+ // sort all points
+ sortedArr = allDataPoints.sort(function (a, b) {
+ return b[2] - a[2];
+ });
+
+ // if length is 0, return empty array
+ if (!sortedArr.length) {
+ return [];
+ }
+ if (sortedArr.length < 2) {
+ // if length is 1,return only one bubble
+ return [
+ 0, 0,
+ sortedArr[0][0],
+ sortedArr[0][1],
+ sortedArr[0][2]
+ ];
+ }
+
+ // create first bubble in the middle of the chart
+ bubblePos.push([
+ [
+ 0, // starting in 0,0 coordinates
+ 0,
+ sortedArr[0][2], // radius
+ sortedArr[0][3], // series index
+ sortedArr[0][4]
+ ] // point index
+ ]); // 0 level bubble
+
+ bubblePos.push([
+ [
+ 0,
+ 0 - sortedArr[1][2] - sortedArr[0][2],
+ // move bubble above first one
+ sortedArr[1][2],
+ sortedArr[1][3],
+ sortedArr[1][4]
+ ]
+ ]); // 1 level 1st bubble
+
+ // first two already positioned so starting from 2
+ for (i = 2; i < sortedArr.length; i++) {
+ sortedArr[i][2] = sortedArr[i][2] || 1;
+ // in case if radius is calculated as 0.
+
+ calculatedBubble = positionBubble(
+ bubblePos[stage][j],
+ bubblePos[stage - 1][k],
+ sortedArr[i]
+ ); // calculate initial bubble position
+
+ if (checkOverlap(calculatedBubble, bubblePos[stage][0])) {
+ // if new bubble is overlapping with first bubble in
+ // current level (stage)
+ bubblePos.push([]);
+ k = 0;
+ // reset index of bubble, used for positioning the bubbles
+ // around it, we are starting from first bubble in next
+ // stage because we are changing level to higher
+ bubblePos[stage + 1].push(
+ positionBubble(
+ bubblePos[stage][j],
+ bubblePos[stage][0],
+ sortedArr[i]
+ )
+ );
+ // (last added bubble, 1st. bbl from cur stage, new bubble)
+ stage++; // the new level is created, above current one
+ j = 0; // set the index of bubble in current level to 0
+ } else if (
+ stage > 1 && bubblePos[stage - 1][k + 1] &&
+ checkOverlap(calculatedBubble, bubblePos[stage - 1][k + 1])
+ ) {
+ // If new bubble is overlapping with one of the previous
+ // stage bubbles, it means that - bubble, used for
+ // positioning the bubbles around it has changed so we need
+ // to recalculate it.
+ k++;
+ bubblePos[stage].push(
+ positionBubble(
+ bubblePos[stage][j],
+ bubblePos[stage - 1][k],
+ sortedArr[i]
+ )
+ );
+ // (last added bubble, previous stage bubble, new bubble)
+ j++;
+ } else { // simply add calculated bubble
+ j++;
+ bubblePos[stage].push(calculatedBubble);
+ }
+ }
+ series.chart.stages = bubblePos;
+ // it may not be necessary but adding it just in case -
+ // it is containing all of the bubble levels
+
+ series.chart.rawPositions = [].concat.apply([], bubblePos);
+ // bubble positions merged into one array
+
+ series.resizeRadius();
+
+ return series.chart.rawPositions;
+ },
+ /**
+ * The function responsible for resizing the bubble radius.
+ * In shortcut: it is taking the initially
+ * calculated positions of bubbles. Then it is calculating the min max
+ * of both dimensions, creating something in shape of bBox.
+ * The comparison of bBox and the size of plotArea
+ * (later it may be also the size set by customer) is giving the
+ * value how to recalculate the radius so it will match the size
+ * @private
+ */
+ resizeRadius: function () {
+
+ var chart = this.chart,
+ positions = chart.rawPositions,
+ min = Math.min,
+ max = Math.max,
+ plotLeft = chart.plotLeft,
+ plotTop = chart.plotTop,
+ chartHeight = chart.plotHeight,
+ chartWidth = chart.plotWidth,
+ minX, maxX, minY, maxY,
+ radius,
+ bBox,
+ spaceRatio,
+ smallerDimension,
+ i;
+
+ minX = minY = Number.POSITIVE_INFINITY; // set initial values
+ maxX = maxY = Number.NEGATIVE_INFINITY;
+
+ for (i = 0; i < positions.length; i++) {
+ radius = positions[i][2];
+ minX = min(minX, positions[i][0] - radius);
+ // (x center-radius) is the min x value used by specific bubble
+ maxX = max(maxX, positions[i][0] + radius);
+ minY = min(minY, positions[i][1] - radius);
+ maxY = max(maxY, positions[i][1] + radius);
+ }
+
+ bBox = [maxX - minX, maxY - minY];
+ spaceRatio = [
+ (chartWidth - plotLeft) / bBox[0],
+ (chartHeight - plotTop) / bBox[1]
+ ];
+
+ smallerDimension = min.apply([], spaceRatio);
+
+ if (Math.abs(smallerDimension - 1) > 1e-10) {
+ // if bBox is considered not the same width as possible size
+ for (i = 0; i < positions.length; i++) {
+ positions[i][2] *= smallerDimension;
+ }
+ this.placeBubbles(positions);
+ } else {
+ // If no radius recalculation is needed, we need to position the
+ // whole bubbles in center of chart plotarea for this, we are
+ // adding two parameters, diffY and diffX, that are related to
+ // differences between the initial center and the bounding box.
+ chart.diffY = chartHeight / 2 +
+ plotTop - minY - (maxY - minY) / 2;
+ chart.diffX = chartWidth / 2 +
+ plotLeft - minX - (maxX - minX) / 2;
+ }
+ },
+
+ // Calculate radius of bubbles in series.
+ getPointRadius: function () { // bubbles array
+
+ var series = this,
+ chart = series.chart,
+ plotWidth = chart.plotWidth,
+ plotHeight = chart.plotHeight,
+ seriesOptions = series.options,
+ smallestSize = Math.min(plotWidth, plotHeight),
+ extremes = {},
+ radii = [],
+ allDataPoints = chart.allDataPoints,
+ minSize,
+ maxSize,
+ value,
+ radius;
+
+ ['minSize', 'maxSize'].forEach(function (prop) {
+ var length = parseInt(seriesOptions[prop], 10),
+ isPercent = /%$/.test(length);
+
+ extremes[prop] = isPercent ?
+ smallestSize * length / 100 :
+ length;
+ });
+
+ chart.minRadius = minSize = extremes.minSize;
+ chart.maxRadius = maxSize = extremes.maxSize;
+
+ (allDataPoints || []).forEach(function (point, i) {
+
+ value = point[2];
+
+ radius = series.getRadius(
+ minSize,
+ maxSize,
+ minSize,
+ maxSize,
+ value
+ );
+
+ if (value === 0) {
+ radius = null;
+ }
+
+ allDataPoints[i][2] = radius;
+ radii.push(radius);
+ });
+
+ this.radii = radii;
+ },
+
+ alignDataLabel: H.Series.prototype.alignDataLabel
+ }
+ );
+
+ // When one series is modified, the others need to be recomputed
+ H.addEvent(H.seriesTypes.packedbubble, 'updatedData', function () {
+ var self = this;
+
+ this.chart.series.forEach(function (s) {
+ if (s.type === self.type) {
+ s.isDirty = true;
+ }
+ });
+ });
+
+ // Remove accumulated data points to redistribute all of them again
+ // (i.e after hiding series by legend)
+ H.addEvent(H.Chart, 'beforeRedraw', function () {
+ if (this.allDataPoints) {
+ delete this.allDataPoints;
+ }
+ });
+
+ /**
+ * A `packedbubble` series. If the [type](#series.packedbubble.type) option is
+ * not specified, it is inherited from [chart.type](#chart.type).
+ *
+ * @extends series,plotOptions.packedbubble
+ * @excluding dataParser, dataURL, stack
+ * @product highcharts
+ * @apioption series.packedbubble
+ */
+
+ /**
+ * An array of data points for the series. For the `packedbubble` series type,
+ * points can be given in the following ways:
+ *
+ * 1. An array of `value` values.
+ * ```js
+ * data: [5, 1, 20]
+ * ```
+ *
+ * 2. An array of objects with named values. The objects are point configuration
+ * objects as seen below. If the total number of data points exceeds the
+ * series' [turboThreshold](#series.packedbubble.turboThreshold), this option
+ * is not available.
+ * ```js
+ * data: [{
+ * value: 1,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * value: 5,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
+ *
+ * @type {Array}
+ * @extends series.line.data
+ * @excluding marker,x,y
+ * @product highcharts
+ * @apioption series.packedbubble.data
*/
- }(Highcharts));
- (function(H) {
/**
- * (c) 2010-2017 Torstein Honsi
+ * The value of a bubble. The bubble's size proportional to its `value`.
*
- * License: www.highcharts.com/license
+ * @type {number}
+ * @product highcharts
+ * @apioption series.packedbubble.data.weight
*/
/**
- * Extensions for polar charts. Additionally, much of the geometry required for polar charts is
- * gathered in RadialAxes.js.
+ * @excluding enabled, enabledThreshold, height, radius, width
+ * @apioption series.packedbubble.marker
+ */
+
+ }(Highcharts));
+ (function (H) {
+ /* *
+ * (c) 2010-2019 Torstein Honsi
*
+ * License: www.highcharts.com/license
*/
- var each = H.each,
- pick = H.pick,
+
+
+ // Extensions for polar charts. Additionally, much of the geometry required for
+ // polar charts is gathered in RadialAxes.js.
+
+ var pick = H.pick,
Pointer = H.Pointer,
Series = H.Series,
seriesTypes = H.seriesTypes,
@@ -4673,523 +7201,617 @@
pointerProto = Pointer.prototype,
colProto;
- /**
- * Search a k-d tree by the point angle, used for shared tooltips in polar charts
- */
- seriesProto.searchPointByAngle = function(e) {
- var series = this,
- chart = series.chart,
- xAxis = series.xAxis,
- center = xAxis.pane.center,
- plotX = e.chartX - center[0] - chart.plotLeft,
- plotY = e.chartY - center[1] - chart.plotTop;
-
- return this.searchKDTree({
- clientX: 180 + (Math.atan2(plotX, plotY) * (-180 / Math.PI))
- });
+ if (!H.polarExtended) {
+ H.polarExtended = true;
- };
- /**
- * #6212 Calculate connectors for spline series in polar chart.
- * @param {Boolean} calculateNeighbours - Check if connectors should be calculated for neighbour points as well
- * allows short recurence
- */
- seriesProto.getConnectors = function(segment, index, calculateNeighbours, connectEnds) {
-
- var i,
- prevPointInd,
- nextPointInd,
- previousPoint,
- nextPoint,
- previousX,
- previousY,
- nextX,
- nextY,
- plotX,
- plotY,
- ret,
- smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc;
- denom = smoothing + 1,
- leftContX,
- leftContY,
- rightContX,
- rightContY,
- dLControlPoint, // distance left control point
- dRControlPoint,
- leftContAngle,
- rightContAngle,
- jointAngle,
- addedNumber = connectEnds ? 1 : 0;
-
- /** calculate final index of points depending on the initial index value.
- * Because of calculating neighbours, index may be outisde segment array.
+ /**
+ * Search a k-d tree by the point angle, used for shared tooltips in polar
+ * charts
*/
- if (index >= 0 && index <= segment.length - 1) {
- i = index;
- } else if (index < 0) {
- i = segment.length - 1 + index;
- } else {
- i = 0;
- }
+ seriesProto.searchPointByAngle = function (e) {
+ var series = this,
+ chart = series.chart,
+ xAxis = series.xAxis,
+ center = xAxis.pane.center,
+ plotX = e.chartX - center[0] - chart.plotLeft,
+ plotY = e.chartY - center[1] - chart.plotTop;
+
+ return this.searchKDTree({
+ clientX: 180 + (Math.atan2(plotX, plotY) * (-180 / Math.PI))
+ });
- prevPointInd = (i - 1 < 0) ? segment.length - (1 + addedNumber) : i - 1;
- nextPointInd = (i + 1 > segment.length - 1) ? addedNumber : i + 1;
- previousPoint = segment[prevPointInd];
- nextPoint = segment[nextPointInd];
- previousX = previousPoint.plotX;
- previousY = previousPoint.plotY;
- nextX = nextPoint.plotX;
- nextY = nextPoint.plotY;
- plotX = segment[i].plotX; // actual point
- plotY = segment[i].plotY;
- leftContX = (smoothing * plotX + previousX) / denom;
- leftContY = (smoothing * plotY + previousY) / denom;
- rightContX = (smoothing * plotX + nextX) / denom;
- rightContY = (smoothing * plotY + nextY) / denom;
- dLControlPoint = Math.sqrt(Math.pow(leftContX - plotX, 2) + Math.pow(leftContY - plotY, 2));
- dRControlPoint = Math.sqrt(Math.pow(rightContX - plotX, 2) + Math.pow(rightContY - plotY, 2));
- leftContAngle = Math.atan2(leftContY - plotY, leftContX - plotX);
- rightContAngle = Math.atan2(rightContY - plotY, rightContX - plotX);
- jointAngle = (Math.PI / 2) + ((leftContAngle + rightContAngle) / 2);
- // Ensure the right direction, jointAngle should be in the same quadrant as leftContAngle
- if (Math.abs(leftContAngle - jointAngle) > Math.PI / 2) {
- jointAngle -= Math.PI;
- }
- // Find the corrected control points for a spline straight through the point
- leftContX = plotX + Math.cos(jointAngle) * dLControlPoint;
- leftContY = plotY + Math.sin(jointAngle) * dLControlPoint;
- rightContX = plotX + Math.cos(Math.PI + jointAngle) * dRControlPoint;
- rightContY = plotY + Math.sin(Math.PI + jointAngle) * dRControlPoint;
-
- // push current point's connectors into returned object
-
- ret = {
- rightContX: rightContX,
- rightContY: rightContY,
- leftContX: leftContX,
- leftContY: leftContY,
- plotX: plotX,
- plotY: plotY
};
- // calculate connectors for previous and next point and push them inside returned object
- if (calculateNeighbours) {
- ret.prevPointCont = this.getConnectors(segment, prevPointInd, false, connectEnds);
- }
- return ret;
- };
+ /**
+ * #6212 Calculate connectors for spline series in polar chart.
+ * @param {boolean} calculateNeighbours
+ * Check if connectors should be calculated for neighbour points as
+ * well allows short recurence
+ */
+ seriesProto.getConnectors = function (
+ segment,
+ index,
+ calculateNeighbours,
+ connectEnds
+ ) {
- /**
- * Wrap the buildKDTree function so that it searches by angle (clientX) in case of shared tooltip,
- * and by two dimensional distance in case of non-shared.
- */
- wrap(seriesProto, 'buildKDTree', function(proceed) {
- if (this.chart.polar) {
- if (this.kdByAngle) {
- this.searchPoint = this.searchPointByAngle;
+ var i,
+ prevPointInd,
+ nextPointInd,
+ previousPoint,
+ nextPoint,
+ previousX,
+ previousY,
+ nextX,
+ nextY,
+ plotX,
+ plotY,
+ ret,
+ // 1 means control points midway between points, 2 means 1/3 from
+ // the point, 3 is 1/4 etc;
+ smoothing = 1.5,
+ denom = smoothing + 1,
+ leftContX,
+ leftContY,
+ rightContX,
+ rightContY,
+ dLControlPoint, // distance left control point
+ dRControlPoint,
+ leftContAngle,
+ rightContAngle,
+ jointAngle,
+ addedNumber = connectEnds ? 1 : 0;
+
+ // Calculate final index of points depending on the initial index value.
+ // Because of calculating neighbours, index may be outisde segment
+ // array.
+ if (index >= 0 && index <= segment.length - 1) {
+ i = index;
+ } else if (index < 0) {
+ i = segment.length - 1 + index;
} else {
- this.options.findNearestPointBy = 'xy';
+ i = 0;
}
- }
- proceed.apply(this);
- });
- /**
- * Translate a point's plotX and plotY from the internal angle and radius measures to
- * true plotX, plotY coordinates
- */
- seriesProto.toXY = function(point) {
- var xy,
- chart = this.chart,
- plotX = point.plotX,
- plotY = point.plotY,
- clientX;
-
- // Save rectangular plotX, plotY for later computation
- point.rectPlotX = plotX;
- point.rectPlotY = plotY;
-
- // Find the polar plotX and plotY
- xy = this.xAxis.postTranslate(point.plotX, this.yAxis.len - plotY);
- point.plotX = point.polarPlotX = xy.x - chart.plotLeft;
- point.plotY = point.polarPlotY = xy.y - chart.plotTop;
-
- // If shared tooltip, record the angle in degrees in order to align X points. Otherwise,
- // use a standard k-d tree to get the nearest point in two dimensions.
- if (this.kdByAngle) {
- clientX = ((plotX / Math.PI * 180) + this.xAxis.pane.options.startAngle) % 360;
- if (clientX < 0) { // #2665
- clientX += 360;
+ prevPointInd = (i - 1 < 0) ? segment.length - (1 + addedNumber) : i - 1;
+ nextPointInd = (i + 1 > segment.length - 1) ? addedNumber : i + 1;
+ previousPoint = segment[prevPointInd];
+ nextPoint = segment[nextPointInd];
+ previousX = previousPoint.plotX;
+ previousY = previousPoint.plotY;
+ nextX = nextPoint.plotX;
+ nextY = nextPoint.plotY;
+ plotX = segment[i].plotX; // actual point
+ plotY = segment[i].plotY;
+ leftContX = (smoothing * plotX + previousX) / denom;
+ leftContY = (smoothing * plotY + previousY) / denom;
+ rightContX = (smoothing * plotX + nextX) / denom;
+ rightContY = (smoothing * plotY + nextY) / denom;
+ dLControlPoint = Math.sqrt(
+ Math.pow(leftContX - plotX, 2) + Math.pow(leftContY - plotY, 2)
+ );
+ dRControlPoint = Math.sqrt(
+ Math.pow(rightContX - plotX, 2) + Math.pow(rightContY - plotY, 2)
+ );
+ leftContAngle = Math.atan2(leftContY - plotY, leftContX - plotX);
+ rightContAngle = Math.atan2(rightContY - plotY, rightContX - plotX);
+ jointAngle = (Math.PI / 2) + ((leftContAngle + rightContAngle) / 2);
+ // Ensure the right direction, jointAngle should be in the same quadrant
+ // as leftContAngle
+ if (Math.abs(leftContAngle - jointAngle) > Math.PI / 2) {
+ jointAngle -= Math.PI;
}
- point.clientX = clientX;
- } else {
- point.clientX = point.plotX;
- }
- };
+ // Find the corrected control points for a spline straight through the
+ // point
+ leftContX = plotX + Math.cos(jointAngle) * dLControlPoint;
+ leftContY = plotY + Math.sin(jointAngle) * dLControlPoint;
+ rightContX = plotX + Math.cos(Math.PI + jointAngle) * dRControlPoint;
+ rightContY = plotY + Math.sin(Math.PI + jointAngle) * dRControlPoint;
- if (seriesTypes.spline) {
- /**
- * Overridden method for calculating a spline from one point to the next
- */
- wrap(seriesTypes.spline.prototype, 'getPointSpline', function(proceed, segment, point, i) {
- var ret,
- connectors;
+ // push current point's connectors into returned object
- if (this.chart.polar) {
- // moveTo or lineTo
- if (!i) {
- ret = ['M', point.plotX, point.plotY];
- } else { // curve from last point to this
- connectors = this.getConnectors(segment, i, true, this.connectEnds);
- ret = [
- 'C',
- connectors.prevPointCont.rightContX,
- connectors.prevPointCont.rightContY,
- connectors.leftContX,
- connectors.leftContY,
- connectors.plotX,
- connectors.plotY
- ];
- }
- } else {
- ret = proceed.call(this, segment, point, i);
+ ret = {
+ rightContX: rightContX,
+ rightContY: rightContY,
+ leftContX: leftContX,
+ leftContY: leftContY,
+ plotX: plotX,
+ plotY: plotY
+ };
+
+ // calculate connectors for previous and next point and push them inside
+ // returned object
+ if (calculateNeighbours) {
+ ret.prevPointCont = this.getConnectors(
+ segment,
+ prevPointInd,
+ false,
+ connectEnds
+ );
}
return ret;
- });
-
- // #6430 Areasplinerange series use unwrapped getPointSpline method, so we need to set this method again.
- if (seriesTypes.areasplinerange) {
- seriesTypes.areasplinerange.prototype.getPointSpline = seriesTypes.spline.prototype.getPointSpline;
- }
- }
+ };
- /**
- * Extend translate. The plotX and plotY values are computed as if the polar chart were a
- * cartesian plane, where plotX denotes the angle in radians and (yAxis.len - plotY) is the pixel distance from
- * center.
- */
- wrap(seriesProto, 'translate', function(proceed) {
- var chart = this.chart,
- points,
- i;
+ /**
+ * Translate a point's plotX and plotY from the internal angle and radius
+ * measures to true plotX, plotY coordinates
+ */
+ seriesProto.toXY = function (point) {
+ var xy,
+ chart = this.chart,
+ plotX = point.plotX,
+ plotY = point.plotY,
+ clientX;
- // Run uber method
- proceed.call(this);
+ // Save rectangular plotX, plotY for later computation
+ point.rectPlotX = plotX;
+ point.rectPlotY = plotY;
- // Postprocess plot coordinates
- if (chart.polar) {
- this.kdByAngle = chart.tooltip && chart.tooltip.shared;
+ // Find the polar plotX and plotY
+ xy = this.xAxis.postTranslate(point.plotX, this.yAxis.len - plotY);
+ point.plotX = point.polarPlotX = xy.x - chart.plotLeft;
+ point.plotY = point.polarPlotY = xy.y - chart.plotTop;
- if (!this.preventPostTranslate) {
- points = this.points;
- i = points.length;
+ // If shared tooltip, record the angle in degrees in order to align X
+ // points. Otherwise, use a standard k-d tree to get the nearest point
+ // in two dimensions.
+ if (this.kdByAngle) {
+ clientX = (
+ (plotX / Math.PI * 180) + this.xAxis.pane.options.startAngle
+ ) % 360;
+ if (clientX < 0) { // #2665
+ clientX += 360;
+ }
+ point.clientX = clientX;
+ } else {
+ point.clientX = point.plotX;
+ }
+ };
- while (i--) {
- // Translate plotX, plotY from angle and radius to true plot coordinates
- this.toXY(points[i]);
+ if (seriesTypes.spline) {
+ /**
+ * Overridden method for calculating a spline from one point to the next
+ */
+ wrap(
+ seriesTypes.spline.prototype,
+ 'getPointSpline',
+ function (proceed, segment, point, i) {
+ var ret,
+ connectors;
+
+ if (this.chart.polar) {
+ // moveTo or lineTo
+ if (!i) {
+ ret = ['M', point.plotX, point.plotY];
+ } else { // curve from last point to this
+ connectors = this.getConnectors(
+ segment,
+ i,
+ true,
+ this.connectEnds
+ );
+ ret = [
+ 'C',
+ connectors.prevPointCont.rightContX,
+ connectors.prevPointCont.rightContY,
+ connectors.leftContX,
+ connectors.leftContY,
+ connectors.plotX,
+ connectors.plotY
+ ];
+ }
+ } else {
+ ret = proceed.call(this, segment, point, i);
+ }
+ return ret;
}
+ );
+
+ // #6430 Areasplinerange series use unwrapped getPointSpline method, so
+ // we need to set this method again.
+ if (seriesTypes.areasplinerange) {
+ seriesTypes.areasplinerange.prototype.getPointSpline =
+ seriesTypes.spline.prototype.getPointSpline;
}
}
- });
- /**
- * Extend getSegmentPath to allow connecting ends across 0 to provide a closed circle in
- * line-like series.
- */
- wrap(seriesProto, 'getGraphPath', function(proceed, points) {
- var series = this,
- i,
- firstValid,
- popLastPoint;
+ /**
+ * Extend translate. The plotX and plotY values are computed as if the polar
+ * chart were a cartesian plane, where plotX denotes the angle in radians
+ * and (yAxis.len - plotY) is the pixel distance from center.
+ */
+ H.addEvent(Series, 'afterTranslate', function () {
+ var chart = this.chart,
+ points,
+ i;
- // Connect the path
- if (this.chart.polar) {
- points = points || this.points;
+ if (chart.polar) {
- // Append first valid point in order to connect the ends
- for (i = 0; i < points.length; i++) {
- if (!points[i].isNull) {
- firstValid = i;
- break;
+ // Prepare k-d-tree handling. It searches by angle (clientX) in
+ // case of shared tooltip, and by two dimensional distance in case
+ // of non-shared.
+ this.kdByAngle = chart.tooltip && chart.tooltip.shared;
+ if (this.kdByAngle) {
+ this.searchPoint = this.searchPointByAngle;
+ } else {
+ this.options.findNearestPointBy = 'xy';
}
- }
+ // Postprocess plot coordinates
+ if (!this.preventPostTranslate) {
+ points = this.points;
+ i = points.length;
- /**
- * Polar charts only. Whether to connect the ends of a line series plot
- * across the extremes.
- *
- * @type {Boolean}
- * @sample {highcharts} highcharts/plotoptions/line-connectends-false/
- * Do not connect
- * @since 2.3.0
- * @product highcharts
- * @apioption plotOptions.series.connectEnds
- */
- if (this.options.connectEnds !== false && firstValid !== undefined) {
- this.connectEnds = true; // re-used in splines
- points.splice(points.length, 0, points[firstValid]);
- popLastPoint = true;
- }
-
- // For area charts, pseudo points are added to the graph, now we need to translate these
- each(points, function(point) {
- if (point.polarPlotY === undefined) {
- series.toXY(point);
+ while (i--) {
+ // Translate plotX, plotY from angle and radius to true plot
+ // coordinates
+ this.toXY(points[i]);
+ }
}
- });
- }
- // Run uber method
- var ret = proceed.apply(this, [].slice.call(arguments, 1));
+ // Perform clip after render
+ if (!this.hasClipCircleSetter) {
+ this.hasClipCircleSetter = Boolean(
+ H.addEvent(this, 'afterRender', function () {
+ var circ;
+
+ if (chart.polar) {
+ circ = this.yAxis.center;
+ this.group.clip(
+ chart.renderer.clipCircle(
+ circ[0],
+ circ[1],
+ circ[2] / 2
+ )
+ );
+ this.setClip = H.noop;
+ }
+ })
+ );
+ }
+ }
+ }, { order: 2 }); // Run after translation of ||-coords
- /** #6212 points.splice method is adding points to an array. In case of areaspline getGraphPath method is used two times
- * and in both times points are added to an array. That is why points.pop is used, to get unmodified points.
+ /**
+ * Extend getSegmentPath to allow connecting ends across 0 to provide a
+ * closed circle in line-like series.
*/
- if (popLastPoint) {
- points.pop();
- }
- return ret;
- });
-
+ wrap(seriesProto, 'getGraphPath', function (proceed, points) {
+ var series = this,
+ i,
+ firstValid,
+ popLastPoint;
- function polarAnimate(proceed, init) {
- var chart = this.chart,
- animation = this.options.animation,
- group = this.group,
- markerGroup = this.markerGroup,
- center = this.xAxis.center,
- plotLeft = chart.plotLeft,
- plotTop = chart.plotTop,
- attribs;
+ // Connect the path
+ if (this.chart.polar) {
+ points = points || this.points;
- // Specific animation for polar charts
- if (chart.polar) {
+ // Append first valid point in order to connect the ends
+ for (i = 0; i < points.length; i++) {
+ if (!points[i].isNull) {
+ firstValid = i;
+ break;
+ }
+ }
- // Enable animation on polar charts only in SVG. In VML, the scaling is different, plus animation
- // would be so slow it would't matter.
- if (chart.renderer.isSVG) {
- if (animation === true) {
- animation = {};
+ /**
+ * Polar charts only. Whether to connect the ends of a line series
+ * plot across the extremes.
+ *
+ * @sample {highcharts} highcharts/plotoptions/line-connectends-false/
+ * Do not connect
+ *
+ * @type {boolean}
+ * @since 2.3.0
+ * @product highcharts
+ * @apioption plotOptions.series.connectEnds
+ */
+ if (this.options.connectEnds !== false &&
+ firstValid !== undefined
+ ) {
+ this.connectEnds = true; // re-used in splines
+ points.splice(points.length, 0, points[firstValid]);
+ popLastPoint = true;
}
- // Initialize the animation
- if (init) {
+ // For area charts, pseudo points are added to the graph, now we
+ // need to translate these
+ points.forEach(function (point) {
+ if (point.polarPlotY === undefined) {
+ series.toXY(point);
+ }
+ });
+ }
- // Scale down the group and place it in the center
- attribs = {
- translateX: center[0] + plotLeft,
- translateY: center[1] + plotTop,
- scaleX: 0.001, // #1499
- scaleY: 0.001
- };
+ // Run uber method
+ var ret = proceed.apply(this, [].slice.call(arguments, 1));
+
+ // #6212 points.splice method is adding points to an array. In case of
+ // areaspline getGraphPath method is used two times and in both times
+ // points are added to an array. That is why points.pop is used, to get
+ // unmodified points.
+ if (popLastPoint) {
+ points.pop();
+ }
+ return ret;
+ });
- group.attr(attribs);
- if (markerGroup) {
- markerGroup.attr(attribs);
+
+ var polarAnimate = function (proceed, init) {
+ var chart = this.chart,
+ animation = this.options.animation,
+ group = this.group,
+ markerGroup = this.markerGroup,
+ center = this.xAxis.center,
+ plotLeft = chart.plotLeft,
+ plotTop = chart.plotTop,
+ attribs;
+
+ // Specific animation for polar charts
+ if (chart.polar) {
+
+ // Enable animation on polar charts only in SVG. In VML, the scaling
+ // is different, plus animation would be so slow it would't matter.
+ if (chart.renderer.isSVG) {
+
+ if (animation === true) {
+ animation = {};
}
+ // Initialize the animation
+ if (init) {
+
+ // Scale down the group and place it in the center
+ attribs = {
+ translateX: center[0] + plotLeft,
+ translateY: center[1] + plotTop,
+ scaleX: 0.001, // #1499
+ scaleY: 0.001
+ };
+
+ group.attr(attribs);
+ if (markerGroup) {
+ markerGroup.attr(attribs);
+ }
+
// Run the animation
- } else {
- attribs = {
- translateX: plotLeft,
- translateY: plotTop,
- scaleX: 1,
- scaleY: 1
- };
- group.animate(attribs, animation);
- if (markerGroup) {
- markerGroup.animate(attribs, animation);
- }
+ } else {
+ attribs = {
+ translateX: plotLeft,
+ translateY: plotTop,
+ scaleX: 1,
+ scaleY: 1
+ };
+ group.animate(attribs, animation);
+ if (markerGroup) {
+ markerGroup.animate(attribs, animation);
+ }
- // Delete this function to allow it only once
- this.animate = null;
+ // Delete this function to allow it only once
+ this.animate = null;
+ }
}
- }
// For non-polar charts, revert to the basic animation
- } else {
- proceed.call(this, init);
- }
- }
-
- // Define the animate method for regular series
- wrap(seriesProto, 'animate', polarAnimate);
+ } else {
+ proceed.call(this, init);
+ }
+ };
+ // Define the animate method for regular series
+ wrap(seriesProto, 'animate', polarAnimate);
- if (seriesTypes.column) {
- colProto = seriesTypes.column.prototype;
+ if (seriesTypes.column) {
- colProto.polarArc = function(low, high, start, end) {
- var center = this.xAxis.center,
- len = this.yAxis.len;
+ colProto = seriesTypes.column.prototype;
- return this.chart.renderer.symbols.arc(
- center[0],
- center[1],
- len - high,
- null, {
- start: start,
- end: end,
- innerR: len - pick(low, len)
- }
- );
- };
+ colProto.polarArc = function (low, high, start, end) {
+ var center = this.xAxis.center,
+ len = this.yAxis.len;
- /**
- * Define the animate method for columnseries
- */
- wrap(colProto, 'animate', polarAnimate);
+ return this.chart.renderer.symbols.arc(
+ center[0],
+ center[1],
+ len - high,
+ null,
+ {
+ start: start,
+ end: end,
+ innerR: len - pick(low, len)
+ }
+ );
+ };
+ /**
+ * Define the animate method for columnseries
+ */
+ wrap(colProto, 'animate', polarAnimate);
- /**
- * Extend the column prototype's translate method
- */
- wrap(colProto, 'translate', function(proceed) {
- var xAxis = this.xAxis,
- startAngleRad = xAxis.startAngleRad,
- start,
- points,
- point,
- i;
+ /**
+ * Extend the column prototype's translate method
+ */
+ wrap(colProto, 'translate', function (proceed) {
+
+ var xAxis = this.xAxis,
+ startAngleRad = xAxis.startAngleRad,
+ start,
+ points,
+ point,
+ i;
+
+ this.preventPostTranslate = true;
+
+ // Run uber method
+ proceed.call(this);
+
+ // Postprocess plot coordinates
+ if (xAxis.isRadial) {
+ points = this.points;
+ i = points.length;
+ while (i--) {
+ point = points[i];
+ start = point.barX + startAngleRad;
+ point.shapeType = 'path';
+ point.shapeArgs = {
+ d: this.polarArc(
+ point.yBottom,
+ point.plotY,
+ start,
+ start + point.pointWidth
+ )
+ };
+ // Provide correct plotX, plotY for tooltip
+ this.toXY(point);
+ point.tooltipPos = [point.plotX, point.plotY];
+ point.ttBelow = point.plotY > xAxis.center[1];
+ }
+ }
+ });
- this.preventPostTranslate = true;
- // Run uber method
- proceed.call(this);
+ /**
+ * Align column data labels outside the columns. #1199.
+ */
+ wrap(colProto, 'alignDataLabel', function (
+ proceed,
+ point,
+ dataLabel,
+ options,
+ alignTo,
+ isNew
+ ) {
+
+ if (this.chart.polar) {
+ var angle = point.rectPlotX / Math.PI * 180,
+ align,
+ verticalAlign;
+
+ // Align nicely outside the perimeter of the columns
+ if (options.align === null) {
+ if (angle > 20 && angle < 160) {
+ align = 'left'; // right hemisphere
+ } else if (angle > 200 && angle < 340) {
+ align = 'right'; // left hemisphere
+ } else {
+ align = 'center'; // top or bottom
+ }
+ options.align = align;
+ }
+ if (options.verticalAlign === null) {
+ if (angle < 45 || angle > 315) {
+ verticalAlign = 'bottom'; // top part
+ } else if (angle > 135 && angle < 225) {
+ verticalAlign = 'top'; // bottom part
+ } else {
+ verticalAlign = 'middle'; // left or right
+ }
+ options.verticalAlign = verticalAlign;
+ }
- // Postprocess plot coordinates
- if (xAxis.isRadial) {
- points = this.points;
- i = points.length;
- while (i--) {
- point = points[i];
- start = point.barX + startAngleRad;
- point.shapeType = 'path';
- point.shapeArgs = {
- d: this.polarArc(point.yBottom, point.plotY, start, start + point.pointWidth)
- };
- // Provide correct plotX, plotY for tooltip
- this.toXY(point);
- point.tooltipPos = [point.plotX, point.plotY];
- point.ttBelow = point.plotY > xAxis.center[1];
+ seriesProto.alignDataLabel.call(
+ this,
+ point,
+ dataLabel,
+ options,
+ alignTo,
+ isNew
+ );
+ } else {
+ proceed.call(this, point, dataLabel, options, alignTo, isNew);
}
- }
- });
+ });
+ }
/**
- * Align column data labels outside the columns. #1199.
+ * Extend getCoordinates to prepare for polar axis values
*/
- wrap(colProto, 'alignDataLabel', function(proceed, point, dataLabel, options, alignTo, isNew) {
+ wrap(pointerProto, 'getCoordinates', function (proceed, e) {
+ var chart = this.chart,
+ ret = {
+ xAxis: [],
+ yAxis: []
+ };
- if (this.chart.polar) {
- var angle = point.rectPlotX / Math.PI * 180,
- align,
- verticalAlign;
-
- // Align nicely outside the perimeter of the columns
- if (options.align === null) {
- if (angle > 20 && angle < 160) {
- align = 'left'; // right hemisphere
- } else if (angle > 200 && angle < 340) {
- align = 'right'; // left hemisphere
- } else {
- align = 'center'; // top or bottom
- }
- options.align = align;
- }
- if (options.verticalAlign === null) {
- if (angle < 45 || angle > 315) {
- verticalAlign = 'bottom'; // top part
- } else if (angle > 135 && angle < 225) {
- verticalAlign = 'top'; // bottom part
- } else {
- verticalAlign = 'middle'; // left or right
- }
- options.verticalAlign = verticalAlign;
- }
+ if (chart.polar) {
+
+ chart.axes.forEach(function (axis) {
+ var isXAxis = axis.isXAxis,
+ center = axis.center,
+ x = e.chartX - center[0] - chart.plotLeft,
+ y = e.chartY - center[1] - chart.plotTop;
+
+ ret[isXAxis ? 'xAxis' : 'yAxis'].push({
+ axis: axis,
+ value: axis.translate(
+ isXAxis ?
+ Math.PI - Math.atan2(x, y) : // angle
+ // distance from center
+ Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)),
+ true
+ )
+ });
+ });
- seriesProto.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);
} else {
- proceed.call(this, point, dataLabel, options, alignTo, isNew);
+ ret = proceed.call(this, e);
}
+ return ret;
});
- }
-
- /**
- * Extend getCoordinates to prepare for polar axis values
- */
- wrap(pointerProto, 'getCoordinates', function(proceed, e) {
- var chart = this.chart,
- ret = {
- xAxis: [],
- yAxis: []
- };
-
- if (chart.polar) {
-
- each(chart.axes, function(axis) {
- var isXAxis = axis.isXAxis,
- center = axis.center,
- x = e.chartX - center[0] - chart.plotLeft,
- y = e.chartY - center[1] - chart.plotTop;
-
- ret[isXAxis ? 'xAxis' : 'yAxis'].push({
- axis: axis,
- value: axis.translate(
- isXAxis ?
- Math.PI - Math.atan2(x, y) : // angle
- Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)), // distance from center
- true
- )
- });
- });
- } else {
- ret = proceed.call(this, e);
- }
+ H.SVGRenderer.prototype.clipCircle = function (x, y, r) {
+ var wrapper,
+ id = H.uniqueKey(),
- return ret;
- });
+ clipPath = this.createElement('clipPath').attr({
+ id: id
+ }).add(this.defs);
- wrap(H.Chart.prototype, 'getAxes', function(proceed) {
+ wrapper = this.circle(x, y, r).add(clipPath);
+ wrapper.id = id;
+ wrapper.clipPath = clipPath;
- if (!this.pane) {
- this.pane = [];
- }
- each(H.splat(this.options.pane), function(paneOptions) {
- new H.Pane( // eslint-disable-line no-new
- paneOptions,
- this
- );
- }, this);
+ return wrapper;
+ };
- proceed.call(this);
- });
+ H.addEvent(H.Chart, 'getAxes', function () {
- wrap(H.Chart.prototype, 'drawChartBox', function(proceed) {
- proceed.call(this);
+ if (!this.pane) {
+ this.pane = [];
+ }
+ H.splat(this.options.pane).forEach(function (paneOptions) {
+ new H.Pane( // eslint-disable-line no-new
+ paneOptions,
+ this
+ );
+ }, this);
+ });
- each(this.pane, function(pane) {
- pane.render();
+ H.addEvent(H.Chart, 'afterDrawChartBox', function () {
+ this.pane.forEach(function (pane) {
+ pane.render();
+ });
});
- });
- /**
- * Extend chart.get to also search in panes. Used internally in responsiveness
- * and chart.update.
- */
- wrap(H.Chart.prototype, 'get', function(proceed, id) {
- return H.find(this.pane, function(pane) {
- return pane.options.id === id;
- }) || proceed.call(this, id);
- });
+ /**
+ * Extend chart.get to also search in panes. Used internally in
+ * responsiveness and chart.update.
+ */
+ wrap(H.Chart.prototype, 'get', function (proceed, id) {
+ return H.find(this.pane, function (pane) {
+ return pane.options.id === id;
+ }) || proceed.call(this, id);
+ });
+ }
}(Highcharts));
+ return (function () {
+
+
+ }());
}));
diff --git a/app/assets/javascripts/highcharts/lib/jspdf.js b/app/assets/javascripts/highcharts/lib/jspdf.js
index 0dace99..c398598 100644
--- a/app/assets/javascripts/highcharts/lib/jspdf.js
+++ b/app/assets/javascripts/highcharts/lib/jspdf.js
@@ -1,15792 +1,20999 @@
(function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
- typeof define === 'function' && define.amd ? define(factory) :
- (global.jspdf = factory());
-}(this, function () { 'use strict';
-
- var babelHelpers = {};
- babelHelpers.typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
- return typeof obj;
- } : function (obj) {
- return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
- };
- babelHelpers;
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
+ (factory((global.jsPDF = {})));
+}(this, (function (exports) { 'use strict';
+
+ /**
+ * JavaScript Polyfill functions for jsPDF
+ * Collected from public resources by
+ * https://github.com/diegocr
+ */
+ (function (global) {
+ if (typeof global.console !== "object") {
+ // Console-polyfill. MIT license.
+ // https://github.com/paulmillr/console-polyfill
+ // Make it safe to do console.log() always.
+ global.console = {};
+ var con = global.console;
+ var prop, method;
+
+ var dummy = function () {};
+
+ var properties = ["memory"];
+ var methods = ("assert,clear,count,debug,dir,dirxml,error,exception,group," + "groupCollapsed,groupEnd,info,log,markTimeline,profile,profiles,profileEnd," + "show,table,time,timeEnd,timeline,timelineEnd,timeStamp,trace,warn").split(",");
+
+ while (prop = properties.pop()) if (!con[prop]) con[prop] = {};
+
+ while (method = methods.pop()) if (!con[method]) con[method] = dummy;
+ }
- /** @preserve
- * jsPDF - PDF Document creation from JavaScript
- * Version 1.2.68 Built on 2017-07-18T14:26:05.034Z
- * CommitID 38732db74a
- *
- * Copyright (c) 2010-2014 James Hall , https://github.com/MrRio/jsPDF
- * 2010 Aaron Spike, https://github.com/acspike
- * 2012 Willow Systems Corporation, willow-systems.com
- * 2012 Pablo Hess, https://github.com/pablohess
- * 2012 Florian Jenett, https://github.com/fjenett
- * 2013 Warren Weckesser, https://github.com/warrenweckesser
- * 2013 Youssef Beddad, https://github.com/lifof
- * 2013 Lee Driscoll, https://github.com/lsdriscoll
- * 2013 Stefan Slonevskiy, https://github.com/stefslon
- * 2013 Jeremy Morel, https://github.com/jmorel
- * 2013 Christoph Hartmann, https://github.com/chris-rock
- * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria
- * 2014 James Makes, https://github.com/dollaruw
- * 2014 Diego Casorran, https://github.com/diegocr
- * 2014 Steven Spungin, https://github.com/Flamenco
- * 2014 Kenneth Glassey, https://github.com/Gavvers
- *
- * Licensed under the MIT License
- *
- * Contributor(s):
- * siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango,
- * kim3er, mfo, alnorth, Flamenco
- */
+ var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+
+ if (typeof global.btoa === "undefined") {
+ global.btoa = function (data) {
+ // discuss at: http://phpjs.org/functions/base64_encode/
+ // original by: Tyler Akins (http://rumkin.com)
+ // improved by: Bayron Guevara
+ // improved by: Thunder.m
+ // improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // improved by: Rafal Kukawski (http://kukawski.pl)
+ // bugfixed by: Pellentesque Malesuada
+ // example 1: base64_encode('Kevin van Zonneveld');
+ // returns 1: 'S2V2aW4gdmFuIFpvbm5ldmVsZA=='
+ var o1,
+ o2,
+ o3,
+ h1,
+ h2,
+ h3,
+ h4,
+ bits,
+ i = 0,
+ ac = 0,
+ enc = "",
+ tmp_arr = [];
+
+ if (!data) {
+ return data;
+ }
- /**
- * Creates new jsPDF document object instance.
- *
- * @class
- * @param orientation One of "portrait" or "landscape" (or shortcuts "p" (Default), "l")
- * @param unit Measurement unit to be used when coordinates are specified.
- * One of "pt" (points), "mm" (Default), "cm", "in"
- * @param format One of 'pageFormats' as shown below, default: a4
- * @returns {jsPDF}
- * @name jsPDF
- */
- var jsPDF = function (global) {
- 'use strict';
-
- var pdfVersion = '1.3',
- pageFormats = { // Size in pt of various paper formats
- 'a0': [2383.94, 3370.39], 'a1': [1683.78, 2383.94],
- 'a2': [1190.55, 1683.78], 'a3': [841.89, 1190.55],
- 'a4': [595.28, 841.89], 'a5': [419.53, 595.28],
- 'a6': [297.64, 419.53], 'a7': [209.76, 297.64],
- 'a8': [147.40, 209.76], 'a9': [104.88, 147.40],
- 'a10': [73.70, 104.88], 'b0': [2834.65, 4008.19],
- 'b1': [2004.09, 2834.65], 'b2': [1417.32, 2004.09],
- 'b3': [1000.63, 1417.32], 'b4': [708.66, 1000.63],
- 'b5': [498.90, 708.66], 'b6': [354.33, 498.90],
- 'b7': [249.45, 354.33], 'b8': [175.75, 249.45],
- 'b9': [124.72, 175.75], 'b10': [87.87, 124.72],
- 'c0': [2599.37, 3676.54], 'c1': [1836.85, 2599.37],
- 'c2': [1298.27, 1836.85], 'c3': [918.43, 1298.27],
- 'c4': [649.13, 918.43], 'c5': [459.21, 649.13],
- 'c6': [323.15, 459.21], 'c7': [229.61, 323.15],
- 'c8': [161.57, 229.61], 'c9': [113.39, 161.57],
- 'c10': [79.37, 113.39], 'dl': [311.81, 623.62],
- 'letter': [612, 792],
- 'government-letter': [576, 756],
- 'legal': [612, 1008],
- 'junior-legal': [576, 360],
- 'ledger': [1224, 792],
- 'tabloid': [792, 1224],
- 'credit-card': [153, 243]
- };
-
- /**
- * jsPDF's Internal PubSub Implementation.
- * See mrrio.github.io/jsPDF/doc/symbols/PubSub.html
- * Backward compatible rewritten on 2014 by
- * Diego Casorran, https://github.com/diegocr
- *
- * @class
- * @name PubSub
- */
- function PubSub(context) {
- var topics = {};
-
- this.subscribe = function (topic, callback, once) {
- if (typeof callback !== 'function') {
- return false;
- }
-
- if (!topics.hasOwnProperty(topic)) {
- topics[topic] = {};
- }
-
- var id = Math.random().toString(35);
- topics[topic][id] = [callback, !!once];
-
- return id;
- };
-
- this.unsubscribe = function (token) {
- for (var topic in topics) {
- if (topics[topic][token]) {
- delete topics[topic][token];
- return true;
- }
- }
- return false;
- };
-
- this.publish = function (topic) {
- if (topics.hasOwnProperty(topic)) {
- var args = Array.prototype.slice.call(arguments, 1),
- idr = [];
-
- for (var id in topics[topic]) {
- var sub = topics[topic][id];
- try {
- sub[0].apply(context, args);
- } catch (ex) {
- if (global.console) {
- console.error('jsPDF PubSub Error', ex.message, ex);
- }
- }
- if (sub[1]) idr.push(id);
- }
- if (idr.length) idr.forEach(this.unsubscribe);
- }
- };
- }
-
- /**
- * @constructor
- * @private
- */
- function jsPDF(orientation, unit, format, compressPdf) {
- var options = {};
-
- if ((typeof orientation === 'undefined' ? 'undefined' : babelHelpers.typeof(orientation)) === 'object') {
- options = orientation;
-
- orientation = options.orientation;
- unit = options.unit || unit;
- format = options.format || format;
- compressPdf = options.compress || options.compressPdf || compressPdf;
- }
-
- // Default options
- unit = unit || 'mm';
- format = format || 'a4';
- orientation = ('' + (orientation || 'P')).toLowerCase();
-
- var format_as_string = ('' + format).toLowerCase(),
- compress = !!compressPdf && typeof Uint8Array === 'function',
- textColor = options.textColor || '0 g',
- drawColor = options.drawColor || '0 G',
- activeFontSize = options.fontSize || 16,
- lineHeightProportion = options.lineHeight || 1.15,
- lineWidth = options.lineWidth || 0.200025,
- // 2mm
- objectNumber = 2,
- // 'n' Current object number
- outToPages = !1,
- // switches where out() prints. outToPages true = push to pages obj. outToPages false = doc builder content
- offsets = [],
- // List of offsets. Activated and reset by buildDocument(). Pupulated by various calls buildDocument makes.
- fonts = {},
- // collection of font objects, where key is fontKey - a dynamically created label for a given font.
- fontmap = {},
- // mapping structure fontName > fontStyle > font key - performance layer. See addFont()
- activeFontKey,
- // will be string representing the KEY of the font as combination of fontName + fontStyle
-
- fontStateStack = [],
- //
-
- patterns = {},
- // collection of pattern objects
- patternMap = {},
- // see fonts
-
- gStates = {},
- // collection of graphic state objects
- gStatesMap = {},
- // see fonts
- activeGState = null,
- k,
- // Scale factor
- tmp,
- page = 0,
- currentPage,
- pages = [],
- pagesContext = [],
- // same index as pages and pagedim
- pagedim = [],
- content = [],
- additionalObjects = [],
- lineCapID = 0,
- lineJoinID = 0,
- content_length = 0,
- renderTargets = {},
- renderTargetMap = {},
- renderTargetStack = [],
- pageX,
- pageY,
- pageMatrix,
- // only used for FormObjects
- pageWidth,
- pageHeight,
- pageMode,
- zoomMode,
- layoutMode,
- documentProperties = {
- 'title': '',
- 'subject': '',
- 'author': '',
- 'keywords': '',
- 'creator': ''
- },
- API = {},
- events = new PubSub(API),
-
-
- /////////////////////
- // Private functions
- /////////////////////
- f2 = function f2(number) {
- return number.toFixed(2); // Ie, %.2f
- },
- f3 = function f3(number) {
- return number.toFixed(3); // Ie, %.3f
- },
- padd2 = function padd2(number) {
- return ('0' + parseInt(number)).slice(-2);
- },
- padd2Hex = function padd2Hex(hexString) {
- var s = "00" + hexString;
- return s.substr(s.length - 2);
- },
- out = function out(string) {
- if (outToPages) {
- /* set by beginPage */
- pages[currentPage].push(string);
- } else {
- // +1 for '\n' that will be used to join 'content'
- content_length += string.length + 1;
- content.push(string);
- }
- },
- newObject = function newObject() {
- // Begin a new object
- objectNumber++;
- offsets[objectNumber] = content_length;
- out(objectNumber + ' 0 obj');
- return objectNumber;
- },
-
- // Does not output the object until after the pages have been output.
- // Returns an object containing the objectId and content.
- // All pages have been added so the object ID can be estimated to start right after.
- // This does not modify the current objectNumber; It must be updated after the newObjects are output.
- newAdditionalObject = function newAdditionalObject() {
- var objId = pages.length * 2 + 1;
- objId += additionalObjects.length;
- var obj = { objId: objId, content: '' };
- additionalObjects.push(obj);
- return obj;
- },
-
- // Does not output the object. The caller must call newObjectDeferredBegin(oid) before outputing any data
- newObjectDeferred = function newObjectDeferred() {
- objectNumber++;
- offsets[objectNumber] = function () {
- return content_length;
- };
- return objectNumber;
- },
- newObjectDeferredBegin = function newObjectDeferredBegin(oid) {
- offsets[oid] = content_length;
- },
- putStream = function putStream(str) {
- out('stream');
- out(str);
- out('endstream');
- },
- putPages = function putPages() {
- var n, p, arr, i, deflater, adler32, adler32cs, wPt, hPt;
-
- adler32cs = global.adler32cs || jsPDF.adler32cs;
- if (compress && typeof adler32cs === 'undefined') {
- compress = false;
- }
-
- // outToPages = false as set in endDocument(). out() writes to content.
-
- for (n = 1; n <= page; n++) {
- newObject();
- wPt = (pageWidth = pagedim[n].width) * k;
- hPt = (pageHeight = pagedim[n].height) * k;
- out('<>');
- out('endobj');
-
- // Page content
- p = pages[n].join('\n');
-
- // prepend global change of basis matrix
- // (Now, instead of converting every coordinate to the pdf coordinate system, we apply a matrix
- // that does this job for us (however, texts, images and similar objects must be drawn bottom up))
- p = new Matrix(k, 0, 0, -k, 0, pageHeight).toString() + " cm\n" + p;
-
- newObject();
- if (compress) {
- arr = [];
- i = p.length;
- while (i--) {
- arr[i] = p.charCodeAt(i);
- }
- adler32 = adler32cs.from(p);
- deflater = new Deflater(6);
- deflater.append(new Uint8Array(arr));
- p = deflater.flush();
- arr = new Uint8Array(p.length + 6);
- arr.set(new Uint8Array([120, 156]));
- arr.set(p, 2);
- arr.set(new Uint8Array([adler32 & 0xFF, adler32 >> 8 & 0xFF, adler32 >> 16 & 0xFF, adler32 >> 24 & 0xFF]), p.length + 2);
- p = String.fromCharCode.apply(null, arr);
- out('<>');
- } else {
- out('<>');
- }
- putStream(p);
- out('endobj');
- }
- offsets[1] = content_length;
- out('1 0 obj');
- out('<>');
- out('endobj');
- events.publish('postPutPages');
- },
- putFont = function putFont(font) {
- font.objectNumber = newObject();
- out('<>');
- out('endobj');
- },
- putFonts = function putFonts() {
- for (var fontKey in fonts) {
- if (fonts.hasOwnProperty(fontKey)) {
- putFont(fonts[fontKey]);
- }
- }
- },
- putXObject = function putXObject(xObject) {
- xObject.objectNumber = newObject();
- out("<<");
- out("/Type /XObject");
- out("/Subtype /Form");
- out("/BBox [" + [f2(xObject.x), f2(xObject.y), f2(xObject.x + xObject.width), f2(xObject.y + xObject.height)].join(" ") + "]");
- out("/Matrix [" + xObject.matrix.toString() + "]");
- // TODO: /Resources
-
- var p = xObject.pages[1].join("\n");
- out("/Length " + p.length);
-
- out(">>");
- putStream(p);
- out("endobj");
- },
- putXObjects = function putXObjects() {
- for (var xObjectKey in renderTargets) {
- if (renderTargets.hasOwnProperty(xObjectKey)) {
- putXObject(renderTargets[xObjectKey]);
- }
- }
- },
- interpolateAndEncodeRGBStream = function interpolateAndEncodeRGBStream(colors, numberSamples) {
- var tValues = [];
- var t;
- var dT = 1.0 / (numberSamples - 1);
- for (t = 0.0; t < 1.0; t += dT) {
- tValues.push(t);
- }
- tValues.push(1.0);
-
- // add first and last control point if not present
- if (colors[0].offset != 0.0) {
- var c0 = {
- offset: 0.0,
- color: colors[0].color
- };
- colors.unshift(c0);
- }
- if (colors[colors.length - 1].offset != 1.0) {
- var c1 = {
- offset: 1.0,
- color: colors[colors.length - 1].color
- };
- colors.push(c1);
- }
-
- var out = "";
- var index = 0;
-
- for (var i = 0; i < tValues.length; i++) {
- t = tValues[i];
-
- while (t > colors[index + 1].offset) {
- index++;
- }var a = colors[index].offset;
- var b = colors[index + 1].offset;
- var d = (t - a) / (b - a);
-
- var aColor = colors[index].color;
- var bColor = colors[index + 1].color;
-
- out += padd2Hex(Math.round((1 - d) * aColor[0] + d * bColor[0]).toString(16)) + padd2Hex(Math.round((1 - d) * aColor[1] + d * bColor[1]).toString(16)) + padd2Hex(Math.round((1 - d) * aColor[2] + d * bColor[2]).toString(16));
- }
- return out.trim();
- },
- putShadingPattern = function putShadingPattern(pattern, numberSamples) {
- /*
- Axial patterns shade between the two points specified in coords, radial patterns between the inner
- and outer circle.
- The user can specify an array (colors) that maps t-Values in [0, 1] to RGB colors. These are now
- interpolated to equidistant samples and written to pdf as a sample (type 0) function.
- */
+ do {
+ // pack three octets into four hexets
+ o1 = data.charCodeAt(i++);
+ o2 = data.charCodeAt(i++);
+ o3 = data.charCodeAt(i++);
+ bits = o1 << 16 | o2 << 8 | o3;
+ h1 = bits >> 18 & 0x3f;
+ h2 = bits >> 12 & 0x3f;
+ h3 = bits >> 6 & 0x3f;
+ h4 = bits & 0x3f; // use hexets to index into b64, and append result to encoded string
+
+ tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
+ } while (i < data.length);
+
+ enc = tmp_arr.join("");
+ var r = data.length % 3;
+ return (r ? enc.slice(0, r - 3) : enc) + "===".slice(r || 3);
+ };
+ }
- // The number of color samples that should be used to describe the shading.
- // The higher, the more accurate the gradient will be.
- numberSamples || (numberSamples = 21);
-
- var funcObjectNumber = newObject();
- var stream = interpolateAndEncodeRGBStream(pattern.colors, numberSamples);
- out("<< /FunctionType 0");
- out("/Domain [0.0 1.0]");
- out("/Size [" + numberSamples + "]");
- out("/BitsPerSample 8");
- out("/Range [0.0 1.0 0.0 1.0 0.0 1.0]");
- out("/Decode [0.0 1.0 0.0 1.0 0.0 1.0]");
- out("/Length " + stream.length);
- // The stream is Hex encoded
- out("/Filter /ASCIIHexDecode");
- out(">>");
- putStream(stream);
- out("endobj");
-
- pattern.objectNumber = newObject();
- out("<< /ShadingType " + pattern.type);
- out("/ColorSpace /DeviceRGB");
-
- var coords = "/Coords [" + f3(parseFloat(pattern.coords[0])) + " " // x1
- + f3(parseFloat(pattern.coords[1])) + " "; // y1
- if (pattern.type === 2) {
- // axial
- coords += f3(parseFloat(pattern.coords[2])) + " " // x2
- + f3(parseFloat(pattern.coords[3])); // y2
- } else {
- // radial
- coords += f3(parseFloat(pattern.coords[2])) + " " // r1
- + f3(parseFloat(pattern.coords[3])) + " " // x2
- + f3(parseFloat(pattern.coords[4])) + " " // y2
- + f3(parseFloat(pattern.coords[5])); // r2
- }
- coords += "]";
- out(coords);
-
- if (pattern.matrix) {
- out("/Matrix [" + pattern.matrix.toString() + "]");
- }
-
- out("/Function " + funcObjectNumber + " 0 R");
- out("/Extend [true true]");
- out(">>");
- out("endobj");
- },
- putTilingPattern = function putTilingPattern(pattern) {
- var resourcesObjectNumber = newObject();
- out("<<");
- putResourceDictionary();
- out(">>");
- out("endobj");
-
- pattern.objectNumber = newObject();
- out("<< /Type /Pattern");
- out("/PatternType 1"); // tiling pattern
- out("/PaintType 1"); // colored tiling pattern
- out("/TilingType 1"); // constant spacing
- out("/BBox [" + pattern.boundingBox.map(f3).join(" ") + "]");
- out("/XStep " + f3(pattern.xStep));
- out("/YStep " + f3(pattern.yStep));
- out("/Length " + pattern.stream.length);
- out("/Resources " + resourcesObjectNumber + " 0 R"); // TODO: resources
- pattern.matrix && out("/Matrix [" + pattern.matrix.toString() + "]");
-
- out(">>");
-
- putStream(pattern.stream);
-
- out("endobj");
- },
- putPatterns = function putPatterns() {
- var patternKey;
- for (patternKey in patterns) {
- if (patterns.hasOwnProperty(patternKey)) {
- if (patterns[patternKey] instanceof API.ShadingPattern) {
- putShadingPattern(patterns[patternKey]);
- } else if (patterns[patternKey] instanceof API.TilingPattern) {
- putTilingPattern(patterns[patternKey]);
- }
- }
- }
- },
- putGState = function putGState(gState) {
- gState.objectNumber = newObject();
- out("<<");
- for (var p in gState) {
- switch (p) {
- case "opacity":
- out("/ca " + f2(gState[p]));
- break;
- }
- }
- out(">>");
- out("endobj");
- },
- putGStates = function putGStates() {
- var gStateKey;
- for (gStateKey in gStates) {
- if (gStates.hasOwnProperty(gStateKey)) {
- putGState(gStates[gStateKey]);
- }
- }
- },
- putXobjectDict = function putXobjectDict() {
- for (var xObjectKey in renderTargets) {
- if (renderTargets.hasOwnProperty(xObjectKey) && renderTargets[xObjectKey].objectNumber >= 0) {
- out("/" + xObjectKey + " " + renderTargets[xObjectKey].objectNumber + " 0 R");
- }
- }
-
- events.publish('putXobjectDict');
- },
- putShadingPatternDict = function putShadingPatternDict() {
- for (var patternKey in patterns) {
- if (patterns.hasOwnProperty(patternKey) && patterns[patternKey] instanceof API.ShadingPattern && patterns[patternKey].objectNumber >= 0) {
- out("/" + patternKey + " " + patterns[patternKey].objectNumber + " 0 R");
- }
- }
-
- events.publish("putShadingPatternDict");
- },
- putTilingPatternDict = function putTilingPatternDict() {
- for (var patternKey in patterns) {
- if (patterns.hasOwnProperty(patternKey) && patterns[patternKey] instanceof API.TilingPattern && patterns[patternKey].objectNumber >= 0) {
- out("/" + patternKey + " " + patterns[patternKey].objectNumber + " 0 R");
- }
- }
-
- events.publish("putTilingPatternDict");
- },
- putGStatesDict = function putGStatesDict() {
- var gStateKey;
- for (gStateKey in gStates) {
- if (gStates.hasOwnProperty(gStateKey) && gStates[gStateKey].objectNumber >= 0) {
- out("/" + gStateKey + " " + gStates[gStateKey].objectNumber + " 0 R");
- }
- }
-
- events.publish("putGStateDict");
- },
- putResourceDictionary = function putResourceDictionary() {
- out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
- out('/Font <<');
- // Do this for each font, the '1' bit is the index of the font
- for (var fontKey in fonts) {
- if (fonts.hasOwnProperty(fontKey)) {
- out('/' + fontKey + ' ' + fonts[fontKey].objectNumber + ' 0 R');
- }
- }
- out('>>');
-
- out("/Shading <<");
- putShadingPatternDict();
- out(">>");
-
- out("/Pattern <<");
- putTilingPatternDict();
- out(">>");
-
- out("/ExtGState <<");
- putGStatesDict();
- out('>>');
-
- out('/XObject <<');
- putXobjectDict();
- out('>>');
- },
- putResources = function putResources() {
- putFonts();
- putGStates();
- putXObjects();
- putPatterns();
- events.publish('putResources');
- // Resource dictionary
- offsets[2] = content_length;
- out('2 0 obj');
- out('<<');
- putResourceDictionary();
- out('>>');
- out('endobj');
- events.publish('postPutResources');
- },
- putAdditionalObjects = function putAdditionalObjects() {
- events.publish('putAdditionalObjects');
- for (var i = 0; i < additionalObjects.length; i++) {
- var obj = additionalObjects[i];
- offsets[obj.objId] = content_length;
- out(obj.objId + ' 0 obj');
- out(obj.content);
- out('endobj');
- }
- objectNumber += additionalObjects.length;
- events.publish('postPutAdditionalObjects');
- },
- addToFontDictionary = function addToFontDictionary(fontKey, fontName, fontStyle) {
- // this is mapping structure for quick font key lookup.
- // returns the KEY of the font (ex: "F1") for a given
- // pair of font name and type (ex: "Arial". "Italic")
- if (!fontmap.hasOwnProperty(fontName)) {
- fontmap[fontName] = {};
- }
- fontmap[fontName][fontStyle] = fontKey;
- },
-
- /**
- * FontObject describes a particular font as member of an instnace of jsPDF
- *
- * It's a collection of properties like 'id' (to be used in PDF stream),
- * 'fontName' (font's family name), 'fontStyle' (font's style variant label)
- *
- * @public
- * @property id {String} PDF-document-instance-specific label assinged to the font.
- * @property PostScriptName {String} PDF specification full name for the font
- * @property encoding {Object} Encoding_name-to-Font_metrics_object mapping.
- * @name FontObject
- */
- addFont = function addFont(PostScriptName, fontName, fontStyle, encoding) {
- var fontKey = 'F' + (Object.keys(fonts).length + 1).toString(10),
-
- // This is FontObject
- font = fonts[fontKey] = {
- 'id': fontKey,
- 'PostScriptName': PostScriptName,
- 'fontName': fontName,
- 'fontStyle': fontStyle,
- 'encoding': encoding,
- 'metadata': {}
- };
- addToFontDictionary(fontKey, fontName, fontStyle);
- events.publish('addFont', font);
-
- return fontKey;
- },
- addFonts = function addFonts() {
-
- var HELVETICA = "helvetica",
- TIMES = "times",
- COURIER = "courier",
- NORMAL = "normal",
- BOLD = "bold",
- ITALIC = "italic",
- BOLD_ITALIC = "bolditalic",
- encoding = 'StandardEncoding',
- ZAPF = "zapfdingbats",
- standardFonts = [['Helvetica', HELVETICA, NORMAL], ['Helvetica-Bold', HELVETICA, BOLD], ['Helvetica-Oblique', HELVETICA, ITALIC], ['Helvetica-BoldOblique', HELVETICA, BOLD_ITALIC], ['Courier', COURIER, NORMAL], ['Courier-Bold', COURIER, BOLD], ['Courier-Oblique', COURIER, ITALIC], ['Courier-BoldOblique', COURIER, BOLD_ITALIC], ['Times-Roman', TIMES, NORMAL], ['Times-Bold', TIMES, BOLD], ['Times-Italic', TIMES, ITALIC], ['Times-BoldItalic', TIMES, BOLD_ITALIC], ['ZapfDingbats', ZAPF]];
-
- for (var i = 0, l = standardFonts.length; i < l; i++) {
- var fontKey = addFont(standardFonts[i][0], standardFonts[i][1], standardFonts[i][2], encoding);
-
- // adding aliases for standard fonts, this time matching the capitalization
- var parts = standardFonts[i][0].split('-');
- addToFontDictionary(fontKey, parts[0], parts[1] || '');
- }
- events.publish('addFonts', { fonts: fonts, dictionary: fontmap });
- },
- matrixMult = function matrixMult(m1, m2) {
- return new Matrix(m1.a * m2.a + m1.b * m2.c, m1.a * m2.b + m1.b * m2.d, m1.c * m2.a + m1.d * m2.c, m1.c * m2.b + m1.d * m2.d, m1.e * m2.a + m1.f * m2.c + m2.e, m1.e * m2.b + m1.f * m2.d + m2.f);
- },
- Matrix = function Matrix(a, b, c, d, e, f) {
- this.a = a;
- this.b = b;
- this.c = c;
- this.d = d;
- this.e = e;
- this.f = f;
- };
-
- Matrix.prototype = {
- toString: function toString() {
- return [f3(this.a), f3(this.b), f3(this.c), f3(this.d), f3(this.e), f3(this.f)].join(" ");
- }
- };
-
- var unitMatrix = new Matrix(1, 0, 0, 1, 0, 0),
-
-
- // Used (1) to save the current stream state to the XObjects stack and (2) to save completed form
- // objects in the xObjects map.
- RenderTarget = function RenderTarget() {
- this.page = page;
- this.currentPage = currentPage;
- this.pages = pages.slice(0);
- this.pagedim = pagedim.slice(0);
- this.pagesContext = pagesContext.slice(0);
- this.x = pageX;
- this.y = pageY;
- this.matrix = pageMatrix;
- this.width = pageWidth;
- this.height = pageHeight;
-
- this.id = ""; // set by endFormObject()
- this.objectNumber = -1; // will be set by putXObject()
- };
-
- RenderTarget.prototype = {
- restore: function restore() {
- page = this.page;
- currentPage = this.currentPage;
- pagesContext = this.pagesContext;
- pagedim = this.pagedim;
- pages = this.pages;
- pageX = this.x;
- pageY = this.y;
- pageMatrix = this.matrix;
- pageWidth = this.width;
- pageHeight = this.height;
- }
- };
-
- var beginNewRenderTarget = function beginNewRenderTarget(x, y, width, height, matrix) {
- // save current state
- renderTargetStack.push(new RenderTarget());
-
- // clear pages
- page = currentPage = 0;
- pages = [];
- pageX = x;
- pageY = y;
-
- pageMatrix = matrix;
-
- beginPage(width, height);
- },
- endFormObject = function endFormObject(key) {
- // only add it if it is not already present (the keys provided by the user must be unique!)
- if (renderTargetMap[key]) return;
-
- // save the created xObject
- var newXObject = new RenderTarget();
-
- var xObjectId = 'Xo' + (Object.keys(renderTargets).length + 1).toString(10);
- newXObject.id = xObjectId;
-
- renderTargetMap[key] = xObjectId;
- renderTargets[xObjectId] = newXObject;
-
- events.publish('addFormObject', newXObject);
-
- // restore state from stack
- renderTargetStack.pop().restore();
- },
-
-
- /**
- * Adds a new pattern for later use.
- * @param {String} key The key by it can be referenced later. The keys must be unique!
- * @param {API.Pattern} pattern The pattern
- */
- addPattern = function addPattern(key, pattern) {
- // only add it if it is not already present (the keys provided by the user must be unique!)
- if (patternMap[key]) return;
+ if (typeof global.atob === "undefined") {
+ global.atob = function (data) {
+ // discuss at: http://phpjs.org/functions/base64_decode/
+ // original by: Tyler Akins (http://rumkin.com)
+ // improved by: Thunder.m
+ // improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // input by: Aman Gupta
+ // input by: Brett Zamir (http://brett-zamir.me)
+ // bugfixed by: Onno Marsman
+ // bugfixed by: Pellentesque Malesuada
+ // bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // example 1: base64_decode('S2V2aW4gdmFuIFpvbm5ldmVsZA==');
+ // returns 1: 'Kevin van Zonneveld'
+ var o1,
+ o2,
+ o3,
+ h1,
+ h2,
+ h3,
+ h4,
+ bits,
+ i = 0,
+ ac = 0,
+ dec = "",
+ tmp_arr = [];
+
+ if (!data) {
+ return data;
+ }
- var prefix = pattern instanceof API.ShadingPattern ? "Sh" : "P";
- var patternKey = prefix + (Object.keys(patterns).length + 1).toString(10);
- pattern.id = patternKey;
+ data += "";
+
+ do {
+ // unpack four hexets into three octets using index points in b64
+ h1 = b64.indexOf(data.charAt(i++));
+ h2 = b64.indexOf(data.charAt(i++));
+ h3 = b64.indexOf(data.charAt(i++));
+ h4 = b64.indexOf(data.charAt(i++));
+ bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
+ o1 = bits >> 16 & 0xff;
+ o2 = bits >> 8 & 0xff;
+ o3 = bits & 0xff;
+
+ if (h3 == 64) {
+ tmp_arr[ac++] = String.fromCharCode(o1);
+ } else if (h4 == 64) {
+ tmp_arr[ac++] = String.fromCharCode(o1, o2);
+ } else {
+ tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
+ }
+ } while (i < data.length);
- patternMap[key] = patternKey;
- patterns[patternKey] = pattern;
+ dec = tmp_arr.join("");
+ return dec;
+ };
+ }
- events.publish('addPattern', pattern);
- },
+ if (!Array.prototype.map) {
+ Array.prototype.map = function (fun
+ /*, thisArg */
+ ) {
+ if (this === void 0 || this === null || typeof fun !== "function") throw new TypeError();
+ var t = Object(this),
+ len = t.length >>> 0,
+ res = new Array(len);
+ var thisArg = arguments.length > 1 ? arguments[1] : void 0;
+
+ for (var i = 0; i < len; i++) {
+ // NOTE: Absolute correctness would demand Object.defineProperty
+ // be used. But this method is fairly new, and failure is
+ // possible only if Object.prototype or Array.prototype
+ // has a property |i| (very unlikely), so use a less-correct
+ // but more portable alternative.
+ if (i in t) res[i] = fun.call(thisArg, t[i], i, t);
+ }
+ return res;
+ };
+ }
- /**
- * Adds a new Graphics State. Duplicates are automatically eliminated.
- * @param {String} key Might also be null, if no later reference to this gState is needed
- * @param {Object} gState The gState object
- */
- addGState = function addGState(key, gState) {
- // only add it if it is not already present (the keys provided by the user must be unique!)
- if (key && gStatesMap[key]) return;
-
- var duplicate = false;
- for (var s in gStates) {
- if (gStates.hasOwnProperty(s)) {
- if (gStates[s].equals(gState)) {
- duplicate = true;
- break;
- }
- }
- }
-
- if (duplicate) {
- gState = gStates[s];
- } else {
- var gStateKey = 'GS' + (Object.keys(gStates).length + 1).toString(10);
- gStates[gStateKey] = gState;
- gState.id = gStateKey;
- }
-
- // several user keys may point to the same GState object
- key && (gStatesMap[key] = gState.id);
-
- events.publish('addGState', gState);
-
- return gState;
- },
- SAFE = function __safeCall(fn) {
- fn.foo = function __safeCallWrapper() {
- try {
- return fn.apply(this, arguments);
- } catch (e) {
- var stack = e.stack || '';
- if (~stack.indexOf(' at ')) stack = stack.split(" at ")[1];
- var m = "Error in function " + stack.split("\n")[0].split('<')[0] + ": " + e.message;
- if (global.console) {
- global.console.error(m, e);
- if (global.alert) alert(m);
- } else {
- throw new Error(m);
- }
- }
- };
- fn.foo.bar = fn;
- return fn.foo;
- },
- to8bitStream = function to8bitStream(text, flags) {
- /**
- * PDF 1.3 spec:
- * "For text strings encoded in Unicode, the first two bytes must be 254 followed by
- * 255, representing the Unicode byte order marker, U+FEFF. (This sequence conflicts
- * with the PDFDocEncoding character sequence thorn ydieresis, which is unlikely
- * to be a meaningful beginning of a word or phrase.) The remainder of the
- * string consists of Unicode character codes, according to the UTF-16 encoding
- * specified in the Unicode standard, version 2.0. Commonly used Unicode values
- * are represented as 2 bytes per character, with the high-order byte appearing first
- * in the string."
- *
- * In other words, if there are chars in a string with char code above 255, we
- * recode the string to UCS2 BE - string doubles in length and BOM is prepended.
- *
- * HOWEVER!
- * Actual *content* (body) text (as opposed to strings used in document properties etc)
- * does NOT expect BOM. There, it is treated as a literal GID (Glyph ID)
- *
- * Because of Adobe's focus on "you subset your fonts!" you are not supposed to have
- * a font that maps directly Unicode (UCS2 / UTF16BE) code to font GID, but you could
- * fudge it with "Identity-H" encoding and custom CIDtoGID map that mimics Unicode
- * code page. There, however, all characters in the stream are treated as GIDs,
- * including BOM, which is the reason we need to skip BOM in content text (i.e. that
- * that is tied to a font).
- *
- * To signal this "special" PDFEscape / to8bitStream handling mode,
- * API.text() function sets (unless you overwrite it with manual values
- * given to API.text(.., flags) )
- * flags.autoencode = true
- * flags.noBOM = true
- *
- * ===================================================================================
- * `flags` properties relied upon:
- * .sourceEncoding = string with encoding label.
- * "Unicode" by default. = encoding of the incoming text.
- * pass some non-existing encoding name
- * (ex: 'Do not touch my strings! I know what I am doing.')
- * to make encoding code skip the encoding step.
- * .outputEncoding = Either valid PDF encoding name
- * (must be supported by jsPDF font metrics, otherwise no encoding)
- * or a JS object, where key = sourceCharCode, value = outputCharCode
- * missing keys will be treated as: sourceCharCode === outputCharCode
- * .noBOM
- * See comment higher above for explanation for why this is important
- * .autoencode
- * See comment higher above for explanation for why this is important
- */
+ if (!Array.isArray) {
+ Array.isArray = function (arg) {
+ return Object.prototype.toString.call(arg) === "[object Array]";
+ };
+ }
- var i, l, sourceEncoding, encodingBlock, outputEncoding, newtext, isUnicode, ch, bch;
-
- flags = flags || {};
- sourceEncoding = flags.sourceEncoding || 'Unicode';
- outputEncoding = flags.outputEncoding;
-
- // This 'encoding' section relies on font metrics format
- // attached to font objects by, among others,
- // "Willow Systems' standard_font_metrics plugin"
- // see jspdf.plugin.standard_font_metrics.js for format
- // of the font.metadata.encoding Object.
- // It should be something like
- // .encoding = {'codePages':['WinANSI....'], 'WinANSI...':{code:code, ...}}
- // .widths = {0:width, code:width, ..., 'fof':divisor}
- // .kerning = {code:{previous_char_code:shift, ..., 'fof':-divisor},...}
- if ((flags.autoencode || outputEncoding) && fonts[activeFontKey].metadata && fonts[activeFontKey].metadata[sourceEncoding] && fonts[activeFontKey].metadata[sourceEncoding].encoding) {
- encodingBlock = fonts[activeFontKey].metadata[sourceEncoding].encoding;
-
- // each font has default encoding. Some have it clearly defined.
- if (!outputEncoding && fonts[activeFontKey].encoding) {
- outputEncoding = fonts[activeFontKey].encoding;
- }
-
- // Hmmm, the above did not work? Let's try again, in different place.
- if (!outputEncoding && encodingBlock.codePages) {
- outputEncoding = encodingBlock.codePages[0]; // let's say, first one is the default
- }
-
- if (typeof outputEncoding === 'string') {
- outputEncoding = encodingBlock[outputEncoding];
- }
- // we want output encoding to be a JS Object, where
- // key = sourceEncoding's character code and
- // value = outputEncoding's character code.
- if (outputEncoding) {
- isUnicode = false;
- newtext = [];
- for (i = 0, l = text.length; i < l; i++) {
- ch = outputEncoding[text.charCodeAt(i)];
- if (ch) {
- newtext.push(String.fromCharCode(ch));
- } else {
- newtext.push(text[i]);
- }
-
- // since we are looping over chars anyway, might as well
- // check for residual unicodeness
- if (newtext[i].charCodeAt(0) >> 8) {
- /* more than 255 */
- isUnicode = true;
- }
- }
- text = newtext.join('');
- }
- }
-
- i = text.length;
- // isUnicode may be set to false above. Hence the triple-equal to undefined
- while (isUnicode === undefined && i !== 0) {
- if (text.charCodeAt(i - 1) >> 8) {
- /* more than 255 */
- isUnicode = true;
- }
- i--;
- }
- if (!isUnicode) {
- return text;
- }
-
- newtext = flags.noBOM ? [] : [254, 255];
- for (i = 0, l = text.length; i < l; i++) {
- ch = text.charCodeAt(i);
- bch = ch >> 8; // divide by 256
- if (bch >> 8) {
- /* something left after dividing by 256 second time */
- throw new Error("Character at position " + i + " of string '" + text + "' exceeds 16bits. Cannot be encoded into UCS-2 BE");
- }
- newtext.push(bch);
- newtext.push(ch - (bch << 8));
- }
- return String.fromCharCode.apply(undefined, newtext);
- },
- pdfEscape = function pdfEscape(text, flags) {
- /**
- * Replace '/', '(', and ')' with pdf-safe versions
- *
- * Doing to8bitStream does NOT make this PDF display unicode text. For that
- * we also need to reference a unicode font and embed it - royal pain in the rear.
- *
- * There is still a benefit to to8bitStream - PDF simply cannot handle 16bit chars,
- * which JavaScript Strings are happy to provide. So, while we still cannot display
- * 2-byte characters property, at least CONDITIONALLY converting (entire string containing)
- * 16bit chars to (USC-2-BE) 2-bytes per char + BOM streams we ensure that entire PDF
- * is still parseable.
- * This will allow immediate support for unicode in document properties strings.
- */
- return to8bitStream(text, flags).replace(/\\/g, '\\\\').replace(/\(/g, '\\(').replace(/\)/g, '\\)');
- },
- putInfo = function putInfo() {
- out('/Producer (jsPDF ' + jsPDF.version + ')');
- for (var key in documentProperties) {
- if (documentProperties.hasOwnProperty(key) && documentProperties[key]) {
- out('/' + key.substr(0, 1).toUpperCase() + key.substr(1) + ' (' + pdfEscape(documentProperties[key]) + ')');
- }
- }
- var created = new Date(),
- tzoffset = created.getTimezoneOffset(),
- tzsign = tzoffset < 0 ? '+' : '-',
- tzhour = Math.floor(Math.abs(tzoffset / 60)),
- tzmin = Math.abs(tzoffset % 60),
- tzstr = [tzsign, padd2(tzhour), "'", padd2(tzmin), "'"].join('');
- out(['/CreationDate (D:', created.getFullYear(), padd2(created.getMonth() + 1), padd2(created.getDate()), padd2(created.getHours()), padd2(created.getMinutes()), padd2(created.getSeconds()), tzstr, ')'].join(''));
- },
- putCatalog = function putCatalog() {
- out('/Type /Catalog');
- out('/Pages 1 0 R');
- // PDF13ref Section 7.2.1
- if (!zoomMode) zoomMode = 'fullwidth';
- switch (zoomMode) {
- case 'fullwidth':
- out('/OpenAction [3 0 R /FitH null]');break;
- case 'fullheight':
- out('/OpenAction [3 0 R /FitV null]');break;
- case 'fullpage':
- out('/OpenAction [3 0 R /Fit]');break;
- case 'original':
- out('/OpenAction [3 0 R /XYZ null null 1]');break;
- default:
- var pcn = '' + zoomMode;
- if (pcn.substr(pcn.length - 1) === '%') zoomMode = parseInt(zoomMode) / 100;
- if (typeof zoomMode === 'number') {
- out('/OpenAction [3 0 R /XYZ null null ' + f2(zoomMode) + ']');
- }
- }
- if (!layoutMode) layoutMode = 'continuous';
- switch (layoutMode) {
- case 'continuous':
- out('/PageLayout /OneColumn');break;
- case 'single':
- out('/PageLayout /SinglePage');break;
- case 'two':
- case 'twoleft':
- out('/PageLayout /TwoColumnLeft');break;
- case 'tworight':
- out('/PageLayout /TwoColumnRight');break;
- }
- if (pageMode) {
- /**
- * A name object specifying how the document should be displayed when opened:
- * UseNone : Neither document outline nor thumbnail images visible -- DEFAULT
- * UseOutlines : Document outline visible
- * UseThumbs : Thumbnail images visible
- * FullScreen : Full-screen mode, with no menu bar, window controls, or any other window visible
- */
- out('/PageMode /' + pageMode);
- }
- events.publish('putCatalog');
- },
- putTrailer = function putTrailer() {
- out('/Size ' + (objectNumber + 1));
- out('/Root ' + objectNumber + ' 0 R');
- out('/Info ' + (objectNumber - 1) + ' 0 R');
- },
- beginPage = function beginPage(width, height) {
- // Dimensions are stored as user units and converted to points on output
- var orientation = typeof height === 'string' && height.toLowerCase();
- if (typeof width === 'string') {
- var format = width.toLowerCase();
- if (pageFormats.hasOwnProperty(format)) {
- width = pageFormats[format][0] / k;
- height = pageFormats[format][1] / k;
- }
- }
- if (Array.isArray(width)) {
- height = width[1];
- width = width[0];
- }
- //if (orientation) {
- // switch(orientation.substr(0,1)) {
- // case 'l': if (height > width ) orientation = 's'; break;
- // case 'p': if (width > height ) orientation = 's'; break;
- // }
- // TODO: What is the reason for this (for me it only seems to raise bugs)?
- // if (orientation === 's') { tmp = width; width = height; height = tmp; }
- //}
- outToPages = true;
- pages[++page] = [];
- pagedim[page] = {
- width: Number(width) || pageWidth,
- height: Number(height) || pageHeight
- };
- pagesContext[page] = {};
- _setPage(page);
- },
- _addPage = function _addPage() {
- beginPage.apply(this, arguments);
- // Set line width
- out(f2(lineWidth) + ' w');
- // Set draw color
- out(drawColor);
- // resurrecting non-default line caps, joins
- if (lineCapID !== 0) {
- out(lineCapID + ' J');
- }
- if (lineJoinID !== 0) {
- out(lineJoinID + ' j');
- }
- events.publish('addPage', { pageNumber: page });
- },
- _deletePage = function _deletePage(n) {
- if (n > 0 && n <= page) {
- pages.splice(n, 1);
- pagedim.splice(n, 1);
- page--;
- if (currentPage > page) {
- currentPage = page;
- }
- this.setPage(currentPage);
- }
- },
- _setPage = function _setPage(n) {
- if (n > 0 && n <= page) {
- currentPage = n;
- pageWidth = pagedim[n].width;
- pageHeight = pagedim[n].height;
- }
- },
-
- /**
- * Returns a document-specific font key - a label assigned to a
- * font name + font type combination at the time the font was added
- * to the font inventory.
- *
- * Font key is used as label for the desired font for a block of text
- * to be added to the PDF document stream.
- * @private
- * @function
- * @param {String} fontName can be undefined on "falthy" to indicate "use current"
- * @param {String} fontStyle can be undefined on "falthy" to indicate "use current"
- * @returns {String} Font key.
- */
- _getFont = function _getFont(fontName, fontStyle) {
- var key;
-
- fontName = fontName !== undefined ? fontName : fonts[activeFontKey].fontName;
- fontStyle = fontStyle !== undefined ? fontStyle : fonts[activeFontKey].fontStyle;
-
- if (fontName !== undefined) {
- fontName = fontName.toLowerCase();
- }
- switch (fontName) {
- case 'sans-serif':
- case 'verdana':
- case 'arial':
- case 'helvetica':
- fontName = 'helvetica';
- break;
- case 'fixed':
- case 'monospace':
- case 'terminal':
- case 'courier':
- fontName = 'courier';
- break;
- case 'serif':
- case 'cursive':
- case 'fantasy':
- default:
- fontName = 'times';
- break;
- }
-
- try {
- // get a string like 'F3' - the KEY corresponding tot he font + type combination.
- key = fontmap[fontName][fontStyle];
- } catch (e) {}
-
- if (!key) {
- //throw new Error("Unable to look up font label for font '" + fontName + "', '"
- //+ fontStyle + "'. Refer to getFontList() for available fonts.");
- key = fontmap['times'][fontStyle];
- if (key == null) {
- key = fontmap['times']['normal'];
- }
- }
- return key;
- },
- buildDocument = function buildDocument() {
-
- outToPages = false; // switches out() to content
- objectNumber = 2;
- content = [];
- offsets = [];
- additionalObjects = [];
-
- // putHeader()
- out('%PDF-' + pdfVersion);
-
- putPages();
-
- // Must happen after putPages
- // Modifies current object Id
- putAdditionalObjects();
-
- putResources();
-
- // Info
- newObject();
- out('<<');
- putInfo();
- out('>>');
- out('endobj');
-
- // Catalog
- newObject();
- out('<<');
- putCatalog();
- out('>>');
- out('endobj');
-
- // Cross-ref
- var o = content_length,
- i,
- p = "0000000000";
- out('xref');
- out('0 ' + (objectNumber + 1));
- out(p + ' 65535 f ');
- for (i = 1; i <= objectNumber; i++) {
- var offset = offsets[i];
- if (typeof offset === 'function') {
- out((p + offsets[i]()).slice(-10) + ' 00000 n ');
- } else {
- out((p + offsets[i]).slice(-10) + ' 00000 n ');
- }
- }
- // Trailer
- out('trailer');
- out('<<');
- putTrailer();
- out('>>');
- out('startxref');
- out(o);
- out('%%EOF');
-
- outToPages = true;
-
- return content.join('\n');
- },
- getStyle = function getStyle(style) {
- // see path-painting operators in PDF spec
- var op = 'n'; // none
- if (style === "D") {
- op = 'S'; // stroke
- } else if (style === 'F') {
- op = 'f'; // fill
- } else if (style === 'FD' || style === 'DF') {
- op = 'B'; // both
- } else if (style === 'f' || style === 'f*' || style === 'B' || style === 'B*') {
- /*
- Allow direct use of these PDF path-painting operators:
- - f fill using nonzero winding number rule
- - f* fill using even-odd rule
- - B fill then stroke with fill using non-zero winding number rule
- - B* fill then stroke with fill using even-odd rule
- */
- op = style;
- }
- return op;
- },
-
- // puts the style for the previously drawn path. If a patternKey is provided, the pattern is used to fill
- // the path. Use patternMatrix to transform the pattern to rhe right location.
- putStyle = function putStyle(style, patternKey, patternData) {
- style = getStyle(style);
-
- // stroking / filling / both the path
- if (!patternKey) {
- out(style);
- return;
- }
-
- patternData || (patternData = unitMatrix);
-
- var patternId = patternMap[patternKey];
- var pattern = patterns[patternId];
-
- if (pattern instanceof API.ShadingPattern) {
- out("q");
- out("W " + style);
-
- if (pattern.gState) {
- API.setGState(pattern.gState);
- }
-
- out(patternData.toString() + " cm");
- out("/" + patternId + " sh");
- out("Q");
- } else if (pattern instanceof API.TilingPattern) {
- // pdf draws patterns starting at the bottom left corner and they are not affected by the global transformation,
- // so we must flip them
- var matrix = new Matrix(1, 0, 0, -1, 0, pageHeight);
-
- if (patternData.matrix) {
- matrix = matrixMult(patternData.matrix || unitMatrix, matrix);
-
- // we cannot apply a matrix to the pattern on use so we must abuse the pattern matrix and create new instances
- // for each use
- patternId = pattern.createClone(patternKey, patternData.boundingBox, patternData.xStep, patternData.yStep, matrix).id;
- }
-
- out("q");
- out("/Pattern cs");
- out("/" + patternId + " scn");
-
- if (pattern.gState) {
- API.setGState(pattern.gState);
- }
-
- out(style);
- out("Q");
- }
- },
- getArrayBuffer = function getArrayBuffer() {
- var data = buildDocument(),
- len = data.length,
- ab = new ArrayBuffer(len),
- u8 = new Uint8Array(ab);
-
- while (len--) {
- u8[len] = data.charCodeAt(len);
- }return ab;
- },
- getBlob = function getBlob() {
- return new Blob([getArrayBuffer()], { type: "application/pdf" });
- },
-
- /**
- * Generates the PDF document.
- *
- * If `type` argument is undefined, output is raw body of resulting PDF returned as a string.
- *
- * @param {String} type A string identifying one of the possible output types.
- * @param {Object} options An object providing some additional signalling to PDF generator.
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name output
- */
- _output = SAFE(function (type, options) {
- var datauri = ('' + type).substr(0, 6) === 'dataur' ? 'data:application/pdf;base64,' + btoa(buildDocument()) : 0;
-
- switch (type) {
- case undefined:
- return buildDocument();
- case 'save':
- if (navigator.getUserMedia) {
- if (global.URL === undefined || global.URL.createObjectURL === undefined) {
- return API.output('dataurlnewwindow');
- }
- }
- saveAs(getBlob(), options);
- if (typeof saveAs.unload === 'function') {
- if (global.setTimeout) {
- setTimeout(saveAs.unload, 911);
- }
- }
- break;
- case 'arraybuffer':
- return getArrayBuffer();
- case 'blob':
- return getBlob();
- case 'bloburi':
- case 'bloburl':
- // User is responsible of calling revokeObjectURL
- return global.URL && global.URL.createObjectURL(getBlob()) || void 0;
- case 'datauristring':
- case 'dataurlstring':
- return datauri;
- case 'dataurlnewwindow':
- var nW = global.open(datauri);
- if (nW || typeof safari === "undefined") return nW;
- /* pass through */
- case 'datauri':
- case 'dataurl':
- return global.document.location.href = datauri;
- default:
- throw new Error('Output type "' + type + '" is not supported.');
- }
- // @TODO: Add different output options
- });
-
- switch (unit) {
- case 'pt':
- k = 1;break;
- case 'mm':
- k = 72 / 25.4000508;break;
- case 'cm':
- k = 72 / 2.54000508;break;
- case 'in':
- k = 72;break;
- case 'px':
- k = 96 / 72;break;
- case 'pc':
- k = 12;break;
- case 'em':
- k = 12;break;
- case 'ex':
- k = 6;break;
- default:
- throw 'Invalid unit: ' + unit;
- }
-
- //---------------------------------------
- // Public API
-
- /**
- * Object exposing internal API to plugins
- * @public
- */
- API.internal = {
- 'pdfEscape': pdfEscape,
- 'getStyle': getStyle,
- /**
- * Returns {FontObject} describing a particular font.
- * @public
- * @function
- * @param {String} fontName (Optional) Font's family name
- * @param {String} fontStyle (Optional) Font's style variation name (Example:"Italic")
- * @returns {FontObject}
- */
- 'getFont': function getFont() {
- return fonts[_getFont.apply(API, arguments)];
- },
- 'getFontSize': function getFontSize() {
- return activeFontSize;
- },
- 'getLineHeight': function getLineHeight() {
- return activeFontSize * lineHeightProportion;
- },
- 'write': function write(string1 /*, string2, string3, etc */) {
- out(arguments.length === 1 ? string1 : Array.prototype.join.call(arguments, ' '));
- },
- 'getCoordinateString': function getCoordinateString(value) {
- return f2(value);
- },
- 'getVerticalCoordinateString': function getVerticalCoordinateString(value) {
- return f2(value);
- },
- 'collections': {},
- 'newObject': newObject,
- 'newAdditionalObject': newAdditionalObject,
- 'newObjectDeferred': newObjectDeferred,
- 'newObjectDeferredBegin': newObjectDeferredBegin,
- 'putStream': putStream,
- 'events': events,
- // ratio that you use in multiplication of a given "size" number to arrive to 'point'
- // units of measurement.
- // scaleFactor is set at initialization of the document and calculated against the stated
- // default measurement units for the document.
- // If default is "mm", k is the number that will turn number in 'mm' into 'points' number.
- // through multiplication.
- 'scaleFactor': k,
- 'pageSize': {
- get width() {
- return pageWidth;
- },
- get height() {
- return pageHeight;
- }
- },
- 'output': function output(type, options) {
- return _output(type, options);
- },
- 'getNumberOfPages': function getNumberOfPages() {
- return pages.length - 1;
- },
- 'pages': pages,
- 'out': out,
- 'f2': f2,
- 'getPageInfo': function getPageInfo(pageNumberOneBased) {
- var objId = (pageNumberOneBased - 1) * 2 + 3;
- return { objId: objId, pageNumber: pageNumberOneBased, pageContext: pagesContext[pageNumberOneBased] };
- },
- 'getCurrentPageInfo': function getCurrentPageInfo() {
- var objId = (currentPage - 1) * 2 + 3;
- return { objId: objId, pageNumber: currentPage, pageContext: pagesContext[currentPage] };
- },
- 'getPDFVersion': function getPDFVersion() {
- return pdfVersion;
- }
- };
-
- /**
- * An object representing a pdf graphics state.
- * @param parameters A parameter object that contains all properties this graphics state wants to set.
- * Supported are: opacity
- * @constructor
- */
- API.GState = function (parameters) {
- var supported = "opacity";
- for (var p in parameters) {
- if (parameters.hasOwnProperty(p) && supported.indexOf(p) >= 0) {
- this[p] = parameters[p];
- }
- }
- this.id = ""; // set by addGState()
- this.objectNumber = -1; // will be set by putGState()
-
- this.equals = function (other) {
- var ignore = "id,objectNumber,equals";
- if (!other || (typeof other === 'undefined' ? 'undefined' : babelHelpers.typeof(other)) !== babelHelpers.typeof(this)) return false;
- var count = 0;
- for (var p in this) {
- if (ignore.indexOf(p) >= 0) continue;
- if (this.hasOwnProperty(p) && !other.hasOwnProperty(p)) return false;
- if (this[p] !== other[p]) return false;
- count++;
- }
- for (var p in other) {
- if (other.hasOwnProperty(p) && ignore.indexOf(p) < 0) count--;
- }
- return count === 0;
- };
- };
-
- /**
- * Adds a new {@link GState} for later use {@see setGState}.
- * @param {String} key
- * @param {GState} gState
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name addGState
- */
- API.addGState = function (key, gState) {
- addGState(key, gState);
- return this;
- };
+ if (!Array.prototype.forEach) {
+ Array.prototype.forEach = function (fun, thisArg) {
- /**
- * Adds (and transfers the focus to) new page to the PDF document.
- * @function
- * @returns {jsPDF}
- *
- * @methodOf jsPDF#
- * @name addPage
- */
- API.addPage = function () {
- _addPage.apply(this, arguments);
- return this;
- };
- API.setPage = function () {
- _setPage.apply(this, arguments);
- return this;
- };
- API.insertPage = function (beforePage) {
- this.addPage();
- this.movePage(currentPage, beforePage);
- return this;
- };
- API.movePage = function (targetPage, beforePage) {
- var tmpPagesContext, tmpPagedim, tmpPages, i;
- if (targetPage > beforePage) {
- tmpPages = pages[targetPage];
- tmpPagedim = pagedim[targetPage];
- tmpPagesContext = pagesContext[targetPage];
- for (i = targetPage; i > beforePage; i--) {
- pages[i] = pages[i - 1];
- pagedim[i] = pagedim[i - 1];
- pagesContext[i] = pagesContext[i - 1];
- }
- pages[beforePage] = tmpPages;
- pagedim[beforePage] = tmpPagedim;
- pagesContext[beforePage] = tmpPagesContext;
- this.setPage(beforePage);
- } else if (targetPage < beforePage) {
- tmpPages = pages[targetPage];
- tmpPagedim = pagedim[targetPage];
- tmpPagesContext = pagesContext[targetPage];
- for (i = targetPage; i < beforePage; i++) {
- pages[i] = pages[i + 1];
- pagedim[i] = pagedim[i + 1];
- pagesContext[i] = pagesContext[i + 1];
- }
- pages[beforePage] = tmpPages;
- pagedim[beforePage] = tmpPagedim;
- pagesContext[beforePage] = tmpPagesContext;
- this.setPage(beforePage);
- }
- return this;
- };
-
- API.deletePage = function () {
- _deletePage.apply(this, arguments);
- return this;
- };
- API.setDisplayMode = function (zoom, layout, pmode) {
- zoomMode = zoom;
- layoutMode = layout;
- pageMode = pmode;
- return this;
- };
-
- /**
- * Saves the current graphics state ("pushes it on the stack"). It can be restored by {@link restoreGraphicsState}
- * later. Here, the general pdf graphics state is meant, also including the current transformation matrix,
- * fill and stroke colors etc.
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name saveGraphicsState
- */
- API.saveGraphicsState = function () {
- out("q");
- // as we cannot set font key and size independently we must keep track of both
- fontStateStack.push({
- key: activeFontKey,
- size: activeFontSize
- });
- return this;
- };
-
- /**
- * Restores a previously saved graphics state saved by {@link saveGraphicsState} ("pops the stack").
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name restoreGraphicsState
- */
- API.restoreGraphicsState = function () {
- out("Q");
+ if (this === void 0 || this === null || typeof fun !== "function") throw new TypeError();
+ var t = Object(this),
+ len = t.length >>> 0;
- // restore previous font state
- var fontState = fontStateStack.pop();
- activeFontKey = fontState.key;
- activeFontSize = fontState.size;
- activeGState = null;
+ for (var i = 0; i < len; i++) {
+ if (i in t) fun.call(thisArg, t[i], i, t);
+ }
+ };
+ }
- return this;
- };
+ if (!Object.keys) {
+ Object.keys = function () {
+
+ var hasOwnProperty = Object.prototype.hasOwnProperty,
+ hasDontEnumBug = !{
+ toString: null
+ }.propertyIsEnumerable("toString"),
+ dontEnums = ["toString", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "constructor"],
+ dontEnumsLength = dontEnums.length;
+ return function (obj) {
+ if (typeof obj !== "object" && (typeof obj !== "function" || obj === null)) {
+ throw new TypeError();
+ }
- /**
- * Appends this matrix to the left of all previously applied matrices.
- * @param {Matrix} matrix
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name setCurrentTransformationMatrix
- */
- API.setCurrentTransformationMatrix = function (matrix) {
- out(matrix.toString() + " cm");
- return this;
- };
+ var result = [],
+ prop,
+ i;
- /**
- * Starts a new pdf form object, which means that all conseequent draw calls target a new independent object
- * until {@link endFormObject} is called. The created object can be referenced and drawn later using
- * {@link doFormObject}. Nested form objects are possible.
- * x, y, width, height set the bounding box that is used to clip the content.
- * @param {number} x
- * @param {number} y
- * @param {number} width
- * @param {number} height
- * @param {Matrix} matrix The matrix that will be applied to convert the form objects coordinate system to
- * the parent's.
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- */
- API.beginFormObject = function (x, y, width, height, matrix) {
- // The user can set the output target to a new form object. Nested form objects are possible.
- // Currently, they use the resource dictionary of the surrounding stream. This should be changed, as
- // the PDF-Spec states:
- // "In PDF 1.2 and later versions, form XObjects may be independent of the content streams in which
- // they appear, and this is strongly recommended although not requiredIn PDF 1.2 and later versions,
- // form XObjects may be independent of the content streams in which they appear, and this is strongly
- // recommended although not required"
- beginNewRenderTarget(x, y, width, height, matrix);
- return this;
- };
-
- /**
- * Completes and saves the form object.
- * @param {String} key The key by which this form object can be referenced.
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name endFormObject
- */
- API.endFormObject = function (key) {
- endFormObject(key);
- return this;
- };
+ for (prop in obj) {
+ if (hasOwnProperty.call(obj, prop)) {
+ result.push(prop);
+ }
+ }
- /**
- * Draws the specified form object by referencing to the respective pdf XObject created with
- * {@link API.beginFormObject} and {@link endFormObject}.
- * The location is determined by matrix.
- * @param {String} key The key to the form object.
- * @param {Matrix} matrix The matrix applied before drawing the form object.
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name doFormObject
- */
- API.doFormObject = function (key, matrix) {
- var xObject = renderTargets[renderTargetMap[key]];
- out("q");
- out(matrix.toString() + " cm");
- out("/" + xObject.id + " Do");
- out("Q");
- return this;
- };
-
- /**
- * Returns the form object specified by key.
- * @param key {String}
- * @returns {{x: number, y: number, width: number, height: number, matrix: Matrix}}
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name getFormObject
- */
- API.getFormObject = function (key) {
- var xObject = renderTargets[renderTargetMap[key]];
- return {
- x: xObject.x,
- y: xObject.y,
- width: xObject.width,
- height: xObject.height,
- matrix: xObject.matrix
- };
- };
-
- /**
- * A matrix object for 2D homogenous transformations:
- * | a b 0 |
- * | c d 0 |
- * | e f 1 |
- * pdf multiplies matrices righthand: v' = v x m1 x m2 x ...
- * @param {number} a
- * @param {number} b
- * @param {number} c
- * @param {number} d
- * @param {number} e
- * @param {number} f
- * @constructor
- */
- API.Matrix = Matrix;
+ if (hasDontEnumBug) {
+ for (i = 0; i < dontEnumsLength; i++) {
+ if (hasOwnProperty.call(obj, dontEnums[i])) {
+ result.push(dontEnums[i]);
+ }
+ }
+ }
- /**
- * Multiplies two matrices. (see {@link Matrix})
- * @param {Matrix} m1
- * @param {Matrix} m2
- */
- API.matrixMult = matrixMult;
+ return result;
+ };
+ }();
+ }
- /**
- * The unit matrix (equal to new Matrix(1, 0, 0, 1, 0, 0).
- * @type {Matrix}
- */
- API.unitMatrix = unitMatrix;
+ if (typeof Object.assign != "function") {
+ Object.assign = function (target) {
- var Pattern = function Pattern(gState, matrix) {
- this.gState = gState;
- this.matrix = matrix;
+ if (target == null) {
+ throw new TypeError("Cannot convert undefined or null to object");
+ }
- this.id = ""; // set by addPattern()
- this.objectNumber = -1; // will be set by putPattern()
- };
+ target = Object(target);
- /**
- * A pattern describing a shading pattern.
- * @param {String} type One of "axial" or "radial"
- * @param {Array} coords Either [x1, y1, x2, y2] for "axial" type describing the two interpolation points
- * or [x1, y1, r, x2, y2, r2] for "radial" describing inner and the outer circle.
- * @param {Array} colors An array of objects with the fields "offset" and "color". "offset" describes
- * the offset in parameter space [0, 1]. "color" is an array of length 3 describing RGB values in [0, 255].
- * @param {GState=} gState An additional graphics state that gets applied to the pattern (optional).
- * @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system
- * and the use coordinate system (optional).
- * @constructor
- * @extends API.Pattern
- */
- API.ShadingPattern = function (type, coords, colors, gState, matrix) {
- // see putPattern() for information how they are realized
- this.type = type === "axial" ? 2 : 3;
- this.coords = coords;
- this.colors = colors;
+ for (var index = 1; index < arguments.length; index++) {
+ var source = arguments[index];
- Pattern.call(this, gState, matrix);
- };
+ if (source != null) {
+ for (var key in source) {
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
+ target[key] = source[key];
+ }
+ }
+ }
+ }
- /**
- * A PDF Tiling pattern.
- * @param {Array.} boundingBox The bounding box at which one pattern cell gets clipped.
- * @param {Number} xStep Horizontal spacing between pattern cells.
- * @param {Number} yStep Vertical spacing between pattern cells.
- * @param {API.GState=} gState An additional graphics state that gets applied to the pattern (optional).
- * @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system
- * and the use coordinate system (optional).
- * @constructor
- * @extends API.Pattern
- */
- API.TilingPattern = function (boundingBox, xStep, yStep, gState, matrix) {
- this.boundingBox = boundingBox;
- this.xStep = xStep;
- this.yStep = yStep;
-
- this.stream = ""; // set by endTilingPattern();
-
- this.cloneIndex = 0;
-
- Pattern.call(this, gState, matrix);
- };
-
- API.TilingPattern.prototype = {
- createClone: function createClone(patternKey, boundingBox, xStep, yStep, matrix) {
- var clone = new API.TilingPattern(boundingBox || this.boundingBox, xStep || this.xStep, yStep || this.yStep, this.gState, matrix || this.matrix);
- clone.stream = this.stream;
- var key = patternKey + "$$" + this.cloneIndex++ + "$$";
- addPattern(key, clone);
- return clone;
- }
- };
-
- /**
- * Adds a new {@link API.ShadingPattern} for later use.
- * @param {String} key
- * @param {Pattern} pattern
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name addPattern
- */
- API.addShadingPattern = function (key, pattern) {
- addPattern(key, pattern);
- return this;
- };
+ return target;
+ };
+ }
- /**
- * Begins a new tiling pattern. All subsequent render calls are drawn to this pattern until {@link API.endTilingPattern}
- * gets called.
- * @param {API.Pattern} pattern
- */
- API.beginTilingPattern = function (pattern) {
- beginNewRenderTarget(pattern.boundingBox[0], pattern.boundingBox[1], pattern.boundingBox[2] - pattern.boundingBox[0], pattern.boundingBox[3] - pattern.boundingBox[1], pattern.matrix);
- };
+ if (!String.prototype.trim) {
+ String.prototype.trim = function () {
+ return this.replace(/^\s+|\s+$/g, "");
+ };
+ }
- /**
- * Ends a tiling pattern and sets the render target to the one active before {@link API.beginTilingPattern} has been called.
- * @param {string} key A unique key that is used to reference this pattern at later use.
- * @param {API.Pattern} pattern The pattern to end.
- */
- API.endTilingPattern = function (key, pattern) {
- // retrieve the stream
- pattern.stream = pages[currentPage].join("\n");
+ if (!String.prototype.trimLeft) {
+ String.prototype.trimLeft = function () {
+ return this.replace(/^\s+/g, "");
+ };
+ }
- addPattern(key, pattern);
+ if (!String.prototype.trimRight) {
+ String.prototype.trimRight = function () {
+ return this.replace(/\s+$/g, "");
+ };
+ }
+ })(typeof self !== "undefined" && self || typeof window !== "undefined" && window || typeof global !== "undefined" && global || Function('return typeof this === "object" && this.content')() || Function("return this")()); // `self` is undefined in Firefox for Android content script context
+ // while `this` is nsIContentFrameMessageManager
+ // with an attribute `content` that corresponds to the window
- events.publish("endTilingPattern", pattern);
+ (function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory() : typeof define === 'function' && define.amd ? define(factory) : factory();
+ })(window, function () {
+ /**
+ * @this {Promise}
+ */
- // restore state from stack
- renderTargetStack.pop().restore();
- };
+ function finallyConstructor(callback) {
+ var constructor = this.constructor;
+ return this.then(function (value) {
+ return constructor.resolve(callback()).then(function () {
+ return value;
+ });
+ }, function (reason) {
+ return constructor.resolve(callback()).then(function () {
+ return constructor.reject(reason);
+ });
+ });
+ } // Store setTimeout reference so promise-polyfill will be unaffected by
+ // other code modifying setTimeout (like sinon.useFakeTimers())
- /**
- * Adds text to page. Supports adding multiline text when 'text' argument is an Array of Strings.
- *
- * @function
- * @param {String|Array} text String or array of strings to be added to the page. Each line is shifted one line down
- * per font, spacing settings declared before this call.
- * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
- * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
- * @param {Object} flags Collection of settings signalling how the text must be encoded. Defaults are sane. If you
- * think you want to pass some flags, you likely can read the source.
- * @param {number|Matrix} transform If transform is a number the text will be rotated by this value. If it is a Matrix,
- * this matrix gets directly applied to the text, which allows shearing effects etc.
- * @param align {string}
- * @returns {jsPDF}
- * @methodOf jsPDF#
- */
- API.text = function (text, x, y, flags, transform, align) {
- /**
- * Inserts something like this into PDF
- * BT
- * /F1 16 Tf % Font name + size
- * 16 TL % How many units down for next line in multiline text
- * 0 g % color
- * 28.35 813.54 Td % position
- * (line one) Tj
- * T* (line two) Tj
- * T* (line three) Tj
- * ET
- */
- function ESC(s) {
- s = s.split("\t").join(Array(options.TabLen || 9).join(" "));
- return pdfEscape(s, flags);
- }
-
- // Pre-August-2012 the order of arguments was function(x, y, text, flags)
- // in effort to make all calls have similar signature like
- // function(data, coordinates... , miscellaneous)
- // this method had its args flipped.
- // code below allows backward compatibility with old arg order.
- if (typeof text === 'number') {
- var tmp = y;
- y = x;
- x = text;
- text = tmp;
- }
-
- // If there are any newlines in text, we assume
- // the user wanted to print multiple lines, so break the
- // text up into an array. If the text is already an array,
- // we assume the user knows what they are doing.
- // Convert text into an array anyway to simplify
- // later code.
- if (typeof text === 'string') {
- if (text.match(/[\n\r]/)) {
- text = text.split(/\r\n|\r|\n/g);
- } else {
- text = [text];
- }
- }
- if (typeof transform === 'string') {
- align = transform;
- transform = null;
- }
- if (typeof flags === 'string') {
- align = flags;
- flags = null;
- }
- if (typeof flags === 'number') {
- transform = flags;
- flags = null;
- }
-
- var todo;
- if (transform && typeof transform === "number") {
- transform *= Math.PI / 180;
- var c = Math.cos(transform),
- s = Math.sin(transform);
- transform = new Matrix(c, s, -s, c, 0, 0);
- } else if (!transform) {
- transform = unitMatrix;
- }
-
- flags = flags || {};
- if (!('noBOM' in flags)) flags.noBOM = true;
- if (!('autoencode' in flags)) flags.autoencode = true;
-
- var strokeOption = '';
- var pageContext = this.internal.getCurrentPageInfo().pageContext;
- if (true === flags.stroke) {
- if (pageContext.lastTextWasStroke !== true) {
- strokeOption = '1 Tr\n';
- pageContext.lastTextWasStroke = true;
- }
- } else {
- if (pageContext.lastTextWasStroke) {
- strokeOption = '0 Tr\n';
- }
- pageContext.lastTextWasStroke = false;
- }
-
- if (typeof this._runningPageHeight === 'undefined') {
- this._runningPageHeight = 0;
- }
-
- if (typeof text === 'string') {
- text = ESC(text);
- } else if (Object.prototype.toString.call(text) === '[object Array]') {
- // we don't want to destroy original text array, so cloning it
- var sa = text.concat(),
- da = [],
- len = sa.length;
- // we do array.join('text that must not be PDFescaped")
- // thus, pdfEscape each component separately
- while (len--) {
- da.push(ESC(sa.shift()));
- }
- var linesLeft = Math.ceil((y - this._runningPageHeight) / (activeFontSize * lineHeightProportion));
- if (0 <= linesLeft && linesLeft < da.length + 1) {
- //todo = da.splice(linesLeft-1);
- }
-
- if (align) {
- var left,
- prevX,
- maxLineLength,
- leading = activeFontSize * lineHeightProportion,
- lineWidths = text.map(function (v) {
- return this.getStringUnitWidth(v) * activeFontSize;
- }, this);
- maxLineLength = Math.max.apply(Math, lineWidths);
- // The first line uses the "main" Td setting,
- // and the subsequent lines are offset by the
- // previous line's x coordinate.
- if (align === "center") {
- // The passed in x coordinate defines
- // the center point.
- left = x - maxLineLength / 2;
- x -= lineWidths[0] / 2;
- } else if (align === "right") {
- // The passed in x coordinate defines the
- // rightmost point of the text.
- left = x - maxLineLength;
- x -= lineWidths[0];
- } else {
- throw new Error('Unrecognized alignment option, use "center" or "right".');
- }
- prevX = x;
- text = da[0] + ") Tj\n";
- for (i = 1, len = da.length; i < len; i++) {
- var delta = maxLineLength - lineWidths[i];
- if (align === "center") delta /= 2;
- // T* = x-offset leading Td ( text )
- text += left - prevX + delta + " -" + leading + " Td (" + da[i];
- prevX = left + delta;
- if (i < len - 1) {
- text += ") Tj\n";
- }
- }
- } else {
- text = da.join(") Tj\nT* (");
- }
- } else {
- throw new Error('Type of text must be string or Array. "' + text + '" is not recognized.');
- }
- // Using "'" ("go next line and render text" mark) would save space but would complicate our rendering code, templates
-
- // BT .. ET does NOT have default settings for Tf. You must state that explicitely every time for BT .. ET
- // if you want text transformation matrix (+ multiline) to work reliably (which reads sizes of things from font declarations)
- // Thus, there is NO useful, *reliable* concept of "default" font for a page.
- // The fact that "default" (reuse font used before) font worked before in basic cases is an accident
- // - readers dealing smartly with brokenness of jsPDF's markup.
-
- var curY;
-
- if (todo) {
- //this.addPage();
- //this._runningPageHeight += y - (activeFontSize * 1.7);
- //curY = f2(activeFontSize * 1.7);
- } else {
- curY = f2(y);
- }
- //curY = f2(((y - this._runningPageHeight));
-
- // if (curY < 0){
- // console.log('auto page break');
- // this.addPage();
- // this._runningPageHeight = y - (activeFontSize * 1.7);
- // curY = f2(activeFontSize * 1.7);
- // }
-
- var translate = new Matrix(1, 0, 0, -1, x, curY);
- transform = matrixMult(translate, transform);
- var position = transform.toString() + " Tm";
-
- out('BT\n' + activeFontSize * lineHeightProportion + ' TL\n' + // line spacing
- strokeOption + // stroke option
- position + '\n(' + text + ') Tj\nET');
-
- if (todo) {
- //this.text( todo, x, activeFontSize * 1.7);
- //this.text( todo, x, this._runningPageHeight + (activeFontSize * 1.7));
- this.text(todo, x, y); // + (activeFontSize * 1.7));
- }
-
- return this;
- };
-
- API.lstext = function (text, x, y, spacing) {
- for (var i = 0, len = text.length; i < len; i++, x += spacing) {
- this.text(text[i], x, y);
- }
- };
-
- /**
- * Draw a line
- * @param {number} x1
- * @param {number} y1
- * @param {number} x2
- * @param {number} y2
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name line
- */
- API.line = function (x1, y1, x2, y2) {
- return this.lines([[x2 - x1, y2 - y1]], x1, y1, [1, 1], "D");
- };
-
- API.clip = function () {
- // By patrick-roberts, github.com/MrRio/jsPDF/issues/328
- // Call .clip() after calling .rect() with a style argument of null
- out('W'); // clip
- out('S'); // stroke path; necessary for clip to work
- };
-
- /**
- * @typedef {Object} PatternData
- * {Matrix|undefined} matrix
- * {Number|undefined} xStep
- * {Number|undefined} yStep
- * {Array.|undefined} boundingBox
- */
- /**
- * Adds series of curves (straight lines or cubic bezier curves) to canvas, starting at `x`, `y` coordinates.
- * All data points in `lines` are relative to last line origin.
- * `x`, `y` become x1,y1 for first line / curve in the set.
- * For lines you only need to specify [x2, y2] - (ending point) vector against x1, y1 starting point.
- * For bezier curves you need to specify [x2,y2,x3,y3,x4,y4] - vectors to control points 1, 2, ending point. All vectors are against the start of the curve - x1,y1.
- *
- * @example .lines([[2,2],[-2,2],[1,1,2,2,3,3],[2,1]], 212,110, 10) // line, line, bezier curve, line
- * @param {Array} lines Array of *vector* shifts as pairs (lines) or sextets (cubic bezier curves).
- * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
- * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
- * @param {Number} scale (Defaults to [1.0,1.0]) x,y Scaling factor for all vectors. Elements can be any floating number Sub-one makes drawing smaller. Over-one grows the drawing. Negative flips the direction.
- * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument.
- * @param {Boolean} closed If true, the path is closed with a straight line from the end of the last curve to the starting point.
- * @param {String} patternKey The pattern key for the pattern that should be used to fill the path.
- * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that
- * will modify the pattern on use.
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name lines
- */
- API.lines = function (lines, x, y, scale, style, closed, patternKey, patternData) {
- var scalex, scaley, i, l, leg, x2, y2, x3, y3, x4, y4;
-
- // Pre-August-2012 the order of arguments was function(x, y, lines, scale, style)
- // in effort to make all calls have similar signature like
- // function(content, coordinateX, coordinateY , miscellaneous)
- // this method had its args flipped.
- // code below allows backward compatibility with old arg order.
- if (typeof lines === 'number') {
- var tmp = y;
- y = x;
- x = lines;
- lines = tmp;
- }
-
- scale = scale || [1, 1];
-
- // starting point
- out(f3(x) + ' ' + f3(y) + ' m ');
-
- scalex = scale[0];
- scaley = scale[1];
- l = lines.length;
- //, x2, y2 // bezier only. In page default measurement "units", *after* scaling
- //, x3, y3 // bezier only. In page default measurement "units", *after* scaling
- // ending point for all, lines and bezier. . In page default measurement "units", *after* scaling
- x4 = x; // last / ending point = starting point for first item.
- y4 = y; // last / ending point = starting point for first item.
-
- for (i = 0; i < l; i++) {
- leg = lines[i];
- if (leg.length === 2) {
- // simple line
- x4 = leg[0] * scalex + x4; // here last x4 was prior ending point
- y4 = leg[1] * scaley + y4; // here last y4 was prior ending point
- out(f3(x4) + ' ' + f3(y4) + ' l');
- } else {
- // bezier curve
- x2 = leg[0] * scalex + x4; // here last x4 is prior ending point
- y2 = leg[1] * scaley + y4; // here last y4 is prior ending point
- x3 = leg[2] * scalex + x4; // here last x4 is prior ending point
- y3 = leg[3] * scaley + y4; // here last y4 is prior ending point
- x4 = leg[4] * scalex + x4; // here last x4 was prior ending point
- y4 = leg[5] * scaley + y4; // here last y4 was prior ending point
- out(f3(x2) + ' ' + f3(y2) + ' ' + f3(x3) + ' ' + f3(y3) + ' ' + f3(x4) + ' ' + f3(y4) + ' c');
- }
- }
-
- if (closed) {
- out('h');
- }
-
- putStyle(style, patternKey, patternData);
-
- return this;
- };
-
- /**
- * Similar to {@link API.lines} but all coordinates are interpreted as absolute coordinates instead of relative.
- * @param {Array} lines An array of {op: operator, c: coordinates} object, where op is one of "m" (move to), "l" (line to)
- * "c" (cubic bezier curve) and "h" (close (sub)path)). c is an array of coordinates. "m" and "l" expect two, "c"
- * six and "h" an empty array (or undefined).
- * @param {String} style The style
- * @param {String} patternKey The pattern key for the pattern that should be used to fill the path.
- * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that
- * will modify the pattern on use.
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name path
- */
- API.path = function (lines, style, patternKey, patternData) {
-
- for (var i = 0; i < lines.length; i++) {
- var leg = lines[i];
- var coords = leg.c;
- switch (leg.op) {
- case "m":
- // move
- out(f3(coords[0]) + ' ' + f3(coords[1]) + ' m');
- break;
- case "l":
- // simple line
- out(f3(coords[0]) + ' ' + f3(coords[1]) + ' l');
- break;
- case "c":
- // bezier curve
- out([f3(coords[0]), f3(coords[1]), f3(coords[2]), f3(coords[3]), f3(coords[4]), f3(coords[5]), "c"].join(" "));
- break;
- case "h":
- // close path
- out("h");
- }
- }
-
- putStyle(style, patternKey, patternData);
-
- return this;
- };
-
- /**
- * Adds a rectangle to PDF
- *
- * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
- * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
- * @param {Number} w Width (in units declared at inception of PDF document)
- * @param {Number} h Height (in units declared at inception of PDF document)
- * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument.
- * @param {String} patternKey The pattern key for the pattern that should be used to fill the primitive.
- * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that
- * will modify the pattern on use.
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name rect
- */
- API.rect = function (x, y, w, h, style, patternKey, patternData) {
- out([f2(x), f2(y), f2(w), f2(-h), 're'].join(' '));
+ var setTimeoutFunc = setTimeout;
- putStyle(style, patternKey, patternData);
+ function noop() {} // Polyfill for Function.prototype.bind
- return this;
- };
- /**
- * Adds a triangle to PDF
- *
- * @param {Number} x1 Coordinate (in units declared at inception of PDF document) against left edge of the page
- * @param {Number} y1 Coordinate (in units declared at inception of PDF document) against upper edge of the page
- * @param {Number} x2 Coordinate (in units declared at inception of PDF document) against left edge of the page
- * @param {Number} y2 Coordinate (in units declared at inception of PDF document) against upper edge of the page
- * @param {Number} x3 Coordinate (in units declared at inception of PDF document) against left edge of the page
- * @param {Number} y3 Coordinate (in units declared at inception of PDF document) against upper edge of the page
- * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument.
- * @param {String} patternKey The pattern key for the pattern that should be used to fill the primitive.
- * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that
- * will modify the pattern on use.
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name triangle
- */
- API.triangle = function (x1, y1, x2, y2, x3, y3, style, patternKey, patternData) {
- this.lines([[x2 - x1, y2 - y1], // vector to point 2
- [x3 - x2, y3 - y2], // vector to point 3
- [x1 - x3, y1 - y3] // closing vector back to point 1
- ], x1, y1, // start of path
- [1, 1], style, true, patternKey, patternData);
- return this;
- };
-
- /**
- * Adds a rectangle with rounded corners to PDF
- *
- * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
- * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
- * @param {Number} w Width (in units declared at inception of PDF document)
- * @param {Number} h Height (in units declared at inception of PDF document)
- * @param {Number} rx Radius along x axis (in units declared at inception of PDF document)
- * @param {Number} ry Radius along y axis (in units declared at inception of PDF document)
- * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument.
- * @param {String} patternKey The pattern key for the pattern that should be used to fill the primitive.
- * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that
- * will modify the pattern on use.
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name roundedRect
- */
- API.roundedRect = function (x, y, w, h, rx, ry, style, patternKey, patternData) {
- var MyArc = 4 / 3 * (Math.SQRT2 - 1);
+ function bind(fn, thisArg) {
+ return function () {
+ fn.apply(thisArg, arguments);
+ };
+ }
+ /**
+ * @constructor
+ * @param {Function} fn
+ */
- rx = Math.min(rx, w * 0.5);
- ry = Math.min(ry, h * 0.5);
- this.lines([[w - 2 * rx, 0], [rx * MyArc, 0, rx, ry - ry * MyArc, rx, ry], [0, h - 2 * ry], [0, ry * MyArc, -(rx * MyArc), ry, -rx, ry], [-w + 2 * rx, 0], [-(rx * MyArc), 0, -rx, -(ry * MyArc), -rx, -ry], [0, -h + 2 * ry], [0, -(ry * MyArc), rx * MyArc, -ry, rx, -ry]], x + rx, y, // start of path
- [1, 1], style, true, patternKey, patternData);
- return this;
- };
+ function Promise(fn) {
+ if (!(this instanceof Promise)) throw new TypeError('Promises must be constructed via new');
+ if (typeof fn !== 'function') throw new TypeError('not a function');
+ /** @type {!number} */
- /**
- * Adds an ellipse to PDF
- *
- * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
- * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
- * @param {Number} rx Radius along x axis (in units declared at inception of PDF document)
- * @param {Number} ry Radius along y axis (in units declared at inception of PDF document)
- * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument.
- * @param {String} patternKey The pattern key for the pattern that should be used to fill the primitive.
- * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that
- * will modify the pattern on use.
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name ellipse
- */
- API.ellipse = function (x, y, rx, ry, style, patternKey, patternData) {
- var lx = 4 / 3 * (Math.SQRT2 - 1) * rx,
- ly = 4 / 3 * (Math.SQRT2 - 1) * ry;
+ this._state = 0;
+ /** @type {!boolean} */
- out([f2(x + rx), f2(y), 'm', f2(x + rx), f2(y - ly), f2(x + lx), f2(y - ry), f2(x), f2(y - ry), 'c'].join(' '));
- out([f2(x - lx), f2(y - ry), f2(x - rx), f2(y - ly), f2(x - rx), f2(y), 'c'].join(' '));
- out([f2(x - rx), f2(y + ly), f2(x - lx), f2(y + ry), f2(x), f2(y + ry), 'c'].join(' '));
- out([f2(x + lx), f2(y + ry), f2(x + rx), f2(y + ly), f2(x + rx), f2(y), 'c'].join(' '));
+ this._handled = false;
+ /** @type {Promise|undefined} */
- putStyle(style, patternKey, patternData);
+ this._value = undefined;
+ /** @type {!Array} */
- return this;
- };
+ this._deferreds = [];
+ doResolve(fn, this);
+ }
- /**
- * Adds an circle to PDF
- *
- * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
- * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
- * @param {Number} r Radius (in units declared at inception of PDF document)
- * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument.
- * @param {String} patternKey The pattern key for the pattern that should be used to fill the primitive.
- * @param {Matrix|PatternData} patternData The matrix that transforms the pattern into user space, or an object that
- * will modify the pattern on use.
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name circle
- */
- API.circle = function (x, y, r, style, patternKey, patternData) {
- return this.ellipse(x, y, r, r, style, patternKey, patternData);
- };
+ function handle(self, deferred) {
+ while (self._state === 3) {
+ self = self._value;
+ }
- /**
- * Adds a properties to the PDF document
- *
- * @param {Object} properties A property_name-to-property_value object structure.
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name setProperties
- */
- API.setProperties = function (properties) {
- // copying only those properties we can render.
- for (var property in documentProperties) {
- if (documentProperties.hasOwnProperty(property) && properties[property]) {
- documentProperties[property] = properties[property];
- }
- }
- return this;
- };
-
- /**
- * Sets font size for upcoming text elements.
- *
- * @param {Number} size Font size in points.
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name setFontSize
- */
- API.setFontSize = function (size) {
- activeFontSize = size;
- out("/" + activeFontKey + " " + activeFontSize + " Tf");
- return this;
- };
+ if (self._state === 0) {
+ self._deferreds.push(deferred);
- API.getFontSize = function () {
- return activeFontSize;
- };
+ return;
+ }
- /**
- * Sets text font face, variant for upcoming text elements.
- * See output of jsPDF.getFontList() for possible font names, styles.
- *
- * @param {String} fontName Font name or family. Example: "times"
- * @param {String} fontStyle Font style or variant. Example: "italic"
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name setFont
- */
- API.setFont = function (fontName, fontStyle) {
- activeFontKey = _getFont(fontName, fontStyle);
- // if font is not found, the above line blows up and we never go further
- out("/" + activeFontKey + " " + activeFontSize + " Tf");
- return this;
- };
-
- /**
- * Switches font style or variant for upcoming text elements,
- * while keeping the font face or family same.
- * See output of jsPDF.getFontList() for possible font names, styles.
- *
- * @param {String} style Font style or variant. Example: "italic"
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name setFontStyle
- */
- API.setFontStyle = API.setFontType = function (style) {
- activeFontKey = _getFont(undefined, style);
- // if font is not found, the above line blows up and we never go further
- return this;
- };
+ self._handled = true;
- /**
- * Returns an object - a tree of fontName to fontStyle relationships available to
- * active PDF document.
- *
- * @public
- * @function
- * @returns {Object} Like {'times':['normal', 'italic', ... ], 'arial':['normal', 'bold', ... ], ... }
- * @methodOf jsPDF#
- * @name getFontList
- */
- API.getFontList = function () {
- // TODO: iterate over fonts array or return copy of fontmap instead in case more are ever added.
- var list = {},
- fontName,
- fontStyle,
- tmp;
-
- for (fontName in fontmap) {
- if (fontmap.hasOwnProperty(fontName)) {
- list[fontName] = tmp = [];
- for (fontStyle in fontmap[fontName]) {
- if (fontmap[fontName].hasOwnProperty(fontStyle)) {
- tmp.push(fontStyle);
- }
- }
- }
- }
-
- return list;
- };
-
- /**
- * Add a custom font.
- *
- * @param {String} postScriptName name of the Font. Example: "Menlo-Regular"
- * @param {String} fontName of font-family from @font-face definition. Example: "Menlo Regular"
- * @param {String} fontStyle style. Example: "normal"
- * @function
- * @returns the {fontKey} (same as the internal method)
- * @methodOf jsPDF#
- * @name addFont
- */
- API.addFont = function (postScriptName, fontName, fontStyle) {
- addFont(postScriptName, fontName, fontStyle, 'StandardEncoding');
- };
+ Promise._immediateFn(function () {
+ var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
- /**
- * Sets line width for upcoming lines.
- *
- * @param {Number} width Line width (in units declared at inception of PDF document)
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name setLineWidth
- */
- API.setLineWidth = function (width) {
- out(width.toFixed(2) + ' w');
- return this;
- };
+ if (cb === null) {
+ (self._state === 1 ? resolve : reject)(deferred.promise, self._value);
+ return;
+ }
- /**
- * Sets the stroke color for upcoming elements.
- *
- * Depending on the number of arguments given, Gray, RGB, or CMYK
- * color space is implied.
- *
- * When only ch1 is given, "Gray" color space is implied and it
- * must be a value in the range from 0.00 (solid black) to to 1.00 (white)
- * if values are communicated as String types, or in range from 0 (black)
- * to 255 (white) if communicated as Number type.
- * The RGB-like 0-255 range is provided for backward compatibility.
- *
- * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each
- * value must be in the range from 0.00 (minimum intensity) to to 1.00
- * (max intensity) if values are communicated as String types, or
- * from 0 (min intensity) to to 255 (max intensity) if values are communicated
- * as Number types.
- * The RGB-like 0-255 range is provided for backward compatibility.
- *
- * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each
- * value must be a in the range from 0.00 (0% concentration) to to
- * 1.00 (100% concentration)
- *
- * Because JavaScript treats fixed point numbers badly (rounds to
- * floating point nearest to binary representation) it is highly advised to
- * communicate the fractional numbers as String types, not JavaScript Number type.
- *
- * @param {Number|String} ch1 Color channel value
- * @param {Number|String} ch2 Color channel value
- * @param {Number|String} ch3 Color channel value
- * @param {Number|String} ch4 Color channel value
- *
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name setDrawColor
- */
- API.setDrawColor = function (ch1, ch2, ch3, ch4) {
- var color;
- if (ch2 === undefined || ch4 === undefined && ch1 === ch2 === ch3) {
- // Gray color space.
- if (typeof ch1 === 'string') {
- color = ch1 + ' G';
- } else {
- color = f2(ch1 / 255) + ' G';
- }
- } else if (ch4 === undefined) {
- // RGB
- if (typeof ch1 === 'string') {
- color = [ch1, ch2, ch3, 'RG'].join(' ');
- } else {
- color = [f2(ch1 / 255), f2(ch2 / 255), f2(ch3 / 255), 'RG'].join(' ');
- }
- } else {
- // CMYK
- if (typeof ch1 === 'string') {
- color = [ch1, ch2, ch3, ch4, 'K'].join(' ');
- } else {
- color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), 'K'].join(' ');
- }
- }
-
- out(color);
- return this;
- };
-
- /**
- * Sets the fill color for upcoming elements.
- *
- * Depending on the number of arguments given, Gray, RGB, or CMYK
- * color space is implied.
- *
- * When only ch1 is given, "Gray" color space is implied and it
- * must be a value in the range from 0.00 (solid black) to to 1.00 (white)
- * if values are communicated as String types, or in range from 0 (black)
- * to 255 (white) if communicated as Number type.
- * The RGB-like 0-255 range is provided for backward compatibility.
- *
- * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each
- * value must be in the range from 0.00 (minimum intensity) to to 1.00
- * (max intensity) if values are communicated as String types, or
- * from 0 (min intensity) to to 255 (max intensity) if values are communicated
- * as Number types.
- * The RGB-like 0-255 range is provided for backward compatibility.
- *
- * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each
- * value must be a in the range from 0.00 (0% concentration) to to
- * 1.00 (100% concentration)
- *
- * Because JavaScript treats fixed point numbers badly (rounds to
- * floating point nearest to binary representation) it is highly advised to
- * communicate the fractional numbers as String types, not JavaScript Number type.
- *
- * @param {Number|String} ch1 Color channel value
- * @param {Number|String} ch2 Color channel value
- * @param {Number|String} ch3 Color channel value
- * @param {Number|String} ch4 Color channel value
- *
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name setFillColor
- */
- API.setFillColor = function (ch1, ch2, ch3, ch4) {
- var color;
-
- if (ch2 === undefined || ch4 === undefined && ch1 === ch2 === ch3) {
- // Gray color space.
- if (typeof ch1 === 'string') {
- color = ch1 + ' g';
- } else {
- color = f2(ch1 / 255) + ' g';
- }
- } else if (ch4 === undefined || (typeof ch4 === 'undefined' ? 'undefined' : babelHelpers.typeof(ch4)) === 'object') {
- // RGB
- if (typeof ch1 === 'string') {
- color = [ch1, ch2, ch3, 'rg'].join(' ');
- } else {
- color = [f2(ch1 / 255), f2(ch2 / 255), f2(ch3 / 255), 'rg'].join(' ');
- }
- if (ch4 && ch4.a === 0) {
- //TODO Implement transparency.
- //WORKAROUND use white for now
- color = ['255', '255', '255', 'rg'].join(' ');
- }
- } else {
- // CMYK
- if (typeof ch1 === 'string') {
- color = [ch1, ch2, ch3, ch4, 'k'].join(' ');
- } else {
- color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), 'k'].join(' ');
- }
- }
-
- out(color);
- return this;
- };
-
- /**
- * Sets the text color for upcoming elements.
- * If only one, first argument is given,
- * treats the value as gray-scale color value.
- *
- * @param {Number} r Red channel color value in range 0-255 or {String} r color value in hexadecimal, example: '#FFFFFF'
- * @param {Number} g Green channel color value in range 0-255
- * @param {Number} b Blue channel color value in range 0-255
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name setTextColor
- */
- API.setTextColor = function (r, g, b) {
- if (typeof r === 'string' && /^#[0-9A-Fa-f]{6}$/.test(r)) {
- var hex = parseInt(r.substr(1), 16);
- r = hex >> 16 & 255;
- g = hex >> 8 & 255;
- b = hex & 255;
- }
-
- if (r === 0 && g === 0 && b === 0 || typeof g === 'undefined') {
- textColor = f3(r / 255) + ' g';
- } else {
- textColor = [f3(r / 255), f3(g / 255), f3(b / 255), 'rg'].join(' ');
- }
-
- out(textColor);
-
- return this;
- };
-
- /**
- * Sets a either previously added {@link GState} (via {@link addGState}) or a new {@link GState}.
- * @param {String|GState} gState If type is string, a previously added GState is used, if type is GState
- * it will be added before use.
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name setGState
- */
- API.setGState = function (gState) {
- if (typeof gState === "string") {
- gState = gStates[gStatesMap[gState]];
- } else {
- gState = addGState(null, gState);
- }
-
- if (!gState.equals(activeGState)) {
- out("/" + gState.id + " gs");
- activeGState = gState;
- }
- };
-
- /**
- * Is an Object providing a mapping from human-readable to
- * integer flag values designating the varieties of line cap
- * and join styles.
- *
- * @returns {Object}
- * @fieldOf jsPDF#
- * @name CapJoinStyles
- */
- API.CapJoinStyles = {
- 0: 0,
- 'butt': 0,
- 'but': 0,
- 'miter': 0,
- 1: 1,
- 'round': 1,
- 'rounded': 1,
- 'circle': 1,
- 2: 2,
- 'projecting': 2,
- 'project': 2,
- 'square': 2,
- 'bevel': 2
- };
-
- /**
- * Sets the line cap styles
- * See {jsPDF.CapJoinStyles} for variants
+ var ret;
+
+ try {
+ ret = cb(self._value);
+ } catch (e) {
+ reject(deferred.promise, e);
+ return;
+ }
+
+ resolve(deferred.promise, ret);
+ });
+ }
+
+ function resolve(self, newValue) {
+ try {
+ // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
+ if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.');
+
+ if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
+ var then = newValue.then;
+
+ if (newValue instanceof Promise) {
+ self._state = 3;
+ self._value = newValue;
+ finale(self);
+ return;
+ } else if (typeof then === 'function') {
+ doResolve(bind(then, newValue), self);
+ return;
+ }
+ }
+
+ self._state = 1;
+ self._value = newValue;
+ finale(self);
+ } catch (e) {
+ reject(self, e);
+ }
+ }
+
+ function reject(self, newValue) {
+ self._state = 2;
+ self._value = newValue;
+ finale(self);
+ }
+
+ function finale(self) {
+ if (self._state === 2 && self._deferreds.length === 0) {
+ Promise._immediateFn(function () {
+ if (!self._handled) {
+ Promise._unhandledRejectionFn(self._value);
+ }
+ });
+ }
+
+ for (var i = 0, len = self._deferreds.length; i < len; i++) {
+ handle(self, self._deferreds[i]);
+ }
+
+ self._deferreds = null;
+ }
+ /**
+ * @constructor
+ */
+
+
+ function Handler(onFulfilled, onRejected, promise) {
+ this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
+ this.onRejected = typeof onRejected === 'function' ? onRejected : null;
+ this.promise = promise;
+ }
+ /**
+ * Take a potentially misbehaving resolver function and make sure
+ * onFulfilled and onRejected are only called once.
+ *
+ * Makes no guarantees about asynchrony.
+ */
+
+
+ function doResolve(fn, self) {
+ var done = false;
+
+ try {
+ fn(function (value) {
+ if (done) return;
+ done = true;
+ resolve(self, value);
+ }, function (reason) {
+ if (done) return;
+ done = true;
+ reject(self, reason);
+ });
+ } catch (ex) {
+ if (done) return;
+ done = true;
+ reject(self, ex);
+ }
+ }
+
+ Promise.prototype['catch'] = function (onRejected) {
+ return this.then(null, onRejected);
+ };
+
+ Promise.prototype.then = function (onFulfilled, onRejected) {
+ // @ts-ignore
+ var prom = new this.constructor(noop);
+ handle(this, new Handler(onFulfilled, onRejected, prom));
+ return prom;
+ };
+
+ Promise.prototype['finally'] = finallyConstructor;
+
+ Promise.all = function (arr) {
+ return new Promise(function (resolve, reject) {
+ if (!arr || typeof arr.length === 'undefined') throw new TypeError('Promise.all accepts an array');
+ var args = Array.prototype.slice.call(arr);
+ if (args.length === 0) return resolve([]);
+ var remaining = args.length;
+
+ function res(i, val) {
+ try {
+ if (val && (typeof val === 'object' || typeof val === 'function')) {
+ var then = val.then;
+
+ if (typeof then === 'function') {
+ then.call(val, function (val) {
+ res(i, val);
+ }, reject);
+ return;
+ }
+ }
+
+ args[i] = val;
+
+ if (--remaining === 0) {
+ resolve(args);
+ }
+ } catch (ex) {
+ reject(ex);
+ }
+ }
+
+ for (var i = 0; i < args.length; i++) {
+ res(i, args[i]);
+ }
+ });
+ };
+
+ Promise.resolve = function (value) {
+ if (value && typeof value === 'object' && value.constructor === Promise) {
+ return value;
+ }
+
+ return new Promise(function (resolve) {
+ resolve(value);
+ });
+ };
+
+ Promise.reject = function (value) {
+ return new Promise(function (resolve, reject) {
+ reject(value);
+ });
+ };
+
+ Promise.race = function (values) {
+ return new Promise(function (resolve, reject) {
+ for (var i = 0, len = values.length; i < len; i++) {
+ values[i].then(resolve, reject);
+ }
+ });
+ }; // Use polyfill for setImmediate for performance gains
+
+
+ Promise._immediateFn = typeof setImmediate === 'function' && function (fn) {
+ setImmediate(fn);
+ } || function (fn) {
+ setTimeoutFunc(fn, 0);
+ };
+
+ Promise._unhandledRejectionFn = function _unhandledRejectionFn(err) {
+ if (typeof console !== 'undefined' && console) {
+ console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console
+ }
+ };
+ /** @suppress {undefinedVars} */
+
+
+ var globalNS = function () {
+ // the only reliable means to get the global object is
+ // `Function('return this')()`
+ // However, this causes CSP violations in Chrome apps.
+ if (typeof self !== 'undefined') {
+ return self;
+ }
+
+ if (typeof window !== 'undefined') {
+ return window;
+ }
+
+ if (typeof global !== 'undefined') {
+ return global;
+ }
+
+ throw new Error('unable to locate global object');
+ }();
+
+ if (!('Promise' in globalNS)) {
+ globalNS['Promise'] = Promise;
+ } else if (!globalNS.Promise.prototype['finally']) {
+ globalNS.Promise.prototype['finally'] = finallyConstructor;
+ }
+ });
+
+ function _typeof(obj) {
+ if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
+ _typeof = function (obj) {
+ return typeof obj;
+ };
+ } else {
+ _typeof = function (obj) {
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
+ };
+ }
+
+ return _typeof(obj);
+ }
+
+ /** @license
+ * jsPDF - PDF Document creation from JavaScript
+ * Version 2.0.0 Built on 2019-01-22T15:32:38.220Z
+ * CommitID 0110a2202b
+ *
+ * Copyright (c) 2015-2018 yWorks GmbH, http://www.yworks.com
+ * 2015-2018 Lukas Holländer , https://github.com/HackbrettXXX
+ * 2010-2016 James Hall , https://github.com/MrRio/jsPDF
+ * 2010 Aaron Spike, https://github.com/acspike
+ * 2012 Willow Systems Corporation, willow-systems.com
+ * 2012 Pablo Hess, https://github.com/pablohess
+ * 2012 Florian Jenett, https://github.com/fjenett
+ * 2013 Warren Weckesser, https://github.com/warrenweckesser
+ * 2013 Youssef Beddad, https://github.com/lifof
+ * 2013 Lee Driscoll, https://github.com/lsdriscoll
+ * 2013 Stefan Slonevskiy, https://github.com/stefslon
+ * 2013 Jeremy Morel, https://github.com/jmorel
+ * 2013 Christoph Hartmann, https://github.com/chris-rock
+ * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria
+ * 2014 James Makes, https://github.com/dollaruw
+ * 2014 Diego Casorran, https://github.com/diegocr
+ * 2014 Steven Spungin, https://github.com/Flamenco
+ * 2014 Kenneth Glassey, https://github.com/Gavvers
+ *
+ * Licensed under the MIT License
+ *
+ * Contributor(s):
+ * siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango,
+ * kim3er, mfo, alnorth, Flamenco
+ */
+
+ /**
+ * Creates new jsPDF document object instance.
+ * @name jsPDF
+ * @class
+ * @param orientation {string/Object} Orientation of the first page. Possible values are "portrait" or "landscape" (or shortcuts "p" (Default), "l")
+ * Can also be an options object.
+ * @param unit {string} Measurement unit to be used when coordinates are specified.
+ * Possible values are "pt" (points), "mm" (Default), "cm", "in" or "px".
+ * @param format {string/Array} The format of the first page. Can be a0 - a10 b0 - b10 c0 - c10 c0 - c10 dl letter government-letter legal junior-legal ledger tabloid credit-card
+ * Default is "a4". If you want to use your own format just pass instead of one of the above predefined formats the size as an number-array , e.g. [595.28, 841.89]
+ * @returns {jsPDF} jsPDF-instance
+ * @description
+ * If the first parameter (orientation) is an object, it will be interpreted as an object of named parameters
+ * ```
+ * {
+ * orientation: 'p',
+ * unit: 'mm',
+ * format: 'a4',
+ * hotfixes: [] // an array of hotfix strings to enable
+ * }
+ * ```
+ */
+ var jsPDF = function (global) {
+
+ var pdfVersion = "1.3",
+ pageFormats = {
+ // Size in pt of various paper formats
+ a0: [2383.94, 3370.39],
+ a1: [1683.78, 2383.94],
+ a2: [1190.55, 1683.78],
+ a3: [841.89, 1190.55],
+ a4: [595.28, 841.89],
+ a5: [419.53, 595.28],
+ a6: [297.64, 419.53],
+ a7: [209.76, 297.64],
+ a8: [147.4, 209.76],
+ a9: [104.88, 147.4],
+ a10: [73.7, 104.88],
+ b0: [2834.65, 4008.19],
+ b1: [2004.09, 2834.65],
+ b2: [1417.32, 2004.09],
+ b3: [1000.63, 1417.32],
+ b4: [708.66, 1000.63],
+ b5: [498.9, 708.66],
+ b6: [354.33, 498.9],
+ b7: [249.45, 354.33],
+ b8: [175.75, 249.45],
+ b9: [124.72, 175.75],
+ b10: [87.87, 124.72],
+ c0: [2599.37, 3676.54],
+ c1: [1836.85, 2599.37],
+ c2: [1298.27, 1836.85],
+ c3: [918.43, 1298.27],
+ c4: [649.13, 918.43],
+ c5: [459.21, 649.13],
+ c6: [323.15, 459.21],
+ c7: [229.61, 323.15],
+ c8: [161.57, 229.61],
+ c9: [113.39, 161.57],
+ c10: [79.37, 113.39],
+ dl: [311.81, 623.62],
+ letter: [612, 792],
+ "government-letter": [576, 756],
+ legal: [612, 1008],
+ "junior-legal": [576, 360],
+ ledger: [1224, 792],
+ tabloid: [792, 1224],
+ "credit-card": [153, 243]
+ };
+ /**
+ * jsPDF's Internal PubSub Implementation.
+ * Backward compatible rewritten on 2014 by
+ * Diego Casorran, https://github.com/diegocr
+ *
+ * @class
+ * @name PubSub
+ * @ignore
+ */
+
+ function PubSub(context) {
+ var topics = {};
+
+ this.subscribe = function (topic, callback, once) {
+ if (typeof callback !== "function") {
+ return false;
+ }
+
+ if (!topics.hasOwnProperty(topic)) {
+ topics[topic] = {};
+ }
+
+ var id = Math.random().toString(35);
+ topics[topic][id] = [callback, !!once];
+ return id;
+ };
+
+ this.unsubscribe = function (token) {
+ for (var topic in topics) {
+ if (topics[topic][token]) {
+ delete topics[topic][token];
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ this.publish = function (topic) {
+ if (topics.hasOwnProperty(topic)) {
+ var args = Array.prototype.slice.call(arguments, 1),
+ idr = [];
+
+ for (var id in topics[topic]) {
+ var sub = topics[topic][id];
+
+ try {
+ sub[0].apply(context, args);
+ } catch (ex) {
+ if (global.console) {
+ console.error("jsPDF PubSub Error", ex.message, ex);
+ }
+ }
+
+ if (sub[1]) idr.push(id);
+ }
+
+ if (idr.length) idr.forEach(this.unsubscribe);
+ }
+ };
+ }
+ /**
+ * @constructor
+ * @private
+ */
+
+
+ function jsPDF(orientation, unit, format, compressPdf) {
+ var options = {};
+
+ if (_typeof(orientation) === "object") {
+ options = orientation;
+ orientation = options.orientation;
+ unit = options.unit || unit;
+ format = options.format || format;
+ compressPdf = options.compress || options.compressPdf || compressPdf;
+ } // Default options
+
+
+ unit = unit || "mm";
+ format = format || "a4";
+ orientation = ("" + (orientation || "P")).toLowerCase();
+
+ var format_as_string = ("" + format).toLowerCase(),
+ compress = !!compressPdf && typeof Uint8Array === "function",
+ textColor = options.textColor || "0 g",
+ drawColor = options.drawColor || "0 G",
+ activeFontSize = options.fontSize || 16,
+ activeCharSpace = options.charSpace || 0,
+ R2L = options.R2L || false,
+ lineHeightProportion = options.lineHeight || 1.15,
+ lineWidth = options.lineWidth || 0.200025,
+ // 2mm
+ fileId = "00000000000000000000000000000000",
+ objectNumber = 2,
+ // 'n' Current object number
+ outToPages = !1,
+ // switches where out() prints. outToPages true = push to pages obj. outToPages false = doc builder content
+ offsets = [],
+ // List of offsets. Activated and reset by buildDocument(). Pupulated by various calls buildDocument makes.
+ fonts = {},
+ // collection of font objects, where key is fontKey - a dynamically created label for a given font.
+ fontmap = {},
+ // mapping structure fontName > fontStyle > font key - performance layer. See addFont()
+ activeFontKey,
+ // will be string representing the KEY of the font as combination of fontName + fontStyle
+ fontStateStack = [],
+ //
+ patterns = {},
+ // collection of pattern objects
+ patternMap = {},
+ // see fonts
+ gStates = {},
+ // collection of graphic state objects
+ gStatesMap = {},
+ // see fonts
+ activeGState = null,
+ k,
+ // Scale factor
+ tmp,
+ page = 0,
+ currentPage,
+ pages = [],
+ pagesContext = [],
+ // same index as pages and pagedim
+ pagedim = [],
+ content = [],
+ additionalObjects = [],
+ lineCapID = 0,
+ lineJoinID = 0,
+ content_length = 0,
+ renderTargets = {},
+ renderTargetMap = {},
+ renderTargetStack = [],
+ pageX,
+ pageY,
+ pageMatrix,
+ // only used for FormObjects
+ pageWidth,
+ pageHeight,
+ pageMode,
+ zoomMode,
+ layoutMode,
+ creationDate,
+ documentProperties = {
+ title: "",
+ subject: "",
+ author: "",
+ keywords: "",
+ creator: ""
+ },
+ API = {},
+ ApiMode = {
+ COMPAT: "compat",
+ ADVANCED: "advanced"
+ },
+ apiMode = ApiMode.COMPAT,
+ events = new PubSub(API),
+ hotfixes = options.hotfixes || [],
+ /////////////////////
+ // Private functions
+ /////////////////////
+ generateColorString = function generateColorString(options) {
+ var color;
+ var ch1 = options.ch1;
+ var ch2 = options.ch2;
+ var ch3 = options.ch3;
+ var ch4 = options.ch4;
+ var precision = options.precision;
+ var letterArray = options.pdfColorType === "draw" ? ["G", "RG", "K"] : ["g", "rg", "k"];
+
+ if (typeof ch1 === "string" && ch1.charAt(0) !== "#") {
+ var rgbColor = new RGBColor(ch1);
+
+ if (rgbColor.ok) {
+ ch1 = rgbColor.toHex();
+ }
+ } //convert short rgb to long form
+
+
+ if (typeof ch1 === "string" && /^#[0-9A-Fa-f]{3}$/.test(ch1)) {
+ ch1 = "#" + ch1[1] + ch1[1] + ch1[2] + ch1[2] + ch1[3] + ch1[3];
+ }
+
+ if (typeof ch1 === "string" && /^#[0-9A-Fa-f]{6}$/.test(ch1)) {
+ var hex = parseInt(ch1.substr(1), 16);
+ ch1 = hex >> 16 & 255;
+ ch2 = hex >> 8 & 255;
+ ch3 = hex & 255;
+ }
+
+ if (typeof ch2 === "undefined" || typeof ch4 === "undefined" && ch1 === ch2 && ch2 === ch3) {
+ // Gray color space.
+ if (typeof ch1 === "string") {
+ color = ch1 + " " + letterArray[0];
+ } else {
+ switch (options.precision) {
+ case 2:
+ color = f2(ch1 / 255) + " " + letterArray[0];
+ break;
+
+ case 3:
+ default:
+ color = f3(ch1 / 255) + " " + letterArray[0];
+ }
+ }
+ } else if (typeof ch4 === "undefined" || _typeof(ch4) === "object") {
+ // assume RGB
+ if (typeof ch1 === "string") {
+ color = [ch1, ch2, ch3, letterArray[1]].join(" ");
+ } else {
+ switch (options.precision) {
+ case 2:
+ color = [f2(ch1 / 255), f2(ch2 / 255), f2(ch3 / 255), letterArray[1]].join(" ");
+ break;
+
+ default:
+ case 3:
+ color = [f3(ch1 / 255), f3(ch2 / 255), f3(ch3 / 255), letterArray[1]].join(" ");
+ }
+ } // assume RGBA
+
+
+ if (ch4 && ch4.a === 0) {
+ //TODO Implement transparency.
+ //WORKAROUND use white for now
+ color = ["255", "255", "255", letterArray[1]].join(" ");
+ }
+ } else {
+ // assume CMYK
+ if (typeof ch1 === "string") {
+ color = [ch1, ch2, ch3, ch4, letterArray[2]].join(" ");
+ } else {
+ switch (options.precision) {
+ case 2:
+ color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), letterArray[2]].join(" ");
+ break;
+
+ case 3:
+ default:
+ color = [f3(ch1), f3(ch2), f3(ch3), f3(ch4), letterArray[2]].join(" ");
+ }
+ }
+ }
+
+ return color;
+ },
+ convertDateToPDFDate = function convertDateToPDFDate(parmDate) {
+ var padd2 = function padd2(number) {
+ return ("0" + parseInt(number)).slice(-2);
+ };
+
+ var result = "";
+ var tzoffset = parmDate.getTimezoneOffset(),
+ tzsign = tzoffset < 0 ? "+" : "-",
+ tzhour = Math.floor(Math.abs(tzoffset / 60)),
+ tzmin = Math.abs(tzoffset % 60),
+ timeZoneString = [tzsign, padd2(tzhour), "'", padd2(tzmin), "'"].join("");
+ result = ["D:", parmDate.getFullYear(), padd2(parmDate.getMonth() + 1), padd2(parmDate.getDate()), padd2(parmDate.getHours()), padd2(parmDate.getMinutes()), padd2(parmDate.getSeconds()), timeZoneString].join("");
+ return result;
+ },
+ convertPDFDateToDate = function convertPDFDateToDate(parmPDFDate) {
+ var year = parseInt(parmPDFDate.substr(2, 4), 10);
+ var month = parseInt(parmPDFDate.substr(6, 2), 10) - 1;
+ var date = parseInt(parmPDFDate.substr(8, 2), 10);
+ var hour = parseInt(parmPDFDate.substr(10, 2), 10);
+ var minutes = parseInt(parmPDFDate.substr(12, 2), 10);
+ var seconds = parseInt(parmPDFDate.substr(14, 2), 10);
+ var timeZoneHour = parseInt(parmPDFDate.substr(16, 2), 10);
+ var timeZoneMinutes = parseInt(parmPDFDate.substr(20, 2), 10);
+ var resultingDate = new Date(year, month, date, hour, minutes, seconds, 0);
+ return resultingDate;
+ },
+ setCreationDate = function setCreationDate(date) {
+ var tmpCreationDateString;
+ var regexPDFCreationDate = /^D:(20[0-2][0-9]|203[0-7]|19[7-9][0-9])(0[0-9]|1[0-2])([0-2][0-9]|3[0-1])(0[0-9]|1[0-9]|2[0-3])(0[0-9]|[1-5][0-9])(0[0-9]|[1-5][0-9])(\+0[0-9]|\+1[0-4]|\-0[0-9]|\-1[0-1])\'(0[0-9]|[1-5][0-9])\'?$/;
+
+ if (_typeof(date) === undefined) {
+ date = new Date();
+ }
+
+ if (_typeof(date) === "object" && Object.prototype.toString.call(date) === "[object Date]") {
+ tmpCreationDateString = convertDateToPDFDate(date);
+ } else if (regexPDFCreationDate.test(date)) {
+ tmpCreationDateString = date;
+ } else {
+ tmpCreationDateString = convertDateToPDFDate(new Date());
+ }
+
+ creationDate = tmpCreationDateString;
+ return creationDate;
+ },
+ getCreationDate = function getCreationDate(type) {
+ var result = creationDate;
+
+ if (type === "jsDate") {
+ result = convertPDFDateToDate(creationDate);
+ }
+
+ return result;
+ },
+ setFileId = function setFileId(value) {
+ value = value || "12345678901234567890123456789012".split("").map(function () {
+ return "ABCDEF0123456789".charAt(Math.floor(Math.random() * 16));
+ }).join("");
+ fileId = value;
+ return fileId;
+ },
+ getFileId = function getFileId() {
+ return fileId;
+ },
+ f2 = function f2(number) {
+ return number.toFixed(2); // Ie, %.2f
+ },
+ f3 = function f3(number) {
+ return number.toFixed(3); // Ie, %.3f
+ },
+ // high precision float
+ hpf = function hpf(number) {
+ return number.toFixed(16).replace(/0+$/, "");
+ },
+ scaleByK = function scaleByK(coordinate) {
+ if (apiMode === ApiMode.COMPAT) {
+ return coordinate * k;
+ } else if (apiMode === ApiMode.ADVANCED) {
+ return coordinate;
+ }
+ },
+ transformY = function transformY(y) {
+ if (apiMode === ApiMode.COMPAT) {
+ return pageHeight - y;
+ } else if (apiMode === ApiMode.ADVANCED) {
+ return y;
+ }
+ },
+ transformScaleY = function transformScaleY(y) {
+ return scaleByK(transformY(y));
+ },
+ padd2Hex = function padd2Hex(hexString) {
+ var s = "00" + hexString;
+ return s.substr(s.length - 2);
+ },
+ advancedApiModeTrap = function advancedApiModeTrap(methodName) {
+ if (apiMode !== ApiMode.ADVANCED) {
+ throw new Error(methodName + " is only available in 'advanced' API mode. " + "You need to call advancedAPI() first.");
+ }
+ },
+ out = function out(string) {
+ string = typeof string === "string" ? string : string.toString();
+
+ if (outToPages) {
+ /* set by beginPage */
+ pages[currentPage].push(string);
+ } else {
+ // +1 for '\n' that will be used to join 'content'
+ content_length += string.length + 1;
+ content.push(string);
+ }
+ },
+ newObject = function newObject() {
+ // Begin a new object
+ objectNumber++;
+ offsets[objectNumber] = content_length;
+ out(objectNumber + " 0 obj");
+ return objectNumber;
+ },
+ // Does not output the object until after the pages have been output.
+ // Returns an object containing the objectId and content.
+ // All pages have been added so the object ID can be estimated to start right after.
+ // This does not modify the current objectNumber; It must be updated after the newObjects are output.
+ newAdditionalObject = function newAdditionalObject() {
+ var objId = pages.length * 2 + 1;
+ objId += additionalObjects.length;
+ var obj = {
+ objId: objId,
+ content: ""
+ };
+ additionalObjects.push(obj);
+ return obj;
+ },
+ // Does not output the object. The caller must call newObjectDeferredBegin(oid) before outputing any data
+ newObjectDeferred = function newObjectDeferred() {
+ objectNumber++;
+
+ offsets[objectNumber] = function () {
+ return content_length;
+ };
+
+ return objectNumber;
+ },
+ newObjectDeferredBegin = function newObjectDeferredBegin(oid) {
+ offsets[oid] = content_length;
+ },
+ putStream = function putStream(str) {
+ out("stream");
+ out(str);
+ out("endstream");
+ },
+ appendBuffer = function appendBuffer(buffer1, buffer2) {
+ var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
+ tmp.set(new Uint8Array(buffer1), 0);
+ tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
+ return tmp;
+ },
+ putPages = function putPages() {
+ var n,
+ p,
+ arr,
+ i,
+ deflater,
+ adler32,
+ adler32cs,
+ wPt,
+ hPt,
+ pageObjectNumbers = [];
+ adler32cs = global.adler32cs || jsPDF.API.adler32cs;
+
+ if (compress && typeof adler32cs === "undefined") {
+ compress = false;
+ } // outToPages = false as set in endDocument(). out() writes to content.
+
+
+ for (n = 1; n <= page; n++) {
+ pageObjectNumbers.push(newObject());
+ wPt = (pageWidth = pagedim[n].width) * k;
+ hPt = (pageHeight = pagedim[n].height) * k;
+ out("<>");
+ out("endobj"); // Page content
+
+ p = pages[n].join("\n");
+
+ if (apiMode === ApiMode.ADVANCED) {
+ // if the user forgot to switch back to COMPAT mode, we must balance the graphics stack again
+ p += "\nQ";
+ }
+
+ newObject();
+
+ if (compress) {
+ arr = [];
+ i = p.length;
+
+ while (i--) {
+ arr[i] = p.charCodeAt(i);
+ }
+
+ adler32 = adler32cs.from(p);
+ deflater = new Deflater(6);
+ p = deflater.append(new Uint8Array(arr));
+ p = appendBuffer(p, deflater.flush());
+ arr = new Uint8Array(p.byteLength + 6);
+ arr.set(new Uint8Array([120, 156]));
+ arr.set(p, 2);
+ arr.set(new Uint8Array([adler32 & 0xff, adler32 >> 8 & 0xff, adler32 >> 16 & 0xff, adler32 >> 24 & 0xff]), p.byteLength + 2);
+ var strings = [],
+ chunkSize = 0xffff; // There is a maximum stack size. We cannot call String.fromCharCode with as many arguments as we want
+
+ for (var j = 0; j * chunkSize < arr.length; j++) {
+ strings.push(String.fromCharCode.apply(null, arr.subarray(j * chunkSize, (j + 1) * chunkSize)));
+ }
+
+ p = strings.join('');
+ out("<>");
+ } else {
+ out("<>");
+ }
+
+ putStream(p);
+ out("endobj");
+ }
+
+ offsets[1] = content_length;
+ out("1 0 obj");
+ out("<>");
+ out("endobj");
+ events.publish("postPutPages");
+ },
+ putFont = function putFont(font) {
+ events.publish("putFont", {
+ font: font,
+ out: out,
+ newObject: newObject,
+ putStream: putStream
+ });
+
+ if (font.isAlreadyPutted !== true) {
+ font.objectNumber = newObject();
+ out("<<");
+ out("/Type /Font");
+ out("/BaseFont /" + font.postScriptName);
+ out("/Subtype /Type1");
+
+ if (typeof font.encoding === "string") {
+ out("/Encoding /" + font.encoding);
+ }
+
+ out("/FirstChar 32");
+ out("/LastChar 255");
+ out(">>");
+ out("endobj");
+ }
+ },
+ putFonts = function putFonts() {
+ for (var fontKey in fonts) {
+ if (fonts.hasOwnProperty(fontKey)) {
+ putFont(fonts[fontKey]);
+ }
+ }
+ },
+ putXObject = function putXObject(xObject) {
+ xObject.objectNumber = newObject();
+ out("<<");
+ out("/Type /XObject");
+ out("/Subtype /Form");
+ out("/BBox [" + [hpf(xObject.x), hpf(xObject.y), hpf(xObject.x + xObject.width), hpf(xObject.y + xObject.height)].join(" ") + "]");
+ out("/Matrix [" + xObject.matrix.toString() + "]"); // TODO: /Resources
+
+ var p = xObject.pages[1].join("\n");
+ out("/Length " + p.length);
+ out(">>");
+ putStream(p);
+ out("endobj");
+ },
+ putXObjects = function putXObjects() {
+ for (var xObjectKey in renderTargets) {
+ if (renderTargets.hasOwnProperty(xObjectKey)) {
+ putXObject(renderTargets[xObjectKey]);
+ }
+ }
+ },
+ interpolateAndEncodeRGBStream = function interpolateAndEncodeRGBStream(colors, numberSamples) {
+ var tValues = [];
+ var t;
+ var dT = 1.0 / (numberSamples - 1);
+
+ for (t = 0.0; t < 1.0; t += dT) {
+ tValues.push(t);
+ }
+
+ tValues.push(1.0); // add first and last control point if not present
+
+ if (colors[0].offset != 0.0) {
+ var c0 = {
+ offset: 0.0,
+ color: colors[0].color
+ };
+ colors.unshift(c0);
+ }
+
+ if (colors[colors.length - 1].offset != 1.0) {
+ var c1 = {
+ offset: 1.0,
+ color: colors[colors.length - 1].color
+ };
+ colors.push(c1);
+ }
+
+ var out = "";
+ var index = 0;
+
+ for (var i = 0; i < tValues.length; i++) {
+ t = tValues[i];
+
+ while (t > colors[index + 1].offset) {
+ index++;
+ }
+
+ var a = colors[index].offset;
+ var b = colors[index + 1].offset;
+ var d = (t - a) / (b - a);
+ var aColor = colors[index].color;
+ var bColor = colors[index + 1].color;
+ out += padd2Hex(Math.round((1 - d) * aColor[0] + d * bColor[0]).toString(16)) + padd2Hex(Math.round((1 - d) * aColor[1] + d * bColor[1]).toString(16)) + padd2Hex(Math.round((1 - d) * aColor[2] + d * bColor[2]).toString(16));
+ }
+
+ return out.trim();
+ },
+ putShadingPattern = function putShadingPattern(pattern, numberSamples) {
+ /*
+ Axial patterns shade between the two points specified in coords, radial patterns between the inner
+ and outer circle.
+ The user can specify an array (colors) that maps t-Values in [0, 1] to RGB colors. These are now
+ interpolated to equidistant samples and written to pdf as a sample (type 0) function.
+ */
+ // The number of color samples that should be used to describe the shading.
+ // The higher, the more accurate the gradient will be.
+ numberSamples || (numberSamples = 21);
+ var funcObjectNumber = newObject();
+ var stream = interpolateAndEncodeRGBStream(pattern.colors, numberSamples);
+ out("<< /FunctionType 0");
+ out("/Domain [0.0 1.0]");
+ out("/Size [" + numberSamples + "]");
+ out("/BitsPerSample 8");
+ out("/Range [0.0 1.0 0.0 1.0 0.0 1.0]");
+ out("/Decode [0.0 1.0 0.0 1.0 0.0 1.0]");
+ out("/Length " + stream.length); // The stream is Hex encoded
+
+ out("/Filter /ASCIIHexDecode");
+ out(">>");
+ putStream(stream);
+ out("endobj");
+ pattern.objectNumber = newObject();
+ out("<< /ShadingType " + pattern.type);
+ out("/ColorSpace /DeviceRGB");
+ var coords = "/Coords [" + hpf(parseFloat(pattern.coords[0])) + " " + // x1
+ hpf(parseFloat(pattern.coords[1])) + " "; // y1
+
+ if (pattern.type === 2) {
+ // axial
+ coords += hpf(parseFloat(pattern.coords[2])) + " " + // x2
+ hpf(parseFloat(pattern.coords[3])); // y2
+ } else {
+ // radial
+ coords += hpf(parseFloat(pattern.coords[2])) + " " + // r1
+ hpf(parseFloat(pattern.coords[3])) + " " + // x2
+ hpf(parseFloat(pattern.coords[4])) + " " + // y2
+ hpf(parseFloat(pattern.coords[5])); // r2
+ }
+
+ coords += "]";
+ out(coords);
+
+ if (pattern.matrix) {
+ out("/Matrix [" + pattern.matrix.toString() + "]");
+ }
+
+ out("/Function " + funcObjectNumber + " 0 R");
+ out("/Extend [true true]");
+ out(">>");
+ out("endobj");
+ },
+ putTilingPattern = function putTilingPattern(pattern) {
+ var resourcesObjectNumber = newObject();
+ out("<<");
+ putResourceDictionary();
+ out(">>");
+ out("endobj");
+ pattern.objectNumber = newObject();
+ out("<< /Type /Pattern");
+ out("/PatternType 1"); // tiling pattern
+
+ out("/PaintType 1"); // colored tiling pattern
+
+ out("/TilingType 1"); // constant spacing
+
+ out("/BBox [" + pattern.boundingBox.map(hpf).join(" ") + "]");
+ out("/XStep " + hpf(pattern.xStep));
+ out("/YStep " + hpf(pattern.yStep));
+ out("/Length " + pattern.stream.length);
+ out("/Resources " + resourcesObjectNumber + " 0 R"); // TODO: resources
+
+ pattern.matrix && out("/Matrix [" + pattern.matrix.toString() + "]");
+ out(">>");
+ putStream(pattern.stream);
+ out("endobj");
+ },
+ putPatterns = function putPatterns() {
+ var patternKey;
+
+ for (patternKey in patterns) {
+ if (patterns.hasOwnProperty(patternKey)) {
+ if (patterns[patternKey] instanceof API.ShadingPattern) {
+ putShadingPattern(patterns[patternKey]);
+ } else if (patterns[patternKey] instanceof API.TilingPattern) {
+ putTilingPattern(patterns[patternKey]);
+ }
+ }
+ }
+ },
+ putGState = function putGState(gState) {
+ gState.objectNumber = newObject();
+ out("<<");
+
+ for (var p in gState) {
+ switch (p) {
+ case "opacity":
+ out("/ca " + f2(gState[p]));
+ break;
+
+ case "stroke-opacity":
+ out("/CA " + f2(gState[p]));
+ break;
+ }
+ }
+
+ out(">>");
+ out("endobj");
+ },
+ putGStates = function putGStates() {
+ var gStateKey;
+
+ for (gStateKey in gStates) {
+ if (gStates.hasOwnProperty(gStateKey)) {
+ putGState(gStates[gStateKey]);
+ }
+ }
+ },
+ putXobjectDict = function putXobjectDict() {
+ for (var xObjectKey in renderTargets) {
+ if (renderTargets.hasOwnProperty(xObjectKey) && renderTargets[xObjectKey].objectNumber >= 0) {
+ out("/" + xObjectKey + " " + renderTargets[xObjectKey].objectNumber + " 0 R");
+ }
+ }
+
+ events.publish("putXobjectDict");
+ },
+ putShadingPatternDict = function putShadingPatternDict() {
+ for (var patternKey in patterns) {
+ if (patterns.hasOwnProperty(patternKey) && patterns[patternKey] instanceof API.ShadingPattern && patterns[patternKey].objectNumber >= 0) {
+ out("/" + patternKey + " " + patterns[patternKey].objectNumber + " 0 R");
+ }
+ }
+
+ events.publish("putShadingPatternDict");
+ },
+ putTilingPatternDict = function putTilingPatternDict() {
+ for (var patternKey in patterns) {
+ if (patterns.hasOwnProperty(patternKey) && patterns[patternKey] instanceof API.TilingPattern && patterns[patternKey].objectNumber >= 0) {
+ out("/" + patternKey + " " + patterns[patternKey].objectNumber + " 0 R");
+ }
+ }
+
+ events.publish("putTilingPatternDict");
+ },
+ putGStatesDict = function putGStatesDict() {
+ var gStateKey;
+
+ for (gStateKey in gStates) {
+ if (gStates.hasOwnProperty(gStateKey) && gStates[gStateKey].objectNumber >= 0) {
+ out("/" + gStateKey + " " + gStates[gStateKey].objectNumber + " 0 R");
+ }
+ }
+
+ events.publish("putGStateDict");
+ },
+ putResourceDictionary = function putResourceDictionary() {
+ out("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]");
+ out("/Font <<"); // Do this for each font, the '1' bit is the index of the font
+
+ for (var fontKey in fonts) {
+ if (fonts.hasOwnProperty(fontKey)) {
+ out("/" + fontKey + " " + fonts[fontKey].objectNumber + " 0 R");
+ }
+ }
+
+ out(">>");
+ out("/Shading <<");
+ putShadingPatternDict();
+ out(">>");
+ out("/Pattern <<");
+ putTilingPatternDict();
+ out(">>");
+ out("/ExtGState <<");
+ putGStatesDict();
+ out(">>");
+ out("/XObject <<");
+ putXobjectDict();
+ out(">>");
+ },
+ putResources = function putResources() {
+ putFonts();
+ putGStates();
+ putXObjects();
+ putPatterns();
+ events.publish("putResources"); // Resource dictionary
+
+ offsets[2] = content_length;
+ out("2 0 obj");
+ out("<<");
+ putResourceDictionary();
+ out(">>");
+ out("endobj");
+ events.publish("postPutResources");
+ },
+ putAdditionalObjects = function putAdditionalObjects() {
+ events.publish("putAdditionalObjects");
+
+ for (var i = 0; i < additionalObjects.length; i++) {
+ var obj = additionalObjects[i];
+ offsets[obj.objId] = content_length;
+ out(obj.objId + " 0 obj");
+ out(obj.content);
+ out("endobj");
+ }
+
+ objectNumber += additionalObjects.length;
+ events.publish("postPutAdditionalObjects");
+ },
+ addToFontDictionary = function addToFontDictionary(fontKey, fontName, fontStyle) {
+ // this is mapping structure for quick font key lookup.
+ // returns the KEY of the font (ex: "F1") for a given
+ // pair of font name and type (ex: "Arial". "Italic")
+ if (!fontmap.hasOwnProperty(fontName)) {
+ fontmap[fontName] = {};
+ }
+
+ fontmap[fontName][fontStyle] = fontKey;
+ },
+ addFont = function addFont(postScriptName, fontName, fontStyle, encoding, isStandardFont) {
+ isStandardFont = isStandardFont || false;
+ var fontKey = "F" + (Object.keys(fonts).length + 1).toString(10),
+ // This is FontObject
+ font = {
+ id: fontKey,
+ postScriptName: postScriptName,
+ fontName: fontName,
+ fontStyle: fontStyle,
+ encoding: encoding,
+ isStandardFont: isStandardFont,
+ metadata: {}
+ };
+ var instance = this;
+ events.publish("addFont", {
+ font: font,
+ instance: instance
+ });
+
+ if (fontKey !== undefined) {
+ fonts[fontKey] = font;
+ addToFontDictionary(fontKey, fontName, fontStyle);
+ }
+
+ return fontKey;
+ },
+ addFonts = function addFonts() {
+ var HELVETICA = "helvetica",
+ TIMES = "times",
+ COURIER = "courier",
+ NORMAL = "normal",
+ BOLD = "bold",
+ ITALIC = "italic",
+ BOLD_ITALIC = "bolditalic",
+ ZAPF = "zapfdingbats",
+ SYMBOL = "symbol",
+ standardFonts = [["Helvetica", HELVETICA, NORMAL, "WinAnsiEncoding"], ["Helvetica-Bold", HELVETICA, BOLD, "WinAnsiEncoding"], ["Helvetica-Oblique", HELVETICA, ITALIC, "WinAnsiEncoding"], ["Helvetica-BoldOblique", HELVETICA, BOLD_ITALIC, "WinAnsiEncoding"], ["Courier", COURIER, NORMAL, "WinAnsiEncoding"], ["Courier-Bold", COURIER, BOLD, "WinAnsiEncoding"], ["Courier-Oblique", COURIER, ITALIC, "WinAnsiEncoding"], ["Courier-BoldOblique", COURIER, BOLD_ITALIC, "WinAnsiEncoding"], ["Times-Roman", TIMES, NORMAL, "WinAnsiEncoding"], ["Times-Bold", TIMES, BOLD, "WinAnsiEncoding"], ["Times-Italic", TIMES, ITALIC, "WinAnsiEncoding"], ["Times-BoldItalic", TIMES, BOLD_ITALIC, "WinAnsiEncoding"], ["ZapfDingbats", ZAPF, NORMAL, null], ["Symbol", SYMBOL, NORMAL, null]];
+
+ for (var i = 0, l = standardFonts.length; i < l; i++) {
+ var fontKey = addFont(standardFonts[i][0], standardFonts[i][1], standardFonts[i][2], standardFonts[i][3], true); // adding aliases for standard fonts, this time matching the capitalization
+
+ var parts = standardFonts[i][0].split("-");
+ addToFontDictionary(fontKey, parts[0], parts[1] || "");
+ }
+
+ events.publish("addFonts", {
+ fonts: fonts,
+ dictionary: fontmap
+ });
+ },
+ matrixMult = function matrixMult(m1, m2) {
+ return new Matrix(m1.a * m2.a + m1.b * m2.c, m1.a * m2.b + m1.b * m2.d, m1.c * m2.a + m1.d * m2.c, m1.c * m2.b + m1.d * m2.d, m1.e * m2.a + m1.f * m2.c + m2.e, m1.e * m2.b + m1.f * m2.d + m2.f);
+ },
+ Matrix = function Matrix(a, b, c, d, e, f) {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ this.d = d;
+ this.e = e;
+ this.f = f;
+ };
+
+ Matrix.prototype = {
+ toString: function toString() {
+ return [hpf(this.a), hpf(this.b), hpf(this.c), hpf(this.d), hpf(this.e), hpf(this.f)].join(" ");
+ },
+ inversed: function inversed() {
+ var a = this.a,
+ b = this.b,
+ c = this.c,
+ d = this.d,
+ e = this.e,
+ f = this.f;
+ var quot = 1 / (a * d - b * c);
+ var aInv = d * quot;
+ var bInv = -b * quot;
+ var cInv = -c * quot;
+ var dInv = a * quot;
+ var eInv = -aInv * e - cInv * f;
+ var fInv = -bInv * e - dInv * f;
+ return new Matrix(aInv, bInv, cInv, dInv, eInv, fInv);
+ }
+ };
+
+ var unitMatrix = new Matrix(1, 0, 0, 1, 0, 0),
+ // Used (1) to save the current stream state to the XObjects stack and (2) to save completed form
+ // objects in the xObjects map.
+ RenderTarget = function RenderTarget() {
+ this.page = page;
+ this.currentPage = currentPage;
+ this.pages = pages.slice(0);
+ this.pagedim = pagedim.slice(0);
+ this.pagesContext = pagesContext.slice(0);
+ this.x = pageX;
+ this.y = pageY;
+ this.matrix = pageMatrix;
+ this.width = pageWidth;
+ this.height = pageHeight;
+ this.id = ""; // set by endFormObject()
+
+ this.objectNumber = -1; // will be set by putXObject()
+ };
+
+ RenderTarget.prototype = {
+ restore: function restore() {
+ page = this.page;
+ currentPage = this.currentPage;
+ pagesContext = this.pagesContext;
+ pagedim = this.pagedim;
+ pages = this.pages;
+ pageX = this.x;
+ pageY = this.y;
+ pageMatrix = this.matrix;
+ pageWidth = this.width;
+ pageHeight = this.height;
+ }
+ };
+
+ var beginNewRenderTarget = function beginNewRenderTarget(x, y, width, height, matrix) {
+ // save current state
+ renderTargetStack.push(new RenderTarget()); // clear pages
+
+ page = currentPage = 0;
+ pages = [];
+ pageX = x;
+ pageY = y;
+ pageMatrix = matrix;
+ beginPage(width, height);
+ },
+ endFormObject = function endFormObject(key) {
+ // only add it if it is not already present (the keys provided by the user must be unique!)
+ if (renderTargetMap[key]) return; // save the created xObject
+
+ var newXObject = new RenderTarget();
+ var xObjectId = "Xo" + (Object.keys(renderTargets).length + 1).toString(10);
+ newXObject.id = xObjectId;
+ renderTargetMap[key] = xObjectId;
+ renderTargets[xObjectId] = newXObject;
+ events.publish("addFormObject", newXObject); // restore state from stack
+
+ renderTargetStack.pop().restore();
+ },
+
+ /**
+ * Adds a new pattern for later use.
+ * @param {String} key The key by it can be referenced later. The keys must be unique!
+ * @param {API.Pattern} pattern The pattern
+ */
+ addPattern = function addPattern(key, pattern) {
+ // only add it if it is not already present (the keys provided by the user must be unique!)
+ if (patternMap[key]) return;
+ var prefix = pattern instanceof API.ShadingPattern ? "Sh" : "P";
+ var patternKey = prefix + (Object.keys(patterns).length + 1).toString(10);
+ pattern.id = patternKey;
+ patternMap[key] = patternKey;
+ patterns[patternKey] = pattern;
+ events.publish("addPattern", pattern);
+ },
+
+ /**
+ * Adds a new Graphics State. Duplicates are automatically eliminated.
+ * @param {String} key Might also be null, if no later reference to this gState is needed
+ * @param {Object} gState The gState object
+ */
+ addGState = function addGState(key, gState) {
+ // only add it if it is not already present (the keys provided by the user must be unique!)
+ if (key && gStatesMap[key]) return;
+ var duplicate = false;
+
+ for (var s in gStates) {
+ if (gStates.hasOwnProperty(s)) {
+ if (gStates[s].equals(gState)) {
+ duplicate = true;
+ break;
+ }
+ }
+ }
+
+ if (duplicate) {
+ gState = gStates[s];
+ } else {
+ var gStateKey = "GS" + (Object.keys(gStates).length + 1).toString(10);
+ gStates[gStateKey] = gState;
+ gState.id = gStateKey;
+ } // several user keys may point to the same GState object
+
+
+ key && (gStatesMap[key] = gState.id);
+ events.publish("addGState", gState);
+ return gState;
+ },
+ SAFE = function __safeCall(fn) {
+ fn.foo = function __safeCallWrapper() {
+ try {
+ return fn.apply(this, arguments);
+ } catch (e) {
+ var stack = e.stack || "";
+ if (~stack.indexOf(" at ")) stack = stack.split(" at ")[1];
+ var m = "Error in function " + stack.split("\n")[0].split("<")[0] + ": " + e.message;
+
+ if (global.console) {
+ global.console.error(m, e);
+ if (global.alert) alert(m);
+ } else {
+ throw new Error(m);
+ }
+ }
+ };
+
+ fn.foo.bar = fn;
+ return fn.foo;
+ },
+ to8bitStream = function to8bitStream(text, flags) {
+ /**
+ * PDF 1.3 spec:
+ * "For text strings encoded in Unicode, the first two bytes must be 254 followed by
+ * 255, representing the Unicode byte order marker, U+FEFF. (This sequence conflicts
+ * with the PDFDocEncoding character sequence thorn ydieresis, which is unlikely
+ * to be a meaningful beginning of a word or phrase.) The remainder of the
+ * string consists of Unicode character codes, according to the UTF-16 encoding
+ * specified in the Unicode standard, version 2.0. Commonly used Unicode values
+ * are represented as 2 bytes per character, with the high-order byte appearing first
+ * in the string."
+ *
+ * In other words, if there are chars in a string with char code above 255, we
+ * recode the string to UCS2 BE - string doubles in length and BOM is prepended.
+ *
+ * HOWEVER!
+ * Actual *content* (body) text (as opposed to strings used in document properties etc)
+ * does NOT expect BOM. There, it is treated as a literal GID (Glyph ID)
+ *
+ * Because of Adobe's focus on "you subset your fonts!" you are not supposed to have
+ * a font that maps directly Unicode (UCS2 / UTF16BE) code to font GID, but you could
+ * fudge it with "Identity-H" encoding and custom CIDtoGID map that mimics Unicode
+ * code page. There, however, all characters in the stream are treated as GIDs,
+ * including BOM, which is the reason we need to skip BOM in content text (i.e. that
+ * that is tied to a font).
+ *
+ * To signal this "special" PDFEscape / to8bitStream handling mode,
+ * API.text() function sets (unless you overwrite it with manual values
+ * given to API.text(.., flags) )
+ * flags.autoencode = true
+ * flags.noBOM = true
+ *
+ * ===================================================================================
+ * `flags` properties relied upon:
+ * .sourceEncoding = string with encoding label.
+ * "Unicode" by default. = encoding of the incoming text.
+ * pass some non-existing encoding name
+ * (ex: 'Do not touch my strings! I know what I am doing.')
+ * to make encoding code skip the encoding step.
+ * .outputEncoding = Either valid PDF encoding name
+ * (must be supported by jsPDF font metrics, otherwise no encoding)
+ * or a JS object, where key = sourceCharCode, value = outputCharCode
+ * missing keys will be treated as: sourceCharCode === outputCharCode
+ * .noBOM
+ * See comment higher above for explanation for why this is important
+ * .autoencode
+ * See comment higher above for explanation for why this is important
+ */
+ var i, l, sourceEncoding, encodingBlock, outputEncoding, newtext, isUnicode, ch, bch;
+ flags = flags || {};
+ sourceEncoding = flags.sourceEncoding || "Unicode";
+ outputEncoding = flags.outputEncoding; // This 'encoding' section relies on font metrics format
+ // attached to font objects by, among others,
+ // "Willow Systems' standard_font_metrics plugin"
+ // see jspdf.plugin.standard_font_metrics.js for format
+ // of the font.metadata.encoding Object.
+ // It should be something like
+ // .encoding = {'codePages':['WinANSI....'], 'WinANSI...':{code:code, ...}}
+ // .widths = {0:width, code:width, ..., 'fof':divisor}
+ // .kerning = {code:{previous_char_code:shift, ..., 'fof':-divisor},...}
+
+ if ((flags.autoencode || outputEncoding) && fonts[activeFontKey].metadata && fonts[activeFontKey].metadata[sourceEncoding] && fonts[activeFontKey].metadata[sourceEncoding].encoding) {
+ encodingBlock = fonts[activeFontKey].metadata[sourceEncoding].encoding; // each font has default encoding. Some have it clearly defined.
+
+ if (!outputEncoding && fonts[activeFontKey].encoding) {
+ outputEncoding = fonts[activeFontKey].encoding;
+ } // Hmmm, the above did not work? Let's try again, in different place.
+
+
+ if (!outputEncoding && encodingBlock.codePages) {
+ outputEncoding = encodingBlock.codePages[0]; // let's say, first one is the default
+ }
+
+ if (typeof outputEncoding === "string") {
+ outputEncoding = encodingBlock[outputEncoding];
+ } // we want output encoding to be a JS Object, where
+ // key = sourceEncoding's character code and
+ // value = outputEncoding's character code.
+
+
+ if (outputEncoding) {
+ isUnicode = false;
+ newtext = [];
+
+ for (i = 0, l = text.length; i < l; i++) {
+ ch = outputEncoding[text.charCodeAt(i)];
+
+ if (ch) {
+ newtext.push(String.fromCharCode(ch));
+ } else {
+ newtext.push(text[i]);
+ } // since we are looping over chars anyway, might as well
+ // check for residual unicodeness
+
+
+ if (newtext[i].charCodeAt(0) >> 8) {
+ /* more than 255 */
+ isUnicode = true;
+ }
+ }
+
+ text = newtext.join("");
+ }
+ }
+
+ i = text.length; // isUnicode may be set to false above. Hence the triple-equal to undefined
+
+ while (isUnicode === undefined && i !== 0) {
+ if (text.charCodeAt(i - 1) >> 8) {
+ /* more than 255 */
+ isUnicode = true;
+ }
+
+ i--;
+ }
+
+ if (!isUnicode) {
+ return text;
+ }
+
+ newtext = flags.noBOM ? [] : [254, 255];
+
+ for (i = 0, l = text.length; i < l; i++) {
+ ch = text.charCodeAt(i);
+ bch = ch >> 8; // divide by 256
+
+ if (bch >> 8) {
+ /* something left after dividing by 256 second time */
+ throw new Error("Character at position " + i + " of string '" + text + "' exceeds 16bits. Cannot be encoded into UCS-2 BE");
+ }
+
+ newtext.push(bch);
+ newtext.push(ch - (bch << 8));
+ }
+
+ return String.fromCharCode.apply(undefined, newtext);
+ },
+ pdfEscape = function pdfEscape(text, flags) {
+ /**
+ * Replace '/', '(', and ')' with pdf-safe versions
+ *
+ * Doing to8bitStream does NOT make this PDF display unicode text. For that
+ * we also need to reference a unicode font and embed it - royal pain in the rear.
+ *
+ * There is still a benefit to to8bitStream - PDF simply cannot handle 16bit chars,
+ * which JavaScript Strings are happy to provide. So, while we still cannot display
+ * 2-byte characters property, at least CONDITIONALLY converting (entire string containing)
+ * 16bit chars to (USC-2-BE) 2-bytes per char + BOM streams we ensure that entire PDF
+ * is still parseable.
+ * This will allow immediate support for unicode in document properties strings.
+ */
+ return to8bitStream(text, flags).replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
+ },
+ putInfo = function putInfo() {
+ out("/Producer (jsPDF " + jsPDF.version + ")");
+
+ for (var key in documentProperties) {
+ if (documentProperties.hasOwnProperty(key) && documentProperties[key]) {
+ out("/" + key.substr(0, 1).toUpperCase() + key.substr(1) + " (" + pdfEscape(documentProperties[key]) + ")");
+ }
+ }
+
+ out("/CreationDate (" + creationDate + ")");
+ },
+ putCatalog = function putCatalog() {
+ out("/Type /Catalog");
+ out("/Pages 1 0 R"); // PDF13ref Section 7.2.1
+
+ if (!zoomMode) zoomMode = "fullwidth";
+
+ switch (zoomMode) {
+ case "fullwidth":
+ out("/OpenAction [3 0 R /FitH null]");
+ break;
+
+ case "fullheight":
+ out("/OpenAction [3 0 R /FitV null]");
+ break;
+
+ case "fullpage":
+ out("/OpenAction [3 0 R /Fit]");
+ break;
+
+ case "original":
+ out("/OpenAction [3 0 R /XYZ null null 1]");
+ break;
+
+ default:
+ var pcn = "" + zoomMode;
+ if (pcn.substr(pcn.length - 1) === "%") zoomMode = parseInt(zoomMode) / 100;
+
+ if (typeof zoomMode === "number") {
+ out("/OpenAction [3 0 R /XYZ null null " + f2(zoomMode) + "]");
+ }
+
+ }
+
+ if (!layoutMode) layoutMode = "continuous";
+
+ switch (layoutMode) {
+ case "continuous":
+ out("/PageLayout /OneColumn");
+ break;
+
+ case "single":
+ out("/PageLayout /SinglePage");
+ break;
+
+ case "two":
+ case "twoleft":
+ out("/PageLayout /TwoColumnLeft");
+ break;
+
+ case "tworight":
+ out("/PageLayout /TwoColumnRight");
+ break;
+ }
+
+ if (pageMode) {
+ /**
+ * A name object specifying how the document should be displayed when opened:
+ * UseNone : Neither document outline nor thumbnail images visible -- DEFAULT
+ * UseOutlines : Document outline visible
+ * UseThumbs : Thumbnail images visible
+ * FullScreen : Full-screen mode, with no menu bar, window controls, or any other window visible
+ */
+ out("/PageMode /" + pageMode);
+ }
+
+ events.publish("putCatalog");
+ },
+ putTrailer = function putTrailer() {
+ out("/Size " + (objectNumber + 1));
+ out("/Root " + objectNumber + " 0 R");
+ out("/Info " + (objectNumber - 1) + " 0 R");
+ out("/ID [ <" + fileId + "> <" + fileId + "> ]");
+ },
+ beginPage = function beginPage(width, height) {
+ outToPages = true;
+ pages[++page] = [];
+ pagedim[page] = {
+ width: Number(width) || pageWidth,
+ height: Number(height) || pageHeight
+ };
+ pagesContext[page] = {};
+
+ _setPage(page);
+ },
+ _addPage = function _addPage(width, height) {
+ // Dimensions are stored as user units and converted to points on output
+ var orientation = typeof height === "string" && height.toLowerCase();
+
+ if (typeof width === "string") {
+ var format = width.toLowerCase();
+
+ if (pageFormats.hasOwnProperty(format)) {
+ width = pageFormats[format][0] / k;
+ height = pageFormats[format][1] / k;
+ }
+ }
+
+ if (Array.isArray(width)) {
+ height = width[1];
+ width = width[0];
+ }
+
+ if (orientation) {
+ switch (orientation.substr(0, 1)) {
+ case "l":
+ if (height > width) orientation = "s";
+ break;
+
+ case "p":
+ if (width > height) orientation = "s";
+ break;
+ }
+
+ if (orientation === "s") {
+ tmp = width;
+ width = height;
+ height = tmp;
+ }
+ }
+
+ beginPage(width, height); // Set line width
+
+ out(hpf(lineWidth * k) + " w"); // Set draw color
+
+ out(drawColor); // resurrecting non-default line caps, joins
+
+ if (lineCapID !== 0) {
+ out(lineCapID + " J");
+ }
+
+ if (lineJoinID !== 0) {
+ out(lineJoinID + " j");
+ }
+
+ events.publish("addPage", {
+ pageNumber: page
+ });
+ },
+ _deletePage = function _deletePage(n) {
+ if (n > 0 && n <= page) {
+ pages.splice(n, 1);
+ pagedim.splice(n, 1);
+ page--;
+
+ if (currentPage > page) {
+ currentPage = page;
+ }
+
+ this.setPage(currentPage);
+ }
+ },
+ _setPage = function _setPage(n) {
+ if (n > 0 && n <= page) {
+ currentPage = n;
+ pageWidth = pagedim[n].width;
+ pageHeight = pagedim[n].height;
+ }
+ },
+
+ /**
+ * Returns a document-specific font key - a label assigned to a
+ * font name + font type combination at the time the font was added
+ * to the font inventory.
+ *
+ * Font key is used as label for the desired font for a block of text
+ * to be added to the PDF document stream.
+ * @private
+ * @function
+ * @param {String} fontName can be undefined on "falthy" to indicate "use current"
+ * @param {String} fontStyle can be undefined on "falthy" to indicate "use current"
+ * @returns {String} Font key.
+ */
+ _getFont = function getFont(fontName, fontStyle, options) {
+ var key = undefined,
+ fontNameLowerCase;
+ options = options || {};
+ fontName = fontName !== undefined ? fontName : fonts[activeFontKey].fontName;
+ fontStyle = fontStyle !== undefined ? fontStyle : fonts[activeFontKey].fontStyle;
+ fontNameLowerCase = fontName.toLowerCase();
+
+ if (fontmap[fontNameLowerCase] !== undefined && fontmap[fontNameLowerCase][fontStyle] !== undefined) {
+ key = fontmap[fontNameLowerCase][fontStyle];
+ } else if (fontmap[fontName] !== undefined && fontmap[fontName][fontStyle] !== undefined) {
+ key = fontmap[fontName][fontStyle];
+ } else {
+ if (options.disableWarning === false) {
+ console.warn("Unable to look up font label for font '" + fontName + "', '" + fontStyle + "'. Refer to getFontList() for available fonts.");
+ }
+ }
+
+ if (!key && !options.noFallback) {
+ key = fontmap["times"][fontStyle];
+
+ if (key == null) {
+ key = fontmap["times"]["normal"];
+ }
+ }
+
+ return key;
+ },
+ buildDocument = function buildDocument() {
+ outToPages = false; // switches out() to content
+
+ objectNumber = 2;
+ content_length = 0;
+ content = [];
+ offsets = [];
+ additionalObjects = []; // Added for AcroForm
+
+ events.publish("buildDocument"); // putHeader()
+
+ out("%PDF-" + pdfVersion);
+ out("%\xBA\xDF\xAC\xE0");
+ putPages(); // Must happen after putPages
+ // Modifies current object Id
+
+ putAdditionalObjects();
+ putResources(); // Info
+
+ newObject();
+ out("<<");
+ putInfo();
+ out(">>");
+ out("endobj"); // Catalog
+
+ newObject();
+ out("<<");
+ putCatalog();
+ out(">>");
+ out("endobj"); // Cross-ref
+
+ var o = content_length,
+ i,
+ p = "0000000000";
+ out("xref");
+ out("0 " + (objectNumber + 1));
+ out(p + " 65535 f ");
+
+ for (i = 1; i <= objectNumber; i++) {
+ var offset = offsets[i];
+
+ if (typeof offset === "function") {
+ out((p + offsets[i]()).slice(-10) + " 00000 n ");
+ } else {
+ out((p + offsets[i]).slice(-10) + " 00000 n ");
+ }
+ } // Trailer
+
+
+ out("trailer");
+ out("<<");
+ putTrailer();
+ out(">>");
+ out("startxref");
+ out("" + o);
+ out("%%EOF");
+ outToPages = true;
+ return content.join("\n");
+ },
+ getStyle = function getStyle(style) {
+ // see path-painting operators in PDF spec
+ // The default in MrRio's implementation is "S" (stroke), whereas the default in the yWorks implementation
+ // was "n" (none). Although this has nothing to do with transforms, we should use the API switch here.
+ var op = apiMode === ApiMode.COMPAT ? "S" : "n";
+
+ if (style === "D") {
+ op = "S"; // stroke
+ } else if (style === "F") {
+ op = "f"; // fill
+ } else if (style === "FD" || style === "DF") {
+ op = "B"; // both
+ } else if (style === "f" || style === "f*" || style === "B" || style === "B*") {
+ /*
+ Allow direct use of these PDF path-painting operators:
+ - f fill using nonzero winding number rule
+ - f* fill using even-odd rule
+ - B fill then stroke with fill using non-zero winding number rule
+ - B* fill then stroke with fill using even-odd rule
+ */
+ op = style;
+ }
+
+ return op;
+ },
+ // puts the style for the previously drawn path. If a patternKey is provided, the pattern is used to fill
+ // the path. Use patternMatrix to transform the pattern to rhe right location.
+ putStyle = function putStyle(style, patternKey, patternData) {
+ if (style === null || apiMode === ApiMode.ADVANCED && style === undefined) {
+ return;
+ }
+
+ style = getStyle(style); // stroking / filling / both the path
+
+ if (!patternKey) {
+ out(style);
+ return;
+ }
+
+ if (!patternData) {
+ patternData = {
+ matrix: unitMatrix
+ };
+ }
+
+ if (patternData instanceof Matrix) {
+ patternData = {
+ matrix: patternData
+ };
+ }
+
+ patternData.key = patternKey;
+ patternData || (patternData = unitMatrix);
+ fillWithPattern(patternData, style);
+ },
+ fillWithPattern = function fillWithPattern(patternData, style) {
+ var patternId = patternMap[patternData.key];
+ var pattern = patterns[patternId];
+
+ if (pattern instanceof API.ShadingPattern) {
+ out("q");
+ out(clipRuleFromStyle(style));
+
+ if (pattern.gState) {
+ API.setGState(pattern.gState);
+ }
+
+ out(patternData.matrix.toString() + " cm");
+ out("/" + patternId + " sh");
+ out("Q");
+ } else if (pattern instanceof API.TilingPattern) {
+ // pdf draws patterns starting at the bottom left corner and they are not affected by the global transformation,
+ // so we must flip them
+ var matrix = new Matrix(1, 0, 0, -1, 0, pageHeight);
+
+ if (patternData.matrix) {
+ matrix = matrixMult(patternData.matrix || unitMatrix, matrix); // we cannot apply a matrix to the pattern on use so we must abuse the pattern matrix and create new instances
+ // for each use
+
+ patternId = pattern.createClone(patternData.key, patternData.boundingBox, patternData.xStep, patternData.yStep, matrix).id;
+ }
+
+ out("q");
+ out("/Pattern cs");
+ out("/" + patternId + " scn");
+
+ if (pattern.gState) {
+ API.setGState(pattern.gState);
+ }
+
+ out(style);
+ out("Q");
+ }
+ },
+ clipRuleFromStyle = function clipRuleFromStyle(style) {
+ switch (style) {
+ case "f":
+ case "F":
+ return "W n";
+
+ case "f*":
+ return "W* n";
+
+ case "B":
+ return "W S";
+
+ case "B*":
+ return "W* S";
+ // these two are for compatibility reasons (in the past, calling any primitive method with a shading pattern
+ // and "n"/"S" as style would still fill/fill and stroke the path)
+
+ case "S":
+ return "W S";
+
+ case "n":
+ return "W n";
+ }
+ },
+ getArrayBuffer = function getArrayBuffer() {
+ var data = buildDocument(),
+ len = data.length,
+ ab = new ArrayBuffer(len),
+ u8 = new Uint8Array(ab);
+
+ while (len--) {
+ u8[len] = data.charCodeAt(len);
+ }
+
+ return ab;
+ },
+ getBlob = function getBlob() {
+ return new Blob([getArrayBuffer()], {
+ type: "application/pdf"
+ });
+ },
+ _output = SAFE(function (type, options) {
+ if (typeof options === "string") {
+ options = {
+ filename: options
+ };
+ } else {
+ options = options || {};
+ options.filename = options.filename || "generated.pdf";
+ }
+
+ var datauri = ("" + type).substr(0, 6) === "dataur" ? "data:application/pdf;filename=" + options.filename + ";base64," + btoa(buildDocument()) : 0;
+
+ switch (type) {
+ case undefined:
+ return buildDocument();
+
+ case "save":
+ if ((typeof navigator === "undefined" ? "undefined" : _typeof(navigator)) === "object" && navigator.getUserMedia) {
+ if (global.URL === undefined || global.URL.createObjectURL === undefined) {
+ return API.output("dataurlnewwindow");
+ }
+ }
+
+ saveAs(getBlob(), options.filename);
+
+ if (typeof saveAs.unload === "function") {
+ if (global.setTimeout) {
+ setTimeout(saveAs.unload, 911);
+ }
+ }
+
+ break;
+
+ case "arraybuffer":
+ return getArrayBuffer();
+
+ case "blob":
+ return getBlob();
+
+ case "bloburi":
+ case "bloburl":
+ // User is responsible of calling revokeObjectURL
+ return global.URL && global.URL.createObjectURL(getBlob()) || void 0;
+
+ case "datauristring":
+ case "dataurlstring":
+ return datauri;
+
+ case "dataurlnewwindow":
+ var htmlForNewWindow = "" + "" + "" + '' + "";
+ var nW = global.open();
+
+ if (nW !== null) {
+ nW.document.write(htmlForNewWindow);
+ }
+
+ if (nW || typeof safari === "undefined") return nW;
+
+ /* pass through */
+
+ case "datauri":
+ case "dataurl":
+ return global.document.location.href = datauri;
+
+ default:
+ throw new Error('Output type "' + type + '" is not supported.');
+ } // @TODO: Add different output options
+
+ }),
+
+ /**
+ * Used to see if a supplied hotfix was requested when the pdf instance was created.
+ * @param {string} hotfixName - The name of the hotfix to check.
+ * @returns {boolean}
+ */
+ hasHotfix = function hasHotfix(hotfixName) {
+ return Array.isArray(hotfixes) === true && hotfixes.indexOf(hotfixName) > -1;
+ };
+
+ switch (unit) {
+ case "pt":
+ k = 1;
+ break;
+
+ case "mm":
+ k = 72 / 25.4000508;
+ break;
+
+ case "cm":
+ k = 72 / 2.54000508;
+ break;
+
+ case "in":
+ k = 72;
+ break;
+
+ case "px":
+ if (hasHotfix("px_scaling") == true) {
+ k = 72 / 96;
+ } else {
+ k = 96 / 72;
+ }
+
+ break;
+
+ case "pc":
+ k = 12;
+ break;
+
+ case "em":
+ k = 12;
+ break;
+
+ case "ex":
+ k = 6;
+ break;
+
+ default:
+ throw "Invalid unit: " + unit;
+ }
+
+ setCreationDate();
+ setFileId(); //---------------------------------------
+ // Public API
+
+ /**
+ * Object exposing internal API to plugins
+ * @public
+ * @ignore
+ */
+
+ API.internal = {
+ pdfEscape: pdfEscape,
+ getStyle: getStyle,
+
+ /**
+ * Returns {FontObject} describing a particular font.
+ * @public
+ * @function
+ * @param {String} fontName (Optional) Font's family name
+ * @param {String} fontStyle (Optional) Font's style variation name (Example:"Italic")
+ * @returns {FontObject}
+ */
+ getFont: function getFont() {
+ return fonts[_getFont.apply(API, arguments)];
+ },
+ getFontSize: function getFontSize() {
+ return activeFontSize;
+ },
+ getCharSpace: function getCharSpace() {
+ return activeCharSpace;
+ },
+ getTextColor: function getTextColor() {
+ var colorEncoded = textColor.split(" ");
+
+ if (colorEncoded.length === 2 && colorEncoded[1] === "g") {
+ // convert grayscale value to rgb so that it can be converted to hex for consistency
+ var floatVal = parseFloat(colorEncoded[0]);
+ colorEncoded = [floatVal, floatVal, floatVal, "r"];
+ }
+
+ var colorAsHex = "#";
+
+ for (var i = 0; i < 3; i++) {
+ colorAsHex += ("0" + Math.floor(parseFloat(colorEncoded[i]) * 255).toString(16)).slice(-2);
+ }
+
+ return colorAsHex;
+ },
+ getLineHeight: function getLineHeight() {
+ return activeFontSize * lineHeightProportion;
+ },
+ write: function write(string1
+ /*, string2, string3, etc */
+ ) {
+ out(arguments.length === 1 ? string1 : Array.prototype.join.call(arguments, " "));
+ },
+ getCoordinateString: function getCoordinateString(value) {
+ return hpf(scaleByK(value));
+ },
+ getVerticalCoordinateString: function getVerticalCoordinateString(value) {
+ return hpf(transformScaleY(value));
+ },
+ collections: {},
+ newObject: newObject,
+ newAdditionalObject: newAdditionalObject,
+ newObjectDeferred: newObjectDeferred,
+ newObjectDeferredBegin: newObjectDeferredBegin,
+ putStream: putStream,
+ events: events,
+ // ratio that you use in multiplication of a given "size" number to arrive to 'point'
+ // units of measurement.
+ // scaleFactor is set at initialization of the document and calculated against the stated
+ // default measurement units for the document.
+ // If default is "mm", k is the number that will turn number in 'mm' into 'points' number.
+ // through multiplication.
+ scaleFactor: k,
+ pageSize: {
+ getWidth: function getWidth() {
+ return pageWidth;
+ },
+ getHeight: function getHeight() {
+ return pageHeight;
+ }
+ },
+ output: function output(type, options) {
+ return _output(type, options);
+ },
+ getNumberOfPages: function getNumberOfPages() {
+ return pages.length - 1;
+ },
+ pages: pages,
+ out: out,
+ f2: f2,
+ getPageInfo: function getPageInfo(pageNumberOneBased) {
+ var objId = (pageNumberOneBased - 1) * 2 + 3;
+ return {
+ objId: objId,
+ pageNumber: pageNumberOneBased,
+ pageContext: pagesContext[pageNumberOneBased]
+ };
+ },
+ getCurrentPageInfo: function getCurrentPageInfo() {
+ var objId = (currentPage - 1) * 2 + 3;
+ return {
+ objId: objId,
+ pageNumber: currentPage,
+ pageContext: pagesContext[currentPage]
+ };
+ },
+ getPDFVersion: function getPDFVersion() {
+ return pdfVersion;
+ },
+ hasHotfix: hasHotfix //Expose the hasHotfix check so plugins can also check them.
+
+ };
+
+ function advancedAPI() {
+ // prepend global change of basis matrix
+ // (Now, instead of converting every coordinate to the pdf coordinate system, we apply a matrix
+ // that does this job for us (however, texts, images and similar objects must be drawn bottom up))
+ this.saveGraphicsState();
+ out(new Matrix(k, 0, 0, -k, 0, pageHeight * k).toString() + " cm");
+ this.setFontSize(this.getFontSize() / k);
+ apiMode = ApiMode.ADVANCED;
+ }
+
+ function compatAPI() {
+ this.restoreGraphicsState();
+ apiMode = ApiMode.COMPAT;
+ }
+ /**
+ * @callback ApiSwitchBody
+ * @param {jsPDF} pdf
+ */
+
+ /**
+ * For compatibility reasons jsPDF offers two API modes which differ in the way they convert between the the usual
+ * screen coordinates and the PDF coordinate system.
+ * - "compat": Offers full compatibility across all plugins but does not allow arbitrary transforms
+ * - "advanced": Allows arbitrary transforms and more advanced features like pattern fills. Some plugins might
+ * not support this mode, though.
+ * Initial mode is "compat".
+ *
+ * You can either provide a callback to the body argument, which means that jsPDF will automatically switch back to
+ * the original API mode afterwards; or you can omit the callback and switch back manually using {@link compatAPI}.
+ *
+ * Note, that the calls to {@link saveGraphicsState} and {@link restoreGraphicsState} need to be balanced within the
+ * callback or between calls of this method and its counterpart {@link compatAPI}. Calls to {@link beginFormObject}
+ * or {@link beginTilingPattern} need to be closed by their counterparts before switching back to "compat" API mode.
+ *
+ * @param {ApiSwitchBody=} body When provided, this callback will be called after the API mode has been switched.
+ * The API mode will be switched back automatically afterwards.
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name advancedAPI
+ */
+
+
+ API.advancedAPI = function (body) {
+ var doSwitch = apiMode === ApiMode.COMPAT;
+
+ if (doSwitch) {
+ advancedAPI.call(this);
+ }
+
+ if (typeof body !== "function") {
+ return this;
+ }
+
+ body(this);
+
+ if (doSwitch) {
+ compatAPI.call(this);
+ }
+
+ return this;
+ };
+ /**
+ * Switches to "compat" API mode. See {@link advancedAPI} for more details.
+ *
+ * @param {ApiSwitchBody=} body When provided, this callback will be called after the API mode has been switched.
+ * The API mode will be switched back automatically afterwards.
+ * @return {jsPDF}
+ * @methodOf jsPDF#
+ * @name compatApi
+ */
+
+
+ API.compatAPI = function (body) {
+ var doSwitch = apiMode === ApiMode.ADVANCED;
+
+ if (doSwitch) {
+ compatAPI.call(this);
+ }
+
+ if (typeof body !== "function") {
+ return this;
+ }
+
+ body(this);
+
+ if (doSwitch) {
+ advancedAPI.call(this);
+ }
+
+ return this;
+ };
+ /**
+ * @return {boolean} True iff the current API mode is "advanced". See {@link advancedAPI}.
+ * @methodOf jsPDF#
+ * @name isAdvancedAPI
+ */
+
+
+ API.isAdvancedAPI = function () {
+ return apiMode === ApiMode.ADVANCED;
+ };
+ /**
+ * Inserts a debug comment into the pdf.
+ * @param {String} text
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name comment
+ */
+
+
+ API.comment = function (text) {
+ out("#" + text);
+ return this;
+ };
+ /**
+ * An object representing a pdf graphics state.
+ * @param parameters A parameter object that contains all properties this graphics state wants to set.
+ * Supported are: opacity, stroke-opacity
+ * @constructor
+ */
+
+
+ API.GState = function (parameters) {
+ var supported = "opacity,stroke-opacity".split(",");
+
+ for (var p in parameters) {
+ if (parameters.hasOwnProperty(p) && supported.indexOf(p) >= 0) {
+ this[p] = parameters[p];
+ }
+ }
+
+ this.id = ""; // set by addGState()
+
+ this.objectNumber = -1; // will be set by putGState()
+ };
+
+ API.GState.prototype.equals = function equals(other) {
+ var ignore = "id,objectNumber,equals";
+ if (!other || _typeof(other) !== _typeof(this)) return false;
+ var count = 0;
+
+ for (var p in this) {
+ if (ignore.indexOf(p) >= 0) continue;
+ if (this.hasOwnProperty(p) && !other.hasOwnProperty(p)) return false;
+ if (this[p] !== other[p]) return false;
+ count++;
+ }
+
+ for (var p in other) {
+ if (other.hasOwnProperty(p) && ignore.indexOf(p) < 0) count--;
+ }
+
+ return count === 0;
+ };
+ /**
+ * Adds a new {@link GState} for later use. See {@link setGState}.
+ * @param {String} key
+ * @param {GState} gState
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ *
+ * @methodOf jsPDF#
+ * @name addPage
+ */
+
+
+ API.addGState = function (key, gState) {
+ addGState(key, gState);
+ return this;
+ };
+ /**
+ * Adds (and transfers the focus to) new page to the PDF document.
+ * @param {String/Array} format The format of the new page. Can be a0 - a10 b0 - b10 c0 - c10 c0 - c10 dl letter government-letter legal junior-legal ledger tabloid credit-card
+ * Default is "a4". If you want to use your own format just pass instead of one of the above predefined formats the size as an number-array , e.g. [595.28, 841.89]
+ * @param {string} orientation Orientation of the new page. Possible values are "portrait" or "landscape" (or shortcuts "p" (Default), "l")
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ *
+ * @memberOf jsPDF
+ * @name addPage
+ */
+
+
+ API.addPage = function (format, orientation) {
+ _addPage.apply(this, arguments);
+
+ return this;
+ };
+ /**
+ * Adds (and transfers the focus to) new page to the PDF document.
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ *
+ * @memberOf jsPDF
+ * @name setPage
+ * @param {number} page Switch the active page to the page number specified
+ * @example
+ * doc = jsPDF()
+ * doc.addPage()
+ * doc.addPage()
+ * doc.text('I am on page 3', 10, 10)
+ * doc.setPage(1)
+ * doc.text('I am on page 1', 10, 10)
+ */
+
+
+ API.setPage = function (page) {
+ _setPage.apply(this, arguments);
+
+ return this;
+ };
+ /**
+ * @name insertPage
+ * @memberOf jsPDF
+ *
+ * @function
+ * @instance
+ * @param {Object} beforePage
+ * @returns {jsPDF}
+ */
+
+
+ API.insertPage = function (beforePage) {
+ this.addPage();
+ this.movePage(currentPage, beforePage);
+ return this;
+ };
+ /**
+ * @name movePage
+ * @memberOf jsPDF
+ * @function
+ * @instance
+ * @param {Object} targetPage
+ * @param {Object} beforePage
+ * @returns {jsPDF}
+ */
+
+
+ API.movePage = function (targetPage, beforePage) {
+ var tmpPagesContext, tmpPagedim, tmpPages, i;
+
+ if (targetPage > beforePage) {
+ tmpPages = pages[targetPage];
+ tmpPagedim = pagedim[targetPage];
+ tmpPagesContext = pagesContext[targetPage];
+
+ for (i = targetPage; i > beforePage; i--) {
+ pages[i] = pages[i - 1];
+ pagedim[i] = pagedim[i - 1];
+ pagesContext[i] = pagesContext[i - 1];
+ }
+
+ pages[beforePage] = tmpPages;
+ pagedim[beforePage] = tmpPagedim;
+ pagesContext[beforePage] = tmpPagesContext;
+ this.setPage(beforePage);
+ } else if (targetPage < beforePage) {
+ tmpPages = pages[targetPage];
+ tmpPagedim = pagedim[targetPage];
+ tmpPagesContext = pagesContext[targetPage];
+
+ for (i = targetPage; i < beforePage; i++) {
+ pages[i] = pages[i + 1];
+ pagedim[i] = pagedim[i + 1];
+ pagesContext[i] = pagesContext[i + 1];
+ }
+
+ pages[beforePage] = tmpPages;
+ pagedim[beforePage] = tmpPagedim;
+ pagesContext[beforePage] = tmpPagesContext;
+ this.setPage(beforePage);
+ }
+
+ return this;
+ };
+ /**
+ * @name deletePage
+ * @memberOf jsPDF
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ */
+
+
+ API.deletePage = function () {
+ _deletePage.apply(this, arguments);
+
+ return this;
+ };
+ /**
+ * @name setCreationDate
+ * @memberOf jsPDF
+ * @function
+ * @instance
+ * @param {Object} date
+ * @returns {jsPDF}
+ */
+
+
+ API.setCreationDate = function (date) {
+ setCreationDate(date);
+ return this;
+ };
+ /**
+ * @name getCreationDate
+ * @memberOf jsPDF
+ * @function
+ * @instance
+ * @param {Object} type
+ * @returns {Object}
+ */
+
+
+ API.getCreationDate = function (type) {
+ return getCreationDate(type);
+ };
+ /**
+ * @name setFileId
+ * @memberOf jsPDF
+ * @function
+ * @instance
+ * @param {string} value GUID
+ * @returns {jsPDF}
+ */
+
+
+ API.setFileId = function (value) {
+ setFileId(value);
+ return this;
+ };
+ /**
+ * @name getFileId
+ * @memberOf jsPDF
+ * @function
+ * @instance
+ *
+ * @returns {string} GUID
+ */
+
+
+ API.getFileId = function () {
+ return getFileId();
+ };
+ /**
+ * Set the display mode options of the page like zoom and layout.
+ *
+ * @param {integer|String} zoom You can pass an integer or percentage as
+ * a string. 2 will scale the document up 2x, '200%' will scale up by the
+ * same amount. You can also set it to 'fullwidth', 'fullheight',
+ * 'fullpage', or 'original'.
+ *
+ * Only certain PDF readers support this, such as Adobe Acrobat
+ *
+ * @param {string} layout Layout mode can be: 'continuous' - this is the
+ * default continuous scroll. 'single' - the single page mode only shows one
+ * page at a time. 'twoleft' - two column left mode, first page starts on
+ * the left, and 'tworight' - pages are laid out in two columns, with the
+ * first page on the right. This would be used for books.
+ * @param {string} pmode 'UseOutlines' - it shows the
+ * outline of the document on the left. 'UseThumbs' - shows thumbnails along
+ * the left. 'FullScreen' - prompts the user to enter fullscreen mode.
+ *
+ * @returns {jsPDF}
+ * @function
+ * @instance
+ * @name setDisplayMode
+ * @methodOf jsPDF#
+ */
+
+
+ API.setDisplayMode = function (zoom, layout, pmode) {
+ zoomMode = zoom;
+ layoutMode = layout;
+ pageMode = pmode;
+ var validPageModes = [undefined, null, "UseNone", "UseOutlines", "UseThumbs", "FullScreen"];
+
+ if (validPageModes.indexOf(pmode) == -1) {
+ throw new Error('Page mode must be one of UseNone, UseOutlines, UseThumbs, or FullScreen. "' + pmode + '" is not recognized.');
+ }
+
+ return this;
+ };
+ /**
+ * Saves the current graphics state ("pushes it on the stack"). It can be restored by {@link restoreGraphicsState}
+ * later. Here, the general pdf graphics state is meant, also including the current transformation matrix,
+ * fill and stroke colors etc.
+ * @function
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name saveGraphicsState
+ */
+
+
+ API.saveGraphicsState = function () {
+ out("q"); // as we cannot set font key and size independently we must keep track of both
+
+ fontStateStack.push({
+ key: activeFontKey,
+ size: activeFontSize,
+ color: textColor
+ });
+ return this;
+ };
+ /**
+ * Restores a previously saved graphics state saved by {@link saveGraphicsState} ("pops the stack").
+ * @function
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name restoreGraphicsState
+ */
+
+
+ API.restoreGraphicsState = function () {
+ out("Q"); // restore previous font state
+
+ var fontState = fontStateStack.pop();
+ activeFontKey = fontState.key;
+ activeFontSize = fontState.size;
+ textColor = fontState.color;
+ activeGState = null;
+ return this;
+ };
+ /**
+ * Appends this matrix to the left of all previously applied matrices.
+ *
+ * Only available in "advanced" API mode.
+ *
+ * @param {Matrix} matrix
+ * @function
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name setCurrentTransformationMatrix
+ */
+
+
+ API.setCurrentTransformationMatrix = function (matrix) {
+ advancedApiModeTrap("setCurrentTransformationMatrix()");
+ out(matrix.toString() + " cm");
+ return this;
+ };
+ /**
+ * Starts a new pdf form object, which means that all conseequent draw calls target a new independent object
+ * until {@link endFormObject} is called. The created object can be referenced and drawn later using
+ * {@link doFormObject}. Nested form objects are possible.
+ * x, y, width, height set the bounding box that is used to clip the content.
+ *
+ * Only available in "advanced" API mode.
+ *
+ * @param {number} x
+ * @param {number} y
+ * @param {number} width
+ * @param {number} height
+ * @param {Matrix} matrix The matrix that will be applied to convert the form objects coordinate system to
+ * the parent's.
+ * @function
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name beginFormObject
+ */
+
+
+ API.beginFormObject = function (x, y, width, height, matrix) {
+ advancedApiModeTrap("beginFormObject()"); // The user can set the output target to a new form object. Nested form objects are possible.
+ // Currently, they use the resource dictionary of the surrounding stream. This should be changed, as
+ // the PDF-Spec states:
+ // "In PDF 1.2 and later versions, form XObjects may be independent of the content streams in which
+ // they appear, and this is strongly recommended although not requiredIn PDF 1.2 and later versions,
+ // form XObjects may be independent of the content streams in which they appear, and this is strongly
+ // recommended although not required"
+
+ beginNewRenderTarget(x, y, width, height, matrix);
+ return this;
+ };
+ /**
+ * Completes and saves the form object. Only available in "advanced" API mode.
+ * @param {String} key The key by which this form object can be referenced.
+ * @function
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name endFormObject
+ */
+
+
+ API.endFormObject = function (key) {
+ advancedApiModeTrap("endFormObject()");
+ endFormObject(key);
+ return this;
+ };
+ /**
+ * Draws the specified form object by referencing to the respective pdf XObject created with
+ * {@link API.beginFormObject} and {@link endFormObject}.
+ * The location is determined by matrix.
+ *
+ * Only available in "advanced" API mode.
+ *
+ * @param {String} key The key to the form object.
+ * @param {Matrix} matrix The matrix applied before drawing the form object.
+ * @function
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name doFormObject
+ */
+
+
+ API.doFormObject = function (key, matrix) {
+ advancedApiModeTrap("doFormObject()");
+ var xObject = renderTargets[renderTargetMap[key]];
+ out("q");
+ out(matrix.toString() + " cm");
+ out("/" + xObject.id + " Do");
+ out("Q");
+ return this;
+ };
+ /**
+ * Returns the form object specified by key.
+ * @param key {String}
+ * @returns {{x: number, y: number, width: number, height: number, matrix: Matrix}}
+ * @function
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name getFormObject
+ */
+
+
+ API.getFormObject = function (key) {
+ var xObject = renderTargets[renderTargetMap[key]];
+ return {
+ x: xObject.x,
+ y: xObject.y,
+ width: xObject.width,
+ height: xObject.height,
+ matrix: xObject.matrix
+ };
+ };
+ /**
+ * A matrix object for 2D homogenous transformations:
+ * | a b 0 |
+ * | c d 0 |
+ * | e f 1 |
+ * pdf multiplies matrices righthand: v' = v x m1 x m2 x ...
+ * @param {number} a
+ * @param {number} b
+ * @param {number} c
+ * @param {number} d
+ * @param {number} e
+ * @param {number} f
+ * @constructor
+ */
+
+
+ API.Matrix = Matrix;
+ /**
+ * Multiplies two matrices. (see {@link Matrix})
+ * @param {Matrix} m1
+ * @param {Matrix} m2
+ * @methodOf jsPDF#
+ * @name matrixMult
+ */
+
+ API.matrixMult = matrixMult;
+ /**
+ * The unit matrix (equivalent to new Matrix(1, 0, 0, 1, 0, 0)).
+ * @type {Matrix}
+ * @fieldOf jsPDF#
+ * @name unitMatrix
+ */
+
+ API.unitMatrix = unitMatrix;
+
+ var Pattern = function Pattern(gState, matrix) {
+ this.gState = gState;
+ this.matrix = matrix;
+ this.id = ""; // set by addPattern()
+
+ this.objectNumber = -1; // will be set by putPattern()
+ };
+ /**
+ * A pattern describing a shading pattern.
+ *
+ * Only available in "advanced" API mode.
+ *
+ * @param {String} type One of "axial" or "radial"
+ * @param {Array} coords Either [x1, y1, x2, y2] for "axial" type describing the two interpolation points
+ * or [x1, y1, r, x2, y2, r2] for "radial" describing inner and the outer circle.
+ * @param {Array} colors An array of objects with the fields "offset" and "color". "offset" describes
+ * the offset in parameter space [0, 1]. "color" is an array of length 3 describing RGB values in [0, 255].
+ * @param {GState=} gState An additional graphics state that gets applied to the pattern (optional).
+ * @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system
+ * and the use coordinate system (optional).
+ * @constructor
+ * @extends API.Pattern
+ */
+
+
+ API.ShadingPattern = function (type, coords, colors, gState, matrix) {
+ advancedApiModeTrap("ShadingPattern"); // see putPattern() for information how they are realized
+
+ this.type = type === "axial" ? 2 : 3;
+ this.coords = coords;
+ this.colors = colors;
+ Pattern.call(this, gState, matrix);
+ };
+ /**
+ * A PDF Tiling pattern.
+ *
+ * Only available in "advanced" API mode.
+ *
+ * @param {Array.} boundingBox The bounding box at which one pattern cell gets clipped.
+ * @param {Number} xStep Horizontal spacing between pattern cells.
+ * @param {Number} yStep Vertical spacing between pattern cells.
+ * @param {API.GState=} gState An additional graphics state that gets applied to the pattern (optional).
+ * @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system
+ * and the use coordinate system (optional).
+ * @constructor
+ * @extends API.Pattern
+ */
+
+
+ API.TilingPattern = function (boundingBox, xStep, yStep, gState, matrix) {
+ advancedApiModeTrap("TilingPattern");
+ this.boundingBox = boundingBox;
+ this.xStep = xStep;
+ this.yStep = yStep;
+ this.stream = ""; // set by endTilingPattern();
+
+ this.cloneIndex = 0;
+ Pattern.call(this, gState, matrix);
+ };
+
+ API.TilingPattern.prototype = {
+ createClone: function createClone(patternKey, boundingBox, xStep, yStep, matrix) {
+ var clone = new API.TilingPattern(boundingBox || this.boundingBox, xStep || this.xStep, yStep || this.yStep, this.gState, matrix || this.matrix);
+ clone.stream = this.stream;
+ var key = patternKey + "$$" + this.cloneIndex++ + "$$";
+ addPattern(key, clone);
+ return clone;
+ }
+ };
+ /**
+ * Adds a new {@link API.ShadingPattern} for later use. Only available in "advanced" API mode.
+ * @param {String} key
+ * @param {Pattern} pattern
+ * @function
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name addPattern
+ */
+
+ API.addShadingPattern = function (key, pattern) {
+ advancedApiModeTrap("addShadingPattern()");
+ addPattern(key, pattern);
+ return this;
+ };
+ /**
+ * Begins a new tiling pattern. All subsequent render calls are drawn to this pattern until {@link API.endTilingPattern}
+ * gets called. Only available in "advanced" API mode.
+ * @param {API.Pattern} pattern
+ * @methodOf jsPDF#
+ * @name beginTilingPattern
+ */
+
+
+ API.beginTilingPattern = function (pattern) {
+ advancedApiModeTrap("beginTilingPattern()");
+ beginNewRenderTarget(pattern.boundingBox[0], pattern.boundingBox[1], pattern.boundingBox[2] - pattern.boundingBox[0], pattern.boundingBox[3] - pattern.boundingBox[1], pattern.matrix);
+ };
+ /**
+ * Ends a tiling pattern and sets the render target to the one active before {@link API.beginTilingPattern} has been called.
+ *
+ * Only available in "advanced" API mode.
+ *
+ * @param {string} key A unique key that is used to reference this pattern at later use.
+ * @param {API.Pattern} pattern The pattern to end.
+ * @methodOf jsPDF#
+ * @name endTilingPattern
+ */
+
+
+ API.endTilingPattern = function (key, pattern) {
+ advancedApiModeTrap("endTilingPattern()"); // retrieve the stream
+
+ pattern.stream = pages[currentPage].join("\n");
+ addPattern(key, pattern);
+ events.publish("endTilingPattern", pattern); // restore state from stack
+
+ renderTargetStack.pop().restore();
+ };
+ /**
+ * Adds text to page. Supports adding multiline text when 'text' argument is an Array of Strings.
+ *
+ * @function
+ * @param {String|Array} text String or array of strings to be added to the page. Each line is shifted one line down
+ * per font, spacing settings declared before this call.
+ * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
+ * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
+ * @param {Object} options Collection of settings signalling how the text must be encoded. Defaults are sane. If you
+ * think you want to pass some flags, you likely can read the source.
+ * @param {number|Matrix} transform If transform is a number the text will be rotated by this value around the
+ * anchor set by x and y.
+ *
+ * If it is a Matrix, this matrix gets directly applied to the text, which allows shearing
+ * effects etc.; the x and y offsets are then applied AFTER the coordinate system has been established by this
+ * matrix. This means passing a rotation matrix that is equivalent to some rotation angle will in general yield a
+ * DIFFERENT result. A matrix is only allowed in "advanced" API mode.
+ *
+ * @param align {string}
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name text
+ */
+
+
+ API.text = function (text, x, y, options, transform) {
+ /**
+ * Inserts something like this into PDF
+ * BT
+ * /F1 16 Tf % Font name + size
+ * 16 TL % How many units down for next line in multiline text
+ * 0 g % color
+ * 28.35 813.54 Td % position
+ * (line one) Tj
+ * T* (line two) Tj
+ * T* (line three) Tj
+ * ET
+ */
+ if (transform !== undefined && transform instanceof Matrix) {
+ advancedApiModeTrap("The transform parameter of text() with a Matrix value");
+ }
+
+ var xtra = "";
+ var isHex = false;
+ var lineHeight = lineHeightProportion;
+ var scope = this;
+
+ function ESC(s) {
+ s = s.split("\t").join(Array(options.TabLen || 9).join(" "));
+ return pdfEscape(s, flags);
+ }
+
+ function transformTextToSpecialArray(text) {
+ //we don't want to destroy original text array, so cloning it
+ var sa = text.concat();
+ var da = [];
+ var len = sa.length;
+ var curDa; //we do array.join('text that must not be PDFescaped")
+ //thus, pdfEscape each component separately
+
+ while (len--) {
+ curDa = sa.shift();
+
+ if (typeof curDa === "string") {
+ da.push(curDa);
+ } else {
+ if (Object.prototype.toString.call(text) === "[object Array]" && curDa.length === 1) {
+ da.push(curDa[0]);
+ } else {
+ da.push([curDa[0], curDa[1], curDa[2]]);
+ }
+ }
+ }
+
+ return da;
+ }
+
+ function processTextByFunction(text, processingFunction) {
+ var result;
+
+ if (typeof text === "string") {
+ result = processingFunction(text)[0];
+ } else if (Object.prototype.toString.call(text) === "[object Array]") {
+ //we don't want to destroy original text array, so cloning it
+ var sa = text.concat();
+ var da = [];
+ var len = sa.length;
+ var curDa;
+ var tmpResult; //we do array.join('text that must not be PDFescaped")
+ //thus, pdfEscape each component separately
+
+ while (len--) {
+ curDa = sa.shift();
+
+ if (typeof curDa === "string") {
+ da.push(processingFunction(curDa)[0]);
+ } else if (Object.prototype.toString.call(curDa) === "[object Array]" && curDa[0] === "string") {
+ tmpResult = processingFunction(curDa[0], curDa[1], curDa[2]);
+ da.push([tmpResult[0], tmpResult[1], tmpResult[2]]);
+ }
+ }
+
+ result = da;
+ }
+
+ return result;
+ } //backwardsCompatibility
+
+
+ var tmp; // Pre-August-2012 the order of arguments was function(x, y, text, flags)
+ // in effort to make all calls have similar signature like
+ // function(data, coordinates... , miscellaneous)
+ // this method had its args flipped.
+ // code below allows backward compatibility with old arg order.
+
+ if (typeof text === "number") {
+ tmp = y;
+ y = x;
+ x = text;
+ text = tmp;
+ }
+
+ var flags = arguments[3];
+ var angle = arguments[4];
+ var align = arguments[5];
+
+ if (_typeof(flags) !== "object" || flags === null) {
+ if (typeof angle === "string") {
+ align = angle;
+ angle = null;
+ }
+
+ if (typeof flags === "string") {
+ align = flags;
+ flags = null;
+ }
+
+ if (typeof flags === "number") {
+ angle = flags;
+ flags = null;
+ }
+
+ options = {
+ flags: flags,
+ angle: angle,
+ align: align
+ };
+ } //Check if text is of type String
+
+
+ var textIsOfTypeString = false;
+ var tmpTextIsOfTypeString = true;
+
+ if (typeof text === "string") {
+ textIsOfTypeString = true;
+ } else if (Object.prototype.toString.call(text) === "[object Array]") {
+ //we don't want to destroy original text array, so cloning it
+ var sa = text.concat();
+ var da = [];
+ var len = sa.length;
+ var curDa; //we do array.join('text that must not be PDFescaped")
+ //thus, pdfEscape each component separately
+
+ while (len--) {
+ curDa = sa.shift();
+
+ if (typeof curDa !== "string" || Object.prototype.toString.call(curDa) === "[object Array]" && typeof curDa[0] !== "string") {
+ tmpTextIsOfTypeString = false;
+ }
+ }
+
+ textIsOfTypeString = tmpTextIsOfTypeString;
+ }
+
+ if (textIsOfTypeString === false) {
+ throw new Error('Type of text must be string or Array. "' + text + '" is not recognized.');
+ } //Escaping
+
+
+ var activeFontEncoding = fonts[activeFontKey].encoding;
+
+ if (activeFontEncoding === "WinAnsiEncoding" || activeFontEncoding === "StandardEncoding") {
+ text = processTextByFunction(text, function (text, posX, posY) {
+ return [ESC(text), posX, posY];
+ });
+ } //If there are any newlines in text, we assume
+ //the user wanted to print multiple lines, so break the
+ //text up into an array. If the text is already an array,
+ //we assume the user knows what they are doing.
+ //Convert text into an array anyway to simplify
+ //later code.
+
+
+ if (typeof text === "string") {
+ if (text.match(/[\r?\n]/)) {
+ text = text.split(/\r\n|\r|\n/g);
+ } else {
+ text = [text];
+ }
+ } //multiline
+
+
+ var maxWidth = options.maxWidth || 0;
+
+ if (maxWidth > 0) {
+ if (typeof text === "string") {
+ text = scope.splitTextToSize(text, maxWidth);
+ } else if (Object.prototype.toString.call(text) === "[object Array]") {
+ text = scope.splitTextToSize(text.join(" "), maxWidth);
+ }
+ } //creating Payload-Object to make text byRef
+
+
+ var payload = {
+ text: text,
+ x: x,
+ y: y,
+ options: options,
+ mutex: {
+ pdfEscape: pdfEscape,
+ activeFontKey: activeFontKey,
+ fonts: fonts,
+ activeFontSize: activeFontSize
+ }
+ };
+ events.publish("preProcessText", payload);
+ text = payload.text;
+ options = payload.options; //angle
+
+ var angle = options.angle;
+ var transformationMatrix = null;
+
+ if (angle && typeof angle === "number") {
+ angle *= Math.PI / 180;
+
+ if (apiMode === ApiMode.ADVANCED) {
+ angle = -angle;
+ }
+
+ var c = Math.cos(angle),
+ s = Math.sin(angle);
+ transformationMatrix = new Matrix(c, s, -s, c, 0, 0);
+ } else if (angle && angle instanceof Matrix) {
+ transformationMatrix = angle;
+ } //charSpace
+
+
+ var charSpace = options.charSpace;
+
+ if (charSpace !== undefined) {
+ xtra += charSpace + " Tc\n";
+ } //lang
+
+
+ var lang = options.lang;
+ var tmpRenderingMode = -1;
+ var parmRenderingMode = options.renderingMode || options.stroke;
+ var pageContext = scope.internal.getCurrentPageInfo().pageContext;
+
+ switch (parmRenderingMode) {
+ case 0:
+ case false:
+ case "fill":
+ tmpRenderingMode = 0;
+ break;
+
+ case 1:
+ case true:
+ case "stroke":
+ tmpRenderingMode = 1;
+ break;
+
+ case 2:
+ case "fillThenStroke":
+ tmpRenderingMode = 2;
+ break;
+
+ case 3:
+ case "invisible":
+ tmpRenderingMode = 3;
+ break;
+
+ case 4:
+ case "fillAndAddForClipping":
+ tmpRenderingMode = 4;
+ break;
+
+ case 5:
+ case "strokeAndAddPathForClipping":
+ tmpRenderingMode = 5;
+ break;
+
+ case 6:
+ case "fillThenStrokeAndAddToPathForClipping":
+ tmpRenderingMode = 6;
+ break;
+
+ case 7:
+ case "addToPathForClipping":
+ tmpRenderingMode = 7;
+ break;
+ }
+
+ var usedRenderingMode = pageContext.usedRenderingMode || -1; //if the coder wrote it explicitly to use a specific
+ //renderingMode, then use it
+
+ if (tmpRenderingMode !== -1) {
+ xtra += tmpRenderingMode + " Tr\n"; //otherwise check if we used the rendering Mode already
+ //if so then set the rendering Mode...
+ } else if (usedRenderingMode !== -1) {
+ xtra += "0 Tr\n";
+ }
+
+ if (tmpRenderingMode !== -1) {
+ pageContext.usedRenderingMode = tmpRenderingMode;
+ } //align
+
+
+ var align = options.align || "left";
+ var leading = activeFontSize * lineHeight;
+ var pageWidth = scope.internal.pageSize.getWidth();
+ var k = scope.internal.scaleFactor;
+ var activeFont = fonts[activeFontKey];
+ var charSpace = options.charSpace || activeCharSpace;
+ var maxWidth = options.maxWidth || 0;
+ var lineWidths;
+ var flags = {};
+ var wordSpacingPerLine = [];
+
+ if (Object.prototype.toString.call(text) === "[object Array]") {
+ var da = transformTextToSpecialArray(text);
+ var newY;
+ var maxLineLength;
+ var lineWidths;
+
+ if (align !== "left") {
+ lineWidths = da.map(function (v) {
+ return scope.getStringUnitWidth(v, {
+ font: activeFont,
+ charSpace: charSpace,
+ fontSize: activeFontSize
+ }) * activeFontSize / k;
+ });
+ }
+
+ var maxLineLength = Math.max.apply(Math, lineWidths); //The first line uses the "main" Td setting,
+ //and the subsequent lines are offset by the
+ //previous line's x coordinate.
+
+ var prevWidth = 0;
+ var delta;
+ var newX;
+ var xOffset = 0;
+
+ if (align === "right") {
+ xOffset = -lineWidths[0];
+ text = [];
+
+ for (var i = 0, len = da.length; i < len; i++) {
+ delta = maxLineLength - lineWidths[i];
+
+ if (i === 0) {
+ newX = 0;
+ newY = 0;
+ } else {
+ newX = prevWidth - lineWidths[i];
+ newY = leading;
+ }
+
+ text.push([da[i], newX, newY]);
+ prevWidth = lineWidths[i];
+ }
+ } else if (align === "center") {
+ xOffset = -lineWidths[0] / 2;
+ text = [];
+
+ for (var i = 0, len = da.length; i < len; i++) {
+ delta = (maxLineLength - lineWidths[i]) / 2;
+
+ if (i === 0) {
+ newX = 0;
+ newY = 0;
+ } else {
+ newX = (prevWidth - lineWidths[i]) / 2;
+ newY = leading;
+ }
+
+ text.push([da[i], newX, newY]);
+ prevWidth = lineWidths[i];
+ }
+ } else if (align === "left") {
+ text = [];
+
+ for (var i = 0, len = da.length; i < len; i++) {
+ text.push(da[i]);
+ }
+ } else if (align === "justify") {
+ text = [];
+ var maxWidth = maxWidth !== 0 ? maxWidth : pageWidth;
+
+ for (var i = 0, len = da.length; i < len; i++) {
+ newX = 0;
+ newY = i === 0 ? 0 : leading;
+
+ if (i < len - 1) {
+ wordSpacingPerLine.push((maxWidth - lineWidths[i]) / (da[i].split(" ").length - 1));
+ }
+
+ text.push([da[i], newX, newY]);
+ }
+ } else {
+ throw new Error('Unrecognized alignment option, use "left", "center", "right" or "justify".');
+ }
+ } //R2L
+
+
+ var doReversing = typeof options.R2L === "boolean" ? options.R2L : R2L;
+
+ if (doReversing === true) {
+ text = processTextByFunction(text, function (text, posX, posY) {
+ return [text.split("").reverse().join(""), posX, posY];
+ });
+ } //creating Payload-Object to make text byRef
+
+
+ var payload = {
+ text: text,
+ x: x,
+ y: y,
+ options: options,
+ mutex: {
+ pdfEscape: pdfEscape,
+ activeFontKey: activeFontKey,
+ fonts: fonts,
+ activeFontSize: activeFontSize
+ }
+ };
+ events.publish("postProcessText", payload);
+ text = payload.text;
+ isHex = payload.mutex.isHex;
+ var da = transformTextToSpecialArray(text);
+ text = [];
+ var variant = 0;
+ var len = da.length;
+ var posX;
+ var posY;
+ var content;
+ var wordSpacing = "";
+
+ for (var i = 0; i < len; i++) {
+ wordSpacing = "";
+
+ if (Object.prototype.toString.call(da[i]) !== "[object Array]") {
+ content = (isHex ? "<" : "(") + da[i] + (isHex ? ">" : ")");
+ variant = 0;
+ } else if (Object.prototype.toString.call(da[i]) === "[object Array]") {
+ posX = da[i][1] * k; // x offset must always be scaled!
+ // y offset/leading must NOT be scaled by k as it is dependent of the font size, which is always given
+ // in plain pt
+
+ posY = -da[i][2];
+ content = (isHex ? "<" : "(") + da[i][0] + (isHex ? ">" : ")");
+ variant = 1;
+ }
+
+ if (wordSpacingPerLine !== undefined && wordSpacingPerLine[i] !== undefined) {
+ wordSpacing = wordSpacingPerLine[i] + " Tw\n";
+ }
+
+ if (variant === 1 && i > 0) {
+ text.push(wordSpacing + hpf(posX) + " " + hpf(posY) + " " + " Td\n" + content);
+ } else {
+ text.push(wordSpacing + content);
+ }
+ }
+
+ if (variant === 0) {
+ text = text.join(" Tj\nT* ");
+ } else {
+ text = text.join(" Tj\n");
+ }
+
+ if (apiMode === ApiMode.ADVANCED && transformationMatrix === null) {
+ transformationMatrix = unitMatrix;
+ }
+
+ if (transformationMatrix !== null) {
+ // It is kind of more intuitive to apply a plain rotation around the text anchor set by x and y
+ // but when the user supplies an arbitrary transformation matrix, the x and y offsets should be applied
+ // in the coordinate system established by this matrix
+ if (typeof angle === "number") {
+ transformationMatrix = matrixMult(transformationMatrix, new Matrix(1, 0, 0, 1, scaleByK(x), transformScaleY(y)));
+ } else {
+ transformationMatrix = matrixMult(new Matrix(1, 0, 0, 1, scaleByK(x), transformScaleY(y)), transformationMatrix);
+ }
+
+ transformationMatrix = matrixMult( // xOffset must always be scaled!
+ new Matrix(1, 0, 0, 1, xOffset * k, 0), transformationMatrix);
+
+ if (apiMode === ApiMode.ADVANCED) {
+ transformationMatrix = matrixMult(new Matrix(1, 0, 0, -1, 0, 0), transformationMatrix);
+ }
+
+ text = transformationMatrix.toString() + " Tm\n" + text;
+ } else {
+ text = hpf(scaleByK(x + xOffset)) + " " + hpf(transformScaleY(y)) + " " + " Td\n" + text;
+ }
+
+ text += " Tj\n";
+ var result = "BT\n/" + activeFontKey + " " + activeFontSize + " Tf\n" + // font face, style, size
+ (activeFontSize * lineHeight).toFixed(2) + " TL\n" + // line spacing
+ textColor + "\n";
+ result += xtra;
+ result += text;
+ result += "ET";
+ out(result);
+ return scope;
+ };
+ /**
+ * Letter spacing method to print text with gaps
+ *
+ * @function
+ * @instance
+ * @param {String|Array} text String to be added to the page.
+ * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
+ * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
+ * @param {number} spacing Spacing (in units declared at inception)
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name lstext
+ * @deprecated We'll be removing this function. It doesn't take character width into account.
+ */
+
+
+ API.lstext = function (text, x, y, spacing) {
+ console.warn("jsPDF.lstext is deprecated");
+
+ for (var i = 0, len = text.length; i < len; i++, x += spacing) {
+ this.text(text[i], x, y);
+ }
+
+ return this;
+ };
+ /**
+ * Draws a line from (x1, y1) to (x2, y2). No extra call to {@link API.stroke} is needed.
+ * @param {number} x1
+ * @param {number} y1
+ * @param {number} x2
+ * @param {number} y2
+ * @return {jsPDF}
+ * @methodOf jsPDF#
+ * @name line
+ */
+
+
+ API.line = function (x1, y1, x2, y2) {
+ if (apiMode === ApiMode.COMPAT) {
+ return this.lines([[x2 - x1, y2 - y1]], x1, y1, [1, 1], "D");
+ } else {
+ return this.lines([[x2 - x1, y2 - y1]], x1, y1, [1, 1]).stroke();
+ }
+ };
+ /**
+ * Begin a new subpath by moving the current point to coordinates (x, y). The PDF "m" operator.
+ * @param {number} x
+ * @param {number} y
+ * @methodOf jsPDF#
+ * @name moveTo
+ */
+
+
+ API.moveTo = function (x, y) {
+ out(hpf(scaleByK(x)) + " " + hpf(transformScaleY(y)) + " m");
+ };
+ /**
+ * Append a straight line segment from the current point to the point (x, y). The PDF "l" operator.
+ * @param {number} x
+ * @param {number} y
+ * @methodOf jsPDF#
+ * @name lineTo
+ */
+
+
+ API.lineTo = function (x, y) {
+ out(hpf(scaleByK(x)) + " " + hpf(transformScaleY(y)) + " l");
+ };
+ /**
+ * Append a cubic Bézier curve to the current path. The curve shall extend from the current point to the point
+ * (x3, y3), using (x1, y1) and (x2, y2) as Bézier control points. The new current point shall be (x3, x3).
+ * @param {number} x1
+ * @param {number} y1
+ * @param {number} x2
+ * @param {number} y2
+ * @param {number} x3
+ * @param {number} y3
+ * @methodOf jsPDF#
+ * @name curveTo
+ */
+
+
+ API.curveTo = function (x1, y1, x2, y2, x3, y3) {
+ out([hpf(scaleByK(x1)), hpf(transformScaleY(y1)), hpf(scaleByK(x2)), hpf(transformScaleY(y2)), hpf(scaleByK(x3)), hpf(transformScaleY(y3)), "c"].join(" "));
+ }; // PDF supports these path painting and clip path operators:
+ //
+ // S - stroke
+ // s - close/stroke
+ // f (F) - fill non-zero
+ // f* - fill evenodd
+ // B - fill stroke nonzero
+ // B* - fill stroke evenodd
+ // b - close fill stroke nonzero
+ // b* - close fill stroke evenodd
+ // n - nothing (consume path)
+ // W - clip nonzero
+ // W* - clip evenodd
+ //
+ // In order to keep the API small, we omit the close-and-fill/stroke operators and provide a separate close()
+ // method.
+
+ /**
+ * Close the current path. The PDF "h" operator.
+ * @return jsPDF
+ * @methodOf jsPDF#
+ * @name close
+ */
+
+
+ API.close = function () {
+ out("h");
+ return this;
+ };
+ /**
+ * Stroke the path. The PDF "S" operator.
+ * @return jsPDF
+ * @methodOf jsPDF#
+ * @name stroke
+ */
+
+
+ API.stroke = function () {
+ out("S");
+ return this;
+ };
+ /**
+ * @typedef {Object} PatternData
+ * @property {string} key The key of the pattern
+ * @property {Matrix} matrix The matrix that gets applied to the pattern right before drawing.
+ * @property {number[]|undefined} boundingBox Only relevant for tiling patterns. The bounding box at which one
+ * pattern cell gets clipped
+ * @property {number|undefined} xStep Only relevant for tiling patterns. Horizontal spacing between pattern cells
+ * @property {number|undefined} yStep Only relevant for tiling patterns. Vertical spacing between pattern cells
+ */
+
+ /**
+ * Fill the current path using the nonzero winding number rule. If a pattern is provided, the path will be filled
+ * with this pattern, otherwise with the current fill color. Equivalent to the PDF "f" operator.
+ * @param {PatternData=} pattern If provided the path will be filled with this pattern
+ * @return jsPDF
+ * @methodOf jsPDF#
+ * @name fill
+ */
+
+
+ API.fill = function (pattern) {
+ fillWithOptionalPattern("f", pattern);
+ return this;
+ };
+ /**
+ * Fill the current path using the even-odd rule. The PDF f* operator.
+ * @see API.fill
+ * @param {PatternData=} pattern Optional pattern
+ * @return jsPDF
+ * @methodOf jsPDF#
+ * @name fillEvenOdd
+ */
+
+
+ API.fillEvenOdd = function (pattern) {
+ fillWithOptionalPattern("f*", pattern);
+ return this;
+ };
+ /**
+ * Fill using the nonzero winding number rule and then stroke the current Path. The PDF "B" operator.
+ * @see API.fill
+ * @param {PatternData=} pattern Optional pattern
+ * @return jsPDF
+ * @methodOf jsPDF#
+ * @name fillStroke
+ */
+
+
+ API.fillStroke = function (pattern) {
+ fillWithOptionalPattern("B", pattern);
+ return this;
+ };
+ /**
+ * Fill using the even-odd rule and then stroke the current Path. The PDF "B" operator.
+ * @see API.fill
+ * @param {PatternData=} pattern Optional pattern
+ * @return jsPDF
+ * @methodOf jsPDF#
+ * @name fillStrokeEvenOdd
+ */
+
+
+ API.fillStrokeEvenOdd = function (pattern) {
+ fillWithOptionalPattern("B*", pattern);
+ return this;
+ };
+
+ function fillWithOptionalPattern(style, pattern) {
+ if (_typeof(pattern) === "object") {
+ fillWithPattern(pattern, style);
+ } else {
+ out(style);
+ }
+ }
+ /**
+ * Modify the current clip path by intersecting it with the current path using the nonzero winding number rule. Note
+ * that this will NOT consume the current path. In order to only use this path for clipping call
+ * {@link API.discardPath} afterwards.
+ *
+ * When in "compat" API mode this method has a historical bug and will always stroke the path as well, use
+ * {@link API.clip_fixed} instead.
+ * @return jsPDF
+ * @methodOf jsPDF#
+ * @name clip
+ */
+
+
+ API.clip = function () {
+ if (apiMode === ApiMode.COMPAT) {
+ // By patrick-roberts, github.com/MrRio/jsPDF/issues/328
+ // Call .clip() after calling .rect() with a style argument of null
+ out("W"); // clip
+
+ out("S"); // stroke path; necessary for clip to work
+ } else {
+ out("W");
+ }
+
+ return this;
+ };
+ /**
+ * Modify the current clip path by intersecting it with the current path using the even-odd rule. Note
+ * that this will NOT consume the current path. In order to only use this path for clipping call
+ * {@link API.discardPath} afterwards.
+ *
+ * @return jsPDF
+ * @methodOf jsPDF#
+ * @name clipEvenOdd
+ */
+
+
+ API.clipEvenOdd = function () {
+ out("W*");
+ return this;
+ };
+ /**
+ * Consumes the current path without any effect. Mainly used in combination with {@link clip} or
+ * {@link clipEvenOdd}. The PDF "n" operator.
+ * @return {jsPDF}
+ * @methodOf jsPDF#
+ * @name discardPath
+ */
+
+
+ API.discardPath = function () {
+ out("n");
+ return this;
+ };
+ /**
+ * This fixes the previous function clip(). Perhaps the 'stroke path' hack was due to the missing 'n' instruction?
+ * We introduce the fixed version so as to not break API.
+ * @param fillRule
+ * @deprecated Don't use this method when in "advanced" API mode.
+ * @methodOf jsPDF#
+ * @name clip_fixed
+ */
+
+
+ API.clip_fixed = function (fillRule) {
+ // Call .clip() after calling drawing ops with a style argument of null
+ // W is the PDF clipping op
+ if ("evenodd" === fillRule) {
+ out("W*");
+ } else {
+ out("W");
+ } // End the path object without filling or stroking it.
+ // This operator is a path-painting no-op, used primarily for the side effect of changing the current clipping path
+ // (see Section 4.4.3, “Clipping Path Operators”)
+
+
+ out("n");
+ };
+ /**
+ * @typedef {Object} PatternData
+ * {Matrix|undefined} matrix
+ * {Number|undefined} xStep
+ * {Number|undefined} yStep
+ * {Array.|undefined} boundingBox
+ */
+
+ /**
+ * Adds series of curves (straight lines or cubic bezier curves) to canvas, starting at `x`, `y` coordinates.
+ * All data points in `lines` are relative to last line origin.
+ * `x`, `y` become x1,y1 for first line / curve in the set.
+ * For lines you only need to specify [x2, y2] - (ending point) vector against x1, y1 starting point.
+ * For bezier curves you need to specify [x2,y2,x3,y3,x4,y4] - vectors to control points 1, 2, ending point. All vectors are against the start of the curve - x1,y1.
+ *
+ * @example .lines([[2,2],[-2,2],[1,1,2,2,3,3],[2,1]], 212,110, 10) // line, line, bezier curve, line
+ * @param {Array} lines Array of *vector* shifts as pairs (lines) or sextets (cubic bezier curves).
+ * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
+ * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
+ * @param {Number} scale (Defaults to [1.0,1.0]) x,y Scaling factor for all vectors. Elements can be any floating number Sub-one makes drawing smaller. Over-one grows the drawing. Negative flips the direction.
+ * @param {String=} style A string specifying the painting style or null. Valid styles include:
+ * 'S' [default] - stroke,
+ * 'F' - fill,
+ * and 'DF' (or 'FD') - fill then stroke.
+ * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
+ * method calls. The last drawing method call used to define the shape should not have a null style argument.
+ *
+ * In "advanced" API mode this parameter is deprecated.
+ * @param {Boolean=} closed If true, the path is closed with a straight line from the end of the last curve to the starting point.
+ * @param {String=} patternKey The pattern key for the pattern that should be used to fill the path. Deprecated!
+ * @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
+ * will modify the pattern on use. Deprecated!
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name lines
+ */
+
+
+ API.lines = function (lines, x, y, scale, style, closed, patternKey, patternData) {
+ var scalex, scaley, i, l, leg, x2, y2, x3, y3, x4, y4; // Pre-August-2012 the order of arguments was function(x, y, lines, scale, style)
+ // in effort to make all calls have similar signature like
+ // function(content, coordinateX, coordinateY , miscellaneous)
+ // this method had its args flipped.
+ // code below allows backward compatibility with old arg order.
+
+ if (typeof lines === "number") {
+ var tmp = y;
+ y = x;
+ x = lines;
+ lines = tmp;
+ }
+
+ scale = scale || [1, 1]; // starting point
+
+ this.moveTo(x, y);
+ scalex = scale[0];
+ scaley = scale[1];
+ l = lines.length; //, x2, y2 // bezier only. In page default measurement "units", *after* scaling
+ //, x3, y3 // bezier only. In page default measurement "units", *after* scaling
+ // ending point for all, lines and bezier. . In page default measurement "units", *after* scaling
+
+ x4 = x; // last / ending point = starting point for first item.
+
+ y4 = y; // last / ending point = starting point for first item.
+
+ for (i = 0; i < l; i++) {
+ leg = lines[i];
+
+ if (leg.length === 2) {
+ // simple line
+ x4 = leg[0] * scalex + x4; // here last x4 was prior ending point
+
+ y4 = leg[1] * scaley + y4; // here last y4 was prior ending point
+
+ this.lineTo(x4, y4);
+ } else {
+ // bezier curve
+ x2 = leg[0] * scalex + x4; // here last x4 is prior ending point
+
+ y2 = leg[1] * scaley + y4; // here last y4 is prior ending point
+
+ x3 = leg[2] * scalex + x4; // here last x4 is prior ending point
+
+ y3 = leg[3] * scaley + y4; // here last y4 is prior ending point
+
+ x4 = leg[4] * scalex + x4; // here last x4 was prior ending point
+
+ y4 = leg[5] * scaley + y4; // here last y4 was prior ending point
+
+ this.curveTo(x2, y2, x3, y3, x4, y4);
+ }
+ }
+
+ if (closed) {
+ this.close();
+ }
+
+ putStyle(style, patternKey, patternData);
+ return this;
+ };
+ /**
+ * Similar to {@link API.lines} but all coordinates are interpreted as absolute coordinates instead of relative.
+ * @param {Array} lines An array of {op: operator, c: coordinates} object, where op is one of "m" (move to), "l" (line to)
+ * "c" (cubic bezier curve) and "h" (close (sub)path)). c is an array of coordinates. "m" and "l" expect two, "c"
+ * six and "h" an empty array (or undefined).
+ * @param {String=} style The style. Deprecated!
+ * @param {String=} patternKey The pattern key for the pattern that should be used to fill the path. Deprecated!
+ * @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
+ * will modify the pattern on use. Deprecated!
+ * @function
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name path
+ */
+
+
+ API.path = function (lines, style, patternKey, patternData) {
+ for (var i = 0; i < lines.length; i++) {
+ var leg = lines[i];
+ var coords = leg.c;
+
+ switch (leg.op) {
+ case "m":
+ this.moveTo(coords[0], coords[1]);
+ break;
+
+ case "l":
+ this.lineTo(coords[0], coords[1]);
+ break;
+
+ case "c":
+ this.curveTo.apply(this, coords);
+ break;
+
+ case "h":
+ this.close();
+ break;
+ }
+ }
+
+ putStyle(style, patternKey, patternData);
+ return this;
+ };
+ /**
+ * Adds a rectangle to PDF
+ *
+ * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
+ * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
+ * @param {Number} w Width (in units declared at inception of PDF document)
+ * @param {Number} h Height (in units declared at inception of PDF document)
+ * @param {String=} style A string specifying the painting style or null. Valid styles include:
+ * 'S' [default] - stroke,
+ * 'F' - fill,
+ * and 'DF' (or 'FD') - fill then stroke.
+ * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
+ * method calls. The last drawing method call used to define the shape should not have a null style argument.
+ *
+ * In "advanced" API mode this parameter is deprecated.
+ * @param {String=} patternKey The pattern key for the pattern that should be used to fill the primitive. Deprecated!
+ * @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
+ * will modify the pattern on use. Deprecated!
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name rect
+ */
+
+
+ API.rect = function (x, y, w, h, style, patternKey, patternData) {
+ if (apiMode === ApiMode.COMPAT) {
+ h = -h;
+ }
+
+ out([hpf(scaleByK(x)), hpf(transformScaleY(y)), hpf(scaleByK(w)), hpf(scaleByK(h)), "re"].join(" "));
+ putStyle(style, patternKey, patternData);
+ return this;
+ };
+ /**
+ * Adds a triangle to PDF
+ *
+ * @param {Number} x1 Coordinate (in units declared at inception of PDF document) against left edge of the page
+ * @param {Number} y1 Coordinate (in units declared at inception of PDF document) against upper edge of the page
+ * @param {Number} x2 Coordinate (in units declared at inception of PDF document) against left edge of the page
+ * @param {Number} y2 Coordinate (in units declared at inception of PDF document) against upper edge of the page
+ * @param {Number} x3 Coordinate (in units declared at inception of PDF document) against left edge of the page
+ * @param {Number} y3 Coordinate (in units declared at inception of PDF document) against upper edge of the page
+ * @param {String=} style A string specifying the painting style or null. Valid styles include:
+ * 'S' [default] - stroke,
+ * 'F' - fill,
+ * and 'DF' (or 'FD') - fill then stroke.
+ * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
+ * method calls. The last drawing method call used to define the shape should not have a null style argument.
+ *
+ * In "advanced" API mode this parameter is deprecated.
+ * @param {String=} patternKey The pattern key for the pattern that should be used to fill the primitive. Deprecated!
+ * @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
+ * will modify the pattern on use. Deprecated!
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name triangle
+ */
+
+
+ API.triangle = function (x1, y1, x2, y2, x3, y3, style, patternKey, patternData) {
+ this.lines([[x2 - x1, y2 - y1], // vector to point 2
+ [x3 - x2, y3 - y2], // vector to point 3
+ [x1 - x3, y1 - y3] // closing vector back to point 1
+ ], x1, y1, // start of path
+ [1, 1], style, true, patternKey, patternData);
+ return this;
+ };
+ /**
+ * Adds a rectangle with rounded corners to PDF
+ *
+ * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
+ * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
+ * @param {Number} w Width (in units declared at inception of PDF document)
+ * @param {Number} h Height (in units declared at inception of PDF document)
+ * @param {Number} rx Radius along x axis (in units declared at inception of PDF document)
+ * @param {Number} ry Radius along y axis (in units declared at inception of PDF document)
+ * @param {String=} style A string specifying the painting style or null. Valid styles include:
+ * 'S' [default] - stroke,
+ * 'F' - fill,
+ * and 'DF' (or 'FD') - fill then stroke.
+ * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
+ * method calls. The last drawing method call used to define the shape should not have a null style argument.
+ *
+ * In "advanced" API mode this parameter is deprecated.
+ * @param {String=} patternKey The pattern key for the pattern that should be used to fill the primitive. Deprecated!
+ * @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
+ * will modify the pattern on use. Deprecated!
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name roundedRect
+ */
+
+
+ API.roundedRect = function (x, y, w, h, rx, ry, style, patternKey, patternData) {
+ var MyArc = 4 / 3 * (Math.SQRT2 - 1);
+ rx = Math.min(rx, w * 0.5);
+ ry = Math.min(ry, h * 0.5);
+ this.lines([[w - 2 * rx, 0], [rx * MyArc, 0, rx, ry - ry * MyArc, rx, ry], [0, h - 2 * ry], [0, ry * MyArc, -(rx * MyArc), ry, -rx, ry], [-w + 2 * rx, 0], [-(rx * MyArc), 0, -rx, -(ry * MyArc), -rx, -ry], [0, -h + 2 * ry], [0, -(ry * MyArc), rx * MyArc, -ry, rx, -ry]], x + rx, y, // start of path
+ [1, 1], style, true, patternKey, patternData);
+ return this;
+ };
+ /**
+ * Adds an ellipse to PDF
+ *
+ * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
+ * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
+ * @param {Number} rx Radius along x axis (in units declared at inception of PDF document)
+ * @param {Number} ry Radius along y axis (in units declared at inception of PDF document)
+ * @param {String=} style A string specifying the painting style or null. Valid styles include:
+ * 'S' [default] - stroke,
+ * 'F' - fill,
+ * and 'DF' (or 'FD') - fill then stroke.
+ * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
+ * method calls. The last drawing method call used to define the shape should not have a null style argument.
+ *
+ * In "advanced" API mode this parameter is deprecated.
+ * @param {String=} patternKey The pattern key for the pattern that should be used to fill the primitive. Deprecated!
+ * @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
+ * will modify the pattern on use. Deprecated!
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name ellipse
+ */
+
+
+ API.ellipse = function (x, y, rx, ry, style, patternKey, patternData) {
+ var lx = 4 / 3 * (Math.SQRT2 - 1) * rx,
+ ly = 4 / 3 * (Math.SQRT2 - 1) * ry;
+ this.moveTo(x + rx, y);
+ this.curveTo(x + rx, y - ly, x + lx, y - ry, x, y - ry);
+ this.curveTo(x - lx, y - ry, x - rx, y - ly, x - rx, y);
+ this.curveTo(x - rx, y + ly, x - lx, y + ry, x, y + ry);
+ this.curveTo(x + lx, y + ry, x + rx, y + ly, x + rx, y);
+ putStyle(style, patternKey, patternData);
+ return this;
+ };
+ /**
+ * Adds an circle to PDF
+ *
+ * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
+ * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
+ * @param {Number} r Radius (in units declared at inception of PDF document)
+ * @param {String=} style A string specifying the painting style or null. Valid styles include:
+ * 'S' [default] - stroke,
+ * 'F' - fill,
+ * and 'DF' (or 'FD') - fill then stroke.
+ * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple
+ * method calls. The last drawing method call used to define the shape should not have a null style argument.
+ *
+ * In "advanced" API mode this parameter is deprecated.
+ * @param {String=} patternKey The pattern key for the pattern that should be used to fill the primitive. Deprecated!
+ * @param {(Matrix|PatternData)=} patternData The matrix that transforms the pattern into user space, or an object that
+ * will modify the pattern on use. Deprecated!
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name circle
+ */
+
+
+ API.circle = function (x, y, r, style, patternKey, patternData) {
+ return this.ellipse(x, y, r, r, style, patternKey, patternData);
+ };
+ /**
+ * Adds a properties to the PDF document
+ *
+ * @param {Object} properties A property_name-to-property_value object structure.
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name setProperties
+ */
+
+
+ API.setProperties = function (properties) {
+ // copying only those properties we can render.
+ for (var property in documentProperties) {
+ if (documentProperties.hasOwnProperty(property) && properties[property]) {
+ documentProperties[property] = properties[property];
+ }
+ }
+
+ return this;
+ };
+ /**
+ * Sets font size for upcoming text elements.
+ *
+ * @param {number} size Font size in points.
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name setFontSize
+ */
+
+
+ API.setFontSize = function (size) {
+ // convert font size into current unit system
+ if (apiMode === ApiMode.ADVANCED) {
+ activeFontSize = size / k;
+ } else {
+ activeFontSize = size;
+ }
+
+ out("/" + activeFontKey + " " + activeFontSize + " Tf");
+ return this;
+ };
+ /**
+ * @return {number}
+ * @methodOf jsPDF#
+ * @name getFontSize
+ */
+
+
+ API.getFontSize = function () {
+ if (apiMode === ApiMode.COMPAT) {
+ return activeFontSize;
+ } else {
+ return activeFontSize * k;
+ }
+ };
+ /**
+ * Sets text font face, variant for upcoming text elements.
+ * See output of jsPDF.getFontList() for possible font names, styles.
+ *
+ * @param {string} fontName Font name or family. Example: "times"
+ * @param {string} fontStyle Font style or variant. Example: "italic"
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name setFont
+ */
+
+
+ API.setFont = function (fontName, fontStyle) {
+ activeFontKey = _getFont(fontName, fontStyle); // if font is not found, the above line blows up and we never go further
+
+ out("/" + activeFontKey + " " + activeFontSize + " Tf");
+ return this;
+ };
+ /**
+ * Switches font style or variant for upcoming text elements,
+ * while keeping the font face or family same.
+ * See output of jsPDF.getFontList() for possible font names, styles.
+ *
+ * @param {string} style Font style or variant. Example: "italic"
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name setFontStyle
+ */
+
+
+ API.setFontStyle = API.setFontType = function (style) {
+ activeFontKey = _getFont(undefined, style); // if font is not found, the above line blows up and we never go further
+
+ out("/" + activeFontKey + " " + activeFontSize + " Tf");
+ return this;
+ };
+ /**
+ * Returns an object - a tree of fontName to fontStyle relationships available to
+ * active PDF document.
+ *
+ * @public
+ * @function
+ * @instance
+ * @returns {Object} Like {'times':['normal', 'italic', ... ], 'arial':['normal', 'bold', ... ], ... }
+ * @methodOf jsPDF#
+ * @name getFontList
+ */
+
+
+ API.getFontList = function () {
+ // TODO: iterate over fonts array or return copy of fontmap instead in case more are ever added.
+ var list = {},
+ fontName,
+ fontStyle,
+ tmp;
+
+ for (fontName in fontmap) {
+ if (fontmap.hasOwnProperty(fontName)) {
+ list[fontName] = tmp = [];
+
+ for (fontStyle in fontmap[fontName]) {
+ if (fontmap[fontName].hasOwnProperty(fontStyle)) {
+ tmp.push(fontStyle);
+ }
+ }
+ }
+ }
+
+ return list;
+ };
+ /**
+ * Add a custom font to the current instance.
+ *
+ * @param {String} postScriptName name of the Font. Example: "Menlo-Regular"
+ * @param {String} fontName of font-family from @font-face definition. Example: "Menlo Regular"
+ * @param {String} fontStyle style. Example: "normal"
+ * @function
+ * @instance
+ * @methodOf jsPDF
+ * @name addFont
+ */
+
+
+ API.addFont = function (postScriptName, fontName, fontStyle, encoding) {
+ encoding = encoding || "Identity-H";
+ addFont.call(this, postScriptName, fontName, fontStyle, encoding);
+ };
+ /**
+ * Sets line width for upcoming lines.
+ *
+ * @param {number} width Line width (in units declared at inception of PDF document)
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name setLineWidth
+ */
+
+
+ API.setLineWidth = function (width) {
+ out(scaleByK(width).toFixed(2) + " w");
+ return this;
+ };
+ /**
+ * Sets the stroke color for upcoming elements.
+ *
+ * Depending on the number of arguments given, Gray, RGB, or CMYK
+ * color space is implied.
+ *
+ * When only ch1 is given, "Gray" color space is implied and it
+ * must be a value in the range from 0.00 (solid black) to to 1.00 (white)
+ * if values are communicated as String types, or in range from 0 (black)
+ * to 255 (white) if communicated as Number type.
+ * The RGB-like 0-255 range is provided for backward compatibility.
+ *
+ * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each
+ * value must be in the range from 0.00 (minimum intensity) to to 1.00
+ * (max intensity) if values are communicated as String types, or
+ * from 0 (min intensity) to to 255 (max intensity) if values are communicated
+ * as Number types.
+ * The RGB-like 0-255 range is provided for backward compatibility.
+ *
+ * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each
+ * value must be a in the range from 0.00 (0% concentration) to to
+ * 1.00 (100% concentration)
+ *
+ * Because JavaScript treats fixed point numbers badly (rounds to
+ * floating point nearest to binary representation) it is highly advised to
+ * communicate the fractional numbers as String types, not JavaScript Number type.
+ *
+ * @param {Number|String} ch1 Color channel value or {string} ch1 color value in hexadecimal, example: '#FFFFFF'
+ * @param {Number|String} ch2 Color channel value
+ * @param {Number|String} ch3 Color channel value
+ * @param {Number|String} ch4 Color channel value
+ *
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name setDrawColor
+ */
+
+
+ API.setDrawColor = function (ch1, ch2, ch3, ch4) {
+ var options = {
+ ch1: ch1,
+ ch2: ch2,
+ ch3: ch3,
+ ch4: ch4,
+ pdfColorType: "draw",
+ precision: 2
+ };
+ out(generateColorString(options));
+ return this;
+ };
+ /**
+ * Sets the fill color for upcoming elements.
+ *
+ * Depending on the number of arguments given, Gray, RGB, or CMYK
+ * color space is implied.
+ *
+ * When only ch1 is given, "Gray" color space is implied and it
+ * must be a value in the range from 0.00 (solid black) to to 1.00 (white)
+ * if values are communicated as String types, or in range from 0 (black)
+ * to 255 (white) if communicated as Number type.
+ * The RGB-like 0-255 range is provided for backward compatibility.
+ *
+ * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each
+ * value must be in the range from 0.00 (minimum intensity) to to 1.00
+ * (max intensity) if values are communicated as String types, or
+ * from 0 (min intensity) to to 255 (max intensity) if values are communicated
+ * as Number types.
+ * The RGB-like 0-255 range is provided for backward compatibility.
+ *
+ * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each
+ * value must be a in the range from 0.00 (0% concentration) to to
+ * 1.00 (100% concentration)
+ *
+ * Because JavaScript treats fixed point numbers badly (rounds to
+ * floating point nearest to binary representation) it is highly advised to
+ * communicate the fractional numbers as String types, not JavaScript Number type.
+ *
+ * @param {Number|String} ch1 Color channel value or {string} ch1 color value in hexadecimal, example: '#FFFFFF'
+ * @param {Number|String} ch2 Color channel value
+ * @param {Number|String} ch3 Color channel value
+ * @param {Number|String} ch4 Color channel value
+ *
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name setFillColor
+ */
+
+
+ API.setFillColor = function (ch1, ch2, ch3, ch4) {
+ var options = {
+ ch1: ch1,
+ ch2: ch2,
+ ch3: ch3,
+ ch4: ch4,
+ pdfColorType: "fill",
+ precision: 2
+ };
+ out(generateColorString(options));
+ return this;
+ };
+ /**
+ * Sets the text color for upcoming elements.
+ *
+ * Depending on the number of arguments given, Gray, RGB, or CMYK
+ * color space is implied.
+ *
+ * When only ch1 is given, "Gray" color space is implied and it
+ * must be a value in the range from 0.00 (solid black) to to 1.00 (white)
+ * if values are communicated as String types, or in range from 0 (black)
+ * to 255 (white) if communicated as Number type.
+ * The RGB-like 0-255 range is provided for backward compatibility.
+ *
+ * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each
+ * value must be in the range from 0.00 (minimum intensity) to to 1.00
+ * (max intensity) if values are communicated as String types, or
+ * from 0 (min intensity) to to 255 (max intensity) if values are communicated
+ * as Number types.
+ * The RGB-like 0-255 range is provided for backward compatibility.
+ *
+ * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each
+ * value must be a in the range from 0.00 (0% concentration) to to
+ * 1.00 (100% concentration)
+ *
+ * Because JavaScript treats fixed point numbers badly (rounds to
+ * floating point nearest to binary representation) it is highly advised to
+ * communicate the fractional numbers as String types, not JavaScript Number type.
+ *
+ * @param {Number|String} ch1 Color channel value or {string} ch1 color value in hexadecimal, example: '#FFFFFF'
+ * @param {Number|String} ch2 Color channel value
+ * @param {Number|String} ch3 Color channel value
+ * @param {Number|String} ch4 Color channel value
+ *
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name setTextColor
+ */
+
+
+ API.setTextColor = function (ch1, ch2, ch3, ch4) {
+ var options = {
+ ch1: ch1,
+ ch2: ch2,
+ ch3: ch3,
+ ch4: ch4,
+ pdfColorType: "text",
+ precision: 3
+ };
+ textColor = generateColorString(options);
+ return this;
+ };
+ /**
+ * Initializes the default character set that the user wants to be global..
+ *
+ * @param {number} charSpace
+ * @function
+ * @instance
+ * @returns {jsPDF} jsPDF-instance
+ * @methodOf jsPDF
+ * @name setCharSpace
+ */
+
+
+ API.setCharSpace = function (charSpace) {
+ if (apiMode === ApiMode.COMPAT) {
+ activeCharSpace = charSpace;
+ } else if (apiMode === ApiMode.ADVANCED) {
+ activeCharSpace = charSpace / k;
+ }
+
+ return this;
+ };
+ /**
+ * Initializes the default character set that the user wants to be global..
+ *
+ * @param {boolean} boolean
+ * @function
+ * @instance
+ * @returns {jsPDF} jsPDF-instance
+ * @methodOf jsPDF
+ * @name setR2L
+ */
+
+
+ API.setR2L = function (boolean) {
+ R2L = boolean;
+ return this;
+ };
+ /**
+ * Sets a either previously added {@link GState} (via {@link addGState}) or a new {@link GState}.
+ * @param {String|GState} gState If type is string, a previously added GState is used, if type is GState
+ * it will be added before use.
+ * @function
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name setGState
+ */
+
+
+ API.setGState = function (gState) {
+ if (typeof gState === "string") {
+ gState = gStates[gStatesMap[gState]];
+ } else {
+ gState = addGState(null, gState);
+ }
+
+ if (!gState.equals(activeGState)) {
+ out("/" + gState.id + " gs");
+ activeGState = gState;
+ }
+ };
+ /**
+ * Is an Object providing a mapping from human-readable to
+ * integer flag values designating the varieties of line cap
+ * and join styles.
+ *
+ * @fieldOf jsPDF#
+ * @name CapJoinStyles
+ */
+
+
+ API.CapJoinStyles = {
+ 0: 0,
+ butt: 0,
+ but: 0,
+ miter: 0,
+ 1: 1,
+ round: 1,
+ rounded: 1,
+ circle: 1,
+ 2: 2,
+ projecting: 2,
+ project: 2,
+ square: 2,
+ bevel: 2
+ };
+ /**
+ * Sets the line cap styles
+ * See {jsPDF.CapJoinStyles} for variants
+ *
+ * @param {String|Number} style A string or number identifying the type of line cap
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name setLineCap
+ */
+
+ API.setLineCap = function (style) {
+ var id = this.CapJoinStyles[style];
+
+ if (id === undefined) {
+ throw new Error("Line cap style of '" + style + "' is not recognized. See or extend .CapJoinStyles property for valid styles");
+ }
+
+ lineCapID = id;
+ out(id + " J");
+ return this;
+ };
+ /**
+ * Sets the line join styles
+ * See {jsPDF.CapJoinStyles} for variants
+ *
+ * @param {String|Number} style A string or number identifying the type of line join
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name setLineJoin
+ */
+
+
+ API.setLineJoin = function (style) {
+ var id = this.CapJoinStyles[style];
+
+ if (id === undefined) {
+ throw new Error("Line join style of '" + style + "' is not recognized. See or extend .CapJoinStyles property for valid styles");
+ }
+
+ lineJoinID = id;
+ out(id + " j");
+ return this;
+ };
+ /**
+ * Sets the miter limit.
+ * @param {number} miterLimit
+ * @function
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name setMiterLimit
+ */
+
+
+ API.setLineMiterLimit = function (miterLimit) {
+ out(hpf(miterLimit) + " M");
+ return this;
+ };
+ /**
+ * Sets the line dash pattern.
+ * @param {Array} array An array containing 0-2 numbers. The first number sets the length of the
+ * dashes, the second number the length of the gaps. If the second number is missing, the gaps are considered
+ * to be as long as the dashes. An empty array means solid, unbroken lines.
+ * @param phase The phase lines start with.
+ * @function
+ * @returns {jsPDF}
+ * @methodOf jsPDF#
+ * @name setLineDashPattern
+ */
+
+
+ API.setLineDashPattern = function (array, phase) {
+ out(["[" + (array[0] !== undefined ? array[0] : ""), (array[1] !== undefined ? array[1] : "") + "]", phase, "d"].join(" "));
+ return this;
+ };
+ /**
+ * Generates the PDF document.
+ *
+ * If `type` argument is undefined, output is raw body of resulting PDF returned as a string.
+ *
+ * @param {string} type A string identifying one of the possible output types.
+ * @param {Object} options An object providing some additional signalling to PDF generator.
+ *
+ * @function
+ * @instance
+ * @returns {jsPDF}
+ * @memberOf jsPDF
+ * @name output
+ */
+
+
+ API.output = _output;
+ /**
+ * Saves as PDF document. An alias of jsPDF.output('save', 'filename.pdf')
+ *
+ * @memberOf jsPDF
+ * @name save
+ * @function
+ * @instance
+ * @param {string} filename The filename including extension.
+ * @returns {jsPDF} jsPDF-instance
+ */
+
+ API.save = function (filename) {
+ API.output("save", filename);
+ }; // applying plugins (more methods) ON TOP of built-in API.
+ // this is intentional as we allow plugins to override
+ // built-ins
+
+
+ for (var plugin in jsPDF.API) {
+ if (jsPDF.API.hasOwnProperty(plugin)) {
+ if (plugin === "events" && jsPDF.API.events.length) {
+ (function (events, newEvents) {
+ // jsPDF.API.events is a JS Array of Arrays
+ // where each Array is a pair of event name, handler
+ // Events were added by plugins to the jsPDF instantiator.
+ // These are always added to the new instance and some ran
+ // during instantiation.
+ var eventname, handler_and_args, i;
+
+ for (i = newEvents.length - 1; i !== -1; i--) {
+ // subscribe takes 3 args: 'topic', function, runonce_flag
+ // if undefined, runonce is false.
+ // users can attach callback directly,
+ // or they can attach an array with [callback, runonce_flag]
+ // that's what the "apply" magic is for below.
+ eventname = newEvents[i][0];
+ handler_and_args = newEvents[i][1];
+ events.subscribe.apply(events, [eventname].concat(typeof handler_and_args === "function" ? [handler_and_args] : handler_and_args));
+ }
+ })(events, jsPDF.API.events);
+ } else {
+ API[plugin] = jsPDF.API[plugin];
+ }
+ }
+ } //////////////////////////////////////////////////////
+ // continuing initialization of jsPDF Document object
+ //////////////////////////////////////////////////////
+ // Add the first page automatically
+
+
+ addFonts();
+ activeFontKey = "F1";
+
+ _addPage(format, orientation);
+
+ events.publish("initialized");
+ return API;
+ }
+ /**
+ * jsPDF.API is a STATIC property of jsPDF class.
+ * jsPDF.API is an object you can add methods and properties to.
+ * The methods / properties you add will show up in new jsPDF objects.
+ *
+ * One property is prepopulated. It is the 'events' Object. Plugin authors can add topics,
+ * callbacks to this object. These will be reassigned to all new instances of jsPDF.
+ *
+ * @static
+ * @public
+ * @memberOf jsPDF
+ * @name API
+ *
+ * @example
+ * jsPDF.API.mymethod = function(){
+ * // 'this' will be ref to internal API object. see jsPDF source
+ * // , so you can refer to built-in methods like so:
+ * // this.line(....)
+ * // this.text(....)
+ * }
+ * var pdfdoc = new jsPDF()
+ * pdfdoc.mymethod() // <- !!!!!!
+ */
+
+
+ jsPDF.API = {
+ events: []
+ };
+ /**
+ * The version of jsPDF
+ * @name version
+ * @type {string}
+ * @memberOf jsPDF
+ */
+
+ jsPDF.version = "0.0.0";
+
+ if (typeof define === "function" && define.amd) {
+ define(function () {
+ return jsPDF;
+ });
+ } else if (typeof module !== "undefined" && module.exports) {
+ module.exports = jsPDF;
+ module.exports.jsPDF = jsPDF;
+ } else {
+ global.jsPDF = jsPDF;
+ }
+
+ return jsPDF;
+ }(typeof self !== "undefined" && self || typeof window !== "undefined" && window || typeof global !== "undefined" && global || Function('return typeof this === "object" && this.content')() || Function("return this")()); // `self` is undefined in Firefox for Android content script context
+ // while `this` is nsIContentFrameMessageManager
+ // with an attribute `content` that corresponds to the window
+
+ /**
+ * @license
+ * Copyright (c) 2016 Alexander Weidt,
+ * https://github.com/BiggA94
+ *
+ * Licensed under the MIT License. http://opensource.org/licenses/mit-license
+ */
+
+ /**
+ * jsPDF AcroForm Plugin
+ *
+ * @name AcroForm
+ * @module
+ */
+ (function (jsPDFAPI, globalObj) {
+
+ var scope;
+ var pageHeight;
+ var scaleFactor = 1;
+
+ var inherit = function inherit(child, parent) {
+
+ child.prototype = Object.create(parent.prototype);
+ child.prototype.constructor = child;
+ };
+
+ var scale = function scale(x) {
+ return x * (scaleFactor / 1); // 1 = (96 / 72)
+ };
+
+ var createFormXObject = function createFormXObject(formObject) {
+ var xobj = new AcroFormXObject();
+ var height = AcroFormAppearance.internal.getHeight(formObject) || 0;
+ var width = AcroFormAppearance.internal.getWidth(formObject) || 0;
+ xobj.BBox = [0, 0, width.toFixed(2), height.toFixed(2)];
+ return xobj;
+ };
+
+ var setBitPosition = function setBitPosition(variable, position, value) {
+ variable = variable || 0;
+ value = value || 1;
+ var bitMask = 1;
+ bitMask = bitMask << position - 1;
+
+ if (value == 1) {
+ // Set the Bit to 1
+ var variable = variable | bitMask;
+ } else {
+ // Set the Bit to 0
+ var variable = variable & ~bitMask;
+ }
+
+ return variable;
+ };
+ /**
+ * Calculating the Ff entry:
+ *
+ * The Ff entry contains flags, that have to be set bitwise In the Following
+ * the number in the Comment is the BitPosition
+ */
+
+
+ var calculateFlagsOnOptions = function calculateFlagsOnOptions(flags, opts, PDFVersion) {
+ var PDFVersion = PDFVersion || 1.3;
+ var flags = flags || 0; // 1, readOnly
+
+ if (opts.readOnly == true) {
+ flags = setBitPosition(flags, 1);
+ } // 2, required
+
+
+ if (opts.required == true) {
+ flags = setBitPosition(flags, 2);
+ } // 4, noExport
+
+
+ if (opts.noExport == true) {
+ flags = setBitPosition(flags, 3);
+ } // 13, multiline
+
+
+ if (opts.multiline == true) {
+ flags = setBitPosition(flags, 13);
+ } // 14, Password
+
+
+ if (opts.password) {
+ flags = setBitPosition(flags, 14);
+ } // 15, NoToggleToOff (Radio buttons only
+
+
+ if (opts.noToggleToOff) {
+ flags = setBitPosition(flags, 15);
+ } // 16, Radio
+
+
+ if (opts.radio) {
+ flags = setBitPosition(flags, 16);
+ } // 17, Pushbutton
+
+
+ if (opts.pushbutton) {
+ flags = setBitPosition(flags, 17);
+ } // 18, Combo (If not set, the choiceField is a listBox!!)
+
+
+ if (opts.combo) {
+ flags = setBitPosition(flags, 18);
+ } // 19, Edit
+
+
+ if (opts.edit) {
+ flags = setBitPosition(flags, 19);
+ } // 20, Sort
+
+
+ if (opts.sort) {
+ flags = setBitPosition(flags, 20);
+ } // 21, FileSelect, PDF 1.4...
+
+
+ if (opts.fileSelect && PDFVersion >= 1.4) {
+ flags = setBitPosition(flags, 21);
+ } // 22, MultiSelect (PDF 1.4)
+
+
+ if (opts.multiSelect && PDFVersion >= 1.4) {
+ flags = setBitPosition(flags, 22);
+ } // 23, DoNotSpellCheck (PDF 1.4)
+
+
+ if (opts.doNotSpellCheck && PDFVersion >= 1.4) {
+ flags = setBitPosition(flags, 23);
+ } // 24, DoNotScroll (PDF 1.4)
+
+
+ if (opts.doNotScroll == true && PDFVersion >= 1.4) {
+ flags = setBitPosition(flags, 24);
+ } // 25, RichText (PDF 1.4)
+
+
+ if (opts.richText && PDFVersion >= 1.4) {
+ flags = setBitPosition(flags, 25);
+ }
+
+ return flags;
+ };
+
+ var calculateCoordinates = function calculateCoordinates(args) {
+ var x = args[0];
+ var y = args[1];
+ var w = args[2];
+ var h = args[3];
+ var coordinates = {};
+
+ if (Array.isArray(x)) {
+ x[0] = scale(x[0]);
+ x[1] = scale(x[1]);
+ x[2] = scale(x[2]);
+ x[3] = scale(x[3]);
+ } else {
+ x = scale(x);
+ y = scale(y);
+ w = scale(w);
+ h = scale(h);
+ }
+
+ coordinates.lowerLeft_X = x || 0;
+ coordinates.lowerLeft_Y = scale(pageHeight) - y - h || 0;
+ coordinates.upperRight_X = x + w || 0;
+ coordinates.upperRight_Y = scale(pageHeight) - y || 0;
+ return [coordinates.lowerLeft_X.toFixed(2), coordinates.lowerLeft_Y.toFixed(2), coordinates.upperRight_X.toFixed(2), coordinates.upperRight_Y.toFixed(2)];
+ };
+
+ var calculateAppearanceStream = function calculateAppearanceStream(formObject) {
+ if (formObject.appearanceStreamContent) {
+ // If appearanceStream is already set, use it
+ return formObject.appearanceStreamContent;
+ }
+
+ if (!formObject.V && !formObject.DV) {
+ return;
+ } // else calculate it
+
+
+ var stream = [];
+ var text = formObject.V || formObject.DV;
+ var calcRes = calculateX(formObject, text);
+ stream.push("/Tx BMC");
+ stream.push("q");
+ stream.push("/F1 " + calcRes.fontSize.toFixed(2) + " Tf");
+ stream.push("1 0 0 1 0 0 Tm"); // Text Matrix
+
+ stream.push("BT"); // Begin Text
+
+ stream.push(calcRes.text);
+ stream.push("ET"); // End Text
+
+ stream.push("Q");
+ stream.push("EMC");
+ var appearanceStreamContent = new createFormXObject(formObject);
+ appearanceStreamContent.stream = stream.join("\n");
+ return appearanceStreamContent;
+ };
+
+ var calculateX = function calculateX(formObject, text, font, maxFontSize) {
+ var maxFontSize = maxFontSize || 12;
+ var font = font || "helvetica";
+ var returnValue = {
+ text: "",
+ fontSize: ""
+ }; // Remove Brackets
+
+ text = text.substr(0, 1) == "(" ? text.substr(1) : text;
+ text = text.substr(text.length - 1) == ")" ? text.substr(0, text.length - 1) : text; // split into array of words
+
+ var textSplit = text.split(" ");
+ var fontSize = maxFontSize; // The Starting fontSize (The Maximum)
+
+ var lineSpacing = 2;
+ var borderPadding = 2;
+ var height = AcroFormAppearance.internal.getHeight(formObject) || 0;
+ height = height < 0 ? -height : height;
+ var width = AcroFormAppearance.internal.getWidth(formObject) || 0;
+ width = width < 0 ? -width : width;
+
+ var isSmallerThanWidth = function isSmallerThanWidth(i, lastLine, fontSize) {
+ if (i + 1 < textSplit.length) {
+ var tmp = lastLine + " " + textSplit[i + 1];
+ var TextWidth = calculateFontSpace(tmp, fontSize + "px", font).width;
+ var FieldWidth = width - 2 * borderPadding;
+ return TextWidth <= FieldWidth;
+ } else {
+ return false;
+ }
+ };
+
+ fontSize++;
+
+ FontSize: while (true) {
+ var text = "";
+ fontSize--;
+ var textHeight = calculateFontSpace("3", fontSize + "px", font).height;
+ var startY = formObject.multiline ? height - fontSize : (height - textHeight) / 2;
+ startY += lineSpacing;
+ var startX = -borderPadding;
+ var lastY = startY;
+ var firstWordInLine = 0,
+ lastWordInLine = 0;
+ var lastLength = 0;
+
+ if (fontSize <= 0) {
+ // In case, the Text doesn't fit at all
+ fontSize = 12;
+ text = "(...) Tj\n";
+ text += "% Width of Text: " + calculateFontSpace(text, "1px").width + ", FieldWidth:" + width + "\n";
+ break;
+ }
+
+ lastLength = calculateFontSpace(textSplit[0] + " ", fontSize + "px", font).width;
+ var lastLine = "";
+ var lineCount = 0;
+
+ Line: for (var i in textSplit) {
+ if (textSplit.hasOwnProperty(i)) {
+ lastLine += textSplit[i] + " "; // Remove last blank
+
+ lastLine = lastLine.substr(lastLine.length - 1) == " " ? lastLine.substr(0, lastLine.length - 1) : lastLine;
+ var key = parseInt(i);
+ lastLength = calculateFontSpace(lastLine + " ", fontSize + "px", font).width;
+ var nextLineIsSmaller = isSmallerThanWidth(key, lastLine, fontSize);
+ var isLastWord = i >= textSplit.length - 1;
+
+ if (nextLineIsSmaller && !isLastWord) {
+ lastLine += " ";
+ continue; // Line
+ } else if (!nextLineIsSmaller && !isLastWord) {
+ if (!formObject.multiline) {
+ continue FontSize;
+ } else {
+ if ((textHeight + lineSpacing) * (lineCount + 2) + lineSpacing > height) {
+ // If the Text is higher than the
+ // FieldObject
+ continue FontSize;
+ }
+
+ lastWordInLine = key; // go on
+ }
+ } else if (isLastWord) {
+ lastWordInLine = key;
+ } else {
+ if (formObject.multiline && (textHeight + lineSpacing) * (lineCount + 2) + lineSpacing > height) {
+ // If the Text is higher than the FieldObject
+ continue FontSize;
+ }
+ }
+
+ var line = "";
+
+ for (var x = firstWordInLine; x <= lastWordInLine; x++) {
+ line += textSplit[x] + " ";
+ } // Remove last blank
+
+
+ line = line.substr(line.length - 1) == " " ? line.substr(0, line.length - 1) : line; // lastLength -= blankSpace.width;
+
+ lastLength = calculateFontSpace(line, fontSize + "px", font).width; // Calculate startX
+
+ switch (formObject.Q) {
+ case 2:
+ // Right justified
+ startX = width - lastLength - borderPadding;
+ break;
+
+ case 1:
+ // Q = 1 := Text-Alignment: Center
+ startX = (width - lastLength) / 2;
+ break;
+
+ case 0:
+ default:
+ startX = borderPadding;
+ break;
+ }
+
+ text += startX.toFixed(2) + " " + lastY.toFixed(2) + " Td\n";
+ text += "(" + line + ") Tj\n"; // reset X in PDF
+
+ text += -startX.toFixed(2) + " 0 Td\n"; // After a Line, adjust y position
+
+ lastY = -(fontSize + lineSpacing);
+
+ lastLength = 0;
+ firstWordInLine = lastWordInLine + 1;
+ lineCount++;
+ lastLine = "";
+ continue Line;
+ }
+ }
+
+ break;
+ }
+
+ returnValue.text = text;
+ returnValue.fontSize = fontSize;
+ return returnValue;
+ };
+ /**
+ * Small workaround for calculating the TextMetric approximately.
+ *
+ * @param text
+ * @param fontsize
+ * @returns {TextMetrics} (Has Height and Width)
+ */
+
+
+ var calculateFontSpace = function calculateFontSpace(text, fontSize, fontType) {
+ fontType = fontType || "helvetica";
+ var font = scope.internal.getFont(fontType);
+ var width = scope.getStringUnitWidth(text, {
+ font: font,
+ fontSize: parseFloat(fontSize),
+ charSpace: 0
+ }) * parseFloat(fontSize);
+ var height = scope.getStringUnitWidth("3", {
+ font: font,
+ fontSize: parseFloat(fontSize),
+ charSpace: 0
+ }) * parseFloat(fontSize) * 1.5;
+ var result = {
+ height: height,
+ width: width
+ };
+ return result;
+ };
+
+ var acroformPluginTemplate = {
+ fields: [],
+ xForms: [],
+
+ /**
+ * acroFormDictionaryRoot contains information about the AcroForm
+ * Dictionary 0: The Event-Token, the AcroFormDictionaryCallback has
+ * 1: The Object ID of the Root
+ */
+ acroFormDictionaryRoot: null,
+
+ /**
+ * After the PDF gets evaluated, the reference to the root has to be
+ * reset, this indicates, whether the root has already been printed
+ * out
+ */
+ printedOut: false,
+ internal: null,
+ isInitialized: false
+ };
+
+ var annotReferenceCallback = function annotReferenceCallback() {
+ var fields = scope.internal.acroformPlugin.acroFormDictionaryRoot.Fields;
+
+ for (var i in fields) {
+ if (fields.hasOwnProperty(i)) {
+ var formObject = fields[i]; // add Annot Reference!
+
+ if (formObject.hasAnnotation) {
+ // If theres an Annotation Widget in the Form Object, put the
+ // Reference in the /Annot array
+ createAnnotationReference.call(scope, formObject);
+ }
+ }
+ }
+ };
+
+ var putForm = function putForm(formObject) {
+ if (scope.internal.acroformPlugin.printedOut) {
+ scope.internal.acroformPlugin.printedOut = false;
+ scope.internal.acroformPlugin.acroFormDictionaryRoot = null;
+ }
+
+ if (!scope.internal.acroformPlugin.acroFormDictionaryRoot) {
+ initializeAcroForm.call(scope);
+ }
+
+ scope.internal.acroformPlugin.acroFormDictionaryRoot.Fields.push(formObject);
+ };
+ /**
+ * Create the Reference to the widgetAnnotation, so that it gets referenced
+ * in the Annot[] int the+ (Requires the Annotation Plugin)
+ */
+
+
+ var createAnnotationReference = function createAnnotationReference(object) {
+ var options = {
+ type: "reference",
+ object: object
+ };
+ scope.annotationPlugin.annotations[scope.internal.getPageInfo(object.page).pageNumber].push(options);
+ }; // Callbacks
+
+
+ var putCatalogCallback = function putCatalogCallback() {
+ // Put reference to AcroForm to DocumentCatalog
+ if (typeof scope.internal.acroformPlugin.acroFormDictionaryRoot != "undefined") {
+ // for safety, shouldn't normally be the case
+ scope.internal.write("/AcroForm " + scope.internal.acroformPlugin.acroFormDictionaryRoot.objId + " " + 0 + " R");
+ } else {
+ console.log("Root missing...");
+ }
+ };
+ /**
+ * Adds /Acroform X 0 R to Document Catalog, and creates the AcroForm
+ * Dictionary
+ */
+
+
+ var AcroFormDictionaryCallback = function AcroFormDictionaryCallback() {
+ // Remove event
+ scope.internal.events.unsubscribe(scope.internal.acroformPlugin.acroFormDictionaryRoot._eventID);
+ delete scope.internal.acroformPlugin.acroFormDictionaryRoot._eventID;
+ scope.internal.acroformPlugin.printedOut = true;
+ };
+ /**
+ * Creates the single Fields and writes them into the Document
+ *
+ * If fieldArray is set, use the fields that are inside it instead of the
+ * fields from the AcroRoot (for the FormXObjects...)
+ */
+
+
+ var createFieldCallback = function createFieldCallback(fieldArray) {
+ var standardFields = !fieldArray;
+
+ if (!fieldArray) {
+ // in case there is no fieldArray specified, we want to print out
+ // the Fields of the AcroForm
+ // Print out Root
+ scope.internal.newObjectDeferredBegin(scope.internal.acroformPlugin.acroFormDictionaryRoot.objId);
+ scope.internal.out(scope.internal.acroformPlugin.acroFormDictionaryRoot.getString());
+ }
+
+ var fieldArray = fieldArray || scope.internal.acroformPlugin.acroFormDictionaryRoot.Kids;
+
+ for (var i in fieldArray) {
+ if (fieldArray.hasOwnProperty(i)) {
+ var form = fieldArray[i];
+ var oldRect = form.Rect;
+
+ if (form.Rect) {
+ form.Rect = calculateCoordinates.call(this, form.Rect);
+ } // Start Writing the Object
+
+
+ scope.internal.newObjectDeferredBegin(form.objId);
+ var content = form.objId + " 0 obj\n<<\n";
+
+ if (_typeof(form) === "object" && typeof form.getContent === "function") {
+ content += form.getContent();
+ }
+
+ form.Rect = oldRect;
+
+ if (form.hasAppearanceStream && !form.appearanceStreamContent) {
+ // Calculate Appearance
+ var appearance = calculateAppearanceStream.call(this, form);
+ content += "/AP << /N " + appearance + " >>\n";
+ scope.internal.acroformPlugin.xForms.push(appearance);
+ } // Assume AppearanceStreamContent is a Array with N,R,D (at least
+ // one of them!)
+
+
+ if (form.appearanceStreamContent) {
+ content += "/AP << "; // Iterate over N,R and D
+
+ for (var k in form.appearanceStreamContent) {
+ if (form.appearanceStreamContent.hasOwnProperty(k)) {
+ var value = form.appearanceStreamContent[k];
+ content += "/" + k + " ";
+ content += "<< ";
+
+ if (Object.keys(value).length >= 1 || Array.isArray(value)) {
+ // appearanceStream is an Array or Object!
+ for (var i in value) {
+ if (value.hasOwnProperty(i)) {
+ var obj = value[i];
+
+ if (typeof obj === "function") {
+ // if Function is referenced, call it in order
+ // to get the FormXObject
+ obj = obj.call(this, form);
+ }
+
+ content += "/" + i + " " + obj + " "; // In case the XForm is already used, e.g. OffState
+ // of CheckBoxes, don't add it
+
+ if (!(scope.internal.acroformPlugin.xForms.indexOf(obj) >= 0)) scope.internal.acroformPlugin.xForms.push(obj);
+ }
+ }
+ } else {
+ var obj = value;
+
+ if (typeof obj === "function") {
+ // if Function is referenced, call it in order to
+ // get the FormXObject
+ obj = obj.call(this, form);
+ }
+
+ content += "/" + i + " " + obj + " \n";
+ if (!(scope.internal.acroformPlugin.xForms.indexOf(obj) >= 0)) scope.internal.acroformPlugin.xForms.push(obj);
+ }
+
+ content += " >>\n";
+ }
+ } // appearance stream is a normal Object..
+
+
+ content += ">>\n";
+ }
+
+ content += ">>\nendobj\n";
+ scope.internal.out(content);
+ }
+ }
+
+ if (standardFields) {
+ createXFormObjectCallback.call(this, scope.internal.acroformPlugin.xForms);
+ }
+ };
+
+ var createXFormObjectCallback = function createXFormObjectCallback(fieldArray) {
+ for (var i in fieldArray) {
+ if (fieldArray.hasOwnProperty(i)) {
+ var key = i;
+ var form = fieldArray[i]; // Start Writing the Object
+
+ scope.internal.newObjectDeferredBegin(form && form.objId);
+ var content = "";
+
+ if (_typeof(form) === "object" && typeof form.getString === "function") {
+ content = form.getString();
+ }
+
+ scope.internal.out(content);
+ delete fieldArray[key];
+ }
+ }
+ };
+
+ var initializeAcroForm = function initializeAcroForm() {
+ if (this.internal !== undefined && (this.internal.acroformPlugin === undefined || this.internal.acroformPlugin.isInitialized === false)) {
+ scope = this;
+ AcroFormField.FieldNum = 0;
+ this.internal.acroformPlugin = JSON.parse(JSON.stringify(acroformPluginTemplate));
+
+ if (this.internal.acroformPlugin.acroFormDictionaryRoot) {
+ // return;
+ throw new Error("Exception while creating AcroformDictionary");
+ }
+
+ scaleFactor = scope.internal.scaleFactor;
+ pageHeight = scope.internal.pageSize.getHeight(); // The Object Number of the AcroForm Dictionary
+
+ scope.internal.acroformPlugin.acroFormDictionaryRoot = new AcroFormDictionary(); // add Callback for creating the AcroForm Dictionary
+
+ scope.internal.acroformPlugin.acroFormDictionaryRoot._eventID = scope.internal.events.subscribe("postPutResources", AcroFormDictionaryCallback);
+ scope.internal.events.subscribe("buildDocument", annotReferenceCallback); // buildDocument
+ // Register event, that is triggered when the DocumentCatalog is
+ // written, in order to add /AcroForm
+
+ scope.internal.events.subscribe("putCatalog", putCatalogCallback); // Register event, that creates all Fields
+
+ scope.internal.events.subscribe("postPutPages", createFieldCallback);
+ scope.internal.acroformPlugin.isInitialized = true;
+ }
+ };
+
+ var arrayToPdfArray = function arrayToPdfArray(array) {
+ if (Array.isArray(array)) {
+ var content = " [";
+
+ for (var i in array) {
+ if (array.hasOwnProperty(i)) {
+ var element = array[i].toString();
+ content += element;
+ content += i < array.length - 1 ? " " : "";
+ }
+ }
+
+ content += "]";
+ return content;
+ }
+ };
+
+ var toPdfString = function toPdfString(string) {
+ string = string || ""; // put Bracket at the Beginning of the String
+
+ if (string.indexOf("(") !== 0) {
+ string = "(" + string;
+ }
+
+ if (string.substring(string.length - 1) != ")") {
+ string += ")";
+ }
+
+ return string;
+ }; // ##########################
+ // Classes
+ // ##########################
+
+
+ var AcroFormPDFObject = function AcroFormPDFObject() {
+ // The Object ID in the PDF Object Model
+ // todo
+ var _objId;
+
+ Object.defineProperty(this, "objId", {
+ get: function get() {
+ if (!_objId) {
+ _objId = scope.internal.newObjectDeferred();
+ }
+
+ if (!_objId) {
+ console.log("Couldn't create Object ID");
+ }
+
+ return _objId;
+ },
+ configurable: false
+ });
+ };
+
+ AcroFormPDFObject.prototype.toString = function () {
+ return this.objId + " 0 R";
+ };
+
+ AcroFormPDFObject.prototype.getString = function () {
+ var res = this.objId + " 0 obj\n<<";
+ var content = this.getContent();
+ res += content + ">>\n";
+
+ if (this.stream) {
+ res += "stream\n";
+ res += this.stream;
+ res += "\nendstream\n";
+ }
+
+ res += "endobj\n";
+ return res;
+ };
+
+ AcroFormPDFObject.prototype.getContent = function () {
+ /**
+ * Prints out all enumerable Variables from the Object
+ *
+ * @param fieldObject
+ * @returns {string}
+ */
+ var createContentFromFieldObject = function createContentFromFieldObject(fieldObject) {
+ var content = "";
+ var keys = Object.keys(fieldObject).filter(function (key) {
+ return key != "content" && key != "appearanceStreamContent" && key.substring(0, 1) != "_";
+ });
+
+ for (var i in keys) {
+ if (keys.hasOwnProperty(i)) {
+ var key = keys[i];
+ var value = fieldObject[key];
+ /*
+ * if (key == 'Rect' && value) { value =
+ * AcroForm.internal.calculateCoordinates.call(jsPDF.API.acroformPlugin.internal,
+ * value); }
+ */
+
+ if (value) {
+ if (Array.isArray(value)) {
+ content += "/" + key + " " + arrayToPdfArray(value) + "\n";
+ } else if (value instanceof AcroFormPDFObject) {
+ // In case it is a reference to another PDFObject,
+ // take the referennce number
+ content += "/" + key + " " + value.objId + " 0 R" + "\n";
+ } else {
+ content += "/" + key + " " + value + "\n";
+ }
+ }
+ }
+ }
+
+ return content;
+ };
+
+ var object = "";
+ object += createContentFromFieldObject(this);
+ return object;
+ };
+
+ var AcroFormXObject = function AcroFormXObject() {
+ AcroFormPDFObject.call(this);
+ this.Type = "/XObject";
+ this.Subtype = "/Form";
+ this.FormType = 1;
+ this.BBox;
+ this.Matrix;
+ this.Resources = "2 0 R";
+ this.PieceInfo;
+
+ var _stream;
+
+ Object.defineProperty(this, "Length", {
+ enumerable: true,
+ get: function get() {
+ return _stream !== undefined ? _stream.length : 0;
+ }
+ });
+ Object.defineProperty(this, "stream", {
+ enumerable: false,
+ set: function set(val) {
+ _stream = val.trim();
+ },
+ get: function get() {
+ if (_stream) {
+ return _stream;
+ } else {
+ return null;
+ }
+ }
+ });
+ };
+
+ inherit(AcroFormXObject, AcroFormPDFObject); // ##### The Objects, the User can Create:
+
+ var AcroFormDictionary = function AcroFormDictionary() {
+ AcroFormPDFObject.call(this);
+ var _Kids = [];
+ Object.defineProperty(this, "Kids", {
+ enumerable: false,
+ configurable: true,
+ get: function get() {
+ if (_Kids.length > 0) {
+ return _Kids;
+ } else {
+ return;
+ }
+ }
+ });
+ Object.defineProperty(this, "Fields", {
+ enumerable: true,
+ configurable: true,
+ get: function get() {
+ return _Kids;
+ }
+ }); // Default Appearance
+
+ this.DA;
+ };
+
+ inherit(AcroFormDictionary, AcroFormPDFObject); // The Field Object contains the Variables, that every Field needs
+ // Rectangle for Appearance: lower_left_X, lower_left_Y, width, height
+
+ var AcroFormField = function AcroFormField() {
+
+ AcroFormPDFObject.call(this);
+ var _Rect = null;
+ Object.defineProperty(this, "Rect", {
+ enumerable: true,
+ configurable: false,
+ get: function get() {
+ if (!_Rect) {
+ return;
+ }
+
+ var tmp = _Rect; // var calculatedRes =
+ // AcroForm.internal.calculateCoordinates(_Rect); // do
+ // later!
+
+ return tmp;
+ },
+ set: function set(val) {
+ _Rect = val;
+ }
+ });
+ var _FT = "";
+ Object.defineProperty(this, "FT", {
+ enumerable: true,
+ set: function set(val) {
+ _FT = val;
+ },
+ get: function get() {
+ return _FT;
+ }
+ });
+ var _F = 4;
+ Object.defineProperty(this, "F", {
+ enumerable: true,
+ set: function set(val) {
+ _F = val;
+ },
+ get: function get() {
+ return _F;
+ }
+ });
+ /**
+ * The Partial name of the Field Object. It has to be unique.
+ */
+
+ var _T = null;
+ Object.defineProperty(this, "T", {
+ enumerable: true,
+ configurable: false,
+ set: function set(val) {
+ _T = val;
+ },
+ get: function get() {
+ if (!_T || _T.length < 1) {
+ if (this instanceof AcroFormChildClass) {
+ // In case of a Child from a Radio´Group, you don't
+ // need a FieldName!!!
+ return;
+ }
+
+ return "(FieldObject" + AcroFormField.FieldNum++ + ")";
+ }
+
+ if (_T.substring(0, 1) == "(" && _T.substring(_T.length - 1)) {
+ return _T;
+ }
+
+ return "(" + _T + ")";
+ }
+ });
+ var _DA = null; // Defines the default appearance (Needed for variable Text)
+
+ Object.defineProperty(this, "DA", {
+ enumerable: true,
+ get: function get() {
+ if (!_DA) {
+ return;
+ }
+
+ return "(" + _DA + ")";
+ },
+ set: function set(val) {
+ _DA = val;
+ }
+ });
+ var _DV = null; // Defines the default value
+
+ Object.defineProperty(this, "DV", {
+ enumerable: true,
+ configurable: true,
+ get: function get() {
+ if (!_DV) {
+ return;
+ }
+
+ return _DV;
+ },
+ set: function set(val) {
+ _DV = val;
+ }
+ });
+ var _V = null; // Defines the default value
+
+ Object.defineProperty(this, "V", {
+ enumerable: true,
+ configurable: true,
+ get: function get() {
+ if (!_V) {
+ return;
+ }
+
+ return _V;
+ },
+ set: function set(val) {
+ _V = val;
+ }
+ }); // this.Type = "/Annot";
+ // this.Subtype = "/Widget";
+
+ Object.defineProperty(this, "Type", {
+ enumerable: true,
+ get: function get() {
+ return this.hasAnnotation ? "/Annot" : null;
+ }
+ });
+ Object.defineProperty(this, "Subtype", {
+ enumerable: true,
+ get: function get() {
+ return this.hasAnnotation ? "/Widget" : null;
+ }
+ });
+ /**
+ *
+ * @type {Array}
+ */
+
+ this.BG;
+ Object.defineProperty(this, "hasAnnotation", {
+ enumerable: false,
+ get: function get() {
+ if (this.Rect || this.BC || this.BG) {
+ return true;
+ }
+
+ return false;
+ }
+ });
+ Object.defineProperty(this, "hasAppearanceStream", {
+ enumerable: false,
+ configurable: true,
+ writable: true
+ });
+ Object.defineProperty(this, "page", {
+ enumerable: false,
+ configurable: true,
+ writable: true
+ });
+ };
+
+ inherit(AcroFormField, AcroFormPDFObject);
+
+ var AcroFormChoiceField = function AcroFormChoiceField() {
+ AcroFormField.call(this); // Field Type = Choice Field
+
+ this.FT = "/Ch"; // options
+
+ this.Opt = [];
+ this.V = "()"; // Top Index
+
+ this.TI = 0;
+ /**
+ * Defines, whether the
+ *
+ * @type {boolean}
+ */
+
+ var _combo = false;
+ Object.defineProperty(this, "combo", {
+ enumerable: false,
+ get: function get() {
+ return _combo;
+ },
+ set: function set(val) {
+ _combo = val;
+ }
+ });
+ /**
+ * Defines, whether the Choice Field is an Edit Field. An Edit Field
+ * is automatically an Combo Field.
+ */
+
+ Object.defineProperty(this, "edit", {
+ enumerable: true,
+ set: function set(val) {
+ if (val == true) {
+ this._edit = true; // ComboBox has to be true
+
+ this.combo = true;
+ } else {
+ this._edit = false;
+ }
+ },
+ get: function get() {
+ if (!this._edit) {
+ return false;
+ }
+
+ return this._edit;
+ },
+ configurable: false
+ });
+ this.hasAppearanceStream = true;
+ };
+
+ inherit(AcroFormChoiceField, AcroFormField);
+
+ var AcroFormListBox = function AcroFormListBox() {
+ AcroFormChoiceField.call(this);
+ this.combo = false;
+ };
+
+ inherit(AcroFormListBox, AcroFormChoiceField);
+
+ var AcroFormComboBox = function AcroFormComboBox() {
+ AcroFormListBox.call(this);
+ this.combo = true;
+ };
+
+ inherit(AcroFormComboBox, AcroFormListBox);
+
+ var AcroFormEditBox = function AcroFormEditBox() {
+ AcroFormComboBox.call(this);
+ this.edit = true;
+ };
+
+ inherit(AcroFormEditBox, AcroFormComboBox);
+
+ var AcroFormButton = function AcroFormButton() {
+ AcroFormField.call(this);
+ this.FT = "/Btn"; // this.hasAnnotation = true;
+ };
+
+ inherit(AcroFormButton, AcroFormField);
+
+ var AcroFormPushButton = function AcroFormPushButton() {
+ AcroFormButton.call(this);
+ var _pushbutton = true;
+ Object.defineProperty(this, "pushbutton", {
+ enumerable: false,
+ get: function get() {
+ return _pushbutton;
+ },
+ set: function set(val) {
+ _pushbutton = val;
+ }
+ });
+ };
+
+ inherit(AcroFormPushButton, AcroFormButton);
+
+ var AcroFormRadioButton = function AcroFormRadioButton() {
+ AcroFormButton.call(this);
+ var _radio = true;
+ Object.defineProperty(this, "radio", {
+ enumerable: false,
+ get: function get() {
+ return _radio;
+ },
+ set: function set(val) {
+ _radio = val;
+ }
+ });
+ var _Kids = [];
+ Object.defineProperty(this, "Kids", {
+ enumerable: true,
+ get: function get() {
+ if (_Kids.length > 0) {
+ return _Kids;
+ }
+ }
+ });
+ Object.defineProperty(this, "__Kids", {
+ get: function get() {
+ return _Kids;
+ }
+ });
+
+ var _noToggleToOff;
+
+ Object.defineProperty(this, "noToggleToOff", {
+ enumerable: false,
+ get: function get() {
+ return _noToggleToOff;
+ },
+ set: function set(val) {
+ _noToggleToOff = val;
+ }
+ }); // this.hasAnnotation = false;
+ };
+
+ inherit(AcroFormRadioButton, AcroFormButton);
+ /*
+ * The Child classs of a RadioButton (the radioGroup) -> The single
+ * Buttons
+ */
+
+ var AcroFormChildClass = function AcroFormChildClass(parent, name) {
+ AcroFormField.call(this);
+ this.Parent = parent; // todo: set AppearanceType as variable that can be set from the
+ // outside...
+
+ this._AppearanceType = AcroFormAppearance.RadioButton.Circle; // The Default appearanceType is the Circle
+
+ this.appearanceStreamContent = this._AppearanceType.createAppearanceStream(name); // Set Print in the Annot Flag
+
+ this.F = setBitPosition(this.F, 3, 1); // Set AppearanceCharacteristicsDictionary with default appearance
+ // if field is not interacting with user
+
+ this.MK = this._AppearanceType.createMK(); // (8) -> Cross, (1)-> Circle, ()-> nothing
+ // Default Appearance is Off
+
+ this.AS = "/Off"; // + name;
+
+ this._Name = name;
+ };
+
+ inherit(AcroFormChildClass, AcroFormField);
+
+ AcroFormRadioButton.prototype.setAppearance = function (appearance) {
+ if (!("createAppearanceStream" in appearance && "createMK" in appearance)) {
+ console.log("Couldn't assign Appearance to RadioButton. Appearance was Invalid!");
+ return;
+ }
+
+ for (var i in this.__Kids) {
+ if (this.__Kids.hasOwnProperty(i)) {
+ var child = this.__Kids[i];
+ child.appearanceStreamContent = appearance.createAppearanceStream(child._Name);
+ child.MK = appearance.createMK();
+ }
+ }
+ };
+
+ AcroFormRadioButton.prototype.createOption = function (name) {
+ var parent = this;
+ var kidCount = this.__Kids.length; // Create new Child for RadioGroup
+
+ var child = new AcroFormChildClass(parent, name); // Add to Parent
+
+ this.__Kids.push(child);
+
+ jsPDFAPI.addField(child);
+ return child;
+ };
+ /**
+ * @name AcroFormCheckBox
+ *
+ * @memberOf AcroForm
+ * @function
+ */
+
+
+ var AcroFormCheckBox = function AcroFormCheckBox() {
+ AcroFormButton.call(this);
+ this.appearanceStreamContent = AcroFormAppearance.CheckBox.createAppearanceStream();
+ this.MK = AcroFormAppearance.CheckBox.createMK();
+ this.AS = "/On";
+ this.V = "/On";
+ };
+
+ inherit(AcroFormCheckBox, AcroFormButton);
+
+ var AcroFormTextField = function AcroFormTextField() {
+ AcroFormField.call(this);
+ this.DA = AcroFormAppearance.createDefaultAppearanceStream();
+ this.F = 4;
+
+ var _V;
+
+ Object.defineProperty(this, "V", {
+ get: function get() {
+ if (_V) {
+ return toPdfString(_V);
+ } else {
+ return _V;
+ }
+ },
+ enumerable: true,
+ set: function set(val) {
+ _V = val;
+ }
+ });
+
+ var _DV;
+
+ Object.defineProperty(this, "DV", {
+ get: function get() {
+ if (_DV) {
+ return toPdfString(_DV);
+ } else {
+ return _DV;
+ }
+ },
+ enumerable: true,
+ set: function set(val) {
+ _DV = val;
+ }
+ });
+ var _multiline = false;
+ Object.defineProperty(this, "multiline", {
+ enumerable: false,
+ get: function get() {
+ return _multiline;
+ },
+ set: function set(val) {
+ _multiline = val;
+ }
+ });
+ /**
+ * For PDF 1.4
+ *
+ * @type {boolean}
+ */
+
+ var _fileSelect = false;
+ Object.defineProperty(this, "fileSelect", {
+ enumerable: false,
+ get: function get() {
+ return _fileSelect;
+ },
+ set: function set(val) {
+ _fileSelect = val;
+ }
+ });
+ /**
+ * For PDF 1.4
+ *
+ * @type {boolean}
+ */
+
+ var _doNotSpellCheck = false;
+ Object.defineProperty(this, "doNotSpellCheck", {
+ enumerable: false,
+ get: function get() {
+ return _doNotSpellCheck;
+ },
+ set: function set(val) {
+ _doNotSpellCheck = val;
+ }
+ });
+ /**
+ * For PDF 1.4
+ *
+ * @type {boolean}
+ */
+
+ var _doNotScroll = false;
+ Object.defineProperty(this, "doNotScroll", {
+ enumerable: false,
+ get: function get() {
+ return _doNotScroll;
+ },
+ set: function set(val) {
+ _doNotScroll = val;
+ }
+ });
+ var _MaxLen = false;
+ Object.defineProperty(this, "MaxLen", {
+ enumerable: true,
+ get: function get() {
+ return _MaxLen;
+ },
+ set: function set(val) {
+ _MaxLen = val;
+ }
+ });
+ Object.defineProperty(this, "hasAppearanceStream", {
+ enumerable: false,
+ get: function get() {
+ return this.V || this.DV;
+ }
+ });
+ };
+
+ inherit(AcroFormTextField, AcroFormField);
+
+ var AcroFormPasswordField = function AcroFormPasswordField() {
+ AcroFormTextField.call(this);
+ var _password = true;
+ Object.defineProperty(this, "password", {
+ enumerable: false,
+ get: function get() {
+ return _password;
+ },
+ set: function set(val) {
+ _password = val;
+ }
+ });
+ };
+
+ inherit(AcroFormPasswordField, AcroFormTextField); // Contains Methods for creating standard appearances
+
+ var AcroFormAppearance = {
+ CheckBox: {
+ createAppearanceStream: function createAppearanceStream() {
+ var appearance = {
+ N: {
+ On: AcroFormAppearance.CheckBox.YesNormal
+ },
+ D: {
+ On: AcroFormAppearance.CheckBox.YesPushDown,
+ Off: AcroFormAppearance.CheckBox.OffPushDown
+ }
+ };
+ return appearance;
+ },
+
+ /**
+ * If any other icons are needed, the number between the
+ * brackets can be changed
+ *
+ * @returns {string}
+ */
+ createMK: function createMK() {
+ return "<< /CA (3)>>";
+ },
+
+ /**
+ * Returns the standard On Appearance for a CheckBox
+ *
+ * @returns {AcroFormXObject}
+ */
+ YesPushDown: function YesPushDown(formObject) {
+ var xobj = createFormXObject(formObject);
+ var stream = [];
+ var zapfDingbatsId = scope.internal.getFont("zapfdingbats", "normal").id;
+ formObject.Q = 1; // set text-alignment as centered
+
+ var calcRes = calculateX(formObject, "3", "ZapfDingbats", 50);
+ stream.push("0.749023 g");
+ stream.push("0 0 " + AcroFormAppearance.internal.getWidth(formObject).toFixed(2) + " " + AcroFormAppearance.internal.getHeight(formObject).toFixed(2) + " re");
+ stream.push("f");
+ stream.push("BMC");
+ stream.push("q");
+ stream.push("0 0 1 rg");
+ stream.push("/" + zapfDingbatsId + " " + calcRes.fontSize.toFixed(2) + " Tf 0 g");
+ stream.push("BT");
+ stream.push(calcRes.text);
+ stream.push("ET");
+ stream.push("Q");
+ stream.push("EMC");
+ xobj.stream = stream.join("\n");
+ return xobj;
+ },
+ YesNormal: function YesNormal(formObject) {
+ var xobj = createFormXObject(formObject);
+ var zapfDingbatsId = scope.internal.getFont("zapfdingbats", "normal").id;
+ var stream = [];
+ formObject.Q = 1; // set text-alignment as centered
+
+ var height = AcroFormAppearance.internal.getHeight(formObject);
+ var width = AcroFormAppearance.internal.getWidth(formObject);
+ var calcRes = calculateX(formObject, "3", "ZapfDingbats", height * 0.9);
+ stream.push("1 g");
+ stream.push("0 0 " + width.toFixed(2) + " " + height.toFixed(2) + " re");
+ stream.push("f");
+ stream.push("q");
+ stream.push("0 0 1 rg");
+ stream.push("0 0 " + (width - 1).toFixed(2) + " " + (height - 1).toFixed(2) + " re");
+ stream.push("W");
+ stream.push("n");
+ stream.push("0 g");
+ stream.push("BT");
+ stream.push("/" + zapfDingbatsId + " " + calcRes.fontSize.toFixed(2) + " Tf 0 g");
+ stream.push(calcRes.text);
+ stream.push("ET");
+ stream.push("Q");
+ xobj.stream = stream.join("\n");
+ return xobj;
+ },
+
+ /**
+ * Returns the standard Off Appearance for a CheckBox
+ *
+ * @returns {AcroFormXObject}
+ */
+ OffPushDown: function OffPushDown(formObject) {
+ var xobj = createFormXObject(formObject);
+ var stream = [];
+ stream.push("0.749023 g");
+ stream.push("0 0 " + AcroFormAppearance.internal.getWidth(formObject).toFixed(2) + " " + AcroFormAppearance.internal.getHeight(formObject).toFixed(2) + " re");
+ stream.push("f");
+ xobj.stream = stream.join("\n");
+ return xobj;
+ }
+ },
+ RadioButton: {
+ Circle: {
+ createAppearanceStream: function createAppearanceStream(name) {
+ var appearanceStreamContent = {
+ D: {
+ Off: AcroFormAppearance.RadioButton.Circle.OffPushDown
+ },
+ N: {}
+ };
+ appearanceStreamContent.N[name] = AcroFormAppearance.RadioButton.Circle.YesNormal;
+ appearanceStreamContent.D[name] = AcroFormAppearance.RadioButton.Circle.YesPushDown;
+ return appearanceStreamContent;
+ },
+ createMK: function createMK() {
+ return "<< /CA (l)>>";
+ },
+ YesNormal: function YesNormal(formObject) {
+ var xobj = createFormXObject(formObject);
+ var stream = []; // Make the Radius of the Circle relative to min(height,
+ // width) of formObject
+
+ var DotRadius = AcroFormAppearance.internal.getWidth(formObject) <= AcroFormAppearance.internal.getHeight(formObject) ? AcroFormAppearance.internal.getWidth(formObject) / 4 : AcroFormAppearance.internal.getHeight(formObject) / 4; // The Borderpadding...
+
+ DotRadius *= 0.9;
+ var c = AcroFormAppearance.internal.Bezier_C;
+ /*
+ * The Following is a Circle created with Bezier-Curves.
+ */
+
+ stream.push("q");
+ stream.push("1 0 0 1 " + AcroFormAppearance.internal.getWidth(formObject) / 2 + " " + AcroFormAppearance.internal.getHeight(formObject) / 2 + " cm");
+ stream.push(DotRadius + " 0 m");
+ stream.push(DotRadius + " " + DotRadius * c + " " + DotRadius * c + " " + DotRadius + " 0 " + DotRadius + " c");
+ stream.push("-" + DotRadius * c + " " + DotRadius + " -" + DotRadius + " " + DotRadius * c + " -" + DotRadius + " 0 c");
+ stream.push("-" + DotRadius + " -" + DotRadius * c + " -" + DotRadius * c + " -" + DotRadius + " 0 -" + DotRadius + " c");
+ stream.push(DotRadius * c + " -" + DotRadius + " " + DotRadius + " -" + DotRadius * c + " " + DotRadius + " 0 c");
+ stream.push("f");
+ stream.push("Q");
+ xobj.stream = stream.join("\n");
+ return xobj;
+ },
+ YesPushDown: function YesPushDown(formObject) {
+ var xobj = createFormXObject(formObject);
+ var stream = [];
+ var DotRadius = AcroFormAppearance.internal.getWidth(formObject) <= AcroFormAppearance.internal.getHeight(formObject) ? AcroFormAppearance.internal.getWidth(formObject) / 4 : AcroFormAppearance.internal.getHeight(formObject) / 4; // The Borderpadding...
+
+ DotRadius *= 0.9; // Save results for later use; no need to waste
+ // processor ticks on doing math
+
+ var k = DotRadius * 2; // var c = AcroFormAppearance.internal.Bezier_C;
+
+ var kc = k * AcroFormAppearance.internal.Bezier_C;
+ var dc = DotRadius * AcroFormAppearance.internal.Bezier_C;
+ stream.push("0.749023 g");
+ stream.push("q");
+ stream.push("1 0 0 1 " + (AcroFormAppearance.internal.getWidth(formObject) / 2).toFixed(2) + " " + (AcroFormAppearance.internal.getHeight(formObject) / 2).toFixed(2) + " cm");
+ stream.push(k + " 0 m");
+ stream.push(k + " " + kc + " " + kc + " " + k + " 0 " + k + " c");
+ stream.push("-" + kc + " " + k + " -" + k + " " + kc + " -" + k + " 0 c");
+ stream.push("-" + k + " -" + kc + " -" + kc + " -" + k + " 0 -" + k + " c");
+ stream.push(kc + " -" + k + " " + k + " -" + kc + " " + k + " 0 c");
+ stream.push("f");
+ stream.push("Q");
+ stream.push("0 g");
+ stream.push("q");
+ stream.push("1 0 0 1 " + (AcroFormAppearance.internal.getWidth(formObject) / 2).toFixed(2) + " " + (AcroFormAppearance.internal.getHeight(formObject) / 2).toFixed(2) + " cm");
+ stream.push(DotRadius + " 0 m");
+ stream.push("" + DotRadius + " " + dc + " " + dc + " " + DotRadius + " 0 " + DotRadius + " c");
+ stream.push("-" + dc + " " + DotRadius + " -" + DotRadius + " " + dc + " -" + DotRadius + " 0 c");
+ stream.push("-" + DotRadius + " -" + dc + " -" + dc + " -" + DotRadius + " 0 -" + DotRadius + " c");
+ stream.push(dc + " -" + DotRadius + " " + DotRadius + " -" + dc + " " + DotRadius + " 0 c");
+ stream.push("f");
+ stream.push("Q");
+ xobj.stream = stream.join("\n");
+ return xobj;
+ },
+ OffPushDown: function OffPushDown(formObject) {
+ var xobj = createFormXObject(formObject);
+ var stream = [];
+ var DotRadius = AcroFormAppearance.internal.getWidth(formObject) <= AcroFormAppearance.internal.getHeight(formObject) ? AcroFormAppearance.internal.getWidth(formObject) / 4 : AcroFormAppearance.internal.getHeight(formObject) / 4; // The Borderpadding...
+
+ DotRadius *= 0.9; // Save results for later use; no need to waste
+ // processor ticks on doing math
+
+ var k = DotRadius * 2; // var c = AcroFormAppearance.internal.Bezier_C;
+
+ var kc = k * AcroFormAppearance.internal.Bezier_C;
+ stream.push("0.749023 g");
+ stream.push("q");
+ stream.push("1 0 0 1 " + (AcroFormAppearance.internal.getWidth(formObject) / 2).toFixed(2) + " " + (AcroFormAppearance.internal.getHeight(formObject) / 2).toFixed(2) + " cm");
+ stream.push(k + " 0 m");
+ stream.push(k + " " + kc + " " + kc + " " + k + " 0 " + k + " c");
+ stream.push("-" + kc + " " + k + " -" + k + " " + kc + " -" + k + " 0 c");
+ stream.push("-" + k + " -" + kc + " -" + kc + " -" + k + " 0 -" + k + " c");
+ stream.push(kc + " -" + k + " " + k + " -" + kc + " " + k + " 0 c");
+ stream.push("f");
+ stream.push("Q");
+ xobj.stream = stream.join("\n");
+ return xobj;
+ }
+ },
+ Cross: {
+ /**
+ * Creates the Actual AppearanceDictionary-References
+ *
+ * @param {string} name
+ * @returns {Object}
+ * @ignore
+ */
+ createAppearanceStream: function createAppearanceStream(name) {
+ var appearanceStreamContent = {
+ D: {
+ Off: AcroFormAppearance.RadioButton.Cross.OffPushDown
+ },
+ N: {}
+ };
+ appearanceStreamContent.N[name] = AcroFormAppearance.RadioButton.Cross.YesNormal;
+ appearanceStreamContent.D[name] = AcroFormAppearance.RadioButton.Cross.YesPushDown;
+ return appearanceStreamContent;
+ },
+ createMK: function createMK() {
+ return "<< /CA (8)>>";
+ },
+ YesNormal: function YesNormal(formObject) {
+ var xobj = createFormXObject(formObject);
+ var stream = [];
+ var cross = AcroFormAppearance.internal.calculateCross(formObject);
+ stream.push("q");
+ stream.push("1 1 " + (AcroFormAppearance.internal.getWidth(formObject) - 2).toFixed(2) + " " + (AcroFormAppearance.internal.getHeight(formObject) - 2).toFixed(2) + " re");
+ stream.push("W");
+ stream.push("n");
+ stream.push(cross.x1.x.toFixed(2) + " " + cross.x1.y.toFixed(2) + " m");
+ stream.push(cross.x2.x.toFixed(2) + " " + cross.x2.y.toFixed(2) + " l");
+ stream.push(cross.x4.x.toFixed(2) + " " + cross.x4.y.toFixed(2) + " m");
+ stream.push(cross.x3.x.toFixed(2) + " " + cross.x3.y.toFixed(2) + " l");
+ stream.push("s");
+ stream.push("Q");
+ xobj.stream = stream.join("\n");
+ return xobj;
+ },
+ YesPushDown: function YesPushDown(formObject) {
+ var xobj = createFormXObject(formObject);
+ var cross = AcroFormAppearance.internal.calculateCross(formObject);
+ var stream = [];
+ stream.push("0.749023 g");
+ stream.push("0 0 " + AcroFormAppearance.internal.getWidth(formObject).toFixed(2) + " " + AcroFormAppearance.internal.getHeight(formObject).toFixed(2) + " re");
+ stream.push("f");
+ stream.push("q");
+ stream.push("1 1 " + (AcroFormAppearance.internal.getWidth(formObject) - 2).toFixed(2) + " " + (AcroFormAppearance.internal.getHeight(formObject) - 2).toFixed(2) + " re");
+ stream.push("W");
+ stream.push("n");
+ stream.push(cross.x1.x.toFixed(2) + " " + cross.x1.y.toFixed(2) + " m");
+ stream.push(cross.x2.x.toFixed(2) + " " + cross.x2.y.toFixed(2) + " l");
+ stream.push(cross.x4.x.toFixed(2) + " " + cross.x4.y.toFixed(2) + " m");
+ stream.push(cross.x3.x.toFixed(2) + " " + cross.x3.y.toFixed(2) + " l");
+ stream.push("s");
+ stream.push("Q");
+ xobj.stream = stream.join("\n");
+ return xobj;
+ },
+ OffPushDown: function OffPushDown(formObject) {
+ var xobj = createFormXObject(formObject);
+ var stream = [];
+ stream.push("0.749023 g");
+ stream.push("0 0 " + AcroFormAppearance.internal.getWidth(formObject).toFixed(2) + " " + AcroFormAppearance.internal.getHeight(formObject).toFixed(2) + " re");
+ stream.push("f");
+ xobj.stream = stream.join("\n");
+ return xobj;
+ }
+ }
+ },
+
+ /**
+ * Returns the standard Appearance
+ *
+ * @returns {AcroFormXObject}
+ */
+ createDefaultAppearanceStream: function createDefaultAppearanceStream(formObject) {
+ // Set Helvetica to Standard Font (size: auto)
+ // Color: Black
+ return "/F1 0 Tf 0 g";
+ }
+ };
+ AcroFormAppearance.internal = {
+ Bezier_C: 0.551915024494,
+ calculateCross: function calculateCross(formObject) {
+ var min = function min(x, y) {
+ return x > y ? y : x;
+ };
+
+ var width = AcroFormAppearance.internal.getWidth(formObject);
+ var height = AcroFormAppearance.internal.getHeight(formObject);
+ var a = min(width, height);
+
+ var cross = {
+ x1: {
+ // upperLeft
+ x: (width - a) / 2,
+ y: (height - a) / 2 + a // height - borderPadding
+
+ },
+ x2: {
+ // lowerRight
+ x: (width - a) / 2 + a,
+ y: (height - a) / 2 // borderPadding
+
+ },
+ x3: {
+ // lowerLeft
+ x: (width - a) / 2,
+ y: (height - a) / 2 // borderPadding
+
+ },
+ x4: {
+ // upperRight
+ x: (width - a) / 2 + a,
+ y: (height - a) / 2 + a // height - borderPadding
+
+ }
+ };
+ return cross;
+ }
+ };
+
+ AcroFormAppearance.internal.getWidth = function (formObject) {
+ var result = 0;
+
+ if (_typeof(formObject) === "object") {
+ result = scale(formObject.Rect[2]); // (formObject.Rect[2] -
+ // formObject.Rect[0]) || 0;
+ }
+
+ return result;
+ };
+
+ AcroFormAppearance.internal.getHeight = function (formObject) {
+ var result = 0;
+
+ if (_typeof(formObject) === "object") {
+ result = scale(formObject.Rect[3]); // (formObject.Rect[1] -
+ // formObject.Rect[3]) || 0;
+ }
+
+ return result;
+ }; // Public:
+
+ /**
+ * Add an AcroForm-Field to the {jsPDF}-instance
+ *
+ * @memberOf AcroForm
+ * @name addField
+ * @param {Object} fieldObject
+ * @returns {jsPDF}
+ */
+
+
+ jsPDFAPI.addField = function (fieldObject) {
+ initializeAcroForm.call(this); // var opt = parseOptions(fieldObject);
+
+ if (fieldObject instanceof AcroFormTextField) {
+ this.addTextField.call(this, fieldObject);
+ } else if (fieldObject instanceof AcroFormChoiceField) {
+ this.addChoiceField.call(this, fieldObject);
+ } else if (fieldObject instanceof AcroFormButton) {
+ this.addButton.call(this, fieldObject);
+ } else if (fieldObject instanceof AcroFormChildClass) {
+ putForm.call(this, fieldObject);
+ } else if (fieldObject) {
+ // try to put..
+ putForm.call(this, fieldObject);
+ }
+
+ fieldObject.page = scope.internal.getCurrentPageInfo().pageNumber;
+ return this;
+ };
+ /**
+ * @name addButton
+ * @param {AcroFormButton}
+ * @memberOf AcroForm
+ */
+
+
+ jsPDFAPI.addButton = function (opts) {
+ initializeAcroForm.call(this);
+ var options = opts || new AcroFormField();
+ options.FT = "/Btn";
+ options.Ff = calculateFlagsOnOptions(options.Ff, opts, scope.internal.getPDFVersion());
+ putForm.call(this, options);
+ };
+
+ jsPDFAPI.addTextField = function (opts) {
+ initializeAcroForm.call(this);
+ var options = opts || new AcroFormField();
+ options.FT = "/Tx";
+ options.Ff = calculateFlagsOnOptions(options.Ff, opts, scope.internal.getPDFVersion()); // Add field
+
+ putForm.call(this, options);
+ };
+
+ jsPDFAPI.addChoiceField = function (opts) {
+ initializeAcroForm.call(this);
+ var options = opts || new AcroFormField();
+ options.FT = "/Ch";
+ options.Ff = calculateFlagsOnOptions(options.Ff, opts, scope.internal.getPDFVersion()); // options.hasAnnotation = true;
+ // Add field
+
+ putForm.call(this, options);
+ };
+
+ if (_typeof(globalObj) == "object") {
+ globalObj["ChoiceField"] = AcroFormChoiceField;
+ globalObj["ListBox"] = AcroFormListBox;
+ globalObj["ComboBox"] = AcroFormComboBox;
+ globalObj["EditBox"] = AcroFormEditBox;
+ globalObj["Button"] = AcroFormButton;
+ globalObj["PushButton"] = AcroFormPushButton;
+ globalObj["RadioButton"] = AcroFormRadioButton;
+ globalObj["CheckBox"] = AcroFormCheckBox;
+ globalObj["TextField"] = AcroFormTextField;
+ globalObj["PasswordField"] = AcroFormPasswordField; // backwardsCompatibility
+
+ globalObj["AcroForm"] = {
+ Appearance: AcroFormAppearance
+ };
+ }
+
+ jsPDFAPI.AcroFormChoiceField = AcroFormChoiceField;
+ jsPDFAPI.AcroFormListBox = AcroFormListBox;
+ jsPDFAPI.AcroFormComboBox = AcroFormComboBox;
+ jsPDFAPI.AcroFormEditBox = AcroFormEditBox;
+ jsPDFAPI.AcroFormButton = AcroFormButton;
+ jsPDFAPI.AcroFormPushButton = AcroFormPushButton;
+ jsPDFAPI.AcroFormRadioButton = AcroFormRadioButton;
+ jsPDFAPI.AcroFormCheckBox = AcroFormCheckBox;
+ jsPDFAPI.AcroFormTextField = AcroFormTextField;
+ jsPDFAPI.AcroFormPasswordField = AcroFormPasswordField;
+ jsPDFAPI.AcroFormAppearance = AcroFormAppearance;
+ jsPDFAPI.AcroForm = {
+ ChoiceField: AcroFormChoiceField,
+ ListBox: AcroFormListBox,
+ ComboBox: AcroFormComboBox,
+ EditBox: AcroFormEditBox,
+ Button: AcroFormButton,
+ PushButton: AcroFormPushButton,
+ RadioButton: AcroFormRadioButton,
+ CheckBox: AcroFormCheckBox,
+ TextField: AcroFormTextField,
+ PasswordField: AcroFormPasswordField,
+ Appearance: AcroFormAppearance
+ };
+ })(jsPDF.API, typeof window !== "undefined" && window || typeof global !== "undefined" && global);
+
+ /** @license
+ * jsPDF addImage plugin
+ * Copyright (c) 2012 Jason Siefken, https://github.com/siefkenj/
+ * 2013 Chris Dowling, https://github.com/gingerchris
+ * 2013 Trinh Ho, https://github.com/ineedfat
+ * 2013 Edwin Alejandro Perez, https://github.com/eaparango
+ * 2013 Norah Smith, https://github.com/burnburnrocket
+ * 2014 Diego Casorran, https://github.com/diegocr
+ * 2014 James Robb, https://github.com/jamesbrobb
+ *
+ *
+ */
+
+ /**
+ * @name addImage
+ * @module
+ */
+ (function (jsPDFAPI) {
+
+ var namespace = "addImage_";
+ var imageFileTypeHeaders = {
+ PNG: [[0x89, 0x50, 0x4e, 0x47]],
+ TIFF: [[0x4d, 0x4d, 0x00, 0x2a], //Motorola
+ [0x49, 0x49, 0x2a, 0x00] //Intel
+ ],
+ JPEG: [[0xff, 0xd8, 0xff, 0xe0, undefined, undefined, 0x4a, 0x46, 0x49, 0x46, 0x00], //JFIF
+ [0xff, 0xd8, 0xff, 0xe1, undefined, undefined, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00] //Exif
+ ],
+ JPEG2000: [[0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20]],
+ GIF87a: [[0x47, 0x49, 0x46, 0x38, 0x37, 0x61]],
+ GIF89a: [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61]],
+ BMP: [[0x42, 0x4d], //BM - Windows 3.1x, 95, NT, ... etc.
+ [0x42, 0x41], //BA - OS/2 struct bitmap array
+ [0x43, 0x49], //CI - OS/2 struct color icon
+ [0x43, 0x50], //CP - OS/2 const color pointer
+ [0x49, 0x43], //IC - OS/2 struct icon
+ [0x50, 0x54] //PT - OS/2 pointer
+ ]
+ };
+ /**
+ * Recognize filetype of Image by magic-bytes
+ *
+ * https://en.wikipedia.org/wiki/List_of_file_signatures
+ *
+ * @name getImageFileTypeByImageData
+ * @public
+ * @function
+ * @param {string|arraybuffer} imageData imageData as base64 encoded DataUrl or arraybuffer
+ * @param {string} format format of file if filetype-recognition fails, e.g. 'JPEG'
+ *
+ * @returns {string} filetype of Image
+ */
+
+ jsPDFAPI.getImageFileTypeByImageData = function (imageData, fallbackFormat) {
+ fallbackFormat = fallbackFormat || "UNKNOWN";
+ var i;
+ var j;
+ var result = "UNKNOWN";
+ var headerSchemata;
+ var compareResult;
+ var fileType;
+
+ if (jsPDFAPI.isArrayBufferView(imageData)) {
+ imageData = jsPDFAPI.arrayBufferToBinaryString(imageData);
+ }
+
+ for (fileType in imageFileTypeHeaders) {
+ headerSchemata = imageFileTypeHeaders[fileType];
+
+ for (i = 0; i < headerSchemata.length; i += 1) {
+ compareResult = true;
+
+ for (j = 0; j < headerSchemata[i].length; j += 1) {
+ if (headerSchemata[i][j] === undefined) {
+ continue;
+ }
+
+ if (headerSchemata[i][j] !== imageData.charCodeAt(j)) {
+ compareResult = false;
+ break;
+ }
+ }
+
+ if (compareResult === true) {
+ result = fileType;
+ break;
+ }
+ }
+ }
+
+ if (result === "UNKNOWN" && fallbackFormat !== "UNKNOWN") {
+ console.warn('FileType of Image not recognized. Processing image as "' + fallbackFormat + '".');
+ result = fallbackFormat;
+ }
+
+ return result;
+ }; // Image functionality ported from pdf.js
+
+
+ var putImage = function putImage(img) {
+ var objectNumber = this.internal.newObject(),
+ out = this.internal.write,
+ putStream = this.internal.putStream;
+ img["n"] = objectNumber;
+ out("<>");
+ }
+
+ if ("trns" in img && img["trns"].constructor == Array) {
+ var trns = "",
+ i = 0,
+ len = img["trns"].length;
+
+ for (; i < len; i++) {
+ trns += img["trns"][i] + " " + img["trns"][i] + " ";
+ }
+
+ out("/Mask [" + trns + "]");
+ }
+
+ if ("smask" in img) {
+ out("/SMask " + (objectNumber + 1) + " 0 R");
+ }
+
+ out("/Length " + img["data"].length + ">>");
+ putStream(img["data"]);
+ out("endobj"); // Soft mask
+
+ if ("smask" in img) {
+ var dp = "/Predictor " + img["p"] + " /Colors 1 /BitsPerComponent " + img["bpc"] + " /Columns " + img["w"];
+ var smask = {
+ w: img["w"],
+ h: img["h"],
+ cs: "DeviceGray",
+ bpc: img["bpc"],
+ dp: dp,
+ data: img["smask"]
+ };
+ if ("f" in img) smask.f = img["f"];
+ putImage.call(this, smask);
+ } //Palette
+
+
+ if (img["cs"] === this.color_spaces.INDEXED) {
+ this.internal.newObject(); //out('<< /Filter / ' + img['f'] +' /Length ' + img['pal'].length + '>>');
+ //putStream(zlib.compress(img['pal']));
+
+ out("<< /Length " + img["pal"].length + ">>");
+ putStream(this.arrayBufferToBinaryString(new Uint8Array(img["pal"])));
+ out("endobj");
+ }
+ },
+ putResourcesCallback = function putResourcesCallback() {
+ var images = this.internal.collections[namespace + "images"];
+
+ for (var i in images) {
+ putImage.call(this, images[i]);
+ }
+ },
+ putXObjectsDictCallback = function putXObjectsDictCallback() {
+ var images = this.internal.collections[namespace + "images"],
+ out = this.internal.write,
+ image;
+
+ for (var i in images) {
+ image = images[i];
+ out("/I" + image["i"], image["n"], "0", "R");
+ }
+ },
+ checkCompressValue = function checkCompressValue(value) {
+ if (value && typeof value === "string") value = value.toUpperCase();
+ return value in jsPDFAPI.image_compression ? value : jsPDFAPI.image_compression.NONE;
+ },
+ getImages = function getImages() {
+ var images = this.internal.collections[namespace + "images"]; //first run, so initialise stuff
+
+ if (!images) {
+ this.internal.collections[namespace + "images"] = images = {};
+ this.internal.events.subscribe("putResources", putResourcesCallback);
+ this.internal.events.subscribe("putXobjectDict", putXObjectsDictCallback);
+ }
+
+ return images;
+ },
+ getImageIndex = function getImageIndex(images) {
+ var imageIndex = 0;
+
+ if (images) {
+ // this is NOT the first time this method is ran on this instance of jsPDF object.
+ imageIndex = Object.keys ? Object.keys(images).length : function (o) {
+ var i = 0;
+
+ for (var e in o) {
+ if (o.hasOwnProperty(e)) {
+ i++;
+ }
+ }
+
+ return i;
+ }(images);
+ }
+
+ return imageIndex;
+ },
+ notDefined = function notDefined(value) {
+ return typeof value === "undefined" || value === null || value.length === 0;
+ },
+ generateAliasFromData = function generateAliasFromData(data) {
+ return typeof data === "string" && jsPDFAPI.sHashCode(data);
+ },
+ isImageTypeSupported = function isImageTypeSupported(type) {
+ return typeof jsPDFAPI["process" + type.toUpperCase()] === "function";
+ },
+ isDOMElement = function isDOMElement(object) {
+ return _typeof(object) === "object" && object.nodeType === 1;
+ },
+ createDataURIFromElement = function createDataURIFromElement(element, format) {
+ //if element is an image which uses data url definition, just return the dataurl
+ if (element.nodeName === "IMG" && element.hasAttribute("src")) {
+ var src = "" + element.getAttribute("src");
+ if (src.indexOf("data:image/") === 0) return unescape(src); // only if the user doesn't care about a format
+
+ if (!format && /\.png(?:[?#].*)?$/i.test(src)) format = "png";
+ }
+
+ if (element.nodeName === "CANVAS") {
+ var canvas = element;
+ } else {
+ var canvas = document.createElement("canvas");
+ canvas.width = element.clientWidth || element.width;
+ canvas.height = element.clientHeight || element.height;
+ var ctx = canvas.getContext("2d");
+
+ if (!ctx) {
+ throw "addImage requires canvas to be supported by browser.";
+ }
+
+ ctx.drawImage(element, 0, 0, canvas.width, canvas.height);
+ }
+
+ return canvas.toDataURL(("" + format).toLowerCase() == "png" ? "image/png" : "image/jpeg");
+ },
+ checkImagesForAlias = function checkImagesForAlias(alias, images) {
+ var cached_info;
+
+ if (images) {
+ for (var e in images) {
+ if (alias === images[e].alias) {
+ cached_info = images[e];
+ break;
+ }
+ }
+ }
+
+ return cached_info;
+ },
+ determineWidthAndHeight = function determineWidthAndHeight(w, h, info) {
+ if (!w && !h) {
+ w = -96;
+ h = -96;
+ }
+
+ if (w < 0) {
+ w = -1 * info["w"] * 72 / w / this.internal.scaleFactor;
+ }
+
+ if (h < 0) {
+ h = -1 * info["h"] * 72 / h / this.internal.scaleFactor;
+ }
+
+ if (w === 0) {
+ w = h * info["w"] / info["h"];
+ }
+
+ if (h === 0) {
+ h = w * info["h"] / info["w"];
+ }
+
+ return [w, h];
+ },
+ writeImageToPDF = function writeImageToPDF(x, y, w, h, info, index, images, rotation) {
+ var dims = determineWidthAndHeight.call(this, w, h, info),
+ coord = this.internal.getCoordinateString,
+ vcoord = this.internal.getVerticalCoordinateString;
+ w = dims[0];
+ h = dims[1];
+ images[index] = info;
+
+ if (rotation) {
+ rotation *= Math.PI / 180;
+ var c = Math.cos(rotation);
+ var s = Math.sin(rotation); //like in pdf Reference do it 4 digits instead of 2
+
+ var f4 = function f4(number) {
+ return number.toFixed(4);
+ };
+
+ var rotationTransformationMatrix = [f4(c), f4(s), f4(s * -1), f4(c), 0, 0, "cm"];
+ }
+
+ this.internal.write("q"); //Save graphics state
+
+ if (rotation) {
+ this.internal.write([1, "0", "0", 1, coord(x), vcoord(y + h), "cm"].join(" ")); //Translate
+
+ this.internal.write(rotationTransformationMatrix.join(" ")); //Rotate
+
+ this.internal.write([coord(w), "0", "0", coord(h), "0", "0", "cm"].join(" ")); //Scale
+ } else {
+ this.internal.write([coord(w), "0", "0", coord(h), coord(x), vcoord(y + h), "cm"].join(" ")); //Translate and Scale
+ }
+
+ if (this.isAdvancedAPI()) {
+ // draw image bottom up when in "advanced" API mode
+ this.internal.write([1, 0, 0, -1, 0, 0, "cm"].join(" "));
+ }
+
+ this.internal.write("/I" + info["i"] + " Do"); //Paint Image
+
+ this.internal.write("Q"); //Restore graphics state
+ };
+ /**
+ * COLOR SPACES
+ */
+
+
+ jsPDFAPI.color_spaces = {
+ DEVICE_RGB: "DeviceRGB",
+ DEVICE_GRAY: "DeviceGray",
+ DEVICE_CMYK: "DeviceCMYK",
+ CAL_GREY: "CalGray",
+ CAL_RGB: "CalRGB",
+ LAB: "Lab",
+ ICC_BASED: "ICCBased",
+ INDEXED: "Indexed",
+ PATTERN: "Pattern",
+ SEPARATION: "Separation",
+ DEVICE_N: "DeviceN"
+ };
+ /**
+ * DECODE METHODS
+ */
+
+ jsPDFAPI.decode = {
+ DCT_DECODE: "DCTDecode",
+ FLATE_DECODE: "FlateDecode",
+ LZW_DECODE: "LZWDecode",
+ JPX_DECODE: "JPXDecode",
+ JBIG2_DECODE: "JBIG2Decode",
+ ASCII85_DECODE: "ASCII85Decode",
+ ASCII_HEX_DECODE: "ASCIIHexDecode",
+ RUN_LENGTH_DECODE: "RunLengthDecode",
+ CCITT_FAX_DECODE: "CCITTFaxDecode"
+ };
+ /**
+ * IMAGE COMPRESSION TYPES
+ */
+
+ jsPDFAPI.image_compression = {
+ NONE: "NONE",
+ FAST: "FAST",
+ MEDIUM: "MEDIUM",
+ SLOW: "SLOW"
+ };
+ /**
+ * @name sHashCode
+ * @function
+ * @param {string} str
+ * @returns {string}
+ */
+
+ jsPDFAPI.sHashCode = function (str) {
+ str = str || "";
+ return Array.prototype.reduce && str.split("").reduce(function (a, b) {
+ a = (a << 5) - a + b.charCodeAt(0);
+ return a & a;
+ }, 0);
+ };
+ /**
+ * @name isString
+ * @function
+ * @param {any} object
+ * @returns {boolean}
+ */
+
+
+ jsPDFAPI.isString = function (object) {
+ return typeof object === "string";
+ };
+ /**
+ * Validates if given String is a valid Base64-String
+ *
+ * @name validateStringAsBase64
+ * @public
+ * @function
+ * @param {String} possible Base64-String
+ *
+ * @returns {boolean}
+ */
+
+
+ jsPDFAPI.validateStringAsBase64 = function (possibleBase64String) {
+ possibleBase64String = possibleBase64String || "";
+ var result = true;
+
+ if (possibleBase64String.length % 4 !== 0) {
+ result = false;
+ }
+
+ if (/[A-Za-z0-9\/]+/.test(possibleBase64String.substr(0, possibleBase64String.length - 2)) === false) {
+ result = false;
+ }
+
+ if (/[A-Za-z0-9\/][A-Za-z0-9+\/]|[A-Za-z0-9+\/]=|==/.test(possibleBase64String.substr(-2)) === false) {
+ result = false;
+ }
+
+ return result;
+ };
+ /**
+ * Strips out and returns info from a valid base64 data URI
+ *
+ * @name extractInfoFromBase64DataURI
+ * @function
+ * @param {string} dataUrl a valid data URI of format 'data:[][;base64],'
+ * @returns {Array}an Array containing the following
+ * [0] the complete data URI
+ * [1]
+ * [2] format - the second part of the mime-type i.e 'png' in 'image/png'
+ * [4]
+ */
+
+
+ jsPDFAPI.extractInfoFromBase64DataURI = function (dataURI) {
+ return /^data:([\w]+?\/([\w]+?));\S*;*base64,(.+)$/g.exec(dataURI);
+ };
+ /**
+ * Check to see if ArrayBuffer is supported
+ *
+ * @name supportsArrayBuffer
+ * @function
+ * @returns {boolean}
+ */
+
+
+ jsPDFAPI.supportsArrayBuffer = function () {
+ return typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined";
+ };
+ /**
+ * Tests supplied object to determine if ArrayBuffer
+ *
+ * @name isArrayBuffer
+ * @function
+ * @param {Object} object an Object
+ *
+ * @returns {boolean}
+ */
+
+
+ jsPDFAPI.isArrayBuffer = function (object) {
+ if (!this.supportsArrayBuffer()) return false;
+ return object instanceof ArrayBuffer;
+ };
+ /**
+ * Tests supplied object to determine if it implements the ArrayBufferView (TypedArray) interface
+ *
+ * @name isArrayBufferView
+ * @function
+ * @param {Object} object an Object
+ * @returns {boolean}
+ */
+
+
+ jsPDFAPI.isArrayBufferView = function (object) {
+ if (!this.supportsArrayBuffer()) return false;
+ if (typeof Uint32Array === "undefined") return false;
+ return object instanceof Int8Array || object instanceof Uint8Array || typeof Uint8ClampedArray !== "undefined" && object instanceof Uint8ClampedArray || object instanceof Int16Array || object instanceof Uint16Array || object instanceof Int32Array || object instanceof Uint32Array || object instanceof Float32Array || object instanceof Float64Array;
+ };
+ /**
+ * Convert the Buffer to a Binary String
+ *
+ * @name binaryStringToUint8Array
+ * @public
+ * @function
+ * @param {ArrayBuffer} BinaryString with ImageData
+ *
+ * @returns {Uint8Array}
+ */
+
+
+ jsPDFAPI.binaryStringToUint8Array = function (binary_string) {
+ /*
+ * not sure how efficient this will be will bigger files. Is there a native method?
+ */
+ var len = binary_string.length;
+ var bytes = new Uint8Array(len);
+
+ for (var i = 0; i < len; i++) {
+ bytes[i] = binary_string.charCodeAt(i);
+ }
+
+ return bytes;
+ };
+ /**
+ * Convert the Buffer to a Binary String
+ *
+ * @name arrayBufferToBinaryString
+ * @public
+ * @function
+ * @param {ArrayBuffer} ArrayBuffer with ImageData
+ *
+ * @returns {String}
+ */
+
+
+ jsPDFAPI.arrayBufferToBinaryString = function (buffer) {
+ if (typeof atob === "function") {
+ return atob(this.arrayBufferToBase64(buffer));
+ }
+
+ if (typeof TextDecoder === "function") {
+ var decoder = new TextDecoder("ascii"); // test if the encoding is supported
+
+ if (decoder.encoding === "ascii") {
+ return decoder.decode(buffer);
+ }
+ } //Fallback-solution
+
+
+ var data = this.isArrayBuffer(buffer) ? buffer : new Uint8Array(buffer);
+ var chunkSizeForSlice = 0x5000;
+ var binary_string = "";
+ var slicesCount = Math.ceil(data.byteLength / chunkSizeForSlice);
+
+ for (var i = 0; i < slicesCount; i++) {
+ binary_string += String.fromCharCode.apply(null, data.slice(i * chunkSizeForSlice, i * chunkSizeForSlice + chunkSizeForSlice));
+ }
+
+ return binary_string;
+ };
+ /**
+ * Converts an ArrayBuffer directly to base64
+ *
+ * Taken from http://jsperf.com/encoding-xhr-image-data/31
+ *
+ * Need to test if this is a better solution for larger files
+ *
+ * @name arrayBufferToBase64
+ * @param {arraybuffer} arrayBuffer
+ * @public
+ * @function
+ *
+ * @returns {string}
+ */
+
+
+ jsPDFAPI.arrayBufferToBase64 = function (arrayBuffer) {
+ var base64 = "";
+ var encodings = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ var bytes = new Uint8Array(arrayBuffer);
+ var byteLength = bytes.byteLength;
+ var byteRemainder = byteLength % 3;
+ var mainLength = byteLength - byteRemainder;
+ var a, b, c, d;
+ var chunk; // Main loop deals with bytes in chunks of 3
+
+ for (var i = 0; i < mainLength; i = i + 3) {
+ // Combine the three bytes into a single integer
+ chunk = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2]; // Use bitmasks to extract 6-bit segments from the triplet
+
+ a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
+
+ b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12
+
+ c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6
+
+ d = chunk & 63; // 63 = 2^6 - 1
+ // Convert the raw binary segments to the appropriate ASCII encoding
+
+ base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
+ } // Deal with the remaining bytes and padding
+
+
+ if (byteRemainder == 1) {
+ chunk = bytes[mainLength];
+ a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
+ // Set the 4 least significant bits to zero
+
+ b = (chunk & 3) << 4; // 3 = 2^2 - 1
+
+ base64 += encodings[a] + encodings[b] + "==";
+ } else if (byteRemainder == 2) {
+ chunk = bytes[mainLength] << 8 | bytes[mainLength + 1];
+ a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
+
+ b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4
+ // Set the 2 least significant bits to zero
+
+ c = (chunk & 15) << 2; // 15 = 2^4 - 1
+
+ base64 += encodings[a] + encodings[b] + encodings[c] + "=";
+ }
+
+ return base64;
+ };
+ /**
+ *
+ * @name createImageInfo
+ * @param {Object} data
+ * @param {number} wd width
+ * @param {number} ht height
+ * @param {Object} cs colorSpace
+ * @param {number} bpc bits per channel
+ * @param {any} f
+ * @param {number} imageIndex
+ * @param {string} alias
+ * @param {any} dp
+ * @param {any} trns
+ * @param {any} pal
+ * @param {any} smask
+ * @param {any} p
+ * @public
+ * @function
+ *
+ * @returns {Object}
+ */
+
+
+ jsPDFAPI.createImageInfo = function (data, wd, ht, cs, bpc, f, imageIndex, alias, dp, trns, pal, smask, p) {
+ var info = {
+ alias: alias,
+ w: wd,
+ h: ht,
+ cs: cs,
+ bpc: bpc,
+ i: imageIndex,
+ data: data // n: objectNumber will be added by putImage code
+
+ };
+ if (f) info.f = f;
+ if (dp) info.dp = dp;
+ if (trns) info.trns = trns;
+ if (pal) info.pal = pal;
+ if (smask) info.smask = smask;
+ if (p) info.p = p; // predictor parameter for PNG compression
+
+ return info;
+ };
+ /**
+ * Adds an Image to the PDF.
+ *
+ * @name addImage
+ * @public
+ * @function
+ * @param {string/Image-Element/Canvas-Element/Uint8Array} imageData imageData as base64 encoded DataUrl or Image-HTMLElement or Canvas-HTMLElement
+ * @param {string} format format of file if filetype-recognition fails, e.g. 'JPEG'
+ * @param {number} x x Coordinate (in units declared at inception of PDF document) against left edge of the page
+ * @param {number} y y Coordinate (in units declared at inception of PDF document) against upper edge of the page
+ * @param {number} width width of the image (in units declared at inception of PDF document)
+ * @param {number} height height of the Image (in units declared at inception of PDF document)
+ * @param {string} alias alias of the image (if used multiple times)
+ * @param {string} compression compression of the generated JPEG, can have the values 'NONE', 'FAST', 'MEDIUM' and 'SLOW'
+ * @param {number} rotation rotation of the image in degrees (0-359)
+ *
+ * @returns jsPDF
+ */
+
+
+ jsPDFAPI.addImage = function (imageData, format, x, y, w, h, alias, compression, rotation) {
+
+ var tmpImageData = "";
+
+ if (typeof format !== "string") {
+ var tmp = h;
+ h = w;
+ w = y;
+ y = x;
+ x = format;
+ format = tmp;
+ }
+
+ if (_typeof(imageData) === "object" && !isDOMElement(imageData) && "imageData" in imageData) {
+ var options = imageData;
+ imageData = options.imageData;
+ format = options.format || format;
+ x = options.x || x || 0;
+ y = options.y || y || 0;
+ w = options.w || w;
+ h = options.h || h;
+ alias = options.alias || alias;
+ compression = options.compression || compression;
+ rotation = options.rotation || options.angle || rotation;
+ }
+
+ if (typeof imageData === "string") {
+ imageData = unescape(imageData);
+ }
+
+ if (isNaN(x) || isNaN(y)) {
+ console.error("jsPDF.addImage: Invalid coordinates", arguments);
+ throw new Error("Invalid coordinates passed to jsPDF.addImage");
+ }
+
+ var images = getImages.call(this),
+ info;
+
+ if (!(info = checkImagesForAlias(imageData, images))) {
+ var dataAsBinaryString;
+ if (isDOMElement(imageData)) imageData = createDataURIFromElement(imageData, format);
+ if (notDefined(alias)) alias = generateAliasFromData(imageData);
+
+ if (!(info = checkImagesForAlias(alias, images))) {
+ if (this.isString(imageData)) {
+ tmpImageData = this.convertStringToImageData(imageData);
+
+ if (tmpImageData !== "") {
+ imageData = tmpImageData;
+ } else {
+ tmpImageData = this.loadImageFile(imageData);
+
+ if (tmpImageData !== undefined) {
+ imageData = tmpImageData;
+ }
+ }
+ }
+
+ format = this.getImageFileTypeByImageData(imageData, format);
+ if (!isImageTypeSupported(format)) throw new Error("addImage does not support files of type '" + format + "', please ensure that a plugin for '" + format + "' support is added.");
+ /**
+ * need to test if it's more efficient to convert all binary strings
+ * to TypedArray - or should we just leave and process as string?
+ */
+
+ if (this.supportsArrayBuffer()) {
+ // no need to convert if imageData is already uint8array
+ if (!(imageData instanceof Uint8Array)) {
+ dataAsBinaryString = imageData;
+ imageData = this.binaryStringToUint8Array(imageData);
+ }
+ }
+
+ info = this["process" + format.toUpperCase()](imageData, getImageIndex(images), alias, checkCompressValue(compression), dataAsBinaryString);
+ if (!info) throw new Error("An unkwown error occurred whilst processing the image");
+ }
+ }
+
+ writeImageToPDF.call(this, x, y, w, h, info, info.i, images, rotation);
+ return this;
+ };
+ /**
+ * @name convertStringToImageData
+ * @function
+ * @param {string} stringData
+ * @returns {string} binary data
+ */
+
+
+ jsPDFAPI.convertStringToImageData = function (stringData) {
+ var base64Info;
+ var imageData = "";
+
+ if (this.isString(stringData)) {
+ var base64Info = this.extractInfoFromBase64DataURI(stringData);
+
+ if (base64Info !== null) {
+ if (jsPDFAPI.validateStringAsBase64(base64Info[3])) {
+ imageData = atob(base64Info[3]); //convert to binary string
+ }
+ } else if (jsPDFAPI.validateStringAsBase64(stringData)) {
+ imageData = atob(stringData);
+ }
+ }
+
+ return imageData;
+ };
+ /**
+ * JPEG SUPPORT
+ **/
+ //takes a string imgData containing the raw bytes of
+ //a jpeg image and returns [width, height]
+ //Algorithm from: http://www.64lines.com/jpeg-width-height
+
+
+ var getJpegSize = function getJpegSize(imgData) {
+
+ var width, height, numcomponents; // Verify we have a valid jpeg header 0xff,0xd8,0xff,0xe0,?,?,'J','F','I','F',0x00
+
+ if (!imgData.charCodeAt(0) === 0xff || !imgData.charCodeAt(1) === 0xd8 || !imgData.charCodeAt(2) === 0xff || !imgData.charCodeAt(3) === 0xe0 || !imgData.charCodeAt(6) === "J".charCodeAt(0) || !imgData.charCodeAt(7) === "F".charCodeAt(0) || !imgData.charCodeAt(8) === "I".charCodeAt(0) || !imgData.charCodeAt(9) === "F".charCodeAt(0) || !imgData.charCodeAt(10) === 0x00) {
+ throw new Error("getJpegSize requires a binary string jpeg file");
+ }
+
+ var blockLength = imgData.charCodeAt(4) * 256 + imgData.charCodeAt(5);
+ var i = 4,
+ len = imgData.length;
+
+ while (i < len) {
+ i += blockLength;
+
+ if (imgData.charCodeAt(i) !== 0xff) {
+ throw new Error("getJpegSize could not find the size of the image");
+ }
+
+ if (imgData.charCodeAt(i + 1) === 0xc0 || //(SOF) Huffman - Baseline DCT
+ imgData.charCodeAt(i + 1) === 0xc1 || //(SOF) Huffman - Extended sequential DCT
+ imgData.charCodeAt(i + 1) === 0xc2 || // Progressive DCT (SOF2)
+ imgData.charCodeAt(i + 1) === 0xc3 || // Spatial (sequential) lossless (SOF3)
+ imgData.charCodeAt(i + 1) === 0xc4 || // Differential sequential DCT (SOF5)
+ imgData.charCodeAt(i + 1) === 0xc5 || // Differential progressive DCT (SOF6)
+ imgData.charCodeAt(i + 1) === 0xc6 || // Differential spatial (SOF7)
+ imgData.charCodeAt(i + 1) === 0xc7) {
+ height = imgData.charCodeAt(i + 5) * 256 + imgData.charCodeAt(i + 6);
+ width = imgData.charCodeAt(i + 7) * 256 + imgData.charCodeAt(i + 8);
+ numcomponents = imgData.charCodeAt(i + 9);
+ return [width, height, numcomponents];
+ } else {
+ i += 2;
+ blockLength = imgData.charCodeAt(i) * 256 + imgData.charCodeAt(i + 1);
+ }
+ }
+ },
+ getJpegSizeFromBytes = function getJpegSizeFromBytes(data) {
+ var hdr = data[0] << 8 | data[1];
+ if (hdr !== 0xffd8) throw new Error("Supplied data is not a JPEG");
+ var len = data.length,
+ block = (data[4] << 8) + data[5],
+ pos = 4,
+ bytes,
+ width,
+ height,
+ numcomponents;
+
+ while (pos < len) {
+ pos += block;
+ bytes = readBytes(data, pos);
+ block = (bytes[2] << 8) + bytes[3];
+
+ if ((bytes[1] === 0xc0 || bytes[1] === 0xc2) && bytes[0] === 0xff && block > 7) {
+ bytes = readBytes(data, pos + 5);
+ width = (bytes[2] << 8) + bytes[3];
+ height = (bytes[0] << 8) + bytes[1];
+ numcomponents = bytes[4];
+ return {
+ width: width,
+ height: height,
+ numcomponents: numcomponents
+ };
+ }
+
+ pos += 2;
+ }
+
+ throw new Error("getJpegSizeFromBytes could not find the size of the image");
+ },
+ readBytes = function readBytes(data, offset) {
+ return data.subarray(offset, offset + 5);
+ };
+ /**
+ * @ignore
+ */
+
+
+ jsPDFAPI.processJPEG = function (data, index, alias, compression, dataAsBinaryString, colorSpace) {
+
+ var filter = this.decode.DCT_DECODE,
+ bpc = 8,
+ dims;
+
+ if (!this.isString(data) && !this.isArrayBuffer(data) && !this.isArrayBufferView(data)) {
+ return null;
+ }
+
+ if (this.isString(data)) {
+ dims = getJpegSize(data);
+ }
+
+ if (this.isArrayBuffer(data)) {
+ data = new Uint8Array(data);
+ }
+
+ if (this.isArrayBufferView(data)) {
+ dims = getJpegSizeFromBytes(data); // if we already have a stored binary string rep use that
+
+ data = dataAsBinaryString || this.arrayBufferToBinaryString(data);
+ }
+
+ if (colorSpace === undefined) {
+ switch (dims.numcomponents) {
+ case 1:
+ colorSpace = this.color_spaces.DEVICE_GRAY;
+ break;
+
+ case 4:
+ colorSpace = this.color_spaces.DEVICE_CMYK;
+ break;
+
+ default:
+ case 3:
+ colorSpace = this.color_spaces.DEVICE_RGB;
+ break;
+ }
+ }
+
+ return this.createImageInfo(data, dims.width, dims.height, colorSpace, bpc, filter, index, alias);
+ };
+ /**
+ * @ignore
+ */
+
+
+ jsPDFAPI.processJPG = function ()
+ /*data, index, alias, compression, dataAsBinaryString*/
+ {
+ return this.processJPEG.apply(this, arguments);
+ };
+ /**
+ * @name loadImageFile
+ * @function
+ * @param {string} path
+ * @param {boolean} sync
+ * @param {function} callback
+ */
+
+
+ jsPDFAPI.loadImageFile = function (path, sync, callback) {
+ sync = sync || true;
+
+ callback = callback || function () {};
+
+ var isNode = Object.prototype.toString.call(typeof process !== "undefined" ? process : 0) === "[object process]";
+
+ var xhrMethod = function xhrMethod(url, sync, callback) {
+ var req = new XMLHttpRequest();
+ var byteArray = [];
+ var i = 0;
+
+ var sanitizeUnicode = function sanitizeUnicode(data) {
+ var dataLength = data.length;
+ var StringFromCharCode = String.fromCharCode; //Transform Unicode to ASCII
+
+ for (i = 0; i < dataLength; i += 1) {
+ byteArray.push(StringFromCharCode(data.charCodeAt(i) & 0xff));
+ }
+
+ return byteArray.join("");
+ };
+
+ req.open("GET", url, !sync); // XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com]
+
+ req.overrideMimeType("text/plain; charset=x-user-defined");
+
+ if (sync === false) {
+ req.onload = function () {
+ return sanitizeUnicode(this.responseText);
+ };
+ }
+
+ req.send(null);
+
+ if (req.status !== 200) {
+ console.warn('Unable to load file "' + url + '"');
+ return;
+ }
+
+ if (sync) {
+ return sanitizeUnicode(req.responseText);
+ }
+ }; //we have a browser and probably no CORS-Problem
+
+
+ if ((typeof window === "undefined" ? "undefined" : _typeof(window)) !== undefined && (typeof location === "undefined" ? "undefined" : _typeof(location)) === "object" && location.protocol.substr(0, 4) === "http") {
+ return xhrMethod(path, sync, callback);
+ }
+ };
+ /**
+ * @name getImageProperties
+ * @function
+ * @param {Object} imageData
+ * @returns {Object}
+ */
+
+
+ jsPDFAPI.getImageProperties = function (imageData) {
+ var info;
+ var tmpImageData = "";
+ var format;
+
+ if (isDOMElement(imageData)) {
+ imageData = createDataURIFromElement(imageData);
+ }
+
+ if (this.isString(imageData)) {
+ tmpImageData = this.convertStringToImageData(imageData);
+
+ if (tmpImageData !== "") {
+ imageData = tmpImageData;
+ } else {
+ tmpImageData = this.loadImageFile(imageData);
+
+ if (tmpImageData !== undefined) {
+ imageData = tmpImageData;
+ }
+ }
+ }
+
+ format = this.getImageFileTypeByImageData(imageData);
+ if (!isImageTypeSupported(format)) throw new Error("addImage does not support files of type '" + format + "', please ensure that a plugin for '" + format + "' support is added.");
+ /**
+ * need to test if it's more efficient to convert all binary strings
+ * to TypedArray - or should we just leave and process as string?
+ */
+
+ if (this.supportsArrayBuffer()) {
+ // no need to convert if imageData is already uint8array
+ if (!(imageData instanceof Uint8Array)) {
+ imageData = this.binaryStringToUint8Array(imageData);
+ }
+ }
+
+ info = this["process" + format.toUpperCase()](imageData);
+
+ if (!info) {
+ throw new Error("An unkwown error occurred whilst processing the image");
+ }
+
+ return {
+ fileType: format,
+ width: info.w,
+ height: info.h,
+ colorSpace: info.cs,
+ compressionMode: info.f,
+ bitsPerComponent: info.bpc
+ };
+ };
+ })(jsPDF.API);
+
+ /**
+ * @license
+ * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv
+ *
+ * Licensed under the MIT License.
+ * http://opensource.org/licenses/mit-license
+ */
+
+ /**
+ * jsPDF Annotations PlugIn
+ *
+ * There are many types of annotations in a PDF document. Annotations are placed
+ * on a page at a particular location. They are not 'attached' to an object.
+ *
+ * This plugin current supports
+ * Goto Page (set pageNumber and top in options)
+ * Goto Name (set name and top in options)
+ * Goto URL (set url in options)
+ *
+ * The destination magnification factor can also be specified when goto is a page number or a named destination. (see documentation below)
+ * (set magFactor in options). XYZ is the default.
+ *
+ *
+ * Links, Text, Popup, and FreeText are supported.
+ *
+ *
+ * Options In PDF spec Not Implemented Yet
+ *
link border
+ * named target
+ * page coordinates
+ * destination page scaling and layout
+ * actions other than URL and GotoPage
+ * background / hover actions
+ *
+ * @name annotations
+ * @module
+ */
+
+ /*
+ Destination Magnification Factors
+ See PDF 1.3 Page 386 for meanings and options
+
+ [supported]
+ XYZ (options; left top zoom)
+ Fit (no options)
+ FitH (options: top)
+ FitV (options: left)
+
+ [not supported]
+ FitR
+ FitB
+ FitBH
+ FitBV
+ */
+ (function (jsPDFAPI) {
+
+ var annotationPlugin = {
+ /**
+ * An array of arrays, indexed by pageNumber .
+ */
+ annotations: [],
+ f2: function f2(number) {
+ return number.toFixed(2);
+ },
+ notEmpty: function notEmpty(obj) {
+ if (typeof obj != "undefined") {
+ if (obj != "") {
+ return true;
+ }
+ }
+ }
+ };
+ jsPDF.API.annotationPlugin = annotationPlugin;
+ jsPDF.API.events.push(["addPage", function (info) {
+ this.annotationPlugin.annotations[info.pageNumber] = [];
+ }]);
+ jsPDFAPI.events.push(["putPage", function (info) {
+ //TODO store annotations in pageContext so reorder/remove will not affect them.
+ var pageAnnos = this.annotationPlugin.annotations[info.pageNumber];
+ var found = false;
+
+ for (var a = 0; a < pageAnnos.length && !found; a++) {
+ var anno = pageAnnos[a];
+
+ switch (anno.type) {
+ case "link":
+ if (annotationPlugin.notEmpty(anno.options.url) || annotationPlugin.notEmpty(anno.options.pageNumber)) {
+ found = true;
+ break;
+ }
+
+ case "reference":
+ case "text":
+ case "freetext":
+ found = true;
+ break;
+ }
+ }
+
+ if (found == false) {
+ return;
+ }
+
+ this.internal.write("/Annots [");
+ var f2 = this.annotationPlugin.f2;
+ var k = this.internal.scaleFactor;
+ var pageHeight = this.internal.pageSize.getHeight();
+ var pageInfo = this.internal.getPageInfo(info.pageNumber);
+
+ for (var a = 0; a < pageAnnos.length; a++) {
+ var anno = pageAnnos[a];
+
+ switch (anno.type) {
+ case "reference":
+ // References to Widget Anotations (for AcroForm Fields)
+ this.internal.write(" " + anno.object.objId + " 0 R ");
+ break;
+
+ case "text":
+ // Create a an object for both the text and the popup
+ var objText = this.internal.newAdditionalObject();
+ var objPopup = this.internal.newAdditionalObject();
+ var title = anno.title || "Note";
+ var rect = "/Rect [" + f2(anno.bounds.x * k) + " " + f2(pageHeight - (anno.bounds.y + anno.bounds.h) * k) + " " + f2((anno.bounds.x + anno.bounds.w) * k) + " " + f2((pageHeight - anno.bounds.y) * k) + "] ";
+ line = "<>";
+ objText.content = line;
+ var parent = objText.objId + " 0 R";
+ var popoff = 30;
+ var rect = "/Rect [" + f2((anno.bounds.x + popoff) * k) + " " + f2(pageHeight - (anno.bounds.y + anno.bounds.h) * k) + " " + f2((anno.bounds.x + anno.bounds.w + popoff) * k) + " " + f2((pageHeight - anno.bounds.y) * k) + "] "; //var rect2 = "/Rect [" + f2(anno.bounds.x * k) + " " + f2((pageHeight - anno.bounds.y) * k) + " " + f2(anno.bounds.x + anno.bounds.w * k) + " " + f2(pageHeight - (anno.bounds.y + anno.bounds.h) * k) + "] ";
+
+ line = "<>";
+ objPopup.content = line;
+ this.internal.write(objText.objId, "0 R", objPopup.objId, "0 R");
+ break;
+
+ case "freetext":
+ var rect = "/Rect [" + f2(anno.bounds.x * k) + " " + f2((pageHeight - anno.bounds.y) * k) + " " + f2(anno.bounds.x + anno.bounds.w * k) + " " + f2(pageHeight - (anno.bounds.y + anno.bounds.h) * k) + "] ";
+ var color = anno.color || "#000000";
+ line = "<>";
+ this.internal.write(line);
+ break;
+
+ case "link":
+ if (anno.options.name) {
+ var loc = this.annotations._nameMap[anno.options.name];
+ anno.options.pageNumber = loc.page;
+ anno.options.top = loc.y;
+ } else {
+ if (!anno.options.top) {
+ anno.options.top = 0;
+ }
+ }
+
+ var rect = "/Rect [" + f2(anno.x * k) + " " + f2((pageHeight - anno.y) * k) + " " + f2((anno.x + anno.w) * k) + " " + f2((pageHeight - (anno.y + anno.h)) * k) + "] ";
+ var line = "";
+
+ if (anno.options.url) {
+ line = "<>";
+ } else if (anno.options.pageNumber) {
+ // first page is 0
+ var info = this.internal.getPageInfo(anno.options.pageNumber);
+ line = "<>";
+ this.internal.write(line);
+ }
+
+ break;
+ }
+ }
+
+ this.internal.write("]");
+ }]);
+ /**
+ * @name createAnnotation
+ * @function
+ * @param {Object} options
+ */
+
+ jsPDFAPI.createAnnotation = function (options) {
+ switch (options.type) {
+ case "link":
+ this.link(options.bounds.x, options.bounds.y, options.bounds.w, options.bounds.h, options);
+ break;
+
+ case "text":
+ case "freetext":
+ this.annotationPlugin.annotations[this.internal.getCurrentPageInfo().pageNumber].push(options);
+ break;
+ }
+ };
+ /**
+ * Create a link
+ *
+ * valid options
+ * pageNumber or url [required]
+ * If pageNumber is specified, top and zoom may also be specified
+ * @name link
+ * @function
+ * @param {number} x
+ * @param {number} y
+ * @param {number} w
+ * @param {number} h
+ * @param {Object} options
+ */
+
+
+ jsPDFAPI.link = function (x, y, w, h, options) {
+
+ this.annotationPlugin.annotations[this.internal.getCurrentPageInfo().pageNumber].push({
+ x: x,
+ y: y,
+ w: w,
+ h: h,
+ options: options,
+ type: "link"
+ });
+ };
+ /**
+ * Currently only supports single line text.
+ * Returns the width of the text/link
+ *
+ * @name textWithLink
+ * @function
+ * @param {string} text
+ * @param {number} x
+ * @param {number} y
+ * @param {Object} options
+ * @returns {number} width the width of the text/link
+ */
+
+
+ jsPDFAPI.textWithLink = function (text, x, y, options) {
+
+ var width = this.getTextWidth(text);
+ var height = this.internal.getLineHeight() / this.internal.scaleFactor;
+ this.text(text, x, y); //TODO We really need the text baseline height to do this correctly.
+ // Or ability to draw text on top, bottom, center, or baseline.
+
+ y += height * 0.2;
+ this.link(x, y - height, width, height, options);
+ return width;
+ }; //TODO move into external library
+
+ /**
+ * @name getTextWidth
+ * @function
+ * @param {string} text
+ * @returns {number} txtWidth
+ */
+
+
+ jsPDFAPI.getTextWidth = function (text) {
+
+ var fontSize = this.internal.getFontSize();
+ var txtWidth = this.getStringUnitWidth(text) * fontSize / this.internal.scaleFactor;
+ return txtWidth;
+ }; //TODO move into external library
+
+ /**
+ * @name getLineHeight
+ * @function
+ * @returns {number} lineHeight
+ */
+
+
+ jsPDFAPI.getLineHeight = function () {
+ return this.internal.getLineHeight();
+ };
+
+ return this;
+ })(jsPDF.API);
+
+ /**
+ * @license
+ * Copyright (c) 2017 Aras Abbasi
+ *
+ * Licensed under the MIT License.
+ * http://opensource.org/licenses/mit-license
+ */
+
+ /**
+ * jsPDF arabic parser PlugIn
+ *
+ * @name arabic
+ * @module
+ */
+ (function (jsPDFAPI) {
+
+ var arLangCodes = {
+ ar: "Arabic (Standard)",
+ "ar-DZ": "Arabic (Algeria)",
+ "ar-BH": "Arabic (Bahrain)",
+ "ar-EG": "Arabic (Egypt)",
+ "ar-IQ": "Arabic (Iraq)",
+ "ar-JO": "Arabic (Jordan)",
+ "ar-KW": "Arabic (Kuwait)",
+ "ar-LB": "Arabic (Lebanon)",
+ "ar-LY": "Arabic (Libya)",
+ "ar-MA": "Arabic (Morocco)",
+ "ar-OM": "Arabic (Oman)",
+ "ar-QA": "Arabic (Qatar)",
+ "ar-SA": "Arabic (Saudi Arabia)",
+ "ar-SY": "Arabic (Syria)",
+ "ar-TN": "Arabic (Tunisia)",
+ "ar-AE": "Arabic (U.A.E.)",
+ "ar-YE": "Arabic (Yemen)",
+ fa: "Persian",
+ "fa-IR": "Persian/Iran",
+ ur: "Urdu"
+ };
+ var arLangCodesKeys = Object.keys(arLangCodes);
+ /**
+ * Arabic shape substitutions: char code => (isolated, final, initial, medial).
+ */
+
+ var arabicSubst = {
+ 1569: [65152],
+ 1570: [65153, 65154, 65153, 65154],
+ 1571: [65155, 65156, 65155, 65156],
+ 1572: [65157, 65158],
+ 1573: [65159, 65160, 65159, 65160],
+ 1574: [65161, 65162, 65163, 65164],
+ 1575: [65165, 65166, 65165, 65166],
+ 1576: [65167, 65168, 65169, 65170],
+ 1577: [65171, 65172],
+ 1578: [65173, 65174, 65175, 65176],
+ 1579: [65177, 65178, 65179, 65180],
+ 1580: [65181, 65182, 65183, 65184],
+ 1581: [65185, 65186, 65187, 65188],
+ 1582: [65189, 65190, 65191, 65192],
+ 1583: [65193, 65194, 65193],
+ 1584: [65195, 65196, 65195],
+ 1585: [65197, 65198, 65197],
+ 1586: [65199, 65200, 65199],
+ 1587: [65201, 65202, 65203, 65204],
+ 1588: [65205, 65206, 65207, 65208],
+ 1589: [65209, 65210, 65211, 65212],
+ 1590: [65213, 65214, 65215, 65216],
+ 1591: [65217, 65218, 65219, 65220],
+ 1592: [65221, 65222, 65223, 65224],
+ 1593: [65225, 65226, 65227, 65228],
+ 1594: [65229, 65230, 65231, 65232],
+ 1601: [65233, 65234, 65235, 65236],
+ 1602: [65237, 65238, 65239, 65240],
+ 1603: [65241, 65242, 65243, 65244],
+ 1604: [65245, 65246, 65247, 65248],
+ 1605: [65249, 65250, 65251, 65252],
+ 1606: [65253, 65254, 65255, 65256],
+ 1607: [65257, 65258, 65259, 65260],
+ 1608: [65261, 65262, 65261],
+ 1609: [65263, 65264, 64488, 64489],
+ 1610: [65265, 65266, 65267, 65268],
+ 1649: [64336, 64337],
+ 1655: [64477],
+ 1657: [64358, 64359, 64360, 64361],
+ 1658: [64350, 64351, 64352, 64353],
+ 1659: [64338, 64339, 64340, 64341],
+ 1662: [64342, 64343, 64344, 64345],
+ 1663: [64354, 64355, 64356, 64357],
+ 1664: [64346, 64347, 64348, 64349],
+ 1667: [64374, 64375, 64376, 64377],
+ 1668: [64370, 64371, 64372, 64373],
+ 1670: [64378, 64379, 64380, 64381],
+ 1671: [64382, 64383, 64384, 64385],
+ 1672: [64392, 64393],
+ 1676: [64388, 64389],
+ 1677: [64386, 64387],
+ 1678: [64390, 64391],
+ 1681: [64396, 64397],
+ 1688: [64394, 64395, 64394],
+ 1700: [64362, 64363, 64364, 64365],
+ 1702: [64366, 64367, 64368, 64369],
+ 1705: [64398, 64399, 64400, 64401],
+ 1709: [64467, 64468, 64469, 64470],
+ 1711: [64402, 64403, 64404, 64405],
+ 1713: [64410, 64411, 64412, 64413],
+ 1715: [64406, 64407, 64408, 64409],
+ 1722: [64414, 64415],
+ 1723: [64416, 64417, 64418, 64419],
+ 1726: [64426, 64427, 64428, 64429],
+ 1728: [64420, 64421],
+ 1729: [64422, 64423, 64424, 64425],
+ 1733: [64480, 64481],
+ 1734: [64473, 64474],
+ 1735: [64471, 64472],
+ 1736: [64475, 64476],
+ 1737: [64482, 64483],
+ 1739: [64478, 64479],
+ 1740: [64508, 64509, 64510, 64511],
+ 1744: [64484, 64485, 64486, 64487],
+ 1746: [64430, 64431],
+ 1747: [64432, 64433]
+ };
+ var arabiclaasubst = {
+ 1570: [65269, 65270, 65269, 65270],
+ 1571: [65271, 65272, 65271, 65272],
+ 1573: [65273, 65274, 65273, 65274],
+ 1575: [65275, 65276, 65275, 65276]
+ };
+ var arabicorigsubst = {
+ 1570: [65153, 65154, 65153, 65154],
+ 1571: [65155, 65156, 65155, 65156],
+ 1573: [65159, 65160, 65159, 65160],
+ 1575: [65165, 65166, 65165, 65166]
+ };
+ var arabic_diacritics = {
+ 1612: 64606,
+ // Shadda + Dammatan
+ 1613: 64607,
+ // Shadda + Kasratan
+ 1614: 64608,
+ // Shadda + Fatha
+ 1615: 64609,
+ // Shadda + Damma
+ 1616: 64610 // Shadda + Kasra
+
+ };
+ var alfletter = [1570, 1571, 1573, 1575];
+ var endedletter = [1569, 1570, 1571, 1572, 1573, 1575, 1577, 1583, 1584, 1585, 1586, 1608, 1688];
+ var isolatedForm = 0;
+ var finalForm = 1;
+ var initialForm = 2;
+ var medialForm = 3; //private
+
+ function isArabicLetter(letter) {
+ return letter !== undefined && arabicSubst[letter.charCodeAt(0)] !== undefined;
+ }
+
+ function isArabicEndLetter(letter) {
+ return letter !== undefined && endedletter.indexOf(letter.charCodeAt(0)) >= 0;
+ }
+
+ function isArabicAlfLetter(letter) {
+ return letter !== undefined && alfletter.indexOf(letter.charCodeAt(0)) >= 0;
+ }
+
+ function arabicLetterHasFinalForm(letter) {
+ return isArabicLetter(letter) && arabicSubst[letter.charCodeAt(0)].length >= 2;
+ }
+
+ function arabicLetterHasMedialForm(letter) {
+ return isArabicLetter(letter) && arabicSubst[letter.charCodeAt(0)].length == 4;
+ }
+
+ function isArabicDiacritic(letter) {
+ return letter !== undefined && arabic_diacritics[letter.charCodeAt(0)] !== undefined;
+ }
+
+ function getCorrectForm(currentChar, beforeChar, nextChar, arabicSubstition) {
+
+ if (!isArabicLetter(currentChar)) {
+ return -1;
+ }
+
+ arabicSubstition = arabicSubstition || {};
+ arabicSubst = Object.assign(arabicSubst, arabicSubstition);
+
+ if (!arabicLetterHasFinalForm(currentChar) || !isArabicLetter(beforeChar) && !isArabicLetter(nextChar) || !isArabicLetter(nextChar) && isArabicEndLetter(beforeChar) || isArabicEndLetter(currentChar) && !isArabicLetter(beforeChar) || isArabicEndLetter(currentChar) && isArabicAlfLetter(beforeChar) || isArabicEndLetter(currentChar) && isArabicEndLetter(beforeChar)) {
+ arabicSubst = Object.assign(arabicSubst, arabicorigsubst);
+ return isolatedForm;
+ }
+
+ if (arabicLetterHasMedialForm(currentChar) && isArabicLetter(beforeChar) && !isArabicEndLetter(beforeChar) && isArabicLetter(nextChar) && arabicLetterHasFinalForm(nextChar)) {
+ arabicSubst = Object.assign(arabicSubst, arabicorigsubst);
+ return medialForm;
+ }
+
+ if (isArabicEndLetter(currentChar) || !isArabicLetter(nextChar)) {
+ arabicSubst = Object.assign(arabicSubst, arabicorigsubst);
+ return finalForm;
+ }
+
+ arabicSubst = Object.assign(arabicSubst, arabicorigsubst);
+ return initialForm;
+ }
+
+ var commonSubstition = function commonSubstition(character) {
+ var replacementTable = {
+ "(": ")",
+ ")": "("
+ };
+ return replacementTable[character] || character;
+ };
+ /**
+ * @name processArabic
+ * @function
+ * @param {string} text
+ * @param {boolean} reverse
+ * @returns {string}
+ */
+
+
+ var processArabic = jsPDFAPI.processArabic = function (text, reverse) {
+ text = text || "";
+ reverse = reverse || false;
+ var result = "";
+ var i = 0;
+ var position = 0;
+ var currentLetter = "";
+ var prevLetter = "";
+ var nextLetter = "";
+ var resultingLetter;
+ var localPrevLetter;
+ var localCurrentLetter;
+ var localNextLetter;
+
+ for (i = 0; i < text.length; i += 1) {
+ currentLetter = text[i];
+ prevLetter = text[i - 1];
+ nextLetter = text[i + 1];
+
+ if (!isArabicLetter(currentLetter)) {
+ result += reverse ? commonSubstition(currentLetter) : currentLetter;
+ } else {
+ if (prevLetter !== undefined && prevLetter.charCodeAt(0) === 1604 && isArabicAlfLetter(currentLetter)) {
+ localPrevLetter = text[i - 2];
+ localCurrentLetter = currentLetter;
+ localNextLetter = text[i + 1];
+ position = getCorrectForm(localCurrentLetter, localPrevLetter, localNextLetter, arabiclaasubst);
+ resultingLetter = String.fromCharCode(arabiclaasubst[currentLetter.charCodeAt(0)][position]);
+ result = result.substr(0, result.length - 1) + resultingLetter;
+ } else if (prevLetter !== undefined && prevLetter.charCodeAt(0) === 1617 && isArabicDiacritic(currentLetter)) {
+ localPrevLetter = text[i - 2];
+ localCurrentLetter = currentLetter;
+ localNextLetter = text[i + 1];
+ position = getCorrectForm(localCurrentLetter, localPrevLetter, localNextLetter, arabicorigsubst);
+ resultingLetter = String.fromCharCode(arabic_diacritics[currentLetter.charCodeAt(0)][position]);
+ result = result.substr(0, result.length - 1) + resultingLetter;
+ } else {
+ position = getCorrectForm(currentLetter, prevLetter, nextLetter, arabicorigsubst);
+ result += String.fromCharCode(arabicSubst[currentLetter.charCodeAt(0)][position]);
+ }
+ }
+ }
+
+ return reverse ? result.split("").reverse().join("") : result;
+ };
+
+ var arabicParserFunction = function arabicParserFunction(args) {
+ var text = args.text;
+ var x = args.x;
+ var y = args.y;
+ var options = args.options || {};
+ var mutex = args.mutex || {};
+ var lang = options.lang;
+ var tmpText = [];
+
+ if (arLangCodesKeys.indexOf(lang) >= 0) {
+ if (Object.prototype.toString.call(text) === "[object Array]") {
+ var i = 0;
+ tmpText = [];
+
+ for (i = 0; i < text.length; i += 1) {
+ if (Object.prototype.toString.call(text[i]) === "[object Array]") {
+ tmpText.push([processArabic(text[i][0], true), text[i][1], text[i][2]]);
+ } else {
+ tmpText.push([processArabic(text[i], true)]);
+ }
+ }
+
+ args.text = tmpText;
+ } else {
+ args.text = processArabic(text, true);
+ } //force charSpace if not given.
+
+
+ if (options.charSpace === undefined) {
+ args.options.charSpace = 0;
+ } //if R2L is true, set it false.
+
+
+ if (options.R2L === true) {
+ args.options.R2L = false;
+ }
+ }
+ };
+
+ jsPDFAPI.events.push(["preProcessText", arabicParserFunction]);
+ })(jsPDF.API);
+
+ /** @license
+ * jsPDF Autoprint Plugin
+ *
+ * Licensed under the MIT License.
+ * http://opensource.org/licenses/mit-license
+ */
+
+ /**
+ * @name autoprint
+ * @module
+ */
+ (function (jsPDFAPI) {
+ /**
+ * Makes the PDF automatically print. This works in Chrome, Firefox, Acrobat
+ * Reader.
+ *
+ * @name autoPrint
+ * @function
+ * @param {Object} options (optional) Set the attribute variant to 'non-conform' (default) or 'javascript' to activate different methods of automatic printing when opening in a PDF-viewer .
+ * @returns {jsPDF}
+ * @example
+ * var doc = new jsPDF();
+ * doc.text(10, 10, 'This is a test');
+ * doc.autoPrint({variant: 'non-conform'});
+ * doc.save('autoprint.pdf');
+ */
+
+ jsPDFAPI.autoPrint = function (options) {
+
+ var refAutoPrintTag;
+ options = options || {};
+ options.variant = options.variant || "non-conform";
+
+ switch (options.variant) {
+ case "javascript":
+ //https://github.com/Rob--W/pdf.js/commit/c676ecb5a0f54677b9f3340c3ef2cf42225453bb
+ this.addJS("print({});");
+ break;
+
+ case "non-conform":
+ default:
+ this.internal.events.subscribe("postPutResources", function () {
+ refAutoPrintTag = this.internal.newObject();
+ this.internal.out("<<");
+ this.internal.out("/S /Named");
+ this.internal.out("/Type /Action");
+ this.internal.out("/N /Print");
+ this.internal.out(">>");
+ this.internal.out("endobj");
+ });
+ this.internal.events.subscribe("putCatalog", function () {
+ this.internal.out("/OpenAction " + refAutoPrintTag + " 0 R");
+ });
+ break;
+ }
+
+ return this;
+ };
+ })(jsPDF.API);
+
+ /**
+ * @license
+ * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv
+ *
+ * Licensed under the MIT License.
+ * http://opensource.org/licenses/mit-license
+ */
+
+ /**
+ * jsPDF Canvas PlugIn
+ * This plugin mimics the HTML5 Canvas
+ *
+ * The goal is to provide a way for current canvas users to print directly to a PDF.
+ * @name canvas
+ * @module
+ */
+ (function (jsPDFAPI) {
+
+ jsPDFAPI.events.push(["initialized", function () {
+ this.canvas.pdf = this;
+ }]);
+ /**
+ * @name canvas
+ * @static
+ * @type {Object}
+ */
+
+ jsPDFAPI.canvas = {
+ /**
+ * @name getContext
+ * @function
+ * @param {string} the context type
+ */
+ getContext: function getContext(name) {
+ this.pdf.context2d._canvas = this;
+ return this.pdf.context2d;
+ },
+ childNodes: [],
+ style: {},
+ autoContext2dResizeX: true,
+ autoContext2dResizeY: true
+ };
+ /**
+ * Width of the canvas
+ *
+ * @name width
+ * @property {number} width
+ */
+
+ Object.defineProperty(jsPDFAPI.canvas, "width", {
+ get: function get() {
+ return this._width;
+ },
+ set: function set(value) {
+ this._width = value;
+
+ if (this.autoContext2dResizeX) {
+ this.getContext("2d").pageWrapX = value + 1;
+ }
+ }
+ });
+ /**
+ * Height of the canvas
+ *
+ * @name height
+ * @property {number} height
+ */
+
+ Object.defineProperty(jsPDFAPI.canvas, "height", {
+ get: function get() {
+ return this._height;
+ },
+ set: function set(value) {
+ this._height = value;
+
+ if (this.autoContext2dResizeY) {
+ this.getContext("2d").pageWrapY = value + 1;
+ }
+ }
+ });
+ return this;
+ })(jsPDF.API);
+
+ /**
+ * @license
+ * ====================================================================
+ * Copyright (c) 2013 Youssef Beddad, youssef.beddad@gmail.com
+ * 2013 Eduardo Menezes de Morais, eduardo.morais@usp.br
+ * 2013 Lee Driscoll, https://github.com/lsdriscoll
+ * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria
+ * 2014 James Hall, james@parall.ax
+ * 2014 Diego Casorran, https://github.com/diegocr
+ *
+ *
+ * ====================================================================
+ */
+
+ /**
+ * @name cell
+ * @module
+ */
+ (function (jsPDFAPI) {
+ /*jslint browser:true */
+
+ /*global document: false, jsPDF */
+
+ var padding = 3,
+ margin = 13,
+ headerFunction,
+ lastCellPos = {
+ x: undefined,
+ y: undefined,
+ w: undefined,
+ h: undefined,
+ ln: undefined
+ },
+ pages = 1,
+ setLastCellPosition = function setLastCellPosition(x, y, w, h, ln) {
+ lastCellPos = {
+ x: x,
+ y: y,
+ w: w,
+ h: h,
+ ln: ln
+ };
+ },
+ getLastCellPosition = function getLastCellPosition() {
+ return lastCellPos;
+ },
+ NO_MARGINS = {
+ left: 0,
+ top: 0,
+ bottom: 0
+ };
+ /**
+ * @name setHeaderFunction
+ * @function
+ * @param {function} func
+ */
+
+
+ jsPDFAPI.setHeaderFunction = function (func) {
+ headerFunction = func;
+ };
+ /**
+ * @name getTextDimensions
+ * @function
+ * @param {string} txt
+ * @returns {Object} dimensions
+ */
+
+
+ jsPDFAPI.getTextDimensions = function (text, options) {
+ var fontSize = this.table_font_size || this.internal.getFontSize();
+ var fontStyle = this.internal.getFont().fontStyle;
+ options = options || {};
+ var scaleFactor = options.scaleFactor || this.internal.scaleFactor;
+ var width = 0;
+ var amountOfLines = 0;
+ var height = 0;
+ var tempWidth = 0;
+
+ if (typeof text === "string") {
+ width = this.getStringUnitWidth(text) * fontSize;
+
+ if (width !== 0) {
+ amountOfLines = 1;
+ }
+ } else if (Object.prototype.toString.call(text) === "[object Array]") {
+ for (var i = 0; i < text.length; i++) {
+ tempWidth = this.getStringUnitWidth(text[i]) * fontSize;
+
+ if (width < tempWidth) {
+ width = tempWidth;
+ }
+ }
+
+ if (width !== 0) {
+ amountOfLines = text.length;
+ }
+ } else {
+ console.error("getTextDimensions expects text-parameter to be of type String or an Array of Strings.");
+ }
+
+ width = width / scaleFactor;
+ height = amountOfLines * fontSize * 1.15 / scaleFactor;
+ return {
+ w: width,
+ h: height
+ };
+ };
+ /**
+ * @name cellAddPage
+ * @function
+ */
+
+
+ jsPDFAPI.cellAddPage = function () {
+ var margins = this.margins || NO_MARGINS;
+ this.addPage();
+ setLastCellPosition(margins.left, margins.top, undefined, undefined); //setLastCellPosition(undefined, undefined, undefined, undefined, undefined);
+
+ pages += 1;
+ };
+ /**
+ * @name cellInitialize
+ * @function
+ */
+
+
+ jsPDFAPI.cellInitialize = function () {
+ lastCellPos = {
+ x: undefined,
+ y: undefined,
+ w: undefined,
+ h: undefined,
+ ln: undefined
+ };
+ pages = 1;
+ };
+ /**
+ * @name cell
+ * @function
+ * @param {number} x
+ * @param {number} y
+ * @param {number} w
+ * @param {number} h
+ * @param {string} txt
+ * @param {number} ln lineNumber
+ * @param {string} align
+ * @return {jsPDF} jsPDF-instance
+ */
+
+
+ jsPDFAPI.cell = function (x, y, w, h, txt, ln, align) {
+ var curCell = getLastCellPosition();
+ var pgAdded = false; // If this is not the first cell, we must change its position
+
+ if (curCell.ln !== undefined) {
+ if (curCell.ln === ln) {
+ //Same line
+ x = curCell.x + curCell.w;
+ y = curCell.y;
+ } else {
+ //New line
+ var margins = this.margins || NO_MARGINS;
+
+ if (curCell.y + curCell.h + h + margin >= this.internal.pageSize.getHeight() - margins.bottom) {
+ this.cellAddPage();
+ pgAdded = true;
+
+ if (this.printHeaders && this.tableHeaderRow) {
+ this.printHeaderRow(ln, true);
+ }
+ } //We ignore the passed y: the lines may have different heights
+
+
+ y = getLastCellPosition().y + getLastCellPosition().h;
+ if (pgAdded) y = margin + 10;
+ }
+ }
+
+ if (txt[0] !== undefined) {
+ if (this.printingHeaderRow) {
+ this.rect(x, y, w, h, "FD");
+ } else {
+ this.rect(x, y, w, h);
+ }
+
+ if (align === "right") {
+ if (!(txt instanceof Array)) {
+ txt = [txt];
+ }
+
+ for (var i = 0; i < txt.length; i++) {
+ var currentLine = txt[i];
+ var textSize = this.getStringUnitWidth(currentLine) * this.internal.getFontSize();
+ this.text(currentLine, x + w - textSize - padding, y + this.internal.getLineHeight() * (i + 1));
+ }
+ } else {
+ this.text(txt, x + padding, y + this.internal.getLineHeight());
+ }
+ }
+
+ setLastCellPosition(x, y, w, h, ln);
+ return this;
+ };
+ /**
+ * Return the maximum value from an array
+ *
+ * @name arrayMax
+ * @function
+ * @param {Array} array
+ * @param comparisonFn
+ * @returns {number}
+ */
+
+
+ jsPDFAPI.arrayMax = function (array, comparisonFn) {
+ var max = array[0],
+ i,
+ ln,
+ item;
+
+ for (i = 0, ln = array.length; i < ln; i += 1) {
+ item = array[i];
+
+ if (comparisonFn) {
+ if (comparisonFn(max, item) === -1) {
+ max = item;
+ }
+ } else {
+ if (item > max) {
+ max = item;
+ }
+ }
+ }
+
+ return max;
+ };
+ /**
+ * Create a table from a set of data.
+ * @name table
+ * @function
+ * @param {Integer} [x] : left-position for top-left corner of table
+ * @param {Integer} [y] top-position for top-left corner of table
+ * @param {Object[]} [data] As array of objects containing key-value pairs corresponding to a row of data.
+ * @param {String[]} [headers] Omit or null to auto-generate headers at a performance cost
+ * @param {Object} [config.printHeaders] True to print column headers at the top of every page
+ * @param {Object} [config.autoSize] True to dynamically set the column widths to match the widest cell value
+ * @param {Object} [config.margins] margin values for left, top, bottom, and width
+ * @param {Object} [config.fontSize] Integer fontSize to use (optional)
+ * @returns {jsPDF} jsPDF-instance
+ */
+
+
+ jsPDFAPI.table = function (x, y, data, headers, config) {
+ if (!data) {
+ throw "No data for PDF table";
+ }
+
+ var headerNames = [],
+ headerPrompts = [],
+ header,
+ i,
+ ln,
+ cln,
+ columnMatrix = {},
+ columnWidths = {},
+ columnData,
+ column,
+ columnMinWidths = [],
+ j,
+ tableHeaderConfigs = [],
+ model,
+ jln,
+ func,
+ //set up defaults. If a value is provided in config, defaults will be overwritten:
+ autoSize = false,
+ printHeaders = true,
+ fontSize = 12,
+ margins = NO_MARGINS;
+ margins.width = this.internal.pageSize.getWidth();
+
+ if (config) {
+ //override config defaults if the user has specified non-default behavior:
+ if (config.autoSize === true) {
+ autoSize = true;
+ }
+
+ if (config.printHeaders === false) {
+ printHeaders = false;
+ }
+
+ if (config.fontSize) {
+ fontSize = config.fontSize;
+ }
+
+ if (config.css && typeof config.css["font-size"] !== "undefined") {
+ fontSize = config.css["font-size"] * 16;
+ }
+
+ if (config.margins) {
+ margins = config.margins;
+ }
+ }
+ /**
+ * @property {Number} lnMod
+ * Keep track of the current line number modifier used when creating cells
+ */
+
+
+ this.lnMod = 0;
+ lastCellPos = {
+ x: undefined,
+ y: undefined,
+ w: undefined,
+ h: undefined,
+ ln: undefined
+ }, pages = 1;
+ this.printHeaders = printHeaders;
+ this.margins = margins;
+ this.setFontSize(fontSize);
+ this.table_font_size = fontSize; // Set header values
+
+ if (headers === undefined || headers === null) {
+ // No headers defined so we derive from data
+ headerNames = Object.keys(data[0]);
+ } else if (headers[0] && typeof headers[0] !== "string") {
+ var px2pt = 0.264583 * 72 / 25.4; // Split header configs into names and prompts
+
+ for (i = 0, ln = headers.length; i < ln; i += 1) {
+ header = headers[i];
+ headerNames.push(header.name);
+ headerPrompts.push(header.prompt);
+ columnWidths[header.name] = header.width * px2pt;
+ }
+ } else {
+ headerNames = headers;
+ }
+
+ if (autoSize) {
+ // Create a matrix of columns e.g., {column_title: [row1_Record, row2_Record]}
+ func = function func(rec) {
+ return rec[header];
+ };
+
+ for (i = 0, ln = headerNames.length; i < ln; i += 1) {
+ header = headerNames[i];
+ columnMatrix[header] = data.map(func); // get header width
+
+ columnMinWidths.push(this.getTextDimensions(headerPrompts[i] || header, {
+ scaleFactor: 1
+ }).w);
+ column = columnMatrix[header]; // get cell widths
+
+ for (j = 0, cln = column.length; j < cln; j += 1) {
+ columnData = column[j];
+ columnMinWidths.push(this.getTextDimensions(columnData, {
+ scaleFactor: 1
+ }).w);
+ } // get final column width
+
+
+ columnWidths[header] = jsPDFAPI.arrayMax(columnMinWidths); //have to reset
+
+ columnMinWidths = [];
+ }
+ } // -- Construct the table
+
+
+ if (printHeaders) {
+ var lineHeight = this.calculateLineHeight(headerNames, columnWidths, headerPrompts.length ? headerPrompts : headerNames); // Construct the header row
+
+ for (i = 0, ln = headerNames.length; i < ln; i += 1) {
+ header = headerNames[i];
+ tableHeaderConfigs.push([x, y, columnWidths[header], lineHeight, String(headerPrompts.length ? headerPrompts[i] : header)]);
+ } // Store the table header config
+
+
+ this.setTableHeaderRow(tableHeaderConfigs); // Print the header for the start of the table
+
+ this.printHeaderRow(1, false);
+ } // Construct the data rows
+
+
+ for (i = 0, ln = data.length; i < ln; i += 1) {
+ var lineHeight;
+ model = data[i];
+ lineHeight = this.calculateLineHeight(headerNames, columnWidths, model);
+
+ for (j = 0, jln = headerNames.length; j < jln; j += 1) {
+ header = headerNames[j];
+ this.cell(x, y, columnWidths[header], lineHeight, model[header], i + 2, header.align);
+ }
+ }
+
+ this.lastCellPos = lastCellPos;
+ this.table_x = x;
+ this.table_y = y;
+ return this;
+ };
+ /**
+ * Calculate the height for containing the highest column
+ *
+ * @name calculateLineHeight
+ * @function
+ * @param {String[]} headerNames is the header, used as keys to the data
+ * @param {Integer[]} columnWidths is size of each column
+ * @param {Object[]} model is the line of data we want to calculate the height of
+ * @returns {number} lineHeight
+ */
+
+
+ jsPDFAPI.calculateLineHeight = function (headerNames, columnWidths, model) {
+ var header,
+ lineHeight = 0;
+
+ for (var j = 0; j < headerNames.length; j++) {
+ header = headerNames[j];
+ model[header] = this.splitTextToSize(String(model[header]), columnWidths[header] - padding);
+ var h = this.internal.getLineHeight() * model[header].length + padding;
+ if (h > lineHeight) lineHeight = h;
+ }
+
+ return lineHeight;
+ };
+ /**
+ * Store the config for outputting a table header
+ *
+ * @name setTableHeaderRow
+ * @function
+ * @param {Object[]} config
+ * An array of cell configs that would define a header row: Each config matches the config used by jsPDFAPI.cell
+ * except the ln parameter is excluded
+ */
+
+
+ jsPDFAPI.setTableHeaderRow = function (config) {
+ this.tableHeaderRow = config;
+ };
+ /**
+ * Output the store header row
+ *
+ * @name printHeaderRow
+ * @function
+ * @param {number} lineNumber The line number to output the header at
+ * @param {boolean} new_page
+ */
+
+
+ jsPDFAPI.printHeaderRow = function (lineNumber, new_page) {
+ if (!this.tableHeaderRow) {
+ throw "Property tableHeaderRow does not exist.";
+ }
+
+ var tableHeaderCell, tmpArray, i, ln;
+ this.printingHeaderRow = true;
+
+ if (headerFunction !== undefined) {
+ var position = headerFunction(this, pages);
+ setLastCellPosition(position[0], position[1], position[2], position[3], -1);
+ }
+
+ this.setFontStyle("bold");
+ var tempHeaderConf = [];
+
+ for (i = 0, ln = this.tableHeaderRow.length; i < ln; i += 1) {
+ this.setFillColor(200, 200, 200);
+ tableHeaderCell = this.tableHeaderRow[i];
+
+ if (new_page) {
+ this.margins.top = margin;
+ tableHeaderCell[1] = this.margins && this.margins.top || 0;
+ tempHeaderConf.push(tableHeaderCell);
+ }
+
+ tmpArray = [].concat(tableHeaderCell);
+ this.cell.apply(this, tmpArray.concat(lineNumber));
+ }
+
+ if (tempHeaderConf.length > 0) {
+ this.setTableHeaderRow(tempHeaderConf);
+ }
+
+ this.setFontStyle("normal");
+ this.printingHeaderRow = false;
+ };
+ })(jsPDF.API);
+
+ /**
+ * jsPDF Context2D PlugIn Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv
+ *
+ * Licensed under the MIT License. http://opensource.org/licenses/mit-license
+ */
+
+ /**
+ * TODO implement stroke opacity (refactor from fill() method )
+ * TODO transform angle and radii parameters
+ */
+
+ /**
+ * This plugin mimics the HTML5 Canvas's context2d.
+ *
+ * The goal is to provide a way for current canvas implementations to print directly to a PDF.
+ *
+ * @name context2d
+ * @module
+ */
+ (function (jsPDFAPI, globalObj) {
+
+ jsPDFAPI.events.push(["initialized", function () {
+ this.context2d.pdf = this;
+ this.context2d.internal.pdf = this;
+ this.context2d.ctx = new context();
+ this.context2d.ctxStack = [];
+ this.context2d.path = [];
+ }]);
+ jsPDFAPI.context2d = {
+ /**
+ * @name pageWrapXEnabled
+ * @type {boolean}
+ * @default false
+ */
+ pageWrapXEnabled: false,
+
+ /**
+ * @name pageWrapYEnabled
+ * @type {boolean}
+ * @default false
+ */
+ pageWrapYEnabled: false,
+
+ /**
+ * @name pageWrapX
+ * @type {number}
+ * @default 9999999
+ */
+ pageWrapX: 9999999,
+
+ /**
+ * @name pageWrapY
+ * @type {number}
+ * @default 9999999
+ */
+ pageWrapY: 9999999,
+
+ /**
+ * @name ctx
+ * @type {Object}
+ */
+ ctx: new context(),
+
+ /**
+ * @name f2
+ * @type {function}
+ * @ignore
+ */
+ f2: function f2(number) {
+ return number.toFixed(2);
+ },
+
+ /**
+ * Fills the current drawing (path)
+ *
+ * @name fill
+ * @function
+ * @description The fill() method fills the current drawing (path). The default color is black.
+ */
+ fill: function fill() {
+ //evenodd or nonzero (default)
+ var rule = "fill";
+
+ this._drawPaths(rule, false);
+ },
+
+ /**
+ * Actually draws the path you have defined
+ *
+ * @name stroke
+ * @function
+ * @description The stroke() method actually draws the path you have defined with all those moveTo() and lineTo() methods. The default color is black.
+ */
+ stroke: function stroke() {
+ var rule = "stroke";
+
+ this._drawPaths(rule, false);
+ },
+
+ /**
+ * Begins a path, or resets the current
+ *
+ * @name beginPath
+ * @function
+ * @description The beginPath() method begins a path, or resets the current path.
+ */
+ beginPath: function beginPath() {
+ this._resetPath();
+
+ this._beginPath();
+ },
+
+ /**
+ * Moves the path to the specified point in the canvas, without creating a line
+ *
+ * @name moveTo
+ * @function
+ * @param x {Number} The x-coordinate of where to move the path to
+ * @param y {Number} The y-coordinate of where to move the path to
+ */
+ moveTo: function moveTo(x, y) {
+ x = this._wrapX(x);
+ y = this._wrapY(y);
+
+ var xpt = this._matrix_map_point(this.ctx._transform, [x, y]);
+
+ x = xpt[0];
+ y = xpt[1];
+ var obj = {
+ type: "mt",
+ x: x,
+ y: y
+ };
+ this.path.push(obj);
+ },
+
+ /**
+ * Creates a path from the current point back to the starting point
+ *
+ * @name closePath
+ * @function
+ * @description The closePath() method creates a path from the current point back to the starting point.
+ */
+ closePath: function closePath() {
+ this._closePath();
+
+ var obj = {
+ type: "close"
+ };
+ this.path.push(obj);
+ },
+
+ /**
+ * Adds a new point and creates a line to that point from the last specified point in the canvas
+ *
+ * @name lineTo
+ * @function
+ * @param x The x-coordinate of where to create the line to
+ * @param y The y-coordinate of where to create the line to
+ * @description The lineTo() method adds a new point and creates a line TO that point FROM the last specified point in the canvas (this method does not draw the line).
+ */
+ lineTo: function lineTo(x, y) {
+ x = this._wrapX(x);
+ y = this._wrapY(y);
+
+ var xpt = this._matrix_map_point(this.ctx._transform, [x, y]);
+
+ x = xpt[0];
+ y = xpt[1];
+ var obj = {
+ type: "lt",
+ x: x,
+ y: y
+ };
+ this.path.push(obj);
+ },
+
+ /**
+ * Clips a region of any shape and size from the original canvas
+ *
+ * @name clip
+ * @function
+ * @description The clip() method clips a region of any shape and size from the original canvas.
+ */
+ clip: function clip() {
+ this._drawPaths(null, true);
+ },
+
+ /**
+ * Creates a cubic Bézier curve
+ *
+ * @name quadraticCurveTo
+ * @function
+ * @param cpx {Number} The x-coordinate of the Bézier control point
+ * @param cpy {Number} The y-coordinate of the Bézier control point
+ * @param x {Number} The x-coordinate of the ending point
+ * @param y {Number} The y-coordinate of the ending point
+ * @description The quadraticCurveTo() method adds a point to the current path by using the specified control points that represent a quadratic Bézier curve. A quadratic Bézier curve requires two points. The first point is a control point that is used in the quadratic Bézier calculation and the second point is the ending point for the curve. The starting point for the curve is the last point in the current path. If a path does not exist, use the beginPath() and moveTo() methods to define a starting point.
+ */
+ quadraticCurveTo: function quadraticCurveTo(cpx, cpy, x, y) {
+ var x1 = this._wrapX(cpx);
+
+ var y1 = this._wrapY(cpy);
+
+ x = this._wrapX(x);
+ y = this._wrapY(y);
+ var xpt;
+ xpt = this._matrix_map_point(this.ctx._transform, [x, y]);
+ x = xpt[0];
+ y = xpt[1];
+ xpt = this._matrix_map_point(this.ctx._transform, [x1, y1]);
+ x1 = xpt[0];
+ y1 = xpt[1];
+ var obj = {
+ type: "qct",
+ x1: x1,
+ y1: y1,
+ x: x,
+ y: y
+ };
+ this.path.push(obj);
+ },
+
+ /**
+ * Creates a cubic Bézier curve
+ *
+ * @name bezierCurveTo
+ * @function
+ * @param cp1x {Number} The x-coordinate of the first Bézier control point
+ * @param cp1y {Number} The y-coordinate of the first Bézier control point
+ * @param cp2x {Number} The x-coordinate of the second Bézier control point
+ * @param cp2y {Number} The y-coordinate of the second Bézier control point
+ * @param x {Number} The x-coordinate of the ending point
+ * @param y {Number} The y-coordinate of the ending point
+ * @description The bezierCurveTo() method adds a point to the current path by using the specified control points that represent a cubic Bézier curve. A cubic bezier curve requires three points. The first two points are control points that are used in the cubic Bézier calculation and the last point is the ending point for the curve. The starting point for the curve is the last point in the current path. If a path does not exist, use the beginPath() and moveTo() methods to define a starting point.
+ */
+ bezierCurveTo: function bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
+ var x1 = this._wrapX(cp1x);
+
+ var y1 = this._wrapY(cp1y);
+
+ var x2 = this._wrapX(cp2x);
+
+ var y2 = this._wrapY(cp2y);
+
+ x = this._wrapX(x);
+ y = this._wrapY(y);
+ var xpt;
+ xpt = this._matrix_map_point(this.ctx._transform, [x, y]);
+ x = xpt[0];
+ y = xpt[1];
+ xpt = this._matrix_map_point(this.ctx._transform, [x1, y1]);
+ x1 = xpt[0];
+ y1 = xpt[1];
+ xpt = this._matrix_map_point(this.ctx._transform, [x2, y2]);
+ x2 = xpt[0];
+ y2 = xpt[1];
+ var obj = {
+ type: "bct",
+ x1: x1,
+ y1: y1,
+ x2: x2,
+ y2: y2,
+ x: x,
+ y: y
+ };
+ this.path.push(obj);
+ },
+
+ /**
+ * Creates an arc/curve (used to create circles, or parts of circles)
+ *
+ * @name arc
+ * @function
+ * @param x {Number} The x-coordinate of the center of the circle
+ * @param y {Number} The y-coordinate of the center of the circle
+ * @param radius {Number} The radius of the circle
+ * @param startAngle {Number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle)
+ * @param endAngle {Number} The ending angle, in radians
+ * @param counterclockwise {Boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise.
+ * @description The arc() method creates an arc/curve (used to create circles, or parts of circles).
+ */
+ arc: function arc(x, y, radius, startAngle, endAngle, counterclockwise) {
+ x = this._wrapX(x);
+ y = this._wrapY(y);
+ counterclockwise = counterclockwise || false;
+
+ if (!this._matrix_is_identity(this.ctx._transform)) {
+ var xpt = this._matrix_map_point(this.ctx._transform, [x, y]);
+
+ x = xpt[0];
+ y = xpt[1];
+
+ var x_radPt0 = this._matrix_map_point(this.ctx._transform, [0, 0]);
+
+ var x_radPt = this._matrix_map_point(this.ctx._transform, [0, radius]);
+
+ radius = Math.sqrt(Math.pow(x_radPt[0] - x_radPt0[0], 2) + Math.pow(x_radPt[1] - x_radPt0[1], 2));
+ }
+
+ var obj = {
+ type: "arc",
+ x: x,
+ y: y,
+ radius: radius,
+ startAngle: startAngle,
+ endAngle: endAngle,
+ counterclockwise: counterclockwise
+ };
+ this.path.push(obj);
+ },
+
+ /**
+ * Creates an arc/curve between two tangents
+ *
+ * @name arcTo
+ * @function
+ * @param x1 {Number} The x-coordinate of the first tangent
+ * @param y1 {Number} The y-coordinate of the first tangent
+ * @param x2 {Number} The x-coordinate of the second tangent
+ * @param y2 {Number} The y-coordinate of the second tangent
+ * @param radius The radius of the
+ * @description The arcTo() method creates an arc/curve between two tangents on the canvas.
+ */
+ arcTo: function arcTo(x1, y1, x2, y2, radius) {//TODO needs to be implemented
+ },
+ //Rectangles
+
+ /**
+ * Creates a rectangle
+ *
+ * @name rect
+ * @function
+ * @param x {Number} The x-coordinate of the upper-left corner of the rectangle
+ * @param y {Number} The y-coordinate of the upper-left corner of the rectangle
+ * @param w {Number} The width of the rectangle, in pixels
+ * @param h {Number} The height of the rectangle, in pixels
+ * @description The rect() method creates a rectangle.
+ */
+ rect: function rect(x, y, w, h) {
+ this.moveTo(x, y);
+ this.lineTo(x + w, y);
+ this.lineTo(x + w, y + h);
+ this.lineTo(x, y + h);
+ this.lineTo(x, y);
+ this.lineTo(x + w, y);
+ this.lineTo(x, y);
+ },
+
+ /**
+ * Draws a "filled" rectangle
+ *
+ * @name fillRect
+ * @function
+ * @param x {Number} The x-coordinate of the upper-left corner of the rectangle
+ * @param y {Number} The y-coordinate of the upper-left corner of the rectangle
+ * @param w {Number} The width of the rectangle, in pixels
+ * @param h {Number} The height of the rectangle, in pixels
+ * @description The fillRect() method draws a "filled" rectangle. The default color of the fill is black.
+ */
+ fillRect: function fillRect(x, y, w, h) {
+ if (this._isFillTransparent()) {
+ return;
+ }
+
+ x = this._wrapX(x);
+ y = this._wrapY(y);
+
+ var xpt1 = this._matrix_map_point(this.ctx._transform, [x, y]);
+
+ var xpt2 = this._matrix_map_point(this.ctx._transform, [x + w, y]);
+
+ var xpt3 = this._matrix_map_point(this.ctx._transform, [x + w, y + h]);
+
+ var xpt4 = this._matrix_map_point(this.ctx._transform, [x, y + h]);
+
+ this.pdf.internal.out("q");
+ this.pdf.setLineCap("butt");
+ this.pdf.setLineJoin("miter");
+ var lines = [];
+ lines.push([xpt2[0] - xpt1[0], xpt2[1] - xpt1[1]]);
+ lines.push([xpt3[0] - xpt2[0], xpt3[1] - xpt2[1]]);
+ lines.push([xpt4[0] - xpt3[0], xpt4[1] - xpt3[1]]);
+ lines.push([xpt1[0] - xpt4[0], xpt1[1] - xpt4[1]]);
+ lines.push([xpt2[0] - xpt1[0], xpt2[1] - xpt1[1]]);
+ lines.push([xpt1[0] - xpt2[0], xpt1[1] - xpt2[1]]);
+ this.pdf.lines(lines, xpt1[0], xpt1[1], null, "F");
+ this.pdf.internal.out("Q");
+ },
+
+ /**
+ * Draws a rectangle (no fill)
+ *
+ * @name strokeRect
+ * @function
+ * @param x {Number} The x-coordinate of the upper-left corner of the rectangle
+ * @param y {Number} The y-coordinate of the upper-left corner of the rectangle
+ * @param w {Number} The width of the rectangle, in pixels
+ * @param h {Number} The height of the rectangle, in pixels
+ * @description The strokeRect() method draws a rectangle (no fill). The default color of the stroke is black.
+ */
+ strokeRect: function strokeRect(x, y, w, h) {
+ if (this._isStrokeTransparent()) {
+ return;
+ }
+
+ x = this._wrapX(x);
+ y = this._wrapY(y);
+
+ var xpt1 = this._matrix_map_point(this.ctx._transform, [x, y]);
+
+ var xpt2 = this._matrix_map_point(this.ctx._transform, [x + w, y]);
+
+ var xpt3 = this._matrix_map_point(this.ctx._transform, [x + w, y + h]);
+
+ var xpt4 = this._matrix_map_point(this.ctx._transform, [x, y + h]);
+
+ var lines = [];
+ lines.push([xpt2[0] - xpt1[0], xpt2[1] - xpt1[1]]);
+ lines.push([xpt3[0] - xpt2[0], xpt3[1] - xpt2[1]]);
+ lines.push([xpt4[0] - xpt3[0], xpt4[1] - xpt3[1]]);
+ lines.push([xpt1[0] - xpt4[0], xpt1[1] - xpt4[1]]);
+ lines.push([xpt2[0] - xpt1[0], xpt2[1] - xpt1[1]]);
+ lines.push([xpt1[0] - xpt2[0], xpt1[1] - xpt2[1]]);
+ this.pdf.lines(lines, xpt1[0], xpt1[1], null, "S");
+ },
+
+ /**
+ * Clears the specified pixels within a given rectangle
+ *
+ * @name clearRect
+ * @function
+ * @param x {Number} The x-coordinate of the upper-left corner of the rectangle
+ * @param y {Number} The y-coordinate of the upper-left corner of the rectangle
+ * @param w {Number} The width of the rectangle to clear, in pixels
+ * @param h {Number} The height of the rectangle to clear, in pixels
+ * @description We cannot clear PDF commands that were already written to PDF, so we use white instead.
+ * As a special case, read a special flag (ignoreClearRect) and do nothing if it is set.
+ * This results in all calls to clearRect() to do nothing, and keep the canvas transparent.
+ * This flag is stored in the save/restore context and is managed the same way as other drawing states.
+ *
+ */
+ clearRect: function clearRect(x, y, w, h) {
+ if (this.ctx.ignoreClearRect) {
+ return;
+ }
+
+ this.save();
+
+ this._setFillStyle("#ffffff");
+
+ this.fillRect(x, y, w, h);
+ this.restore();
+ },
+ //Other
+
+ /**
+ * Saves the state of the current context
+ *
+ * @name save
+ * @function
+ */
+ save: function save() {
+ this.pdf.internal.out("q");
+ this.ctx._fontSize = this.pdf.internal.getFontSize();
+ var ctx = new context();
+ ctx.copy(this.ctx);
+ this.ctxStack.push(this.ctx);
+ this.ctx = ctx;
+ },
+
+ /**
+ * Returns previously saved path state and attributes
+ *
+ * @name restore
+ * @function
+ */
+ restore: function restore() {
+ this.pdf.internal.out("Q");
+
+ if (this.ctxStack.length !== 0) {
+ this.ctx = this.ctxStack.pop();
+
+ this._setFillStyle(this.ctx.fillStyle);
+
+ this._setStrokeStyle(this.ctx.strokeStyle);
+
+ this._setFont(this.ctx.font);
+
+ this.pdf.setFontSize(this.ctx._fontSize);
+
+ this._setLineCap(this.ctx.lineCap);
+
+ this._setLineWidth(this.ctx.lineWidth);
+
+ this._setLineJoin(this.ctx.lineJoin);
+ }
+ },
+ // some stubs
+
+ /**
+ * @name createEvent
+ * @function
+ */
+ createEvent: function createEvent() {
+ console.log("createEvent not implemented (yet)");
+ },
+
+ /**
+ * @name toDataURL
+ * @function
+ */
+ toDataURL: function toDataURL() {
+ console.log("toDataUrl not implemented (yet)");
+ },
+ //helper functions
+ _beginPath: function _beginPath() {
+ this.path.push({
+ type: "begin"
+ });
+ },
+ _closePath: function _closePath() {
+ var pathBegin = {
+ x: 0,
+ y: 0
+ };
+ var i = 0;
+ var len = this.path.length;
+
+ for (i = this.path.length - 1; i !== -1; i--) {
+ if (this.path[i].type === "begin") {
+ if (_typeof(this.path[i + 1]) === "object" && typeof this.path[i + 1].x === "number") {
+ pathBegin = {
+ x: this.path[i + 1].x,
+ y: this.path[i + 1].y
+ };
+ var obj = {
+ type: "lt",
+ x: pathBegin.x,
+ y: pathBegin.y
+ };
+ this.path.push(obj);
+ break;
+ }
+ }
+ } //TODO Repeat second move to get lineJoins correct, maybe only when lineJoin active in _drawPaths
+
+
+ if (_typeof(this.path[i + 2]) === "object" && typeof this.path[i + 2].x === "number") {
+ this.path.push(JSON.parse(JSON.stringify(this.path[i + 2])));
+ }
+ },
+ _resetPath: function _resetPath() {
+ this.path = [];
+ },
+
+ /**
+ * Get the decimal values of r, g, b and a
+ *
+ * @name _getRGBA
+ * @function
+ * @private
+ * @ignore
+ */
+ _getRGBA: function _getRGBA(style) {
+ var r, g, b, a;
+
+ if (style.isCanvasGradient === true) {
+ style = style.getColor();
+ }
+
+ var rgbColor = new RGBColor(style);
+
+ if (!style) {
+ return {
+ r: 0,
+ g: 0,
+ b: 0,
+ a: 0,
+ style: style
+ };
+ }
+
+ if (this.internal.rxTransparent.test(style)) {
+ r = 0;
+ g = 0;
+ b = 0;
+ a = 0;
+ } else {
+ var m = this.internal.rxRgb.exec(style);
+
+ if (m != null) {
+ r = parseInt(m[1]);
+ g = parseInt(m[2]);
+ b = parseInt(m[3]);
+ a = 1;
+ } else {
+ m = this.internal.rxRgba.exec(style);
+
+ if (m != null) {
+ r = parseInt(m[1]);
+ g = parseInt(m[2]);
+ b = parseInt(m[3]);
+ a = parseFloat(m[4]);
+ } else {
+ a = 1;
+
+ if (style.charAt(0) != "#") {
+ if (rgbColor.ok) {
+ style = rgbColor.toHex();
+ } else {
+ style = "#000000";
+ }
+ }
+
+ if (style.length === 4) {
+ r = style.substring(1, 2);
+ r += r;
+ g = style.substring(2, 3);
+ g += g;
+ b = style.substring(3, 4);
+ b += b;
+ } else {
+ r = style.substring(1, 3);
+ g = style.substring(3, 5);
+ b = style.substring(5, 7);
+ }
+
+ r = parseInt(r, 16);
+ g = parseInt(g, 16);
+ b = parseInt(b, 16);
+ }
+ }
+ }
+
+ return {
+ r: r,
+ g: g,
+ b: b,
+ a: a,
+ style: style
+ };
+ },
+
+ /**
+ * @name _isFilllTransparent
+ * @function
+ * @private
+ * @ignore
+ * @returns {Boolean}
+ */
+ _isFillTransparent: function _isFillTransparent() {
+ return this.ctx._isFillTransparent || this.globalAlpha == 0;
+ },
+
+ /**
+ * @name _isStrokeTransparent
+ * @function
+ * @private
+ * @ignore
+ * @returns {Boolean}
+ */
+ _isStrokeTransparent: function _isStrokeTransparent() {
+ return this.ctx._isStrokeTransparent || this.globalAlpha == 0;
+ },
+
+ /**
+ * Sets the color, gradient, or pattern used to fill the drawing
+ *
+ * @name _setFillStyle
+ * @function
+ * @private
+ * @ignore
+ */
+ _setFillStyle: function _setFillStyle(style) {
+ var rgba;
+ rgba = this._getRGBA(style);
+ this.ctx.fillStyle = style;
+ this.ctx._isFillTransparent = rgba.a === 0;
+ this.ctx._fillOpacity = rgba.a;
+ this.pdf.setFillColor(rgba.r, rgba.g, rgba.b, {
+ a: rgba.a
+ });
+ this.pdf.setTextColor(rgba.r, rgba.g, rgba.b, {
+ a: rgba.a
+ });
+ },
+
+ /**
+ * Sets the color, gradient, or pattern used for strokes
+ *
+ * @name _setStrokeStyle
+ * @function
+ * @private
+ * @ignore
+ */
+ _setStrokeStyle: function _setStrokeStyle(style) {
+ var rgba = this._getRGBA(style);
+
+ this.ctx.strokeStyle = rgba.style;
+ this.ctx._isStrokeTransparent = rgba.a === 0;
+ this.ctx._strokeOpacity = rgba.a; //TODO jsPDF to handle rgba
+
+ if (rgba.a === 0) {
+ this.pdf.setDrawColor(255, 255, 255);
+ } else if (rgba.a === 1) {
+ this.pdf.setDrawColor(rgba.r, rgba.g, rgba.b);
+ } else {
+ //this.pdf.setDrawColor(rgba.r, rgba.g, rgba.b, {a: rgba.a});
+ this.pdf.setDrawColor(rgba.r, rgba.g, rgba.b);
+ }
+ },
+
+ /**
+ * Sets font properties for text content
+ *
+ * @name _setFont
+ * @function
+ * @param {String} The font property uses the same syntax as the CSS font property.
+ * @private
+ * @ignore
+ */
+ _setFont: function _setFont(font) {
+ this.ctx.font = font;
+ var rx, m; //source: https://stackoverflow.com/a/10136041
+
+ rx = /^\s*(?=(?:(?:[-a-z]+\s*){0,2}(italic|oblique))?)(?=(?:(?:[-a-z]+\s*){0,2}(small-caps))?)(?=(?:(?:[-a-z]+\s*){0,2}(bold(?:er)?|lighter|[1-9]00))?)(?:(?:normal|\1|\2|\3)\s*){0,3}((?:xx?-)?(?:small|large)|medium|smaller|larger|[.\d]+(?:\%|in|[cem]m|ex|p[ctx]))(?:\s*\/\s*(normal|[.\d]+(?:\%|in|[cem]m|ex|p[ctx])))?\s*([-,\"\'\sa-z]+?)\s*$/i;
+ m = rx.exec(font);
+
+ if (m != null) {
+ var fontStyle = m[1];
+ var fontVariant = m[2];
+ var fontWeight = m[3];
+ var fontSize = m[4];
+ var fontSizeUnit = m[5];
+ var fontFamily = m[6];
+ } else {
+ return;
+ }
+
+ if ("px" === fontSizeUnit) {
+ fontSize = Math.floor(parseFloat(fontSize)); // fontSize = fontSize * 1.25;
+ } else if ("em" === fontSizeUnit) {
+ fontSize = Math.floor(parseFloat(fontSize) * this.pdf.getFontSize());
+ } else {
+ fontSize = Math.floor(parseFloat(fontSize));
+ }
+
+ this.pdf.setFontSize(fontSize);
+ var style = "";
+
+ if (fontWeight === "bold" || parseInt(fontWeight, 10) >= 700 || fontStyle === "bold") {
+ style = "bold";
+ }
+
+ if (fontStyle === "italic") {
+ style += "italic";
+ }
+
+ if (style.length === 0) {
+ style = "normal";
+ }
+
+ var jsPdfFontName = "";
+ var parts = fontFamily.toLowerCase().replace(/"|'/g, "").split(/\s*,\s*/);
+ var fallbackFonts = {
+ arial: "Helvetica",
+ verdana: "Helvetica",
+ helvetica: "Helvetica",
+ "sans-serif": "Helvetica",
+ fixed: "Courier",
+ monospace: "Courier",
+ terminal: "Courier",
+ courier: "Courier",
+ times: "Times",
+ cursive: "Times",
+ fantasy: "Times",
+ serif: "Times"
+ };
+
+ for (var i = 0; i < parts.length; i++) {
+ if (this.pdf.internal.getFont(parts[i], style, {
+ noFallback: true,
+ disableWarning: true
+ }) !== undefined) {
+ jsPdfFontName = parts[i];
+ break;
+ } else if (style === "bolditalic" && this.pdf.internal.getFont(parts[i], "bold", {
+ noFallback: true,
+ disableWarning: true
+ }) !== undefined) {
+ jsPdfFontName = parts[i];
+ style = "bold";
+ } else if (this.pdf.internal.getFont(parts[i], "normal", {
+ noFallback: true,
+ disableWarning: true
+ }) !== undefined) {
+ jsPdfFontName = parts[i];
+ style = "normal";
+ break;
+ }
+ }
+
+ if (jsPdfFontName === "") {
+ for (var i = 0; i < parts.length; i++) {
+ if (fallbackFonts[parts[i]]) {
+ jsPdfFontName = fallbackFonts[parts[i]];
+ break;
+ }
+ }
+ }
+
+ jsPdfFontName = jsPdfFontName === "" ? "Times" : jsPdfFontName;
+ this.pdf.setFont(jsPdfFontName, style);
+ },
+
+ /**
+ * @name _setTextBaseline
+ * @function
+ * @private
+ * @ignore
+ */
+ _setTextBaseline: function _setTextBaseline(baseline) {
+ this.ctx.textBaseline = baseline;
+ },
+
+ /**
+ * @name _getTextBaseline
+ * @function
+ * @private
+ * @ignore
+ * @returns {Number}
+ */
+ _getTextBaseline: function _getTextBaseline() {
+ return this.ctx.textBaseline;
+ },
+
+ /**
+ * @name _setTextAlign
+ * @function
+ * @private
+ * @ignore
+ */
+ _setTextAlign: function _setTextAlign(canvasAlign) {
+ switch (canvasAlign) {
+ case "right":
+ case "end":
+ this.ctx.textAlign = "right";
+ break;
+
+ case "center":
+ this.ctx.textAlign = "center";
+ break;
+
+ case "left":
+ case "start":
+ default:
+ this.ctx.textAlign = "left";
+ break;
+ }
+ },
+
+ /**
+ * @name _getTextAlign
+ * @function
+ * @private
+ * @ignore
+ * @returns {String}
+ */
+ _getTextAlign: function _getTextAlign() {
+ return this.ctx.textAlign;
+ },
+
+ /**
+ * Draws "filled" text on the canvas
+ *
+ * @name fillText
+ * @function
+ * @param text {String} Specifies the text that will be written on the canvas
+ * @param x {Number} The x coordinate where to start painting the text (relative to the canvas)
+ * @param y {Number} The y coordinate where to start painting the text (relative to the canvas)
+ * @param maxWidth {Number} Optional. The maximum allowed width of the text, in pixels
+ * @description The fillText() method draws filled text on the canvas. The default color of the text is black.
+ */
+ fillText: function fillText(text, x, y, maxWidth) {
+ if (this._isFillTransparent()) {
+ return;
+ }
+
+ x = this._wrapX(x);
+ y = this._wrapY(y);
+
+ var xpt = this._matrix_map_point(this.ctx._transform, [x, y]);
+
+ x = xpt[0];
+ y = xpt[1];
+
+ var rads = this._matrix_rotation(this.ctx._transform);
+
+ var degs = rads * 57.2958; // We only use X axis as scale hint
+
+ var scale = 1;
+
+ try {
+ scale = this._matrix_decompose(this._getTransform()).scale[0];
+ } catch (e) {
+ console.warn(e);
+ } // In some cases the transform was very small (5.715760606202283e-17). Most likely a canvg rounding error.
+
+
+ if (scale < 0.01) {
+ this.pdf.text(text, x, this._getBaseline(y), {
+ angle: degs,
+ align: this.textAlign
+ });
+ } else {
+ var oldSize = this.pdf.internal.getFontSize();
+ this.pdf.setFontSize(oldSize * scale);
+ this.pdf.text(text, x, this._getBaseline(y), {
+ angle: degs,
+ align: this.textAlign
+ });
+ this.pdf.setFontSize(oldSize);
+ }
+ },
+
+ /**
+ * Draws text on the canvas (no fill)
+ *
+ * @name strokeText
+ * @function
+ * @param text {String} Specifies the text that will be written on the canvas
+ * @param x {Number} The x coordinate where to start painting the text (relative to the canvas)
+ * @param y {Number} The y coordinate where to start painting the text (relative to the canvas)
+ * @param maxWidth {Number} Optional. The maximum allowed width of the text, in pixels
+ * @description The strokeText() method draws text (with no fill) on the canvas. The default color of the text is black.
+ */
+ strokeText: function strokeText(text, x, y, maxWidth) {
+ if (this._isStrokeTransparent()) {
+ return;
+ }
+
+ x = this._wrapX(x);
+ y = this._wrapY(y);
+
+ var xpt = this._matrix_map_point(this.ctx._transform, [x, y]);
+
+ x = xpt[0];
+ y = xpt[1];
+
+ var rads = this._matrix_rotation(this.ctx._transform);
+
+ var degs = rads * 57.2958;
+ var scale = 1; // We only use the X axis as scale hint
+
+ try {
+ scale = this._matrix_decompose(this._getTransform()).scale[0];
+ } catch (e) {
+ console.warn(e);
+ }
+
+ if (scale < 0.01) {
+ this.pdf.text(text, x, this._getBaseline(y), {
+ angle: degs,
+ align: this.textAlign,
+ renderingMode: "stroke"
+ });
+ } else {
+ var oldSize = this.pdf.internal.getFontSize();
+ this.pdf.setFontSize(oldSize * scale);
+ this.pdf.text(text, x, this._getBaseline(y), {
+ angle: degs,
+ align: this.textAlign,
+ renderingMode: "stroke"
+ });
+ this.pdf.setFontSize(oldSize);
+ }
+ },
+
+ /**
+ * Returns an object that contains the width of the specified text
+ *
+ * @name measureText
+ * @function
+ * @param text {String} The text to be measured
+ * @description The measureText() method returns an object that contains the width of the specified text, in pixels.
+ * @returns {Number}
+ */
+ measureText: function measureText(text) {
+ var pdf = this.pdf;
+ return {
+ getWidth: function getWidth() {
+ var fontSize = pdf.internal.getFontSize();
+ var txtWidth = pdf.getStringUnitWidth(text) * fontSize / pdf.internal.scaleFactor; // Convert points to pixels
+ //TODO Use scaleFactor?
+
+ txtWidth *= 1.3333;
+ return txtWidth;
+ },
+
+ get width() {
+ return this.getWidth(text);
+ }
+
+ };
+ },
+ //Line Styles
+
+ /**
+ *
+ * @name _setLineWidth
+ * @function
+ * @private
+ * @ignore
+ */
+ _setLineWidth: function _setLineWidth(width) {
+ this.ctx.lineWidth = width;
+ this.pdf.setLineWidth(width);
+ },
+
+ /**
+ * @name _setLineCap
+ * @function
+ * @private
+ * @ignore
+ */
+ _setLineCap: function _setLineCap(style) {
+ this.ctx.lineCap = style;
+ this.pdf.setLineCap(style);
+ },
+
+ /**
+ *
+ * @name _setLineJoin
+ * @function
+ * @private
+ * @ignore
+ */
+ _setLineJoin: function _setLineJoin(style) {
+ this.ctx.lineJoin = style;
+ this.pdf.setLineJoin(style);
+ },
+
+ /**
+ *
+ * @name _getLineJoin
+ * @function
+ * @private
+ * @ignore
+ */
+ _getLineJoin: function _getLineJoin() {
+ return this.ctx.lineJoin;
+ },
+
+ /**
+ *
+ * @name _wrapX
+ * @function
+ * @private
+ * @ignore
+ */
+ _wrapX: function _wrapX(x) {
+ if (this.pageWrapXEnabled) {
+ return x % this.pageWrapX;
+ } else {
+ return x;
+ }
+ },
+
+ /**
+ * @name _wrapY
+ * @function
+ * @private
+ * @ignore
+ */
+ _wrapY: function _wrapY(y) {
+ if (this.pageWrapYEnabled) {
+ this._gotoPage(this._page(y));
+
+ return (y - this.lastBreak) % this.pageWrapY;
+ } else {
+ return y;
+ }
+ },
+ //Transformations
+
+ /**
+ * Scales the current drawing bigger or smaller
+ *
+ * @name scale
+ * @function
+ * @param scalewidth {Number} Scales the width of the current drawing (1=100%, 0.5=50%, 2=200%, etc.)
+ * @param scaleheight {Number} Scales the height of the current drawing (1=100%, 0.5=50%, 2=200%, etc.)
+ * @description The scale() method scales the current drawing, bigger or smaller.
+ */
+ scale: function scale(scalewidth, scaleheight) {
+ var matrix = [scalewidth, 0.0, 0.0, scaleheight, 0.0, 0.0];
+ this.ctx._transform = this._matrix_multiply(this.ctx._transform, matrix);
+ },
+
+ /**
+ * Rotates the current drawing
+ *
+ * @name rotate
+ * @function
+ * @param angle {Number} The rotation angle, in radians.
+ * @description To calculate from degrees to radians: degrees*Math.PI/180.
+ * Example: to rotate 5 degrees, specify the following: 5*Math.PI/180
+ */
+ rotate: function rotate(angle) {
+ var matrix = [Math.cos(angle), Math.sin(angle), -Math.sin(angle), Math.cos(angle), 0.0, 0.0];
+ this.ctx._transform = this._matrix_multiply(this.ctx._transform, matrix);
+ },
+
+ /**
+ * Remaps the (0,0) position on the canvas
+ *
+ * @name translate
+ * @function
+ * @param x {Number} The value to add to horizontal (x) coordinates
+ * @param y {Number} The value to add to vertical (y) coordinates
+ * @description The translate() method remaps the (0,0) position on the canvas.
+ */
+ translate: function translate(x, y) {
+ var matrix = [1.0, 0.0, 0.0, 1.0, x, y];
+ this.ctx._transform = this._matrix_multiply(this.ctx._transform, matrix);
+ },
+
+ /**
+ * Replaces the current transformation matrix for the drawing
*
- * @param {String|Number} style A string or number identifying the type of line cap
+ * @name transform
* @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name setLineCap
+ * @param a {Number} Horizontal scaling
+ * @param b {Number} Horizontal skewing
+ * @param c {Number} Vertical skewing
+ * @param d {Number} Vertical scaling
+ * @param e {Number} Horizontal moving
+ * @param f {Number} Vertical moving
+ * @description Each object on the canvas has a current transformation matrix. The transform() method replaces the current transformation matrix. It multiplies the current transformation matrix with the matrix described by: a c e b d f 0 0 1 In other words, the transform() method lets you scale, rotate, move, and skew the current context.
*/
- API.setLineCap = function (style) {
- var id = this.CapJoinStyles[style];
- if (id === undefined) {
- throw new Error("Line cap style of '" + style + "' is not recognized. See or extend .CapJoinStyles property for valid styles");
- }
- lineCapID = id;
- out(id + ' J');
-
- return this;
- };
-
- /**
- * Sets the line join styles
- * See {jsPDF.CapJoinStyles} for variants
+ transform: function transform(a, b, c, d, e, f) {
+ this.ctx._transform = this._matrix_multiply(this.ctx._transform, [a, b, c, d, e, f]);
+ },
+
+ /**
+ * Resets the current transform to the identity matrix. Then runs transform()
*
- * @param {String|Number} style A string or number identifying the type of line join
+ * @name setTransform
* @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name setLineJoin
+ * @param a {Number} Horizontal scaling
+ * @param b {Number} Horizontal skewing
+ * @param c {Number} Vertical skewing
+ * @param d {Number} Vertical scaling
+ * @param e {Number} Horizontal moving
+ * @param f {Number} Vertical moving
+ * @description Each object on the canvas has a current transformation matrix. The setTransform() method resets the current transform to the identity matrix, and then runs transform() with the same arguments. In other words, the setTransform() method lets you scale, rotate, move, and skew the current context.
*/
- API.setLineJoin = function (style) {
- var id = this.CapJoinStyles[style];
- if (id === undefined) {
- throw new Error("Line join style of '" + style + "' is not recognized. See or extend .CapJoinStyles property for valid styles");
- }
- lineJoinID = id;
- out(id + ' j');
-
- return this;
- };
-
- /**
- * Sets the miter limit.
- * @param {number} miterLimit
+ setTransform: function setTransform(a, b, c, d, e, f) {
+ this.ctx._transform = [a, b, c, d, e, f];
+ },
+
+ /**
+ * @name _getTransform
* @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name setMiterLimit
+ * @private
+ * @ignore
+ */
+ _getTransform: function _getTransform() {
+ return this.ctx._transform;
+ },
+ lastBreak: 0,
+ // Y Position of page breaks.
+ pageBreaks: [],
+
+ /**
+ * Should only be used if pageWrapYEnabled is true
+ *
+ * @name _page
+ * @function
+ * @private
+ * @ignore
+ * @returns One-based Page Number
+ */
+ _page: function _page(y) {
+ if (this.pageWrapYEnabled) {
+ this.lastBreak = 0;
+ var manualBreaks = 0;
+ var autoBreaks = 0;
+
+ for (var i = 0; i < this.pageBreaks.length; i++) {
+ if (y >= this.pageBreaks[i]) {
+ manualBreaks++;
+
+ if (this.lastBreak === 0) {
+ autoBreaks++;
+ }
+
+ var spaceBetweenLastBreak = this.pageBreaks[i] - this.lastBreak;
+ this.lastBreak = this.pageBreaks[i];
+ var pagesSinceLastBreak = Math.floor(spaceBetweenLastBreak / this.pageWrapY);
+ autoBreaks += pagesSinceLastBreak;
+ }
+ }
+
+ if (this.lastBreak === 0) {
+ var pagesSinceLastBreak = Math.floor(y / this.pageWrapY) + 1;
+ autoBreaks += pagesSinceLastBreak;
+ }
+
+ return autoBreaks + manualBreaks;
+ } else {
+ return this.pdf.internal.getCurrentPageInfo().pageNumber;
+ }
+ },
+ _gotoPage: function _gotoPage(pageOneBased) {// This is a stub to be overriden if needed
+ },
+
+ /**
+ * Draws an image, canvas, or video onto the canvas
+ *
+ * @function
+ * @param img {} Specifies the image, canvas, or video element to use
+ * @param sx {Number} Optional. The x coordinate where to start clipping
+ * @param sy {Number} Optional. The y coordinate where to start clipping
+ * @param swidth {Number} Optional. The width of the clipped image
+ * @param sheight {Number} Optional. The height of the clipped image
+ * @param x {Number} The x coordinate where to place the image on the canvas
+ * @param y {Number} The y coordinate where to place the image on the canvas
+ * @param width {Number} Optional. The width of the image to use (stretch or reduce the image)
+ * @param height {Number} Optional. The height of the image to use (stretch or reduce the image)
+ *
+ */
+ drawImage: function drawImage(img, x, y, w, h, x2, y2, w2, h2) {
+ if (x2 !== undefined) {
+ x = x2;
+ y = y2;
+ w = w2;
+ h = h2;
+ }
+
+ x = this._wrapX(x);
+ y = this._wrapY(y);
+
+ var xRect = this._matrix_map_rect(this.ctx._transform, {
+ x: x,
+ y: y,
+ w: w,
+ h: h
+ });
+
+ var xRect2 = this._matrix_map_rect(this.ctx._transform, {
+ x: x2,
+ y: y2,
+ w: w2,
+ h: h2
+ }); // TODO implement source clipping and image scaling
+
+
+ var format;
+ var rx = /data:image\/(\w+).*/i;
+ var m = rx.exec(img);
+
+ if (m != null) {
+ format = m[1];
+ } else {
+ // format = "jpeg";
+ format = "png";
+ }
+
+ this.pdf.addImage(img, format, xRect.x, xRect.y, xRect.w, xRect.h);
+ },
+
+ /**
+ * Multiply the first matrix by the second
+ *
+ * @name matrix_multiply
+ * @function
+ * @param m1
+ * @param m2
+ * @returns {Array}
+ * @private
+ * @ignore
+ */
+ _matrix_multiply: function _matrix_multiply(m2, m1) {
+ var sx = m1[0];
+ var shy = m1[1];
+ var shx = m1[2];
+ var sy = m1[3];
+ var tx = m1[4];
+ var ty = m1[5];
+ var t0 = sx * m2[0] + shy * m2[2];
+ var t2 = shx * m2[0] + sy * m2[2];
+ var t4 = tx * m2[0] + ty * m2[2] + m2[4];
+ shy = sx * m2[1] + shy * m2[3];
+ sy = shx * m2[1] + sy * m2[3];
+ ty = tx * m2[1] + ty * m2[3] + m2[5];
+ sx = t0;
+ shx = t2;
+ tx = t4;
+ return [sx, shy, shx, sy, tx, ty];
+ },
+
+ /**
+ * @name _matrix_rotation
+ * @function
+ * @private
+ * @ignore
+ */
+ _matrix_rotation: function _matrix_rotation(m) {
+ return Math.atan2(m[2], m[0]);
+ },
+
+ /**
+ * @name _matrix_decompose
+ * @function
+ * @private
+ * @ignore
+ */
+ _matrix_decompose: function _matrix_decompose(matrix) {
+ var a = matrix[0];
+ var b = matrix[1];
+ var c = matrix[2];
+ var d = matrix[3];
+ var scaleX = Math.sqrt(a * a + b * b);
+ a /= scaleX;
+ b /= scaleX;
+ var shear = a * c + b * d;
+ c -= a * shear;
+ d -= b * shear;
+ var scaleY = Math.sqrt(c * c + d * d);
+ c /= scaleY;
+ d /= scaleY;
+ shear /= scaleY;
+
+ if (a * d < b * c) {
+ a = -a;
+ b = -b;
+ shear = -shear;
+ scaleX = -scaleX;
+ }
+
+ return {
+ scale: [scaleX, 0, 0, scaleY, 0, 0],
+ translate: [1, 0, 0, 1, matrix[4], matrix[5]],
+ rotate: [a, b, -b, a, 0, 0],
+ skew: [1, 0, shear, 1, 0, 0]
+ };
+ },
+
+ /**
+ * @name _matrix_map_point
+ * @function
+ * @private
+ * @ignore
+ */
+ _matrix_map_point: function _matrix_map_point(m1, pt) {
+ var sx = m1[0];
+ var shy = m1[1];
+ var shx = m1[2];
+ var sy = m1[3];
+ var tx = m1[4];
+ var ty = m1[5];
+ var px = pt[0];
+ var py = pt[1];
+ var x = px * sx + py * shx + tx;
+ var y = px * shy + py * sy + ty;
+ return [x, y];
+ },
+
+ /**
+ * @name _matrix_map_point_obj
+ * @function
+ * @private
+ * @ignore
+ */
+ _matrix_map_point_obj: function _matrix_map_point_obj(m1, pt) {
+ var xpt = this._matrix_map_point(m1, [pt.x, pt.y]);
+
+ return {
+ x: xpt[0],
+ y: xpt[1]
+ };
+ },
+
+ /**
+ * @name _matrix_map_rect
+ * @function
+ * @private
+ * @ignore
+ */
+ _matrix_map_rect: function _matrix_map_rect(m1, rect) {
+ var p1 = this._matrix_map_point(m1, [rect.x, rect.y]);
+
+ var p2 = this._matrix_map_point(m1, [rect.x + rect.w, rect.y + rect.h]);
+
+ return {
+ x: p1[0],
+ y: p1[1],
+ w: p2[0] - p1[0],
+ h: p2[1] - p1[1]
+ };
+ },
+
+ /**
+ * @name _matrix_is_identity
+ * @function
+ * @private
+ * @ignore
+ */
+ _matrix_is_identity: function _matrix_is_identity(m1) {
+ if (m1[0] != 1) {
+ return false;
+ }
+
+ if (m1[1] != 0) {
+ return false;
+ }
+
+ if (m1[2] != 0) {
+ return false;
+ }
+
+ if (m1[3] != 1) {
+ return false;
+ }
+
+ if (m1[4] != 0) {
+ return false;
+ }
+
+ if (m1[5] != 0) {
+ return false;
+ }
+
+ return true;
+ },
+
+ /**
+ * Processes the paths
+ *
+ * @function
+ * @param rule {String}
+ * @param isClip {Boolean}
+ * @private
+ * @ignore
+ */
+ _drawPaths: function _drawPaths(rule, isClip) {
+ var isStroke = rule === "stroke";
+ var isFill = !isStroke;
+
+ if (isStroke && !isClip && this._isStrokeTransparent()) {
+ return;
+ }
+
+ if (isFill && !isClip && this._isFillTransparent()) {
+ return;
+ }
+
+ var v2Support = typeof this.pdf.internal.newObject2 === "function";
+ var lines;
+
+ if (globalObj.outIntercept) {
+ lines = globalObj.outIntercept.type === "group" ? globalObj.outIntercept.stream : globalObj.outIntercept;
+ } else {
+ lines = this.internal.getCurrentPage();
+ }
+
+ var moves = [];
+ var outInterceptOld = globalObj.outIntercept;
+ /*
+ if (v2Support) {
+ // Blend and Mask
+ switch (this.ctx.globalCompositeOperation) {
+ case 'normal':
+ case 'source-over':
+ break;
+ case 'destination-in':
+ case 'destination-out':
+ //TODO this need to be added to the current group or page
+ // define a mask stream
+ var obj = this.pdf.internal.newStreamObject(); // define a mask state
+ var obj2 = this.pdf.internal.newObject2();
+ obj2.push('<>'); // /S /Luminosity will need to define color space
+ obj2.push('>>'); // add mask to page resources
+ var gsName = 'MASK' + obj2.objId;
+ this.pdf.internal.addGraphicsState(gsName, obj2.objId);
+ var instruction = '/' + gsName + ' gs'; // add mask to page, group, or stream
+ lines.splice(0, 0, 'q');
+ lines.splice(1, 0, instruction);
+ lines.push('Q');
+ globalObj.outIntercept = obj;
+ break;
+ default:
+ var dictionaryEntry = '/' + this.pdf.internal.blendModeMap[this.ctx.globalCompositeOperation.toUpperCase()];
+ if (dictionaryEntry) {
+ this.pdf.internal.out(dictionaryEntry + ' gs');
+ }
+ break;
+ }
+ }
+ */
+
+ var alpha = this.ctx.globalAlpha;
+
+ if (this.ctx._fillOpacity < 1) {
+ // TODO combine this with global opacity
+ alpha = this.ctx._fillOpacity;
+ } //TODO check for an opacity graphics state that was already created
+ //TODO do not set opacity if current value is already active
+
+ /*
+ if (v2Support) {
+ var objOpac = this.pdf.internal.newObject2();
+ objOpac.push('<>');
+ var gsName = 'GS_O_' + objOpac.objId;
+ this.pdf.internal.addGraphicsState(gsName, objOpac.objId);
+ this.pdf.internal.out('/' + gsName + ' gs');
+ }
+ */
+
+
+ var xPath = this.path;
+
+ for (var i = 0; i < xPath.length; i++) {
+ var pt = xPath[i];
+
+ switch (pt.type) {
+ case "begin":
+ moves.push({
+ begin: true
+ });
+ break;
+
+ case "close":
+ moves.push({
+ close: true
+ });
+ break;
+
+ case "mt":
+ moves.push({
+ start: pt,
+ deltas: [],
+ abs: []
+ });
+ break;
+
+ case "lt":
+ var delta = [pt.x - xPath[i - 1].x, pt.y - xPath[i - 1].y];
+ var iii = moves.length;
+
+ if (iii > 0) {
+ for (iii; iii > 0; iii--) {
+ if (moves[iii - 1].close !== true && moves[iii - 1].begin !== true) {
+ moves[iii - 1].deltas.push(delta);
+ moves[iii - 1].abs.push(pt);
+ break;
+ }
+ }
+ }
+
+ break;
+
+ case "bct":
+ var delta = [pt.x1 - xPath[i - 1].x, pt.y1 - xPath[i - 1].y, pt.x2 - xPath[i - 1].x, pt.y2 - xPath[i - 1].y, pt.x - xPath[i - 1].x, pt.y - xPath[i - 1].y];
+ moves[moves.length - 1].deltas.push(delta);
+ break;
+
+ case "qct":
+ // convert to bezier
+ var x1 = xPath[i - 1].x + 2.0 / 3.0 * (pt.x1 - xPath[i - 1].x);
+ var y1 = xPath[i - 1].y + 2.0 / 3.0 * (pt.y1 - xPath[i - 1].y);
+ var x2 = pt.x + 2.0 / 3.0 * (pt.x1 - pt.x);
+ var y2 = pt.y + 2.0 / 3.0 * (pt.y1 - pt.y);
+ var x3 = pt.x;
+ var y3 = pt.y;
+ var delta = [x1 - xPath[i - 1].x, y1 - xPath[i - 1].y, x2 - xPath[i - 1].x, y2 - xPath[i - 1].y, x3 - xPath[i - 1].x, y3 - xPath[i - 1].y];
+ moves[moves.length - 1].deltas.push(delta);
+ break;
+
+ case "arc":
+ moves.push({
+ deltas: [],
+ abs: []
+ });
+ moves[moves.length - 1].arc = true;
+
+ if (Array.isArray(moves[moves.length - 1].abs)) {
+ moves[moves.length - 1].abs.push(pt);
+ }
+
+ break;
+ }
+ }
+
+ var style;
+
+ if (!isClip) {
+ if (isStroke) {
+ style = "S";
+ } else {
+ style = "f";
+ }
+ } else {
+ style = null;
+ }
+
+ for (var i = 0; i < moves.length; i++) {
+ if (moves[i].begin) ;
+
+ if (moves[i].arc) {
+ if (moves[i].start) {
+ this.internal.move2(this, moves[i].start.x, moves[i].start.y);
+ }
+
+ var arcs = moves[i].abs;
+
+ for (var ii = 0; ii < arcs.length; ii++) {
+ var arc = arcs[ii]; //TODO lines deltas were getting in here
+
+ if (typeof arc.startAngle !== "undefined") {
+ var start = arc.startAngle * 360 / (2 * Math.PI);
+ var end = arc.endAngle * 360 / (2 * Math.PI);
+ var x = arc.x;
+ var y = arc.y;
+ this.internal.arc2(this, x, y, arc.radius, start, end, arc.counterclockwise, style, isClip);
+ } else {
+ this.internal.line2(c2d, arc.x, arc.y);
+ }
+ }
+ }
+
+ if (!moves[i].arc) {
+ if (moves[i].close !== true && moves[i].begin !== true) {
+ var x = moves[i].start.x;
+ var y = moves[i].start.y;
+ this.pdf.lines(moves[i].deltas, x, y, null, null);
+ }
+ }
+
+ if (moves[i].close) ;
+ }
+
+ if (style) {
+ this.pdf.internal.out(style);
+ }
+
+ if (isClip) {
+ this.pdf.clip();
+ }
+
+ globalObj.outIntercept = outInterceptOld; // if (this.ctx._clip_path.length > 0) {
+ // lines.push('Q');
+ // }
+ },
+
+ /*
+ _pushMask: function () {
+ var v2Support = typeof this.pdf.internal.newObject2 === 'function';
+ if (!v2Support) {
+ console.log('jsPDF v2 not enabled')
+ return;
+ }
+ // define a mask stream
+ var obj = this.pdf.internal.newStreamObject();
+ // define a mask state
+ var obj2 = this.pdf.internal.newObject2();
+ obj2.push('<>'); // /S /Luminosity will need to define color space
+ obj2.push('>>');
+ // add mask to page resources
+ var gsName = 'MASK' + obj2.objId;
+ this.pdf.internal.addGraphicsState(gsName, obj2.objId);
+ var instruction = '/' + gsName + ' gs';
+ this.pdf.internal.out(instruction);
+ },
+ */
+ _getBaseline: function _getBaseline(y) {
+ var height = parseInt(this.pdf.internal.getFontSize() / this.pdf.internal.scaleFactor); // TODO Get descent from font descriptor
+
+ var descent = height * 0.25;
+
+ switch (this.ctx.textBaseline) {
+ case "bottom":
+ return y - descent;
+
+ case "top":
+ return y + height;
+
+ case "hanging":
+ return y + height - descent;
+
+ case "middle":
+ return y + height / 2 - descent;
+
+ case "ideographic":
+ // TODO not implemented
+ return y;
+
+ case "alphabetic":
+ default:
+ return y;
+ }
+ },
+ createLinearGradient: function createLinearGradient() {
+ var canvasGradient = function canvasGradient() {};
+
+ canvasGradient.colorStops = [];
+
+ canvasGradient.addColorStop = function (offset, color) {
+ this.colorStops.push([offset, color]);
+ };
+
+ canvasGradient.getColor = function () {
+ if (this.colorStops.length === 0) {
+ return "#000000";
+ }
+
+ return this.colorStops[0][1];
+ };
+
+ canvasGradient.isCanvasGradient = true;
+ return canvasGradient;
+ },
+ createPattern: function createPattern() {
+ return this.createLinearGradient();
+ },
+ createRadialGradient: function createRadialGradient() {
+ return this.createLinearGradient();
+ }
+ };
+ var c2d = jsPDFAPI.context2d;
+ /**
+ * Sets or returns the color, gradient, or pattern used to fill the drawing
+ *
+ * @name fillStyle
+ * @default #000000
+ * @property {(color|gradient|pattern)} value The color of the drawing. Default value is #000000
+ * A gradient object (linear or radial) used to fill the drawing (not supported by context2d)
+ * A pattern object to use to fill the drawing (not supported by context2d)
+ */
+
+ Object.defineProperty(c2d, "fillStyle", {
+ set: function set(value) {
+ this._setFillStyle(value);
+ },
+ get: function get() {
+ return this.ctx.fillStyle;
+ }
+ });
+ /**
+ * Sets or returns the color, gradient, or pattern used for strokes
+ *
+ * @name strokeStyle
+ * @default #000000
+ * @property {color} color A CSS color value that indicates the stroke color of the drawing. Default value is #000000 (not supported by context2d)
+ * @property {gradient} gradient A gradient object (linear or radial) used to create a gradient stroke (not supported by context2d)
+ * @property {pattern} pattern A pattern object used to create a pattern stroke (not supported by context2d)
+ */
+
+ Object.defineProperty(c2d, "strokeStyle", {
+ set: function set(value) {
+ this._setStrokeStyle(value);
+ },
+ get: function get() {
+ return this.ctx.strokeStyle;
+ }
+ }); //Line Styles
+
+ /**
+ * Sets or returns the style of the end caps for a line
+ *
+ * @name lineCap
+ * @default butt
+ * @property {(butt|round|square)} lineCap butt A flat edge is added to each end of the line
+ * round A rounded end cap is added to each end of the line
+ * square A square end cap is added to each end of the line
+ */
+
+ Object.defineProperty(c2d, "lineCap", {
+ set: function set(val) {
+ this._setLineCap(val);
+ },
+ get: function get() {
+ return this.ctx.lineCap;
+ }
+ });
+ /**
+ * Sets or returns the current line width
+ *
+ * @name lineWidth
+ * @default 1
+ * @property {number} lineWidth The current line width, in pixels
+ */
+
+ Object.defineProperty(c2d, "lineWidth", {
+ set: function set(value) {
+ this._setLineWidth(value);
+ },
+ get: function get() {
+ return this.ctx.lineWidth;
+ }
+ });
+ /**
+ * Sets or returns the type of corner created, when two lines meet
+ */
+
+ Object.defineProperty(c2d, "lineJoin", {
+ set: function set(val) {
+ this._setLineJoin(val);
+ },
+ get: function get() {
+ return this.ctx.lineJoin;
+ }
+ });
+ /**
+ * Sets or returns the maximum miter length
+ */
+
+ Object.defineProperty(c2d, "miterLimit", {
+ set: function set(val) {
+ this.ctx.miterLimit = val;
+ },
+ get: function get() {
+ return this.ctx.miterLimit;
+ }
+ });
+ Object.defineProperty(c2d, "textBaseline", {
+ set: function set(value) {
+ this._setTextBaseline(value);
+ },
+ get: function get() {
+ return this._getTextBaseline();
+ }
+ });
+ Object.defineProperty(c2d, "textAlign", {
+ set: function set(value) {
+ this._setTextAlign(value);
+ },
+ get: function get() {
+ return this._getTextAlign();
+ }
+ });
+ Object.defineProperty(c2d, "font", {
+ set: function set(value) {
+ this._setFont(value);
+ },
+ get: function get() {
+ return this.ctx.font;
+ }
+ });
+ Object.defineProperty(c2d, "globalCompositeOperation", {
+ set: function set(value) {
+ this.ctx.globalCompositeOperation = value;
+ },
+ get: function get() {
+ return this.ctx.globalCompositeOperation;
+ }
+ });
+ Object.defineProperty(c2d, "globalAlpha", {
+ set: function set(value) {
+ this.ctx.globalAlpha = value;
+ },
+ get: function get() {
+ return this.ctx.globalAlpha;
+ }
+ });
+ Object.defineProperty(c2d, "canvas", {
+ get: function get() {
+ return {
+ parentNode: false,
+ style: false
+ };
+ }
+ }); // Not HTML API
+
+ Object.defineProperty(c2d, "ignoreClearRect", {
+ set: function set(value) {
+ this.ctx.ignoreClearRect = value;
+ },
+ get: function get() {
+ return this.ctx.ignoreClearRect;
+ }
+ }); // End Not HTML API
+
+ c2d.internal = {};
+ c2d.internal.rxRgb = /rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/;
+ c2d.internal.rxRgba = /rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d\.]+)\s*\)/;
+ c2d.internal.rxTransparent = /transparent|rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*0+\s*\)/; // http://hansmuller-flex.blogspot.com/2011/10/more-about-approximating-circular-arcs.html
+
+ c2d.internal.arc = function (c2d, xc, yc, r, a1, a2, counterclockwise, style) {
+ var k = this.pdf.internal.scaleFactor;
+ var pageHeight = this.pdf.internal.pageSize.getHeight();
+ var f2 = this.pdf.internal.f2;
+ var a1r = a1 * (Math.PI / 180);
+ var a2r = a2 * (Math.PI / 180);
+ var curves = this.createArc(r, a1r, a2r, counterclockwise);
+
+ for (var i = 0; i < curves.length; i++) {
+ var curve = curves[i];
+
+ if (i === 0) {
+ this.pdf.internal.out([f2((curve.x1 + xc) * k), f2((pageHeight - (curve.y1 + yc)) * k), "m", f2((curve.x2 + xc) * k), f2((pageHeight - (curve.y2 + yc)) * k), f2((curve.x3 + xc) * k), f2((pageHeight - (curve.y3 + yc)) * k), f2((curve.x4 + xc) * k), f2((pageHeight - (curve.y4 + yc)) * k), "c"].join(" "));
+ } else {
+ this.pdf.internal.out([f2((curve.x2 + xc) * k), f2((pageHeight - (curve.y2 + yc)) * k), f2((curve.x3 + xc) * k), f2((pageHeight - (curve.y3 + yc)) * k), f2((curve.x4 + xc) * k), f2((pageHeight - (curve.y4 + yc)) * k), "c"].join(" "));
+ } //c2d._lastPoint = {x: curve.x1 + xc, y: curve.y1 + yc};
+
+
+ c2d._lastPoint = {
+ x: xc,
+ y: yc
+ }; // f2((curve.x1 + xc) * k), f2((pageHeight - (curve.y1 + yc)) * k), 'm', f2((curve.x2 + xc) * k), f2((pageHeight - (curve.y2 + yc)) * k), f2((curve.x3 + xc) * k), f2((pageHeight - (curve.y3 + yc)) * k), f2((curve.x4 + xc) * k), f2((pageHeight - (curve.y4 + yc)) * k), 'c'
+ }
+
+ if (style !== null) {
+ this.pdf.internal.out(this.pdf.internal.getStyle(style));
+ }
+ };
+ /**
+ *
+ * @param x Edge point X
+ * @param y Edge point Y
+ * @param r Radius
+ * @param a1 start angle
+ * @param a2 end angle
+ * @param counterclockwise
+ * @param style
+ * @param isClip
+ */
+
+
+ c2d.internal.arc2 = function (c2d, x, y, r, a1, a2, counterclockwise, style, isClip) {
+ // we need to convert from cartesian to polar here methinks.
+ var centerX = x; // + r;
+
+ var centerY = y;
+
+ if (!isClip) {
+ this.arc(c2d, centerX, centerY, r, a1, a2, counterclockwise, style);
+ } else {
+ this.arc(c2d, centerX, centerY, r, a1, a2, counterclockwise, null);
+ this.pdf.clip();
+ }
+ };
+
+ c2d.internal.move2 = function (c2d, x, y) {
+ var k = this.pdf.internal.scaleFactor;
+ var pageHeight = this.pdf.internal.pageSize.getHeight();
+ var f2 = this.pdf.internal.f2;
+ this.pdf.internal.out([f2(x * k), f2((pageHeight - y) * k), "m"].join(" "));
+ c2d._lastPoint = {
+ x: x,
+ y: y
+ };
+ };
+
+ c2d.internal.line2 = function (c2d, dx, dy) {
+ var k = this.pdf.internal.scaleFactor;
+ var pageHeight = this.pdf.internal.pageSize.getHeight();
+ var f2 = this.pdf.internal.f2; //var pt = {x: c2d._lastPoint.x + dx, y: c2d._lastPoint.y + dy};
+
+ var pt = {
+ x: dx,
+ y: dy
+ };
+ this.pdf.internal.out([f2(pt.x * k), f2((pageHeight - pt.y) * k), "l"].join(" ")); //this.pdf.internal.out('f');
+
+ c2d._lastPoint = pt;
+ };
+ /**
+ * Return a array of objects that represent bezier curves which approximate the circular arc centered at the origin, from startAngle to endAngle (radians) with the specified radius.
+ *
+ * Each bezier curve is an object with four points, where x1,y1 and x4,y4 are the arc's end points and x2,y2 and x3,y3 are the cubic bezier's control points.
+ */
+
+
+ c2d.internal.createArc = function (radius, startAngle, endAngle, anticlockwise) {
+ var EPSILON = 0.00001; // Roughly 1/1000th of a degree, see below
+ // normalize startAngle, endAngle to [-2PI, 2PI]
+
+ var twoPI = Math.PI * 2;
+ var startAngleN = startAngle;
+
+ if (startAngleN < twoPI || startAngleN > twoPI) {
+ startAngleN = startAngleN % twoPI;
+ }
+
+ var endAngleN = endAngle;
+
+ if (endAngleN < twoPI || endAngleN > twoPI) {
+ endAngleN = endAngleN % twoPI;
+ } // Compute the sequence of arc curves, up to PI/2 at a time.
+ // Total arc angle is less than 2PI.
+
+
+ var curves = [];
+ var piOverTwo = Math.PI / 2.0; //var sgn = (startAngle < endAngle) ? +1 : -1; // clockwise or counterclockwise
+
+ var sgn = anticlockwise ? -1 : +1;
+ var a1 = startAngle;
+
+ for (var totalAngle = Math.min(twoPI, Math.abs(endAngleN - startAngleN)); totalAngle > EPSILON;) {
+ var a2 = a1 + sgn * Math.min(totalAngle, piOverTwo);
+ curves.push(this.createSmallArc(radius, a1, a2));
+ totalAngle -= Math.abs(a2 - a1);
+ a1 = a2;
+ }
+
+ return curves;
+ };
+
+ c2d.internal.getCurrentPage = function () {
+ return this.pdf.internal.pages[this.pdf.internal.getCurrentPageInfo().pageNumber];
+ };
+ /**
+ * Cubic bezier approximation of a circular arc centered at the origin, from (radians) a1 to a2, where a2-a1 < pi/2. The arc's radius is r.
+ *
+ * Returns an object with four points, where x1,y1 and x4,y4 are the arc's end points and x2,y2 and x3,y3 are the cubic bezier's control points.
+ *
+ * This algorithm is based on the approach described in: A. Riškus, "Approximation of a Cubic Bezier Curve by Circular Arcs and Vice Versa," Information Technology and Control, 35(4), 2006 pp. 371-378.
+ */
+
+
+ c2d.internal.createSmallArc = function (r, a1, a2) {
+ // Compute all four points for an arc that subtends the same total angle
+ // but is centered on the X-axis
+ var a = (a2 - a1) / 2.0;
+ var x4 = r * Math.cos(a);
+ var y4 = r * Math.sin(a);
+ var x1 = x4;
+ var y1 = -y4;
+ var q1 = x1 * x1 + y1 * y1;
+ var q2 = q1 + x1 * x4 + y1 * y4;
+ var k2 = 4 / 3 * (Math.sqrt(2 * q1 * q2) - q2) / (x1 * y4 - y1 * x4);
+ var x2 = x1 - k2 * y1;
+ var y2 = y1 + k2 * x1;
+ var x3 = x2;
+ var y3 = -y2; // Find the arc points' actual locations by computing x1,y1 and x4,y4
+ // and rotating the control points by a + a1
+
+ var ar = a + a1;
+ var cos_ar = Math.cos(ar);
+ var sin_ar = Math.sin(ar);
+ return {
+ x1: r * Math.cos(a1),
+ y1: r * Math.sin(a1),
+ x2: x2 * cos_ar - y2 * sin_ar,
+ y2: x2 * sin_ar + y2 * cos_ar,
+ x3: x3 * cos_ar - y3 * sin_ar,
+ y3: x3 * sin_ar + y3 * cos_ar,
+ x4: r * Math.cos(a2),
+ y4: r * Math.sin(a2)
+ };
+ };
+ /**
+ *
+ * @function
+ * @constructor
+ */
+
+
+ function context() {
+ this._isStrokeTransparent = false;
+ this._strokeOpacity = 1;
+ this.strokeStyle = "#000000";
+ this.fillStyle = "#000000";
+ this._isFillTransparent = false;
+ this._fillOpacity = 1;
+ this.font = "12pt times";
+ this.textBaseline = "alphabetic"; // top,bottom,middle,ideographic,alphabetic,hanging
+
+ this.textAlign = "left";
+ this.lineWidth = 1;
+ this.lineJoin = "miter"; // round, bevel, miter
+
+ this.lineCap = "butt"; // butt, round, square
+
+ this._transform = [1, 0, 0, 1, 0, 0]; // sx, shy, shx, sy, tx, ty
+
+ this.globalCompositeOperation = "normal";
+ this.globalAlpha = 1.0;
+ this._clip_path = [];
+ this.currentPoint = {
+ x: 0,
+ y: 0
+ }; // TODO miter limit //default 10
+ // Not HTML API
+
+ this.ignoreClearRect = false;
+
+ this.copy = function (ctx) {
+ this._isStrokeTransparent = ctx._isStrokeTransparent;
+ this._strokeOpacity = ctx._strokeOpacity;
+ this.strokeStyle = ctx.strokeStyle;
+ this._isFillTransparent = ctx._isFillTransparent;
+ this._fillOpacity = ctx._fillOpacity;
+ this.fillStyle = ctx.fillStyle;
+ this.font = ctx.font;
+ this.lineWidth = ctx.lineWidth;
+ this.lineJoin = ctx.lineJoin;
+ this.lineCap = ctx.lineCap;
+ this.textBaseline = ctx.textBaseline;
+ this.textAlign = ctx.textAlign;
+ this._fontSize = ctx._fontSize;
+ this._transform = ctx._transform.slice(0);
+ this.globalCompositeOperation = ctx.globalCompositeOperation;
+ this.globalAlpha = ctx.globalAlpha;
+ this._clip_path = ctx._clip_path.slice(0); //TODO deep copy?
+
+ this.currentPoint = ctx.currentPoint; // Not HTML API
+
+ this.ignoreClearRect = ctx.ignoreClearRect;
+ };
+ }
+
+ return this;
+ })(jsPDF.API, typeof self !== "undefined" && self || typeof window !== "undefined" && window || typeof global !== "undefined" && global || Function('return typeof this === "object" && this.content')() || Function("return this")());
+
+ /**
+ * Copyright (c) 2018 Erik Koopmans
+ * Released under the MIT License.
+ *
+ * Licensed under the MIT License.
+ * http://opensource.org/licenses/mit-license
+ */
+
+ /**
+ * jsPDF html PlugIn
+ *
+ * @name html
+ * @module
+ */
+ (function (jsPDFAPI, global) {
+ /**
+ * Determine the type of a variable/object.
+ *
+ * @private
+ * @ignore
+ */
+
+ var objType = function objType(obj) {
+ var type = _typeof(obj);
+
+ if (type === "undefined") return "undefined";else if (type === "string" || obj instanceof String) return "string";else if (type === "number" || obj instanceof Number) return "number";else if (type === "function" || obj instanceof Function) return "function";else if (!!obj && obj.constructor === Array) return "array";else if (obj && obj.nodeType === 1) return "element";else if (type === "object") return "object";else return "unknown";
+ };
+ /**
+ * Create an HTML element with optional className, innerHTML, and style.
+ *
+ * @private
+ * @ignore
+ */
+
+
+ var createElement = function createElement(tagName, opt) {
+ var el = document.createElement(tagName);
+ if (opt.className) el.className = opt.className;
+
+ if (opt.innerHTML) {
+ el.innerHTML = opt.innerHTML;
+ var scripts = el.getElementsByTagName("script");
+
+ for (var i = scripts.length; i-- > 0; null) {
+ scripts[i].parentNode.removeChild(scripts[i]);
+ }
+ }
+
+ for (var key in opt.style) {
+ el.style[key] = opt.style[key];
+ }
+
+ return el;
+ };
+ /**
+ * Deep-clone a node and preserve contents/properties.
+ *
+ * @private
+ * @ignore
+ */
+
+
+ var cloneNode = function cloneNode(node, javascriptEnabled) {
+ // Recursively clone the node.
+ var clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false);
+
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ if (javascriptEnabled === true || child.nodeType !== 1 || child.nodeName !== "SCRIPT") {
+ clone.appendChild(cloneNode(child, javascriptEnabled));
+ }
+ }
+
+ if (node.nodeType === 1) {
+ // Preserve contents/properties of special nodes.
+ if (node.nodeName === "CANVAS") {
+ clone.width = node.width;
+ clone.height = node.height;
+ clone.getContext("2d").drawImage(node, 0, 0);
+ } else if (node.nodeName === "TEXTAREA" || node.nodeName === "SELECT") {
+ clone.value = node.value;
+ } // Preserve the node's scroll position when it loads.
+
+
+ clone.addEventListener("load", function () {
+ clone.scrollTop = node.scrollTop;
+ clone.scrollLeft = node.scrollLeft;
+ }, true);
+ } // Return the cloned node.
+
+
+ return clone;
+ };
+ /* ----- CONSTRUCTOR ----- */
+
+
+ var Worker = function Worker(opt) {
+ // Create the root parent for the proto chain, and the starting Worker.
+ var root = Object.assign(Worker.convert(Promise.resolve()), JSON.parse(JSON.stringify(Worker.template)));
+ var self = Worker.convert(Promise.resolve(), root); // Set progress, optional settings, and return.
+
+ self = self.setProgress(1, Worker, 1, [Worker]);
+ self = self.set(opt);
+ return self;
+ }; // Boilerplate for subclassing Promise.
+
+
+ Worker.prototype = Object.create(Promise.prototype);
+ Worker.prototype.constructor = Worker; // Converts/casts promises into Workers.
+
+ Worker.convert = function convert(promise, inherit) {
+ // Uses prototypal inheritance to receive changes made to ancestors' properties.
+ promise.__proto__ = inherit || Worker.prototype;
+ return promise;
+ };
+
+ Worker.template = {
+ prop: {
+ src: null,
+ container: null,
+ overlay: null,
+ canvas: null,
+ img: null,
+ pdf: null,
+ pageSize: null,
+ callback: function callback() {}
+ },
+ progress: {
+ val: 0,
+ state: null,
+ n: 0,
+ stack: []
+ },
+ opt: {
+ filename: "file.pdf",
+ margin: [0, 0, 0, 0],
+ enableLinks: true,
+ html2canvas: {},
+ jsPDF: {}
+ }
+ };
+ /* ----- FROM / TO ----- */
+
+ Worker.prototype.from = function from(src, type) {
+ function getType(src) {
+ switch (objType(src)) {
+ case "string":
+ return "string";
+
+ case "element":
+ return src.nodeName.toLowerCase === "canvas" ? "canvas" : "element";
+
+ default:
+ return "unknown";
+ }
+ }
+
+ return this.then(function from_main() {
+ type = type || getType(src);
+
+ switch (type) {
+ case "string":
+ return this.set({
+ src: createElement("div", {
+ innerHTML: src
+ })
+ });
+
+ case "element":
+ return this.set({
+ src: src
+ });
+
+ case "canvas":
+ return this.set({
+ canvas: src
+ });
+
+ case "img":
+ return this.set({
+ img: src
+ });
+
+ default:
+ return this.error("Unknown source type.");
+ }
+ });
+ };
+
+ Worker.prototype.to = function to(target) {
+ // Route the 'to' request to the appropriate method.
+ switch (target) {
+ case "container":
+ return this.toContainer();
+
+ case "canvas":
+ return this.toCanvas();
+
+ case "img":
+ return this.toImg();
+
+ case "pdf":
+ return this.toPdf();
+
+ default:
+ return this.error("Invalid target.");
+ }
+ };
+
+ Worker.prototype.toContainer = function toContainer() {
+ // Set up function prerequisites.
+ var prereqs = [function checkSrc() {
+ return this.prop.src || this.error("Cannot duplicate - no source HTML.");
+ }, function checkPageSize() {
+ return this.prop.pageSize || this.setPageSize();
+ }];
+ return this.thenList(prereqs).then(function toContainer_main() {
+ // Define the CSS styles for the container and its overlay parent.
+ var overlayCSS = {
+ position: "fixed",
+ overflow: "hidden",
+ zIndex: 1000,
+ left: "-100000px",
+ right: 0,
+ bottom: 0,
+ top: 0
+ };
+ var containerCSS = {
+ position: "relative",
+ display: "inline-block",
+ width: Math.max(this.prop.src.clientWidth, this.prop.src.scrollWidth, this.prop.src.offsetWidth) + "px",
+ left: 0,
+ right: 0,
+ top: 0,
+ margin: "auto",
+ backgroundColor: "white"
+ }; // Set the overlay to hidden (could be changed in the future to provide a print preview).
+
+ var source = cloneNode(this.prop.src, this.opt.html2canvas.javascriptEnabled);
+
+ if (source.tagName === "BODY") {
+ containerCSS.height = Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight) + "px";
+ }
+
+ this.prop.overlay = createElement("div", {
+ className: "html2pdf__overlay",
+ style: overlayCSS
+ });
+ this.prop.container = createElement("div", {
+ className: "html2pdf__container",
+ style: containerCSS
+ });
+ this.prop.container.appendChild(source);
+ this.prop.container.firstChild.appendChild(createElement("div", {
+ style: {
+ clear: "both",
+ border: "0 none transparent",
+ margin: 0,
+ padding: 0,
+ height: 0
+ }
+ }));
+ this.prop.container.style.float = "none";
+ this.prop.overlay.appendChild(this.prop.container);
+ document.body.appendChild(this.prop.overlay);
+ this.prop.container.firstChild.style.position = "relative";
+ this.prop.container.height = Math.max(this.prop.container.firstChild.clientHeight, this.prop.container.firstChild.scrollHeight, this.prop.container.firstChild.offsetHeight) + "px";
+ });
+ };
+
+ Worker.prototype.toCanvas = function toCanvas() {
+ // Set up function prerequisites.
+ var prereqs = [function checkContainer() {
+ return document.body.contains(this.prop.container) || this.toContainer();
+ }]; // Fulfill prereqs then create the canvas.
+
+ return this.thenList(prereqs).then(function toCanvas_main() {
+ // Handle old-fashioned 'onrendered' argument.
+ var options = Object.assign({}, this.opt.html2canvas);
+ delete options.onrendered;
+
+ if (!this.isHtml2CanvasLoaded()) {
+ return;
+ }
+
+ return html2canvas(this.prop.container, options);
+ }).then(function toCanvas_post(canvas) {
+ // Handle old-fashioned 'onrendered' argument.
+ var onRendered = this.opt.html2canvas.onrendered || function () {};
+
+ onRendered(canvas);
+ this.prop.canvas = canvas;
+ document.body.removeChild(this.prop.overlay);
+ });
+ };
+
+ Worker.prototype.toContext2d = function toContext2d() {
+ // Set up function prerequisites.
+ var prereqs = [function checkContainer() {
+ return document.body.contains(this.prop.container) || this.toContainer();
+ }]; // Fulfill prereqs then create the canvas.
+
+ return this.thenList(prereqs).then(function toContext2d_main() {
+ // Handle old-fashioned 'onrendered' argument.
+ var pdf = this.opt.jsPDF;
+ var options = Object.assign({
+ async: true,
+ allowTaint: true,
+ backgroundColor: "#ffffff",
+ imageTimeout: 15000,
+ logging: true,
+ proxy: null,
+ removeContainer: true,
+ foreignObjectRendering: false,
+ useCORS: false
+ }, this.opt.html2canvas);
+ delete options.onrendered;
+ options.windowHeight = options.windowHeight || 0;
+ options.windowHeight = options.windowHeight == 0 ? Math.max(this.prop.container.clientHeight, this.prop.container.scrollHeight, this.prop.container.offsetHeight) : options.windowHeight;
+
+ if (!this.isHtml2CanvasLoaded()) {
+ return;
+ }
+
+ return html2canvas(this.prop.container, options);
+ }).then(function toContext2d_post(canvas) {
+ // Handle old-fashioned 'onrendered' argument.
+ var onRendered = this.opt.html2canvas.onrendered || function () {};
+
+ onRendered(canvas);
+ this.prop.canvas = canvas;
+ document.body.removeChild(this.prop.overlay);
+ });
+ };
+
+ Worker.prototype.toImg = function toImg() {
+ // Set up function prerequisites.
+ var prereqs = [function checkCanvas() {
+ return this.prop.canvas || this.toCanvas();
+ }]; // Fulfill prereqs then create the image.
+
+ return this.thenList(prereqs).then(function toImg_main() {
+ var imgData = this.prop.canvas.toDataURL("image/" + this.opt.image.type, this.opt.image.quality);
+ this.prop.img = document.createElement("img");
+ this.prop.img.src = imgData;
+ });
+ };
+
+ Worker.prototype.toPdf = function toPdf() {
+ // Set up function prerequisites.
+ var prereqs = [function checkContext2d() {
+ return this.toContext2d();
+ } //function checkCanvas() { return this.prop.canvas || this.toCanvas(); }
+ ]; // Fulfill prereqs then create the image.
+
+ return this.thenList(prereqs).then(function toPdf_main() {
+ // Create local copies of frequently used properties.
+ this.prop.pdf = this.prop.pdf || this.opt.jsPDF;
+ });
+ };
+ /* ----- OUTPUT / SAVE ----- */
+
+
+ Worker.prototype.output = function output(type, options, src) {
+ // Redirect requests to the correct function (outputPdf / outputImg).
+ src = src || "pdf";
+
+ if (src.toLowerCase() === "img" || src.toLowerCase() === "image") {
+ return this.outputImg(type, options);
+ } else {
+ return this.outputPdf(type, options);
+ }
+ };
+
+ Worker.prototype.outputPdf = function outputPdf(type, options) {
+ // Set up function prerequisites.
+ var prereqs = [function checkPdf() {
+ return this.prop.pdf || this.toPdf();
+ }]; // Fulfill prereqs then perform the appropriate output.
+
+ return this.thenList(prereqs).then(function outputPdf_main() {
+ /* Currently implemented output types:
+ * https://rawgit.com/MrRio/jsPDF/master/docs/jspdf.js.html#line992
+ * save(options), arraybuffer, blob, bloburi/bloburl,
+ * datauristring/dataurlstring, dataurlnewwindow, datauri/dataurl
+ */
+ return this.prop.pdf.output(type, options);
+ });
+ };
+
+ Worker.prototype.outputImg = function outputImg(type, options) {
+ // Set up function prerequisites.
+ var prereqs = [function checkImg() {
+ return this.prop.img || this.toImg();
+ }]; // Fulfill prereqs then perform the appropriate output.
+
+ return this.thenList(prereqs).then(function outputImg_main() {
+ switch (type) {
+ case undefined:
+ case "img":
+ return this.prop.img;
+
+ case "datauristring":
+ case "dataurlstring":
+ return this.prop.img.src;
+
+ case "datauri":
+ case "dataurl":
+ return document.location.href = this.prop.img.src;
+
+ default:
+ throw 'Image output type "' + type + '" is not supported.';
+ }
+ });
+ };
+
+ Worker.prototype.isHtml2CanvasLoaded = function () {
+ var result = typeof global.html2canvas !== "undefined";
+
+ if (!result) {
+ console.error("html2canvas not loaded.");
+ }
+
+ return result;
+ };
+
+ Worker.prototype.save = function save(filename) {
+ // Set up function prerequisites.
+ var prereqs = [function checkPdf() {
+ return this.prop.pdf || this.toPdf();
+ }];
+
+ if (!this.isHtml2CanvasLoaded()) {
+ return;
+ } // Fulfill prereqs, update the filename (if provided), and save the PDF.
+
+
+ return this.thenList(prereqs).set(filename ? {
+ filename: filename
+ } : null).then(function save_main() {
+ this.prop.pdf.save(this.opt.filename);
+ });
+ };
+
+ Worker.prototype.doCallback = function doCallback(filename) {
+ // Set up function prerequisites.
+ var prereqs = [function checkPdf() {
+ return this.prop.pdf || this.toPdf();
+ }];
+
+ if (!this.isHtml2CanvasLoaded()) {
+ return;
+ } // Fulfill prereqs, update the filename (if provided), and save the PDF.
+
+
+ return this.thenList(prereqs).then(function doCallback_main() {
+ this.prop.callback(this.prop.pdf);
+ });
+ };
+ /* ----- SET / GET ----- */
+
+
+ Worker.prototype.set = function set(opt) {
+ // TODO: Implement ordered pairs?
+ // Silently ignore invalid or empty input.
+ if (objType(opt) !== "object") {
+ return this;
+ } // Build an array of setter functions to queue.
+
+
+ var fns = Object.keys(opt || {}).map(function (key) {
+ if (key in Worker.template.prop) {
+ // Set pre-defined properties.
+ return function set_prop() {
+ this.prop[key] = opt[key];
+ };
+ } else {
+ switch (key) {
+ case "margin":
+ return this.setMargin.bind(this, opt.margin);
+
+ case "jsPDF":
+ return function set_jsPDF() {
+ this.opt.jsPDF = opt.jsPDF;
+ return this.setPageSize();
+ };
+
+ case "pageSize":
+ return this.setPageSize.bind(this, opt.pageSize);
+
+ default:
+ // Set any other properties in opt.
+ return function set_opt() {
+ this.opt[key] = opt[key];
+ };
+ }
+ }
+ }, this); // Set properties within the promise chain.
+
+ return this.then(function set_main() {
+ return this.thenList(fns);
+ });
+ };
+
+ Worker.prototype.get = function get(key, cbk) {
+ return this.then(function get_main() {
+ // Fetch the requested property, either as a predefined prop or in opt.
+ var val = key in Worker.template.prop ? this.prop[key] : this.opt[key];
+ return cbk ? cbk(val) : val;
+ });
+ };
+
+ Worker.prototype.setMargin = function setMargin(margin) {
+ return this.then(function setMargin_main() {
+ // Parse the margin property.
+ switch (objType(margin)) {
+ case "number":
+ margin = [margin, margin, margin, margin];
+
+ case "array":
+ if (margin.length === 2) {
+ margin = [margin[0], margin[1], margin[0], margin[1]];
+ }
+
+ if (margin.length === 4) {
+ break;
+ }
+
+ default:
+ return this.error("Invalid margin array.");
+ } // Set the margin property, then update pageSize.
+
+
+ this.opt.margin = margin;
+ }).then(this.setPageSize);
+ };
+
+ Worker.prototype.setPageSize = function setPageSize(pageSize) {
+ function toPx(val, k) {
+ return Math.floor(val * k / 72 * 96);
+ }
+
+ return this.then(function setPageSize_main() {
+ // Retrieve page-size based on jsPDF settings, if not explicitly provided.
+ pageSize = pageSize || jsPDF.getPageSize(this.opt.jsPDF); // Add 'inner' field if not present.
+
+ if (!pageSize.hasOwnProperty("inner")) {
+ pageSize.inner = {
+ width: pageSize.width - this.opt.margin[1] - this.opt.margin[3],
+ height: pageSize.height - this.opt.margin[0] - this.opt.margin[2]
+ };
+ pageSize.inner.px = {
+ width: toPx(pageSize.inner.width, pageSize.k),
+ height: toPx(pageSize.inner.height, pageSize.k)
+ };
+ pageSize.inner.ratio = pageSize.inner.height / pageSize.inner.width;
+ } // Attach pageSize to this.
+
+
+ this.prop.pageSize = pageSize;
+ });
+ };
+
+ Worker.prototype.setProgress = function setProgress(val, state, n, stack) {
+ // Immediately update all progress values.
+ if (val != null) this.progress.val = val;
+ if (state != null) this.progress.state = state;
+ if (n != null) this.progress.n = n;
+ if (stack != null) this.progress.stack = stack;
+ this.progress.ratio = this.progress.val / this.progress.state; // Return this for command chaining.
+
+ return this;
+ };
+
+ Worker.prototype.updateProgress = function updateProgress(val, state, n, stack) {
+ // Immediately update all progress values, using setProgress.
+ return this.setProgress(val ? this.progress.val + val : null, state ? state : null, n ? this.progress.n + n : null, stack ? this.progress.stack.concat(stack) : null);
+ };
+ /* ----- PROMISE MAPPING ----- */
+
+
+ Worker.prototype.then = function then(onFulfilled, onRejected) {
+ // Wrap `this` for encapsulation.
+ var self = this;
+ return this.thenCore(onFulfilled, onRejected, function then_main(onFulfilled, onRejected) {
+ // Update progress while queuing, calling, and resolving `then`.
+ self.updateProgress(null, null, 1, [onFulfilled]);
+ return Promise.prototype.then.call(this, function then_pre(val) {
+ self.updateProgress(null, onFulfilled);
+ return val;
+ }).then(onFulfilled, onRejected).then(function then_post(val) {
+ self.updateProgress(1);
+ return val;
+ });
+ });
+ };
+
+ Worker.prototype.thenCore = function thenCore(onFulfilled, onRejected, thenBase) {
+ // Handle optional thenBase parameter.
+ thenBase = thenBase || Promise.prototype.then; // Wrap `this` for encapsulation and bind it to the promise handlers.
+
+ var self = this;
+
+ if (onFulfilled) {
+ onFulfilled = onFulfilled.bind(self);
+ }
+
+ if (onRejected) {
+ onRejected = onRejected.bind(self);
+ } // Cast self into a Promise to avoid polyfills recursively defining `then`.
+
+
+ var isNative = Promise.toString().indexOf("[native code]") !== -1 && Promise.name === "Promise";
+ var selfPromise = isNative ? self : Worker.convert(Object.assign({}, self), Promise.prototype); // Return the promise, after casting it into a Worker and preserving props.
+
+ var returnVal = thenBase.call(selfPromise, onFulfilled, onRejected);
+ return Worker.convert(returnVal, self.__proto__);
+ };
+
+ Worker.prototype.thenExternal = function thenExternal(onFulfilled, onRejected) {
+ // Call `then` and return a standard promise (exits the Worker chain).
+ return Promise.prototype.then.call(this, onFulfilled, onRejected);
+ };
+
+ Worker.prototype.thenList = function thenList(fns) {
+ // Queue a series of promise 'factories' into the promise chain.
+ var self = this;
+ fns.forEach(function thenList_forEach(fn) {
+ self = self.thenCore(fn);
+ });
+ return self;
+ };
+
+ Worker.prototype["catch"] = function (onRejected) {
+ // Bind `this` to the promise handler, call `catch`, and return a Worker.
+ if (onRejected) {
+ onRejected = onRejected.bind(this);
+ }
+
+ var returnVal = Promise.prototype["catch"].call(this, onRejected);
+ return Worker.convert(returnVal, this);
+ };
+
+ Worker.prototype.catchExternal = function catchExternal(onRejected) {
+ // Call `catch` and return a standard promise (exits the Worker chain).
+ return Promise.prototype["catch"].call(this, onRejected);
+ };
+
+ Worker.prototype.error = function error(msg) {
+ // Throw the error in the Promise chain.
+ return this.then(function error_main() {
+ throw new Error(msg);
+ });
+ };
+ /* ----- ALIASES ----- */
+
+
+ Worker.prototype.using = Worker.prototype.set;
+ Worker.prototype.saveAs = Worker.prototype.save;
+ Worker.prototype.export = Worker.prototype.output;
+ Worker.prototype.run = Worker.prototype.then; // Get dimensions of a PDF page, as determined by jsPDF.
+
+ jsPDF.getPageSize = function (orientation, unit, format) {
+ // Decode options object
+ if (_typeof(orientation) === "object") {
+ var options = orientation;
+ orientation = options.orientation;
+ unit = options.unit || unit;
+ format = options.format || format;
+ } // Default options
+
+
+ unit = unit || "mm";
+ format = format || "a4";
+ orientation = ("" + (orientation || "P")).toLowerCase();
+ var format_as_string = ("" + format).toLowerCase(); // Size in pt of various paper formats
+
+ var pageFormats = {
+ a0: [2383.94, 3370.39],
+ a1: [1683.78, 2383.94],
+ a2: [1190.55, 1683.78],
+ a3: [841.89, 1190.55],
+ a4: [595.28, 841.89],
+ a5: [419.53, 595.28],
+ a6: [297.64, 419.53],
+ a7: [209.76, 297.64],
+ a8: [147.4, 209.76],
+ a9: [104.88, 147.4],
+ a10: [73.7, 104.88],
+ b0: [2834.65, 4008.19],
+ b1: [2004.09, 2834.65],
+ b2: [1417.32, 2004.09],
+ b3: [1000.63, 1417.32],
+ b4: [708.66, 1000.63],
+ b5: [498.9, 708.66],
+ b6: [354.33, 498.9],
+ b7: [249.45, 354.33],
+ b8: [175.75, 249.45],
+ b9: [124.72, 175.75],
+ b10: [87.87, 124.72],
+ c0: [2599.37, 3676.54],
+ c1: [1836.85, 2599.37],
+ c2: [1298.27, 1836.85],
+ c3: [918.43, 1298.27],
+ c4: [649.13, 918.43],
+ c5: [459.21, 649.13],
+ c6: [323.15, 459.21],
+ c7: [229.61, 323.15],
+ c8: [161.57, 229.61],
+ c9: [113.39, 161.57],
+ c10: [79.37, 113.39],
+ dl: [311.81, 623.62],
+ letter: [612, 792],
+ "government-letter": [576, 756],
+ legal: [612, 1008],
+ "junior-legal": [576, 360],
+ ledger: [1224, 792],
+ tabloid: [792, 1224],
+ "credit-card": [153, 243]
+ }; // Unit conversion
+
+ switch (unit) {
+ case "pt":
+ var k = 1;
+ break;
+
+ case "mm":
+ var k = 72 / 25.4;
+ break;
+
+ case "cm":
+ var k = 72 / 2.54;
+ break;
+
+ case "in":
+ var k = 72;
+ break;
+
+ case "px":
+ var k = 72 / 96;
+ break;
+
+ case "pc":
+ var k = 12;
+ break;
+
+ case "em":
+ var k = 12;
+ break;
+
+ case "ex":
+ var k = 6;
+ break;
+
+ default:
+ throw "Invalid unit: " + unit;
+ } // Dimensions are stored as user units and converted to points on output
+
+
+ if (pageFormats.hasOwnProperty(format_as_string)) {
+ var pageHeight = pageFormats[format_as_string][1] / k;
+ var pageWidth = pageFormats[format_as_string][0] / k;
+ } else {
+ try {
+ var pageHeight = format[1];
+ var pageWidth = format[0];
+ } catch (err) {
+ throw new Error("Invalid format: " + format);
+ }
+ } // Handle page orientation
+
+
+ if (orientation === "p" || orientation === "portrait") {
+ orientation = "p";
+
+ if (pageWidth > pageHeight) {
+ var tmp = pageWidth;
+ pageWidth = pageHeight;
+ pageHeight = tmp;
+ }
+ } else if (orientation === "l" || orientation === "landscape") {
+ orientation = "l";
+
+ if (pageHeight > pageWidth) {
+ var tmp = pageWidth;
+ pageWidth = pageHeight;
+ pageHeight = tmp;
+ }
+ } else {
+ throw "Invalid orientation: " + orientation;
+ } // Return information (k is the unit conversion ratio from pts)
+
+
+ var info = {
+ width: pageWidth,
+ height: pageHeight,
+ unit: unit,
+ k: k
+ };
+ return info;
+ };
+ /**
+ * Generate a PDF from an HTML element or string using.
+ *
+ * @name html
+ * @function
+ * @param {Element|string} source The source element or HTML string.
+ * @param {Object=} options An object of optional settings.
+ * @description The Plugin needs html2canvas from niklasvh
+ */
+
+
+ jsPDFAPI.html = function (src, options) {
+
+ options = options || {};
+
+ options.callback = options.callback || function () {};
+
+ options.html2canvas = options.html2canvas || {};
+ options.html2canvas.canvas = options.html2canvas.canvas || this.canvas;
+ options.jsPDF = options.jsPDF || this; // Create a new worker with the given options.
+
+ var pdf = options.jsPDF;
+ pdf.annotations = {
+ _nameMap: [],
+ createAnnotation: function createAnnotation(href, bounds) {
+ var x = pdf.context2d._wrapX(bounds.left);
+
+ var y = pdf.context2d._wrapY(bounds.top);
+
+ var page = pdf.context2d._page(bounds.top);
+
+ var options;
+ var index = href.indexOf("#");
+
+ if (index >= 0) {
+ options = {
+ name: href.substring(index + 1)
+ };
+ } else {
+ options = {
+ url: href
+ };
+ }
+
+ pdf.link(x, y, bounds.right - bounds.left, bounds.bottom - bounds.top, options);
+ },
+ setName: function setName(name, bounds) {
+ var x = pdf.context2d._wrapX(bounds.left);
+
+ var y = pdf.context2d._wrapY(bounds.top);
+
+ var page = pdf.context2d._page(bounds.top);
+
+ this._nameMap[name] = {
+ page: page,
+ x: x,
+ y: y
+ };
+ }
+ };
+
+ pdf.context2d._pageBreakAt = function (y) {
+ this.pageBreaks.push(y);
+ };
+
+ pdf.context2d._gotoPage = function (pageOneBased) {
+ while (pdf.internal.getNumberOfPages() < pageOneBased) {
+ pdf.addPage();
+ }
+
+ pdf.setPage(pageOneBased);
+ };
+
+ pdf.context2d.pageWrapYEnabled = true;
+ pdf.context2d.pageWrapY = pdf.internal.pageSize.getHeight() / pdf.internal.scaleFactor + 1;
+ pdf.canvas.autoContext2dResizeY = false;
+ var worker = new Worker(options);
+
+ if (!options.worker) {
+ // If worker is not set to true, perform the traditional 'simple' operation.
+ return worker.from(src).doCallback();
+ } else {
+ // Otherwise, return the worker for new Promise-based operation.
+ return worker;
+ }
+
+ return this;
+ };
+ })(jsPDF.API, typeof window !== "undefined" && window || typeof global !== "undefined" && global);
+
+ /**
+ * @license
+ * ====================================================================
+ * Copyright (c) 2013 Youssef Beddad, youssef.beddad@gmail.com
+ *
+ *
+ * ====================================================================
+ */
+
+ /*global jsPDF */
+
+ /**
+ * jsPDF JavaScript plugin
+ *
+ * @name javascript
+ * @module
+ */
+ (function (jsPDFAPI) {
+
+ var jsNamesObj, jsJsObj, text;
+ /**
+ * @name addJS
+ * @function
+ * @param {string} javascript The javascript to be embedded into the PDF-file.
+ * @returns {jsPDF}
+ */
+
+ jsPDFAPI.addJS = function (javascript) {
+ text = javascript;
+ this.internal.events.subscribe("postPutResources", function (javascript) {
+ jsNamesObj = this.internal.newObject();
+ this.internal.out("<<");
+ this.internal.out("/Names [(EmbeddedJS) " + (jsNamesObj + 1) + " 0 R]");
+ this.internal.out(">>");
+ this.internal.out("endobj");
+ jsJsObj = this.internal.newObject();
+ this.internal.out("<<");
+ this.internal.out("/S /JavaScript");
+ this.internal.out("/JS (" + text + ")");
+ this.internal.out(">>");
+ this.internal.out("endobj");
+ });
+ this.internal.events.subscribe("putCatalog", function () {
+ if (jsNamesObj !== undefined && jsJsObj !== undefined) {
+ this.internal.out("/Names <>");
+ }
+ });
+ return this;
+ };
+ })(jsPDF.API);
+
+ /**
+ * @license
+ * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv
+ *
+ * Licensed under the MIT License.
+ * http://opensource.org/licenses/mit-license
+ */
+
+ /**
+ * jsPDF Outline PlugIn
+ *
+ * Generates a PDF Outline
+ * @name outline
+ * @module
+ */
+ (function (jsPDFAPI) {
+
+ jsPDFAPI.events.push(["postPutResources", function () {
+ var pdf = this;
+ var rx = /^(\d+) 0 obj$/; // Write action goto objects for each page
+ // this.outline.destsGoto = [];
+ // for (var i = 0; i < totalPages; i++) {
+ // var id = pdf.internal.newObject();
+ // this.outline.destsGoto.push(id);
+ // pdf.internal.write("<> endobj");
+ // }
+ //
+ // for (var i = 0; i < dests.length; i++) {
+ // pdf.internal.write("(page_" + (i + 1) + ")" + dests[i] + " 0
+ // R");
+ // }
+ //
+
+ if (this.outline.root.children.length > 0) {
+ var lines = pdf.outline.render().split(/\r\n/);
+
+ for (var i = 0; i < lines.length; i++) {
+ var line = lines[i];
+ var m = rx.exec(line);
+
+ if (m != null) {
+ var oid = m[1];
+ pdf.internal.newObjectDeferredBegin(oid);
+ }
+
+ pdf.internal.write(line);
+ }
+ } // This code will write named destination for each page reference
+ // (page_1, etc)
+
+
+ if (this.outline.createNamedDestinations) {
+ var totalPages = this.internal.pages.length; // WARNING: this assumes jsPDF starts on page 3 and pageIDs
+ // follow 5, 7, 9, etc
+ // Write destination objects for each page
+
+ var dests = [];
+
+ for (var i = 0; i < totalPages; i++) {
+ var id = pdf.internal.newObject();
+ dests.push(id);
+ var info = pdf.internal.getPageInfo(i + 1);
+ pdf.internal.write("<< /D[" + info.objId + " 0 R /XYZ null null null]>> endobj");
+ } // assign a name for each destination
+
+
+ var names2Oid = pdf.internal.newObject();
+ pdf.internal.write("<< /Names [ ");
+
+ for (var i = 0; i < dests.length; i++) {
+ pdf.internal.write("(page_" + (i + 1) + ")" + dests[i] + " 0 R");
+ }
+
+ pdf.internal.write(" ] >>", "endobj"); // var kids = pdf.internal.newObject();
+ // pdf.internal.write('<< /Kids [ ' + names2Oid + ' 0 R');
+ // pdf.internal.write(' ] >>', 'endobj');
+
+ var namesOid = pdf.internal.newObject();
+ pdf.internal.write("<< /Dests " + names2Oid + " 0 R");
+ pdf.internal.write(">>", "endobj");
+ }
+ }]);
+ jsPDFAPI.events.push(["putCatalog", function () {
+ var pdf = this;
+
+ if (pdf.outline.root.children.length > 0) {
+ pdf.internal.write("/Outlines", this.outline.makeRef(this.outline.root));
+
+ if (this.outline.createNamedDestinations) {
+ pdf.internal.write("/Names " + namesOid + " 0 R");
+ } // Open with Bookmarks showing
+ // pdf.internal.write("/PageMode /UseOutlines");
+
+ }
+ }]);
+ jsPDFAPI.events.push(["initialized", function () {
+ var pdf = this;
+ pdf.outline = {
+ createNamedDestinations: false,
+ root: {
+ children: []
+ }
+ };
+ /**
+ * Options: pageNumber
*/
- API.setLineMiterLimit = function (miterLimit) {
- out(f2(miterLimit) + " M");
- return this;
- };
+ pdf.outline.add = function (parent, title, options) {
+ var item = {
+ title: title,
+ options: options,
+ children: []
+ };
+
+ if (parent == null) {
+ parent = this.root;
+ }
+
+ parent.children.push(item);
+ return item;
+ };
+
+ pdf.outline.render = function () {
+ this.ctx = {};
+ this.ctx.val = "";
+ this.ctx.pdf = pdf;
+ this.genIds_r(this.root);
+ this.renderRoot(this.root);
+ this.renderItems(this.root);
+ return this.ctx.val;
+ };
+
+ pdf.outline.genIds_r = function (node) {
+ node.id = pdf.internal.newObjectDeferred();
+
+ for (var i = 0; i < node.children.length; i++) {
+ this.genIds_r(node.children[i]);
+ }
+ };
+
+ pdf.outline.renderRoot = function (node) {
+ this.objStart(node);
+ this.line("/Type /Outlines");
+
+ if (node.children.length > 0) {
+ this.line("/First " + this.makeRef(node.children[0]));
+ this.line("/Last " + this.makeRef(node.children[node.children.length - 1]));
+ }
+
+ this.line("/Count " + this.count_r({
+ count: 0
+ }, node));
+ this.objEnd();
+ };
+
+ pdf.outline.renderItems = function (node) {
+ for (var i = 0; i < node.children.length; i++) {
+ var item = node.children[i];
+ this.objStart(item);
+ this.line("/Title " + this.makeString(item.title));
+ this.line("/Parent " + this.makeRef(node));
+
+ if (i > 0) {
+ this.line("/Prev " + this.makeRef(node.children[i - 1]));
+ }
+
+ if (i < node.children.length - 1) {
+ this.line("/Next " + this.makeRef(node.children[i + 1]));
+ }
+
+ if (item.children.length > 0) {
+ this.line("/First " + this.makeRef(item.children[0]));
+ this.line("/Last " + this.makeRef(item.children[item.children.length - 1]));
+ }
+
+ var count = this.count = this.count_r({
+ count: 0
+ }, item);
+
+ if (count > 0) {
+ this.line("/Count " + count);
+ }
+
+ if (item.options) {
+ if (item.options.pageNumber) {
+ // Explicit Destination
+ //WARNING this assumes page ids are 3,5,7, etc.
+ var info = pdf.internal.getPageInfo(item.options.pageNumber);
+ this.line("/Dest " + "[" + info.objId + " 0 R /XYZ 0 " + this.ctx.pdf.internal.pageSize.getHeight() * this.ctx.pdf.internal.scaleFactor + " 0]"); // this line does not work on all clients (pageNumber instead of page ref)
+ //this.line('/Dest ' + '[' + (item.options.pageNumber - 1) + ' /XYZ 0 ' + this.ctx.pdf.internal.pageSize.getHeight() + ' 0]');
+ // Named Destination
+ // this.line('/Dest (page_' + (item.options.pageNumber) + ')');
+ // Action Destination
+ // var id = pdf.internal.newObject();
+ // pdf.internal.write('<> endobj');
+ // this.line('/A ' + id + ' 0 R' );
+ }
+ }
+
+ this.objEnd();
+ }
+
+ for (var i = 0; i < node.children.length; i++) {
+ var item = node.children[i];
+ this.renderItems(item);
+ }
+ };
+
+ pdf.outline.line = function (text) {
+ this.ctx.val += text + "\r\n";
+ };
+
+ pdf.outline.makeRef = function (node) {
+ return node.id + " 0 R";
+ };
+
+ pdf.outline.makeString = function (val) {
+ return "(" + pdf.internal.pdfEscape(val) + ")";
+ };
+
+ pdf.outline.objStart = function (node) {
+ this.ctx.val += "\r\n" + node.id + " 0 obj" + "\r\n<<\r\n";
+ };
+
+ pdf.outline.objEnd = function (node) {
+ this.ctx.val += ">> \r\n" + "endobj" + "\r\n";
+ };
+
+ pdf.outline.count_r = function (ctx, node) {
+ for (var i = 0; i < node.children.length; i++) {
+ ctx.count++;
+ this.count_r(ctx, node.children[i]);
+ }
+
+ return ctx.count;
+ };
+ }]);
+ return this;
+ })(jsPDF.API);
+
+ /**
+ * @license
+ *
+ * Copyright (c) 2014 James Robb, https://github.com/jamesbrobb
+ *
+ *
+ * ====================================================================
+ */
+
+ /**
+ * jsPDF PNG PlugIn
+ * @name png_support
+ * @module
+ */
+ (function (jsPDFAPI) {
+ /*
+ * @see http://www.w3.org/TR/PNG-Chunks.html
+ *
+ Color Allowed Interpretation
+ Type Bit Depths
+ 0 1,2,4,8,16 Each pixel is a grayscale sample.
+ 2 8,16 Each pixel is an R,G,B triple.
+ 3 1,2,4,8 Each pixel is a palette index;
+ a PLTE chunk must appear.
+ 4 8,16 Each pixel is a grayscale sample,
+ followed by an alpha sample.
+ 6 8,16 Each pixel is an R,G,B triple,
+ followed by an alpha sample.
+ */
+
+ /*
+ * PNG filter method types
+ *
+ * @see http://www.w3.org/TR/PNG-Filters.html
+ * @see http://www.libpng.org/pub/png/book/chapter09.html
+ *
+ * This is what the value 'Predictor' in decode params relates to
+ *
+ * 15 is "optimal prediction", which means the prediction algorithm can change from line to line.
+ * In that case, you actually have to read the first byte off each line for the prediction algorthim (which should be 0-4, corresponding to PDF 10-14) and select the appropriate unprediction algorithm based on that byte.
+ *
+ 0 None
+ 1 Sub
+ 2 Up
+ 3 Average
+ 4 Paeth
+ */
+
+ var doesNotHavePngJS = function doesNotHavePngJS() {
+ return typeof PNG !== "function" || typeof FlateStream !== "function";
+ },
+ canCompress = function canCompress(value) {
+ return value !== jsPDFAPI.image_compression.NONE && hasCompressionJS();
+ },
+ hasCompressionJS = function hasCompressionJS() {
+ var inst = typeof Deflater === "function";
+ if (!inst) throw new Error("requires deflate.js for compression");
+ return inst;
+ },
+ compressBytes = function compressBytes(bytes, lineLength, colorsPerPixel, compression) {
+ var level = 5,
+ filter_method = filterUp;
+
+ switch (compression) {
+ case jsPDFAPI.image_compression.FAST:
+ level = 3;
+ filter_method = filterSub;
+ break;
+
+ case jsPDFAPI.image_compression.MEDIUM:
+ level = 6;
+ filter_method = filterAverage;
+ break;
+
+ case jsPDFAPI.image_compression.SLOW:
+ level = 9;
+ filter_method = filterPaeth; //uses to sum to choose best filter for each line
+
+ break;
+ }
+
+ bytes = applyPngFilterMethod(bytes, lineLength, colorsPerPixel, filter_method);
+ var header = new Uint8Array(createZlibHeader(level));
+ var checksum = adler32(bytes);
+ var deflate = new Deflater(level);
+ var a = deflate.append(bytes);
+ var cBytes = deflate.flush();
+ var len = header.length + a.length + cBytes.length;
+ var cmpd = new Uint8Array(len + 4);
+ cmpd.set(header);
+ cmpd.set(a, header.length);
+ cmpd.set(cBytes, header.length + a.length);
+ cmpd[len++] = checksum >>> 24 & 0xff;
+ cmpd[len++] = checksum >>> 16 & 0xff;
+ cmpd[len++] = checksum >>> 8 & 0xff;
+ cmpd[len++] = checksum & 0xff;
+ return jsPDFAPI.arrayBufferToBinaryString(cmpd);
+ },
+ createZlibHeader = function createZlibHeader(bytes, level) {
+ /*
+ * @see http://www.ietf.org/rfc/rfc1950.txt for zlib header
+ */
+ var cm = 8;
+ var cinfo = Math.LOG2E * Math.log(0x8000) - 8;
+ var cmf = cinfo << 4 | cm;
+ var hdr = cmf << 8;
+ var flevel = Math.min(3, (level - 1 & 0xff) >> 1);
+ hdr |= flevel << 6;
+ hdr |= 0; //FDICT
+
+ hdr += 31 - hdr % 31;
+ return [cmf, hdr & 0xff & 0xff];
+ },
+ adler32 = function adler32(array, param) {
+ var adler = 1;
+ var s1 = adler & 0xffff,
+ s2 = adler >>> 16 & 0xffff;
+ var len = array.length;
+ var tlen;
+ var i = 0;
+
+ while (len > 0) {
+ tlen = len > param ? param : len;
+ len -= tlen;
+
+ do {
+ s1 += array[i++];
+ s2 += s1;
+ } while (--tlen);
+
+ s1 %= 65521;
+ s2 %= 65521;
+ }
+
+ return (s2 << 16 | s1) >>> 0;
+ },
+ applyPngFilterMethod = function applyPngFilterMethod(bytes, lineLength, colorsPerPixel, filter_method) {
+ var lines = bytes.length / lineLength,
+ result = new Uint8Array(bytes.length + lines),
+ filter_methods = getFilterMethods(),
+ i = 0,
+ line,
+ prevLine,
+ offset;
+
+ for (; i < lines; i++) {
+ offset = i * lineLength;
+ line = bytes.subarray(offset, offset + lineLength);
+
+ if (filter_method) {
+ result.set(filter_method(line, colorsPerPixel, prevLine), offset + i);
+ } else {
+ var j = 0,
+ len = filter_methods.length,
+ results = [];
+
+ for (; j < len; j++) {
+ results[j] = filter_methods[j](line, colorsPerPixel, prevLine);
+ }
+
+ var ind = getIndexOfSmallestSum(results.concat());
+ result.set(results[ind], offset + i);
+ }
+
+ prevLine = line;
+ }
+
+ return result;
+ },
+ filterNone = function filterNone(line, colorsPerPixel, prevLine) {
+ /*var result = new Uint8Array(line.length + 1);
+ result[0] = 0;
+ result.set(line, 1);*/
+ var result = Array.apply([], line);
+ result.unshift(0);
+ return result;
+ },
+ filterSub = function filterSub(line, colorsPerPixel, prevLine) {
+ var result = [],
+ i = 0,
+ len = line.length,
+ left;
+ result[0] = 1;
+
+ for (; i < len; i++) {
+ left = line[i - colorsPerPixel] || 0;
+ result[i + 1] = line[i] - left + 0x0100 & 0xff;
+ }
+
+ return result;
+ },
+ filterUp = function filterUp(line, colorsPerPixel, prevLine) {
+ var result = [],
+ i = 0,
+ len = line.length,
+ up;
+ result[0] = 2;
+
+ for (; i < len; i++) {
+ up = prevLine && prevLine[i] || 0;
+ result[i + 1] = line[i] - up + 0x0100 & 0xff;
+ }
+
+ return result;
+ },
+ filterAverage = function filterAverage(line, colorsPerPixel, prevLine) {
+ var result = [],
+ i = 0,
+ len = line.length,
+ left,
+ up;
+ result[0] = 3;
+
+ for (; i < len; i++) {
+ left = line[i - colorsPerPixel] || 0;
+ up = prevLine && prevLine[i] || 0;
+ result[i + 1] = line[i] + 0x0100 - (left + up >>> 1) & 0xff;
+ }
+
+ return result;
+ },
+ filterPaeth = function filterPaeth(line, colorsPerPixel, prevLine) {
+ var result = [],
+ i = 0,
+ len = line.length,
+ left,
+ up,
+ upLeft,
+ paeth;
+ result[0] = 4;
+
+ for (; i < len; i++) {
+ left = line[i - colorsPerPixel] || 0;
+ up = prevLine && prevLine[i] || 0;
+ upLeft = prevLine && prevLine[i - colorsPerPixel] || 0;
+ paeth = paethPredictor(left, up, upLeft);
+ result[i + 1] = line[i] - paeth + 0x0100 & 0xff;
+ }
+
+ return result;
+ },
+ paethPredictor = function paethPredictor(left, up, upLeft) {
+ var p = left + up - upLeft,
+ pLeft = Math.abs(p - left),
+ pUp = Math.abs(p - up),
+ pUpLeft = Math.abs(p - upLeft);
+ return pLeft <= pUp && pLeft <= pUpLeft ? left : pUp <= pUpLeft ? up : upLeft;
+ },
+ getFilterMethods = function getFilterMethods() {
+ return [filterNone, filterSub, filterUp, filterAverage, filterPaeth];
+ },
+ getIndexOfSmallestSum = function getIndexOfSmallestSum(arrays) {
+ var i = 0,
+ len = arrays.length,
+ sum,
+ min,
+ ind;
+
+ while (i < len) {
+ sum = absSum(arrays[i].slice(1));
+
+ if (sum < min || !min) {
+ min = sum;
+ ind = i;
+ }
+
+ i++;
+ }
+
+ return ind;
+ },
+ absSum = function absSum(array) {
+ var i = 0,
+ len = array.length,
+ sum = 0;
+
+ while (i < len) {
+ sum += Math.abs(array[i++]);
+ }
+
+ return sum;
+ },
+ getPredictorFromCompression = function getPredictorFromCompression(compression) {
+ var predictor;
+
+ switch (compression) {
+ case jsPDFAPI.image_compression.FAST:
+ predictor = 11;
+ break;
+
+ case jsPDFAPI.image_compression.MEDIUM:
+ predictor = 13;
+ break;
+
+ case jsPDFAPI.image_compression.SLOW:
+ predictor = 14;
+ break;
+
+ default:
+ predictor = 12;
+ break;
+ }
+
+ return predictor;
+ };
+ /**
+ *
+ * @name processPNG
+ * @function
+ * @ignore
+ */
+
+
+ jsPDFAPI.processPNG = function (imageData, imageIndex, alias, compression, dataAsBinaryString) {
+
+ var colorSpace = this.color_spaces.DEVICE_RGB,
+ decode = this.decode.FLATE_DECODE,
+ bpc = 8,
+ img,
+ dp,
+ trns,
+ colors,
+ pal,
+ smask;
+ /* if(this.isString(imageData)) {
+ }*/
+
+ if (this.isArrayBuffer(imageData)) imageData = new Uint8Array(imageData);
+
+ if (this.isArrayBufferView(imageData)) {
+ if (doesNotHavePngJS()) throw new Error("PNG support requires png.js and zlib.js");
+ img = new PNG(imageData);
+ imageData = img.imgData;
+ bpc = img.bits;
+ colorSpace = img.colorSpace;
+ colors = img.colors; //logImg(img);
+
+ /*
+ * colorType 6 - Each pixel is an R,G,B triple, followed by an alpha sample.
+ *
+ * colorType 4 - Each pixel is a grayscale sample, followed by an alpha sample.
+ *
+ * Extract alpha to create two separate images, using the alpha as a sMask
+ */
+
+ if ([4, 6].indexOf(img.colorType) !== -1) {
+ /*
+ * processes 8 bit RGBA and grayscale + alpha images
+ */
+ if (img.bits === 8) {
+ var pixels = img.pixelBitlength == 32 ? new Uint32Array(img.decodePixels().buffer) : img.pixelBitlength == 16 ? new Uint16Array(img.decodePixels().buffer) : new Uint8Array(img.decodePixels().buffer),
+ len = pixels.length,
+ imgData = new Uint8Array(len * img.colors),
+ alphaData = new Uint8Array(len),
+ pDiff = img.pixelBitlength - img.bits,
+ i = 0,
+ n = 0,
+ pixel,
+ pbl;
+
+ for (; i < len; i++) {
+ pixel = pixels[i];
+ pbl = 0;
+
+ while (pbl < pDiff) {
+ imgData[n++] = pixel >>> pbl & 0xff;
+ pbl = pbl + img.bits;
+ }
+
+ alphaData[i] = pixel >>> pbl & 0xff;
+ }
+ }
+ /*
+ * processes 16 bit RGBA and grayscale + alpha images
+ */
+
+
+ if (img.bits === 16) {
+ var pixels = new Uint32Array(img.decodePixels().buffer),
+ len = pixels.length,
+ imgData = new Uint8Array(len * (32 / img.pixelBitlength) * img.colors),
+ alphaData = new Uint8Array(len * (32 / img.pixelBitlength)),
+ hasColors = img.colors > 1,
+ i = 0,
+ n = 0,
+ a = 0,
+ pixel;
+
+ while (i < len) {
+ pixel = pixels[i++];
+ imgData[n++] = pixel >>> 0 & 0xff;
+
+ if (hasColors) {
+ imgData[n++] = pixel >>> 16 & 0xff;
+ pixel = pixels[i++];
+ imgData[n++] = pixel >>> 0 & 0xff;
+ }
+
+ alphaData[a++] = pixel >>> 16 & 0xff;
+ }
+
+ bpc = 8;
+ }
+
+ if (canCompress(compression)) {
+ imageData = compressBytes(imgData, img.width * img.colors, img.colors, compression);
+ smask = compressBytes(alphaData, img.width, 1, compression);
+ } else {
+ imageData = imgData;
+ smask = alphaData;
+ decode = null;
+ }
+ }
+ /*
+ * Indexed png. Each pixel is a palette index.
+ */
+
+
+ if (img.colorType === 3) {
+ colorSpace = this.color_spaces.INDEXED;
+ pal = img.palette;
+
+ if (img.transparency.indexed) {
+ var trans = img.transparency.indexed;
+ var total = 0,
+ i = 0,
+ len = trans.length;
+
+ for (; i < len; ++i) {
+ total += trans[i];
+ }
+
+ total = total / 255;
+ /*
+ * a single color is specified as 100% transparent (0),
+ * so we set trns to use a /Mask with that index
+ */
+
+ if (total === len - 1 && trans.indexOf(0) !== -1) {
+ trns = [trans.indexOf(0)];
+ /*
+ * there's more than one colour within the palette that specifies
+ * a transparency value less than 255, so we unroll the pixels to create an image sMask
+ */
+ } else if (total !== len) {
+ var pixels = img.decodePixels(),
+ alphaData = new Uint8Array(pixels.length),
+ i = 0,
+ len = pixels.length;
+
+ for (; i < len; i++) {
+ alphaData[i] = trans[pixels[i]];
+ }
+
+ smask = compressBytes(alphaData, img.width, 1);
+ }
+ }
+ }
+
+ var predictor = getPredictorFromCompression(compression);
+ if (decode === this.decode.FLATE_DECODE) dp = "/Predictor " + predictor + " /Colors " + colors + " /BitsPerComponent " + bpc + " /Columns " + img.width; //remove 'Predictor' as it applies to the type of png filter applied to its IDAT - we only apply with compression
+ else dp = "/Colors " + colors + " /BitsPerComponent " + bpc + " /Columns " + img.width;
+ if (this.isArrayBuffer(imageData) || this.isArrayBufferView(imageData)) imageData = this.arrayBufferToBinaryString(imageData);
+ if (smask && this.isArrayBuffer(smask) || this.isArrayBufferView(smask)) smask = this.arrayBufferToBinaryString(smask);
+ return this.createImageInfo(imageData, img.width, img.height, colorSpace, bpc, decode, imageIndex, alias, dp, trns, pal, smask, predictor);
+ }
+
+ throw new Error("Unsupported PNG image data, try using JPEG instead.");
+ };
+ })(jsPDF.API);
+
+ /**
+ * @license
+ * Copyright (c) 2017 Aras Abbasi
+ *
+ * Licensed under the MIT License.
+ * http://opensource.org/licenses/mit-license
+ */
+
+ /**
+ * jsPDF gif Support PlugIn
+ *
+ * @name gif_support
+ * @module
+ */
+ (function (jsPDFAPI) {
+
+ jsPDFAPI.processGIF89A = function (imageData, imageIndex, alias, compression, dataAsBinaryString) {
+ var reader = new GifReader(imageData);
+ var width = reader.width,
+ height = reader.height;
+ var qu = 100;
+ var pixels = [];
+ reader.decodeAndBlitFrameRGBA(0, pixels);
+ var rawImageData = {
+ data: pixels,
+ width: width,
+ height: height
+ };
+ var encoder = new JPEGEncoder(qu);
+ var data = encoder.encode(rawImageData, qu);
+ return jsPDFAPI.processJPEG.call(this, data, imageIndex, alias, compression);
+ };
+
+ jsPDFAPI.processGIF87A = jsPDFAPI.processGIF89A;
+ })(jsPDF.API);
+
+ /**
+ * Copyright (c) 2018 Aras Abbasi
+ *
+ * Licensed under the MIT License.
+ * http://opensource.org/licenses/mit-license
+ */
+
+ /**
+ * jsPDF bmp Support PlugIn
+ * @name bmp_support
+ * @module
+ */
+ (function (jsPDFAPI) {
+
+ jsPDFAPI.processBMP = function (imageData, imageIndex, alias, compression, dataAsBinaryString) {
+ var reader = new BmpDecoder(imageData, false);
+ var width = reader.width,
+ height = reader.height;
+ var qu = 100;
+ var pixels = reader.getData();
+ var rawImageData = {
+ data: pixels,
+ width: width,
+ height: height
+ };
+ var encoder = new JPEGEncoder(qu);
+ var data = encoder.encode(rawImageData, qu);
+ return jsPDFAPI.processJPEG.call(this, data, imageIndex, alias, compression);
+ };
+ })(jsPDF.API);
+
+ /**
+ * @license
+ * Licensed under the MIT License.
+ * http://opensource.org/licenses/mit-license
+ */
+
+ /**
+ * jsPDF setLanguage Plugin
+ *
+ * @name setLanguage
+ * @module
+ */
+ (function (jsPDFAPI) {
+ /**
+ * Add Language Tag to the generated PDF
+ *
+ * @name setLanguage
+ * @function
+ * @param {string} langCode The Language code as ISO-639-1 (e.g. 'en') or as country language code (e.g. 'en-GB').
+ * @returns {jsPDF}
+ * @example
+ * var doc = new jsPDF()
+ * doc.text(10, 10, 'This is a test')
+ * doc.setLanguage("en-US")
+ * doc.save('english.pdf')
+ */
+
+ jsPDFAPI.setLanguage = function (langCode) {
+
+ var langCodes = {
+ af: "Afrikaans",
+ sq: "Albanian",
+ ar: "Arabic (Standard)",
+ "ar-DZ": "Arabic (Algeria)",
+ "ar-BH": "Arabic (Bahrain)",
+ "ar-EG": "Arabic (Egypt)",
+ "ar-IQ": "Arabic (Iraq)",
+ "ar-JO": "Arabic (Jordan)",
+ "ar-KW": "Arabic (Kuwait)",
+ "ar-LB": "Arabic (Lebanon)",
+ "ar-LY": "Arabic (Libya)",
+ "ar-MA": "Arabic (Morocco)",
+ "ar-OM": "Arabic (Oman)",
+ "ar-QA": "Arabic (Qatar)",
+ "ar-SA": "Arabic (Saudi Arabia)",
+ "ar-SY": "Arabic (Syria)",
+ "ar-TN": "Arabic (Tunisia)",
+ "ar-AE": "Arabic (U.A.E.)",
+ "ar-YE": "Arabic (Yemen)",
+ an: "Aragonese",
+ hy: "Armenian",
+ as: "Assamese",
+ ast: "Asturian",
+ az: "Azerbaijani",
+ eu: "Basque",
+ be: "Belarusian",
+ bn: "Bengali",
+ bs: "Bosnian",
+ br: "Breton",
+ bg: "Bulgarian",
+ my: "Burmese",
+ ca: "Catalan",
+ ch: "Chamorro",
+ ce: "Chechen",
+ zh: "Chinese",
+ "zh-HK": "Chinese (Hong Kong)",
+ "zh-CN": "Chinese (PRC)",
+ "zh-SG": "Chinese (Singapore)",
+ "zh-TW": "Chinese (Taiwan)",
+ cv: "Chuvash",
+ co: "Corsican",
+ cr: "Cree",
+ hr: "Croatian",
+ cs: "Czech",
+ da: "Danish",
+ nl: "Dutch (Standard)",
+ "nl-BE": "Dutch (Belgian)",
+ en: "English",
+ "en-AU": "English (Australia)",
+ "en-BZ": "English (Belize)",
+ "en-CA": "English (Canada)",
+ "en-IE": "English (Ireland)",
+ "en-JM": "English (Jamaica)",
+ "en-NZ": "English (New Zealand)",
+ "en-PH": "English (Philippines)",
+ "en-ZA": "English (South Africa)",
+ "en-TT": "English (Trinidad & Tobago)",
+ "en-GB": "English (United Kingdom)",
+ "en-US": "English (United States)",
+ "en-ZW": "English (Zimbabwe)",
+ eo: "Esperanto",
+ et: "Estonian",
+ fo: "Faeroese",
+ fj: "Fijian",
+ fi: "Finnish",
+ fr: "French (Standard)",
+ "fr-BE": "French (Belgium)",
+ "fr-CA": "French (Canada)",
+ "fr-FR": "French (France)",
+ "fr-LU": "French (Luxembourg)",
+ "fr-MC": "French (Monaco)",
+ "fr-CH": "French (Switzerland)",
+ fy: "Frisian",
+ fur: "Friulian",
+ gd: "Gaelic (Scots)",
+ "gd-IE": "Gaelic (Irish)",
+ gl: "Galacian",
+ ka: "Georgian",
+ de: "German (Standard)",
+ "de-AT": "German (Austria)",
+ "de-DE": "German (Germany)",
+ "de-LI": "German (Liechtenstein)",
+ "de-LU": "German (Luxembourg)",
+ "de-CH": "German (Switzerland)",
+ el: "Greek",
+ gu: "Gujurati",
+ ht: "Haitian",
+ he: "Hebrew",
+ hi: "Hindi",
+ hu: "Hungarian",
+ is: "Icelandic",
+ id: "Indonesian",
+ iu: "Inuktitut",
+ ga: "Irish",
+ it: "Italian (Standard)",
+ "it-CH": "Italian (Switzerland)",
+ ja: "Japanese",
+ kn: "Kannada",
+ ks: "Kashmiri",
+ kk: "Kazakh",
+ km: "Khmer",
+ ky: "Kirghiz",
+ tlh: "Klingon",
+ ko: "Korean",
+ "ko-KP": "Korean (North Korea)",
+ "ko-KR": "Korean (South Korea)",
+ la: "Latin",
+ lv: "Latvian",
+ lt: "Lithuanian",
+ lb: "Luxembourgish",
+ mk: "FYRO Macedonian",
+ ms: "Malay",
+ ml: "Malayalam",
+ mt: "Maltese",
+ mi: "Maori",
+ mr: "Marathi",
+ mo: "Moldavian",
+ nv: "Navajo",
+ ng: "Ndonga",
+ ne: "Nepali",
+ no: "Norwegian",
+ nb: "Norwegian (Bokmal)",
+ nn: "Norwegian (Nynorsk)",
+ oc: "Occitan",
+ or: "Oriya",
+ om: "Oromo",
+ fa: "Persian",
+ "fa-IR": "Persian/Iran",
+ pl: "Polish",
+ pt: "Portuguese",
+ "pt-BR": "Portuguese (Brazil)",
+ pa: "Punjabi",
+ "pa-IN": "Punjabi (India)",
+ "pa-PK": "Punjabi (Pakistan)",
+ qu: "Quechua",
+ rm: "Rhaeto-Romanic",
+ ro: "Romanian",
+ "ro-MO": "Romanian (Moldavia)",
+ ru: "Russian",
+ "ru-MO": "Russian (Moldavia)",
+ sz: "Sami (Lappish)",
+ sg: "Sango",
+ sa: "Sanskrit",
+ sc: "Sardinian",
+ sd: "Sindhi",
+ si: "Singhalese",
+ sr: "Serbian",
+ sk: "Slovak",
+ sl: "Slovenian",
+ so: "Somani",
+ sb: "Sorbian",
+ es: "Spanish",
+ "es-AR": "Spanish (Argentina)",
+ "es-BO": "Spanish (Bolivia)",
+ "es-CL": "Spanish (Chile)",
+ "es-CO": "Spanish (Colombia)",
+ "es-CR": "Spanish (Costa Rica)",
+ "es-DO": "Spanish (Dominican Republic)",
+ "es-EC": "Spanish (Ecuador)",
+ "es-SV": "Spanish (El Salvador)",
+ "es-GT": "Spanish (Guatemala)",
+ "es-HN": "Spanish (Honduras)",
+ "es-MX": "Spanish (Mexico)",
+ "es-NI": "Spanish (Nicaragua)",
+ "es-PA": "Spanish (Panama)",
+ "es-PY": "Spanish (Paraguay)",
+ "es-PE": "Spanish (Peru)",
+ "es-PR": "Spanish (Puerto Rico)",
+ "es-ES": "Spanish (Spain)",
+ "es-UY": "Spanish (Uruguay)",
+ "es-VE": "Spanish (Venezuela)",
+ sx: "Sutu",
+ sw: "Swahili",
+ sv: "Swedish",
+ "sv-FI": "Swedish (Finland)",
+ "sv-SV": "Swedish (Sweden)",
+ ta: "Tamil",
+ tt: "Tatar",
+ te: "Teluga",
+ th: "Thai",
+ tig: "Tigre",
+ ts: "Tsonga",
+ tn: "Tswana",
+ tr: "Turkish",
+ tk: "Turkmen",
+ uk: "Ukrainian",
+ hsb: "Upper Sorbian",
+ ur: "Urdu",
+ ve: "Venda",
+ vi: "Vietnamese",
+ vo: "Volapuk",
+ wa: "Walloon",
+ cy: "Welsh",
+ xh: "Xhosa",
+ ji: "Yiddish",
+ zu: "Zulu"
+ };
+
+ if (this.internal.languageSettings === undefined) {
+ this.internal.languageSettings = {};
+ this.internal.languageSettings.isSubscribed = false;
+ }
+
+ if (langCodes[langCode] !== undefined) {
+ this.internal.languageSettings.languageCode = langCode;
+
+ if (this.internal.languageSettings.isSubscribed === false) {
+ this.internal.events.subscribe("putCatalog", function () {
+ this.internal.write("/Lang (" + this.internal.languageSettings.languageCode + ")");
+ });
+ this.internal.languageSettings.isSubscribed = true;
+ }
+ }
+
+ return this;
+ };
+ })(jsPDF.API);
+
+ /** @license
+ * MIT license.
+ * Copyright (c) 2012 Willow Systems Corporation, willow-systems.com
+ * 2014 Diego Casorran, https://github.com/diegocr
+ *
+ *
+ * ====================================================================
+ */
+
+ /**
+ * jsPDF split_text_to_size plugin
+ *
+ * @name split_text_to_size
+ * @module
+ */
+ (function (API) {
+ /**
+ * Returns an array of length matching length of the 'word' string, with each
+ * cell occupied by the width of the char in that position.
+ *
+ * @name getCharWidthsArray
+ * @function
+ * @param {string} text
+ * @param {Object} options
+ * @returns {Array}
+ */
+
+ var getCharWidthsArray = API.getCharWidthsArray = function (text, options) {
+ options = options || {};
+ var activeFont = options.font || this.internal.getFont();
+ var fontSize = options.fontSize || this.internal.getFontSize();
+ var charSpace = options.charSpace || this.internal.getCharSpace();
+ var widths = options.widths ? options.widths : activeFont.metadata.Unicode.widths;
+ var widthsFractionOf = widths.fof ? widths.fof : 1;
+ var kerning = options.kerning ? options.kerning : activeFont.metadata.Unicode.kerning;
+ var kerningFractionOf = kerning.fof ? kerning.fof : 1;
+ var i;
+ var l;
+ var char_code;
+ var prior_char_code = 0; //for kerning
+
+ var default_char_width = widths[0] || widthsFractionOf;
+ var output = [];
+
+ for (i = 0, l = text.length; i < l; i++) {
+ char_code = text.charCodeAt(i);
+
+ if (typeof activeFont.metadata.widthOfString === "function") {
+ output.push((activeFont.metadata.widthOfGlyph(activeFont.metadata.characterToGlyph(char_code)) + charSpace * (1000 / fontSize) || 0) / 1000);
+ } else {
+ output.push((widths[char_code] || default_char_width) / widthsFractionOf + (kerning[char_code] && kerning[char_code][prior_char_code] || 0) / kerningFractionOf);
+ }
+
+ prior_char_code = char_code;
+ }
+
+ return output;
+ };
+ /**
+ * Calculate the sum of a number-array
+ *
+ * @name getArraySum
+ * @public
+ * @function
+ * @param {Array} array Array of numbers
+ * @returns {number}
+ */
+
+
+ var getArraySum = API.getArraySum = function (array) {
+ var i = array.length,
+ output = 0;
+
+ while (i) {
+ i--;
+ output += array[i];
+ }
+
+ return output;
+ };
+ /**
+ * Returns a widths of string in a given font, if the font size is set as 1 point.
+ *
+ * In other words, this is "proportional" value. For 1 unit of font size, the length
+ * of the string will be that much.
+ *
+ * Multiply by font size to get actual width in *points*
+ * Then divide by 72 to get inches or divide by (72/25.6) to get 'mm' etc.
+ *
+ * @name getStringUnitWidth
+ * @public
+ * @function
+ * @param {string} text
+ * @param {string} options
+ * @returns {number} result
+ */
+
+
+ var getStringUnitWidth = API.getStringUnitWidth = function (text, options) {
+ options = options || {};
+ var fontSize = options.fontSize || this.internal.getFontSize();
+ var font = options.font || this.internal.getFont();
+ var charSpace = options.charSpace || this.internal.getCharSpace();
+ var result = 0;
+
+ if (typeof font.metadata.widthOfString === "function") {
+ result = font.metadata.widthOfString(text, fontSize, charSpace) / fontSize;
+ } else {
+ result = getArraySum(getCharWidthsArray.apply(this, arguments));
+ }
+
+ return result;
+ };
+ /**
+ returns array of lines
+ */
+
+
+ var splitLongWord = function splitLongWord(word, widths_array, firstLineMaxLen, maxLen) {
+ var answer = []; // 1st, chop off the piece that can fit on the hanging line.
+
+ var i = 0,
+ l = word.length,
+ workingLen = 0;
+
+ while (i !== l && workingLen + widths_array[i] < firstLineMaxLen) {
+ workingLen += widths_array[i];
+ i++;
+ } // this is first line.
+
+
+ answer.push(word.slice(0, i)); // 2nd. Split the rest into maxLen pieces.
+
+ var startOfLine = i;
+ workingLen = 0;
+
+ while (i !== l) {
+ if (workingLen + widths_array[i] > maxLen) {
+ answer.push(word.slice(startOfLine, i));
+ workingLen = 0;
+ startOfLine = i;
+ }
+
+ workingLen += widths_array[i];
+ i++;
+ }
+
+ if (startOfLine !== i) {
+ answer.push(word.slice(startOfLine, i));
+ }
+
+ return answer;
+ }; // Note, all sizing inputs for this function must be in "font measurement units"
+ // By default, for PDF, it's "point".
+
+
+ var splitParagraphIntoLines = function splitParagraphIntoLines(text, maxlen, options) {
+ // at this time works only on Western scripts, ones with space char
+ // separating the words. Feel free to expand.
+ if (!options) {
+ options = {};
+ }
+
+ var line = [],
+ lines = [line],
+ line_length = options.textIndent || 0,
+ separator_length = 0,
+ current_word_length = 0,
+ word,
+ widths_array,
+ words = text.split(" "),
+ spaceCharWidth = getCharWidthsArray.apply(this, [" ", options])[0],
+ i,
+ l,
+ tmp,
+ lineIndent;
+
+ if (options.lineIndent === -1) {
+ lineIndent = words[0].length + 2;
+ } else {
+ lineIndent = options.lineIndent || 0;
+ }
+
+ if (lineIndent) {
+ var pad = Array(lineIndent).join(" "),
+ wrds = [];
+ words.map(function (wrd) {
+ wrd = wrd.split(/\s*\n/);
+
+ if (wrd.length > 1) {
+ wrds = wrds.concat(wrd.map(function (wrd, idx) {
+ return (idx && wrd.length ? "\n" : "") + wrd;
+ }));
+ } else {
+ wrds.push(wrd[0]);
+ }
+ });
+ words = wrds;
+ lineIndent = getStringUnitWidth.apply(this, [pad, options]);
+ }
+
+ for (i = 0, l = words.length; i < l; i++) {
+ var force = 0;
+ word = words[i];
+
+ if (lineIndent && word[0] == "\n") {
+ word = word.substr(1);
+ force = 1;
+ }
+
+ widths_array = getCharWidthsArray.apply(this, [word, options]);
+ current_word_length = getArraySum(widths_array);
+
+ if (line_length + separator_length + current_word_length > maxlen || force) {
+ if (current_word_length > maxlen) {
+ // this happens when you have space-less long URLs for example.
+ // we just chop these to size. We do NOT insert hiphens
+ tmp = splitLongWord.apply(this, [word, widths_array, maxlen - (line_length + separator_length), maxlen]); // first line we add to existing line object
+
+ line.push(tmp.shift()); // it's ok to have extra space indicator there
+ // last line we make into new line object
+
+ line = [tmp.pop()]; // lines in the middle we apped to lines object as whole lines
+
+ while (tmp.length) {
+ lines.push([tmp.shift()]); // single fragment occupies whole line
+ }
+
+ current_word_length = getArraySum(widths_array.slice(word.length - (line[0] ? line[0].length : 0)));
+ } else {
+ // just put it on a new line
+ line = [word];
+ } // now we attach new line to lines
+
+
+ lines.push(line);
+ line_length = current_word_length + lineIndent;
+ separator_length = spaceCharWidth;
+ } else {
+ line.push(word);
+ line_length += separator_length + current_word_length;
+ separator_length = spaceCharWidth;
+ }
+ }
+
+ if (lineIndent) {
+ var postProcess = function postProcess(ln, idx) {
+ return (idx ? pad : "") + ln.join(" ");
+ };
+ } else {
+ var postProcess = function postProcess(ln) {
+ return ln.join(" ");
+ };
+ }
+
+ return lines.map(postProcess);
+ };
+ /**
+ * Splits a given string into an array of strings. Uses 'size' value
+ * (in measurement units declared as default for the jsPDF instance)
+ * and the font's "widths" and "Kerning" tables, where available, to
+ * determine display length of a given string for a given font.
+ *
+ * We use character's 100% of unit size (height) as width when Width
+ * table or other default width is not available.
+ *
+ * @name splitTextToSize
+ * @public
+ * @function
+ * @param {string} text Unencoded, regular JavaScript (Unicode, UTF-16 / UCS-2) string.
+ * @param {number} size Nominal number, measured in units default to this instance of jsPDF.
+ * @param {Object} options Optional flags needed for chopper to do the right thing.
+ * @returns {Array} array Array with strings chopped to size.
+ */
+
+
+ API.splitTextToSize = function (text, maxlen, options) {
+
+ options = options || {};
+
+ var fsize = options.fontSize || this.internal.getFontSize(),
+ newOptions = function (options) {
+ var widths = {
+ 0: 1
+ },
+ kerning = {};
+
+ if (!options.widths || !options.kerning) {
+ var f = this.internal.getFont(options.fontName, options.fontStyle),
+ encoding = "Unicode"; // NOT UTF8, NOT UTF16BE/LE, NOT UCS2BE/LE
+ // Actual JavaScript-native String's 16bit char codes used.
+ // no multi-byte logic here
+
+ if (f.metadata[encoding]) {
+ return {
+ widths: f.metadata[encoding].widths || widths,
+ kerning: f.metadata[encoding].kerning || kerning
+ };
+ } else {
+ return {
+ font: f.metadata,
+ fontSize: this.internal.getFontSize(),
+ charSpace: this.internal.getCharSpace()
+ };
+ }
+ } else {
+ return {
+ widths: options.widths,
+ kerning: options.kerning
+ };
+ } // then use default values
+
+
+ return {
+ widths: widths,
+ kerning: kerning
+ };
+ }.call(this, options); // first we split on end-of-line chars
+
+
+ var paragraphs;
+
+ if (Array.isArray(text)) {
+ paragraphs = text;
+ } else {
+ paragraphs = text.split(/\r?\n/);
+ } // now we convert size (max length of line) into "font size units"
+ // at present time, the "font size unit" is always 'point'
+ // 'proportional' means, "in proportion to font size"
+
+
+ var fontUnit_maxLen = 1.0 * this.internal.scaleFactor * maxlen / fsize; // at this time, fsize is always in "points" regardless of the default measurement unit of the doc.
+ // this may change in the future?
+ // until then, proportional_maxlen is likely to be in 'points'
+ // If first line is to be indented (shorter or longer) than maxLen
+ // we indicate that by using CSS-style "text-indent" option.
+ // here it's in font units too (which is likely 'points')
+ // it can be negative (which makes the first line longer than maxLen)
+
+ newOptions.textIndent = options.textIndent ? options.textIndent * 1.0 * this.internal.scaleFactor / fsize : 0;
+ newOptions.lineIndent = options.lineIndent;
+ var i,
+ l,
+ output = [];
+
+ for (i = 0, l = paragraphs.length; i < l; i++) {
+ output = output.concat(splitParagraphIntoLines.apply(this, [paragraphs[i], fontUnit_maxLen, newOptions]));
+ }
+
+ return output;
+ };
+ })(jsPDF.API);
+
+ /** @license
+ jsPDF standard_fonts_metrics plugin
+ * Copyright (c) 2012 Willow Systems Corporation, willow-systems.com
+ * MIT license.
+ *
+ * ====================================================================
+ */
+
+ /**
+ * This file adds the standard font metrics to jsPDF.
+ *
+ * Font metrics data is reprocessed derivative of contents of
+ * "Font Metrics for PDF Core 14 Fonts" package, which exhibits the following copyright and license:
+ *
+ * Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+ *
+ * This file and the 14 PostScript(R) AFM files it accompanies may be used,
+ * copied, and distributed for any purpose and without charge, with or without
+ * modification, provided that all copyright notices are retained; that the AFM
+ * files are not distributed without this file; that all modifications to this
+ * file or any of the AFM files are prominently noted in the modified file(s);
+ * and that this paragraph is not modified. Adobe Systems has no responsibility
+ * or obligation to support the use of the AFM files.
+ *
+ * @name standard_fonts_metrics
+ * @module
+ */
+ (function (API) {
+ /*
+ # reference (Python) versions of 'compress' and 'uncompress'
+ # only 'uncompress' function is featured lower as JavaScript
+ # if you want to unit test "roundtrip", just transcribe the reference
+ # 'compress' function from Python into JavaScript
+ def compress(data):
+ keys = '0123456789abcdef'
+ values = 'klmnopqrstuvwxyz'
+ mapping = dict(zip(keys, values))
+ vals = []
+ for key in data.keys():
+ value = data[key]
+ try:
+ keystring = hex(key)[2:]
+ keystring = keystring[:-1] + mapping[keystring[-1:]]
+ except:
+ keystring = key.join(["'","'"])
+ #print('Keystring is %s' % keystring)
+ try:
+ if value < 0:
+ valuestring = hex(value)[3:]
+ numberprefix = '-'
+ else:
+ valuestring = hex(value)[2:]
+ numberprefix = ''
+ valuestring = numberprefix + valuestring[:-1] + mapping[valuestring[-1:]]
+ except:
+ if type(value) == dict:
+ valuestring = compress(value)
+ else:
+ raise Exception("Don't know what to do with value type %s" % type(value))
+ vals.append(keystring+valuestring)
+
+ return '{' + ''.join(vals) + '}'
+ def uncompress(data):
+ decoded = '0123456789abcdef'
+ encoded = 'klmnopqrstuvwxyz'
+ mapping = dict(zip(encoded, decoded))
+ sign = +1
+ stringmode = False
+ stringparts = []
+ output = {}
+ activeobject = output
+ parentchain = []
+ keyparts = ''
+ valueparts = ''
+ key = None
+ ending = set(encoded)
+ i = 1
+ l = len(data) - 1 # stripping starting, ending {}
+ while i != l: # stripping {}
+ # -, {, }, ' are special.
+ ch = data[i]
+ i += 1
+ if ch == "'":
+ if stringmode:
+ # end of string mode
+ stringmode = False
+ key = ''.join(stringparts)
+ else:
+ # start of string mode
+ stringmode = True
+ stringparts = []
+ elif stringmode == True:
+ #print("Adding %s to stringpart" % ch)
+ stringparts.append(ch)
+ elif ch == '{':
+ # start of object
+ parentchain.append( [activeobject, key] )
+ activeobject = {}
+ key = None
+ #DEBUG = True
+ elif ch == '}':
+ # end of object
+ parent, key = parentchain.pop()
+ parent[key] = activeobject
+ key = None
+ activeobject = parent
+ #DEBUG = False
+ elif ch == '-':
+ sign = -1
+ else:
+ # must be number
+ if key == None:
+ #debug("In Key. It is '%s', ch is '%s'" % (keyparts, ch))
+ if ch in ending:
+ #debug("End of key")
+ keyparts += mapping[ch]
+ key = int(keyparts, 16) * sign
+ sign = +1
+ keyparts = ''
+ else:
+ keyparts += ch
+ else:
+ #debug("In value. It is '%s', ch is '%s'" % (valueparts, ch))
+ if ch in ending:
+ #debug("End of value")
+ valueparts += mapping[ch]
+ activeobject[key] = int(valueparts, 16) * sign
+ sign = +1
+ key = None
+ valueparts = ''
+ else:
+ valueparts += ch
+ #debug(activeobject)
+ return output
+ */
+
+ /**
+ Uncompresses data compressed into custom, base16-like format.
+ @public
+ @function
+ @param
+ @returns {Type}
+ */
+
+ var uncompress = function uncompress(data) {
+ var decoded = "0123456789abcdef",
+ encoded = "klmnopqrstuvwxyz",
+ mapping = {};
+
+ for (var i = 0; i < encoded.length; i++) {
+ mapping[encoded[i]] = decoded[i];
+ }
+
+ var undef,
+ output = {},
+ sign = 1,
+ stringparts,
+ // undef. will be [] in string mode
+ activeobject = output,
+ parentchain = [],
+ parent_key_pair,
+ keyparts = "",
+ valueparts = "",
+ key,
+ // undef. will be Truthy when Key is resolved.
+ datalen = data.length - 1,
+ // stripping ending }
+ ch;
+ i = 1; // stripping starting {
+
+ while (i != datalen) {
+ // - { } ' are special.
+ ch = data[i];
+ i += 1;
+
+ if (ch == "'") {
+ if (stringparts) {
+ // end of string mode
+ key = stringparts.join("");
+ stringparts = undef;
+ } else {
+ // start of string mode
+ stringparts = [];
+ }
+ } else if (stringparts) {
+ stringparts.push(ch);
+ } else if (ch == "{") {
+ // start of object
+ parentchain.push([activeobject, key]);
+ activeobject = {};
+ key = undef;
+ } else if (ch == "}") {
+ // end of object
+ parent_key_pair = parentchain.pop();
+ parent_key_pair[0][parent_key_pair[1]] = activeobject;
+ key = undef;
+ activeobject = parent_key_pair[0];
+ } else if (ch == "-") {
+ sign = -1;
+ } else {
+ // must be number
+ if (key === undef) {
+ if (mapping.hasOwnProperty(ch)) {
+ keyparts += mapping[ch];
+ key = parseInt(keyparts, 16) * sign;
+ sign = +1;
+ keyparts = "";
+ } else {
+ keyparts += ch;
+ }
+ } else {
+ if (mapping.hasOwnProperty(ch)) {
+ valueparts += mapping[ch];
+ activeobject[key] = parseInt(valueparts, 16) * sign;
+ sign = +1;
+ key = undef;
+ valueparts = "";
+ } else {
+ valueparts += ch;
+ }
+ }
+ }
+ } // end while
+
+
+ return output;
+ }; // encoding = 'Unicode'
+ // NOT UTF8, NOT UTF16BE/LE, NOT UCS2BE/LE. NO clever BOM behavior
+ // Actual 16bit char codes used.
+ // no multi-byte logic here
+ // Unicode characters to WinAnsiEncoding:
+ // {402: 131, 8211: 150, 8212: 151, 8216: 145, 8217: 146, 8218: 130, 8220: 147, 8221: 148, 8222: 132, 8224: 134, 8225: 135, 8226: 149, 8230: 133, 8364: 128, 8240:137, 8249: 139, 8250: 155, 710: 136, 8482: 153, 338: 140, 339: 156, 732: 152, 352: 138, 353: 154, 376: 159, 381: 142, 382: 158}
+ // as you can see, all Unicode chars are outside of 0-255 range. No char code conflicts.
+ // this means that you can give Win cp1252 encoded strings to jsPDF for rendering directly
+ // as well as give strings with some (supported by these fonts) Unicode characters and
+ // these will be mapped to win cp1252
+ // for example, you can send char code (cp1252) 0x80 or (unicode) 0x20AC, getting "Euro" glyph displayed in both cases.
+
+
+ var encodingBlock = {
+ codePages: ["WinAnsiEncoding"],
+ WinAnsiEncoding: uncompress("{19m8n201n9q201o9r201s9l201t9m201u8m201w9n201x9o201y8o202k8q202l8r202m9p202q8p20aw8k203k8t203t8v203u9v2cq8s212m9t15m8w15n9w2dw9s16k8u16l9u17s9z17x8y17y9y}")
+ },
+ encodings = {
+ Unicode: {
+ Courier: encodingBlock,
+ "Courier-Bold": encodingBlock,
+ "Courier-BoldOblique": encodingBlock,
+ "Courier-Oblique": encodingBlock,
+ Helvetica: encodingBlock,
+ "Helvetica-Bold": encodingBlock,
+ "Helvetica-BoldOblique": encodingBlock,
+ "Helvetica-Oblique": encodingBlock,
+ "Times-Roman": encodingBlock,
+ "Times-Bold": encodingBlock,
+ "Times-BoldItalic": encodingBlock,
+ "Times-Italic": encodingBlock // , 'Symbol'
+ // , 'ZapfDingbats'
+
+ }
+ },
+ fontMetrics = {
+ Unicode: {
+ // all sizing numbers are n/fontMetricsFractionOf = one font size unit
+ // this means that if fontMetricsFractionOf = 1000, and letter A's width is 476, it's
+ // width is 476/1000 or 47.6% of its height (regardless of font size)
+ // At this time this value applies to "widths" and "kerning" numbers.
+ // char code 0 represents "default" (average) width - use it for chars missing in this table.
+ // key 'fof' represents the "fontMetricsFractionOf" value
+ "Courier-Oblique": uncompress("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"),
+ "Times-BoldItalic": uncompress("{'widths'{k3o2q4ycx2r201n3m201o6o201s2l201t2l201u2l201w3m201x3m201y3m2k1t2l2r202m2n2n3m2o3m2p5n202q6o2r1w2s2l2t2l2u3m2v3t2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w3t3x3t3y3t3z3m4k5n4l4m4m4m4n4m4o4s4p4m4q4m4r4s4s4y4t2r4u3m4v4m4w3x4x5t4y4s4z4s5k3x5l4s5m4m5n3r5o3x5p4s5q4m5r5t5s4m5t3x5u3x5v2l5w1w5x2l5y3t5z3m6k2l6l3m6m3m6n2w6o3m6p2w6q2l6r3m6s3r6t1w6u1w6v3m6w1w6x4y6y3r6z3m7k3m7l3m7m2r7n2r7o1w7p3r7q2w7r4m7s3m7t2w7u2r7v2n7w1q7x2n7y3t202l3mcl4mal2ram3man3mao3map3mar3mas2lat4uau1uav3maw3way4uaz2lbk2sbl3t'fof'6obo2lbp3tbq3mbr1tbs2lbu1ybv3mbz3mck4m202k3mcm4mcn4mco4mcp4mcq5ycr4mcs4mct4mcu4mcv4mcw2r2m3rcy2rcz2rdl4sdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek3mel3mem3men3meo3mep3meq4ser2wes2wet2weu2wev2wew1wex1wey1wez1wfl3rfm3mfn3mfo3mfp3mfq3mfr3tfs3mft3rfu3rfv3rfw3rfz2w203k6o212m6o2dw2l2cq2l3t3m3u2l17s3x19m3m}'kerning'{cl{4qu5kt5qt5rs17ss5ts}201s{201ss}201t{cks4lscmscnscoscpscls2wu2yu201ts}201x{2wu2yu}2k{201ts}2w{4qx5kx5ou5qx5rs17su5tu}2x{17su5tu5ou}2y{4qx5kx5ou5qx5rs17ss5ts}'fof'-6ofn{17sw5tw5ou5qw5rs}7t{cksclscmscnscoscps4ls}3u{17su5tu5os5qs}3v{17su5tu5os5qs}7p{17su5tu}ck{4qu5kt5qt5rs17ss5ts}4l{4qu5kt5qt5rs17ss5ts}cm{4qu5kt5qt5rs17ss5ts}cn{4qu5kt5qt5rs17ss5ts}co{4qu5kt5qt5rs17ss5ts}cp{4qu5kt5qt5rs17ss5ts}6l{4qu5ou5qw5rt17su5tu}5q{ckuclucmucnucoucpu4lu}5r{ckuclucmucnucoucpu4lu}7q{cksclscmscnscoscps4ls}6p{4qu5ou5qw5rt17sw5tw}ek{4qu5ou5qw5rt17su5tu}el{4qu5ou5qw5rt17su5tu}em{4qu5ou5qw5rt17su5tu}en{4qu5ou5qw5rt17su5tu}eo{4qu5ou5qw5rt17su5tu}ep{4qu5ou5qw5rt17su5tu}es{17ss5ts5qs4qu}et{4qu5ou5qw5rt17sw5tw}eu{4qu5ou5qw5rt17ss5ts}ev{17ss5ts5qs4qu}6z{17sw5tw5ou5qw5rs}fm{17sw5tw5ou5qw5rs}7n{201ts}fo{17sw5tw5ou5qw5rs}fp{17sw5tw5ou5qw5rs}fq{17sw5tw5ou5qw5rs}7r{cksclscmscnscoscps4ls}fs{17sw5tw5ou5qw5rs}ft{17su5tu}fu{17su5tu}fv{17su5tu}fw{17su5tu}fz{cksclscmscnscoscps4ls}}}"),
+ "Helvetica-Bold": uncompress("{'widths'{k3s2q4scx1w201n3r201o6o201s1w201t1w201u1w201w3m201x3m201y3m2k1w2l2l202m2n2n3r2o3r2p5t202q6o2r1s2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v2l3w3u3x3u3y3u3z3x4k6l4l4s4m4s4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3r4v4s4w3x4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v2l5w1w5x2l5y3u5z3r6k2l6l3r6m3x6n3r6o3x6p3r6q2l6r3x6s3x6t1w6u1w6v3r6w1w6x5t6y3x6z3x7k3x7l3x7m2r7n3r7o2l7p3x7q3r7r4y7s3r7t3r7u3m7v2r7w1w7x2r7y3u202l3rcl4sal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3xbq3rbr1wbs2lbu2obv3rbz3xck4s202k3rcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw1w2m2zcy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3res3ret3reu3rev3rew1wex1wey1wez1wfl3xfm3xfn3xfo3xfp3xfq3xfr3ufs3xft3xfu3xfv3xfw3xfz3r203k6o212m6o2dw2l2cq2l3t3r3u2l17s4m19m3r}'kerning'{cl{4qs5ku5ot5qs17sv5tv}201t{2ww4wy2yw}201w{2ks}201x{2ww4wy2yw}2k{201ts201xs}2w{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}2x{5ow5qs}2y{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}'fof'-6o7p{17su5tu5ot}ck{4qs5ku5ot5qs17sv5tv}4l{4qs5ku5ot5qs17sv5tv}cm{4qs5ku5ot5qs17sv5tv}cn{4qs5ku5ot5qs17sv5tv}co{4qs5ku5ot5qs17sv5tv}cp{4qs5ku5ot5qs17sv5tv}6l{17st5tt5os}17s{2kwclvcmvcnvcovcpv4lv4wwckv}5o{2kucltcmtcntcotcpt4lt4wtckt}5q{2ksclscmscnscoscps4ls4wvcks}5r{2ks4ws}5t{2kwclvcmvcnvcovcpv4lv4wwckv}eo{17st5tt5os}fu{17su5tu5ot}6p{17ss5ts}ek{17st5tt5os}el{17st5tt5os}em{17st5tt5os}en{17st5tt5os}6o{201ts}ep{17st5tt5os}es{17ss5ts}et{17ss5ts}eu{17ss5ts}ev{17ss5ts}6z{17su5tu5os5qt}fm{17su5tu5os5qt}fn{17su5tu5os5qt}fo{17su5tu5os5qt}fp{17su5tu5os5qt}fq{17su5tu5os5qt}fs{17su5tu5os5qt}ft{17su5tu5ot}7m{5os}fv{17su5tu5ot}fw{17su5tu5ot}}}"),
+ Courier: uncompress("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"),
+ "Courier-BoldOblique": uncompress("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"),
+ "Times-Bold": uncompress("{'widths'{k3q2q5ncx2r201n3m201o6o201s2l201t2l201u2l201w3m201x3m201y3m2k1t2l2l202m2n2n3m2o3m2p6o202q6o2r1w2s2l2t2l2u3m2v3t2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w3t3x3t3y3t3z3m4k5x4l4s4m4m4n4s4o4s4p4m4q3x4r4y4s4y4t2r4u3m4v4y4w4m4x5y4y4s4z4y5k3x5l4y5m4s5n3r5o4m5p4s5q4s5r6o5s4s5t4s5u4m5v2l5w1w5x2l5y3u5z3m6k2l6l3m6m3r6n2w6o3r6p2w6q2l6r3m6s3r6t1w6u2l6v3r6w1w6x5n6y3r6z3m7k3r7l3r7m2w7n2r7o2l7p3r7q3m7r4s7s3m7t3m7u2w7v2r7w1q7x2r7y3o202l3mcl4sal2lam3man3mao3map3mar3mas2lat4uau1yav3maw3tay4uaz2lbk2sbl3t'fof'6obo2lbp3rbr1tbs2lbu2lbv3mbz3mck4s202k3mcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw2r2m3rcy2rcz2rdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3rek3mel3mem3men3meo3mep3meq4ser2wes2wet2weu2wev2wew1wex1wey1wez1wfl3rfm3mfn3mfo3mfp3mfq3mfr3tfs3mft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3m3u2l17s4s19m3m}'kerning'{cl{4qt5ks5ot5qy5rw17sv5tv}201t{cks4lscmscnscoscpscls4wv}2k{201ts}2w{4qu5ku7mu5os5qx5ru17su5tu}2x{17su5tu5ou5qs}2y{4qv5kv7mu5ot5qz5ru17su5tu}'fof'-6o7t{cksclscmscnscoscps4ls}3u{17su5tu5os5qu}3v{17su5tu5os5qu}fu{17su5tu5ou5qu}7p{17su5tu5ou5qu}ck{4qt5ks5ot5qy5rw17sv5tv}4l{4qt5ks5ot5qy5rw17sv5tv}cm{4qt5ks5ot5qy5rw17sv5tv}cn{4qt5ks5ot5qy5rw17sv5tv}co{4qt5ks5ot5qy5rw17sv5tv}cp{4qt5ks5ot5qy5rw17sv5tv}6l{17st5tt5ou5qu}17s{ckuclucmucnucoucpu4lu4wu}5o{ckuclucmucnucoucpu4lu4wu}5q{ckzclzcmzcnzcozcpz4lz4wu}5r{ckxclxcmxcnxcoxcpx4lx4wu}5t{ckuclucmucnucoucpu4lu4wu}7q{ckuclucmucnucoucpu4lu}6p{17sw5tw5ou5qu}ek{17st5tt5qu}el{17st5tt5ou5qu}em{17st5tt5qu}en{17st5tt5qu}eo{17st5tt5qu}ep{17st5tt5ou5qu}es{17ss5ts5qu}et{17sw5tw5ou5qu}eu{17sw5tw5ou5qu}ev{17ss5ts5qu}6z{17sw5tw5ou5qu5rs}fm{17sw5tw5ou5qu5rs}fn{17sw5tw5ou5qu5rs}fo{17sw5tw5ou5qu5rs}fp{17sw5tw5ou5qu5rs}fq{17sw5tw5ou5qu5rs}7r{cktcltcmtcntcotcpt4lt5os}fs{17sw5tw5ou5qu5rs}ft{17su5tu5ou5qu}7m{5os}fv{17su5tu5ou5qu}fw{17su5tu5ou5qu}fz{cksclscmscnscoscps4ls}}}"),
+ Symbol: uncompress("{'widths'{k3uaw4r19m3m2k1t2l2l202m2y2n3m2p5n202q6o3k3m2s2l2t2l2v3r2w1t3m3m2y1t2z1wbk2sbl3r'fof'6o3n3m3o3m3p3m3q3m3r3m3s3m3t3m3u1w3v1w3w3r3x3r3y3r3z2wbp3t3l3m5v2l5x2l5z3m2q4yfr3r7v3k7w1o7x3k}'kerning'{'fof'-6o}}"),
+ Helvetica: uncompress("{'widths'{k3p2q4mcx1w201n3r201o6o201s1q201t1q201u1q201w2l201x2l201y2l2k1w2l1w202m2n2n3r2o3r2p5t202q6o2r1n2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v1w3w3u3x3u3y3u3z3r4k6p4l4m4m4m4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3m4v4m4w3r4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v1w5w1w5x1w5y2z5z3r6k2l6l3r6m3r6n3m6o3r6p3r6q1w6r3r6s3r6t1q6u1q6v3m6w1q6x5n6y3r6z3r7k3r7l3r7m2l7n3m7o1w7p3r7q3m7r4s7s3m7t3m7u3m7v2l7w1u7x2l7y3u202l3rcl4mal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3rbr1wbs2lbu2obv3rbz3xck4m202k3rcm4mcn4mco4mcp4mcq6ocr4scs4mct4mcu4mcv4mcw1w2m2ncy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3mes3ret3reu3rev3rew1wex1wey1wez1wfl3rfm3rfn3rfo3rfp3rfq3rfr3ufs3xft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3r3u1w17s4m19m3r}'kerning'{5q{4wv}cl{4qs5kw5ow5qs17sv5tv}201t{2wu4w1k2yu}201x{2wu4wy2yu}17s{2ktclucmucnu4otcpu4lu4wycoucku}2w{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}2x{17sy5ty5oy5qs}2y{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}'fof'-6o7p{17sv5tv5ow}ck{4qs5kw5ow5qs17sv5tv}4l{4qs5kw5ow5qs17sv5tv}cm{4qs5kw5ow5qs17sv5tv}cn{4qs5kw5ow5qs17sv5tv}co{4qs5kw5ow5qs17sv5tv}cp{4qs5kw5ow5qs17sv5tv}6l{17sy5ty5ow}do{17st5tt}4z{17st5tt}7s{fst}dm{17st5tt}dn{17st5tt}5o{ckwclwcmwcnwcowcpw4lw4wv}dp{17st5tt}dq{17st5tt}7t{5ow}ds{17st5tt}5t{2ktclucmucnu4otcpu4lu4wycoucku}fu{17sv5tv5ow}6p{17sy5ty5ow5qs}ek{17sy5ty5ow}el{17sy5ty5ow}em{17sy5ty5ow}en{5ty}eo{17sy5ty5ow}ep{17sy5ty5ow}es{17sy5ty5qs}et{17sy5ty5ow5qs}eu{17sy5ty5ow5qs}ev{17sy5ty5ow5qs}6z{17sy5ty5ow5qs}fm{17sy5ty5ow5qs}fn{17sy5ty5ow5qs}fo{17sy5ty5ow5qs}fp{17sy5ty5qs}fq{17sy5ty5ow5qs}7r{5ow}fs{17sy5ty5ow5qs}ft{17sv5tv5ow}7m{5ow}fv{17sv5tv5ow}fw{17sv5tv5ow}}}"),
+ "Helvetica-BoldOblique": uncompress("{'widths'{k3s2q4scx1w201n3r201o6o201s1w201t1w201u1w201w3m201x3m201y3m2k1w2l2l202m2n2n3r2o3r2p5t202q6o2r1s2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v2l3w3u3x3u3y3u3z3x4k6l4l4s4m4s4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3r4v4s4w3x4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v2l5w1w5x2l5y3u5z3r6k2l6l3r6m3x6n3r6o3x6p3r6q2l6r3x6s3x6t1w6u1w6v3r6w1w6x5t6y3x6z3x7k3x7l3x7m2r7n3r7o2l7p3x7q3r7r4y7s3r7t3r7u3m7v2r7w1w7x2r7y3u202l3rcl4sal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3xbq3rbr1wbs2lbu2obv3rbz3xck4s202k3rcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw1w2m2zcy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3res3ret3reu3rev3rew1wex1wey1wez1wfl3xfm3xfn3xfo3xfp3xfq3xfr3ufs3xft3xfu3xfv3xfw3xfz3r203k6o212m6o2dw2l2cq2l3t3r3u2l17s4m19m3r}'kerning'{cl{4qs5ku5ot5qs17sv5tv}201t{2ww4wy2yw}201w{2ks}201x{2ww4wy2yw}2k{201ts201xs}2w{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}2x{5ow5qs}2y{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}'fof'-6o7p{17su5tu5ot}ck{4qs5ku5ot5qs17sv5tv}4l{4qs5ku5ot5qs17sv5tv}cm{4qs5ku5ot5qs17sv5tv}cn{4qs5ku5ot5qs17sv5tv}co{4qs5ku5ot5qs17sv5tv}cp{4qs5ku5ot5qs17sv5tv}6l{17st5tt5os}17s{2kwclvcmvcnvcovcpv4lv4wwckv}5o{2kucltcmtcntcotcpt4lt4wtckt}5q{2ksclscmscnscoscps4ls4wvcks}5r{2ks4ws}5t{2kwclvcmvcnvcovcpv4lv4wwckv}eo{17st5tt5os}fu{17su5tu5ot}6p{17ss5ts}ek{17st5tt5os}el{17st5tt5os}em{17st5tt5os}en{17st5tt5os}6o{201ts}ep{17st5tt5os}es{17ss5ts}et{17ss5ts}eu{17ss5ts}ev{17ss5ts}6z{17su5tu5os5qt}fm{17su5tu5os5qt}fn{17su5tu5os5qt}fo{17su5tu5os5qt}fp{17su5tu5os5qt}fq{17su5tu5os5qt}fs{17su5tu5os5qt}ft{17su5tu5ot}7m{5os}fv{17su5tu5ot}fw{17su5tu5ot}}}"),
+ ZapfDingbats: uncompress("{'widths'{k4u2k1w'fof'6o}'kerning'{'fof'-6o}}"),
+ "Courier-Bold": uncompress("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"),
+ "Times-Italic": uncompress("{'widths'{k3n2q4ycx2l201n3m201o5t201s2l201t2l201u2l201w3r201x3r201y3r2k1t2l2l202m2n2n3m2o3m2p5n202q5t2r1p2s2l2t2l2u3m2v4n2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w4n3x4n3y4n3z3m4k5w4l3x4m3x4n4m4o4s4p3x4q3x4r4s4s4s4t2l4u2w4v4m4w3r4x5n4y4m4z4s5k3x5l4s5m3x5n3m5o3r5p4s5q3x5r5n5s3x5t3r5u3r5v2r5w1w5x2r5y2u5z3m6k2l6l3m6m3m6n2w6o3m6p2w6q1w6r3m6s3m6t1w6u1w6v2w6w1w6x4s6y3m6z3m7k3m7l3m7m2r7n2r7o1w7p3m7q2w7r4m7s2w7t2w7u2r7v2s7w1v7x2s7y3q202l3mcl3xal2ram3man3mao3map3mar3mas2lat4wau1vav3maw4nay4waz2lbk2sbl4n'fof'6obo2lbp3mbq3obr1tbs2lbu1zbv3mbz3mck3x202k3mcm3xcn3xco3xcp3xcq5tcr4mcs3xct3xcu3xcv3xcw2l2m2ucy2lcz2ldl4mdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek3mel3mem3men3meo3mep3meq4mer2wes2wet2weu2wev2wew1wex1wey1wez1wfl3mfm3mfn3mfo3mfp3mfq3mfr4nfs3mft3mfu3mfv3mfw3mfz2w203k6o212m6m2dw2l2cq2l3t3m3u2l17s3r19m3m}'kerning'{cl{5kt4qw}201s{201sw}201t{201tw2wy2yy6q-t}201x{2wy2yy}2k{201tw}2w{7qs4qy7rs5ky7mw5os5qx5ru17su5tu}2x{17ss5ts5os}2y{7qs4qy7rs5ky7mw5os5qx5ru17su5tu}'fof'-6o6t{17ss5ts5qs}7t{5os}3v{5qs}7p{17su5tu5qs}ck{5kt4qw}4l{5kt4qw}cm{5kt4qw}cn{5kt4qw}co{5kt4qw}cp{5kt4qw}6l{4qs5ks5ou5qw5ru17su5tu}17s{2ks}5q{ckvclvcmvcnvcovcpv4lv}5r{ckuclucmucnucoucpu4lu}5t{2ks}6p{4qs5ks5ou5qw5ru17su5tu}ek{4qs5ks5ou5qw5ru17su5tu}el{4qs5ks5ou5qw5ru17su5tu}em{4qs5ks5ou5qw5ru17su5tu}en{4qs5ks5ou5qw5ru17su5tu}eo{4qs5ks5ou5qw5ru17su5tu}ep{4qs5ks5ou5qw5ru17su5tu}es{5ks5qs4qs}et{4qs5ks5ou5qw5ru17su5tu}eu{4qs5ks5qw5ru17su5tu}ev{5ks5qs4qs}ex{17ss5ts5qs}6z{4qv5ks5ou5qw5ru17su5tu}fm{4qv5ks5ou5qw5ru17su5tu}fn{4qv5ks5ou5qw5ru17su5tu}fo{4qv5ks5ou5qw5ru17su5tu}fp{4qv5ks5ou5qw5ru17su5tu}fq{4qv5ks5ou5qw5ru17su5tu}7r{5os}fs{4qv5ks5ou5qw5ru17su5tu}ft{17su5tu5qs}fu{17su5tu5qs}fv{17su5tu5qs}fw{17su5tu5qs}}}"),
+ "Times-Roman": uncompress("{'widths'{k3n2q4ycx2l201n3m201o6o201s2l201t2l201u2l201w2w201x2w201y2w2k1t2l2l202m2n2n3m2o3m2p5n202q6o2r1m2s2l2t2l2u3m2v3s2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v1w3w3s3x3s3y3s3z2w4k5w4l4s4m4m4n4m4o4s4p3x4q3r4r4s4s4s4t2l4u2r4v4s4w3x4x5t4y4s4z4s5k3r5l4s5m4m5n3r5o3x5p4s5q4s5r5y5s4s5t4s5u3x5v2l5w1w5x2l5y2z5z3m6k2l6l2w6m3m6n2w6o3m6p2w6q2l6r3m6s3m6t1w6u1w6v3m6w1w6x4y6y3m6z3m7k3m7l3m7m2l7n2r7o1w7p3m7q3m7r4s7s3m7t3m7u2w7v3k7w1o7x3k7y3q202l3mcl4sal2lam3man3mao3map3mar3mas2lat4wau1vav3maw3say4waz2lbk2sbl3s'fof'6obo2lbp3mbq2xbr1tbs2lbu1zbv3mbz2wck4s202k3mcm4scn4sco4scp4scq5tcr4mcs3xct3xcu3xcv3xcw2l2m2tcy2lcz2ldl4sdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek2wel2wem2wen2weo2wep2weq4mer2wes2wet2weu2wev2wew1wex1wey1wez1wfl3mfm3mfn3mfo3mfp3mfq3mfr3sfs3mft3mfu3mfv3mfw3mfz3m203k6o212m6m2dw2l2cq2l3t3m3u1w17s4s19m3m}'kerning'{cl{4qs5ku17sw5ou5qy5rw201ss5tw201ws}201s{201ss}201t{ckw4lwcmwcnwcowcpwclw4wu201ts}2k{201ts}2w{4qs5kw5os5qx5ru17sx5tx}2x{17sw5tw5ou5qu}2y{4qs5kw5os5qx5ru17sx5tx}'fof'-6o7t{ckuclucmucnucoucpu4lu5os5rs}3u{17su5tu5qs}3v{17su5tu5qs}7p{17sw5tw5qs}ck{4qs5ku17sw5ou5qy5rw201ss5tw201ws}4l{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cm{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cn{4qs5ku17sw5ou5qy5rw201ss5tw201ws}co{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cp{4qs5ku17sw5ou5qy5rw201ss5tw201ws}6l{17su5tu5os5qw5rs}17s{2ktclvcmvcnvcovcpv4lv4wuckv}5o{ckwclwcmwcnwcowcpw4lw4wu}5q{ckyclycmycnycoycpy4ly4wu5ms}5r{cktcltcmtcntcotcpt4lt4ws}5t{2ktclvcmvcnvcovcpv4lv4wuckv}7q{cksclscmscnscoscps4ls}6p{17su5tu5qw5rs}ek{5qs5rs}el{17su5tu5os5qw5rs}em{17su5tu5os5qs5rs}en{17su5qs5rs}eo{5qs5rs}ep{17su5tu5os5qw5rs}es{5qs}et{17su5tu5qw5rs}eu{17su5tu5qs5rs}ev{5qs}6z{17sv5tv5os5qx5rs}fm{5os5qt5rs}fn{17sv5tv5os5qx5rs}fo{17sv5tv5os5qx5rs}fp{5os5qt5rs}fq{5os5qt5rs}7r{ckuclucmucnucoucpu4lu5os}fs{17sv5tv5os5qx5rs}ft{17ss5ts5qs}fu{17sw5tw5qs}fv{17sw5tw5qs}fw{17ss5ts5qs}fz{ckuclucmucnucoucpu4lu5os5rs}}}"),
+ "Helvetica-Oblique": uncompress("{'widths'{k3p2q4mcx1w201n3r201o6o201s1q201t1q201u1q201w2l201x2l201y2l2k1w2l1w202m2n2n3r2o3r2p5t202q6o2r1n2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v1w3w3u3x3u3y3u3z3r4k6p4l4m4m4m4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3m4v4m4w3r4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v1w5w1w5x1w5y2z5z3r6k2l6l3r6m3r6n3m6o3r6p3r6q1w6r3r6s3r6t1q6u1q6v3m6w1q6x5n6y3r6z3r7k3r7l3r7m2l7n3m7o1w7p3r7q3m7r4s7s3m7t3m7u3m7v2l7w1u7x2l7y3u202l3rcl4mal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3rbr1wbs2lbu2obv3rbz3xck4m202k3rcm4mcn4mco4mcp4mcq6ocr4scs4mct4mcu4mcv4mcw1w2m2ncy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3mes3ret3reu3rev3rew1wex1wey1wez1wfl3rfm3rfn3rfo3rfp3rfq3rfr3ufs3xft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3r3u1w17s4m19m3r}'kerning'{5q{4wv}cl{4qs5kw5ow5qs17sv5tv}201t{2wu4w1k2yu}201x{2wu4wy2yu}17s{2ktclucmucnu4otcpu4lu4wycoucku}2w{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}2x{17sy5ty5oy5qs}2y{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}'fof'-6o7p{17sv5tv5ow}ck{4qs5kw5ow5qs17sv5tv}4l{4qs5kw5ow5qs17sv5tv}cm{4qs5kw5ow5qs17sv5tv}cn{4qs5kw5ow5qs17sv5tv}co{4qs5kw5ow5qs17sv5tv}cp{4qs5kw5ow5qs17sv5tv}6l{17sy5ty5ow}do{17st5tt}4z{17st5tt}7s{fst}dm{17st5tt}dn{17st5tt}5o{ckwclwcmwcnwcowcpw4lw4wv}dp{17st5tt}dq{17st5tt}7t{5ow}ds{17st5tt}5t{2ktclucmucnu4otcpu4lu4wycoucku}fu{17sv5tv5ow}6p{17sy5ty5ow5qs}ek{17sy5ty5ow}el{17sy5ty5ow}em{17sy5ty5ow}en{5ty}eo{17sy5ty5ow}ep{17sy5ty5ow}es{17sy5ty5qs}et{17sy5ty5ow5qs}eu{17sy5ty5ow5qs}ev{17sy5ty5ow5qs}6z{17sy5ty5ow5qs}fm{17sy5ty5ow5qs}fn{17sy5ty5ow5qs}fo{17sy5ty5ow5qs}fp{17sy5ty5qs}fq{17sy5ty5ow5qs}7r{5ow}fs{17sy5ty5ow5qs}ft{17sv5tv5ow}7m{5ow}fv{17sv5tv5ow}fw{17sv5tv5ow}}}")
+ }
+ };
+ /*
+ This event handler is fired when a new jsPDF object is initialized
+ This event handler appends metrics data to standard fonts within
+ that jsPDF instance. The metrics are mapped over Unicode character
+ codes, NOT CIDs or other codes matching the StandardEncoding table of the
+ standard PDF fonts.
+ Future:
+ Also included is the encoding maping table, converting Unicode (UCS-2, UTF-16)
+ char codes to StandardEncoding character codes. The encoding table is to be used
+ somewhere around "pdfEscape" call.
+ */
+
+ API.events.push(["addFont", function (data) {
+ var font = data.font;
+ var metrics,
+ unicode_section,
+ encoding = "Unicode",
+ encodingBlock;
+ metrics = fontMetrics[encoding][font.postScriptName];
+
+ if (metrics) {
+ if (font.metadata[encoding]) {
+ unicode_section = font.metadata[encoding];
+ } else {
+ unicode_section = font.metadata[encoding] = {};
+ }
+
+ unicode_section.widths = metrics.widths;
+ unicode_section.kerning = metrics.kerning;
+ }
+
+ encodingBlock = encodings[encoding][font.postScriptName];
+
+ if (encodingBlock) {
+ if (font.metadata[encoding]) {
+ unicode_section = font.metadata[encoding];
+ } else {
+ unicode_section = font.metadata[encoding] = {};
+ }
+
+ unicode_section.encoding = encodingBlock;
+
+ if (encodingBlock.codePages && encodingBlock.codePages.length) {
+ font.encoding = encodingBlock.codePages[0];
+ }
+ }
+ }]); // end of adding event handler
+ })(jsPDF.API);
+
+ /**
+ * @license
+ * Licensed under the MIT License.
+ * http://opensource.org/licenses/mit-license
+ */
+
+ /**
+ * @name ttfsupport
+ * @module
+ */
+ (function (jsPDF, global) {
+
+ jsPDF.API.events.push(["addFont", function (data) {
+ var font = data.font;
+ var instance = data.instance;
+
+ if (typeof instance !== "undefined" && instance.existsFileInVFS(font.postScriptName)) {
+ var file = instance.getFileFromVFS(font.postScriptName);
+
+ if (typeof file !== "string") {
+ throw new Error("Font is not stored as string-data in vFS, import fonts or remove declaration doc.addFont('" + font.postScriptName + "').");
+ }
+
+ font.metadata = jsPDF.API.TTFFont.open(font.postScriptName, font.fontName, file, font.encoding);
+ font.metadata.Unicode = font.metadata.Unicode || {
+ encoding: {},
+ kerning: {},
+ widths: []
+ };
+ font.metadata.glyIdsUsed = [0];
+ } else if (font.isStandardFont === false) {
+ throw new Error("Font does not exist in vFS, import fonts or remove declaration doc.addFont('" + font.postScriptName + "').");
+ }
+ }]); // end of adding event handler
+ })(jsPDF, typeof self !== "undefined" && self || typeof global !== "undefined" && global || typeof window !== "undefined" && window || Function("return this")());
+
+ /** @license
+ * Copyright (c) 2012 Willow Systems Corporation, willow-systems.com
+ *
+ *
+ * ====================================================================
+ */
+
+ /**
+ * jsPDF SVG plugin
+ *
+ * @name svg
+ * @module
+ */
+ (function (jsPDFAPI) {
+ /**
+ * Parses SVG XML and converts only some of the SVG elements into
+ * PDF elements.
+ *
+ * Supports:
+ * paths
+ *
+ * @name addSvg
+ * @public
+ * @function
+ * @param {string} SVG-Data as Text
+ * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
+ * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
+ * @param {number} width of SVG (in units declared at inception of PDF document)
+ * @param {number} height of SVG (in units declared at inception of PDF document)
+ * @returns {Object} jsPDF-instance
+ */
+
+ jsPDFAPI.addSvg = function (svgtext, x, y, w, h) {
+ // 'this' is _jsPDF object returned when jsPDF is inited (new jsPDF())
+ var undef;
+
+ if (x === undef || y === undef) {
+ throw new Error("addSVG needs values for 'x' and 'y'");
+ }
+
+ function InjectCSS(cssbody, document) {
+ var styletag = document.createElement("style");
+ styletag.type = "text/css";
+
+ if (styletag.styleSheet) {
+ // ie
+ styletag.styleSheet.cssText = cssbody;
+ } else {
+ // others
+ styletag.appendChild(document.createTextNode(cssbody));
+ }
+
+ document.getElementsByTagName("head")[0].appendChild(styletag);
+ }
+
+ function createWorkerNode(document) {
+ var frameID = "childframe",
+ // Date.now().toString() + '_' + (Math.random() * 100).toString()
+ frame = document.createElement("iframe");
+ InjectCSS(".jsPDF_sillysvg_iframe {display:none;position:absolute;}", document);
+ frame.name = frameID;
+ frame.setAttribute("width", 0);
+ frame.setAttribute("height", 0);
+ frame.setAttribute("frameborder", "0");
+ frame.setAttribute("scrolling", "no");
+ frame.setAttribute("seamless", "seamless");
+ frame.setAttribute("class", "jsPDF_sillysvg_iframe");
+ document.body.appendChild(frame);
+ return frame;
+ }
+
+ function attachSVGToWorkerNode(svgtext, frame) {
+ var framedoc = (frame.contentWindow || frame.contentDocument).document;
+ framedoc.write(svgtext);
+ framedoc.close();
+ return framedoc.getElementsByTagName("svg")[0];
+ }
+
+ function convertPathToPDFLinesArgs(path) {
+ // - starting coordinate pair
+ // - array of arrays of vector shifts (2-len for line, 6 len for bezier)
+ // - scale array [horizontal, vertical] ratios
+ // - style (stroke, fill, both)
+
+ var x = parseFloat(path[1]),
+ y = parseFloat(path[2]),
+ vectors = [],
+ position = 3,
+ len = path.length;
+
+ while (position < len) {
+ if (path[position] === "c") {
+ vectors.push([parseFloat(path[position + 1]), parseFloat(path[position + 2]), parseFloat(path[position + 3]), parseFloat(path[position + 4]), parseFloat(path[position + 5]), parseFloat(path[position + 6])]);
+ position += 7;
+ } else if (path[position] === "l") {
+ vectors.push([parseFloat(path[position + 1]), parseFloat(path[position + 2])]);
+ position += 3;
+ } else {
+ position += 1;
+ }
+ }
+
+ return [x, y, vectors];
+ }
+
+ var workernode = createWorkerNode(document),
+ svgnode = attachSVGToWorkerNode(svgtext, workernode),
+ scale = [1, 1],
+ svgw = parseFloat(svgnode.getAttribute("width")),
+ svgh = parseFloat(svgnode.getAttribute("height"));
+
+ if (svgw && svgh) {
+ // setting both w and h makes image stretch to size.
+ // this may distort the image, but fits your demanded size
+ if (w && h) {
+ scale = [w / svgw, h / svgh];
+ } // if only one is set, that value is set as max and SVG
+ // is scaled proportionately.
+ else if (w) {
+ scale = [w / svgw, w / svgw];
+ } else if (h) {
+ scale = [h / svgh, h / svgh];
+ }
+ }
+
+ var i,
+ l,
+ tmp,
+ linesargs,
+ items = svgnode.childNodes;
+
+ for (i = 0, l = items.length; i < l; i++) {
+ tmp = items[i];
+
+ if (tmp.tagName && tmp.tagName.toUpperCase() === "PATH") {
+ linesargs = convertPathToPDFLinesArgs(tmp.getAttribute("d").split(" ")); // path start x coordinate
+
+ linesargs[0] = linesargs[0] * scale[0] + x; // where x is upper left X of image
+ // path start y coordinate
+
+ linesargs[1] = linesargs[1] * scale[1] + y; // where y is upper left Y of image
+ // the rest of lines are vectors. these will adjust with scale value auto.
+
+ this.lines.call(this, linesargs[2], // lines
+ linesargs[0], // starting x
+ linesargs[1], // starting y
+ scale);
+ }
+ } // clean up
+ // workernode.parentNode.removeChild(workernode)
+
+
+ return this;
+ }; //fallback
+
+
+ jsPDFAPI.addSVG = jsPDFAPI.addSvg;
+ /**
+ * Parses SVG XML and saves it as image into the PDF.
+ *
+ * Depends on canvas-element and canvg
+ *
+ * @name addSvgAsImage
+ * @public
+ * @function
+ * @param {string} SVG-Data as Text
+ * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page
+ * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page
+ * @param {number} width of SVG-Image (in units declared at inception of PDF document)
+ * @param {number} height of SVG-Image (in units declared at inception of PDF document)
+ * @param {string} alias of SVG-Image (if used multiple times)
+ * @param {string} compression of the generated JPEG, can have the values 'NONE', 'FAST', 'MEDIUM' and 'SLOW'
+ * @param {number} rotation of the image in degrees (0-359)
+ *
+ * @returns jsPDF jsPDF-instance
+ */
+
+ jsPDFAPI.addSvgAsImage = function (svg, x, y, w, h, alias, compression, rotation) {
+ if (isNaN(x) || isNaN(y)) {
+ console.error("jsPDF.addSvgAsImage: Invalid coordinates", arguments);
+ throw new Error("Invalid coordinates passed to jsPDF.addSvgAsImage");
+ }
+
+ if (isNaN(w) || isNaN(h)) {
+ console.error("jsPDF.addSvgAsImage: Invalid measurements", arguments);
+ throw new Error("Invalid measurements (width and/or height) passed to jsPDF.addSvgAsImage");
+ }
+
+ var canvas = document.createElement("canvas");
+ canvas.width = w;
+ canvas.height = h;
+ var ctx = canvas.getContext("2d");
+ ctx.fillStyle = "#fff"; /// set white fill style
+
+ ctx.fillRect(0, 0, canvas.width, canvas.height); //load a svg snippet in the canvas with id = 'drawingArea'
+
+ canvg(canvas, svg, {
+ ignoreMouse: true,
+ ignoreAnimation: true,
+ ignoreDimensions: true,
+ ignoreClear: true
+ });
+ this.addImage(canvas.toDataURL("image/jpeg", 1.0), x, y, w, h, compression, rotation);
+ return this;
+ };
+ })(jsPDF.API);
+
+ /**
+ * @license
+ * ====================================================================
+ * Copyright (c) 2013 Eduardo Menezes de Morais, eduardo.morais@usp.br
+ *
+ *
+ * ====================================================================
+ */
+
+ /**
+ * jsPDF total_pages plugin
+ * @name total_pages
+ * @module
+ */
+ (function (jsPDFAPI) {
+ /**
+ * @name putTotalPages
+ * @function
+ * @param {string} pageExpression Regular Expression
+ * @returns {jsPDF} jsPDF-instance
+ */
+
+ jsPDFAPI.putTotalPages = function (pageExpression) {
+
+ var replaceExpression = new RegExp(pageExpression, "g");
+
+ for (var n = 1; n <= this.internal.getNumberOfPages(); n++) {
+ for (var i = 0; i < this.internal.pages[n].length; i++) {
+ this.internal.pages[n][i] = this.internal.pages[n][i].replace(replaceExpression, this.internal.getNumberOfPages());
+ }
+ }
+
+ return this;
+ };
+ })(jsPDF.API);
+
+ /**
+ * jsPDF viewerPreferences Plugin
+ * @author Aras Abbasi (github.com/arasabbasi)
+ * Licensed under the MIT License.
+ * http://opensource.org/licenses/mit-license
+ */
+
+ /**
+ * Adds the ability to set ViewerPreferences and by thus
+ * controlling the way the document is to be presented on the
+ * screen or in print.
+ * @name viewerpreferences
+ * @module
+ */
+ (function (jsPDFAPI) {
+ /**
+ * Set the ViewerPreferences of the generated PDF
+ *
+ * @name viewerPreferences
+ * @function
+ * @public
+ * @param {Object} options Array with the ViewerPreferences
+ * Example: doc.viewerPreferences({"FitWindow":true});
+ *
+ * You can set following preferences:
+ *
+ * HideToolbar (boolean)
+ * Default value: false
+ *
+ * HideMenubar (boolean)
+ * Default value: false.
+ *
+ * HideWindowUI (boolean)
+ * Default value: false.
+ *
+ * FitWindow (boolean)
+ * Default value: false.
+ *
+ * CenterWindow (boolean)
+ * Default value: false
+ *
+ * DisplayDocTitle (boolean)
+ * Default value: false.
+ *
+ * NonFullScreenPageMode (string)
+ * Possible values: UseNone, UseOutlines, UseThumbs, UseOC
+ * Default value: UseNone
+ *
+ * Direction (string)
+ * Possible values: L2R, R2L
+ * Default value: L2R.
+ *
+ * ViewArea (string)
+ * Possible values: MediaBox, CropBox, TrimBox, BleedBox, ArtBox
+ * Default value: CropBox.
+ *
+ * ViewClip (string)
+ * Possible values: MediaBox, CropBox, TrimBox, BleedBox, ArtBox
+ * Default value: CropBox
+ *
+ * PrintArea (string)
+ * Possible values: MediaBox, CropBox, TrimBox, BleedBox, ArtBox
+ * Default value: CropBox
+ *
+ * PrintClip (string)
+ * Possible values: MediaBox, CropBox, TrimBox, BleedBox, ArtBox
+ * Default value: CropBox.
+ *
+ * PrintScaling (string)
+ * Possible values: AppDefault, None
+ * Default value: AppDefault.
+ *
+ * Duplex (string)
+ * Possible values: Simplex, DuplexFlipLongEdge, DuplexFlipShortEdge
+ * Default value: none
+ *
+ * PickTrayByPDFSize (boolean)
+ * Default value: false
+ *
+ * PrintPageRange (Array)
+ * Example: [[1,5], [7,9]]
+ * Default value: as defined by PDF viewer application
+ *
+ * NumCopies (Number)
+ * Possible values: 1, 2, 3, 4, 5
+ * Default value: 1
+ *
+ * For more information see the PDF Reference, sixth edition on Page 577
+ * @param {boolean} doReset True to reset the settings
+ * @function
+ * @returns jsPDF jsPDF-instance
+ * @example
+ * var doc = new jsPDF()
+ * doc.text('This is a test', 10, 10)
+ * doc.viewerPreferences({'FitWindow': true}, true)
+ * doc.save("viewerPreferences.pdf")
+ *
+ * // Example printing 10 copies, using cropbox, and hiding UI.
+ * doc.viewerPreferences({
+ * 'HideWindowUI': true,
+ * 'PrintArea': 'CropBox',
+ * 'NumCopies': 10
+ * })
+ */
+
+ jsPDFAPI.viewerPreferences = function (options, doReset) {
+ options = options || {};
+ doReset = doReset || false;
+ var configuration;
+ var configurationTemplate = {
+ HideToolbar: {
+ defaultValue: false,
+ value: false,
+ type: "boolean",
+ explicitSet: false,
+ valueSet: [true, false],
+ pdfVersion: 1.3
+ },
+ HideMenubar: {
+ defaultValue: false,
+ value: false,
+ type: "boolean",
+ explicitSet: false,
+ valueSet: [true, false],
+ pdfVersion: 1.3
+ },
+ HideWindowUI: {
+ defaultValue: false,
+ value: false,
+ type: "boolean",
+ explicitSet: false,
+ valueSet: [true, false],
+ pdfVersion: 1.3
+ },
+ FitWindow: {
+ defaultValue: false,
+ value: false,
+ type: "boolean",
+ explicitSet: false,
+ valueSet: [true, false],
+ pdfVersion: 1.3
+ },
+ CenterWindow: {
+ defaultValue: false,
+ value: false,
+ type: "boolean",
+ explicitSet: false,
+ valueSet: [true, false],
+ pdfVersion: 1.3
+ },
+ DisplayDocTitle: {
+ defaultValue: false,
+ value: false,
+ type: "boolean",
+ explicitSet: false,
+ valueSet: [true, false],
+ pdfVersion: 1.4
+ },
+ NonFullScreenPageMode: {
+ defaultValue: "UseNone",
+ value: "UseNone",
+ type: "name",
+ explicitSet: false,
+ valueSet: ["UseNone", "UseOutlines", "UseThumbs", "UseOC"],
+ pdfVersion: 1.3
+ },
+ Direction: {
+ defaultValue: "L2R",
+ value: "L2R",
+ type: "name",
+ explicitSet: false,
+ valueSet: ["L2R", "R2L"],
+ pdfVersion: 1.3
+ },
+ ViewArea: {
+ defaultValue: "CropBox",
+ value: "CropBox",
+ type: "name",
+ explicitSet: false,
+ valueSet: ["MediaBox", "CropBox", "TrimBox", "BleedBox", "ArtBox"],
+ pdfVersion: 1.4
+ },
+ ViewClip: {
+ defaultValue: "CropBox",
+ value: "CropBox",
+ type: "name",
+ explicitSet: false,
+ valueSet: ["MediaBox", "CropBox", "TrimBox", "BleedBox", "ArtBox"],
+ pdfVersion: 1.4
+ },
+ PrintArea: {
+ defaultValue: "CropBox",
+ value: "CropBox",
+ type: "name",
+ explicitSet: false,
+ valueSet: ["MediaBox", "CropBox", "TrimBox", "BleedBox", "ArtBox"],
+ pdfVersion: 1.4
+ },
+ PrintClip: {
+ defaultValue: "CropBox",
+ value: "CropBox",
+ type: "name",
+ explicitSet: false,
+ valueSet: ["MediaBox", "CropBox", "TrimBox", "BleedBox", "ArtBox"],
+ pdfVersion: 1.4
+ },
+ PrintScaling: {
+ defaultValue: "AppDefault",
+ value: "AppDefault",
+ type: "name",
+ explicitSet: false,
+ valueSet: ["AppDefault", "None"],
+ pdfVersion: 1.6
+ },
+ Duplex: {
+ defaultValue: "",
+ value: "none",
+ type: "name",
+ explicitSet: false,
+ valueSet: ["Simplex", "DuplexFlipShortEdge", "DuplexFlipLongEdge", "none"],
+ pdfVersion: 1.7
+ },
+ PickTrayByPDFSize: {
+ defaultValue: false,
+ value: false,
+ type: "boolean",
+ explicitSet: false,
+ valueSet: [true, false],
+ pdfVersion: 1.7
+ },
+ PrintPageRange: {
+ defaultValue: "",
+ value: "",
+ type: "array",
+ explicitSet: false,
+ valueSet: null,
+ pdfVersion: 1.7
+ },
+ NumCopies: {
+ defaultValue: 1,
+ value: 1,
+ type: "integer",
+ explicitSet: false,
+ valueSet: null,
+ pdfVersion: 1.7
+ }
+ };
+ var configurationKeys = Object.keys(configurationTemplate);
+ var rangeArray = [];
+ var i = 0;
+ var j = 0;
+ var k = 0;
+ var isValid = true;
+ var method;
+ var value;
+
+ function arrayContainsElement(array, element) {
+ var iterator;
+ var result = false;
+
+ for (iterator = 0; iterator < array.length; iterator += 1) {
+ if (array[iterator] === element) {
+ result = true;
+ }
+ }
+
+ return result;
+ }
+
+ if (this.internal.viewerpreferences === undefined) {
+ this.internal.viewerpreferences = {};
+ this.internal.viewerpreferences.configuration = JSON.parse(JSON.stringify(configurationTemplate));
+ this.internal.viewerpreferences.isSubscribed = false;
+ }
+
+ configuration = this.internal.viewerpreferences.configuration;
+
+ if (options === "reset" || doReset === true) {
+ var len = configurationKeys.length;
+
+ for (k = 0; k < len; k += 1) {
+ configuration[configurationKeys[k]].value = configuration[configurationKeys[k]].defaultValue;
+ configuration[configurationKeys[k]].explicitSet = false;
+ }
+ }
+
+ if (_typeof(options) === "object") {
+ for (method in options) {
+ value = options[method];
+
+ if (arrayContainsElement(configurationKeys, method) && value !== undefined) {
+ if (configuration[method].type === "boolean" && typeof value === "boolean") {
+ configuration[method].value = value;
+ } else if (configuration[method].type === "name" && arrayContainsElement(configuration[method].valueSet, value)) {
+ configuration[method].value = value;
+ } else if (configuration[method].type === "integer" && Number.isInteger(value)) {
+ configuration[method].value = value;
+ } else if (configuration[method].type === "array") {
+ for (i = 0; i < value.length; i += 1) {
+ isValid = true;
+
+ if (value[i].length === 1 && typeof value[i][0] === "number") {
+ rangeArray.push(String(value[i]));
+ } else if (value[i].length > 1) {
+ for (j = 0; j < value[i].length; j += 1) {
+ if (typeof value[i][j] !== "number") {
+ isValid = false;
+ }
+ }
+
+ if (isValid === true) {
+ rangeArray.push(String(value[i].join("-")));
+ }
+ }
+ }
+
+ configuration[method].value = String(rangeArray);
+ } else {
+ configuration[method].value = configuration[method].defaultValue;
+ }
+
+ configuration[method].explicitSet = true;
+ }
+ }
+ }
+
+ if (this.internal.viewerpreferences.isSubscribed === false) {
+ this.internal.events.subscribe("putCatalog", function () {
+ var pdfDict = [];
+ var vPref;
+
+ for (vPref in configuration) {
+ if (configuration[vPref].explicitSet === true) {
+ if (configuration[vPref].type === "name") {
+ pdfDict.push("/" + vPref + " /" + configuration[vPref].value);
+ } else {
+ pdfDict.push("/" + vPref + " " + configuration[vPref].value);
+ }
+ }
+ }
+
+ if (pdfDict.length !== 0) {
+ this.internal.write("/ViewerPreferences\n<<\n" + pdfDict.join("\n") + "\n>>");
+ }
+ });
+ this.internal.viewerpreferences.isSubscribed = true;
+ }
+
+ this.internal.viewerpreferences.configuration = configuration;
+ return this;
+ };
+ })(jsPDF.API);
+
+ /** ====================================================================
+ * jsPDF XMP metadata plugin
+ * Copyright (c) 2016 Jussi Utunen, u-jussi@suomi24.fi
+ *
+ *
+ * ====================================================================
+ */
+
+ /*global jsPDF */
+
+ /**
+ * @name xmp_metadata
+ * @module
+ */
+ (function (jsPDFAPI) {
+
+ var xmpmetadata = "";
+ var xmpnamespaceuri = "";
+ var metadata_object_number = "";
+ /**
+ * Adds XMP formatted metadata to PDF
+ *
+ * @name addMetadata
+ * @function
+ * @param {String} metadata The actual metadata to be added. The metadata shall be stored as XMP simple value. Note that if the metadata string contains XML markup characters "<", ">" or "&", those characters should be written using XML entities.
+ * @param {String} namespaceuri Sets the namespace URI for the metadata. Last character should be slash or hash.
+ * @returns {jsPDF} jsPDF-instance
+ */
+
+ jsPDFAPI.addMetadata = function (metadata, namespaceuri) {
+ xmpnamespaceuri = namespaceuri || "http://jspdf.default.namespaceuri/"; //The namespace URI for an XMP name shall not be empty
+
+ xmpmetadata = metadata;
+ this.internal.events.subscribe("postPutResources", function () {
+ if (!xmpmetadata) {
+ metadata_object_number = "";
+ } else {
+ var xmpmeta_beginning = '';
+ var rdf_beginning = '';
+ var rdf_ending = " ";
+ var xmpmeta_ending = " ";
+ var utf8_xmpmeta_beginning = unescape(encodeURIComponent(xmpmeta_beginning));
+ var utf8_rdf_beginning = unescape(encodeURIComponent(rdf_beginning));
+ var utf8_metadata = unescape(encodeURIComponent(xmpmetadata));
+ var utf8_rdf_ending = unescape(encodeURIComponent(rdf_ending));
+ var utf8_xmpmeta_ending = unescape(encodeURIComponent(xmpmeta_ending));
+ var total_len = utf8_rdf_beginning.length + utf8_metadata.length + utf8_rdf_ending.length + utf8_xmpmeta_beginning.length + utf8_xmpmeta_ending.length;
+ metadata_object_number = this.internal.newObject();
+ this.internal.write("<< /Type /Metadata /Subtype /XML /Length " + total_len + " >>");
+ this.internal.write("stream");
+ this.internal.write(utf8_xmpmeta_beginning + utf8_rdf_beginning + utf8_metadata + utf8_rdf_ending + utf8_xmpmeta_ending);
+ this.internal.write("endstream");
+ this.internal.write("endobj");
+ }
+ });
+ this.internal.events.subscribe("putCatalog", function () {
+ if (metadata_object_number) {
+ this.internal.write("/Metadata " + metadata_object_number + " 0 R");
+ }
+ });
+ return this;
+ };
+ })(jsPDF.API);
+
+ /**
+ * @name utf8
+ * @module
+ */
+ (function (jsPDF, global) {
+
+ var jsPDFAPI = jsPDF.API;
+ /**************************************************/
+
+ /* function : toHex */
+
+ /* comment : Replace str with a hex string. */
+
+ /**************************************************/
+
+ function toHex(str) {
+ var hex = "";
+
+ for (var i = 0; i < str.length; i++) {
+ hex += "" + str.charCodeAt(i).toString(16);
+ }
+
+ return hex;
+ }
+ /***************************************************************************************************/
+
+ /* function : pdfEscape16 */
+
+ /* comment : The character id of a 2-byte string is converted to a hexadecimal number by obtaining */
+
+ /* the corresponding glyph id and width, and then adding padding to the string. */
+
+ /***************************************************************************************************/
+
+
+ var pdfEscape16 = jsPDFAPI.pdfEscape16 = function (text, font) {
+ var widths = font.metadata.Unicode.widths;
+ var padz = ["", "0", "00", "000", "0000"];
+ var ar = [""];
+
+ for (var i = 0, l = text.length, t; i < l; ++i) {
+ t = font.metadata.characterToGlyph(text.charCodeAt(i));
+ font.metadata.glyIdsUsed.push(t);
+ font.metadata.toUnicode[t] = text.charCodeAt(i);
+
+ if (widths.indexOf(t) == -1) {
+ widths.push(t);
+ widths.push([parseInt(font.metadata.widthOfGlyph(t), 10)]);
+ }
+
+ if (t == "0") {
+ //Spaces are not allowed in cmap.
+ return ar.join("");
+ } else {
+ t = t.toString(16);
+ ar.push(padz[4 - t.length], t);
+ }
+ }
+
+ return ar.join("");
+ };
+
+ var toUnicodeCmap = function toUnicodeCmap(map) {
+ var code, codes, range, unicode, unicodeMap, _i, _len;
+
+ unicodeMap = "/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo <<\n /Registry (Adobe)\n /Ordering (UCS)\n /Supplement 0\n>> def\n/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n1 begincodespacerange\n<0000>\nendcodespacerange";
+ codes = Object.keys(map).sort(function (a, b) {
+ return a - b;
+ });
+ range = [];
+
+ for (_i = 0, _len = codes.length; _i < _len; _i++) {
+ code = codes[_i];
+
+ if (range.length >= 100) {
+ unicodeMap += "\n" + range.length + " beginbfchar\n" + range.join("\n") + "\nendbfchar";
+ range = [];
+ }
+
+ unicode = ("0000" + map[code].toString(16)).slice(-4);
+ code = ("0000" + (+code).toString(16)).slice(-4);
+ range.push("<" + code + "><" + unicode + ">");
+ }
+
+ if (range.length) {
+ unicodeMap += "\n" + range.length + " beginbfchar\n" + range.join("\n") + "\nendbfchar\n";
+ }
+
+ unicodeMap += "endcmap\nCMapName currentdict /CMap defineresource pop\nend\nend";
+ return unicodeMap;
+ };
+
+ var identityHFunction = function identityHFunction(font, out, newObject, putStream) {
+ if (font.metadata instanceof jsPDF.API.TTFFont && font.encoding === "Identity-H") {
+ //Tag with Identity-H
+ var widths = font.metadata.Unicode.widths;
+ var data = font.metadata.subset.encode(font.metadata.glyIdsUsed, 1);
+ var pdfOutput = data;
+ var pdfOutput2 = "";
+
+ for (var i = 0; i < pdfOutput.length; i++) {
+ pdfOutput2 += String.fromCharCode(pdfOutput[i]);
+ }
+
+ var fontTable = newObject();
+ out("<<");
+ out("/Length " + pdfOutput2.length);
+ out("/Length1 " + pdfOutput2.length);
+ out(">>");
+ putStream(pdfOutput2);
+ out("endobj");
+ var cmap = newObject();
+ var cmapData = toUnicodeCmap(font.metadata.toUnicode);
+ out("<<");
+ out("/Length " + cmapData.length);
+ out("/Length1 " + cmapData.length);
+ out(">>");
+ putStream(cmapData);
+ out("endobj");
+ var fontDescriptor = newObject();
+ out("<<");
+ out("/Type /FontDescriptor");
+ out("/FontName /" + font.fontName);
+ out("/FontFile2 " + fontTable + " 0 R");
+ out("/FontBBox " + jsPDF.API.PDFObject.convert(font.metadata.bbox));
+ out("/Flags " + font.metadata.flags);
+ out("/StemV " + font.metadata.stemV);
+ out("/ItalicAngle " + font.metadata.italicAngle);
+ out("/Ascent " + font.metadata.ascender);
+ out("/Descent " + font.metadata.decender);
+ out("/CapHeight " + font.metadata.capHeight);
+ out(">>");
+ out("endobj");
+ var DescendantFont = newObject();
+ out("<<");
+ out("/Type /Font");
+ out("/BaseFont /" + font.fontName);
+ out("/FontDescriptor " + fontDescriptor + " 0 R");
+ out("/W " + jsPDF.API.PDFObject.convert(widths));
+ out("/CIDToGIDMap /Identity");
+ out("/DW 1000");
+ out("/Subtype /CIDFontType2");
+ out("/CIDSystemInfo");
+ out("<<");
+ out("/Supplement 0");
+ out("/Registry (Adobe)");
+ out("/Ordering (" + font.encoding + ")");
+ out(">>");
+ out(">>");
+ out("endobj");
+ font.objectNumber = newObject();
+ out("<<");
+ out("/Type /Font");
+ out("/Subtype /Type0");
+ out("/ToUnicode " + cmap + " 0 R");
+ out("/BaseFont /" + font.fontName);
+ out("/Encoding /" + font.encoding);
+ out("/DescendantFonts [" + DescendantFont + " 0 R]");
+ out(">>");
+ out("endobj");
+ font.isAlreadyPutted = true;
+ }
+ };
+
+ jsPDFAPI.events.push(["putFont", function (args) {
+ identityHFunction(args.font, args.out, args.newObject, args.putStream);
+ }]);
+
+ var winAnsiEncodingFunction = function winAnsiEncodingFunction(font, out, newObject, putStream) {
+ if (font.metadata instanceof jsPDF.API.TTFFont && font.encoding === "WinAnsiEncoding") {
+ //Tag with WinAnsi encoding
+ var widths = font.metadata.Unicode.widths;
+ var data = font.metadata.rawData;
+ var pdfOutput = data;
+ var pdfOutput2 = "";
+
+ for (var i = 0; i < pdfOutput.length; i++) {
+ pdfOutput2 += String.fromCharCode(pdfOutput[i]);
+ }
+
+ var fontTable = newObject();
+ putStream({
+ data: pdfOutput2,
+ addLength1: true
+ });
+ out("endobj");
+ var cmap = newObject();
+ var cmapData = toUnicodeCmap(font.metadata.toUnicode);
+ putStream({
+ data: cmapData,
+ addLength1: true
+ });
+ out("endobj");
+ var fontDescriptor = newObject();
+ out("<<");
+ out("/Descent " + font.metadata.decender);
+ out("/CapHeight " + font.metadata.capHeight);
+ out("/StemV " + font.metadata.stemV);
+ out("/Type /FontDescriptor");
+ out("/FontFile2 " + fontTable + " 0 R");
+ out("/Flags 96");
+ out("/FontBBox " + jsPDF.API.PDFObject.convert(font.metadata.bbox));
+ out("/FontName /" + font.fontName);
+ out("/ItalicAngle " + font.metadata.italicAngle);
+ out("/Ascent " + font.metadata.ascender);
+ out(">>");
+ out("endobj");
+ font.objectNumber = newObject();
+
+ for (var i = 0; i < font.metadata.hmtx.widths.length; i++) {
+ font.metadata.hmtx.widths[i] = parseInt(font.metadata.hmtx.widths[i] * (1000 / font.metadata.head.unitsPerEm)); //Change the width of Em units to Point units.
+ }
+
+ out("<>");
+ out("endobj");
+ font.isAlreadyPutted = true;
+ }
+ };
+
+ jsPDFAPI.events.push(["putFont", function (args) {
+ winAnsiEncodingFunction(args.font, args.out, args.newObject, args.putStream);
+ }]);
+
+ var utf8TextFunction = function utf8TextFunction(args) {
+ var text = args.text || "";
+ var x = args.x;
+ var y = args.y;
+ var options = args.options || {};
+ var mutex = args.mutex || {};
+ var pdfEscape = mutex.pdfEscape;
+ var activeFontKey = mutex.activeFontKey;
+ var fonts = mutex.fonts;
+ var key,
+ fontSize = mutex.activeFontSize;
+ var str = "",
+ s = 0,
+ cmapConfirm;
+ var strText = "";
+ var key = activeFontKey;
+ var encoding = fonts[key].encoding;
+
+ if (fonts[key].encoding !== "Identity-H") {
+ return {
+ text: text,
+ x: x,
+ y: y,
+ options: options,
+ mutex: mutex
+ };
+ }
+ strText = text;
+ key = activeFontKey;
+
+ if (Object.prototype.toString.call(text) === "[object Array]") {
+ strText = text[0];
+ }
+
+ for (s = 0; s < strText.length; s += 1) {
+ if (fonts[key].metadata.hasOwnProperty("cmap")) {
+ cmapConfirm = fonts[key].metadata.cmap.unicode.codeMap[strText[s].charCodeAt(0)];
+ /*
+ if (Object.prototype.toString.call(text) === '[object Array]') {
+ var i = 0;
+ // for (i = 0; i < text.length; i += 1) {
+ if (Object.prototype.toString.call(text[s]) === '[object Array]') {
+ cmapConfirm = fonts[key].metadata.cmap.unicode.codeMap[strText[s][0].charCodeAt(0)]; //Make sure the cmap has the corresponding glyph id
+ } else {
+
+ }
+ //}
+
+ } else {
+ cmapConfirm = fonts[key].metadata.cmap.unicode.codeMap[strText[s].charCodeAt(0)]; //Make sure the cmap has the corresponding glyph id
+ }*/
+ }
+
+ if (!cmapConfirm) {
+ if (strText[s].charCodeAt(0) < 256 && fonts[key].metadata.hasOwnProperty("Unicode")) {
+ str += strText[s];
+ } else {
+ str += "";
+ }
+ } else {
+ str += strText[s];
+ }
+ }
+
+ var result = "";
+
+ if (parseInt(key.slice(1)) < 14 || encoding === "WinAnsiEncoding") {
+ //For the default 13 font
+ result = toHex(pdfEscape(str, key));
+ } else if (encoding === "Identity-H") {
+ result = pdfEscape16(str, fonts[key]);
+ }
+
+ mutex.isHex = true;
+ return {
+ text: result,
+ x: x,
+ y: y,
+ options: options,
+ mutex: mutex
+ };
+ };
+
+ var utf8EscapeFunction = function utf8EscapeFunction(parms) {
+ var text = parms.text || "",
+ x = parms.x,
+ y = parms.y,
+ options = parms.options,
+ mutex = parms.mutex;
+ var lang = options.lang;
+ var tmpText = [];
+ var args = {
+ text: text,
+ x: x,
+ y: y,
+ options: options,
+ mutex: mutex
+ };
+
+ if (Object.prototype.toString.call(text) === "[object Array]") {
+ var i = 0;
+
+ for (i = 0; i < text.length; i += 1) {
+ if (Object.prototype.toString.call(text[i]) === "[object Array]") {
+ if (text[i].length === 3) {
+ tmpText.push([utf8TextFunction(Object.assign({}, args, {
+ text: text[i][0]
+ })).text, text[i][1], text[i][2]]);
+ } else {
+ tmpText.push(utf8TextFunction(Object.assign({}, args, {
+ text: text[i]
+ })).text);
+ }
+ } else {
+ tmpText.push(utf8TextFunction(Object.assign({}, args, {
+ text: text[i]
+ })).text);
+ }
+ }
+
+ parms.text = tmpText;
+ } else {
+ parms.text = utf8TextFunction(Object.assign({}, args, {
+ text: text
+ })).text;
+ }
+ };
+
+ jsPDFAPI.events.push(["postProcessText", utf8EscapeFunction]);
+ })(jsPDF, typeof self !== "undefined" && self || typeof global !== "undefined" && global || typeof window !== "undefined" && window || Function("return this")());
+
+ /**
+ * jsPDF virtual FileSystem functionality
+ *
+ * Licensed under the MIT License.
+ * http://opensource.org/licenses/mit-license
+ */
+
+ /**
+ * Use the vFS to handle files
+ *
+ * @name vFS
+ * @module
+ */
+ (function (jsPDFAPI) {
+
+ var vFS = {};
+ /**
+ * Check if the file exists in the vFS
+ *
+ * @name existsFileInVFS
+ * @function
+ * @param {string} Possible filename in the vFS.
+ * @returns {boolean}
+ * @example
+ * doc.existsFileInVFS("someFile.txt");
+ */
+
+ jsPDFAPI.existsFileInVFS = function (filename) {
+ return vFS.hasOwnProperty(filename);
+ };
+ /**
+ * Add a file to the vFS
+ *
+ * @name addFileToVFS
+ * @function
+ * @param {string} filename The name of the file which should be added.
+ * @param {string} filecontent The content of the file.
+ * @returns {jsPDF}
+ * @example
+ * doc.addFileToVFS("someFile.txt", "BADFACE1");
+ */
+
+
+ jsPDFAPI.addFileToVFS = function (filename, filecontent) {
+ vFS[filename] = filecontent;
+ return this;
+ };
+ /**
+ * Get the file from the vFS
+ *
+ * @name getFileFromVFS
+ * @function
+ * @returns {string} The name of the file which gets requested.
+ * @example
+ * doc.getFileFromVFS("someFile.txt");
+ */
+
+
+ jsPDFAPI.getFileFromVFS = function (filename) {
+ if (vFS.hasOwnProperty(filename)) {
+ return vFS[filename];
+ }
+
+ return null;
+ };
+ })(jsPDF.API);
+
+ /* Blob.js
+ * A Blob implementation.
+ * 2014-07-24
+ *
+ * By Eli Grey, http://eligrey.com
+ * By Devin Samarin, https://github.com/dsamarin
+ * License: X11/MIT
+ * See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md
+ */
+
+ /*global self, unescape */
+
+ /*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
+ plusplus: true */
+
+ /*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
+ (function (view) {
+
+ view.URL = view.URL || view.webkitURL;
+
+ if (view.Blob && view.URL) {
+ try {
+ new Blob();
+ return;
+ } catch (e) {}
+ } // Internally we use a BlobBuilder implementation to base Blob off of
+ // in order to support older browsers that only have BlobBuilder
+
+
+ var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || function (view) {
+ var get_class = function (object) {
+ return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
+ },
+ FakeBlobBuilder = function BlobBuilder() {
+ this.data = [];
+ },
+ FakeBlob = function Blob(data, type, encoding) {
+ this.data = data;
+ this.size = data.length;
+ this.type = type;
+ this.encoding = encoding;
+ },
+ FBB_proto = FakeBlobBuilder.prototype,
+ FB_proto = FakeBlob.prototype,
+ FileReaderSync = view.FileReaderSync,
+ FileException = function (type) {
+ this.code = this[this.name = type];
+ },
+ file_ex_codes = ("NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR " + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR").split(" "),
+ file_ex_code = file_ex_codes.length,
+ real_URL = view.URL || view.webkitURL || view,
+ real_create_object_URL = real_URL.createObjectURL,
+ real_revoke_object_URL = real_URL.revokeObjectURL,
+ URL = real_URL,
+ btoa = view.btoa,
+ atob = view.atob,
+ ArrayBuffer = view.ArrayBuffer,
+ Uint8Array = view.Uint8Array,
+ origin = /^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/;
+
+ FakeBlob.fake = FB_proto.fake = true;
+
+ while (file_ex_code--) {
+ FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
+ } // Polyfill URL
+
+
+ if (!real_URL.createObjectURL) {
+ URL = view.URL = function (uri) {
+ var uri_info = document.createElementNS("http://www.w3.org/1999/xhtml", "a"),
+ uri_origin;
+ uri_info.href = uri;
+
+ if (!("origin" in uri_info)) {
+ if (uri_info.protocol.toLowerCase() === "data:") {
+ uri_info.origin = null;
+ } else {
+ uri_origin = uri.match(origin);
+ uri_info.origin = uri_origin && uri_origin[1];
+ }
+ }
+
+ return uri_info;
+ };
+ }
+
+ URL.createObjectURL = function (blob) {
+ var type = blob.type,
+ data_URI_header;
+
+ if (type === null) {
+ type = "application/octet-stream";
+ }
+
+ if (blob instanceof FakeBlob) {
+ data_URI_header = "data:" + type;
+
+ if (blob.encoding === "base64") {
+ return data_URI_header + ";base64," + blob.data;
+ } else if (blob.encoding === "URI") {
+ return data_URI_header + "," + decodeURIComponent(blob.data);
+ }
+
+ if (btoa) {
+ return data_URI_header + ";base64," + btoa(blob.data);
+ } else {
+ return data_URI_header + "," + encodeURIComponent(blob.data);
+ }
+ } else if (real_create_object_URL) {
+ return real_create_object_URL.call(real_URL, blob);
+ }
+ };
+
+ URL.revokeObjectURL = function (object_URL) {
+ if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
+ real_revoke_object_URL.call(real_URL, object_URL);
+ }
+ };
+
+ FBB_proto.append = function (data
+ /*, endings*/
+ ) {
+ var bb = this.data; // decode data to a binary string
+
+ if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
+ var str = "",
+ buf = new Uint8Array(data),
+ i = 0,
+ buf_len = buf.length;
+
+ for (; i < buf_len; i++) {
+ str += String.fromCharCode(buf[i]);
+ }
+
+ bb.push(str);
+ } else if (get_class(data) === "Blob" || get_class(data) === "File") {
+ if (FileReaderSync) {
+ var fr = new FileReaderSync();
+ bb.push(fr.readAsBinaryString(data));
+ } else {
+ // async FileReader won't work as BlobBuilder is sync
+ throw new FileException("NOT_READABLE_ERR");
+ }
+ } else if (data instanceof FakeBlob) {
+ if (data.encoding === "base64" && atob) {
+ bb.push(atob(data.data));
+ } else if (data.encoding === "URI") {
+ bb.push(decodeURIComponent(data.data));
+ } else if (data.encoding === "raw") {
+ bb.push(data.data);
+ }
+ } else {
+ if (typeof data !== "string") {
+ data += ""; // convert unsupported types to strings
+ } // decode UTF-16 to binary string
+
+
+ bb.push(unescape(encodeURIComponent(data)));
+ }
+ };
+
+ FBB_proto.getBlob = function (type) {
+ if (!arguments.length) {
+ type = null;
+ }
+
+ return new FakeBlob(this.data.join(""), type, "raw");
+ };
+
+ FBB_proto.toString = function () {
+ return "[object BlobBuilder]";
+ };
+
+ FB_proto.slice = function (start, end, type) {
+ var args = arguments.length;
+
+ if (args < 3) {
+ type = null;
+ }
+
+ return new FakeBlob(this.data.slice(start, args > 1 ? end : this.data.length), type, this.encoding);
+ };
+
+ FB_proto.toString = function () {
+ return "[object Blob]";
+ };
+
+ FB_proto.close = function () {
+ this.size = 0;
+ delete this.data;
+ };
+
+ return FakeBlobBuilder;
+ }(view);
+
+ view.Blob = function (blobParts, options) {
+ var type = options ? options.type || "" : "";
+ var builder = new BlobBuilder();
+
+ if (blobParts) {
+ for (var i = 0, len = blobParts.length; i < len; i++) {
+ if (Uint8Array && blobParts[i] instanceof Uint8Array) {
+ builder.append(blobParts[i].buffer);
+ } else {
+ builder.append(blobParts[i]);
+ }
+ }
+ }
+
+ var blob = builder.getBlob(type);
+
+ if (!blob.slice && blob.webkitSlice) {
+ blob.slice = blob.webkitSlice;
+ }
+
+ return blob;
+ };
+
+ var getPrototypeOf = Object.getPrototypeOf || function (object) {
+ return object.__proto__;
+ };
+
+ view.Blob.prototype = getPrototypeOf(new view.Blob());
+ })(typeof self !== "undefined" && self || typeof window !== "undefined" && window || window.content || window);
+
+ /* FileSaver.js
+ * A saveAs() FileSaver implementation.
+ * 1.3.2
+ * 2016-06-16 18:25:19
+ *
+ * By Eli Grey, http://eligrey.com
+ * License: MIT
+ * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
+ */
+
+ /*global self */
+
+ /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
+
+ /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
+ var saveAs = saveAs || function (view) {
+
+ if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
+ return;
+ }
+
+ var doc = view.document // only get URL when necessary in case Blob.js hasn't overridden it yet
+ ,
+ get_URL = function () {
+ return view.URL || view.webkitURL || view;
+ },
+ save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a"),
+ can_use_save_link = "download" in save_link,
+ click = function (node) {
+ var event = new MouseEvent("click");
+ node.dispatchEvent(event);
+ },
+ is_safari = /constructor/i.test(view.HTMLElement) || view.safari,
+ is_chrome_ios = /CriOS\/[\d]+/.test(navigator.userAgent),
+ throw_outside = function (ex) {
+ (view.setImmediate || view.setTimeout)(function () {
+ throw ex;
+ }, 0);
+ },
+ force_saveable_type = "application/octet-stream" // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to
+ ,
+ arbitrary_revoke_timeout = 1000 * 40 // in ms
+ ,
+ revoke = function (file) {
+ var revoker = function () {
+ if (typeof file === "string") {
+ // file is an object URL
+ get_URL().revokeObjectURL(file);
+ } else {
+ // file is a File
+ file.remove();
+ }
+ };
+
+ setTimeout(revoker, arbitrary_revoke_timeout);
+ },
+ dispatch = function (filesaver, event_types, event) {
+ event_types = [].concat(event_types);
+ var i = event_types.length;
+
+ while (i--) {
+ var listener = filesaver["on" + event_types[i]];
+
+ if (typeof listener === "function") {
+ try {
+ listener.call(filesaver, event || filesaver);
+ } catch (ex) {
+ throw_outside(ex);
+ }
+ }
+ }
+ },
+ auto_bom = function (blob) {
+ // prepend BOM for UTF-8 XML and text/* types (including HTML)
+ // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
+ if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
+ return new Blob([String.fromCharCode(0xFEFF), blob], {
+ type: blob.type
+ });
+ }
+
+ return blob;
+ },
+ FileSaver = function (blob, name, no_auto_bom) {
+ if (!no_auto_bom) {
+ blob = auto_bom(blob);
+ } // First try a.download, then web filesystem, then object URLs
+
+
+ var filesaver = this,
+ type = blob.type,
+ force = type === force_saveable_type,
+ object_url,
+ dispatch_all = function () {
+ dispatch(filesaver, "writestart progress write writeend".split(" "));
+ } // on any filesys errors revert to saving with object URLs
+ ,
+ fs_error = function () {
+ if ((is_chrome_ios || force && is_safari) && view.FileReader) {
+ // Safari doesn't allow downloading of blob urls
+ var reader = new FileReader();
+
+ reader.onloadend = function () {
+ var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;');
+ var popup = view.open(url, '_blank');
+ if (!popup) view.location.href = url;
+ url = undefined; // release reference before dispatching
+
+ filesaver.readyState = filesaver.DONE;
+ dispatch_all();
+ };
+
+ reader.readAsDataURL(blob);
+ filesaver.readyState = filesaver.INIT;
+ return;
+ } // don't create more object URLs than needed
+
+
+ if (!object_url) {
+ object_url = get_URL().createObjectURL(blob);
+ }
+
+ if (force) {
+ view.location.href = object_url;
+ } else {
+ var opened = view.open(object_url, "_blank");
+
+ if (!opened) {
+ // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html
+ view.location.href = object_url;
+ }
+ }
+
+ filesaver.readyState = filesaver.DONE;
+ dispatch_all();
+ revoke(object_url);
+ };
+
+ filesaver.readyState = filesaver.INIT;
+
+ if (can_use_save_link) {
+ object_url = get_URL().createObjectURL(blob);
+ setTimeout(function () {
+ save_link.href = object_url;
+ save_link.download = name;
+ click(save_link);
+ dispatch_all();
+ revoke(object_url);
+ filesaver.readyState = filesaver.DONE;
+ });
+ return;
+ }
+
+ fs_error();
+ },
+ FS_proto = FileSaver.prototype,
+ saveAs = function (blob, name, no_auto_bom) {
+ return new FileSaver(blob, name || blob.name || "download", no_auto_bom);
+ }; // IE 10+ (native saveAs)
+
+
+ if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
+ return function (blob, name, no_auto_bom) {
+ name = name || blob.name || "download";
+
+ if (!no_auto_bom) {
+ blob = auto_bom(blob);
+ }
+
+ return navigator.msSaveOrOpenBlob(blob, name);
+ };
+ }
+
+ FS_proto.abort = function () {};
+
+ FS_proto.readyState = FS_proto.INIT = 0;
+ FS_proto.WRITING = 1;
+ FS_proto.DONE = 2;
+ FS_proto.error = FS_proto.onwritestart = FS_proto.onprogress = FS_proto.onwrite = FS_proto.onabort = FS_proto.onerror = FS_proto.onwriteend = null;
+ return saveAs;
+ }(typeof self !== "undefined" && self || typeof window !== "undefined" && window || window.content); // `self` is undefined in Firefox for Android content script context
+ // while `this` is nsIContentFrameMessageManager
+ // with an attribute `content` that corresponds to the window
+
+
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports.saveAs = saveAs;
+ } else if (typeof define !== "undefined" && define !== null && define.amd !== null) {
+ define("FileSaver.js", function () {
+ return saveAs;
+ });
+ }
+
+ // (c) Dean McNamee , 2013.
+ //
+ // https://github.com/deanm/omggif
+ //
+ //
+ //
+ // omggif is a JavaScript implementation of a GIF 89a encoder and decoder,
+ // including animation and compression. It does not rely on any specific
+ // underlying system, so should run in the browser, Node, or Plask.
+ function GifWriter(buf, width, height, gopts) {
+ var p = 0;
+ var gopts = gopts === undefined ? {} : gopts;
+ var loop_count = gopts.loop === undefined ? null : gopts.loop;
+ var global_palette = gopts.palette === undefined ? null : gopts.palette;
+ if (width <= 0 || height <= 0 || width > 65535 || height > 65535) throw "Width/Height invalid.";
+
+ function check_palette_and_num_colors(palette) {
+ var num_colors = palette.length;
+ if (num_colors < 2 || num_colors > 256 || num_colors & num_colors - 1) throw "Invalid code/color length, must be power of 2 and 2 .. 256.";
+ return num_colors;
+ } // - Header.
+
+
+ buf[p++] = 0x47;
+ buf[p++] = 0x49;
+ buf[p++] = 0x46; // GIF
+
+ buf[p++] = 0x38;
+ buf[p++] = 0x39;
+ buf[p++] = 0x61; // 89a
+ // Handling of Global Color Table (palette) and background index.
+
+ var gp_num_colors_pow2 = 0;
+ var background = 0;
+
+ if (global_palette !== null) {
+ var gp_num_colors = check_palette_and_num_colors(global_palette);
+
+ while (gp_num_colors >>= 1) ++gp_num_colors_pow2;
+
+ gp_num_colors = 1 << gp_num_colors_pow2;
+ --gp_num_colors_pow2;
+
+ if (gopts.background !== undefined) {
+ background = gopts.background;
+ if (background >= gp_num_colors) throw "Background index out of range."; // The GIF spec states that a background index of 0 should be ignored, so
+ // this is probably a mistake and you really want to set it to another
+ // slot in the palette. But actually in the end most browsers, etc end
+ // up ignoring this almost completely (including for dispose background).
+
+ if (background === 0) throw "Background index explicitly passed as 0.";
+ }
+ } // - Logical Screen Descriptor.
+ // NOTE(deanm): w/h apparently ignored by implementations, but set anyway.
+
+
+ buf[p++] = width & 0xff;
+ buf[p++] = width >> 8 & 0xff;
+ buf[p++] = height & 0xff;
+ buf[p++] = height >> 8 & 0xff; // NOTE: Indicates 0-bpp original color resolution (unused?).
+
+ buf[p++] = (global_palette !== null ? 0x80 : 0) | // Global Color Table Flag.
+ gp_num_colors_pow2; // NOTE: No sort flag (unused?).
+
+ buf[p++] = background; // Background Color Index.
+
+ buf[p++] = 0; // Pixel aspect ratio (unused?).
+ // - Global Color Table
+
+ if (global_palette !== null) {
+ for (var i = 0, il = global_palette.length; i < il; ++i) {
+ var rgb = global_palette[i];
+ buf[p++] = rgb >> 16 & 0xff;
+ buf[p++] = rgb >> 8 & 0xff;
+ buf[p++] = rgb & 0xff;
+ }
+ }
+
+ if (loop_count !== null) {
+ // Netscape block for looping.
+ if (loop_count < 0 || loop_count > 65535) throw "Loop count invalid."; // Extension code, label, and length.
+
+ buf[p++] = 0x21;
+ buf[p++] = 0xff;
+ buf[p++] = 0x0b; // NETSCAPE2.0
+
+ buf[p++] = 0x4e;
+ buf[p++] = 0x45;
+ buf[p++] = 0x54;
+ buf[p++] = 0x53;
+ buf[p++] = 0x43;
+ buf[p++] = 0x41;
+ buf[p++] = 0x50;
+ buf[p++] = 0x45;
+ buf[p++] = 0x32;
+ buf[p++] = 0x2e;
+ buf[p++] = 0x30; // Sub-block
+
+ buf[p++] = 0x03;
+ buf[p++] = 0x01;
+ buf[p++] = loop_count & 0xff;
+ buf[p++] = loop_count >> 8 & 0xff;
+ buf[p++] = 0x00; // Terminator.
+ }
+
+ var ended = false;
+
+ this.addFrame = function (x, y, w, h, indexed_pixels, opts) {
+ if (ended === true) {
+ --p;
+ ended = false;
+ } // Un-end.
+
+
+ opts = opts === undefined ? {} : opts; // TODO(deanm): Bounds check x, y. Do they need to be within the virtual
+ // canvas width/height, I imagine?
+
+ if (x < 0 || y < 0 || x > 65535 || y > 65535) throw "x/y invalid.";
+ if (w <= 0 || h <= 0 || w > 65535 || h > 65535) throw "Width/Height invalid.";
+ if (indexed_pixels.length < w * h) throw "Not enough pixels for the frame size.";
+ var using_local_palette = true;
+ var palette = opts.palette;
+
+ if (palette === undefined || palette === null) {
+ using_local_palette = false;
+ palette = global_palette;
+ }
+
+ if (palette === undefined || palette === null) throw "Must supply either a local or global palette.";
+ var num_colors = check_palette_and_num_colors(palette); // Compute the min_code_size (power of 2), destroying num_colors.
+
+ var min_code_size = 0;
+
+ while (num_colors >>= 1) ++min_code_size;
+
+ num_colors = 1 << min_code_size; // Now we can easily get it back.
+
+ var delay = opts.delay === undefined ? 0 : opts.delay; // From the spec:
+ // 0 - No disposal specified. The decoder is
+ // not required to take any action.
+ // 1 - Do not dispose. The graphic is to be left
+ // in place.
+ // 2 - Restore to background color. The area used by the
+ // graphic must be restored to the background color.
+ // 3 - Restore to previous. The decoder is required to
+ // restore the area overwritten by the graphic with
+ // what was there prior to rendering the graphic.
+ // 4-7 - To be defined.
+ // NOTE(deanm): Dispose background doesn't really work, apparently most
+ // browsers ignore the background palette index and clear to transparency.
+
+ var disposal = opts.disposal === undefined ? 0 : opts.disposal;
+ if (disposal < 0 || disposal > 3) // 4-7 is reserved.
+ throw "Disposal out of range.";
+ var use_transparency = false;
+ var transparent_index = 0;
+
+ if (opts.transparent !== undefined && opts.transparent !== null) {
+ use_transparency = true;
+ transparent_index = opts.transparent;
+ if (transparent_index < 0 || transparent_index >= num_colors) throw "Transparent color index.";
+ }
+
+ if (disposal !== 0 || use_transparency || delay !== 0) {
+ // - Graphics Control Extension
+ buf[p++] = 0x21;
+ buf[p++] = 0xf9; // Extension / Label.
+
+ buf[p++] = 4; // Byte size.
+
+ buf[p++] = disposal << 2 | (use_transparency === true ? 1 : 0);
+ buf[p++] = delay & 0xff;
+ buf[p++] = delay >> 8 & 0xff;
+ buf[p++] = transparent_index; // Transparent color index.
+
+ buf[p++] = 0; // Block Terminator.
+ } // - Image Descriptor
+
+
+ buf[p++] = 0x2c; // Image Seperator.
+
+ buf[p++] = x & 0xff;
+ buf[p++] = x >> 8 & 0xff; // Left.
+
+ buf[p++] = y & 0xff;
+ buf[p++] = y >> 8 & 0xff; // Top.
+
+ buf[p++] = w & 0xff;
+ buf[p++] = w >> 8 & 0xff;
+ buf[p++] = h & 0xff;
+ buf[p++] = h >> 8 & 0xff; // NOTE: No sort flag (unused?).
+ // TODO(deanm): Support interlace.
+
+ buf[p++] = using_local_palette === true ? 0x80 | min_code_size - 1 : 0; // - Local Color Table
+
+ if (using_local_palette === true) {
+ for (var i = 0, il = palette.length; i < il; ++i) {
+ var rgb = palette[i];
+ buf[p++] = rgb >> 16 & 0xff;
+ buf[p++] = rgb >> 8 & 0xff;
+ buf[p++] = rgb & 0xff;
+ }
+ }
+
+ p = GifWriterOutputLZWCodeStream(buf, p, min_code_size < 2 ? 2 : min_code_size, indexed_pixels);
+ };
+
+ this.end = function () {
+ if (ended === false) {
+ buf[p++] = 0x3b; // Trailer.
+
+ ended = true;
+ }
+
+ return p;
+ };
+ } // Main compression routine, palette indexes -> LZW code stream.
+ // |index_stream| must have at least one entry.
+
+
+ function GifWriterOutputLZWCodeStream(buf, p, min_code_size, index_stream) {
+ buf[p++] = min_code_size;
+ var cur_subblock = p++; // Pointing at the length field.
+
+ var clear_code = 1 << min_code_size;
+ var code_mask = clear_code - 1;
+ var eoi_code = clear_code + 1;
+ var next_code = eoi_code + 1;
+ var cur_code_size = min_code_size + 1; // Number of bits per code.
+
+ var cur_shift = 0; // We have at most 12-bit codes, so we should have to hold a max of 19
+ // bits here (and then we would write out).
+
+ var cur = 0;
+
+ function emit_bytes_to_buffer(bit_block_size) {
+ while (cur_shift >= bit_block_size) {
+ buf[p++] = cur & 0xff;
+ cur >>= 8;
+ cur_shift -= 8;
+
+ if (p === cur_subblock + 256) {
+ // Finished a subblock.
+ buf[cur_subblock] = 255;
+ cur_subblock = p++;
+ }
+ }
+ }
+
+ function emit_code(c) {
+ cur |= c << cur_shift;
+ cur_shift += cur_code_size;
+ emit_bytes_to_buffer(8);
+ } // I am not an expert on the topic, and I don't want to write a thesis.
+ // However, it is good to outline here the basic algorithm and the few data
+ // structures and optimizations here that make this implementation fast.
+ // The basic idea behind LZW is to build a table of previously seen runs
+ // addressed by a short id (herein called output code). All data is
+ // referenced by a code, which represents one or more values from the
+ // original input stream. All input bytes can be referenced as the same
+ // value as an output code. So if you didn't want any compression, you
+ // could more or less just output the original bytes as codes (there are
+ // some details to this, but it is the idea). In order to achieve
+ // compression, values greater then the input range (codes can be up to
+ // 12-bit while input only 8-bit) represent a sequence of previously seen
+ // inputs. The decompressor is able to build the same mapping while
+ // decoding, so there is always a shared common knowledge between the
+ // encoding and decoder, which is also important for "timing" aspects like
+ // how to handle variable bit width code encoding.
+ //
+ // One obvious but very important consequence of the table system is there
+ // is always a unique id (at most 12-bits) to map the runs. 'A' might be
+ // 4, then 'AA' might be 10, 'AAA' 11, 'AAAA' 12, etc. This relationship
+ // can be used for an effecient lookup strategy for the code mapping. We
+ // need to know if a run has been seen before, and be able to map that run
+ // to the output code. Since we start with known unique ids (input bytes),
+ // and then from those build more unique ids (table entries), we can
+ // continue this chain (almost like a linked list) to always have small
+ // integer values that represent the current byte chains in the encoder.
+ // This means instead of tracking the input bytes (AAAABCD) to know our
+ // current state, we can track the table entry for AAAABC (it is guaranteed
+ // to exist by the nature of the algorithm) and the next character D.
+ // Therefor the tuple of (table_entry, byte) is guaranteed to also be
+ // unique. This allows us to create a simple lookup key for mapping input
+ // sequences to codes (table indices) without having to store or search
+ // any of the code sequences. So if 'AAAA' has a table entry of 12, the
+ // tuple of ('AAAA', K) for any input byte K will be unique, and can be our
+ // key. This leads to a integer value at most 20-bits, which can always
+ // fit in an SMI value and be used as a fast sparse array / object key.
+ // Output code for the current contents of the index buffer.
+
+
+ var ib_code = index_stream[0] & code_mask; // Load first input index.
+
+ var code_table = {}; // Key'd on our 20-bit "tuple".
+
+ emit_code(clear_code); // Spec says first code should be a clear code.
+ // First index already loaded, process the rest of the stream.
+
+ for (var i = 1, il = index_stream.length; i < il; ++i) {
+ var k = index_stream[i] & code_mask;
+ var cur_key = ib_code << 8 | k; // (prev, k) unique tuple.
+
+ var cur_code = code_table[cur_key]; // buffer + k.
+ // Check if we have to create a new code table entry.
+
+ if (cur_code === undefined) {
+ // We don't have buffer + k.
+ // Emit index buffer (without k).
+ // This is an inline version of emit_code, because this is the core
+ // writing routine of the compressor (and V8 cannot inline emit_code
+ // because it is a closure here in a different context). Additionally
+ // we can call emit_byte_to_buffer less often, because we can have
+ // 30-bits (from our 31-bit signed SMI), and we know our codes will only
+ // be 12-bits, so can safely have 18-bits there without overflow.
+ // emit_code(ib_code);
+ cur |= ib_code << cur_shift;
+ cur_shift += cur_code_size;
+
+ while (cur_shift >= 8) {
+ buf[p++] = cur & 0xff;
+ cur >>= 8;
+ cur_shift -= 8;
+
+ if (p === cur_subblock + 256) {
+ // Finished a subblock.
+ buf[cur_subblock] = 255;
+ cur_subblock = p++;
+ }
+ }
+
+ if (next_code === 4096) {
+ // Table full, need a clear.
+ emit_code(clear_code);
+ next_code = eoi_code + 1;
+ cur_code_size = min_code_size + 1;
+ code_table = {};
+ } else {
+ // Table not full, insert a new entry.
+ // Increase our variable bit code sizes if necessary. This is a bit
+ // tricky as it is based on "timing" between the encoding and
+ // decoder. From the encoders perspective this should happen after
+ // we've already emitted the index buffer and are about to create the
+ // first table entry that would overflow our current code bit size.
+ if (next_code >= 1 << cur_code_size) ++cur_code_size;
+ code_table[cur_key] = next_code++; // Insert into code table.
+ }
+
+ ib_code = k; // Index buffer to single input k.
+ } else {
+ ib_code = cur_code; // Index buffer to sequence in code table.
+ }
+ }
+
+ emit_code(ib_code); // There will still be something in the index buffer.
+
+ emit_code(eoi_code); // End Of Information.
+ // Flush / finalize the sub-blocks stream to the buffer.
+
+ emit_bytes_to_buffer(1); // Finish the sub-blocks, writing out any unfinished lengths and
+ // terminating with a sub-block of length 0. If we have already started
+ // but not yet used a sub-block it can just become the terminator.
+
+ if (cur_subblock + 1 === p) {
+ // Started but unused.
+ buf[cur_subblock] = 0;
+ } else {
+ // Started and used, write length and additional terminator block.
+ buf[cur_subblock] = p - cur_subblock - 1;
+ buf[p++] = 0;
+ }
+
+ return p;
+ }
+
+ function GifReader(buf) {
+ var p = 0; // - Header (GIF87a or GIF89a).
+
+ if (buf[p++] !== 0x47 || buf[p++] !== 0x49 || buf[p++] !== 0x46 || buf[p++] !== 0x38 || (buf[p++] + 1 & 0xfd) !== 0x38 || buf[p++] !== 0x61) {
+ throw "Invalid GIF 87a/89a header.";
+ } // - Logical Screen Descriptor.
+
+
+ var width = buf[p++] | buf[p++] << 8;
+ var height = buf[p++] | buf[p++] << 8;
+ var pf0 = buf[p++]; // .
+
+ var global_palette_flag = pf0 >> 7;
+ var num_global_colors_pow2 = pf0 & 0x7;
+ var num_global_colors = 1 << num_global_colors_pow2 + 1;
+ var background = buf[p++];
+ buf[p++]; // Pixel aspect ratio (unused?).
+
+ var global_palette_offset = null;
+
+ if (global_palette_flag) {
+ global_palette_offset = p;
+ p += num_global_colors * 3; // Seek past palette.
+ }
+
+ var no_eof = true;
+ var frames = [];
+ var delay = 0;
+ var transparent_index = null;
+ var disposal = 0; // 0 - No disposal specified.
+
+ var loop_count = null;
+ this.width = width;
+ this.height = height;
+
+ while (no_eof && p < buf.length) {
+ switch (buf[p++]) {
+ case 0x21:
+ // Graphics Control Extension Block
+ switch (buf[p++]) {
+ case 0xff:
+ // Application specific block
+ // Try if it's a Netscape block (with animation loop counter).
+ if (buf[p] !== 0x0b || // 21 FF already read, check block size.
+ // NETSCAPE2.0
+ buf[p + 1] == 0x4e && buf[p + 2] == 0x45 && buf[p + 3] == 0x54 && buf[p + 4] == 0x53 && buf[p + 5] == 0x43 && buf[p + 6] == 0x41 && buf[p + 7] == 0x50 && buf[p + 8] == 0x45 && buf[p + 9] == 0x32 && buf[p + 10] == 0x2e && buf[p + 11] == 0x30 && // Sub-block
+ buf[p + 12] == 0x03 && buf[p + 13] == 0x01 && buf[p + 16] == 0) {
+ p += 14;
+ loop_count = buf[p++] | buf[p++] << 8;
+ p++; // Skip terminator.
+ } else {
+ // We don't know what it is, just try to get past it.
+ p += 12;
+
+ while (true) {
+ // Seek through subblocks.
+ var block_size = buf[p++];
+ if (block_size === 0) break;
+ p += block_size;
+ }
+ }
+
+ break;
+
+ case 0xf9:
+ // Graphics Control Extension
+ if (buf[p++] !== 0x4 || buf[p + 4] !== 0) throw "Invalid graphics extension block.";
+ var pf1 = buf[p++];
+ delay = buf[p++] | buf[p++] << 8;
+ transparent_index = buf[p++];
+ if ((pf1 & 1) === 0) transparent_index = null;
+ disposal = pf1 >> 2 & 0x7;
+ p++; // Skip terminator.
+
+ break;
+
+ case 0xfe:
+ // Comment Extension.
+ while (true) {
+ // Seek through subblocks.
+ var block_size = buf[p++];
+ if (block_size === 0) break; // console.log(buf.slice(p, p+block_size).toString('ascii'));
+
+ p += block_size;
+ }
+
+ break;
+
+ default:
+ throw "Unknown graphic control label: 0x" + buf[p - 1].toString(16);
+ }
+
+ break;
+
+ case 0x2c:
+ // Image Descriptor.
+ var x = buf[p++] | buf[p++] << 8;
+ var y = buf[p++] | buf[p++] << 8;
+ var w = buf[p++] | buf[p++] << 8;
+ var h = buf[p++] | buf[p++] << 8;
+ var pf2 = buf[p++];
+ var local_palette_flag = pf2 >> 7;
+ var interlace_flag = pf2 >> 6 & 1;
+ var num_local_colors_pow2 = pf2 & 0x7;
+ var num_local_colors = 1 << num_local_colors_pow2 + 1;
+ var palette_offset = global_palette_offset;
+ var has_local_palette = false;
+
+ if (local_palette_flag) {
+ var has_local_palette = true;
+ palette_offset = p; // Override with local palette.
+
+ p += num_local_colors * 3; // Seek past palette.
+ }
+
+ var data_offset = p;
+ p++; // codesize
+
+ while (true) {
+ var block_size = buf[p++];
+ if (block_size === 0) break;
+ p += block_size;
+ }
+
+ frames.push({
+ x: x,
+ y: y,
+ width: w,
+ height: h,
+ has_local_palette: has_local_palette,
+ palette_offset: palette_offset,
+ data_offset: data_offset,
+ data_length: p - data_offset,
+ transparent_index: transparent_index,
+ interlaced: !!interlace_flag,
+ delay: delay,
+ disposal: disposal
+ });
+ break;
+
+ case 0x3b:
+ // Trailer Marker (end of file).
+ no_eof = false;
+ break;
+
+ default:
+ throw "Unknown gif block: 0x" + buf[p - 1].toString(16);
+ break;
+ }
+ }
+
+ this.numFrames = function () {
+ return frames.length;
+ };
+
+ this.loopCount = function () {
+ return loop_count;
+ };
+
+ this.frameInfo = function (frame_num) {
+ if (frame_num < 0 || frame_num >= frames.length) throw "Frame index out of range.";
+ return frames[frame_num];
+ };
+
+ this.decodeAndBlitFrameBGRA = function (frame_num, pixels) {
+ var frame = this.frameInfo(frame_num);
+ var num_pixels = frame.width * frame.height;
+ var index_stream = new Uint8Array(num_pixels); // At most 8-bit indices.
+
+ GifReaderLZWOutputIndexStream(buf, frame.data_offset, index_stream, num_pixels);
+ var palette_offset = frame.palette_offset; // NOTE(deanm): It seems to be much faster to compare index to 256 than
+ // to === null. Not sure why, but CompareStub_EQ_STRICT shows up high in
+ // the profile, not sure if it's related to using a Uint8Array.
+
+ var trans = frame.transparent_index;
+ if (trans === null) trans = 256; // We are possibly just blitting to a portion of the entire frame.
+ // That is a subrect within the framerect, so the additional pixels
+ // must be skipped over after we finished a scanline.
+
+ var framewidth = frame.width;
+ var framestride = width - framewidth;
+ var xleft = framewidth; // Number of subrect pixels left in scanline.
+ // Output indicies of the top left and bottom right corners of the subrect.
+
+ var opbeg = (frame.y * width + frame.x) * 4;
+ var opend = ((frame.y + frame.height) * width + frame.x) * 4;
+ var op = opbeg;
+ var scanstride = framestride * 4; // Use scanstride to skip past the rows when interlacing. This is skipping
+ // 7 rows for the first two passes, then 3 then 1.
+
+ if (frame.interlaced === true) {
+ scanstride += (framewidth + framestride) * 4 * 7; // Pass 1.
+ }
+
+ var interlaceskip = 8; // Tracking the row interval in the current pass.
+
+ for (var i = 0, il = index_stream.length; i < il; ++i) {
+ var index = index_stream[i];
+
+ if (xleft === 0) {
+ // Beginning of new scan line
+ op += scanstride;
+ xleft = framewidth;
+
+ if (op >= opend) {
+ // Catch the wrap to switch passes when interlacing.
+ scanstride = framestride + (framewidth + framestride) * 4 * (interlaceskip - 1); // interlaceskip / 2 * 4 is interlaceskip << 1.
+
+ op = opbeg + (framewidth + framestride) * (interlaceskip << 1);
+ interlaceskip >>= 1;
+ }
+ }
+
+ if (index === trans) {
+ op += 4;
+ } else {
+ var r = buf[palette_offset + index * 3];
+ var g = buf[palette_offset + index * 3 + 1];
+ var b = buf[palette_offset + index * 3 + 2];
+ pixels[op++] = b;
+ pixels[op++] = g;
+ pixels[op++] = r;
+ pixels[op++] = 255;
+ }
+
+ --xleft;
+ }
+ }; // I will go to copy and paste hell one day...
+
+
+ this.decodeAndBlitFrameRGBA = function (frame_num, pixels) {
+ var frame = this.frameInfo(frame_num);
+ var num_pixels = frame.width * frame.height;
+ var index_stream = new Uint8Array(num_pixels); // At most 8-bit indices.
+
+ GifReaderLZWOutputIndexStream(buf, frame.data_offset, index_stream, num_pixels);
+ var palette_offset = frame.palette_offset; // NOTE(deanm): It seems to be much faster to compare index to 256 than
+ // to === null. Not sure why, but CompareStub_EQ_STRICT shows up high in
+ // the profile, not sure if it's related to using a Uint8Array.
+
+ var trans = frame.transparent_index;
+ if (trans === null) trans = 256; // We are possibly just blitting to a portion of the entire frame.
+ // That is a subrect within the framerect, so the additional pixels
+ // must be skipped over after we finished a scanline.
+
+ var framewidth = frame.width;
+ var framestride = width - framewidth;
+ var xleft = framewidth; // Number of subrect pixels left in scanline.
+ // Output indicies of the top left and bottom right corners of the subrect.
+
+ var opbeg = (frame.y * width + frame.x) * 4;
+ var opend = ((frame.y + frame.height) * width + frame.x) * 4;
+ var op = opbeg;
+ var scanstride = framestride * 4; // Use scanstride to skip past the rows when interlacing. This is skipping
+ // 7 rows for the first two passes, then 3 then 1.
+
+ if (frame.interlaced === true) {
+ scanstride += (framewidth + framestride) * 4 * 7; // Pass 1.
+ }
+
+ var interlaceskip = 8; // Tracking the row interval in the current pass.
+
+ for (var i = 0, il = index_stream.length; i < il; ++i) {
+ var index = index_stream[i];
+
+ if (xleft === 0) {
+ // Beginning of new scan line
+ op += scanstride;
+ xleft = framewidth;
+
+ if (op >= opend) {
+ // Catch the wrap to switch passes when interlacing.
+ scanstride = framestride + (framewidth + framestride) * 4 * (interlaceskip - 1); // interlaceskip / 2 * 4 is interlaceskip << 1.
+
+ op = opbeg + (framewidth + framestride) * (interlaceskip << 1);
+ interlaceskip >>= 1;
+ }
+ }
+
+ if (index === trans) {
+ op += 4;
+ } else {
+ var r = buf[palette_offset + index * 3];
+ var g = buf[palette_offset + index * 3 + 1];
+ var b = buf[palette_offset + index * 3 + 2];
+ pixels[op++] = r;
+ pixels[op++] = g;
+ pixels[op++] = b;
+ pixels[op++] = 255;
+ }
+
+ --xleft;
+ }
+ };
+ }
+
+ function GifReaderLZWOutputIndexStream(code_stream, p, output, output_length) {
+ var min_code_size = code_stream[p++];
+ var clear_code = 1 << min_code_size;
+ var eoi_code = clear_code + 1;
+ var next_code = eoi_code + 1;
+ var cur_code_size = min_code_size + 1; // Number of bits per code.
+ // NOTE: This shares the same name as the encoder, but has a different
+ // meaning here. Here this masks each code coming from the code stream.
+
+ var code_mask = (1 << cur_code_size) - 1;
+ var cur_shift = 0;
+ var cur = 0;
+ var op = 0; // Output pointer.
+
+ var subblock_size = code_stream[p++]; // TODO(deanm): Would using a TypedArray be any faster? At least it would
+ // solve the fast mode / backing store uncertainty.
+ // var code_table = Array(4096);
+
+ var code_table = new Int32Array(4096); // Can be signed, we only use 20 bits.
+
+ var prev_code = null; // Track code-1.
+
+ while (true) {
+ // Read up to two bytes, making sure we always 12-bits for max sized code.
+ while (cur_shift < 16) {
+ if (subblock_size === 0) break; // No more data to be read.
+
+ cur |= code_stream[p++] << cur_shift;
+ cur_shift += 8;
+
+ if (subblock_size === 1) {
+ // Never let it get to 0 to hold logic above.
+ subblock_size = code_stream[p++]; // Next subblock.
+ } else {
+ --subblock_size;
+ }
+ } // TODO(deanm): We should never really get here, we should have received
+ // and EOI.
+
+
+ if (cur_shift < cur_code_size) break;
+ var code = cur & code_mask;
+ cur >>= cur_code_size;
+ cur_shift -= cur_code_size; // TODO(deanm): Maybe should check that the first code was a clear code,
+ // at least this is what you're supposed to do. But actually our encoder
+ // now doesn't emit a clear code first anyway.
+
+ if (code === clear_code) {
+ // We don't actually have to clear the table. This could be a good idea
+ // for greater error checking, but we don't really do any anyway. We
+ // will just track it with next_code and overwrite old entries.
+ next_code = eoi_code + 1;
+ cur_code_size = min_code_size + 1;
+ code_mask = (1 << cur_code_size) - 1; // Don't update prev_code ?
+
+ prev_code = null;
+ continue;
+ } else if (code === eoi_code) {
+ break;
+ } // We have a similar situation as the decoder, where we want to store
+ // variable length entries (code table entries), but we want to do in a
+ // faster manner than an array of arrays. The code below stores sort of a
+ // linked list within the code table, and then "chases" through it to
+ // construct the dictionary entries. When a new entry is created, just the
+ // last byte is stored, and the rest (prefix) of the entry is only
+ // referenced by its table entry. Then the code chases through the
+ // prefixes until it reaches a single byte code. We have to chase twice,
+ // first to compute the length, and then to actually copy the data to the
+ // output (backwards, since we know the length). The alternative would be
+ // storing something in an intermediate stack, but that doesn't make any
+ // more sense. I implemented an approach where it also stored the length
+ // in the code table, although it's a bit tricky because you run out of
+ // bits (12 + 12 + 8), but I didn't measure much improvements (the table
+ // entries are generally not the long). Even when I created benchmarks for
+ // very long table entries the complexity did not seem worth it.
+ // The code table stores the prefix entry in 12 bits and then the suffix
+ // byte in 8 bits, so each entry is 20 bits.
+
+
+ var chase_code = code < next_code ? code : prev_code; // Chase what we will output, either {CODE} or {CODE-1}.
+
+ var chase_length = 0;
+ var chase = chase_code;
+
+ while (chase > clear_code) {
+ chase = code_table[chase] >> 8;
+ ++chase_length;
+ }
+
+ var k = chase;
+ var op_end = op + chase_length + (chase_code !== code ? 1 : 0);
+
+ if (op_end > output_length) {
+ console.log("Warning, gif stream longer than expected.");
+ return;
+ } // Already have the first byte from the chase, might as well write it fast.
+
+
+ output[op++] = k;
+ op += chase_length;
+ var b = op; // Track pointer, writing backwards.
+
+ if (chase_code !== code) // The case of emitting {CODE-1} + k.
+ output[op++] = k;
+ chase = chase_code;
+
+ while (chase_length--) {
+ chase = code_table[chase];
+ output[--b] = chase & 0xff; // Write backwards.
+
+ chase >>= 8; // Pull down to the prefix code.
+ }
+
+ if (prev_code !== null && next_code < 4096) {
+ code_table[next_code++] = prev_code << 8 | k; // TODO(deanm): Figure out this clearing vs code growth logic better. I
+ // have an feeling that it should just happen somewhere else, for now it
+ // is awkward between when we grow past the max and then hit a clear code.
+ // For now just check if we hit the max 12-bits (then a clear code should
+ // follow, also of course encoded in 12-bits).
+
+ if (next_code >= code_mask + 1 && cur_code_size < 12) {
+ ++cur_code_size;
+ code_mask = code_mask << 1 | 1;
+ }
+ }
+
+ prev_code = code;
+ }
+
+ if (op !== output_length) {
+ console.log("Warning, gif stream shorter than expected.");
+ }
+
+ return output;
+ }
+
+ try {
+
+
+ } catch (e) {} // CommonJS.
+
+ /*
+ * Copyright (c) 2012 chick307
+ *
+ * Licensed under the MIT License.
+ * http://opensource.org/licenses/mit-license
+ */
+ (function (jsPDF, callback) {
+ jsPDF.API.adler32cs = callback();
+ })(jsPDF, function () {
+ var _hasArrayBuffer = typeof ArrayBuffer === "function" && typeof Uint8Array === "function";
+
+ var _Buffer = null,
+ _isBuffer = function () {
+ if (!_hasArrayBuffer) return function _isBuffer() {
+ return false;
+ };
+
+ return function _isBuffer(value) {
+ return value instanceof ArrayBuffer || _Buffer !== null && value instanceof _Buffer;
+ };
+ }();
+
+ var _utf8ToBinary = function () {
+ if (_Buffer !== null) {
+ return function _utf8ToBinary(utf8String) {
+ return new _Buffer(utf8String, "utf8").toString("binary");
+ };
+ } else {
+ return function _utf8ToBinary(utf8String) {
+ return unescape(encodeURIComponent(utf8String));
+ };
+ }
+ }();
+
+ var MOD = 65521;
+
+ var _update = function _update(checksum, binaryString) {
+ var a = checksum & 0xffff,
+ b = checksum >>> 16;
+
+ for (var i = 0, length = binaryString.length; i < length; i++) {
+ a = (a + (binaryString.charCodeAt(i) & 0xff)) % MOD;
+ b = (b + a) % MOD;
+ }
+
+ return (b << 16 | a) >>> 0;
+ };
+
+ var _updateUint8Array = function _updateUint8Array(checksum, uint8Array) {
+ var a = checksum & 0xffff,
+ b = checksum >>> 16;
+
+ for (var i = 0, length = uint8Array.length; i < length; i++) {
+ a = (a + uint8Array[i]) % MOD;
+ b = (b + a) % MOD;
+ }
+
+ return (b << 16 | a) >>> 0;
+ };
+
+ var exports = {};
+
+ var Adler32 = exports.Adler32 = function () {
+ var ctor = function Adler32(checksum) {
+ if (!(this instanceof ctor)) {
+ throw new TypeError("Constructor cannot called be as a function.");
+ }
+
+ if (!isFinite(checksum = checksum == null ? 1 : +checksum)) {
+ throw new Error("First arguments needs to be a finite number.");
+ }
+
+ this.checksum = checksum >>> 0;
+ };
+
+ var proto = ctor.prototype = {};
+ proto.constructor = ctor;
+
+ ctor.from = function (from) {
+ from.prototype = proto;
+ return from;
+ }(function from(binaryString) {
+ if (!(this instanceof ctor)) {
+ throw new TypeError("Constructor cannot called be as a function.");
+ }
+
+ if (binaryString == null) throw new Error("First argument needs to be a string.");
+ this.checksum = _update(1, binaryString.toString());
+ });
+
+ ctor.fromUtf8 = function (fromUtf8) {
+ fromUtf8.prototype = proto;
+ return fromUtf8;
+ }(function fromUtf8(utf8String) {
+ if (!(this instanceof ctor)) {
+ throw new TypeError("Constructor cannot called be as a function.");
+ }
+
+ if (utf8String == null) throw new Error("First argument needs to be a string.");
+
+ var binaryString = _utf8ToBinary(utf8String.toString());
+
+ this.checksum = _update(1, binaryString);
+ });
+
+ if (_hasArrayBuffer) {
+ ctor.fromBuffer = function (fromBuffer) {
+ fromBuffer.prototype = proto;
+ return fromBuffer;
+ }(function fromBuffer(buffer) {
+ if (!(this instanceof ctor)) {
+ throw new TypeError("Constructor cannot called be as a function.");
+ }
+
+ if (!_isBuffer(buffer)) throw new Error("First argument needs to be ArrayBuffer.");
+ var array = new Uint8Array(buffer);
+ return this.checksum = _updateUint8Array(1, array);
+ });
+ }
+
+ proto.update = function update(binaryString) {
+ if (binaryString == null) throw new Error("First argument needs to be a string.");
+ binaryString = binaryString.toString();
+ return this.checksum = _update(this.checksum, binaryString);
+ };
+
+ proto.updateUtf8 = function updateUtf8(utf8String) {
+ if (utf8String == null) throw new Error("First argument needs to be a string.");
+
+ var binaryString = _utf8ToBinary(utf8String.toString());
+
+ return this.checksum = _update(this.checksum, binaryString);
+ };
+
+ if (_hasArrayBuffer) {
+ proto.updateBuffer = function updateBuffer(buffer) {
+ if (!_isBuffer(buffer)) throw new Error("First argument needs to be ArrayBuffer.");
+ var array = new Uint8Array(buffer);
+ return this.checksum = _updateUint8Array(this.checksum, array);
+ };
+ }
+
+ proto.clone = function clone() {
+ return new Adler32(this.checksum);
+ };
+
+ return ctor;
+ }();
+
+ exports.from = function from(binaryString) {
+ if (binaryString == null) throw new Error("First argument needs to be a string.");
+ return _update(1, binaryString.toString());
+ };
+
+ exports.fromUtf8 = function fromUtf8(utf8String) {
+ if (utf8String == null) throw new Error("First argument needs to be a string.");
+
+ var binaryString = _utf8ToBinary(utf8String.toString());
+
+ return _update(1, binaryString);
+ };
+
+ if (_hasArrayBuffer) {
+ exports.fromBuffer = function fromBuffer(buffer) {
+ if (!_isBuffer(buffer)) throw new Error("First argument need to be ArrayBuffer.");
+ var array = new Uint8Array(buffer);
+ return _updateUint8Array(1, array);
+ };
+ }
+
+ return exports;
+ });
+
+ /*
+ Copyright (c) 2008, Adobe Systems Incorporated
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of Adobe Systems Incorporated nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ /**
+ * @author shaozilee
+ *
+ * Bmp format decoder,support 1bit 4bit 8bit 24bit bmp
+ *
+ */
+
+ /*
+ Copyright (c) 2013 Gildas Lormeau. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the distribution.
+
+ 3. The names of the authors may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
+ INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ /*
+ * This program is based on JZlib 1.0.2 ymnk, JCraft,Inc.
+ * JZlib is based on zlib-1.1.3, so all credit should go authors
+ * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
+ * and contributors of zlib.
+ */
+ (function (global) {
+
+ var MAX_BITS = 15;
+ var D_CODES = 30;
+ var BL_CODES = 19;
+ var LENGTH_CODES = 29;
+ var LITERALS = 256;
+ var L_CODES = LITERALS + 1 + LENGTH_CODES;
+ var HEAP_SIZE = 2 * L_CODES + 1;
+ var END_BLOCK = 256; // Bit length codes must not exceed MAX_BL_BITS bits
+
+ var MAX_BL_BITS = 7; // repeat previous bit length 3-6 times (2 bits of repeat count)
+
+ var REP_3_6 = 16; // repeat a zero length 3-10 times (3 bits of repeat count)
+
+ var REPZ_3_10 = 17; // repeat a zero length 11-138 times (7 bits of repeat count)
+
+ var REPZ_11_138 = 18; // The lengths of the bit length codes are sent in order of decreasing
+ // probability, to avoid transmitting the lengths for unused bit
+ // length codes.
+
+ var Buf_size = 8 * 2; // JZlib version : "1.0.2"
+
+ var Z_DEFAULT_COMPRESSION = -1; // compression strategy
+
+ var Z_FILTERED = 1;
+ var Z_HUFFMAN_ONLY = 2;
+ var Z_DEFAULT_STRATEGY = 0;
+ var Z_NO_FLUSH = 0;
+ var Z_PARTIAL_FLUSH = 1;
+ var Z_FULL_FLUSH = 3;
+ var Z_FINISH = 4;
+ var Z_OK = 0;
+ var Z_STREAM_END = 1;
+ var Z_NEED_DICT = 2;
+ var Z_STREAM_ERROR = -2;
+ var Z_DATA_ERROR = -3;
+ var Z_BUF_ERROR = -5; // Tree
+ // see definition of array dist_code below
+
+ var _dist_code = [0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17, 18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29];
+
+ function Tree() {
+ var that = this; // dyn_tree; // the dynamic tree
+ // max_code; // largest code with non zero frequency
+ // stat_desc; // the corresponding static tree
+ // Compute the optimal bit lengths for a tree and update the total bit
+ // length
+ // for the current block.
+ // IN assertion: the fields freq and dad are set, heap[heap_max] and
+ // above are the tree nodes sorted by increasing frequency.
+ // OUT assertions: the field len is set to the optimal bit length, the
+ // array bl_count contains the frequencies for each bit length.
+ // The length opt_len is updated; static_len is also updated if stree is
+ // not null.
+
+ function gen_bitlen(s) {
+ var tree = that.dyn_tree;
+ var stree = that.stat_desc.static_tree;
+ var extra = that.stat_desc.extra_bits;
+ var base = that.stat_desc.extra_base;
+ var max_length = that.stat_desc.max_length;
+ var h; // heap index
+
+ var n, m; // iterate over the tree elements
+
+ var bits; // bit length
+
+ var xbits; // extra bits
+
+ var f; // frequency
+
+ var overflow = 0; // number of elements with bit length too large
+
+ for (bits = 0; bits <= MAX_BITS; bits++) s.bl_count[bits] = 0; // In a first pass, compute the optimal bit lengths (which may
+ // overflow in the case of the bit length tree).
+
+
+ tree[s.heap[s.heap_max] * 2 + 1] = 0; // root of the heap
+
+ for (h = s.heap_max + 1; h < HEAP_SIZE; h++) {
+ n = s.heap[h];
+ bits = tree[tree[n * 2 + 1] * 2 + 1] + 1;
+
+ if (bits > max_length) {
+ bits = max_length;
+ overflow++;
+ }
+
+ tree[n * 2 + 1] = bits; // We overwrite tree[n*2+1] which is no longer needed
+
+ if (n > that.max_code) continue; // not a leaf node
+
+ s.bl_count[bits]++;
+ xbits = 0;
+ if (n >= base) xbits = extra[n - base];
+ f = tree[n * 2];
+ s.opt_len += f * (bits + xbits);
+ if (stree) s.static_len += f * (stree[n * 2 + 1] + xbits);
+ }
+
+ if (overflow === 0) return; // This happens for example on obj2 and pic of the Calgary corpus
+ // Find the first bit length which could increase:
+
+ do {
+ bits = max_length - 1;
+
+ while (s.bl_count[bits] === 0) bits--;
+
+ s.bl_count[bits]--; // move one leaf down the tree
+
+ s.bl_count[bits + 1] += 2; // move one overflow item as its brother
- /**
- * Sets the line dash pattern.
- * @param {Array} array An array containing 0-2 numbers. The first number sets the length of the
- * dashes, the second number the length of the gaps. If the second number is missing, the gaps are considered
- * to be as long as the dashes. An empty array means solid, unbroken lines.
- * @param phase The phase lines start with.
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name setLineDashPattern
- */
- API.setLineDashPattern = function (array, phase) {
- out(["[" + (array[0] !== undefined ? array[0] : ""), (array[1] !== undefined ? array[1] : "") + "]", phase, "d"].join(" "));
+ s.bl_count[max_length]--; // The brother of the overflow item also moves one step up,
+ // but this does not affect bl_count[max_length]
- return this;
- };
+ overflow -= 2;
+ } while (overflow > 0);
- // Output is both an internal (for plugins) and external function
- API.output = _output;
+ for (bits = max_length; bits !== 0; bits--) {
+ n = s.bl_count[bits];
- /**
- * Saves as PDF document. An alias of jsPDF.output('save', 'filename.pdf')
- * @param {String} filename The filename including extension.
- *
- * @function
- * @returns {jsPDF}
- * @methodOf jsPDF#
- * @name save
- */
- API.save = function (filename) {
- API.output('save', filename);
- };
-
- // applying plugins (more methods) ON TOP of built-in API.
- // this is intentional as we allow plugins to override
- // built-ins
- for (var plugin in jsPDF.API) {
- if (jsPDF.API.hasOwnProperty(plugin)) {
- if (plugin === 'events' && jsPDF.API.events.length) {
- (function (events, newEvents) {
-
- // jsPDF.API.events is a JS Array of Arrays
- // where each Array is a pair of event name, handler
- // Events were added by plugins to the jsPDF instantiator.
- // These are always added to the new instance and some ran
- // during instantiation.
- var eventname, handler_and_args, i;
-
- for (i = newEvents.length - 1; i !== -1; i--) {
- // subscribe takes 3 args: 'topic', function, runonce_flag
- // if undefined, runonce is false.
- // users can attach callback directly,
- // or they can attach an array with [callback, runonce_flag]
- // that's what the "apply" magic is for below.
- eventname = newEvents[i][0];
- handler_and_args = newEvents[i][1];
- events.subscribe.apply(events, [eventname].concat(typeof handler_and_args === 'function' ? [handler_and_args] : handler_and_args));
- }
- })(events, jsPDF.API.events);
- } else {
- API[plugin] = jsPDF.API[plugin];
- }
- }
- }
-
- //////////////////////////////////////////////////////
- // continuing initialization of jsPDF Document object
- //////////////////////////////////////////////////////
- // Add the first page automatically
- addFonts();
- activeFontKey = 'F1';
- _addPage(format, orientation);
-
- events.publish('initialized');
- return API;
- }
-
- /**
- * jsPDF.API is a STATIC property of jsPDF class.
- * jsPDF.API is an object you can add methods and properties to.
- * The methods / properties you add will show up in new jsPDF objects.
- *
- * One property is prepopulated. It is the 'events' Object. Plugin authors can add topics,
- * callbacks to this object. These will be reassigned to all new instances of jsPDF.
- * Examples:
- * jsPDF.API.events['initialized'] = function(){ 'this' is API object }
- * jsPDF.API.events['addFont'] = function(added_font_object){ 'this' is API object }
- *
- * @static
- * @public
- * @memberOf jsPDF
- * @name API
- *
- * @example
- * jsPDF.API.mymethod = function(){
- * // 'this' will be ref to internal API object. see jsPDF source
- * // , so you can refer to built-in methods like so:
- * // this.line(....)
- * // this.text(....)
- * }
- * var pdfdoc = new jsPDF()
- * pdfdoc.mymethod() // <- !!!!!!
- */
- jsPDF.API = { events: [] };
- jsPDF.version = "1.2.68 2017-07-18T14:26:05.034Z:minint-e5ltq7i\vasilcenko";
-
- if (typeof define === 'function' && define.amd) {
- define('jsPDF', function () {
- return jsPDF;
- });
- } else if (typeof module !== 'undefined' && module.exports) {
- module.exports = jsPDF;
- } else {
- global.jsPDF = jsPDF;
- }
- return jsPDF;
- }(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this);
-
+ while (n !== 0) {
+ m = s.heap[--h];
+ if (m > that.max_code) continue;
- /**
- * jsPDF AcroForm Plugin
- * Copyright (c) 2016 Alexander Weidt, https://github.com/BiggA94
- *
- * Licensed under the MIT License.
- * http://opensource.org/licenses/mit-license
- */
+ if (tree[m * 2 + 1] != bits) {
+ s.opt_len += (bits - tree[m * 2 + 1]) * tree[m * 2];
+ tree[m * 2 + 1] = bits;
+ }
- (window.AcroForm = function (jsPDFAPI) {
- 'use strict';
+ n--;
+ }
+ }
+ } // Reverse the first len bits of a code, using straightforward code (a
+ // faster
+ // method would use a table)
+ // IN assertion: 1 <= len <= 15
- var AcroForm = window.AcroForm;
- AcroForm.scale = function (x) {
- return x * (acroformPlugin.internal.scaleFactor / 1); // 1 = (96 / 72)
- };
- AcroForm.antiScale = function (x) {
- return 1 / acroformPlugin.internal.scaleFactor * x;
- };
+ function bi_reverse(code, // the value to invert
+ len // its bit length
+ ) {
+ var res = 0;
- var acroformPlugin = {
- fields: [],
- xForms: [],
- /**
- * acroFormDictionaryRoot contains information about the AcroForm Dictionary
- * 0: The Event-Token, the AcroFormDictionaryCallback has
- * 1: The Object ID of the Root
- */
- acroFormDictionaryRoot: null,
- /**
- * After the PDF gets evaluated, the reference to the root has to be reset,
- * this indicates, whether the root has already been printed out
- */
- printedOut: false,
- internal: null
- };
+ do {
+ res |= code & 1;
+ code >>>= 1;
+ res <<= 1;
+ } while (--len > 0);
- jsPDF.API.acroformPlugin = acroformPlugin;
+ return res >>> 1;
+ } // Generate the codes for a given tree and bit counts (which need not be
+ // optimal).
+ // IN assertion: the array bl_count contains the bit length statistics for
+ // the given tree and the field len is set for all tree elements.
+ // OUT assertion: the field code is set for all tree elements of non
+ // zero code length.
- var annotReferenceCallback = function annotReferenceCallback() {
- for (var i in this.acroformPlugin.acroFormDictionaryRoot.Fields) {
- var formObject = this.acroformPlugin.acroFormDictionaryRoot.Fields[i];
- // add Annot Reference!
- if (formObject.hasAnnotation) {
- // If theres an Annotation Widget in the Form Object, put the Reference in the /Annot array
- createAnnotationReference.call(this, formObject);
- }
- }
- };
- var createAcroForm = function createAcroForm() {
- if (this.acroformPlugin.acroFormDictionaryRoot) {
- //return;
- throw new Error("Exception while creating AcroformDictionary");
- }
+ function gen_codes(tree, // the tree to decorate
+ max_code, // largest code with non zero frequency
+ bl_count // number of codes at each bit length
+ ) {
+ var next_code = []; // next code value for each
+ // bit length
- // The Object Number of the AcroForm Dictionary
- this.acroformPlugin.acroFormDictionaryRoot = new AcroForm.AcroFormDictionary();
+ var code = 0; // running code value
- this.acroformPlugin.internal = this.internal;
+ var bits; // bit index
- // add Callback for creating the AcroForm Dictionary
- this.acroformPlugin.acroFormDictionaryRoot._eventID = this.internal.events.subscribe('postPutResources', AcroFormDictionaryCallback);
+ var n; // code index
- this.internal.events.subscribe('buildDocument', annotReferenceCallback); //buildDocument
+ var len; // The distribution counts are first used to generate the code values
+ // without bit reversal.
- // Register event, that is triggered when the DocumentCatalog is written, in order to add /AcroForm
- this.internal.events.subscribe('putCatalog', putCatalogCallback);
+ for (bits = 1; bits <= MAX_BITS; bits++) {
+ next_code[bits] = code = code + bl_count[bits - 1] << 1;
+ } // Check that the bit counts in bl_count are consistent. The last code
+ // must be all ones.
+ // Assert (code + bl_count[MAX_BITS]-1 == (1<= 1; n--) s.pqdownheap(tree, n); // Construct the Huffman tree by repeatedly combining the least two
+ // frequent nodes.
- /**
- * Adds /Acroform X 0 R to Document Catalog,
- * and creates the AcroForm Dictionary
- */
- var AcroFormDictionaryCallback = function AcroFormDictionaryCallback() {
- // Remove event
- this.internal.events.unsubscribe(this.acroformPlugin.acroFormDictionaryRoot._eventID);
- delete this.acroformPlugin.acroFormDictionaryRoot._eventID;
+ node = elems; // next internal node of the tree
- this.acroformPlugin.printedOut = true;
- };
+ do {
+ // n = node of least frequency
+ n = s.heap[1];
+ s.heap[1] = s.heap[s.heap_len--];
+ s.pqdownheap(tree, 1);
+ m = s.heap[1]; // m = node of next least frequency
- /**
- * Creates the single Fields and writes them into the Document
- *
- * If fieldArray is set, use the fields that are inside it instead of the fields from the AcroRoot
- * (for the FormXObjects...)
- */
- var createFieldCallback = function createFieldCallback(fieldArray) {
- var standardFields = !fieldArray;
-
- if (!fieldArray) {
- // in case there is no fieldArray specified, we wanna print out the Fields of the AcroForm
- // Print out Root
- this.internal.newObjectDeferredBegin(this.acroformPlugin.acroFormDictionaryRoot.objId);
- this.internal.out(this.acroformPlugin.acroFormDictionaryRoot.getString());
- }
+ s.heap[--s.heap_max] = n; // keep the nodes sorted by frequency
- var fieldArray = fieldArray || this.acroformPlugin.acroFormDictionaryRoot.Kids;
+ s.heap[--s.heap_max] = m; // Create a new node father of n and m
- for (var i in fieldArray) {
- var key = i;
- var form = fieldArray[i];
+ tree[node * 2] = tree[n * 2] + tree[m * 2];
+ s.depth[node] = Math.max(s.depth[n], s.depth[m]) + 1;
+ tree[n * 2 + 1] = tree[m * 2 + 1] = node; // and insert the new node in the heap
- var oldRect = form.Rect;
+ s.heap[1] = node++;
+ s.pqdownheap(tree, 1);
+ } while (s.heap_len >= 2);
- if (form.Rect) {
- form.Rect = AcroForm.internal.calculateCoordinates.call(this, form.Rect);
- }
+ s.heap[--s.heap_max] = s.heap[1]; // At this point, the fields freq and dad are set. We can now
+ // generate the bit lengths.
- // Start Writing the Object
- this.internal.newObjectDeferredBegin(form.objId);
+ gen_bitlen(s); // The field len is now set, we can generate the bit codes
- var content = "";
- content += form.objId + " 0 obj\n";
+ gen_codes(tree, that.max_code, s.bl_count);
+ };
+ }
- content += "<<\n" + form.getContent();
+ Tree._length_code = [0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28];
+ Tree.base_length = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 0];
+ Tree.base_dist = [0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576]; // Mapping from a distance to a distance code. dist is the distance - 1 and
+ // must not have side effects. _dist_code[256] and _dist_code[257] are never
+ // used.
- form.Rect = oldRect;
+ Tree.d_code = function (dist) {
+ return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)];
+ }; // extra bits for each length code
- if (form.hasAppearanceStream && !form.appearanceStreamContent) {
- // Calculate Appearance
- var appearance = AcroForm.internal.calculateAppearanceStream.call(this, form);
- content += "/AP << /N " + appearance + " >>\n";
- this.acroformPlugin.xForms.push(appearance);
- }
+ Tree.extra_lbits = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0]; // extra bits for each distance code
- // Assume AppearanceStreamContent is a Array with N,R,D (at least one of them!)
- if (form.appearanceStreamContent) {
- content += "/AP << ";
- // Iterate over N,R and D
- for (var k in form.appearanceStreamContent) {
- var value = form.appearanceStreamContent[k];
- content += "/" + k + " ";
- content += "<< ";
- if (Object.keys(value).length >= 1 || Array.isArray(value)) {
- // appearanceStream is an Array or Object!
- for (var i in value) {
- var obj = value[i];
- if (typeof obj === 'function') {
- // if Function is referenced, call it in order to get the FormXObject
- obj = obj.call(this, form);
- }
- content += "/" + i + " " + obj + " ";
-
- // In case the XForm is already used, e.g. OffState of CheckBoxes, don't add it
- if (!(this.acroformPlugin.xForms.indexOf(obj) >= 0)) this.acroformPlugin.xForms.push(obj);
- }
- } else {
- var obj = value;
- if (typeof obj === 'function') {
- // if Function is referenced, call it in order to get the FormXObject
- obj = obj.call(this, form);
- }
- content += "/" + i + " " + obj + " \n";
- if (!(this.acroformPlugin.xForms.indexOf(obj) >= 0)) this.acroformPlugin.xForms.push(obj);
- }
- content += " >>\n";
- }
+ Tree.extra_dbits = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13]; // extra bits for each bit length code
- // appearance stream is a normal Object..
- content += ">>\n";
- }
+ Tree.extra_blbits = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7];
+ Tree.bl_order = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; // StaticTree
- content += ">>\nendobj\n";
+ function StaticTree(static_tree, extra_bits, extra_base, elems, max_length) {
+ var that = this;
+ that.static_tree = static_tree;
+ that.extra_bits = extra_bits;
+ that.extra_base = extra_base;
+ that.elems = elems;
+ that.max_length = max_length;
+ }
- this.internal.out(content);
- }
- if (standardFields) {
- createXFormObjectCallback.call(this, this.acroformPlugin.xForms);
- }
- };
+ StaticTree.static_ltree = [12, 8, 140, 8, 76, 8, 204, 8, 44, 8, 172, 8, 108, 8, 236, 8, 28, 8, 156, 8, 92, 8, 220, 8, 60, 8, 188, 8, 124, 8, 252, 8, 2, 8, 130, 8, 66, 8, 194, 8, 34, 8, 162, 8, 98, 8, 226, 8, 18, 8, 146, 8, 82, 8, 210, 8, 50, 8, 178, 8, 114, 8, 242, 8, 10, 8, 138, 8, 74, 8, 202, 8, 42, 8, 170, 8, 106, 8, 234, 8, 26, 8, 154, 8, 90, 8, 218, 8, 58, 8, 186, 8, 122, 8, 250, 8, 6, 8, 134, 8, 70, 8, 198, 8, 38, 8, 166, 8, 102, 8, 230, 8, 22, 8, 150, 8, 86, 8, 214, 8, 54, 8, 182, 8, 118, 8, 246, 8, 14, 8, 142, 8, 78, 8, 206, 8, 46, 8, 174, 8, 110, 8, 238, 8, 30, 8, 158, 8, 94, 8, 222, 8, 62, 8, 190, 8, 126, 8, 254, 8, 1, 8, 129, 8, 65, 8, 193, 8, 33, 8, 161, 8, 97, 8, 225, 8, 17, 8, 145, 8, 81, 8, 209, 8, 49, 8, 177, 8, 113, 8, 241, 8, 9, 8, 137, 8, 73, 8, 201, 8, 41, 8, 169, 8, 105, 8, 233, 8, 25, 8, 153, 8, 89, 8, 217, 8, 57, 8, 185, 8, 121, 8, 249, 8, 5, 8, 133, 8, 69, 8, 197, 8, 37, 8, 165, 8, 101, 8, 229, 8, 21, 8, 149, 8, 85, 8, 213, 8, 53, 8, 181, 8, 117, 8, 245, 8, 13, 8, 141, 8, 77, 8, 205, 8, 45, 8, 173, 8, 109, 8, 237, 8, 29, 8, 157, 8, 93, 8, 221, 8, 61, 8, 189, 8, 125, 8, 253, 8, 19, 9, 275, 9, 147, 9, 403, 9, 83, 9, 339, 9, 211, 9, 467, 9, 51, 9, 307, 9, 179, 9, 435, 9, 115, 9, 371, 9, 243, 9, 499, 9, 11, 9, 267, 9, 139, 9, 395, 9, 75, 9, 331, 9, 203, 9, 459, 9, 43, 9, 299, 9, 171, 9, 427, 9, 107, 9, 363, 9, 235, 9, 491, 9, 27, 9, 283, 9, 155, 9, 411, 9, 91, 9, 347, 9, 219, 9, 475, 9, 59, 9, 315, 9, 187, 9, 443, 9, 123, 9, 379, 9, 251, 9, 507, 9, 7, 9, 263, 9, 135, 9, 391, 9, 71, 9, 327, 9, 199, 9, 455, 9, 39, 9, 295, 9, 167, 9, 423, 9, 103, 9, 359, 9, 231, 9, 487, 9, 23, 9, 279, 9, 151, 9, 407, 9, 87, 9, 343, 9, 215, 9, 471, 9, 55, 9, 311, 9, 183, 9, 439, 9, 119, 9, 375, 9, 247, 9, 503, 9, 15, 9, 271, 9, 143, 9, 399, 9, 79, 9, 335, 9, 207, 9, 463, 9, 47, 9, 303, 9, 175, 9, 431, 9, 111, 9, 367, 9, 239, 9, 495, 9, 31, 9, 287, 9, 159, 9, 415, 9, 95, 9, 351, 9, 223, 9, 479, 9, 63, 9, 319, 9, 191, 9, 447, 9, 127, 9, 383, 9, 255, 9, 511, 9, 0, 7, 64, 7, 32, 7, 96, 7, 16, 7, 80, 7, 48, 7, 112, 7, 8, 7, 72, 7, 40, 7, 104, 7, 24, 7, 88, 7, 56, 7, 120, 7, 4, 7, 68, 7, 36, 7, 100, 7, 20, 7, 84, 7, 52, 7, 116, 7, 3, 8, 131, 8, 67, 8, 195, 8, 35, 8, 163, 8, 99, 8, 227, 8];
+ StaticTree.static_dtree = [0, 5, 16, 5, 8, 5, 24, 5, 4, 5, 20, 5, 12, 5, 28, 5, 2, 5, 18, 5, 10, 5, 26, 5, 6, 5, 22, 5, 14, 5, 30, 5, 1, 5, 17, 5, 9, 5, 25, 5, 5, 5, 21, 5, 13, 5, 29, 5, 3, 5, 19, 5, 11, 5, 27, 5, 7, 5, 23, 5];
+ StaticTree.static_l_desc = new StaticTree(StaticTree.static_ltree, Tree.extra_lbits, LITERALS + 1, L_CODES, MAX_BITS);
+ StaticTree.static_d_desc = new StaticTree(StaticTree.static_dtree, Tree.extra_dbits, 0, D_CODES, MAX_BITS);
+ StaticTree.static_bl_desc = new StaticTree(null, Tree.extra_blbits, 0, BL_CODES, MAX_BL_BITS); // Deflate
+
+ var MAX_MEM_LEVEL = 9;
+ var DEF_MEM_LEVEL = 8;
+
+ function Config(good_length, max_lazy, nice_length, max_chain, func) {
+ var that = this;
+ that.good_length = good_length;
+ that.max_lazy = max_lazy;
+ that.nice_length = nice_length;
+ that.max_chain = max_chain;
+ that.func = func;
+ }
- var createXFormObjectCallback = function createXFormObjectCallback(fieldArray) {
- for (var i in fieldArray) {
- var key = i;
- var form = fieldArray[i];
- // Start Writing the Object
- this.internal.newObjectDeferredBegin(form && form.objId);
+ var STORED = 0;
+ var FAST = 1;
+ var SLOW = 2;
+ var config_table = [new Config(0, 0, 0, 0, STORED), new Config(4, 4, 8, 4, FAST), new Config(4, 5, 16, 8, FAST), new Config(4, 6, 32, 32, FAST), new Config(4, 4, 16, 16, SLOW), new Config(8, 16, 32, 32, SLOW), new Config(8, 16, 128, 128, SLOW), new Config(8, 32, 128, 256, SLOW), new Config(32, 128, 258, 1024, SLOW), new Config(32, 258, 258, 4096, SLOW)];
+ var z_errmsg = ["need dictionary", // Z_NEED_DICT
+ // 2
+ "stream end", // Z_STREAM_END 1
+ "", // Z_OK 0
+ "", // Z_ERRNO (-1)
+ "stream error", // Z_STREAM_ERROR (-2)
+ "data error", // Z_DATA_ERROR (-3)
+ "", // Z_MEM_ERROR (-4)
+ "buffer error", // Z_BUF_ERROR (-5)
+ "", // Z_VERSION_ERROR (-6)
+ ""]; // block not completed, need more input or more output
+
+ var NeedMore = 0; // block flush performed
+
+ var BlockDone = 1; // finish started, need only more output at next deflate
+
+ var FinishStarted = 2; // finish done, accept no more input or output
+
+ var FinishDone = 3; // preset dictionary flag in zlib header
+
+ var PRESET_DICT = 0x20;
+ var INIT_STATE = 42;
+ var BUSY_STATE = 113;
+ var FINISH_STATE = 666; // The deflate compression method
+
+ var Z_DEFLATED = 8;
+ var STORED_BLOCK = 0;
+ var STATIC_TREES = 1;
+ var DYN_TREES = 2;
+ var MIN_MATCH = 3;
+ var MAX_MATCH = 258;
+ var MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1;
+
+ function smaller(tree, n, m, depth) {
+ var tn2 = tree[n * 2];
+ var tm2 = tree[m * 2];
+ return tn2 < tm2 || tn2 == tm2 && depth[n] <= depth[m];
+ }
- var content = "";
- content += form ? form.getString() : '';
- this.internal.out(content);
+ function Deflate() {
+ var that = this;
+ var strm; // pointer back to this zlib stream
- delete fieldArray[key];
- }
- };
+ var status; // as the name implies
+ // pending_buf; // output still pending
- // Public:
-
- jsPDFAPI.addField = function (fieldObject) {
- //var opt = parseOptions(fieldObject);
- if (fieldObject instanceof AcroForm.TextField) {
- addTextField.call(this, fieldObject);
- } else if (fieldObject instanceof AcroForm.ChoiceField) {
- addChoiceField.call(this, fieldObject);
- } else if (fieldObject instanceof AcroForm.Button) {
- addButton.call(this, fieldObject);
- } else if (fieldObject instanceof AcroForm.ChildClass) {
- putForm.call(this, fieldObject);
- } else if (fieldObject) {
- // try to put..
- putForm.call(this, fieldObject);
- }
- return this;
- };
+ var pending_buf_size; // size of pending_buf
- // ############### sort in:
+ var last_flush; // value of flush param for previous deflate call
- /**
- * Button
- * FT = Btn
- */
- var addButton = function addButton(options) {
- var options = options || new AcroForm.Field();
+ var w_size; // LZ77 window size (32K by default)
- options.FT = '/Btn';
+ var w_bits; // log2(w_size) (8..16)
- /**
- * Calculating the Ff entry:
- *
- * The Ff entry contains flags, that have to be set bitwise
- * In the Following the number in the Comment is the BitPosition
- */
- var flags = options.Ff || 0;
+ var w_mask; // w_size - 1
- // 17, Pushbutton
- if (options.pushbutton) {
- // Options.pushbutton should be 1 or 0
- flags = AcroForm.internal.setBitPosition(flags, 17);
- delete options.pushbutton;
- }
+ var window; // Sliding window. Input bytes are read into the second half of the window,
+ // and move to the first half later to keep a dictionary of at least wSize
+ // bytes. With this organization, matches are limited to a distance of
+ // wSize-MAX_MATCH bytes, but this ensures that IO is always
+ // performed with a length multiple of the block size. Also, it limits
+ // the window size to 64K, which is quite useful on MSDOS.
+ // To do: use the user input buffer as sliding window.
- //16, Radio
- if (options.radio) {
- //flags = options.Ff | options.radio << 15;
- flags = AcroForm.internal.setBitPosition(flags, 16);
- delete options.radio;
- }
+ var window_size; // Actual size of window: 2*wSize, except when the user input buffer
+ // is directly used as sliding window.
- // 15, NoToggleToOff (Radio buttons only
- if (options.noToggleToOff) {
- //flags = options.Ff | options.noToggleToOff << 14;
- flags = AcroForm.internal.setBitPosition(flags, 15);
- //delete options.noToggleToOff;
- }
+ var prev; // Link to older string with same hash index. To limit the size of this
+ // array to 64K, this link is maintained only for the last 32K strings.
+ // An index in this array is thus a window index modulo 32K.
- // In case, there is no Flag set, it is a check-box
- options.Ff = flags;
+ var head; // Heads of the hash chains or NIL.
- putForm.call(this, options);
- };
+ var ins_h; // hash index of string to be inserted
- var addTextField = function addTextField(options) {
- var options = options || new AcroForm.Field();
+ var hash_size; // number of elements in hash table
- options.FT = '/Tx';
+ var hash_bits; // log2(hash_size)
- /**
- * Calculating the Ff entry:
- *
- * The Ff entry contains flags, that have to be set bitwise
- * In the Following the number in the Comment is the BitPosition
- */
+ var hash_mask; // hash_size-1
+ // Number of bits by which ins_h must be shifted at each input
+ // step. It must be such that after MIN_MATCH steps, the oldest
+ // byte no longer takes part in the hash key, that is:
+ // hash_shift * MIN_MATCH >= hash_bits
- var flags = options.Ff || 0;
+ var hash_shift; // Window position at the beginning of the current output block. Gets
+ // negative when the window is moved backwards.
- // 13, multiline
- if (options.multiline) {
- // Set Flag
- flags = flags | 1 << 12;
- // Remove multiline from FieldObject
- //delete options.multiline;
- }
+ var block_start;
+ var match_length; // length of best match
- // 14, Password
- if (options.password) {
- flags = flags | 1 << 13;
- //delete options.password;
- }
+ var prev_match; // previous match
- // 21, FileSelect, PDF 1.4...
- if (options.fileSelect) {
- flags = flags | 1 << 20;
- //delete options.fileSelect;
- }
+ var match_available; // set if previous match exists
- // 23, DoNotSpellCheck, PDF 1.4...
- if (options.doNotSpellCheck) {
- flags = flags | 1 << 22;
- //delete options.doNotSpellCheck;
- }
+ var strstart; // start of string to insert
- // 24, DoNotScroll, PDF 1.4...
- if (options.doNotScroll) {
- flags = flags | 1 << 23;
- //delete options.doNotScroll;
- }
+ var match_start; // start of matching string
- options.Ff = options.Ff || flags;
+ var lookahead; // number of valid bytes ahead in window
+ // Length of the best match at previous step. Matches not greater than this
+ // are discarded. This is used in the lazy match evaluation.
- // Add field
- putForm.call(this, options);
- };
+ var prev_length; // To speed up deflation, hash chains are never searched beyond this
+ // length. A higher limit improves compression ratio but degrades the speed.
- var addChoiceField = function addChoiceField(opt) {
- var options = opt || new AcroForm.Field();
+ var max_chain_length; // Attempt to find a better match only when the current match is strictly
+ // smaller than this value. This mechanism is used only for compression
+ // levels >= 4.
- options.FT = '/Ch';
+ var max_lazy_match; // Insert new strings in the hash table only if the match length is not
+ // greater than this length. This saves time but degrades compression.
+ // max_insert_length is used only for compression levels <= 3.
- /**
- * Calculating the Ff entry:
- *
- * The Ff entry contains flags, that have to be set bitwise
- * In the Following the number in the Comment is the BitPosition
- */
+ var level; // compression level (1..9)
- var flags = options.Ff || 0;
+ var strategy; // favor or force Huffman coding
+ // Use a faster search when the previous match is longer than this
- // 18, Combo (If not set, the choiceField is a listBox!!)
- if (options.combo) {
- // Set Flag
- flags = AcroForm.internal.setBitPosition(flags, 18);
- // Remove combo from FieldObject
- delete options.combo;
- }
+ var good_match; // Stop searching when current match exceeds this
- // 19, Edit
- if (options.edit) {
- flags = AcroForm.internal.setBitPosition(flags, 19);
- delete options.edit;
- }
+ var nice_match;
+ var dyn_ltree; // literal and length tree
- // 20, Sort
- if (options.sort) {
- flags = AcroForm.internal.setBitPosition(flags, 20);
- delete options.sort;
- }
+ var dyn_dtree; // distance tree
- // 22, MultiSelect (PDF 1.4)
- if (options.multiSelect && this.internal.getPDFVersion() >= 1.4) {
- flags = AcroForm.internal.setBitPosition(flags, 22);
- delete options.multiSelect;
- }
+ var bl_tree; // Huffman tree for bit lengths
- // 23, DoNotSpellCheck (PDF 1.4)
- if (options.doNotSpellCheck && this.internal.getPDFVersion() >= 1.4) {
- flags = AcroForm.internal.setBitPosition(flags, 23);
- delete options.doNotSpellCheck;
- }
+ var l_desc = new Tree(); // desc for literal tree
- options.Ff = flags;
+ var d_desc = new Tree(); // desc for distance tree
- //options.hasAnnotation = true;
+ var bl_desc = new Tree(); // desc for bit length tree
+ // that.heap_len; // number of elements in the heap
+ // that.heap_max; // element of largest frequency
+ // The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used.
+ // The same heap array is used to build all trees.
+ // Depth of each subtree used as tie breaker for trees of equal frequency
- // Add field
- putForm.call(this, options);
- };
- })(jsPDF.API);
+ that.depth = [];
+ var l_buf; // index for literals or lengths */
+ // Size of match buffer for literals/lengths. There are 4 reasons for
+ // limiting lit_bufsize to 64K:
+ // - frequencies can be kept in 16 bit counters
+ // - if compression is not successful for the first block, all input
+ // data is still in the window so we can still emit a stored block even
+ // when input comes from standard input. (This can also be done for
+ // all blocks if lit_bufsize is not greater than 32K.)
+ // - if compression is not successful for a file smaller than 64K, we can
+ // even emit a stored file instead of a stored block (saving 5 bytes).
+ // This is applicable only for zip (not gzip or zlib).
+ // - creating new Huffman trees less frequently may not provide fast
+ // adaptation to changes in the input data statistics. (Take for
+ // example a binary file with poorly compressible code followed by
+ // a highly compressible string table.) Smaller buffer sizes give
+ // fast adaptation but have of course the overhead of transmitting
+ // trees more frequently.
+ // - I can't count above 4
- var AcroForm = window.AcroForm;
+ var lit_bufsize;
+ var last_lit; // running index in l_buf
+ // Buffer for distances. To simplify the code, d_buf and l_buf have
+ // the same number of elements. To use different lengths, an extra flag
+ // array would be necessary.
- AcroForm.internal = {};
+ var d_buf; // index of pendig_buf
+ // that.opt_len; // bit length of current block with optimal trees
+ // that.static_len; // bit length of current block with static trees
- AcroForm.createFormXObject = function (formObject) {
- var xobj = new AcroForm.FormXObject();
- var height = AcroForm.Appearance.internal.getHeight(formObject) || 0;
- var width = AcroForm.Appearance.internal.getWidth(formObject) || 0;
- xobj.BBox = [0, 0, width, height];
- return xobj;
- };
+ var matches; // number of string matches in current block
- // Contains Methods for creating standard appearances
- AcroForm.Appearance = {
- CheckBox: {
- createAppearanceStream: function createAppearanceStream() {
- var appearance = {
- N: {
- On: AcroForm.Appearance.CheckBox.YesNormal
- },
- D: {
- On: AcroForm.Appearance.CheckBox.YesPushDown,
- Off: AcroForm.Appearance.CheckBox.OffPushDown
- }
- };
+ var last_eob_len; // bit length of EOB code for last block
+ // Output buffer. bits are inserted starting at the bottom (least
+ // significant bits).
- return appearance;
- },
- /**
- * If any other icons are needed, the number between the brackets can be changed
- * @returns {string}
- */
- createMK: function createMK() {
- // 3-> Hook
- return "<< /CA (3)>>";
- },
- /**
- * Returns the standard On Appearance for a CheckBox
- * @returns {AcroForm.FormXObject}
- */
- YesPushDown: function YesPushDown(formObject) {
- var xobj = AcroForm.createFormXObject(formObject);
- var stream = "";
- // F13 is ZapfDingbats (Symbolic)
- formObject.Q = 1; // set text-alignment as centered
- var calcRes = AcroForm.internal.calculateX(formObject, "3", "ZapfDingbats", 50);
- stream += "0.749023 g\n\
- 0 0 " + AcroForm.Appearance.internal.getWidth(formObject) + " " + AcroForm.Appearance.internal.getHeight(formObject) + " re\n\
- f\n\
- BMC\n\
- q\n\
- 0 0 1 rg\n\
- /F13 " + calcRes.fontSize + " Tf 0 g\n\
- BT\n";
- stream += calcRes.text;
- stream += "ET\n\
- Q\n\
- EMC\n";
- xobj.stream = stream;
- return xobj;
- },
+ var bi_buf; // Number of valid bits in bi_buf. All bits above the last valid bit
+ // are always zero.
- YesNormal: function YesNormal(formObject) {
- var xobj = AcroForm.createFormXObject(formObject);
- var stream = "";
- formObject.Q = 1; // set text-alignment as centered
- var calcRes = AcroForm.internal.calculateX(formObject, "3", "ZapfDingbats", AcroForm.Appearance.internal.getHeight(formObject) * 0.9);
- stream += "1 g\n\
-0 0 " + AcroForm.Appearance.internal.getWidth(formObject) + " " + AcroForm.Appearance.internal.getHeight(formObject) + " re\n\
-f\n\
-q\n\
-0 0 1 rg\n\
-0 0 " + (AcroForm.Appearance.internal.getWidth(formObject) - 1) + " " + (AcroForm.Appearance.internal.getHeight(formObject) - 1) + " re\n\
-W\n\
-n\n\
-0 g\n\
-BT\n\
-/F13 " + calcRes.fontSize + " Tf 0 g\n";
- stream += calcRes.text;
- stream += "ET\n\
- Q\n";
- xobj.stream = stream;
- return xobj;
- },
+ var bi_valid; // number of codes at each bit length for an optimal tree
- /**
- * Returns the standard Off Appearance for a CheckBox
- * @returns {AcroForm.FormXObject}
- */
- OffPushDown: function OffPushDown(formObject) {
- var xobj = AcroForm.createFormXObject(formObject);
- var stream = "";
- stream += "0.749023 g\n\
- 0 0 " + AcroForm.Appearance.internal.getWidth(formObject) + " " + AcroForm.Appearance.internal.getHeight(formObject) + " re\n\
- f\n";
- xobj.stream = stream;
- return xobj;
- }
- },
+ that.bl_count = []; // heap used to build the Huffman trees
- RadioButton: {
- Circle: {
- createAppearanceStream: function createAppearanceStream(name) {
- var appearanceStreamContent = {
- D: {
- 'Off': AcroForm.Appearance.RadioButton.Circle.OffPushDown
- },
- N: {}
- };
- appearanceStreamContent.N[name] = AcroForm.Appearance.RadioButton.Circle.YesNormal;
- appearanceStreamContent.D[name] = AcroForm.Appearance.RadioButton.Circle.YesPushDown;
- return appearanceStreamContent;
- },
- createMK: function createMK() {
- return "<< /CA (l)>>";
- },
-
- YesNormal: function YesNormal(formObject) {
- var xobj = AcroForm.createFormXObject(formObject);
- var stream = "";
- // Make the Radius of the Circle relative to min(height, width) of formObject
- var DotRadius = AcroForm.Appearance.internal.getWidth(formObject) <= AcroForm.Appearance.internal.getHeight(formObject) ? AcroForm.Appearance.internal.getWidth(formObject) / 4 : AcroForm.Appearance.internal.getHeight(formObject) / 4;
- // The Borderpadding...
- DotRadius *= 0.9;
- var c = AcroForm.Appearance.internal.Bezier_C;
- /*
- The Following is a Circle created with Bezier-Curves.
- */
- stream += "q\n\
-1 0 0 1 " + AcroForm.Appearance.internal.getWidth(formObject) / 2 + " " + AcroForm.Appearance.internal.getHeight(formObject) / 2 + " cm\n\
-" + DotRadius + " 0 m\n\
-" + DotRadius + " " + DotRadius * c + " " + DotRadius * c + " " + DotRadius + " 0 " + DotRadius + " c\n\
--" + DotRadius * c + " " + DotRadius + " -" + DotRadius + " " + DotRadius * c + " -" + DotRadius + " 0 c\n\
--" + DotRadius + " -" + DotRadius * c + " -" + DotRadius * c + " -" + DotRadius + " 0 -" + DotRadius + " c\n\
-" + DotRadius * c + " -" + DotRadius + " " + DotRadius + " -" + DotRadius * c + " " + DotRadius + " 0 c\n\
-f\n\
-Q\n";
- xobj.stream = stream;
- return xobj;
- },
- YesPushDown: function YesPushDown(formObject) {
- var xobj = AcroForm.createFormXObject(formObject);
- var stream = "";
- var DotRadius = AcroForm.Appearance.internal.getWidth(formObject) <= AcroForm.Appearance.internal.getHeight(formObject) ? AcroForm.Appearance.internal.getWidth(formObject) / 4 : AcroForm.Appearance.internal.getHeight(formObject) / 4;
- // The Borderpadding...
- DotRadius *= 0.9;
- var c = AcroForm.Appearance.internal.Bezier_C;
- stream += "0.749023 g\n\
- q\n\
- 1 0 0 1 " + AcroForm.Appearance.internal.getWidth(formObject) / 2 + " " + AcroForm.Appearance.internal.getHeight(formObject) / 2 + " cm\n\
-" + DotRadius * 2 + " 0 m\n\
-" + DotRadius * 2 + " " + DotRadius * 2 * c + " " + DotRadius * 2 * c + " " + DotRadius * 2 + " 0 " + DotRadius * 2 + " c\n\
--" + DotRadius * 2 * c + " " + DotRadius * 2 + " -" + DotRadius * 2 + " " + DotRadius * 2 * c + " -" + DotRadius * 2 + " 0 c\n\
--" + DotRadius * 2 + " -" + DotRadius * 2 * c + " -" + DotRadius * 2 * c + " -" + DotRadius * 2 + " 0 -" + DotRadius * 2 + " c\n\
-" + DotRadius * 2 * c + " -" + DotRadius * 2 + " " + DotRadius * 2 + " -" + DotRadius * 2 * c + " " + DotRadius * 2 + " 0 c\n\
- f\n\
- Q\n\
- 0 g\n\
- q\n\
- 1 0 0 1 " + AcroForm.Appearance.internal.getWidth(formObject) / 2 + " " + AcroForm.Appearance.internal.getHeight(formObject) / 2 + " cm\n\
-" + DotRadius + " 0 m\n\
-" + DotRadius + " " + DotRadius * c + " " + DotRadius * c + " " + DotRadius + " 0 " + DotRadius + " c\n\
--" + DotRadius * c + " " + DotRadius + " -" + DotRadius + " " + DotRadius * c + " -" + DotRadius + " 0 c\n\
--" + DotRadius + " -" + DotRadius * c + " -" + DotRadius * c + " -" + DotRadius + " 0 -" + DotRadius + " c\n\
-" + DotRadius * c + " -" + DotRadius + " " + DotRadius + " -" + DotRadius * c + " " + DotRadius + " 0 c\n\
- f\n\
- Q\n";
- xobj.stream = stream;
- return xobj;
- },
- OffPushDown: function OffPushDown(formObject) {
- var xobj = AcroForm.createFormXObject(formObject);
- var stream = "";
- var DotRadius = AcroForm.Appearance.internal.getWidth(formObject) <= AcroForm.Appearance.internal.getHeight(formObject) ? AcroForm.Appearance.internal.getWidth(formObject) / 4 : AcroForm.Appearance.internal.getHeight(formObject) / 4;
- // The Borderpadding...
- DotRadius *= 0.9;
- var c = AcroForm.Appearance.internal.Bezier_C;
- stream += "0.749023 g\n\
- q\n\
- 1 0 0 1 " + AcroForm.Appearance.internal.getWidth(formObject) / 2 + " " + AcroForm.Appearance.internal.getHeight(formObject) / 2 + " cm\n\
-" + DotRadius * 2 + " 0 m\n\
-" + DotRadius * 2 + " " + DotRadius * 2 * c + " " + DotRadius * 2 * c + " " + DotRadius * 2 + " 0 " + DotRadius * 2 + " c\n\
--" + DotRadius * 2 * c + " " + DotRadius * 2 + " -" + DotRadius * 2 + " " + DotRadius * 2 * c + " -" + DotRadius * 2 + " 0 c\n\
--" + DotRadius * 2 + " -" + DotRadius * 2 * c + " -" + DotRadius * 2 * c + " -" + DotRadius * 2 + " 0 -" + DotRadius * 2 + " c\n\
-" + DotRadius * 2 * c + " -" + DotRadius * 2 + " " + DotRadius * 2 + " -" + DotRadius * 2 * c + " " + DotRadius * 2 + " 0 c\n\
- f\n\
- Q\n";
- xobj.stream = stream;
- return xobj;
- }
- },
+ that.heap = [];
+ dyn_ltree = [];
+ dyn_dtree = [];
+ bl_tree = [];
- Cross: {
- /**
- * Creates the Actual AppearanceDictionary-References
- * @param name
- * @returns
- */
- createAppearanceStream: function createAppearanceStream(name) {
- var appearanceStreamContent = {
- D: {
- 'Off': AcroForm.Appearance.RadioButton.Cross.OffPushDown
- },
- N: {}
- };
- appearanceStreamContent.N[name] = AcroForm.Appearance.RadioButton.Cross.YesNormal;
- appearanceStreamContent.D[name] = AcroForm.Appearance.RadioButton.Cross.YesPushDown;
- return appearanceStreamContent;
- },
- createMK: function createMK() {
- return "<< /CA (8)>>";
- },
-
- YesNormal: function YesNormal(formObject) {
- var xobj = AcroForm.createFormXObject(formObject);
- var stream = "";
- var cross = AcroForm.Appearance.internal.calculateCross(formObject);
- stream += "q\n\
- 1 1 " + (AcroForm.Appearance.internal.getWidth(formObject) - 2) + " " + (AcroForm.Appearance.internal.getHeight(formObject) - 2) + " re\n\
- W\n\
- n\n\
- " + cross.x1.x + " " + cross.x1.y + " m\n\
- " + cross.x2.x + " " + cross.x2.y + " l\n\
- " + cross.x4.x + " " + cross.x4.y + " m\n\
- " + cross.x3.x + " " + cross.x3.y + " l\n\
- s\n\
- Q\n";
- xobj.stream = stream;
- return xobj;
- },
- YesPushDown: function YesPushDown(formObject) {
- var xobj = AcroForm.createFormXObject(formObject);
- var cross = AcroForm.Appearance.internal.calculateCross(formObject);
- var stream = "";
- stream += "0.749023 g\n\
- 0 0 " + AcroForm.Appearance.internal.getWidth(formObject) + " " + AcroForm.Appearance.internal.getHeight(formObject) + " re\n\
- f\n\
- q\n\
- 1 1 " + (AcroForm.Appearance.internal.getWidth(formObject) - 2) + " " + (AcroForm.Appearance.internal.getHeight(formObject) - 2) + " re\n\
- W\n\
- n\n\
- " + cross.x1.x + " " + cross.x1.y + " m\n\
- " + cross.x2.x + " " + cross.x2.y + " l\n\
- " + cross.x4.x + " " + cross.x4.y + " m\n\
- " + cross.x3.x + " " + cross.x3.y + " l\n\
- s\n\
- Q\n";
- xobj.stream = stream;
- return xobj;
- },
- OffPushDown: function OffPushDown(formObject) {
- var xobj = AcroForm.createFormXObject(formObject);
- var stream = "";
- stream += "0.749023 g\n\
- 0 0 " + AcroForm.Appearance.internal.getWidth(formObject) + " " + AcroForm.Appearance.internal.getHeight(formObject) + " re\n\
- f\n";
- xobj.stream = stream;
- return xobj;
- }
- }
- },
+ function lm_init() {
+ var i;
+ window_size = 2 * w_size;
+ head[hash_size - 1] = 0;
- /**
- * Returns the standard Appearance
- * @returns {AcroForm.FormXObject}
- */
- createDefaultAppearanceStream: function createDefaultAppearanceStream(formObject) {
- var stream = "";
- // Set Helvetica to Standard Font (12px)
- // Color: Black
- stream += "/Helv 12 Tf 0 g";
- return stream;
+ for (i = 0; i < hash_size - 1; i++) {
+ head[i] = 0;
+ } // Set the default configuration parameters:
+
+
+ max_lazy_match = config_table[level].max_lazy;
+ good_match = config_table[level].good_length;
+ nice_match = config_table[level].nice_length;
+ max_chain_length = config_table[level].max_chain;
+ strstart = 0;
+ block_start = 0;
+ lookahead = 0;
+ match_length = prev_length = MIN_MATCH - 1;
+ match_available = 0;
+ ins_h = 0;
+ }
+
+ function init_block() {
+ var i; // Initialize the trees.
+
+ for (i = 0; i < L_CODES; i++) dyn_ltree[i * 2] = 0;
+
+ for (i = 0; i < D_CODES; i++) dyn_dtree[i * 2] = 0;
+
+ for (i = 0; i < BL_CODES; i++) bl_tree[i * 2] = 0;
+
+ dyn_ltree[END_BLOCK * 2] = 1;
+ that.opt_len = that.static_len = 0;
+ last_lit = matches = 0;
+ } // Initialize the tree data structures for a new zlib stream.
+
+
+ function tr_init() {
+ l_desc.dyn_tree = dyn_ltree;
+ l_desc.stat_desc = StaticTree.static_l_desc;
+ d_desc.dyn_tree = dyn_dtree;
+ d_desc.stat_desc = StaticTree.static_d_desc;
+ bl_desc.dyn_tree = bl_tree;
+ bl_desc.stat_desc = StaticTree.static_bl_desc;
+ bi_buf = 0;
+ bi_valid = 0;
+ last_eob_len = 8; // enough lookahead for inflate
+ // Initialize the first block of the first file:
+
+ init_block();
+ } // Restore the heap property by moving down the tree starting at node k,
+ // exchanging a node with the smallest of its two sons if necessary,
+ // stopping
+ // when the heap property is re-established (each father smaller than its
+ // two sons).
+
+
+ that.pqdownheap = function (tree, // the tree to restore
+ k // node to move down
+ ) {
+ var heap = that.heap;
+ var v = heap[k];
+ var j = k << 1; // left son of k
+
+ while (j <= that.heap_len) {
+ // Set j to the smallest of the two sons:
+ if (j < that.heap_len && smaller(tree, heap[j + 1], heap[j], that.depth)) {
+ j++;
+ } // Exit if v is smaller than both sons
+
+
+ if (smaller(tree, v, heap[j], that.depth)) break; // Exchange v with the smallest son
+
+ heap[k] = heap[j];
+ k = j; // And continue down the tree, setting j to the left son of k
+
+ j <<= 1;
}
- };
- AcroForm.Appearance.internal = {
- Bezier_C: 0.551915024494,
+ heap[k] = v;
+ }; // Scan a literal or distance tree to determine the frequencies of the codes
+ // in the bit length tree.
- calculateCross: function calculateCross(formObject) {
- var min = function min(x, y) {
- return x > y ? y : x;
- };
- var width = AcroForm.Appearance.internal.getWidth(formObject);
- var height = AcroForm.Appearance.internal.getHeight(formObject);
- var a = min(width, height);
- var crossSize = a;
- var borderPadding = 2; // The Padding in px
-
-
- var cross = {
- x1: { // upperLeft
- x: (width - a) / 2,
- y: (height - a) / 2 + a //height - borderPadding
- },
- x2: { // lowerRight
- x: (width - a) / 2 + a,
- y: (height - a) / 2 //borderPadding
- },
- x3: { // lowerLeft
- x: (width - a) / 2,
- y: (height - a) / 2 //borderPadding
- },
- x4: { // upperRight
- x: (width - a) / 2 + a,
- y: (height - a) / 2 + a //height - borderPadding
- }
- };
+ function scan_tree(tree, // the tree to be scanned
+ max_code // and its largest code of non zero frequency
+ ) {
+ var n; // iterates over all tree elements
+
+ var prevlen = -1; // last emitted length
+
+ var curlen; // length of current code
+
+ var nextlen = tree[0 * 2 + 1]; // length of next code
+
+ var count = 0; // repeat count of the current code
- return cross;
+ var max_count = 7; // max repeat count
+
+ var min_count = 4; // min repeat count
+
+ if (nextlen === 0) {
+ max_count = 138;
+ min_count = 3;
}
- };
- AcroForm.Appearance.internal.getWidth = function (formObject) {
- return formObject.Rect[2]; //(formObject.Rect[2] - formObject.Rect[0]) || 0;
- };
- AcroForm.Appearance.internal.getHeight = function (formObject) {
- return formObject.Rect[3]; //(formObject.Rect[1] - formObject.Rect[3]) || 0;
- };
- // ##########################
+ tree[(max_code + 1) * 2 + 1] = 0xffff; // guard
- //### For inheritance:
- AcroForm.internal.inherit = function (child, parent) {
- var ObjectCreate = Object.create || function (o) {
- var F = function F() {};
- F.prototype = o;
- return new F();
- };
- child.prototype = Object.create(parent.prototype);
- child.prototype.constructor = child;
- };
+ for (n = 0; n <= max_code; n++) {
+ curlen = nextlen;
+ nextlen = tree[(n + 1) * 2 + 1];
+
+ if (++count < max_count && curlen == nextlen) {
+ continue;
+ } else if (count < min_count) {
+ bl_tree[curlen * 2] += count;
+ } else if (curlen !== 0) {
+ if (curlen != prevlen) bl_tree[curlen * 2]++;
+ bl_tree[REP_3_6 * 2]++;
+ } else if (count <= 10) {
+ bl_tree[REPZ_3_10 * 2]++;
+ } else {
+ bl_tree[REPZ_11_138 * 2]++;
+ }
+
+ count = 0;
+ prevlen = curlen;
+
+ if (nextlen === 0) {
+ max_count = 138;
+ min_count = 3;
+ } else if (curlen == nextlen) {
+ max_count = 6;
+ min_count = 3;
+ } else {
+ max_count = 7;
+ min_count = 4;
+ }
+ }
+ } // Construct the Huffman tree for the bit lengths and return the index in
+ // bl_order of the last bit length code to send.
+
+
+ function build_bl_tree() {
+ var max_blindex; // index of last bit length code of non zero freq
+ // Determine the bit length frequencies for literal and distance trees
+
+ scan_tree(dyn_ltree, l_desc.max_code);
+ scan_tree(dyn_dtree, d_desc.max_code); // Build the bit length tree:
+
+ bl_desc.build_tree(that); // opt_len now includes the length of the tree representations, except
+ // the lengths of the bit lengths codes and the 5+5+4 bits for the
+ // counts.
+ // Determine the number of bit length codes to send. The pkzip format
+ // requires that at least 4 bit length codes be sent. (appnote.txt says
+ // 3 but the actual value used is 4.)
+
+ for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) {
+ if (bl_tree[Tree.bl_order[max_blindex] * 2 + 1] !== 0) break;
+ } // Update opt_len to include the bit length tree and counts
+
+
+ that.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4;
+ return max_blindex;
+ } // Output a byte on the stream.
+ // IN assertion: there is enough room in pending_buf.
- // ### Handy Functions:
- AcroForm.internal.arrayToPdfArray = function (array) {
- if (Array.isArray(array)) {
- var content = ' [';
- for (var i in array) {
- var element = array[i].toString();
- content += element;
- content += i < array.length - 1 ? ' ' : '';
+ function put_byte(p) {
+ that.pending_buf[that.pending++] = p;
+ }
+
+ function put_short(w) {
+ put_byte(w & 0xff);
+ put_byte(w >>> 8 & 0xff);
+ }
+
+ function putShortMSB(b) {
+ put_byte(b >> 8 & 0xff);
+ put_byte(b & 0xff & 0xff);
+ }
+
+ function send_bits(value, length) {
+ var val,
+ len = length;
+
+ if (bi_valid > Buf_size - len) {
+ val = value; // bi_buf |= (val << bi_valid);
+
+ bi_buf |= val << bi_valid & 0xffff;
+ put_short(bi_buf);
+ bi_buf = val >>> Buf_size - bi_valid;
+ bi_valid += len - Buf_size;
+ } else {
+ // bi_buf |= (value) << bi_valid;
+ bi_buf |= value << bi_valid & 0xffff;
+ bi_valid += len;
+ }
+ }
+
+ function send_code(c, tree) {
+ var c2 = c * 2;
+ send_bits(tree[c2] & 0xffff, tree[c2 + 1] & 0xffff);
+ } // Send a literal or distance tree in compressed form, using the codes in
+ // bl_tree.
+
+
+ function send_tree(tree, // the tree to be sent
+ max_code // and its largest code of non zero frequency
+ ) {
+ var n; // iterates over all tree elements
+
+ var prevlen = -1; // last emitted length
+
+ var curlen; // length of current code
+
+ var nextlen = tree[0 * 2 + 1]; // length of next code
+
+ var count = 0; // repeat count of the current code
+
+ var max_count = 7; // max repeat count
+
+ var min_count = 4; // min repeat count
+
+ if (nextlen === 0) {
+ max_count = 138;
+ min_count = 3;
+ }
+
+ for (n = 0; n <= max_code; n++) {
+ curlen = nextlen;
+ nextlen = tree[(n + 1) * 2 + 1];
+
+ if (++count < max_count && curlen == nextlen) {
+ continue;
+ } else if (count < min_count) {
+ do {
+ send_code(curlen, bl_tree);
+ } while (--count !== 0);
+ } else if (curlen !== 0) {
+ if (curlen != prevlen) {
+ send_code(curlen, bl_tree);
+ count--;
}
- content += ']';
- return content;
+ send_code(REP_3_6, bl_tree);
+ send_bits(count - 3, 2);
+ } else if (count <= 10) {
+ send_code(REPZ_3_10, bl_tree);
+ send_bits(count - 3, 3);
+ } else {
+ send_code(REPZ_11_138, bl_tree);
+ send_bits(count - 11, 7);
+ }
+
+ count = 0;
+ prevlen = curlen;
+
+ if (nextlen === 0) {
+ max_count = 138;
+ min_count = 3;
+ } else if (curlen == nextlen) {
+ max_count = 6;
+ min_count = 3;
+ } else {
+ max_count = 7;
+ min_count = 4;
+ }
}
- };
+ } // Send the header for a block using dynamic Huffman trees: the counts, the
+ // lengths of the bit length codes, the literal tree and the distance tree.
+ // IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
- AcroForm.internal.toPdfString = function (string) {
- string = string || "";
- // put Bracket at the Beginning of the String
- if (string.indexOf('(') !== 0) {
- string = '(' + string;
+ function send_all_trees(lcodes, dcodes, blcodes) {
+ var rank; // index in bl_order
+
+ send_bits(lcodes - 257, 5); // not +255 as stated in appnote.txt
+
+ send_bits(dcodes - 1, 5);
+ send_bits(blcodes - 4, 4); // not -3 as stated in appnote.txt
+
+ for (rank = 0; rank < blcodes; rank++) {
+ send_bits(bl_tree[Tree.bl_order[rank] * 2 + 1], 3);
}
- if (string.substring(string.length - 1) != ')') {
- string += '(';
+ send_tree(dyn_ltree, lcodes - 1); // literal tree
+
+ send_tree(dyn_dtree, dcodes - 1); // distance tree
+ } // Flush the bit buffer, keeping at most 7 bits in it.
+
+
+ function bi_flush() {
+ if (bi_valid == 16) {
+ put_short(bi_buf);
+ bi_buf = 0;
+ bi_valid = 0;
+ } else if (bi_valid >= 8) {
+ put_byte(bi_buf & 0xff);
+ bi_buf >>>= 8;
+ bi_valid -= 8;
+ }
+ } // Send one empty static block to give enough lookahead for inflate.
+ // This takes 10 bits, of which 7 may remain in the bit buffer.
+ // The current inflate code requires 9 bits of lookahead. If the
+ // last two codes for the previous block (real code plus EOB) were coded
+ // on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode
+ // the last real code. In this case we send two empty static blocks instead
+ // of one. (There are no problems if the previous block is stored or fixed.)
+ // To simplify the code, we assume the worst case of last real code encoded
+ // on one bit only.
+
+
+ function _tr_align() {
+ send_bits(STATIC_TREES << 1, 3);
+ send_code(END_BLOCK, StaticTree.static_ltree);
+ bi_flush(); // Of the 10 bits for the empty block, we have already sent
+ // (10 - bi_valid) bits. The lookahead for the last real code (before
+ // the EOB of the previous block) was thus at least one plus the length
+ // of the EOB plus what we have just sent of the empty static block.
+
+ if (1 + last_eob_len + 10 - bi_valid < 9) {
+ send_bits(STATIC_TREES << 1, 3);
+ send_code(END_BLOCK, StaticTree.static_ltree);
+ bi_flush();
}
- return string;
- };
- // ##########################
- // Classes
- // ##########################
+ last_eob_len = 7;
+ } // Save the match info and tally the frequency counts. Return true if
+ // the current block must be flushed.
- AcroForm.PDFObject = function () {
- // The Object ID in the PDF Object Model
- // todo
- var _objId;
- Object.defineProperty(this, 'objId', {
- get: function get() {
- if (!_objId) {
- if (this.internal) {
- _objId = this.internal.newObjectDeferred();
- } else if (jsPDF.API.acroformPlugin.internal) {
- // todo - find better option, that doesn't rely on a Global Static var
- _objId = jsPDF.API.acroformPlugin.internal.newObjectDeferred();
- }
- }
- if (!_objId) {
- console.log("Couldn't create Object ID");
- }
- return _objId;
- },
- configurable: false
- });
- };
+ function _tr_tally(dist, // distance of matched string
+ lc // match length-MIN_MATCH or unmatched char (if dist==0)
+ ) {
+ var out_length, in_length, dcode;
+ that.pending_buf[d_buf + last_lit * 2] = dist >>> 8 & 0xff;
+ that.pending_buf[d_buf + last_lit * 2 + 1] = dist & 0xff;
+ that.pending_buf[l_buf + last_lit] = lc & 0xff;
+ last_lit++;
- AcroForm.PDFObject.prototype.toString = function () {
- return this.objId + " 0 R";
- };
+ if (dist === 0) {
+ // lc is the unmatched char
+ dyn_ltree[lc * 2]++;
+ } else {
+ matches++; // Here, lc is the match length - MIN_MATCH
- AcroForm.PDFObject.prototype.getString = function () {
- var res = this.objId + " 0 obj\n<<";
- var content = this.getContent();
+ dist--; // dist = match distance - 1
- res += content + ">>\n";
- if (this.stream) {
- res += "stream\n";
- res += this.stream;
- res += "endstream\n";
+ dyn_ltree[(Tree._length_code[lc] + LITERALS + 1) * 2]++;
+ dyn_dtree[Tree.d_code(dist) * 2]++;
}
- res += "endobj\n";
- return res;
- };
- AcroForm.PDFObject.prototype.getContent = function () {
- /**
- * Prints out all enumerable Variables from the Object
- * @param fieldObject
- * @returns {string}
- */
- var createContentFromFieldObject = function createContentFromFieldObject(fieldObject) {
- var content = '';
+ if ((last_lit & 0x1fff) === 0 && level > 2) {
+ // Compute an upper bound for the compressed length
+ out_length = last_lit * 8;
+ in_length = strstart - block_start;
- var keys = Object.keys(fieldObject).filter(function (key) {
- return key != 'content' && key != 'appearanceStreamContent' && key.substring(0, 1) != "_";
- });
+ for (dcode = 0; dcode < D_CODES; dcode++) {
+ out_length += dyn_dtree[dcode * 2] * (5 + Tree.extra_dbits[dcode]);
+ }
- for (var i in keys) {
- var key = keys[i];
- var value = fieldObject[key];
-
- /*if (key == 'Rect' && value) {
- value = AcroForm.internal.calculateCoordinates.call(jsPDF.API.acroformPlugin.internal, value);
- }*/
-
- if (value) {
- if (Array.isArray(value)) {
- content += '/' + key + ' ' + AcroForm.internal.arrayToPdfArray(value) + "\n";
- } else if (value instanceof AcroForm.PDFObject) {
- // In case it is a reference to another PDFObject, take the referennce number
- content += '/' + key + ' ' + value.objId + " 0 R" + "\n";
- } else {
- content += '/' + key + ' ' + value + '\n';
- }
- }
- }
- return content;
- };
+ out_length >>>= 3;
+ if (matches < Math.floor(last_lit / 2) && out_length < Math.floor(in_length / 2)) return true;
+ }
+
+ return last_lit == lit_bufsize - 1; // We avoid equality with lit_bufsize because of wraparound at 64K
+ // on 16 bit machines and because stored blocks are restricted to
+ // 64K-1 bytes.
+ } // Send the block data compressed using the given Huffman trees
+
+
+ function compress_block(ltree, dtree) {
+ var dist; // distance of matched string
+
+ var lc; // match length or unmatched char (if dist === 0)
+
+ var lx = 0; // running index in l_buf
+
+ var code; // the code to send
+
+ var extra; // number of extra bits to send
+
+ if (last_lit !== 0) {
+ do {
+ dist = that.pending_buf[d_buf + lx * 2] << 8 & 0xff00 | that.pending_buf[d_buf + lx * 2 + 1] & 0xff;
+ lc = that.pending_buf[l_buf + lx] & 0xff;
+ lx++;
+
+ if (dist === 0) {
+ send_code(lc, ltree); // send a literal byte
+ } else {
+ // Here, lc is the match length - MIN_MATCH
+ code = Tree._length_code[lc];
+ send_code(code + LITERALS + 1, ltree); // send the length
+ // code
+
+ extra = Tree.extra_lbits[code];
+
+ if (extra !== 0) {
+ lc -= Tree.base_length[code];
+ send_bits(lc, extra); // send the extra length bits
+ }
+
+ dist--; // dist is now the match distance - 1
+
+ code = Tree.d_code(dist);
+ send_code(code, dtree); // send the distance code
+
+ extra = Tree.extra_dbits[code];
+
+ if (extra !== 0) {
+ dist -= Tree.base_dist[code];
+ send_bits(dist, extra); // send the extra distance bits
+ }
+ } // literal or match pair ?
+ // Check that the overlay between pending_buf and d_buf+l_buf is
+ // ok:
+
+ } while (lx < last_lit);
+ }
+
+ send_code(END_BLOCK, ltree);
+ last_eob_len = ltree[END_BLOCK * 2 + 1];
+ } // Flush the bit buffer and align the output on a byte boundary
+
+
+ function bi_windup() {
+ if (bi_valid > 8) {
+ put_short(bi_buf);
+ } else if (bi_valid > 0) {
+ put_byte(bi_buf & 0xff);
+ }
+
+ bi_buf = 0;
+ bi_valid = 0;
+ } // Copy a stored block, storing first the length and its
+ // one's complement if requested.
+
+
+ function copy_block(buf, // the input data
+ len, // its length
+ header // true if block header must be written
+ ) {
+ bi_windup(); // align on byte boundary
+
+ last_eob_len = 8; // enough lookahead for inflate
+
+ if (header) {
+ put_short(len);
+ put_short(~len);
+ }
+
+ that.pending_buf.set(window.subarray(buf, buf + len), that.pending);
+ that.pending += len;
+ } // Send a stored block
+
+
+ function _tr_stored_block(buf, // input block
+ stored_len, // length of input block
+ eof // true if this is the last block for a file
+ ) {
+ send_bits((STORED_BLOCK << 1) + (eof ? 1 : 0), 3); // send block type
+
+ copy_block(buf, stored_len, true); // with header
+ } // Determine the best encoding for the current block: dynamic trees, static
+ // trees or store, and output the encoded block to the zip file.
- var object = "";
- object += createContentFromFieldObject(this);
- return object;
- };
+ function _tr_flush_block(buf, // input block, or NULL if too old
+ stored_len, // length of input block
+ eof // true if this is the last block for a file
+ ) {
+ var opt_lenb, static_lenb; // opt_len and static_len in bytes
- AcroForm.FormXObject = function () {
- AcroForm.PDFObject.call(this);
- this.Type = "/XObject";
- this.Subtype = "/Form";
- this.FormType = 1;
- this.BBox;
- this.Matrix;
- this.Resources = "2 0 R";
- this.PieceInfo;
- var _stream;
- Object.defineProperty(this, 'Length', {
- enumerable: true,
- get: function get() {
- return _stream !== undefined ? _stream.length : 0;
- }
- });
- Object.defineProperty(this, 'stream', {
- enumerable: false,
- set: function set(val) {
- _stream = val;
- },
- get: function get() {
- if (_stream) {
- return _stream;
- } else {
- return null;
- }
- }
- });
- };
+ var max_blindex = 0; // index of last bit length code of non zero freq
+ // Build the Huffman trees unless a stored block is forced
- AcroForm.internal.inherit(AcroForm.FormXObject, AcroForm.PDFObject);
-
- AcroForm.AcroFormDictionary = function () {
- AcroForm.PDFObject.call(this);
- var _Kids = [];
- Object.defineProperty(this, 'Kids', {
- enumerable: false,
- configurable: true,
- get: function get() {
- if (_Kids.length > 0) {
- return _Kids;
- } else {
- return;
- }
- }
- });
- Object.defineProperty(this, 'Fields', {
- enumerable: true,
- configurable: true,
- get: function get() {
- return _Kids;
- }
- });
- // Default Appearance
- this.DA;
- };
+ if (level > 0) {
+ // Construct the literal and distance trees
+ l_desc.build_tree(that);
+ d_desc.build_tree(that); // At this point, opt_len and static_len are the total bit lengths
+ // of
+ // the compressed block data, excluding the tree representations.
+ // Build the bit length tree for the above two trees, and get the
+ // index
+ // in bl_order of the last bit length code to send.
- AcroForm.internal.inherit(AcroForm.AcroFormDictionary, AcroForm.PDFObject);
+ max_blindex = build_bl_tree(); // Determine the best encoding. Compute first the block length in
+ // bytes
- // ##### The Objects, the User can Create:
+ opt_lenb = that.opt_len + 3 + 7 >>> 3;
+ static_lenb = that.static_len + 3 + 7 >>> 3;
+ if (static_lenb <= opt_lenb) opt_lenb = static_lenb;
+ } else {
+ opt_lenb = static_lenb = stored_len + 5; // force a stored block
+ }
+ if (stored_len + 4 <= opt_lenb && buf != -1) {
+ // 4: two words for the lengths
+ // The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
+ // Otherwise we can't have processed more than WSIZE input bytes
+ // since
+ // the last block flush, because compression would have been
+ // successful. If LIT_BUFSIZE <= WSIZE, it is never too late to
+ // transform a block into a stored block.
+ _tr_stored_block(buf, stored_len, eof);
+ } else if (static_lenb == opt_lenb) {
+ send_bits((STATIC_TREES << 1) + (eof ? 1 : 0), 3);
+ compress_block(StaticTree.static_ltree, StaticTree.static_dtree);
+ } else {
+ send_bits((DYN_TREES << 1) + (eof ? 1 : 0), 3);
+ send_all_trees(l_desc.max_code + 1, d_desc.max_code + 1, max_blindex + 1);
+ compress_block(dyn_ltree, dyn_dtree);
+ } // The above check is made mod 2^32, for files larger than 512 MB
+ // and uLong implemented on 32 bits.
- // The Field Object contains the Variables, that every Field needs
- // Rectangle for Appearance: lower_left_X, lower_left_Y, width, height
- AcroForm.Field = function () {
- 'use strict';
-
- AcroForm.PDFObject.call(this);
-
- var _Rect;
- Object.defineProperty(this, 'Rect', {
- enumerable: true,
- configurable: false,
- get: function get() {
- if (!_Rect) {
- return;
- }
- var tmp = _Rect;
- //var calculatedRes = AcroForm.internal.calculateCoordinates(_Rect); // do later!
- return tmp;
- },
- set: function set(val) {
- _Rect = val;
- }
- });
- var _FT = "";
- Object.defineProperty(this, 'FT', {
- enumerable: true,
- set: function set(val) {
- _FT = val;
- },
- get: function get() {
- return _FT;
- }
- });
- /**
- * The Partial name of the Field Object.
- * It has to be unique.
- */
- var _T;
+ init_block();
- Object.defineProperty(this, 'T', {
- enumerable: true,
- configurable: false,
- set: function set(val) {
- _T = val;
- },
- get: function get() {
- if (!_T || _T.length < 1) {
- if (this instanceof AcroForm.ChildClass) {
- // In case of a Child from a Radio´Group, you don't need a FieldName!!!
- return;
- }
- return "(FieldObject" + AcroForm.Field.FieldNum++ + ")";
- }
- if (_T.substring(0, 1) == "(" && _T.substring(_T.length - 1)) {
- return _T;
- }
- return "(" + _T + ")";
- }
- });
+ if (eof) {
+ bi_windup();
+ }
+ }
- var _DA;
- // Defines the default appearance (Needed for variable Text)
- Object.defineProperty(this, 'DA', {
- enumerable: true,
- get: function get() {
- if (!_DA) {
- return;
- }
- return '(' + _DA + ')';
- },
- set: function set(val) {
- _DA = val;
- }
- });
+ function flush_block_only(eof) {
+ _tr_flush_block(block_start >= 0 ? block_start : -1, strstart - block_start, eof);
+
+ block_start = strstart;
+ strm.flush_pending();
+ } // Fill the window when the lookahead becomes insufficient.
+ // Updates strstart and lookahead.
+ //
+ // IN assertion: lookahead < MIN_LOOKAHEAD
+ // OUT assertions: strstart <= window_size-MIN_LOOKAHEAD
+ // At least one byte has been read, or avail_in === 0; reads are
+ // performed for at least two bytes (required for the zip translate_eol
+ // option -- not supported here).
+
+
+ function fill_window() {
+ var n, m;
+ var p;
+ var more; // Amount of free space at the end of the window.
+
+ do {
+ more = window_size - lookahead - strstart; // Deal with !@#$% 64K limit:
+
+ if (more === 0 && strstart === 0 && lookahead === 0) {
+ more = w_size;
+ } else if (more == -1) {
+ // Very unlikely, but possible on 16 bit machine if strstart ==
+ // 0
+ // and lookahead == 1 (input done one byte at time)
+ more--; // If the window is almost full and there is insufficient
+ // lookahead,
+ // move the upper half to the lower one to make room in the
+ // upper half.
+ } else if (strstart >= w_size + w_size - MIN_LOOKAHEAD) {
+ window.set(window.subarray(w_size, w_size + w_size), 0);
+ match_start -= w_size;
+ strstart -= w_size; // we now have strstart >= MAX_DIST
+
+ block_start -= w_size; // Slide the hash table (could be avoided with 32 bit values
+ // at the expense of memory usage). We slide even when level ==
+ // 0
+ // to keep the hash table consistent if we switch back to level
+ // > 0
+ // later. (Using level 0 permanently is not an optimal usage of
+ // zlib, so we don't care about this pathological case.)
+
+ n = hash_size;
+ p = n;
+
+ do {
+ m = head[--p] & 0xffff;
+ head[p] = m >= w_size ? m - w_size : 0;
+ } while (--n !== 0);
+
+ n = w_size;
+ p = n;
+
+ do {
+ m = prev[--p] & 0xffff;
+ prev[p] = m >= w_size ? m - w_size : 0; // If n is not on any hash chain, prev[n] is garbage but
+ // its value will never be used.
+ } while (--n !== 0);
+
+ more += w_size;
+ }
- var _DV;
- // Defines the default value
- Object.defineProperty(this, 'DV', {
- enumerable: true,
- configurable: true,
- get: function get() {
- if (!_DV) {
- return;
- }
- return _DV;
- },
- set: function set(val) {
- _DV = val;
- }
- });
+ if (strm.avail_in === 0) return; // If there was no sliding:
+ // strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 &&
+ // more == window_size - lookahead - strstart
+ // => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1)
+ // => more >= window_size - 2*WSIZE + 2
+ // In the BIG_MEM or MMAP case (not yet supported),
+ // window_size == input_size + MIN_LOOKAHEAD &&
+ // strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD.
+ // Otherwise, window_size == 2*WSIZE so more >= 2.
+ // If there was sliding, more >= WSIZE. So in all cases, more >= 2.
+
+ n = strm.read_buf(window, strstart + lookahead, more);
+ lookahead += n; // Initialize the hash value now that we have some input:
+
+ if (lookahead >= MIN_MATCH) {
+ ins_h = window[strstart] & 0xff;
+ ins_h = (ins_h << hash_shift ^ window[strstart + 1] & 0xff) & hash_mask;
+ } // If the whole input has less than MIN_MATCH bytes, ins_h is
+ // garbage,
+ // but this is not important since only literal bytes will be
+ // emitted.
+
+ } while (lookahead < MIN_LOOKAHEAD && strm.avail_in !== 0);
+ } // Copy without compression as much as possible from the input stream,
+ // return
+ // the current block state.
+ // This function does not insert new strings in the dictionary since
+ // uncompressible data is probably not useful. This function is used
+ // only for the level=0 compression option.
+ // NOTE: this function should be optimized to avoid extra copying from
+ // window to pending_buf.
+
+
+ function deflate_stored(flush) {
+ // Stored blocks are limited to 0xffff bytes, pending_buf is limited
+ // to pending_buf_size, and each stored block has a 5 byte header:
+ var max_block_size = 0xffff;
+ var max_start;
+
+ if (max_block_size > pending_buf_size - 5) {
+ max_block_size = pending_buf_size - 5;
+ } // Copy as much as possible from input to output:
- //this.Type = "/Annot";
- //this.Subtype = "/Widget";
- Object.defineProperty(this, 'Type', {
- enumerable: true,
- get: function get() {
- return this.hasAnnotation ? "/Annot" : null;
- }
- });
- Object.defineProperty(this, 'Subtype', {
- enumerable: true,
- get: function get() {
- return this.hasAnnotation ? "/Widget" : null;
- }
- });
+ while (true) {
+ // Fill the window as much as possible:
+ if (lookahead <= 1) {
+ fill_window();
+ if (lookahead === 0 && flush == Z_NO_FLUSH) return NeedMore;
+ if (lookahead === 0) break; // flush the current block
+ }
- /**
- *
- * @type {Array}
- */
- this.BG;
+ strstart += lookahead;
+ lookahead = 0; // Emit a stored block if pending_buf will be full:
- Object.defineProperty(this, 'hasAnnotation', {
- enumerable: false,
- get: function get() {
- if (this.Rect || this.BC || this.BG) {
- return true;
- }
- return false;
- }
- });
+ max_start = block_start + max_block_size;
- Object.defineProperty(this, 'hasAppearanceStream', {
- enumerable: false,
- configurable: true,
- writable: true
- });
- };
- AcroForm.Field.FieldNum = 0;
-
- AcroForm.internal.inherit(AcroForm.Field, AcroForm.PDFObject);
-
- AcroForm.ChoiceField = function () {
- AcroForm.Field.call(this);
- // Field Type = Choice Field
- this.FT = "/Ch";
- // options
- this.Opt = [];
- this.V = '()';
- // Top Index
- this.TI = 0;
- /**
- * Defines, whether the
- * @type {boolean}
- */
- this.combo = false;
- /**
- * Defines, whether the Choice Field is an Edit Field.
- * An Edit Field is automatically an Combo Field.
- */
- Object.defineProperty(this, 'edit', {
- enumerable: true,
- set: function set(val) {
- if (val == true) {
- this._edit = true;
- // ComboBox has to be true
- this.combo = true;
- } else {
- this._edit = false;
- }
- },
- get: function get() {
- if (!this._edit) {
- return false;
- }
- return this._edit;
- },
- configurable: false
- });
- this.hasAppearanceStream = true;
- Object.defineProperty(this, 'V', {
- get: function get() {
- AcroForm.internal.toPdfString();
- }
- });
- };
- AcroForm.internal.inherit(AcroForm.ChoiceField, AcroForm.Field);
- window["ChoiceField"] = AcroForm.ChoiceField;
+ if (strstart === 0 || strstart >= max_start) {
+ // strstart === 0 is possible when wraparound on 16-bit machine
+ lookahead = strstart - max_start;
+ strstart = max_start;
+ flush_block_only(false);
+ if (strm.avail_out === 0) return NeedMore;
+ } // Flush if we may have to slide, otherwise block_start may become
+ // negative and the data will be gone:
- AcroForm.ListBox = function () {
- AcroForm.ChoiceField.call(this);
- //var combo = true;
- };
- AcroForm.internal.inherit(AcroForm.ListBox, AcroForm.ChoiceField);
- window["ListBox"] = AcroForm.ListBox;
- AcroForm.ComboBox = function () {
- AcroForm.ListBox.call(this);
- this.combo = true;
- };
- AcroForm.internal.inherit(AcroForm.ComboBox, AcroForm.ListBox);
- window["ComboBox"] = AcroForm.ComboBox;
+ if (strstart - block_start >= w_size - MIN_LOOKAHEAD) {
+ flush_block_only(false);
+ if (strm.avail_out === 0) return NeedMore;
+ }
+ }
- AcroForm.EditBox = function () {
- AcroForm.ComboBox.call(this);
- this.edit = true;
- };
- AcroForm.internal.inherit(AcroForm.EditBox, AcroForm.ComboBox);
- window["EditBox"] = AcroForm.EditBox;
+ flush_block_only(flush == Z_FINISH);
+ if (strm.avail_out === 0) return flush == Z_FINISH ? FinishStarted : NeedMore;
+ return flush == Z_FINISH ? FinishDone : BlockDone;
+ }
- AcroForm.Button = function () {
- AcroForm.Field.call(this);
- this.FT = "/Btn";
- //this.hasAnnotation = true;
- };
- AcroForm.internal.inherit(AcroForm.Button, AcroForm.Field);
- window["Button"] = AcroForm.Button;
+ function longest_match(cur_match) {
+ var chain_length = max_chain_length; // max hash chain length
- AcroForm.PushButton = function () {
- AcroForm.Button.call(this);
- this.pushbutton = true;
- };
- AcroForm.internal.inherit(AcroForm.PushButton, AcroForm.Button);
- window["PushButton"] = AcroForm.PushButton;
-
- AcroForm.RadioButton = function () {
- AcroForm.Button.call(this);
- this.radio = true;
- var _Kids = [];
- Object.defineProperty(this, 'Kids', {
- enumerable: true,
- get: function get() {
- if (_Kids.length > 0) {
- return _Kids;
- }
- }
- });
+ var scan = strstart; // current string
- Object.defineProperty(this, '__Kids', {
- get: function get() {
- return _Kids;
- }
- });
+ var match; // matched string
- var _noToggleToOff;
+ var len; // length of current match
- Object.defineProperty(this, 'noToggleToOff', {
- enumerable: false,
- get: function get() {
- return _noToggleToOff;
- },
- set: function set(val) {
- _noToggleToOff = val;
- }
- });
+ var best_len = prev_length; // best match length so far
- //this.hasAnnotation = false;
- };
- AcroForm.internal.inherit(AcroForm.RadioButton, AcroForm.Button);
- window["RadioButton"] = AcroForm.RadioButton;
+ var limit = strstart > w_size - MIN_LOOKAHEAD ? strstart - (w_size - MIN_LOOKAHEAD) : 0;
+ var _nice_match = nice_match; // Stop when cur_match becomes <= limit. To simplify the code,
+ // we prevent matches with the string of window index 0.
- /*
- * The Child classs of a RadioButton (the radioGroup)
- * -> The single Buttons
- */
- AcroForm.ChildClass = function (parent, name) {
- AcroForm.Field.call(this);
- this.Parent = parent;
+ var wmask = w_mask;
+ var strend = strstart + MAX_MATCH;
+ var scan_end1 = window[scan + best_len - 1];
+ var scan_end = window[scan + best_len]; // The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of
+ // 16.
+ // It is easy to get rid of this optimization if necessary.
+ // Do not waste too much time if we already have a good match:
- // todo: set AppearanceType as variable that can be set from the outside...
- this._AppearanceType = AcroForm.Appearance.RadioButton.Circle; // The Default appearanceType is the Circle
- this.appearanceStreamContent = this._AppearanceType.createAppearanceStream(name);
+ if (prev_length >= good_match) {
+ chain_length >>= 2;
+ } // Do not look for matches beyond the end of the input. This is
+ // necessary
+ // to make deflate deterministic.
- // Set Print in the Annot Flag
- this.F = AcroForm.internal.setBitPosition(this.F, 3, 1);
- // Set AppearanceCharacteristicsDictionary with default appearance if field is not interacting with user
- this.MK = this._AppearanceType.createMK(); // (8) -> Cross, (1)-> Circle, ()-> nothing
+ if (_nice_match > lookahead) _nice_match = lookahead;
- // Default Appearance is Off
- this.AS = "/Off"; // + name;
+ do {
+ match = cur_match; // Skip to next match if the match length cannot increase
+ // or if the match length is less than 2:
- this._Name = name;
- };
- AcroForm.internal.inherit(AcroForm.ChildClass, AcroForm.Field);
+ if (window[match + best_len] != scan_end || window[match + best_len - 1] != scan_end1 || window[match] != window[scan] || window[++match] != window[scan + 1]) continue; // The check at best_len-1 can be removed because it will be made
+ // again later. (This heuristic is not always a win.)
+ // It is not necessary to compare scan[2] and match[2] since they
+ // are always equal when the other bytes match, given that
+ // the hash keys are equal and that HASH_BITS >= 8.
- AcroForm.RadioButton.prototype.setAppearance = function (appearance) {
- if (!('createAppearanceStream' in appearance && 'createMK' in appearance)) {
- console.log("Couldn't assign Appearance to RadioButton. Appearance was Invalid!");
- return;
- }
- for (var i in this.__Kids) {
- var child = this.__Kids[i];
+ scan += 2;
+ match++; // We check for insufficient lookahead only every 8th comparison;
+ // the 256th check will be made at strstart+258.
- child.appearanceStreamContent = appearance.createAppearanceStream(child._Name);
- child.MK = appearance.createMK();
- }
- };
+ do {} while (window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match] && scan < strend);
- AcroForm.RadioButton.prototype.createOption = function (name) {
- var parent = this;
- var kidCount = this.__Kids.length;
+ len = MAX_MATCH - (strend - scan);
+ scan = strend - MAX_MATCH;
- // Create new Child for RadioGroup
- var child = new AcroForm.ChildClass(parent, name);
- // Add to Parent
- this.__Kids.push(child);
+ if (len > best_len) {
+ match_start = cur_match;
+ best_len = len;
+ if (len >= _nice_match) break;
+ scan_end1 = window[scan + best_len - 1];
+ scan_end = window[scan + best_len];
+ }
+ } while ((cur_match = prev[cur_match & wmask] & 0xffff) > limit && --chain_length !== 0);
- jsPDF.API.addField(child);
+ if (best_len <= lookahead) return best_len;
+ return lookahead;
+ } // Compress as much as possible from the input stream, return the current
+ // block state.
+ // This function does not perform lazy evaluation of matches and inserts
+ // new strings in the dictionary only for unmatched strings or for short
+ // matches. It is used only for the fast compression options.
- return child;
- };
- AcroForm.CheckBox = function () {
- Button.call(this);
- this.appearanceStreamContent = AcroForm.Appearance.CheckBox.createAppearanceStream();
- this.MK = AcroForm.Appearance.CheckBox.createMK();
- this.AS = "/On";
- this.V = "/On";
- };
- AcroForm.internal.inherit(AcroForm.CheckBox, AcroForm.Button);
- window["CheckBox"] = AcroForm.CheckBox;
-
- AcroForm.TextField = function () {
- AcroForm.Field.call(this);
- //this.DA = AcroForm.createDefaultAppearanceStream();
- var _V;
- Object.defineProperty(this, 'V', {
- get: function get() {
- if (_V) {
- return "(" + _V + ")";
- } else {
- return _V;
- }
- },
- enumerable: true,
- set: function set(val) {
- _V = val;
- }
- });
+ function deflate_fast(flush) {
+ // short hash_head = 0; // head of the hash chain
+ var hash_head = 0; // head of the hash chain
- var _DV;
- Object.defineProperty(this, 'DV', {
- get: function get() {
- if (_DV) {
- return "(" + _DV + ")";
- } else {
- return _DV;
- }
- },
- enumerable: true,
- set: function set(val) {
- _DV = val;
- }
- });
+ var bflush; // set if current block must be flushed
- var _multiline = false;
- Object.defineProperty(this, 'multiline', {
- enumerable: false,
- get: function get() {
- return _multiline;
- },
- set: function set(val) {
- _multiline = val;
+ while (true) {
+ // Make sure that we always have enough lookahead, except
+ // at the end of the input file. We need MAX_MATCH bytes
+ // for the next match, plus MIN_MATCH bytes to insert the
+ // string following the next match.
+ if (lookahead < MIN_LOOKAHEAD) {
+ fill_window();
+
+ if (lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) {
+ return NeedMore;
}
- });
- //this.multiline = false;
- //this.password = false;
- /**
- * For PDF 1.4
- * @type {boolean}
- */
- //this.fileSelect = false;
- /**
- * For PDF 1.4
- * @type {boolean}
- */
- //this.doNotSpellCheck = false;
- /**
- * For PDF 1.4
- * @type {boolean}
- */
- //this.doNotScroll = false;
+ if (lookahead === 0) break; // flush the current block
+ } // Insert the string window[strstart .. strstart+2] in the
+ // dictionary, and set hash_head to the head of the hash chain:
- Object.defineProperty(this, 'hasAppearanceStream', {
- enumerable: false,
- get: function get() {
- return this.V || this.DV;
- }
- });
- };
- AcroForm.internal.inherit(AcroForm.TextField, AcroForm.Field);
- window["TextField"] = AcroForm.TextField;
-
- AcroForm.PasswordField = function () {
- TextField.call(this);
- Object.defineProperty(this, 'password', {
- value: true,
- enumerable: false,
- configurable: false,
- writable: false
- });
- };
- AcroForm.internal.inherit(AcroForm.PasswordField, AcroForm.TextField);
- window["PasswordField"] = AcroForm.PasswordField;
+ if (lookahead >= MIN_MATCH) {
+ ins_h = (ins_h << hash_shift ^ window[strstart + (MIN_MATCH - 1)] & 0xff) & hash_mask; // prev[strstart&w_mask]=hash_head=head[ins_h];
- // ############ internal functions
+ hash_head = head[ins_h] & 0xffff;
+ prev[strstart & w_mask] = head[ins_h];
+ head[ins_h] = strstart;
+ } // Find the longest match, discarding those <= prev_length.
+ // At this point we have always match_length < MIN_MATCH
- /*
- * small workaround for calculating the TextMetric aproximately
- * @param text
- * @param fontsize
- * @returns {TextMetrics} (Has Height and Width)
- */
- AcroForm.internal.calculateFontSpace = function (text, fontsize, fonttype) {
- var fonttype = fonttype || "helvetica";
- //re-use canvas object for speed improvements
- var canvas = AcroForm.internal.calculateFontSpace.canvas || (AcroForm.internal.calculateFontSpace.canvas = document.createElement('canvas'));
-
- var context = canvas.getContext('2d');
- context.save();
- var newFont = fontsize + " " + fonttype;
- context.font = newFont;
- var res = context.measureText(text);
- context.fontcolor = 'black';
- // Calculate height:
- var context = canvas.getContext('2d');
- res.height = context.measureText("3").width * 1.5; // 3 because in ZapfDingbats its a Hook and a 3 in normal fonts
- context.restore();
-
- var width = res.width;
- return res;
- };
+ if (hash_head !== 0 && (strstart - hash_head & 0xffff) <= w_size - MIN_LOOKAHEAD) {
+ // To simplify the code, we prevent matches with the string
+ // of window index 0 (in particular we have to avoid a match
+ // of the string with itself at the start of the input file).
+ if (strategy != Z_HUFFMAN_ONLY) {
+ match_length = longest_match(hash_head);
+ } // longest_match() sets match_start
- AcroForm.internal.calculateX = function (formObject, text, font, maxFontSize) {
- var maxFontSize = maxFontSize || 12;
- var font = font || "helvetica";
- var returnValue = {
- text: "",
- fontSize: ""
- };
- // Remove Brackets
- text = text.substr(0, 1) == '(' ? text.substr(1) : text;
- text = text.substr(text.length - 1) == ')' ? text.substr(0, text.length - 1) : text;
- // split into array of words
- var textSplit = text.split(' ');
+ }
- /**
- * the color could be ((alpha)||(r,g,b)||(c,m,y,k))
- * @type {string}
- */
- var color = "0 g\n";
- var fontSize = maxFontSize; // The Starting fontSize (The Maximum)
- var lineSpacing = 2;
- var borderPadding = 2;
-
- var height = AcroForm.Appearance.internal.getHeight(formObject) || 0;
- height = height < 0 ? -height : height;
- var width = AcroForm.Appearance.internal.getWidth(formObject) || 0;
- width = width < 0 ? -width : width;
-
- var isSmallerThanWidth = function isSmallerThanWidth(i, lastLine, fontSize) {
- if (i + 1 < textSplit.length) {
- var tmp = lastLine + " " + textSplit[i + 1];
- var TextWidth = AcroForm.internal.calculateFontSpace(tmp, fontSize + "px", font).width;
- var FieldWidth = width - 2 * borderPadding;
- return TextWidth <= FieldWidth;
+ if (match_length >= MIN_MATCH) {
+ // check_match(strstart, match_start, match_length);
+ bflush = _tr_tally(strstart - match_start, match_length - MIN_MATCH);
+ lookahead -= match_length; // Insert new strings in the hash table only if the match length
+ // is not too large. This saves time but degrades compression.
+
+ if (match_length <= max_lazy_match && lookahead >= MIN_MATCH) {
+ match_length--; // string at strstart already in hash table
+
+ do {
+ strstart++;
+ ins_h = (ins_h << hash_shift ^ window[strstart + (MIN_MATCH - 1)] & 0xff) & hash_mask; // prev[strstart&w_mask]=hash_head=head[ins_h];
+
+ hash_head = head[ins_h] & 0xffff;
+ prev[strstart & w_mask] = head[ins_h];
+ head[ins_h] = strstart; // strstart never exceeds WSIZE-MAX_MATCH, so there are
+ // always MIN_MATCH bytes ahead.
+ } while (--match_length !== 0);
+
+ strstart++;
} else {
- return false;
+ strstart += match_length;
+ match_length = 0;
+ ins_h = window[strstart] & 0xff;
+ ins_h = (ins_h << hash_shift ^ window[strstart + 1] & 0xff) & hash_mask; // If lookahead < MIN_MATCH, ins_h is garbage, but it does
+ // not
+ // matter since it will be recomputed at next deflate call.
}
- };
+ } else {
+ // No match, output a literal byte
+ bflush = _tr_tally(0, window[strstart] & 0xff);
+ lookahead--;
+ strstart++;
+ }
- fontSize++;
- FontSize: while (true) {
- var text = "";
- fontSize--;
- var textHeight = AcroForm.internal.calculateFontSpace("3", fontSize + "px", font).height;
- var startY = formObject.multiline ? height - fontSize : (height - textHeight) / 2;
- startY += lineSpacing;
- var startX = -borderPadding;
-
- var lastX = startX,
- lastY = startY;
- var firstWordInLine = 0,
- lastWordInLine = 0;
- var lastLength = 0;
-
- var y = 0;
- if (fontSize == 0) {
- // In case, the Text doesn't fit at all
- fontSize = 12;
- text = "(...) Tj\n";
- text += "% Width of Text: " + AcroForm.internal.calculateFontSpace(text, "1px").width + ", FieldWidth:" + width + "\n";
- break;
- }
+ if (bflush) {
+ flush_block_only(false);
+ if (strm.avail_out === 0) return NeedMore;
+ }
+ }
- lastLength = AcroForm.internal.calculateFontSpace(textSplit[0] + " ", fontSize + "px", font).width;
-
- var lastLine = "";
- var lineCount = 0;
- Line: for (var i in textSplit) {
- lastLine += textSplit[i] + " ";
- // Remove last blank
- lastLine = lastLine.substr(lastLine.length - 1) == " " ? lastLine.substr(0, lastLine.length - 1) : lastLine;
- var key = parseInt(i);
- lastLength = AcroForm.internal.calculateFontSpace(lastLine + " ", fontSize + "px", font).width;
- var nextLineIsSmaller = isSmallerThanWidth(key, lastLine, fontSize);
- var isLastWord = i >= textSplit.length - 1;
- if (nextLineIsSmaller && !isLastWord) {
- lastLine += " ";
- continue; // Line
- } else if (!nextLineIsSmaller && !isLastWord) {
- if (!formObject.multiline) {
- continue FontSize;
- } else {
- if ((textHeight + lineSpacing) * (lineCount + 2) + lineSpacing > height) {
- // If the Text is higher than the FieldObject
- continue FontSize;
- }
- lastWordInLine = key;
- // go on
- }
- } else if (isLastWord) {
- lastWordInLine = key;
- } else {
- if (formObject.multiline && (textHeight + lineSpacing) * (lineCount + 2) + lineSpacing > height) {
- // If the Text is higher than the FieldObject
- continue FontSize;
- }
- }
+ flush_block_only(flush == Z_FINISH);
- var line = '';
+ if (strm.avail_out === 0) {
+ if (flush == Z_FINISH) return FinishStarted;else return NeedMore;
+ }
- for (var x = firstWordInLine; x <= lastWordInLine; x++) {
- line += textSplit[x] + ' ';
- }
+ return flush == Z_FINISH ? FinishDone : BlockDone;
+ } // Same as above, but achieves better compression. We use a lazy
+ // evaluation for matches: a match is finally adopted only if there is
+ // no better match at the next window position.
- // Remove last blank
- line = line.substr(line.length - 1) == " " ? line.substr(0, line.length - 1) : line;
- //lastLength -= blankSpace.width;
- lastLength = AcroForm.internal.calculateFontSpace(line, fontSize + "px", font).width;
-
- // Calculate startX
- switch (formObject.Q) {
- case 2:
- // Right justified
- startX = width - lastLength - borderPadding;
- break;
- case 1:
- // Q = 1 := Text-Alignment: Center
- startX = (width - lastLength) / 2;
- break;
- case 0:
- default:
- startX = borderPadding;
- break;
- }
- text += startX + ' ' + lastY + ' Td\n';
- text += '(' + line + ') Tj\n';
- // reset X in PDF
- text += -startX + ' 0 Td\n';
-
- // After a Line, adjust y position
- lastY = -(fontSize + lineSpacing);
- lastX = startX;
-
- // Reset for next iteration step
- lastLength = 0;
- firstWordInLine = lastWordInLine + 1;
- lineCount++;
-
- lastLine = "";
- continue Line;
- }
- break;
- }
- returnValue.text = text;
- returnValue.fontSize = fontSize;
+ function deflate_slow(flush) {
+ // short hash_head = 0; // head of hash chain
+ var hash_head = 0; // head of hash chain
- return returnValue;
- };
+ var bflush; // set if current block must be flushed
- AcroForm.internal.calculateAppearanceStream = function (formObject) {
- if (formObject.appearanceStreamContent) {
- // If appearanceStream is already set, use it
- return formObject.appearanceStreamContent;
- }
+ var max_insert; // Process the input block.
- if (!formObject.V && !formObject.DV) {
- return;
- }
+ while (true) {
+ // Make sure that we always have enough lookahead, except
+ // at the end of the input file. We need MAX_MATCH bytes
+ // for the next match, plus MIN_MATCH bytes to insert the
+ // string following the next match.
+ if (lookahead < MIN_LOOKAHEAD) {
+ fill_window();
+
+ if (lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) {
+ return NeedMore;
+ }
- // else calculate it
+ if (lookahead === 0) break; // flush the current block
+ } // Insert the string window[strstart .. strstart+2] in the
+ // dictionary, and set hash_head to the head of the hash chain:
- var stream = '';
- var text = formObject.V || formObject.DV;
+ if (lookahead >= MIN_MATCH) {
+ ins_h = (ins_h << hash_shift ^ window[strstart + (MIN_MATCH - 1)] & 0xff) & hash_mask; // prev[strstart&w_mask]=hash_head=head[ins_h];
- var calcRes = AcroForm.internal.calculateX(formObject, text);
+ hash_head = head[ins_h] & 0xffff;
+ prev[strstart & w_mask] = head[ins_h];
+ head[ins_h] = strstart;
+ } // Find the longest match, discarding those <= prev_length.
- stream += '/Tx BMC\n' + 'q\n' +
- //color + '\n' +
- '/F1 ' + calcRes.fontSize + ' Tf\n' +
- // Text Matrix
- '1 0 0 1 0 0 Tm\n';
- // Begin Text
- stream += 'BT\n';
- stream += calcRes.text;
- // End Text
- stream += 'ET\n';
- stream += 'Q\n' + 'EMC\n';
- var appearanceStreamContent = new AcroForm.createFormXObject(formObject);
+ prev_length = match_length;
+ prev_match = match_start;
+ match_length = MIN_MATCH - 1;
- appearanceStreamContent.stream = stream;
+ if (hash_head !== 0 && prev_length < max_lazy_match && (strstart - hash_head & 0xffff) <= w_size - MIN_LOOKAHEAD) {
+ // To simplify the code, we prevent matches with the string
+ // of window index 0 (in particular we have to avoid a match
+ // of the string with itself at the start of the input file).
+ if (strategy != Z_HUFFMAN_ONLY) {
+ match_length = longest_match(hash_head);
+ } // longest_match() sets match_start
- var appearance = {
- N: {
- 'Normal': appearanceStreamContent
+
+ if (match_length <= 5 && (strategy == Z_FILTERED || match_length == MIN_MATCH && strstart - match_start > 4096)) {
+ // If prev_match is also MIN_MATCH, match_start is garbage
+ // but we will ignore the current match anyway.
+ match_length = MIN_MATCH - 1;
}
- };
+ } // If there was a match at the previous step and the current
+ // match is not better, output the previous match:
- return appearanceStreamContent;
- };
- /*
- * Converts the Parameters from x,y,w,h to lowerLeftX, lowerLeftY, upperRightX, upperRightY
- * @param x
- * @param y
- * @param w
- * @param h
- * @returns {*[]}
- */
- AcroForm.internal.calculateCoordinates = function (x, y, w, h) {
- var coordinates = {};
+ if (prev_length >= MIN_MATCH && match_length <= prev_length) {
+ max_insert = strstart + lookahead - MIN_MATCH; // Do not insert strings in hash table beyond this.
+ // check_match(strstart-1, prev_match, prev_length);
- if (this.internal) {
- var mmtopx = function mmtopx(x) {
- return x * this.internal.scaleFactor;
- };
+ bflush = _tr_tally(strstart - 1 - prev_match, prev_length - MIN_MATCH); // Insert in hash table all strings up to the end of the match.
+ // strstart-1 and strstart are already inserted. If there is not
+ // enough lookahead, the last two strings are not inserted in
+ // the hash table.
- if (Array.isArray(x)) {
- x[0] = AcroForm.scale(x[0]);
- x[1] = AcroForm.scale(x[1]);
- x[2] = AcroForm.scale(x[2]);
- x[3] = AcroForm.scale(x[3]);
+ lookahead -= prev_length - 1;
+ prev_length -= 2;
- coordinates.lowerLeft_X = x[0] | 0;
- coordinates.lowerLeft_Y = mmtopx.call(this, this.internal.pageSize.height) - x[3] - x[1] | 0;
- coordinates.upperRight_X = x[0] + x[2] | 0;
- coordinates.upperRight_Y = mmtopx.call(this, this.internal.pageSize.height) - x[1] | 0;
- } else {
- x = AcroForm.scale(x);
- y = AcroForm.scale(y);
- w = AcroForm.scale(w);
- h = AcroForm.scale(h);
- coordinates.lowerLeft_X = x | 0;
- coordinates.lowerLeft_Y = this.internal.pageSize.height - y | 0;
- coordinates.upperRight_X = x + w | 0;
- coordinates.upperRight_Y = this.internal.pageSize.height - y + h | 0;
+ do {
+ if (++strstart <= max_insert) {
+ ins_h = (ins_h << hash_shift ^ window[strstart + (MIN_MATCH - 1)] & 0xff) & hash_mask; // prev[strstart&w_mask]=hash_head=head[ins_h];
+
+ hash_head = head[ins_h] & 0xffff;
+ prev[strstart & w_mask] = head[ins_h];
+ head[ins_h] = strstart;
+ }
+ } while (--prev_length !== 0);
+
+ match_available = 0;
+ match_length = MIN_MATCH - 1;
+ strstart++;
+
+ if (bflush) {
+ flush_block_only(false);
+ if (strm.avail_out === 0) return NeedMore;
}
- } else {
- // old method, that is fallback, if we can't get the pageheight, the coordinate-system starts from lower left
- if (Array.isArray(x)) {
- coordinates.lowerLeft_X = x[0] | 0;
- coordinates.lowerLeft_Y = x[1] | 0;
- coordinates.upperRight_X = x[0] + x[2] | 0;
- coordinates.upperRight_Y = x[1] + x[3] | 0;
- } else {
- coordinates.lowerLeft_X = x | 0;
- coordinates.lowerLeft_Y = y | 0;
- coordinates.upperRight_X = x + w | 0;
- coordinates.upperRight_Y = y + h | 0;
+ } else if (match_available !== 0) {
+ // If there was no match at the previous position, output a
+ // single literal. If there was a match but the current match
+ // is longer, truncate the previous match to a single literal.
+ bflush = _tr_tally(0, window[strstart - 1] & 0xff);
+
+ if (bflush) {
+ flush_block_only(false);
}
+
+ strstart++;
+ lookahead--;
+ if (strm.avail_out === 0) return NeedMore;
+ } else {
+ // There is no previous match to compare with, wait for
+ // the next step to decide.
+ match_available = 1;
+ strstart++;
+ lookahead--;
+ }
}
- return [coordinates.lowerLeft_X, coordinates.lowerLeft_Y, coordinates.upperRight_X, coordinates.upperRight_Y];
- };
+ if (match_available !== 0) {
+ bflush = _tr_tally(0, window[strstart - 1] & 0xff);
+ match_available = 0;
+ }
- AcroForm.internal.calculateColor = function (r, g, b) {
- var color = new Array(3);
- color.r = r | 0;
- color.g = g | 0;
- color.b = b | 0;
- return color;
- };
+ flush_block_only(flush == Z_FINISH);
- AcroForm.internal.getBitPosition = function (variable, position) {
- variable = variable || 0;
- var bitMask = 1;
- bitMask = bitMask << position - 1;
- return variable | bitMask;
- };
+ if (strm.avail_out === 0) {
+ if (flush == Z_FINISH) return FinishStarted;else return NeedMore;
+ }
- AcroForm.internal.setBitPosition = function (variable, position, value) {
- variable = variable || 0;
- value = value || 1;
+ return flush == Z_FINISH ? FinishDone : BlockDone;
+ }
- var bitMask = 1;
- bitMask = bitMask << position - 1;
+ function deflateReset(strm) {
+ strm.total_in = strm.total_out = 0;
+ strm.msg = null; //
+
+ that.pending = 0;
+ that.pending_out = 0;
+ status = BUSY_STATE;
+ last_flush = Z_NO_FLUSH;
+ tr_init();
+ lm_init();
+ return Z_OK;
+ }
- if (value == 1) {
- // Set the Bit to 1
- var variable = variable | bitMask;
- } else {
- // Set the Bit to 0
- var variable = variable & ~bitMask;
+ that.deflateInit = function (strm, _level, bits, _method, memLevel, _strategy) {
+ if (!_method) _method = Z_DEFLATED;
+ if (!memLevel) memLevel = DEF_MEM_LEVEL;
+ if (!_strategy) _strategy = Z_DEFAULT_STRATEGY; // byte[] my_version=ZLIB_VERSION;
+ //
+ // if (!version || version[0] != my_version[0]
+ // || stream_size != sizeof(z_stream)) {
+ // return Z_VERSION_ERROR;
+ // }
+
+ strm.msg = null;
+ if (_level == Z_DEFAULT_COMPRESSION) _level = 6;
+
+ if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || _method != Z_DEFLATED || bits < 9 || bits > 15 || _level < 0 || _level > 9 || _strategy < 0 || _strategy > Z_HUFFMAN_ONLY) {
+ return Z_STREAM_ERROR;
}
- return variable;
- };
+ strm.dstate = that;
+ w_bits = bits;
+ w_size = 1 << w_bits;
+ w_mask = w_size - 1;
+ hash_bits = memLevel + 7;
+ hash_size = 1 << hash_bits;
+ hash_mask = hash_size - 1;
+ hash_shift = Math.floor((hash_bits + MIN_MATCH - 1) / MIN_MATCH);
+ window = new Uint8Array(w_size * 2);
+ prev = [];
+ head = [];
+ lit_bufsize = 1 << memLevel + 6; // 16K elements by default
+ // We overlay pending_buf and d_buf+l_buf. This works since the average
+ // output size for (length,distance) codes is <= 24 bits.
+
+ that.pending_buf = new Uint8Array(lit_bufsize * 4);
+ pending_buf_size = lit_bufsize * 4;
+ d_buf = Math.floor(lit_bufsize / 2);
+ l_buf = (1 + 2) * lit_bufsize;
+ level = _level;
+ strategy = _strategy;
+ return deflateReset(strm);
+ };
- /**
- * jsPDF addHTML PlugIn
- * Copyright (c) 2014 Diego Casorran
- *
- * Licensed under the MIT License.
- * http://opensource.org/licenses/mit-license
- */
+ that.deflateEnd = function () {
+ if (status != INIT_STATE && status != BUSY_STATE && status != FINISH_STATE) {
+ return Z_STREAM_ERROR;
+ } // Deallocate in reverse order of allocations:
- (function (jsPDFAPI) {
- 'use strict';
-
- /**
- * Renders an HTML element to canvas object which added to the PDF
- *
- * This PlugIn requires html2canvas: https://github.com/niklasvh/html2canvas
- * OR rasterizeHTML: https://github.com/cburgmer/rasterizeHTML.js
- *
- * @public
- * @function
- * @param element {Mixed} HTML Element, or anything supported by html2canvas.
- * @param x {Number} starting X coordinate in jsPDF instance's declared units.
- * @param y {Number} starting Y coordinate in jsPDF instance's declared units.
- * @param options {Object} Additional options, check the code below.
- * @param callback {Function} to call when the rendering has finished.
- *
- * NOTE: Every parameter is optional except 'element' and 'callback', in such
- * case the image is positioned at 0x0 covering the whole PDF document
- * size. Ie, to easily take screenshots of webpages saving them to PDF.
- */
- jsPDFAPI.addHTML = function (element, x, y, options, callback) {
- 'use strict';
-
- if (typeof html2canvas === 'undefined' && typeof rasterizeHTML === 'undefined') throw new Error('You need either ' + 'https://github.com/niklasvh/html2canvas' + ' or https://github.com/cburgmer/rasterizeHTML.js');
-
- if (typeof x !== 'number') {
- options = x;
- callback = y;
- }
-
- if (typeof options === 'function') {
- callback = options;
- options = null;
- }
-
- var I = this.internal,
- K = I.scaleFactor,
- W = I.pageSize.width,
- H = I.pageSize.height;
-
- options = options || {};
- options.onrendered = function (obj) {
- x = parseInt(x) || 0;
- y = parseInt(y) || 0;
- var dim = options.dim || {};
- var h = dim.h || 0;
- var w = dim.w || Math.min(W, obj.width / K) - x;
-
- var format = 'JPEG';
- if (options.format) format = options.format;
-
- if (obj.height > H && options.pagesplit) {
- var crop = function () {
- var cy = 0;
- while (1) {
- var canvas = document.createElement('canvas');
- canvas.width = Math.min(W * K, obj.width);
- canvas.height = Math.min(H * K, obj.height - cy);
- var ctx = canvas.getContext('2d');
- ctx.drawImage(obj, 0, cy, obj.width, canvas.height, 0, 0, canvas.width, canvas.height);
- var args = [canvas, x, cy ? 0 : y, canvas.width / K, canvas.height / K, format, null, 'SLOW'];
- this.addImage.apply(this, args);
- cy += canvas.height;
- if (cy >= obj.height) break;
- this.addPage();
- }
- callback(w, cy, null, args);
- }.bind(this);
- if (obj.nodeName === 'CANVAS') {
- var img = new Image();
- img.onload = crop;
- img.src = obj.toDataURL("image/png");
- obj = img;
- } else {
- crop();
- }
- } else {
- var alias = Math.random().toString(35);
- var args = [obj, x, y, w, h, format, alias, 'SLOW'];
-
- this.addImage.apply(this, args);
-
- callback(w, h, alias, args);
- }
- }.bind(this);
-
- if (typeof html2canvas !== 'undefined' && !options.rstz) {
- return html2canvas(element, options);
- }
-
- if (typeof rasterizeHTML !== 'undefined') {
- var meth = 'drawDocument';
- if (typeof element === 'string') {
- meth = /^http/.test(element) ? 'drawURL' : 'drawHTML';
- }
- options.width = options.width || W * K;
- return rasterizeHTML[meth](element, void 0, options).then(function (r) {
- options.onrendered(r.image);
- }, function (e) {
- callback(null, e);
- });
- }
-
- return null;
- };
- })(jsPDF.API);
-
- /** @preserve
- * jsPDF addImage plugin
- * Copyright (c) 2012 Jason Siefken, https://github.com/siefkenj/
- * 2013 Chris Dowling, https://github.com/gingerchris
- * 2013 Trinh Ho, https://github.com/ineedfat
- * 2013 Edwin Alejandro Perez, https://github.com/eaparango
- * 2013 Norah Smith, https://github.com/burnburnrocket
- * 2014 Diego Casorran, https://github.com/diegocr
- * 2014 James Robb, https://github.com/jamesbrobb
- *
- *
- */
+ that.pending_buf = null;
+ head = null;
+ prev = null;
+ window = null; // free
- ;(function (jsPDFAPI) {
- 'use strict';
-
- var namespace = 'addImage_',
- supported_image_types = ['jpeg', 'jpg', 'png'];
-
- // Image functionality ported from pdf.js
- var putImage = function putImage(img) {
-
- var objectNumber = this.internal.newObject(),
- out = this.internal.write,
- putStream = this.internal.putStream;
-
- img['n'] = objectNumber;
-
- out('<>');
- }
- if ('trns' in img && img['trns'].constructor == Array) {
- var trns = '',
- i = 0,
- len = img['trns'].length;
- for (; i < len; i++) {
- trns += img['trns'][i] + ' ' + img['trns'][i] + ' ';
- }out('/Mask [' + trns + ']');
- }
- if ('smask' in img) {
- out('/SMask ' + (objectNumber + 1) + ' 0 R');
- }
- out('/Length ' + img['data'].length + '>>');
-
- putStream(img['data']);
-
- out('endobj');
-
- // Soft mask
- if ('smask' in img) {
- var dp = '/Predictor 15 /Colors 1 /BitsPerComponent ' + img['bpc'] + ' /Columns ' + img['w'];
- var smask = { 'w': img['w'], 'h': img['h'], 'cs': 'DeviceGray', 'bpc': img['bpc'], 'dp': dp, 'data': img['smask'] };
- if ('f' in img) smask.f = img['f'];
- putImage.call(this, smask);
- }
-
- //Palette
- if (img['cs'] === this.color_spaces.INDEXED) {
-
- this.internal.newObject();
- //out('<< /Filter / ' + img['f'] +' /Length ' + img['pal'].length + '>>');
- //putStream(zlib.compress(img['pal']));
- out('<< /Length ' + img['pal'].length + '>>');
- putStream(this.arrayBufferToBinaryString(new Uint8Array(img['pal'])));
- out('endobj');
- }
- },
- putResourcesCallback = function putResourcesCallback() {
- var images = this.internal.collections[namespace + 'images'];
- for (var i in images) {
- putImage.call(this, images[i]);
- }
- },
- putXObjectsDictCallback = function putXObjectsDictCallback() {
- var images = this.internal.collections[namespace + 'images'],
- out = this.internal.write,
- image;
- for (var i in images) {
- image = images[i];
- out('/I' + image['i'], image['n'], '0', 'R');
- }
- },
- checkCompressValue = function checkCompressValue(value) {
- if (value && typeof value === 'string') value = value.toUpperCase();
- return value in jsPDFAPI.image_compression ? value : jsPDFAPI.image_compression.NONE;
- },
- getImages = function getImages() {
- var images = this.internal.collections[namespace + 'images'];
- //first run, so initialise stuff
- if (!images) {
- this.internal.collections[namespace + 'images'] = images = {};
- this.internal.events.subscribe('putResources', putResourcesCallback);
- this.internal.events.subscribe('putXobjectDict', putXObjectsDictCallback);
- }
-
- return images;
- },
- getImageIndex = function getImageIndex(images) {
- var imageIndex = 0;
-
- if (images) {
- // this is NOT the first time this method is ran on this instance of jsPDF object.
- imageIndex = Object.keys ? Object.keys(images).length : function (o) {
- var i = 0;
- for (var e in o) {
- if (o.hasOwnProperty(e)) {
- i++;
- }
- }
- return i;
- }(images);
- }
-
- return imageIndex;
- },
- notDefined = function notDefined(value) {
- return typeof value === 'undefined' || value === null;
- },
- generateAliasFromData = function generateAliasFromData(data) {
- return typeof data === 'string' && jsPDFAPI.sHashCode(data);
- },
- doesNotSupportImageType = function doesNotSupportImageType(type) {
- return supported_image_types.indexOf(type) === -1;
- },
- processMethodNotEnabled = function processMethodNotEnabled(type) {
- return typeof jsPDFAPI['process' + type.toUpperCase()] !== 'function';
- },
- isDOMElement = function isDOMElement(object) {
- return (typeof object === 'undefined' ? 'undefined' : babelHelpers.typeof(object)) === 'object' && object.nodeType === 1;
- },
- createDataURIFromElement = function createDataURIFromElement(element, format, angle) {
-
- //if element is an image which uses data url defintion, just return the dataurl
- if (element.nodeName === 'IMG' && element.hasAttribute('src')) {
- var src = '' + element.getAttribute('src');
- if (!angle && src.indexOf('data:image/') === 0) return src;
-
- // only if the user doesn't care about a format
- if (!format && /\.png(?:[?#].*)?$/i.test(src)) format = 'png';
- }
-
- if (element.nodeName === 'CANVAS') {
- var canvas = element;
- } else {
- var canvas = document.createElement('canvas');
- canvas.width = element.clientWidth || element.width;
- canvas.height = element.clientHeight || element.height;
-
- var ctx = canvas.getContext('2d');
- if (!ctx) {
- throw 'addImage requires canvas to be supported by browser.';
- }
- if (angle) {
- var x,
- y,
- b,
- c,
- s,
- w,
- h,
- to_radians = Math.PI / 180,
- angleInRadians;
-
- if ((typeof angle === 'undefined' ? 'undefined' : babelHelpers.typeof(angle)) === 'object') {
- x = angle.x;
- y = angle.y;
- b = angle.bg;
- angle = angle.angle;
- }
- angleInRadians = angle * to_radians;
- c = Math.abs(Math.cos(angleInRadians));
- s = Math.abs(Math.sin(angleInRadians));
- w = canvas.width;
- h = canvas.height;
- canvas.width = h * s + w * c;
- canvas.height = h * c + w * s;
-
- if (isNaN(x)) x = canvas.width / 2;
- if (isNaN(y)) y = canvas.height / 2;
-
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- ctx.fillStyle = b || 'white';
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- ctx.save();
- ctx.translate(x, y);
- ctx.rotate(angleInRadians);
- ctx.drawImage(element, -(w / 2), -(h / 2));
- ctx.rotate(-angleInRadians);
- ctx.translate(-x, -y);
- ctx.restore();
- } else {
- ctx.drawImage(element, 0, 0, canvas.width, canvas.height);
- }
- }
- return canvas.toDataURL(('' + format).toLowerCase() == 'png' ? 'image/png' : 'image/jpeg');
- },
- checkImagesForAlias = function checkImagesForAlias(alias, images) {
- var cached_info;
- if (images) {
- for (var e in images) {
- if (alias === images[e].alias) {
- cached_info = images[e];
- break;
- }
- }
- }
- return cached_info;
- },
- determineWidthAndHeight = function determineWidthAndHeight(w, h, info) {
- if (!w && !h) {
- w = -96;
- h = -96;
- }
- if (w < 0) {
- w = -1 * info['w'] * 72 / w / this.internal.scaleFactor;
- }
- if (h < 0) {
- h = -1 * info['h'] * 72 / h / this.internal.scaleFactor;
- }
- if (w === 0) {
- w = h * info['w'] / info['h'];
- }
- if (h === 0) {
- h = w * info['h'] / info['w'];
- }
-
- return [w, h];
- },
- writeImageToPDF = function writeImageToPDF(x, y, w, h, info, index, images) {
- var dims = determineWidthAndHeight.call(this, w, h, info),
- coord = this.internal.getCoordinateString,
- vcoord = this.internal.getVerticalCoordinateString;
-
- w = dims[0];
- h = dims[1];
-
- images[index] = info;
-
- this.internal.write('q', coord(w), '0 0', coord(-h) // TODO: check if this should be shifted by vcoord
- , coord(x), vcoord(y + h), 'cm /I' + info['i'], 'Do Q');
- };
-
- /**
- * COLOR SPACES
- */
- jsPDFAPI.color_spaces = {
- DEVICE_RGB: 'DeviceRGB',
- DEVICE_GRAY: 'DeviceGray',
- DEVICE_CMYK: 'DeviceCMYK',
- CAL_GREY: 'CalGray',
- CAL_RGB: 'CalRGB',
- LAB: 'Lab',
- ICC_BASED: 'ICCBased',
- INDEXED: 'Indexed',
- PATTERN: 'Pattern',
- SEPERATION: 'Seperation',
- DEVICE_N: 'DeviceN'
- };
-
- /**
- * DECODE METHODS
- */
- jsPDFAPI.decode = {
- DCT_DECODE: 'DCTDecode',
- FLATE_DECODE: 'FlateDecode',
- LZW_DECODE: 'LZWDecode',
- JPX_DECODE: 'JPXDecode',
- JBIG2_DECODE: 'JBIG2Decode',
- ASCII85_DECODE: 'ASCII85Decode',
- ASCII_HEX_DECODE: 'ASCIIHexDecode',
- RUN_LENGTH_DECODE: 'RunLengthDecode',
- CCITT_FAX_DECODE: 'CCITTFaxDecode'
- };
-
- /**
- * IMAGE COMPRESSION TYPES
- */
- jsPDFAPI.image_compression = {
- NONE: 'NONE',
- FAST: 'FAST',
- MEDIUM: 'MEDIUM',
- SLOW: 'SLOW'
- };
-
- jsPDFAPI.sHashCode = function (str) {
- return Array.prototype.reduce && str.split("").reduce(function (a, b) {
- a = (a << 5) - a + b.charCodeAt(0);return a & a;
- }, 0);
- };
-
- jsPDFAPI.isString = function (object) {
- return typeof object === 'string';
- };
-
- /**
- * Strips out and returns info from a valid base64 data URI
- * @param {String[dataURI]} a valid data URI of format 'data:[][;base64],'
- * @returns an Array containing the following
- * [0] the complete data URI
- * [1]
- * [2] format - the second part of the mime-type i.e 'png' in 'image/png'
- * [4]
- */
- jsPDFAPI.extractInfoFromBase64DataURI = function (dataURI) {
- return (/^data:([\w]+?\/([\w]+?));base64,(.+?)$/g.exec(dataURI)
- );
- };
+ that.dstate = null;
+ return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK;
+ };
- /**
- * Check to see if ArrayBuffer is supported
- */
- jsPDFAPI.supportsArrayBuffer = function () {
- return typeof ArrayBuffer !== 'undefined' && typeof Uint8Array !== 'undefined';
- };
+ that.deflateParams = function (strm, _level, _strategy) {
+ var err = Z_OK;
- /**
- * Tests supplied object to determine if ArrayBuffer
- * @param {Object[object]}
- */
- jsPDFAPI.isArrayBuffer = function (object) {
- if (!this.supportsArrayBuffer()) return false;
- return object instanceof ArrayBuffer;
- };
-
- /**
- * Tests supplied object to determine if it implements the ArrayBufferView (TypedArray) interface
- * @param {Object[object]}
- */
- jsPDFAPI.isArrayBufferView = function (object) {
- if (!this.supportsArrayBuffer()) return false;
- if (typeof Uint32Array === 'undefined') return false;
- return object instanceof Int8Array || object instanceof Uint8Array || typeof Uint8ClampedArray !== 'undefined' && object instanceof Uint8ClampedArray || object instanceof Int16Array || object instanceof Uint16Array || object instanceof Int32Array || object instanceof Uint32Array || object instanceof Float32Array || object instanceof Float64Array;
- };
-
- /**
- * Exactly what it says on the tin
- */
- jsPDFAPI.binaryStringToUint8Array = function (binary_string) {
- /*
- * not sure how efficient this will be will bigger files. Is there a native method?
- */
- var len = binary_string.length;
- var bytes = new Uint8Array(len);
- for (var i = 0; i < len; i++) {
- bytes[i] = binary_string.charCodeAt(i);
- }
- return bytes;
- };
-
- /**
- * @see this discussion
- * http://stackoverflow.com/questions/6965107/converting-between-strings-and-arraybuffers
- *
- * As stated, i imagine the method below is highly inefficent for large files.
- *
- * Also of note from Mozilla,
- *
- * "However, this is slow and error-prone, due to the need for multiple conversions (especially if the binary data is not actually byte-format data, but, for example, 32-bit integers or floats)."
- *
- * https://developer.mozilla.org/en-US/Add-ons/Code_snippets/StringView
- *
- * Although i'm strugglig to see how StringView solves this issue? Doesn't appear to be a direct method for conversion?
- *
- * Async method using Blob and FileReader could be best, but i'm not sure how to fit it into the flow?
- */
- jsPDFAPI.arrayBufferToBinaryString = function (buffer) {
- if ('TextDecoder' in window) {
- var decoder = new TextDecoder('ascii');
- return decoder.decode(buffer);
- }
-
- if (this.isArrayBuffer(buffer)) buffer = new Uint8Array(buffer);
-
- var binary_string = '';
- var len = buffer.byteLength;
- for (var i = 0; i < len; i++) {
- binary_string += String.fromCharCode(buffer[i]);
- }
- return binary_string;
- /*
- * Another solution is the method below - convert array buffer straight to base64 and then use atob
- */
- //return atob(this.arrayBufferToBase64(buffer));
- };
-
- /**
- * Converts an ArrayBuffer directly to base64
- *
- * Taken from here
- *
- * http://jsperf.com/encoding-xhr-image-data/31
- *
- * Need to test if this is a better solution for larger files
- *
- */
- jsPDFAPI.arrayBufferToBase64 = function (arrayBuffer) {
- var base64 = '';
- var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
-
- var bytes = new Uint8Array(arrayBuffer);
- var byteLength = bytes.byteLength;
- var byteRemainder = byteLength % 3;
- var mainLength = byteLength - byteRemainder;
-
- var a, b, c, d;
- var chunk;
-
- // Main loop deals with bytes in chunks of 3
- for (var i = 0; i < mainLength; i = i + 3) {
- // Combine the three bytes into a single integer
- chunk = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
-
- // Use bitmasks to extract 6-bit segments from the triplet
- a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
- b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12
- c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6
- d = chunk & 63; // 63 = 2^6 - 1
+ if (_level == Z_DEFAULT_COMPRESSION) {
+ _level = 6;
+ }
+
+ if (_level < 0 || _level > 9 || _strategy < 0 || _strategy > Z_HUFFMAN_ONLY) {
+ return Z_STREAM_ERROR;
+ }
+
+ if (config_table[level].func != config_table[_level].func && strm.total_in !== 0) {
+ // Flush the last buffer:
+ err = strm.deflate(Z_PARTIAL_FLUSH);
+ }
+
+ if (level != _level) {
+ level = _level;
+ max_lazy_match = config_table[level].max_lazy;
+ good_match = config_table[level].good_length;
+ nice_match = config_table[level].nice_length;
+ max_chain_length = config_table[level].max_chain;
+ }
- // Convert the raw binary segments to the appropriate ASCII encoding
- base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
- }
+ strategy = _strategy;
+ return err;
+ };
+
+ that.deflateSetDictionary = function (strm, dictionary, dictLength) {
+ var length = dictLength;
+ var n,
+ index = 0;
+ if (!dictionary || status != INIT_STATE) return Z_STREAM_ERROR;
+ if (length < MIN_MATCH) return Z_OK;
+
+ if (length > w_size - MIN_LOOKAHEAD) {
+ length = w_size - MIN_LOOKAHEAD;
+ index = dictLength - length; // use the tail of the dictionary
+ }
- // Deal with the remaining bytes and padding
- if (byteRemainder == 1) {
- chunk = bytes[mainLength];
+ window.set(dictionary.subarray(index, index + length), 0);
+ strstart = length;
+ block_start = length; // Insert all strings in the hash table (except for the last two bytes).
+ // s->lookahead stays null, so s->ins_h will be recomputed at the next
+ // call of fill_window.
- a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
+ ins_h = window[0] & 0xff;
+ ins_h = (ins_h << hash_shift ^ window[1] & 0xff) & hash_mask;
- // Set the 4 least significant bits to zero
- b = (chunk & 3) << 4; // 3 = 2^2 - 1
+ for (n = 0; n <= length - MIN_MATCH; n++) {
+ ins_h = (ins_h << hash_shift ^ window[n + (MIN_MATCH - 1)] & 0xff) & hash_mask;
+ prev[n & w_mask] = head[ins_h];
+ head[ins_h] = n;
+ }
- base64 += encodings[a] + encodings[b] + '==';
- } else if (byteRemainder == 2) {
- chunk = bytes[mainLength] << 8 | bytes[mainLength + 1];
+ return Z_OK;
+ };
- a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
- b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4
+ that.deflate = function (_strm, flush) {
+ var i, header, level_flags, old_flush, bstate;
- // Set the 2 least significant bits to zero
- c = (chunk & 15) << 2; // 15 = 2^4 - 1
+ if (flush > Z_FINISH || flush < 0) {
+ return Z_STREAM_ERROR;
+ }
- base64 += encodings[a] + encodings[b] + encodings[c] + '=';
- }
+ if (!_strm.next_out || !_strm.next_in && _strm.avail_in !== 0 || status == FINISH_STATE && flush != Z_FINISH) {
+ _strm.msg = z_errmsg[Z_NEED_DICT - Z_STREAM_ERROR];
+ return Z_STREAM_ERROR;
+ }
- return base64;
- };
+ if (_strm.avail_out === 0) {
+ _strm.msg = z_errmsg[Z_NEED_DICT - Z_BUF_ERROR];
+ return Z_BUF_ERROR;
+ }
- jsPDFAPI.createImageInfo = function (data, wd, ht, cs, bpc, f, imageIndex, alias, dp, trns, pal, smask) {
- var info = {
- alias: alias,
- w: wd,
- h: ht,
- cs: cs,
- bpc: bpc,
- i: imageIndex,
- data: data
- // n: objectNumber will be added by putImage code
- };
+ strm = _strm; // just in case
+
+ old_flush = last_flush;
+ last_flush = flush; // Write the zlib header
+
+ if (status == INIT_STATE) {
+ header = Z_DEFLATED + (w_bits - 8 << 4) << 8;
+ level_flags = (level - 1 & 0xff) >> 1;
+ if (level_flags > 3) level_flags = 3;
+ header |= level_flags << 6;
+ if (strstart !== 0) header |= PRESET_DICT;
+ header += 31 - header % 31;
+ status = BUSY_STATE;
+ putShortMSB(header);
+ } // Flush as much pending output as possible
+
+
+ if (that.pending !== 0) {
+ strm.flush_pending();
+
+ if (strm.avail_out === 0) {
+ // console.log(" avail_out==0");
+ // Since avail_out is 0, deflate will be called again with
+ // more output space, but possibly with both pending and
+ // avail_in equal to zero. There won't be anything to do,
+ // but this is not an error situation so make sure we
+ // return OK instead of BUF_ERROR at next call of deflate:
+ last_flush = -1;
+ return Z_OK;
+ } // Make sure there is something to do and avoid duplicate
+ // consecutive
+ // flushes. For repeated and useless calls with Z_FINISH, we keep
+ // returning Z_STREAM_END instead of Z_BUFF_ERROR.
+
+ } else if (strm.avail_in === 0 && flush <= old_flush && flush != Z_FINISH) {
+ strm.msg = z_errmsg[Z_NEED_DICT - Z_BUF_ERROR];
+ return Z_BUF_ERROR;
+ } // User must not provide more input after the first FINISH:
+
+
+ if (status == FINISH_STATE && strm.avail_in !== 0) {
+ _strm.msg = z_errmsg[Z_NEED_DICT - Z_BUF_ERROR];
+ return Z_BUF_ERROR;
+ } // Start a new block or continue the current one.
+
+
+ if (strm.avail_in !== 0 || lookahead !== 0 || flush != Z_NO_FLUSH && status != FINISH_STATE) {
+ bstate = -1;
+
+ switch (config_table[level].func) {
+ case STORED:
+ bstate = deflate_stored(flush);
+ break;
- if (f) info.f = f;
- if (dp) info.dp = dp;
- if (trns) info.trns = trns;
- if (pal) info.pal = pal;
- if (smask) info.smask = smask;
+ case FAST:
+ bstate = deflate_fast(flush);
+ break;
- return info;
- };
+ case SLOW:
+ bstate = deflate_slow(flush);
+ break;
- jsPDFAPI.addImage = function (imageData, format, x, y, w, h, alias, compression, rotation) {
- 'use strict';
+ default:
+ }
- if (typeof format !== 'string') {
- var tmp = h;
- h = w;
- w = y;
- y = x;
- x = format;
- format = tmp;
- }
+ if (bstate == FinishStarted || bstate == FinishDone) {
+ status = FINISH_STATE;
+ }
- if ((typeof imageData === 'undefined' ? 'undefined' : babelHelpers.typeof(imageData)) === 'object' && !isDOMElement(imageData) && "imageData" in imageData) {
- var options = imageData;
+ if (bstate == NeedMore || bstate == FinishStarted) {
+ if (strm.avail_out === 0) {
+ last_flush = -1; // avoid BUF_ERROR next call, see above
+ }
- imageData = options.imageData;
- format = options.format || format;
- x = options.x || x || 0;
- y = options.y || y || 0;
- w = options.w || w;
- h = options.h || h;
- alias = options.alias || alias;
- compression = options.compression || compression;
- rotation = options.rotation || options.angle || rotation;
- }
+ return Z_OK; // If flush != Z_NO_FLUSH && avail_out === 0, the next call
+ // of deflate should use the same flush parameter to make sure
+ // that the flush is complete. So we don't have to output an
+ // empty block here, this will be done at next call. This also
+ // ensures that for a very small output buffer, we emit at most
+ // one empty block.
+ }
- if (isNaN(x) || isNaN(y)) {
- console.error('jsPDF.addImage: Invalid coordinates', arguments);
- throw new Error('Invalid coordinates passed to jsPDF.addImage');
- }
+ if (bstate == BlockDone) {
+ if (flush == Z_PARTIAL_FLUSH) {
+ _tr_align();
+ } else {
+ // FULL_FLUSH or SYNC_FLUSH
+ _tr_stored_block(0, 0, false); // For a full flush, this empty block will be recognized
+ // as a special marker by inflate_sync().
- var images = getImages.call(this),
- info;
- if (!(info = checkImagesForAlias(imageData, images))) {
- var dataAsBinaryString;
+ if (flush == Z_FULL_FLUSH) {
+ // state.head[s.hash_size-1]=0;
+ for (i = 0; i < hash_size
+ /*-1*/
+ ; i++) // forget history
+ head[i] = 0;
+ }
+ }
- if (isDOMElement(imageData)) imageData = createDataURIFromElement(imageData, format, rotation);
+ strm.flush_pending();
- if (notDefined(alias)) alias = generateAliasFromData(imageData);
+ if (strm.avail_out === 0) {
+ last_flush = -1; // avoid BUF_ERROR at next call, see above
- if (!(info = checkImagesForAlias(alias, images))) {
+ return Z_OK;
+ }
+ }
+ }
- if (this.isString(imageData)) {
+ if (flush != Z_FINISH) return Z_OK;
+ return Z_STREAM_END;
+ };
+ } // ZStream
- var base64Info = this.extractInfoFromBase64DataURI(imageData);
- if (base64Info) {
+ function ZStream() {
+ var that = this;
+ that.next_in_index = 0;
+ that.next_out_index = 0; // that.next_in; // next input byte
- format = base64Info[2];
- imageData = atob(base64Info[3]); //convert to binary string
- } else {
+ that.avail_in = 0; // number of bytes available at next_in
- if (imageData.charCodeAt(0) === 0x89 && imageData.charCodeAt(1) === 0x50 && imageData.charCodeAt(2) === 0x4e && imageData.charCodeAt(3) === 0x47) format = 'png';
- }
- }
- format = (format || 'JPEG').toLowerCase();
+ that.total_in = 0; // total nb of input bytes read so far
+ // that.next_out; // next output byte should be put there
- if (doesNotSupportImageType(format)) throw new Error('addImage currently only supports formats ' + supported_image_types + ', not \'' + format + '\'');
+ that.avail_out = 0; // remaining free space at next_out
- if (processMethodNotEnabled(format)) throw new Error('please ensure that the plugin for \'' + format + '\' support is added');
+ that.total_out = 0; // total nb of bytes output so far
+ // that.msg;
+ // that.dstate;
+ }
- /**
- * need to test if it's more efficent to convert all binary strings
- * to TypedArray - or should we just leave and process as string?
- */
- if (this.supportsArrayBuffer()) {
- // no need to convert if imageData is already uint8array
- if (!(imageData instanceof Uint8Array)) {
- dataAsBinaryString = imageData;
- imageData = this.binaryStringToUint8Array(imageData);
- }
- }
-
- info = this['process' + format.toUpperCase()](imageData, getImageIndex(images), alias, checkCompressValue(compression), dataAsBinaryString);
-
- if (!info) throw new Error('An unkwown error occurred whilst processing the image');
- }
- }
-
- writeImageToPDF.call(this, x, y, w, h, info, info.i, images);
-
- return this;
- };
-
- /**
- * JPEG SUPPORT
- **/
-
- //takes a string imgData containing the raw bytes of
- //a jpeg image and returns [width, height]
- //Algorithm from: http://www.64lines.com/jpeg-width-height
- var getJpegSize = function getJpegSize(imgData) {
- 'use strict';
-
- var width, height, numcomponents;
- // Verify we have a valid jpeg header 0xff,0xd8,0xff,0xe0,?,?,'J','F','I','F',0x00
- if (!imgData.charCodeAt(0) === 0xff || !imgData.charCodeAt(1) === 0xd8 || !imgData.charCodeAt(2) === 0xff || !imgData.charCodeAt(3) === 0xe0 || !imgData.charCodeAt(6) === 'J'.charCodeAt(0) || !imgData.charCodeAt(7) === 'F'.charCodeAt(0) || !imgData.charCodeAt(8) === 'I'.charCodeAt(0) || !imgData.charCodeAt(9) === 'F'.charCodeAt(0) || !imgData.charCodeAt(10) === 0x00) {
- throw new Error('getJpegSize requires a binary string jpeg file');
- }
- var blockLength = imgData.charCodeAt(4) * 256 + imgData.charCodeAt(5);
- var i = 4,
- len = imgData.length;
- while (i < len) {
- i += blockLength;
- if (imgData.charCodeAt(i) !== 0xff) {
- throw new Error('getJpegSize could not find the size of the image');
- }
- if (imgData.charCodeAt(i + 1) === 0xc0 || //(SOF) Huffman - Baseline DCT
- imgData.charCodeAt(i + 1) === 0xc1 || //(SOF) Huffman - Extended sequential DCT
- imgData.charCodeAt(i + 1) === 0xc2 || // Progressive DCT (SOF2)
- imgData.charCodeAt(i + 1) === 0xc3 || // Spatial (sequential) lossless (SOF3)
- imgData.charCodeAt(i + 1) === 0xc4 || // Differential sequential DCT (SOF5)
- imgData.charCodeAt(i + 1) === 0xc5 || // Differential progressive DCT (SOF6)
- imgData.charCodeAt(i + 1) === 0xc6 || // Differential spatial (SOF7)
- imgData.charCodeAt(i + 1) === 0xc7) {
- height = imgData.charCodeAt(i + 5) * 256 + imgData.charCodeAt(i + 6);
- width = imgData.charCodeAt(i + 7) * 256 + imgData.charCodeAt(i + 8);
- numcomponents = imgData.charCodeAt(i + 9);
- return [width, height, numcomponents];
- } else {
- i += 2;
- blockLength = imgData.charCodeAt(i) * 256 + imgData.charCodeAt(i + 1);
- }
- }
- },
- getJpegSizeFromBytes = function getJpegSizeFromBytes(data) {
-
- var hdr = data[0] << 8 | data[1];
-
- if (hdr !== 0xFFD8) throw new Error('Supplied data is not a JPEG');
-
- var len = data.length,
- block = (data[4] << 8) + data[5],
- pos = 4,
- bytes,
- width,
- height,
- numcomponents;
-
- while (pos < len) {
- pos += block;
- bytes = readBytes(data, pos);
- block = (bytes[2] << 8) + bytes[3];
- if ((bytes[1] === 0xC0 || bytes[1] === 0xC2) && bytes[0] === 0xFF && block > 7) {
- bytes = readBytes(data, pos + 5);
- width = (bytes[2] << 8) + bytes[3];
- height = (bytes[0] << 8) + bytes[1];
- numcomponents = bytes[4];
- return { width: width, height: height, numcomponents: numcomponents };
- }
-
- pos += 2;
- }
-
- throw new Error('getJpegSizeFromBytes could not find the size of the image');
- },
- readBytes = function readBytes(data, offset) {
- return data.subarray(offset, offset + 5);
- };
-
- jsPDFAPI.processJPEG = function (data, index, alias, compression, dataAsBinaryString) {
- 'use strict';
-
- var colorSpace = this.color_spaces.DEVICE_RGB,
- filter = this.decode.DCT_DECODE,
- bpc = 8,
- dims;
-
- if (this.isString(data)) {
- dims = getJpegSize(data);
- return this.createImageInfo(data, dims[0], dims[1], dims[3] == 1 ? this.color_spaces.DEVICE_GRAY : colorSpace, bpc, filter, index, alias);
- }
-
- if (this.isArrayBuffer(data)) data = new Uint8Array(data);
-
- if (this.isArrayBufferView(data)) {
-
- dims = getJpegSizeFromBytes(data);
-
- // if we already have a stored binary string rep use that
- data = dataAsBinaryString || this.arrayBufferToBinaryString(data);
-
- return this.createImageInfo(data, dims.width, dims.height, dims.numcomponents == 1 ? this.color_spaces.DEVICE_GRAY : colorSpace, bpc, filter, index, alias);
- }
-
- return null;
- };
-
- jsPDFAPI.processJPG = function () /*data, index, alias, compression, dataAsBinaryString*/{
- return this.processJPEG.apply(this, arguments);
- };
- })(jsPDF.API);
+ ZStream.prototype = {
+ deflateInit: function (level, bits) {
+ var that = this;
+ that.dstate = new Deflate();
+ if (!bits) bits = MAX_BITS;
+ return that.dstate.deflateInit(that, level, bits);
+ },
+ deflate: function (flush) {
+ var that = this;
+
+ if (!that.dstate) {
+ return Z_STREAM_ERROR;
+ }
- /**
- * jsPDF Annotations PlugIn
- * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv
- *
- * Licensed under the MIT License.
- * http://opensource.org/licenses/mit-license
- */
+ return that.dstate.deflate(that, flush);
+ },
+ deflateEnd: function () {
+ var that = this;
+ if (!that.dstate) return Z_STREAM_ERROR;
+ var ret = that.dstate.deflateEnd();
+ that.dstate = null;
+ return ret;
+ },
+ deflateParams: function (level, strategy) {
+ var that = this;
+ if (!that.dstate) return Z_STREAM_ERROR;
+ return that.dstate.deflateParams(that, level, strategy);
+ },
+ deflateSetDictionary: function (dictionary, dictLength) {
+ var that = this;
+ if (!that.dstate) return Z_STREAM_ERROR;
+ return that.dstate.deflateSetDictionary(that, dictionary, dictLength);
+ },
+ // Read a new buffer from the current input stream, update the
+ // total number of bytes read. All deflate() input goes through
+ // this function so some applications may wish to modify it to avoid
+ // allocating a large strm->next_in buffer and copying from it.
+ // (See also flush_pending()).
+ read_buf: function (buf, start, size) {
+ var that = this;
+ var len = that.avail_in;
+ if (len > size) len = size;
+ if (len === 0) return 0;
+ that.avail_in -= len;
+ buf.set(that.next_in.subarray(that.next_in_index, that.next_in_index + len), start);
+ that.next_in_index += len;
+ that.total_in += len;
+ return len;
+ },
+ // Flush as much pending output as possible. All deflate() output goes
+ // through this function so some applications may wish to modify it
+ // to avoid allocating a large strm->next_out buffer and copying into it.
+ // (See also read_buf()).
+ flush_pending: function () {
+ var that = this;
+ var len = that.dstate.pending;
+ if (len > that.avail_out) len = that.avail_out;
+ if (len === 0) return; // if (that.dstate.pending_buf.length <= that.dstate.pending_out || that.next_out.length <= that.next_out_index
+ // || that.dstate.pending_buf.length < (that.dstate.pending_out + len) || that.next_out.length < (that.next_out_index +
+ // len)) {
+ // console.log(that.dstate.pending_buf.length + ", " + that.dstate.pending_out + ", " + that.next_out.length + ", " +
+ // that.next_out_index + ", " + len);
+ // console.log("avail_out=" + that.avail_out);
+ // }
+
+ that.next_out.set(that.dstate.pending_buf.subarray(that.dstate.pending_out, that.dstate.pending_out + len), that.next_out_index);
+ that.next_out_index += len;
+ that.dstate.pending_out += len;
+ that.total_out += len;
+ that.avail_out -= len;
+ that.dstate.pending -= len;
+
+ if (that.dstate.pending === 0) {
+ that.dstate.pending_out = 0;
+ }
+ }
+ }; // Deflater
+
+ function Deflater(options) {
+ var that = this;
+ var z = new ZStream();
+ var bufsize = 512;
+ var flush = Z_NO_FLUSH;
+ var buf = new Uint8Array(bufsize);
+ var level = options ? options.level : Z_DEFAULT_COMPRESSION;
+ if (typeof level == "undefined") level = Z_DEFAULT_COMPRESSION;
+ z.deflateInit(level);
+ z.next_out = buf;
+
+ that.append = function (data, onprogress) {
+ var err,
+ buffers = [],
+ lastIndex = 0,
+ bufferIndex = 0,
+ bufferSize = 0,
+ array;
+ if (!data.length) return;
+ z.next_in_index = 0;
+ z.next_in = data;
+ z.avail_in = data.length;
+
+ do {
+ z.next_out_index = 0;
+ z.avail_out = bufsize;
+ err = z.deflate(flush);
+ if (err != Z_OK) throw new Error("deflating: " + z.msg);
+ if (z.next_out_index) if (z.next_out_index == bufsize) buffers.push(new Uint8Array(buf));else buffers.push(new Uint8Array(buf.subarray(0, z.next_out_index)));
+ bufferSize += z.next_out_index;
+
+ if (onprogress && z.next_in_index > 0 && z.next_in_index != lastIndex) {
+ onprogress(z.next_in_index);
+ lastIndex = z.next_in_index;
+ }
+ } while (z.avail_in > 0 || z.avail_out === 0);
- /**
- * There are many types of annotations in a PDF document. Annotations are placed
- * on a page at a particular location. They are not 'attached' to an object.
- *
- * This plugin current supports
- * Goto Page (set pageNumber and top in options)
- * Goto Name (set name and top in options)
- * Goto URL (set url in options)
- *
- * The destination magnification factor can also be specified when goto is a page number or a named destination. (see documentation below)
- * (set magFactor in options). XYZ is the default.
- *
- *
- * Links, Text, Popup, and FreeText are supported.
- *
- *
- * Options In PDF spec Not Implemented Yet
- *
link border
- * named target
- * page coordinates
- * destination page scaling and layout
- * actions other than URL and GotoPage
- * background / hover actions
- *
- */
+ array = new Uint8Array(bufferSize);
+ buffers.forEach(function (chunk) {
+ array.set(chunk, bufferIndex);
+ bufferIndex += chunk.length;
+ });
+ return array;
+ };
- /*
- Destination Magnification Factors
- See PDF 1.3 Page 386 for meanings and options
-
- [supported]
- XYZ (options; left top zoom)
- Fit (no options)
- FitH (options: top)
- FitV (options: left)
-
- [not supported]
- FitR
- FitB
- FitBH
- FitBV
- */
+ that.flush = function () {
+ var err,
+ buffers = [],
+ bufferIndex = 0,
+ bufferSize = 0,
+ array;
+
+ do {
+ z.next_out_index = 0;
+ z.avail_out = bufsize;
+ err = z.deflate(Z_FINISH);
+ if (err != Z_STREAM_END && err != Z_OK) throw new Error("deflating: " + z.msg);
+ if (bufsize - z.avail_out > 0) buffers.push(new Uint8Array(buf.subarray(0, z.next_out_index)));
+ bufferSize += z.next_out_index;
+ } while (z.avail_in > 0 || z.avail_out === 0);
+
+ z.deflateEnd();
+ array = new Uint8Array(bufferSize);
+ buffers.forEach(function (chunk) {
+ array.set(chunk, bufferIndex);
+ bufferIndex += chunk.length;
+ });
+ return array;
+ };
+ } // 'zip' may not be defined in z-worker and some tests
- (function (jsPDFAPI) {
- 'use strict';
- var annotationPlugin = {
+ var env = global.zip || global;
+ env.Deflater = env._jzlib_Deflater = Deflater;
+ })(typeof self !== "undefined" && self || typeof window !== "undefined" && window || typeof global !== "undefined" && global || Function('return typeof this === "object" && this.content')() || Function("return this")()); // `self` is undefined in Firefox for Android content script context
+ // while `this` is nsIContentFrameMessageManager
+ // with an attribute `content` that corresponds to the window
- /**
- * An array of arrays, indexed by pageNumber .
- */
- annotations: [],
-
- f2: function f2(number) {
- return number.toFixed(2);
- },
-
- notEmpty: function notEmpty(obj) {
- if (typeof obj != 'undefined') {
- if (obj != '') {
- return true;
- }
- }
- }
- };
-
- jsPDF.API.annotationPlugin = annotationPlugin;
-
- jsPDF.API.events.push(['addPage', function (info) {
- this.annotationPlugin.annotations[info.pageNumber] = [];
- }]);
-
- jsPDFAPI.events.push(['putPage', function (info) {
- //TODO store annotations in pageContext so reorder/remove will not affect them.
- var pageAnnos = this.annotationPlugin.annotations[info.pageNumber];
-
- var found = false;
- for (var a = 0; a < pageAnnos.length && !found; a++) {
- var anno = pageAnnos[a];
- switch (anno.type) {
- case 'link':
- if (annotationPlugin.notEmpty(anno.options.url) || annotationPlugin.notEmpty(anno.options.pageNumber)) {
- found = true;
- break;
- }
- case 'reference':
- case 'text':
- case 'freetext':
- found = true;
- break;
- }
- }
- if (found == false) {
- return;
- }
-
- this.internal.write("/Annots [");
- var f2 = this.annotationPlugin.f2;
- var k = this.internal.scaleFactor;
- var pageHeight = this.internal.pageSize.height;
- var pageInfo = this.internal.getPageInfo(info.pageNumber);
- for (var a = 0; a < pageAnnos.length; a++) {
- var anno = pageAnnos[a];
-
- switch (anno.type) {
- case 'reference':
- // References to Widget Anotations (for AcroForm Fields)
- this.internal.write(' ' + anno.object.objId + ' 0 R ');
- break;
- case 'text':
- // Create a an object for both the text and the popup
- var objText = this.internal.newAdditionalObject();
- var objPopup = this.internal.newAdditionalObject();
-
- var title = anno.title || 'Note';
- var rect = "/Rect [" + f2(anno.bounds.x * k) + " " + f2(pageHeight - (anno.bounds.y + anno.bounds.h) * k) + " " + f2((anno.bounds.x + anno.bounds.w) * k) + " " + f2((pageHeight - anno.bounds.y) * k) + "] ";
- line = '<>';
- objText.content = line;
-
- var parent = objText.objId + ' 0 R';
- var popoff = 30;
- var rect = "/Rect [" + f2((anno.bounds.x + popoff) * k) + " " + f2(pageHeight - (anno.bounds.y + anno.bounds.h) * k) + " " + f2((anno.bounds.x + anno.bounds.w + popoff) * k) + " " + f2((pageHeight - anno.bounds.y) * k) + "] ";
- //var rect2 = "/Rect [" + f2(anno.bounds.x * k) + " " + f2((pageHeight - anno.bounds.y) * k) + " " + f2(anno.bounds.x + anno.bounds.w * k) + " " + f2(pageHeight - (anno.bounds.y + anno.bounds.h) * k) + "] ";
- line = '<>';
- objPopup.content = line;
-
- this.internal.write(objText.objId, '0 R', objPopup.objId, '0 R');
-
- break;
- case 'freetext':
- var rect = "/Rect [" + f2(anno.bounds.x * k) + " " + f2((pageHeight - anno.bounds.y) * k) + " " + f2(anno.bounds.x + anno.bounds.w * k) + " " + f2(pageHeight - (anno.bounds.y + anno.bounds.h) * k) + "] ";
- var color = anno.color || '#000000';
- line = '<>';
- this.internal.write(line);
- break;
- case 'link':
- if (anno.options.name) {
- var loc = this.annotations._nameMap[anno.options.name];
- anno.options.pageNumber = loc.page;
- anno.options.top = loc.y;
- } else {
- if (!anno.options.top) {
- anno.options.top = 0;
- }
- }
-
- //var pageHeight = this.internal.pageSize.height * this.internal.scaleFactor;
- var rect = "/Rect [" + f2(anno.x * k) + " " + f2((pageHeight - anno.y) * k) + " " + f2(anno.x + anno.w * k) + " " + f2(pageHeight - (anno.y + anno.h) * k) + "] ";
-
- var line = '';
- if (anno.options.url) {
- line = '<>';
- } else if (anno.options.pageNumber) {
- // first page is 0
- var info = this.internal.getPageInfo(anno.options.pageNumber);
- line = '<>";
- this.internal.write(line);
- }
- break;
- }
- }
- this.internal.write("]");
- }]);
-
- jsPDFAPI.createAnnotation = function (options) {
- switch (options.type) {
- case 'link':
- this.link(options.bounds.x, options.bounds.y, options.bounds.w, options.bounds.h, options);
- break;
- case 'text':
- case 'freetext':
- this.annotationPlugin.annotations[this.internal.getCurrentPageInfo().pageNumber].push(options);
- break;
- }
- };
-
- /**
- * valid options
- * pageNumber or url [required]
- * If pageNumber is specified, top and zoom may also be specified
- */
- jsPDFAPI.link = function (x, y, w, h, options) {
- 'use strict';
-
- this.annotationPlugin.annotations[this.internal.getCurrentPageInfo().pageNumber].push({
- x: x,
- y: y,
- w: w,
- h: h,
- options: options,
- type: 'link'
- });
- };
-
- /**
- * valid options
- * pageNumber or url [required]
- * If pageNumber is specified, top and zoom may also be specified
- */
- jsPDFAPI.link = function (x, y, w, h, options) {
- 'use strict';
-
- this.annotationPlugin.annotations[this.internal.getCurrentPageInfo().pageNumber].push({
- x: x,
- y: y,
- w: w,
- h: h,
- options: options,
- type: 'link'
- });
- };
-
- /**
- * Currently only supports single line text.
- * Returns the width of the text/link
- */
- jsPDFAPI.textWithLink = function (text, x, y, options) {
- 'use strict';
-
- var width = this.getTextWidth(text);
- var height = this.internal.getLineHeight();
- this.text(text, x, y);
- //TODO We really need the text baseline height to do this correctly.
- // Or ability to draw text on top, bottom, center, or baseline.
- y += height * .2;
- this.link(x, y - height, width, height, options);
- return width;
- };
-
- //TODO move into external library
- jsPDFAPI.getTextWidth = function (text) {
- 'use strict';
-
- var fontSize = this.internal.getFontSize();
- var txtWidth = this.getStringUnitWidth(text) * fontSize / this.internal.scaleFactor;
- return txtWidth;
- };
-
- //TODO move into external library
- jsPDFAPI.getLineHeight = function () {
- return this.internal.getLineHeight();
- };
-
- return this;
- })(jsPDF.API);
+ /**
+ * A class to parse color values
+ * @author Stoyan Stefanov
+ * @link http://www.phpied.com/rgb-color-parser-in-javascript/
+ * @license Use it if you like it
+ */
+ (function (global) {
- /**
- * jsPDF Autoprint Plugin
- *
- * Licensed under the MIT License.
- * http://opensource.org/licenses/mit-license
- */
+ function RGBColor(color_string) {
+ this.ok = false; // strip any leading #
- (function (jsPDFAPI) {
- 'use strict';
+ if (color_string.charAt(0) == "#") {
+ // remove # if any
+ color_string = color_string.substr(1, 6);
+ }
- jsPDFAPI.autoPrint = function () {
- 'use strict';
+ color_string = color_string.replace(/ /g, "");
+ color_string = color_string.toLowerCase();
+ var channels; // before getting into regexps, try simple matches
+ // and overwrite the input
+
+ var simple_colors = {
+ aliceblue: "f0f8ff",
+ antiquewhite: "faebd7",
+ aqua: "00ffff",
+ aquamarine: "7fffd4",
+ azure: "f0ffff",
+ beige: "f5f5dc",
+ bisque: "ffe4c4",
+ black: "000000",
+ blanchedalmond: "ffebcd",
+ blue: "0000ff",
+ blueviolet: "8a2be2",
+ brown: "a52a2a",
+ burlywood: "deb887",
+ cadetblue: "5f9ea0",
+ chartreuse: "7fff00",
+ chocolate: "d2691e",
+ coral: "ff7f50",
+ cornflowerblue: "6495ed",
+ cornsilk: "fff8dc",
+ crimson: "dc143c",
+ cyan: "00ffff",
+ darkblue: "00008b",
+ darkcyan: "008b8b",
+ darkgoldenrod: "b8860b",
+ darkgray: "a9a9a9",
+ darkgreen: "006400",
+ darkkhaki: "bdb76b",
+ darkmagenta: "8b008b",
+ darkolivegreen: "556b2f",
+ darkorange: "ff8c00",
+ darkorchid: "9932cc",
+ darkred: "8b0000",
+ darksalmon: "e9967a",
+ darkseagreen: "8fbc8f",
+ darkslateblue: "483d8b",
+ darkslategray: "2f4f4f",
+ darkturquoise: "00ced1",
+ darkviolet: "9400d3",
+ deeppink: "ff1493",
+ deepskyblue: "00bfff",
+ dimgray: "696969",
+ dodgerblue: "1e90ff",
+ feldspar: "d19275",
+ firebrick: "b22222",
+ floralwhite: "fffaf0",
+ forestgreen: "228b22",
+ fuchsia: "ff00ff",
+ gainsboro: "dcdcdc",
+ ghostwhite: "f8f8ff",
+ gold: "ffd700",
+ goldenrod: "daa520",
+ gray: "808080",
+ green: "008000",
+ greenyellow: "adff2f",
+ honeydew: "f0fff0",
+ hotpink: "ff69b4",
+ indianred: "cd5c5c",
+ indigo: "4b0082",
+ ivory: "fffff0",
+ khaki: "f0e68c",
+ lavender: "e6e6fa",
+ lavenderblush: "fff0f5",
+ lawngreen: "7cfc00",
+ lemonchiffon: "fffacd",
+ lightblue: "add8e6",
+ lightcoral: "f08080",
+ lightcyan: "e0ffff",
+ lightgoldenrodyellow: "fafad2",
+ lightgrey: "d3d3d3",
+ lightgreen: "90ee90",
+ lightpink: "ffb6c1",
+ lightsalmon: "ffa07a",
+ lightseagreen: "20b2aa",
+ lightskyblue: "87cefa",
+ lightslateblue: "8470ff",
+ lightslategray: "778899",
+ lightsteelblue: "b0c4de",
+ lightyellow: "ffffe0",
+ lime: "00ff00",
+ limegreen: "32cd32",
+ linen: "faf0e6",
+ magenta: "ff00ff",
+ maroon: "800000",
+ mediumaquamarine: "66cdaa",
+ mediumblue: "0000cd",
+ mediumorchid: "ba55d3",
+ mediumpurple: "9370d8",
+ mediumseagreen: "3cb371",
+ mediumslateblue: "7b68ee",
+ mediumspringgreen: "00fa9a",
+ mediumturquoise: "48d1cc",
+ mediumvioletred: "c71585",
+ midnightblue: "191970",
+ mintcream: "f5fffa",
+ mistyrose: "ffe4e1",
+ moccasin: "ffe4b5",
+ navajowhite: "ffdead",
+ navy: "000080",
+ oldlace: "fdf5e6",
+ olive: "808000",
+ olivedrab: "6b8e23",
+ orange: "ffa500",
+ orangered: "ff4500",
+ orchid: "da70d6",
+ palegoldenrod: "eee8aa",
+ palegreen: "98fb98",
+ paleturquoise: "afeeee",
+ palevioletred: "d87093",
+ papayawhip: "ffefd5",
+ peachpuff: "ffdab9",
+ peru: "cd853f",
+ pink: "ffc0cb",
+ plum: "dda0dd",
+ powderblue: "b0e0e6",
+ purple: "800080",
+ red: "ff0000",
+ rosybrown: "bc8f8f",
+ royalblue: "4169e1",
+ saddlebrown: "8b4513",
+ salmon: "fa8072",
+ sandybrown: "f4a460",
+ seagreen: "2e8b57",
+ seashell: "fff5ee",
+ sienna: "a0522d",
+ silver: "c0c0c0",
+ skyblue: "87ceeb",
+ slateblue: "6a5acd",
+ slategray: "708090",
+ snow: "fffafa",
+ springgreen: "00ff7f",
+ steelblue: "4682b4",
+ tan: "d2b48c",
+ teal: "008080",
+ thistle: "d8bfd8",
+ tomato: "ff6347",
+ turquoise: "40e0d0",
+ violet: "ee82ee",
+ violetred: "d02090",
+ wheat: "f5deb3",
+ white: "ffffff",
+ whitesmoke: "f5f5f5",
+ yellow: "ffff00",
+ yellowgreen: "9acd32"
+ };
- var refAutoPrintTag;
+ for (var key in simple_colors) {
+ if (color_string == key) {
+ color_string = simple_colors[key];
+ }
+ } // emd of simple type-in colors
+ // array of color definition objects
- this.internal.events.subscribe('postPutResources', function () {
- refAutoPrintTag = this.internal.newObject();
- this.internal.write("<< /S/Named /Type/Action /N/Print >>", "endobj");
- });
- this.internal.events.subscribe("putCatalog", function () {
- this.internal.write("/OpenAction " + refAutoPrintTag + " 0" + " R");
- });
- return this;
- };
- })(jsPDF.API);
+ var color_defs = [{
+ re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
+ example: ["rgb(123, 234, 45)", "rgb(255,234,245)"],
+ process: function (bits) {
+ return [parseInt(bits[1]), parseInt(bits[2]), parseInt(bits[3])];
+ }
+ }, {
+ re: /^(\w{2})(\w{2})(\w{2})$/,
+ example: ["#00ff00", "336699"],
+ process: function (bits) {
+ return [parseInt(bits[1], 16), parseInt(bits[2], 16), parseInt(bits[3], 16)];
+ }
+ }, {
+ re: /^(\w{1})(\w{1})(\w{1})$/,
+ example: ["#fb0", "f0f"],
+ process: function (bits) {
+ return [parseInt(bits[1] + bits[1], 16), parseInt(bits[2] + bits[2], 16), parseInt(bits[3] + bits[3], 16)];
+ }
+ }]; // search through the definitions to find a match
+
+ for (var i = 0; i < color_defs.length; i++) {
+ var re = color_defs[i].re;
+ var processor = color_defs[i].process;
+ var bits = re.exec(color_string);
+
+ if (bits) {
+ channels = processor(bits);
+ this.r = channels[0];
+ this.g = channels[1];
+ this.b = channels[2];
+ this.ok = true;
+ }
+ } // validate/cleanup values
- /**
- * jsPDF Canvas PlugIn
- * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv
- *
- * Licensed under the MIT License.
- * http://opensource.org/licenses/mit-license
- */
- /**
- * This plugin mimicks the HTML5 Canvas
- *
- * The goal is to provide a way for current canvas users to print directly to a PDF.
- */
+ this.r = this.r < 0 || isNaN(this.r) ? 0 : this.r > 255 ? 255 : this.r;
+ this.g = this.g < 0 || isNaN(this.g) ? 0 : this.g > 255 ? 255 : this.g;
+ this.b = this.b < 0 || isNaN(this.b) ? 0 : this.b > 255 ? 255 : this.b; // some getters
- (function (jsPDFAPI) {
- 'use strict';
-
- jsPDFAPI.events.push(['initialized', function () {
- this.canvas.pdf = this;
- }]);
-
- jsPDFAPI.canvas = {
- getContext: function getContext(name) {
- return this.pdf.context2d;
- },
- style: {}
- };
-
- Object.defineProperty(jsPDFAPI.canvas, 'width', {
- get: function get() {
- return this._width;
- },
- set: function set(value) {
- this._width = value;
- this.getContext('2d').pageWrapX = value + 1;
- }
- });
-
- Object.defineProperty(jsPDFAPI.canvas, 'height', {
- get: function get() {
- return this._height;
- },
- set: function set(value) {
- this._height = value;
- this.getContext('2d').pageWrapY = value + 1;
- }
- });
-
- return this;
- })(jsPDF.API);
-
- /** ====================================================================
- * jsPDF Cell plugin
- * Copyright (c) 2013 Youssef Beddad, youssef.beddad@gmail.com
- * 2013 Eduardo Menezes de Morais, eduardo.morais@usp.br
- * 2013 Lee Driscoll, https://github.com/lsdriscoll
- * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria
- * 2014 James Hall, james@parall.ax
- * 2014 Diego Casorran, https://github.com/diegocr
- *
- *
- * ====================================================================
- */
+ this.toRGB = function () {
+ return "rgb(" + this.r + ", " + this.g + ", " + this.b + ")";
+ };
- (function (jsPDFAPI) {
- 'use strict';
- /*jslint browser:true */
- /*global document: false, jsPDF */
+ this.toHex = function () {
+ var r = this.r.toString(16);
+ var g = this.g.toString(16);
+ var b = this.b.toString(16);
+ if (r.length == 1) r = "0" + r;
+ if (g.length == 1) g = "0" + g;
+ if (b.length == 1) b = "0" + b;
+ return "#" + r + g + b;
+ }; // help
- var fontName,
- fontSize,
- fontStyle,
- padding = 3,
- margin = 13,
- headerFunction,
- lastCellPos = { x: undefined, y: undefined, w: undefined, h: undefined, ln: undefined },
- pages = 1,
- setLastCellPosition = function setLastCellPosition(x, y, w, h, ln) {
- lastCellPos = { 'x': x, 'y': y, 'w': w, 'h': h, 'ln': ln };
- },
- getLastCellPosition = function getLastCellPosition() {
- return lastCellPos;
- },
- NO_MARGINS = { left: 0, top: 0, bottom: 0 };
- jsPDFAPI.setHeaderFunction = function (func) {
- headerFunction = func;
- };
+ this.getHelpXML = function () {
+ var examples = new Array(); // add regexps
- jsPDFAPI.getTextDimensions = function (txt) {
- fontName = this.internal.getFont().fontName;
- fontSize = this.table_font_size || this.internal.getFontSize();
- fontStyle = this.internal.getFont().fontStyle;
- // 1 pixel = 0.264583 mm and 1 mm = 72/25.4 point
- var px2pt = 0.264583 * 72 / 25.4,
- dimensions,
- text;
+ for (var i = 0; i < color_defs.length; i++) {
+ var example = color_defs[i].example;
- text = document.createElement('font');
- text.id = "jsPDFCell";
+ for (var j = 0; j < example.length; j++) {
+ examples[examples.length] = example[j];
+ }
+ } // add type-in colors
- try {
- text.style.fontStyle = fontStyle;
- } catch (e) {
- text.style.fontWeight = fontStyle;
- }
- text.style.fontName = fontName;
- text.style.fontSize = fontSize + 'pt';
- try {
- text.textContent = txt;
- } catch (e) {
- text.innerText = txt;
- }
+ for (var sc in simple_colors) {
+ examples[examples.length] = sc;
+ }
- document.body.appendChild(text);
+ var xml = document.createElement("ul");
+ xml.setAttribute("id", "rgbcolor-examples");
+
+ for (var i = 0; i < examples.length; i++) {
+ try {
+ var list_item = document.createElement("li");
+ var list_color = new RGBColor(examples[i]);
+ var example_div = document.createElement("div");
+ example_div.style.cssText = "margin: 3px; " + "border: 1px solid black; " + "background:" + list_color.toHex() + "; " + "color:" + list_color.toHex();
+ example_div.appendChild(document.createTextNode("test"));
+ var list_item_value = document.createTextNode(" " + examples[i] + " -> " + list_color.toRGB() + " -> " + list_color.toHex());
+ list_item.appendChild(example_div);
+ list_item.appendChild(list_item_value);
+ xml.appendChild(list_item);
+ } catch (e) {}
+ }
- dimensions = { w: (text.offsetWidth + 1) * px2pt, h: (text.offsetHeight + 1) * px2pt };
+ return xml;
+ };
+ }
- document.body.removeChild(text);
+ global.RGBColor = RGBColor;
+ })(typeof self !== "undefined" && self || typeof window !== "undefined" && window || typeof global !== "undefined" && global || Function('return typeof this === "object" && this.content')() || Function("return this")()); // `self` is undefined in Firefox for Android content script context
+ // while `this` is nsIContentFrameMessageManager
+ // with an attribute `content` that corresponds to the window
- return dimensions;
- };
+ /************************************************
+ * Title : custom font *
+ * Start Data : 2017. 01. 22. *
+ * Comment : TEXT API *
+ ************************************************/
- jsPDFAPI.cellAddPage = function () {
- var margins = this.margins || NO_MARGINS;
+ /******************************
+ * jsPDF extension API Design *
+ * ****************************/
+ (function (jsPDF) {
- this.addPage();
+ var PLUS = "+".charCodeAt(0);
+ var SLASH = "/".charCodeAt(0);
+ var NUMBER = "0".charCodeAt(0);
+ var LOWER = "a".charCodeAt(0);
+ var UPPER = "A".charCodeAt(0);
+ var PLUS_URL_SAFE = "-".charCodeAt(0);
+ var SLASH_URL_SAFE = "_".charCodeAt(0);
+ /*****************************************************************/
- setLastCellPosition(margins.left, margins.top, undefined, undefined);
- //setLastCellPosition(undefined, undefined, undefined, undefined, undefined);
- pages += 1;
- };
+ /* function : b64ToByteArray */
- jsPDFAPI.cellInitialize = function () {
- lastCellPos = { x: undefined, y: undefined, w: undefined, h: undefined, ln: undefined };
- pages = 1;
- };
+ /* comment : Base64 encoded TTF file contents (b64) are decoded */
- jsPDFAPI.cell = function (x, y, w, h, txt, ln, align) {
- var curCell = getLastCellPosition();
- var pgAdded = false;
+ /* by Byte array and stored. */
- // If this is not the first cell, we must change its position
- if (curCell.ln !== undefined) {
- if (curCell.ln === ln) {
- //Same line
- x = curCell.x + curCell.w;
- y = curCell.y;
- } else {
- //New line
- var margins = this.margins || NO_MARGINS;
- if (curCell.y + curCell.h + h + margin >= this.internal.pageSize.height - margins.bottom) {
- this.cellAddPage();
- pgAdded = true;
- if (this.printHeaders && this.tableHeaderRow) {
- this.printHeaderRow(ln, true);
- }
- }
- //We ignore the passed y: the lines may have diferent heights
- y = getLastCellPosition().y + getLastCellPosition().h;
- if (pgAdded) y = margin + 10;
- }
- }
+ /*****************************************************************/
- if (txt[0] !== undefined) {
- if (this.printingHeaderRow) {
- this.rect(x, y, w, h, 'FD');
- } else {
- this.rect(x, y, w, h);
- }
- if (align === 'right') {
- if (!(txt instanceof Array)) {
- txt = [txt];
- }
- for (var i = 0; i < txt.length; i++) {
- var currentLine = txt[i];
- var textSize = this.getStringUnitWidth(currentLine) * this.internal.getFontSize();
- this.text(currentLine, x + w - textSize - padding, y + this.internal.getLineHeight() * (i + 1));
- }
- } else {
- this.text(txt, x + padding, y + this.internal.getLineHeight());
- }
- }
- setLastCellPosition(x, y, w, h, ln);
- return this;
- };
+ var b64ToByteArray = function (b64) {
+ var i, j, l, tmp, placeHolders, arr;
- /**
- * Return the maximum value from an array
- * @param array
- * @param comparisonFn
- * @returns {*}
- */
- jsPDFAPI.arrayMax = function (array, comparisonFn) {
- var max = array[0],
- i,
- ln,
- item;
-
- for (i = 0, ln = array.length; i < ln; i += 1) {
- item = array[i];
-
- if (comparisonFn) {
- if (comparisonFn(max, item) === -1) {
- max = item;
- }
- } else {
- if (item > max) {
- max = item;
- }
- }
- }
+ if (b64.length % 4 > 0) {
+ throw new Error("Invalid string. Length must be a multiple of 4");
+ } // the number of equal signs (place holders)
+ // if there are two placeholders, than the two characters before it
+ // represent one byte
+ // if there is only one, then the three characters before it represent 2 bytes
+ // this is just a cheap hack to not do indexOf twice
- return max;
- };
- /**
- * Create a table from a set of data.
- * @param {Integer} [x] : left-position for top-left corner of table
- * @param {Integer} [y] top-position for top-left corner of table
- * @param {Object[]} [data] As array of objects containing key-value pairs corresponding to a row of data.
- * @param {String[]} [headers] Omit or null to auto-generate headers at a performance cost
- * @param {Object} [config.printHeaders] True to print column headers at the top of every page
- * @param {Object} [config.autoSize] True to dynamically set the column widths to match the widest cell value
- * @param {Object} [config.margins] margin values for left, top, bottom, and width
- * @param {Object} [config.fontSize] Integer fontSize to use (optional)
- */
+ var len = b64.length;
+ placeHolders = "=" === b64.charAt(len - 2) ? 2 : "=" === b64.charAt(len - 1) ? 1 : 0; // base64 is 4/3 + up to two characters of the original data
- jsPDFAPI.table = function (x, y, data, headers, config) {
- if (!data) {
- throw 'No data for PDF table';
- }
+ arr = new Uint8Array(b64.length * 3 / 4 - placeHolders); // if there are placeholders, only get up to the last complete 4 chars
- var headerNames = [],
- headerPrompts = [],
- header,
- i,
- ln,
- cln,
- columnMatrix = {},
- columnWidths = {},
- columnData,
- column,
- columnMinWidths = [],
- j,
- tableHeaderConfigs = [],
- model,
- jln,
- func,
-
-
- //set up defaults. If a value is provided in config, defaults will be overwritten:
- autoSize = false,
- printHeaders = true,
- fontSize = 12,
- margins = NO_MARGINS;
-
- margins.width = this.internal.pageSize.width;
-
- if (config) {
- //override config defaults if the user has specified non-default behavior:
- if (config.autoSize === true) {
- autoSize = true;
- }
- if (config.printHeaders === false) {
- printHeaders = false;
- }
- if (config.fontSize) {
- fontSize = config.fontSize;
- }
- if (config.css['font-size']) {
- fontSize = config.css['font-size'] * 16;
- }
- if (config.margins) {
- margins = config.margins;
- }
- }
+ l = placeHolders > 0 ? b64.length - 4 : b64.length;
+ var L = 0;
- /**
- * @property {Number} lnMod
- * Keep track of the current line number modifier used when creating cells
- */
- this.lnMod = 0;
- lastCellPos = { x: undefined, y: undefined, w: undefined, h: undefined, ln: undefined }, pages = 1;
-
- this.printHeaders = printHeaders;
- this.margins = margins;
- this.setFontSize(fontSize);
- this.table_font_size = fontSize;
-
- // Set header values
- if (headers === undefined || headers === null) {
- // No headers defined so we derive from data
- headerNames = Object.keys(data[0]);
- } else if (headers[0] && typeof headers[0] !== 'string') {
- var px2pt = 0.264583 * 72 / 25.4;
-
- // Split header configs into names and prompts
- for (i = 0, ln = headers.length; i < ln; i += 1) {
- header = headers[i];
- headerNames.push(header.name);
- headerPrompts.push(header.prompt);
- columnWidths[header.name] = header.width * px2pt;
- }
- } else {
- headerNames = headers;
- }
+ function push(v) {
+ arr[L++] = v;
+ }
- if (autoSize) {
- // Create a matrix of columns e.g., {column_title: [row1_Record, row2_Record]}
- func = function func(rec) {
- return rec[header];
- };
+ for (i = 0, j = 0; i < l; i += 4, j += 3) {
+ tmp = decode(b64.charAt(i)) << 18 | decode(b64.charAt(i + 1)) << 12 | decode(b64.charAt(i + 2)) << 6 | decode(b64.charAt(i + 3));
+ push((tmp & 0xff0000) >> 16);
+ push((tmp & 0xff00) >> 8);
+ push(tmp & 0xff);
+ }
- for (i = 0, ln = headerNames.length; i < ln; i += 1) {
- header = headerNames[i];
+ if (placeHolders === 2) {
+ tmp = decode(b64.charAt(i)) << 2 | decode(b64.charAt(i + 1)) >> 4;
+ push(tmp & 0xff);
+ } else if (placeHolders === 1) {
+ tmp = decode(b64.charAt(i)) << 10 | decode(b64.charAt(i + 1)) << 4 | decode(b64.charAt(i + 2)) >> 2;
+ push(tmp >> 8 & 0xff);
+ push(tmp & 0xff);
+ }
- columnMatrix[header] = data.map(func);
+ return arr;
+ };
+ /***************************************************************/
- // get header width
- columnMinWidths.push(this.getTextDimensions(headerPrompts[i] || header).w);
- column = columnMatrix[header];
+ /* function : decode */
- // get cell widths
- for (j = 0, cln = column.length; j < cln; j += 1) {
- columnData = column[j];
- columnMinWidths.push(this.getTextDimensions(columnData).w);
- }
+ /* comment : Change the base64 encoded font's content to match */
- // get final column width
- columnWidths[header] = jsPDFAPI.arrayMax(columnMinWidths);
+ /* the base64 index value. */
- //have to reset
- columnMinWidths = [];
- }
- }
+ /***************************************************************/
- // -- Construct the table
- if (printHeaders) {
- var lineHeight = this.calculateLineHeight(headerNames, columnWidths, headerPrompts.length ? headerPrompts : headerNames);
+ var decode = function (elt) {
+ var code = elt.charCodeAt(0);
+ if (code === PLUS || code === PLUS_URL_SAFE) return 62; // '+'
- // Construct the header row
- for (i = 0, ln = headerNames.length; i < ln; i += 1) {
- header = headerNames[i];
- tableHeaderConfigs.push([x, y, columnWidths[header], lineHeight, String(headerPrompts.length ? headerPrompts[i] : header)]);
- }
+ if (code === SLASH || code === SLASH_URL_SAFE) return 63; // '/'
- // Store the table header config
- this.setTableHeaderRow(tableHeaderConfigs);
+ if (code < NUMBER) return -1; //no match
- // Print the header for the start of the table
- this.printHeaderRow(1, false);
- }
+ if (code < NUMBER + 10) return code - NUMBER + 26 + 26;
+ if (code < UPPER + 26) return code - UPPER;
+ if (code < LOWER + 26) return code - LOWER + 26;
+ };
- // Construct the data rows
- for (i = 0, ln = data.length; i < ln; i += 1) {
- var lineHeight;
- model = data[i];
- lineHeight = this.calculateLineHeight(headerNames, columnWidths, model);
+ jsPDF.API.TTFFont = function () {
+ /************************************************************************/
- for (j = 0, jln = headerNames.length; j < jln; j += 1) {
- header = headerNames[j];
- this.cell(x, y, columnWidths[header], lineHeight, model[header], i + 2, header.align);
- }
- }
- this.lastCellPos = lastCellPos;
- this.table_x = x;
- this.table_y = y;
- return this;
- };
- /**
- * Calculate the height for containing the highest column
- * @param {String[]} headerNames is the header, used as keys to the data
- * @param {Integer[]} columnWidths is size of each column
- * @param {Object[]} model is the line of data we want to calculate the height of
- */
- jsPDFAPI.calculateLineHeight = function (headerNames, columnWidths, model) {
- var header,
- lineHeight = 0;
- for (var j = 0; j < headerNames.length; j++) {
- header = headerNames[j];
- model[header] = this.splitTextToSize(String(model[header]), columnWidths[header] - padding);
- var h = this.internal.getLineHeight() * model[header].length + padding;
- if (h > lineHeight) lineHeight = h;
- }
- return lineHeight;
- };
+ /* function : open */
- /**
- * Store the config for outputting a table header
- * @param {Object[]} config
- * An array of cell configs that would define a header row: Each config matches the config used by jsPDFAPI.cell
- * except the ln parameter is excluded
- */
- jsPDFAPI.setTableHeaderRow = function (config) {
- this.tableHeaderRow = config;
- };
+ /* comment : Decode the encoded ttf content and create a TTFFont object. */
- /**
- * Output the store header row
- * @param lineNumber The line number to output the header at
- */
- jsPDFAPI.printHeaderRow = function (lineNumber, new_page) {
- if (!this.tableHeaderRow) {
- throw 'Property tableHeaderRow does not exist.';
- }
+ /************************************************************************/
+ TTFFont.open = function (filename, name, vfs, encoding) {
+ var contents;
- var tableHeaderCell, tmpArray, i, ln;
+ if (typeof vfs !== "string") {
+ throw new Error("Invalid argument supplied in TTFFont.open");
+ }
- this.printingHeaderRow = true;
- if (headerFunction !== undefined) {
- var position = headerFunction(this, pages);
- setLastCellPosition(position[0], position[1], position[2], position[3], -1);
- }
- this.setFontStyle('bold');
- var tempHeaderConf = [];
- for (i = 0, ln = this.tableHeaderRow.length; i < ln; i += 1) {
- this.setFillColor(200, 200, 200);
-
- tableHeaderCell = this.tableHeaderRow[i];
- if (new_page) {
- this.margins.top = margin;
- tableHeaderCell[1] = this.margins && this.margins.top || 0;
- tempHeaderConf.push(tableHeaderCell);
- }
- tmpArray = [].concat(tableHeaderCell);
- this.cell.apply(this, tmpArray.concat(lineNumber));
- }
- if (tempHeaderConf.length > 0) {
- this.setTableHeaderRow(tempHeaderConf);
- }
- this.setFontStyle('normal');
- this.printingHeaderRow = false;
- };
- })(jsPDF.API);
+ contents = b64ToByteArray(vfs);
+ return new TTFFont(contents, name, encoding);
+ };
+ /***************************************************************/
- /**
- * jsPDF Context2D PlugIn
- * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv
- *
- * Licensed under the MIT License.
- * http://opensource.org/licenses/mit-license
- */
+ /* function : TTFFont gernerator */
- /**
- * This plugin mimicks the HTML5 Canvas's context2d.
- *
- * The goal is to provide a way for current canvas implementations to print directly to a PDF.
- */
+ /* comment : Decode TTF contents are parsed, Data, */
- /**
- * require('jspdf.js');
- * require('lib/css_colors.js');
- */
+ /* Subset object is created, and registerTTF function is called.*/
- (function (jsPDFAPI) {
- 'use strict';
-
- jsPDFAPI.events.push(['initialized', function () {
- this.context2d.pdf = this;
- this.context2d.internal.pdf = this;
- this.context2d.ctx = new context();
- this.context2d.ctxStack = [];
- this.context2d.path = [];
- }]);
-
- jsPDFAPI.context2d = {
- pageWrapXEnabled: false,
- pageWrapYEnabled: true,
- pageWrapX: 9999999,
- pageWrapY: 9999999,
-
- f2: function f2(number) {
- return number.toFixed(2);
- },
-
- fillRect: function fillRect(x, y, w, h) {
- x = this._wrapX(x);
- y = this._wrapY(y);
- this.pdf.rect(x, y, w, h, "f");
- },
-
- strokeRect: function strokeRect(x, y, w, h) {
- x = this._wrapX(x);
- y = this._wrapY(y);
- this.pdf.rect(x, y, w, h, "s");
- },
-
- clearRect: function clearRect(x, y, w, h) {
- x = this._wrapX(x);
- y = this._wrapY(y);
- this.save();
- this.setFillStyle('#ffffff');
- this.pdf.rect(x, y, w, h, "f");
- this.restore();
- },
-
- save: function save() {
- this.ctx._fontSize = this.pdf.internal.getFontSize();
- var ctx = new context();
- ctx.copy(this.ctx);
- this.ctxStack.push(this.ctx);
- this.ctx = ctx;
- },
-
- restore: function restore() {
- this.ctx = this.ctxStack.pop();
- this.setFillStyle(this.ctx.fillStyle);
- this.setStrokeStyle(this.ctx.strokeStyle);
- this.setFont(this.ctx.font);
- this.pdf.setFontSize(this.ctx._fontSize);
- this.setLineCap(this.ctx.lineCap);
- this.setLineWidth(this.ctx.lineWidth);
- this.setLineJoin(this.ctx.lineJoin);
- },
-
- beginPath: function beginPath() {
- this.path = [];
- },
-
- closePath: function closePath() {
- this.path.push({
- type: 'close'
- });
- },
-
- setFillStyle: function setFillStyle(style) {
-
- // get the decimal values of r, g, and b;
- var r, g, b, a;
-
- var m = this.internal.rxRgb.exec(style);
- if (m != null) {
- r = parseInt(m[1]);
- g = parseInt(m[2]);
- b = parseInt(m[3]);
- } else {
- m = this.internal.rxRgba.exec(style);
- if (m != null) {
- r = parseInt(m[1]);
- g = parseInt(m[2]);
- b = parseInt(m[3]);
- a = parseInt(m[4]);
- } else {
- if (style.charAt(0) != '#') {
- style = CssColors.colorNameToHex(style);
- if (!style) {
- style = '#000000';
- }
- } else {}
- this.ctx.fillStyle = style;
-
- if (style.length === 4) {
- r = this.ctx.fillStyle.substring(1, 2);
- r += r;
- g = this.ctx.fillStyle.substring(2, 3);
- g += g;
- b = this.ctx.fillStyle.substring(3, 4);
- b += b;
- } else {
- r = this.ctx.fillStyle.substring(1, 3);
- g = this.ctx.fillStyle.substring(3, 5);
- b = this.ctx.fillStyle.substring(5, 7);
- }
- r = parseInt(r, 16);
- g = parseInt(g, 16);
- b = parseInt(b, 16);
- }
- }
- this.pdf.setFillColor(r, g, b, {
- a: a
- });
- this.pdf.setTextColor(r, g, b, {
- a: a
- });
- },
-
- setStrokeStyle: function setStrokeStyle(style) {
- if (style.charAt(0) != '#') {
- style = CssColors.colorNameToHex(style);
- if (!style) {
- style = '#000000';
- }
- }
- this.ctx.strokeStyle = style;
- var r = this.ctx.strokeStyle.substring(1, 3);
- r = parseInt(r, 16);
- var g = this.ctx.strokeStyle.substring(3, 5);
- g = parseInt(g, 16);
- var b = this.ctx.strokeStyle.substring(5, 7);
- b = parseInt(b, 16);
- this.pdf.setDrawColor(r, g, b);
- },
-
- fillText: function fillText(text, x, y, maxWidth) {
- x = this._wrapX(x);
- y = this._wrapY(y);
- this.pdf.text(text, x, this._getBaseline(y));
- },
-
- strokeText: function strokeText(text, x, y, maxWidth) {
- x = this._wrapX(x);
- y = this._wrapY(y);
- this.pdf.text(text, x, this._getBaseline(y), {
- stroke: true
- });
- },
-
- setFont: function setFont(font) {
- this.ctx.font = font;
-
- var rx = /\s*(\w+)\s+(\w+)\s+(\w+)\s+([\d\.]+)(px|pt|em)\s+["']?(\w+)['"]?/;
- m = rx.exec(font);
- if (m != null) {
- var fontStyle = m[1];
- var fontVariant = m[2];
- var fontWeight = m[3];
- var fontSize = m[4];
- var fontSizeUnit = m[5];
- var fontFamily = m[6];
-
- if ('px' === fontSizeUnit) {
- fontSize = Math.floor(parseFloat(fontSize));
- //fontSize = fontSize * 1.25;
- } else if ('em' === fontSizeUnit) {
- fontSize = Math.floor(parseFloat(fontSize) * this.pdf.getFontSize());
- } else {
- fontSize = Math.floor(parseFloat(fontSize));
- }
-
- this.pdf.setFontSize(fontSize);
-
- if (fontWeight === 'bold' || fontWeight === '700') {
- this.pdf.setFontStyle('bold');
- } else {
- if (fontStyle === 'italic') {
- this.pdf.setFontStyle('italic');
- } else {
- this.pdf.setFontStyle('normal');
- }
- }
- //TODO This needs to be parsed
- var name = fontFamily;
- this.pdf.setFont(name, style);
- } else {
- var rx = /(\d+)(pt|px|em)\s+(\w+)\s*(\w+)?/;
- var m = rx.exec(font);
- if (m != null) {
- var size = m[1];
- var unit = m[2];
- var name = m[3];
- var style = m[4];
- if (!style) {
- style = 'normal';
- }
- if ('em' === fontSizeUnit) {
- size = Math.floor(parseFloat(fontSize) * this.pdf.getFontSize());
- } else {
- size = Math.floor(parseFloat(size));
- }
- this.pdf.setFontSize(size);
- this.pdf.setFont(name, style);
- }
- }
- },
-
- setTextBaseline: function setTextBaseline(baseline) {
- this.ctx.textBaseline = baseline;
- },
-
- getTextBaseline: function getTextBaseline() {
- return this.ctx.textBaseline;
- },
-
- setLineWidth: function setLineWidth(width) {
- this.ctx.lineWidth = width;
- this.pdf.setLineWidth(width);
- },
-
- setLineCap: function setLineCap(style) {
- this.ctx.lineCap = style;
- this.pdf.setLineCap(style);
- },
-
- setLineJoin: function setLineJoin(style) {
- this.ctx.lineJon = style;
- this.pdf.setLineJoin(style);
- },
-
- moveTo: function moveTo(x, y) {
- x = this._wrapX(x);
- y = this._wrapY(y);
- var obj = {
- type: 'mt',
- x: x,
- y: y
- };
- this.path.push(obj);
- },
-
- _wrapX: function _wrapX(x) {
- if (this.pageWrapXEnabled) {
- return x % this.pageWrapX;
- } else {
- return x;
- }
- },
-
- _wrapY: function _wrapY(y) {
- if (this.pageWrapYEnabled) {
- this._gotoPage(this._page(y));
- return (y - this.lastBreak) % this.pageWrapY;
- } else {
- return y;
- }
- },
-
- lastBreak: 0,
- // Y Position of page breaks.
- pageBreaks: [],
- // returns: One-based Page Number
- // Should only be used if pageWrapYEnabled is true
- _page: function _page(y) {
- if (this.pageWrapYEnabled) {
- this.lastBreak = 0;
- var manualBreaks = 0;
- var autoBreaks = 0;
- for (var i = 0; i < this.pageBreaks.length; i++) {
- if (y >= this.pageBreaks[i]) {
- manualBreaks++;
- if (this.lastBreak === 0) {
- autoBreaks++;
- }
- var spaceBetweenLastBreak = this.pageBreaks[i] - this.lastBreak;
- this.lastBreak = this.pageBreaks[i];
- var pagesSinceLastBreak = Math.floor(spaceBetweenLastBreak / this.pageWrapY);
- autoBreaks += pagesSinceLastBreak;
- }
- }
- if (this.lastBreak === 0) {
- var pagesSinceLastBreak = Math.floor(y / this.pageWrapY) + 1;
- autoBreaks += pagesSinceLastBreak;
- }
- return autoBreaks + manualBreaks;
- } else {
- return this.pdf.internal.getCurrentPageInfo().pageNumber;
- }
- },
-
- _gotoPage: function _gotoPage(pageOneBased) {
- // This is a stub to be overriden if needed
- },
-
- lineTo: function lineTo(x, y) {
- x = this._wrapX(x);
- y = this._wrapY(y);
- var obj = {
- type: 'lt',
- x: x,
- y: y
- };
- this.path.push(obj);
- },
-
- bezierCurveTo: function bezierCurveTo(x1, y1, x2, y2, x, y) {
- x1 = this._wrapX(x1);
- y1 = this._wrapY(y1);
- x2 = this._wrapX(x2);
- y2 = this._wrapY(y2);
- x = this._wrapX(x);
- y = this._wrapY(y);
- var obj = {
- type: 'bct',
- x1: x1,
- y1: y1,
- x2: x2,
- y2: y2,
- x: x,
- y: y
- };
- this.path.push(obj);
- },
-
- quadraticCurveTo: function quadraticCurveTo(x1, y1, x, y) {
- x1 = this._wrapX(x1);
- y1 = this._wrapY(y1);
- x = this._wrapX(x);
- y = this._wrapY(y);
- var obj = {
- type: 'qct',
- x1: x1,
- y1: y1,
- x: x,
- y: y
- };
- this.path.push(obj);
- },
-
- arc: function arc(x, y, radius, startAngle, endAngle, anticlockwise) {
- x = this._wrapX(x);
- y = this._wrapY(y);
- var obj = {
- type: 'arc',
- x: x,
- y: y,
- radius: radius,
- startAngle: startAngle,
- endAngle: endAngle,
- anticlockwise: anticlockwise
- };
- this.path.push(obj);
- },
-
- drawImage: function drawImage(img, x, y, w, h, x2, y2, w2, h2) {
- if (x2 !== undefined) {
- x = x2;
- y = y2;
- w = w2;
- h = h2;
- }
- x = this._wrapX(x);
- y = this._wrapY(y);
-
- //TODO implement source clipping and image scaling
- var format;
- var rx = /data:image\/(\w+).*/i;
- var m = rx.exec(img);
- if (m != null) {
- format = m[1];
- } else {
- //format = "jpeg";
- format = "png";
- }
- this.pdf.addImage(img, format, x, y, w, h);
- },
-
- stroke: function stroke() {
- var start;
- var deltas = [];
- var last;
- var closed = false;
- for (var i = 0; i < this.path.length; i++) {
- var pt = this.path[i];
- switch (pt.type) {
- case 'mt':
- start = pt;
- if (typeof start != 'undefined') {
- this.pdf.lines(deltas, start.x, start.y, null, 's');
- deltas = [];
- }
- break;
- case 'lt':
- var delta = [pt.x - this.path[i - 1].x, pt.y - this.path[i - 1].y];
- deltas.push(delta);
- break;
- case 'bct':
- var delta = [pt.x1 - this.path[i - 1].x, pt.y1 - this.path[i - 1].y, pt.x2 - this.path[i - 1].x, pt.y2 - this.path[i - 1].y, pt.x - this.path[i - 1].x, pt.y - this.path[i - 1].y];
- deltas.push(delta);
- break;
- case 'qct':
- // convert to bezier
- var x1 = this.path[i - 1].x + 2.0 / 3.0 * (pt.x1 - this.path[i - 1].x);
- var y1 = this.path[i - 1].y + 2.0 / 3.0 * (pt.y1 - this.path[i - 1].y);
- var x2 = pt.x + 2.0 / 3.0 * (pt.x1 - pt.x);
- var y2 = pt.y + 2.0 / 3.0 * (pt.y1 - pt.y);
- var x3 = pt.x;
- var y3 = pt.y;
- var delta = [x1 - this.path[i - 1].x, y1 - this.path[i - 1].y, x2 - this.path[i - 1].x, y2 - this.path[i - 1].y, x3 - this.path[i - 1].x, y3 - this.path[i - 1].y];
- deltas.push(delta);
- break;
- case 'close':
- closed = true;
- break;
- }
- }
-
- if (typeof start != 'undefined') {
- this.pdf.lines(deltas, start.x, start.y, null, 's', closed);
- }
-
- for (var i = 0; i < this.path.length; i++) {
- var pt = this.path[i];
- switch (pt.type) {
- case 'arc':
- var start = pt.startAngle * 360 / (2 * Math.PI);
- var end = pt.endAngle * 360 / (2 * Math.PI);
- this.internal.arc(pt.x, pt.y, pt.radius, start, end, pt.anticlockwise, 's');
- break;
- }
- }
-
- this.path = [];
- },
-
- fill: function fill() {
- var start;
- var deltas = [];
- var last;
- for (var i = 0; i < this.path.length; i++) {
- var pt = this.path[i];
- switch (pt.type) {
- case 'mt':
- start = pt;
- if (typeof start != 'undefined') {
- this.pdf.lines(deltas, start.x, start.y, null, 'f');
- deltas = [];
- }
- break;
- case 'lt':
- var delta = [pt.x - this.path[i - 1].x, pt.y - this.path[i - 1].y];
- deltas.push(delta);
- break;
- case 'bct':
- var delta = [pt.x1 - this.path[i - 1].x, pt.y1 - this.path[i - 1].y, pt.x2 - this.path[i - 1].x, pt.y2 - this.path[i - 1].y, pt.x - this.path[i - 1].x, pt.y - this.path[i - 1].y];
- deltas.push(delta);
- break;
- case 'qct':
- // convert to bezier
- var x1 = this.path[i - 1].x + 2.0 / 3.0 * (pt.x1 - this.path[i - 1].x);
- var y1 = this.path[i - 1].y + 2.0 / 3.0 * (pt.y1 - this.path[i - 1].y);
- var x2 = pt.x + 2.0 / 3.0 * (pt.x1 - pt.x);
- var y2 = pt.y + 2.0 / 3.0 * (pt.y1 - pt.y);
- var x3 = pt.x;
- var y3 = pt.y;
- var delta = [x1 - this.path[i - 1].x, y1 - this.path[i - 1].y, x2 - this.path[i - 1].x, y2 - this.path[i - 1].y, x3 - this.path[i - 1].x, y3 - this.path[i - 1].y];
- deltas.push(delta);
- break;
- }
- }
-
- if (typeof start != 'undefined') {
- this.pdf.lines(deltas, start.x, start.y, null, 'f');
- }
-
- for (var i = 0; i < this.path.length; i++) {
- var pt = this.path[i];
- switch (pt.type) {
- case 'arc':
- var start = pt.startAngle * 360 / (2 * Math.PI);
- var end = pt.endAngle * 360 / (2 * Math.PI);
- this.internal.arc(pt.x, pt.y, pt.radius, start, end, pt.anticlockwise, 'f');
- break;
- case 'close':
- this.pdf.internal.out('h');
- break;
- }
- }
-
- this.path = [];
- },
-
- clip: function clip() {
- //TODO not implemented
- },
-
- translate: function translate(x, y) {
- this.ctx._translate = {
- x: x,
- y: y
- };
- //TODO use translate in other drawing methods.
- },
- measureText: function measureText(text) {
- var pdf = this.pdf;
- return {
- getWidth: function getWidth() {
- var fontSize = pdf.internal.getFontSize();
- var txtWidth = pdf.getStringUnitWidth(text) * fontSize / pdf.internal.scaleFactor;
- return txtWidth;
- },
-
- get width() {
- return this.getWidth(text);
- }
- };
- },
- _getBaseline: function _getBaseline(y) {
- var height = parseInt(this.pdf.internal.getFontSize());
- //TODO Get descent from font descriptor
- var descent = height * .25;
- switch (this.ctx.textBaseline) {
- case 'bottom':
- return y - descent;
- case 'top':
- return y + height;
- case 'hanging':
- return y + height - descent;
- case 'middle':
- return y + height / 2 - descent;
- case 'ideographic':
- //TODO not implemented
- return y;
- case 'alphabetic':
- default:
- return y;
- }
- }
- };
-
- var c2d = jsPDFAPI.context2d;
-
- // accessor methods
- Object.defineProperty(c2d, 'fillStyle', {
- set: function set(value) {
- this.setFillStyle(value);
- },
- get: function get() {
- return this.ctx.fillStyle;
- }
- });
- Object.defineProperty(c2d, 'textBaseline', {
- set: function set(value) {
- this.setTextBaseline(value);
- },
- get: function get() {
- return this.getTextBaseline();
- }
- });
- Object.defineProperty(c2d, 'font', {
- set: function set(value) {
- this.setFont(value);
- },
- get: function get() {
- return this.getFont();
- }
- });
-
- c2d.internal = {};
-
- c2d.internal.rxRgb = /rgb\s*\(\s*(\d+),\s*(\d+),\s*(\d+\s*)\)/;
- c2d.internal.rxRgba = /rgba\s*\(\s*(\d+),\s*(\d+),\s*(\d+),\s*(\d+)\s*\)/;
-
- // http://hansmuller-flex.blogspot.com/2011/10/more-about-approximating-circular-arcs.html
- c2d.internal.arc = function (xc, yc, r, a1, a2, anticlockwise, style) {
-
- var k = this.pdf.internal.scaleFactor;
- var pageHeight = this.pdf.internal.pageSize.height;
- var f2 = this.pdf.internal.f2;
-
- var a1r = a1 * (Math.PI / 180);
- var a2r = a2 * (Math.PI / 180);
- var curves = this.createArc(r, a1r, a2r, anticlockwise);
- var pathData = null;
-
- for (var i = 0; i < curves.length; i++) {
- var curve = curves[i];
- if (i == 0) {
- this.pdf.internal.out([f2((curve.x1 + xc) * k), f2((pageHeight - (curve.y1 + yc)) * k), 'm', f2((curve.x2 + xc) * k), f2((pageHeight - (curve.y2 + yc)) * k), f2((curve.x3 + xc) * k), f2((pageHeight - (curve.y3 + yc)) * k), f2((curve.x4 + xc) * k), f2((pageHeight - (curve.y4 + yc)) * k), 'c'].join(' '));
- } else {
- this.pdf.internal.out([f2((curve.x2 + xc) * k), f2((pageHeight - (curve.y2 + yc)) * k), f2((curve.x3 + xc) * k), f2((pageHeight - (curve.y3 + yc)) * k), f2((curve.x4 + xc) * k), f2((pageHeight - (curve.y4 + yc)) * k), 'c'].join(' '));
- }
- //f2((curve.x1 + xc) * k), f2((pageHeight - (curve.y1 + yc)) * k), 'm', f2((curve.x2 + xc) * k), f2((pageHeight - (curve.y2 + yc)) * k), f2((curve.x3 + xc) * k), f2((pageHeight - (curve.y3 + yc)) * k), f2((curve.x4 + xc) * k), f2((pageHeight - (curve.y4 + yc)) * k), 'c'
- }
-
- if (style !== null) {
- this.pdf.internal.out(this.pdf.internal.getStyle(style));
- }
- };
-
- /**
- * Return a array of objects that represent bezier curves which approximate the
- * circular arc centered at the origin, from startAngle to endAngle (radians) with
- * the specified radius.
- *
- * Each bezier curve is an object with four points, where x1,y1 and
- * x4,y4 are the arc's end points and x2,y2 and x3,y3 are the cubic bezier's
- * control points.
- */
+ /***************************************************************/
- c2d.internal.createArc = function (radius, startAngle, endAngle, anticlockwise) {
-
- var EPSILON = 0.00001; // Roughly 1/1000th of a degree, see below
-
- // normalize startAngle, endAngle to [-2PI, 2PI]
- var twoPI = Math.PI * 2;
- var startAngleN = startAngle;
- if (startAngleN < twoPI || startAngleN > twoPI) {
- startAngleN = startAngleN % twoPI;
- }
- var endAngleN = endAngle;
- if (endAngleN < twoPI || endAngleN > twoPI) {
- endAngleN = endAngleN % twoPI;
- }
-
- // Compute the sequence of arc curves, up to PI/2 at a time.
- // Total arc angle is less than 2PI.
- var curves = [];
- var piOverTwo = Math.PI / 2.0;
- //var sgn = (startAngle < endAngle) ? +1 : -1; // clockwise or counterclockwise
- var sgn = anticlockwise ? -1 : +1;
-
- var a1 = startAngle;
- for (var totalAngle = Math.min(twoPI, Math.abs(endAngleN - startAngleN)); totalAngle > EPSILON;) {
- var a2 = a1 + sgn * Math.min(totalAngle, piOverTwo);
- curves.push(this.createSmallArc(radius, a1, a2));
- totalAngle -= Math.abs(a2 - a1);
- a1 = a2;
- }
-
- return curves;
- };
-
- /**
- * Cubic bezier approximation of a circular arc centered at the origin,
- * from (radians) a1 to a2, where a2-a1 < pi/2. The arc's radius is r.
- *
- * Returns an object with four points, where x1,y1 and x4,y4 are the arc's end points
- * and x2,y2 and x3,y3 are the cubic bezier's control points.
- *
- * This algorithm is based on the approach described in:
- * A. Riškus, "Approximation of a Cubic Bezier Curve by Circular Arcs and Vice Versa,"
- * Information Technology and Control, 35(4), 2006 pp. 371-378.
- */
- c2d.internal.createSmallArc = function (r, a1, a2) {
- // Compute all four points for an arc that subtends the same total angle
- // but is centered on the X-axis
-
- var a = (a2 - a1) / 2.0;
-
- var x4 = r * Math.cos(a);
- var y4 = r * Math.sin(a);
- var x1 = x4;
- var y1 = -y4;
-
- var q1 = x1 * x1 + y1 * y1;
- var q2 = q1 + x1 * x4 + y1 * y4;
- var k2 = 4 / 3 * (Math.sqrt(2 * q1 * q2) - q2) / (x1 * y4 - y1 * x4);
-
- var x2 = x1 - k2 * y1;
- var y2 = y1 + k2 * x1;
- var x3 = x2;
- var y3 = -y2;
-
- // Find the arc points' actual locations by computing x1,y1 and x4,y4
- // and rotating the control points by a + a1
-
- var ar = a + a1;
- var cos_ar = Math.cos(ar);
- var sin_ar = Math.sin(ar);
-
- return {
- x1: r * Math.cos(a1),
- y1: r * Math.sin(a1),
- x2: x2 * cos_ar - y2 * sin_ar,
- y2: x2 * sin_ar + y2 * cos_ar,
- x3: x3 * cos_ar - y3 * sin_ar,
- y3: x3 * sin_ar + y3 * cos_ar,
- x4: r * Math.cos(a2),
- y4: r * Math.sin(a2)
- };
- };
-
- function context() {
- this.fillStyle = '#000000';
- this.strokeStyle = '#000000';
- this.font = "12pt times";
- this.textBaseline = 'alphabetic'; //top,bottom,middle,ideographic,alphabetic,hanging
- this.lineWidth = 1;
- this.lineJoin = 'miter'; //round, bevel, miter
- this.lineCap = 'butt'; //butt, round, square
- this._translate = {
- x: 0,
- y: 0
- };
- //TODO miter limit //default 10
-
- this.copy = function (ctx) {
- this.fillStyle = ctx.fillStyle;
- this.strokeStyle = ctx.strokeStyle;
- this.font = ctx.font;
- this.lineWidth = ctx.lineWidth;
- this.lineJoin = ctx.lineJoin;
- this.lineCap = ctx.lineCap;
- this.textBaseline = ctx.textBaseline;
- this._fontSize = ctx._fontSize;
- this._translate = {
- x: ctx._translate.x,
- y: ctx._translate.y
- };
- };
- }
-
- return this;
- })(jsPDF.API);
-
- /** @preserve
- * jsPDF fromHTML plugin. BETA stage. API subject to change. Needs browser
- * Copyright (c) 2012 Willow Systems Corporation, willow-systems.com
- * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria
- * 2014 Diego Casorran, https://github.com/diegocr
- * 2014 Daniel Husar, https://github.com/danielhusar
- * 2014 Wolfgang Gassler, https://github.com/woolfg
- * 2014 Steven Spungin, https://github.com/flamenco
- *
- *
- * ====================================================================
- */
+ function TTFFont(rawData, name, encoding) {
+ var data;
- (function (jsPDFAPI) {
- var clone, _DrillForContent, FontNameDB, FontStyleMap, TextAlignMap, FontWeightMap, FloatMap, ClearMap, GetCSS, PurgeWhiteSpace, Renderer, ResolveFont, ResolveUnitedNumber, UnitedNumberMap, elementHandledElsewhere, images, loadImgs, checkForFooter, process, tableToJson;
- clone = function () {
- return function (obj) {
- Clone.prototype = obj;
- return new Clone();
- };
- function Clone() {}
- }();
- PurgeWhiteSpace = function PurgeWhiteSpace(array) {
- var fragment, i, l, lTrimmed, r, rTrimmed, trailingSpace;
- i = 0;
- l = array.length;
- fragment = void 0;
- lTrimmed = false;
- rTrimmed = false;
- while (!lTrimmed && i !== l) {
- fragment = array[i] = array[i].trimLeft();
- if (fragment) {
- lTrimmed = true;
- }
- i++;
- }
- i = l - 1;
- while (l && !rTrimmed && i !== -1) {
- fragment = array[i] = array[i].trimRight();
- if (fragment) {
- rTrimmed = true;
- }
- i--;
- }
- r = /\s+$/g;
- trailingSpace = true;
- i = 0;
- while (i !== l) {
- // Leave the line breaks intact
- if (array[i] != "\u2028") {
- fragment = array[i].replace(/\s+/g, " ");
- if (trailingSpace) {
- fragment = fragment.trimLeft();
- }
- if (fragment) {
- trailingSpace = r.test(fragment);
- }
- array[i] = fragment;
- }
- i++;
- }
- return array;
- };
- Renderer = function Renderer(pdf, x, y, settings) {
- this.pdf = pdf;
- this.x = x;
- this.y = y;
- this.settings = settings;
- //list of functions which are called after each element-rendering process
- this.watchFunctions = [];
- this.init();
- return this;
- };
- ResolveFont = function ResolveFont(css_font_family_string) {
- var name, part, parts;
- name = void 0;
- parts = css_font_family_string.split(",");
- part = parts.shift();
- while (!name && part) {
- name = FontNameDB[part.trim().toLowerCase()];
- part = parts.shift();
- }
- return name;
- };
- ResolveUnitedNumber = function ResolveUnitedNumber(css_line_height_string) {
-
- //IE8 issues
- css_line_height_string = css_line_height_string === "auto" ? "0px" : css_line_height_string;
- if (css_line_height_string.indexOf("em") > -1 && !isNaN(Number(css_line_height_string.replace("em", "")))) {
- css_line_height_string = Number(css_line_height_string.replace("em", "")) * 18.719 + "px";
- }
- if (css_line_height_string.indexOf("pt") > -1 && !isNaN(Number(css_line_height_string.replace("pt", "")))) {
- css_line_height_string = Number(css_line_height_string.replace("pt", "")) * 1.333 + "px";
- }
-
- var normal, undef, value;
- undef = void 0;
- normal = 16.00;
- value = UnitedNumberMap[css_line_height_string];
- if (value) {
- return value;
- }
- value = {
- "xx-small": 9,
- "x-small": 11,
- small: 13,
- medium: 16,
- large: 19,
- "x-large": 23,
- "xx-large": 28,
- auto: 0
- }[{ css_line_height_string: css_line_height_string }];
-
- if (value !== undef) {
- return UnitedNumberMap[css_line_height_string] = value / normal;
- }
- if (value = parseFloat(css_line_height_string)) {
- return UnitedNumberMap[css_line_height_string] = value / normal;
- }
- value = css_line_height_string.match(/([\d\.]+)(px)/);
- if (value.length === 3) {
- return UnitedNumberMap[css_line_height_string] = parseFloat(value[1]) / normal;
- }
- return UnitedNumberMap[css_line_height_string] = 1;
- };
- GetCSS = function GetCSS(element) {
- var css, tmp, computedCSSElement;
- computedCSSElement = function (el) {
- var compCSS;
- compCSS = function (el) {
- if (document.defaultView && document.defaultView.getComputedStyle) {
- return document.defaultView.getComputedStyle(el, null);
- } else if (el.currentStyle) {
- return el.currentStyle;
- } else {
- return el.style;
- }
- }(el);
- return function (prop) {
- prop = prop.replace(/-\D/g, function (match) {
- return match.charAt(1).toUpperCase();
- });
- return compCSS[prop];
- };
- }(element);
- css = {};
- tmp = void 0;
- css["font-family"] = ResolveFont(computedCSSElement("font-family")) || "times";
- css["font-style"] = FontStyleMap[computedCSSElement("font-style")] || "normal";
- css["text-align"] = TextAlignMap[computedCSSElement("text-align")] || "left";
- tmp = FontWeightMap[computedCSSElement("font-weight")] || "normal";
- if (tmp === "bold") {
- if (css["font-style"] === "normal") {
- css["font-style"] = tmp;
- } else {
- css["font-style"] = tmp + css["font-style"];
- }
- }
- css["font-size"] = ResolveUnitedNumber(computedCSSElement("font-size")) || 1;
- css["line-height"] = ResolveUnitedNumber(computedCSSElement("line-height")) || 1;
- css["display"] = computedCSSElement("display") === "inline" ? "inline" : "block";
-
- tmp = css["display"] === "block";
- css["margin-top"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-top")) || 0;
- css["margin-bottom"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-bottom")) || 0;
- css["padding-top"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-top")) || 0;
- css["padding-bottom"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-bottom")) || 0;
- css["margin-left"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-left")) || 0;
- css["margin-right"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-right")) || 0;
- css["padding-left"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-left")) || 0;
- css["padding-right"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-right")) || 0;
-
- css["page-break-before"] = computedCSSElement("page-break-before") || "auto";
-
- //float and clearing of floats
- css["float"] = FloatMap[computedCSSElement("cssFloat")] || "none";
- css["clear"] = ClearMap[computedCSSElement("clear")] || "none";
-
- css["color"] = computedCSSElement("color");
-
- return css;
- };
- elementHandledElsewhere = function elementHandledElsewhere(element, renderer, elementHandlers) {
- var handlers, i, isHandledElsewhere, l, t;
- isHandledElsewhere = false;
- i = void 0;
- l = void 0;
- t = void 0;
- handlers = elementHandlers["#" + element.id];
- if (handlers) {
- if (typeof handlers === "function") {
- isHandledElsewhere = handlers(element, renderer);
- } else {
- i = 0;
- l = handlers.length;
- while (!isHandledElsewhere && i !== l) {
- isHandledElsewhere = handlers[i](element, renderer);
- i++;
- }
- }
- }
- handlers = elementHandlers[element.nodeName];
- if (!isHandledElsewhere && handlers) {
- if (typeof handlers === "function") {
- isHandledElsewhere = handlers(element, renderer);
- } else {
- i = 0;
- l = handlers.length;
- while (!isHandledElsewhere && i !== l) {
- isHandledElsewhere = handlers[i](element, renderer);
- i++;
- }
- }
- }
- return isHandledElsewhere;
- };
- tableToJson = function tableToJson(table, renderer) {
- var data, headers, i, j, rowData, tableRow, table_obj, table_with, cell, l;
- data = [];
- headers = [];
- i = 0;
- l = table.rows[0].cells.length;
- table_with = table.clientWidth;
- while (i < l) {
- cell = table.rows[0].cells[i];
- headers[i] = {
- name: cell.textContent.toLowerCase().replace(/\s+/g, ''),
- prompt: cell.textContent.replace(/\r?\n/g, ''),
- width: cell.clientWidth / table_with * renderer.pdf.internal.pageSize.width
- };
- i++;
- }
- i = 1;
- while (i < table.rows.length) {
- tableRow = table.rows[i];
- rowData = {};
- j = 0;
- while (j < tableRow.cells.length) {
- rowData[headers[j].name] = tableRow.cells[j].textContent.replace(/\r?\n/g, '');
- j++;
- }
- data.push(rowData);
- i++;
- }
- return table_obj = {
- rows: data,
- headers: headers
- };
- };
- var SkipNode = {
- SCRIPT: 1,
- STYLE: 1,
- NOSCRIPT: 1,
- OBJECT: 1,
- EMBED: 1,
- SELECT: 1
- };
- var listCount = 1;
- _DrillForContent = function DrillForContent(element, renderer, elementHandlers) {
- var cn, cns, fragmentCSS, i, isBlock, l, px2pt, table2json, cb;
- cns = element.childNodes;
- cn = void 0;
- fragmentCSS = GetCSS(element);
- isBlock = fragmentCSS.display === "block";
- if (isBlock) {
- renderer.setBlockBoundary();
- renderer.setBlockStyle(fragmentCSS);
- }
- px2pt = 0.264583 * 72 / 25.4;
- i = 0;
- l = cns.length;
- while (i < l) {
- cn = cns[i];
- if ((typeof cn === "undefined" ? "undefined" : babelHelpers.typeof(cn)) === "object") {
-
- //execute all watcher functions to e.g. reset floating
- renderer.executeWatchFunctions(cn);
-
- /*** HEADER rendering **/
- if (cn.nodeType === 1 && cn.nodeName === 'HEADER') {
- var header = cn;
- //store old top margin
- var oldMarginTop = renderer.pdf.margins_doc.top;
- //subscribe for new page event and render header first on every page
- renderer.pdf.internal.events.subscribe('addPage', function (pageInfo) {
- //set current y position to old margin
- renderer.y = oldMarginTop;
- //render all child nodes of the header element
- _DrillForContent(header, renderer, elementHandlers);
- //set margin to old margin + rendered header + 10 space to prevent overlapping
- //important for other plugins (e.g. table) to start rendering at correct position after header
- renderer.pdf.margins_doc.top = renderer.y + 10;
- renderer.y += 10;
- }, false);
- }
-
- if (cn.nodeType === 8 && cn.nodeName === "#comment") {
- if (~cn.textContent.indexOf("ADD_PAGE")) {
- renderer.pdf.addPage();
- renderer.y = renderer.pdf.margins_doc.top;
- }
- } else if (cn.nodeType === 1 && !SkipNode[cn.nodeName]) {
- /*** IMAGE RENDERING ***/
- var cached_image;
- if (cn.nodeName === "IMG") {
- var url = cn.getAttribute("src");
- cached_image = images[renderer.pdf.sHashCode(url) || url];
- }
- if (cached_image) {
- if (renderer.pdf.internal.pageSize.height - renderer.pdf.margins_doc.bottom < renderer.y + cn.height && renderer.y > renderer.pdf.margins_doc.top) {
- renderer.pdf.addPage();
- renderer.y = renderer.pdf.margins_doc.top;
- //check if we have to set back some values due to e.g. header rendering for new page
- renderer.executeWatchFunctions(cn);
- }
-
- var imagesCSS = GetCSS(cn);
- var imageX = renderer.x;
- var fontToUnitRatio = 12 / renderer.pdf.internal.scaleFactor;
-
- //define additional paddings, margins which have to be taken into account for margin calculations
- var additionalSpaceLeft = (imagesCSS["margin-left"] + imagesCSS["padding-left"]) * fontToUnitRatio;
- var additionalSpaceRight = (imagesCSS["margin-right"] + imagesCSS["padding-right"]) * fontToUnitRatio;
- var additionalSpaceTop = (imagesCSS["margin-top"] + imagesCSS["padding-top"]) * fontToUnitRatio;
- var additionalSpaceBottom = (imagesCSS["margin-bottom"] + imagesCSS["padding-bottom"]) * fontToUnitRatio;
-
- //if float is set to right, move the image to the right border
- //add space if margin is set
- if (imagesCSS['float'] !== undefined && imagesCSS['float'] === 'right') {
- imageX += renderer.settings.width - cn.width - additionalSpaceRight;
- } else {
- imageX += additionalSpaceLeft;
- }
-
- renderer.pdf.addImage(cached_image, imageX, renderer.y + additionalSpaceTop, cn.width, cn.height);
- cached_image = undefined;
- //if the float prop is specified we have to float the text around the image
- if (imagesCSS['float'] === 'right' || imagesCSS['float'] === 'left') {
- //add functiont to set back coordinates after image rendering
- renderer.watchFunctions.push(function (diffX, thresholdY, diffWidth, el) {
- //undo drawing box adaptions which were set by floating
- if (renderer.y >= thresholdY) {
- renderer.x += diffX;
- renderer.settings.width += diffWidth;
- return true;
- } else if (el && el.nodeType === 1 && !SkipNode[el.nodeName] && renderer.x + el.width > renderer.pdf.margins_doc.left + renderer.pdf.margins_doc.width) {
- renderer.x += diffX;
- renderer.y = thresholdY;
- renderer.settings.width += diffWidth;
- return true;
- } else {
- return false;
- }
- }.bind(this, imagesCSS['float'] === 'left' ? -cn.width - additionalSpaceLeft - additionalSpaceRight : 0, renderer.y + cn.height + additionalSpaceTop + additionalSpaceBottom, cn.width));
- //reset floating by clear:both divs
- //just set cursorY after the floating element
- renderer.watchFunctions.push(function (yPositionAfterFloating, pages, el) {
- if (renderer.y < yPositionAfterFloating && pages === renderer.pdf.internal.getNumberOfPages()) {
- if (el.nodeType === 1 && GetCSS(el).clear === 'both') {
- renderer.y = yPositionAfterFloating;
- return true;
- } else {
- return false;
- }
- } else {
- return true;
- }
- }.bind(this, renderer.y + cn.height, renderer.pdf.internal.getNumberOfPages()));
-
- //if floating is set we decrease the available width by the image width
- renderer.settings.width -= cn.width + additionalSpaceLeft + additionalSpaceRight;
- //if left just add the image width to the X coordinate
- if (imagesCSS['float'] === 'left') {
- renderer.x += cn.width + additionalSpaceLeft + additionalSpaceRight;
- }
- } else {
- //if no floating is set, move the rendering cursor after the image height
- renderer.y += cn.height + additionalSpaceTop + additionalSpaceBottom;
- }
-
- /*** TABLE RENDERING ***/
- } else if (cn.nodeName === "TABLE") {
- table2json = tableToJson(cn, renderer);
- renderer.y += 10;
- renderer.pdf.table(renderer.x, renderer.y, table2json.rows, table2json.headers, {
- autoSize: false,
- printHeaders: elementHandlers.printHeaders,
- margins: renderer.pdf.margins_doc,
- css: GetCSS(cn)
- });
- renderer.y = renderer.pdf.lastCellPos.y + renderer.pdf.lastCellPos.h + 20;
- } else if (cn.nodeName === "OL" || cn.nodeName === "UL") {
- listCount = 1;
- if (!elementHandledElsewhere(cn, renderer, elementHandlers)) {
- _DrillForContent(cn, renderer, elementHandlers);
- }
- renderer.y += 10;
- } else if (cn.nodeName === "LI") {
- var temp = renderer.x;
- renderer.x += 20 / renderer.pdf.internal.scaleFactor;
- renderer.y += 3;
- if (!elementHandledElsewhere(cn, renderer, elementHandlers)) {
- _DrillForContent(cn, renderer, elementHandlers);
- }
- renderer.x = temp;
- } else if (cn.nodeName === "BR") {
- renderer.y += fragmentCSS["font-size"] * renderer.pdf.internal.scaleFactor;
- renderer.addText("\u2028", clone(fragmentCSS));
- } else {
- if (!elementHandledElsewhere(cn, renderer, elementHandlers)) {
- _DrillForContent(cn, renderer, elementHandlers);
- }
- }
- } else if (cn.nodeType === 3) {
- var value = cn.nodeValue;
- if (cn.nodeValue && cn.parentNode.nodeName === "LI") {
- if (cn.parentNode.parentNode.nodeName === "OL") {
- value = listCount++ + '. ' + value;
- } else {
- var fontSize = fragmentCSS["font-size"];
- offsetX = (3 - fontSize * 0.75) * renderer.pdf.internal.scaleFactor;
- offsetY = fontSize * 0.75 * renderer.pdf.internal.scaleFactor;
- radius = fontSize * 1.74 / renderer.pdf.internal.scaleFactor;
- cb = function cb(x, y) {
- this.pdf.circle(x + offsetX, y + offsetY, radius, 'FD');
- };
- }
- }
- // Only add the text if the text node is in the body element
- if (cn.ownerDocument.body.contains(cn)) {
- renderer.addText(value, fragmentCSS);
- }
- } else if (typeof cn === "string") {
- renderer.addText(cn, fragmentCSS);
- }
- }
- i++;
- }
- elementHandlers.outY = renderer.y;
-
- if (isBlock) {
- return renderer.setBlockBoundary(cb);
- }
- };
- images = {};
- loadImgs = function loadImgs(element, renderer, elementHandlers, cb) {
- var imgs = element.getElementsByTagName('img'),
- l = imgs.length,
- found_images,
- x = 0;
- function done() {
- renderer.pdf.internal.events.publish('imagesLoaded');
- cb(found_images);
- }
- function loadImage(url, width, height) {
- if (!url) return;
- var img = new Image();
- found_images = ++x;
- img.crossOrigin = '';
- img.onerror = img.onload = function () {
- if (img.complete) {
- //to support data urls in images, set width and height
- //as those values are not recognized automatically
- if (img.src.indexOf('data:image/') === 0) {
- img.width = width || img.width || 0;
- img.height = height || img.height || 0;
- }
- //if valid image add to known images array
- if (img.width + img.height) {
- var hash = renderer.pdf.sHashCode(url) || url;
- images[hash] = images[hash] || img;
- }
- }
- if (! --x) {
- done();
- }
- };
- img.src = url;
- }
- while (l--) {
- loadImage(imgs[l].getAttribute("src"), imgs[l].width, imgs[l].height);
- }return x || done();
- };
- checkForFooter = function checkForFooter(elem, renderer, elementHandlers) {
- //check if we can found a element
- var footer = elem.getElementsByTagName("footer");
- if (footer.length > 0) {
-
- footer = footer[0];
-
- //bad hack to get height of footer
- //creat dummy out and check new y after fake rendering
- var oldOut = renderer.pdf.internal.write;
- var oldY = renderer.y;
- renderer.pdf.internal.write = function () {};
- _DrillForContent(footer, renderer, elementHandlers);
- var footerHeight = Math.ceil(renderer.y - oldY) + 5;
- renderer.y = oldY;
- renderer.pdf.internal.write = oldOut;
-
- //add 20% to prevent overlapping
- renderer.pdf.margins_doc.bottom += footerHeight;
-
- //Create function render header on every page
- var renderFooter = function renderFooter(pageInfo) {
- var pageNumber = pageInfo !== undefined ? pageInfo.pageNumber : 1;
- //set current y position to old margin
- var oldPosition = renderer.y;
- //render all child nodes of the header element
- renderer.y = renderer.pdf.internal.pageSize.height - renderer.pdf.margins_doc.bottom;
- renderer.pdf.margins_doc.bottom -= footerHeight;
-
- //check if we have to add page numbers
- var spans = footer.getElementsByTagName('span');
- for (var i = 0; i < spans.length; ++i) {
- //if we find some span element with class pageCounter, set the page
- if ((" " + spans[i].className + " ").replace(/[\n\t]/g, " ").indexOf(" pageCounter ") > -1) {
- spans[i].innerHTML = pageNumber;
- }
- //if we find some span element with class totalPages, set a variable which is replaced after rendering of all pages
- if ((" " + spans[i].className + " ").replace(/[\n\t]/g, " ").indexOf(" totalPages ") > -1) {
- spans[i].innerHTML = '###jsPDFVarTotalPages###';
- }
- }
-
- //render footer content
- _DrillForContent(footer, renderer, elementHandlers);
- //set bottom margin to previous height including the footer height
- renderer.pdf.margins_doc.bottom += footerHeight;
- //important for other plugins (e.g. table) to start rendering at correct position after header
- renderer.y = oldPosition;
- };
-
- //check if footer contains totalPages which shoudl be replace at the disoposal of the document
- var spans = footer.getElementsByTagName('span');
- for (var i = 0; i < spans.length; ++i) {
- if ((" " + spans[i].className + " ").replace(/[\n\t]/g, " ").indexOf(" totalPages ") > -1) {
- renderer.pdf.internal.events.subscribe('htmlRenderingFinished', renderer.pdf.putTotalPages.bind(renderer.pdf, '###jsPDFVarTotalPages###'), true);
- }
- }
-
- //register event to render footer on every new page
- renderer.pdf.internal.events.subscribe('addPage', renderFooter, false);
- //render footer on first page
- renderFooter();
-
- //prevent footer rendering
- SkipNode['FOOTER'] = 1;
- }
- };
- process = function process(pdf, element, x, y, settings, callback) {
- if (!element) return false;
- if (typeof element !== "string" && !element.parentNode) element = '' + element.innerHTML;
- if (typeof element === "string") {
- element = function (element) {
- var $frame, $hiddendiv, framename, visuallyhidden;
- framename = "jsPDFhtmlText" + Date.now().toString() + (Math.random() * 1000).toFixed(0);
- visuallyhidden = "position: absolute !important;" + "clip: rect(1px 1px 1px 1px); /* IE6, IE7 */" + "clip: rect(1px, 1px, 1px, 1px);" + "padding:0 !important;" + "border:0 !important;" + "height: 1px !important;" + "width: 1px !important; " + "top:auto;" + "left:-100px;" + "overflow: hidden;";
- $hiddendiv = document.createElement('div');
- $hiddendiv.style.cssText = visuallyhidden;
- $hiddendiv.innerHTML = "";
- document.body.appendChild($hiddendiv);
- $frame = window.frames[framename];
- $frame.document.open();
- $frame.document.writeln(element);
- $frame.document.close();
- return $frame.document.body;
- }(element.replace(/<\/?script[^>]*?>/gi, ''));
- }
- var r = new Renderer(pdf, x, y, settings),
- out;
-
- // 1. load images
- // 2. prepare optional footer elements
- // 3. render content
- loadImgs.call(this, element, r, settings.elementHandlers, function (found_images) {
- checkForFooter(element, r, settings.elementHandlers);
- _DrillForContent(element, r, settings.elementHandlers);
- //send event dispose for final taks (e.g. footer totalpage replacement)
- r.pdf.internal.events.publish('htmlRenderingFinished');
- out = r.dispose();
- if (typeof callback === 'function') callback(out);else if (found_images) console.error('jsPDF Warning: rendering issues? provide a callback to fromHTML!');
- });
- return out || { x: r.x, y: r.y };
- };
- Renderer.prototype.init = function () {
- this.paragraph = {
- text: [],
- style: []
- };
- return this.pdf.internal.write("q");
- };
- Renderer.prototype.dispose = function () {
- this.pdf.internal.write("Q");
- return {
- x: this.x,
- y: this.y,
- ready: true
- };
- };
-
- //Checks if we have to execute some watcher functions
- //e.g. to end text floating around an image
- Renderer.prototype.executeWatchFunctions = function (el) {
- var ret = false;
- var narray = [];
- if (this.watchFunctions.length > 0) {
- for (var i = 0; i < this.watchFunctions.length; ++i) {
- if (this.watchFunctions[i](el) === true) {
- ret = true;
- } else {
- narray.push(this.watchFunctions[i]);
- }
- }
- this.watchFunctions = narray;
- }
- return ret;
- };
-
- Renderer.prototype.splitFragmentsIntoLines = function (fragments, styles) {
- var currentLineLength, defaultFontSize, ff, fontMetrics, fontMetricsCache, fragment, fragmentChopped, fragmentLength, fragmentSpecificMetrics, fs, k, line, lines, maxLineLength, style;
- defaultFontSize = 12;
- k = this.pdf.internal.scaleFactor;
- fontMetricsCache = {};
- ff = void 0;
- fs = void 0;
- fontMetrics = void 0;
- fragment = void 0;
- style = void 0;
- fragmentSpecificMetrics = void 0;
- fragmentLength = void 0;
- fragmentChopped = void 0;
- line = [];
- lines = [line];
- currentLineLength = 0;
- maxLineLength = this.settings.width;
- while (fragments.length) {
- fragment = fragments.shift();
- style = styles.shift();
- if (fragment) {
- ff = style["font-family"];
- fs = style["font-style"];
- fontMetrics = fontMetricsCache[ff + fs];
- if (!fontMetrics) {
- fontMetrics = this.pdf.internal.getFont(ff, fs).metadata.Unicode;
- fontMetricsCache[ff + fs] = fontMetrics;
- }
- fragmentSpecificMetrics = {
- widths: fontMetrics.widths,
- kerning: fontMetrics.kerning,
- fontSize: style["font-size"] * defaultFontSize,
- textIndent: currentLineLength
- };
- fragmentLength = this.pdf.getStringUnitWidth(fragment, fragmentSpecificMetrics) * fragmentSpecificMetrics.fontSize / k;
- if (fragment == "\u2028") {
- line = [];
- lines.push(line);
- } else if (currentLineLength + fragmentLength > maxLineLength) {
- fragmentChopped = this.pdf.splitTextToSize(fragment, maxLineLength, fragmentSpecificMetrics);
- line.push([fragmentChopped.shift(), style]);
- while (fragmentChopped.length) {
- line = [[fragmentChopped.shift(), style]];
- lines.push(line);
- }
- currentLineLength = this.pdf.getStringUnitWidth(line[0][0], fragmentSpecificMetrics) * fragmentSpecificMetrics.fontSize / k;
- } else {
- line.push([fragment, style]);
- currentLineLength += fragmentLength;
- }
- }
- }
-
- //if text alignment was set, set margin/indent of each line
- if (style['text-align'] !== undefined && (style['text-align'] === 'center' || style['text-align'] === 'right' || style['text-align'] === 'justify')) {
- for (var i = 0; i < lines.length; ++i) {
- var length = this.pdf.getStringUnitWidth(lines[i][0][0], fragmentSpecificMetrics) * fragmentSpecificMetrics.fontSize / k;
- //if there is more than on line we have to clone the style object as all lines hold a reference on this object
- if (i > 0) {
- lines[i][0][1] = clone(lines[i][0][1]);
- }
- var space = maxLineLength - length;
-
- if (style['text-align'] === 'right') {
- lines[i][0][1]['margin-left'] = space;
- //if alignment is not right, it has to be center so split the space to the left and the right
- } else if (style['text-align'] === 'center') {
- lines[i][0][1]['margin-left'] = space / 2;
- //if justify was set, calculate the word spacing and define in by using the css property
- } else if (style['text-align'] === 'justify') {
- var countSpaces = lines[i][0][0].split(' ').length - 1;
- lines[i][0][1]['word-spacing'] = space / countSpaces;
- //ignore the last line in justify mode
- if (i === lines.length - 1) {
- lines[i][0][1]['word-spacing'] = 0;
- }
- }
- }
- }
-
- return lines;
- };
- Renderer.prototype.RenderTextFragment = function (text, style) {
- var defaultFontSize, font, maxLineHeight;
-
- maxLineHeight = 0;
- defaultFontSize = 12;
-
- if (this.pdf.internal.pageSize.height - this.pdf.margins_doc.bottom < this.y + this.pdf.internal.getFontSize()) {
- this.pdf.internal.write("ET", "Q");
- this.pdf.addPage();
- this.y = this.pdf.margins_doc.top;
- this.pdf.internal.write("q", "BT 0 g", this.pdf.internal.getCoordinateString(this.x), this.pdf.internal.getVerticalCoordinateString(this.y), style.color, "Td");
- //move cursor by one line on new page
- maxLineHeight = Math.max(maxLineHeight, style["line-height"], style["font-size"]);
- this.pdf.internal.write(0, (-1 * defaultFontSize * maxLineHeight).toFixed(2), "Td");
- }
-
- font = this.pdf.internal.getFont(style["font-family"], style["font-style"]);
-
- // text color
- var pdfTextColor = this.getPdfColor(style["color"]);
- if (pdfTextColor !== this.lastTextColor) {
- this.pdf.internal.write(pdfTextColor);
- this.lastTextColor = pdfTextColor;
- }
-
- //set the word spacing for e.g. justify style
- if (style['word-spacing'] !== undefined && style['word-spacing'] > 0) {
- this.pdf.internal.write(style['word-spacing'].toFixed(2), "Tw");
- }
-
- this.pdf.internal.write("/" + font.id, (defaultFontSize * style["font-size"]).toFixed(2), "Tf", "(" + this.pdf.internal.pdfEscape(text) + ") Tj");
-
- //set the word spacing back to neutral => 0
- if (style['word-spacing'] !== undefined) {
- this.pdf.internal.write(0, "Tw");
- }
- };
-
- // Accepts #FFFFFF, rgb(int,int,int), or CSS Color Name
- Renderer.prototype.getPdfColor = function (style) {
- var textColor;
- var r, g, b;
-
- var rx = /rgb\s*\(\s*(\d+),\s*(\d+),\s*(\d+\s*)\)/;
- var m = rx.exec(style);
- if (m != null) {
- r = parseInt(m[1]);
- g = parseInt(m[2]);
- b = parseInt(m[3]);
- } else {
- if (style.charAt(0) != '#') {
- style = CssColors.colorNameToHex(style);
- if (!style) {
- style = '#000000';
- }
- }
- r = style.substring(1, 3);
- r = parseInt(r, 16);
- g = style.substring(3, 5);
- g = parseInt(g, 16);
- b = style.substring(5, 7);
- b = parseInt(b, 16);
- }
-
- if (typeof r === 'string' && /^#[0-9A-Fa-f]{6}$/.test(r)) {
- var hex = parseInt(r.substr(1), 16);
- r = hex >> 16 & 255;
- g = hex >> 8 & 255;
- b = hex & 255;
- }
-
- var f3 = this.f3;
- if (r === 0 && g === 0 && b === 0 || typeof g === 'undefined') {
- textColor = f3(r / 255) + ' g';
- } else {
- textColor = [f3(r / 255), f3(g / 255), f3(b / 255), 'rg'].join(' ');
- }
- return textColor;
- };
-
- Renderer.prototype.f3 = function (number) {
- return number.toFixed(3); // Ie, %.3f
- }, Renderer.prototype.renderParagraph = function (cb) {
- var blockstyle, defaultFontSize, fontToUnitRatio, fragments, i, l, line, lines, maxLineHeight, out, paragraphspacing_after, paragraphspacing_before, priorblockstyle, styles, fontSize;
- fragments = PurgeWhiteSpace(this.paragraph.text);
- styles = this.paragraph.style;
- blockstyle = this.paragraph.blockstyle;
- priorblockstyle = this.paragraph.priorblockstyle || {};
- this.paragraph = {
- text: [],
- style: [],
- blockstyle: {},
- priorblockstyle: blockstyle
- };
- if (!fragments.join("").trim()) {
- return;
- }
- lines = this.splitFragmentsIntoLines(fragments, styles);
- line = void 0;
- maxLineHeight = void 0;
- defaultFontSize = 12;
- fontToUnitRatio = defaultFontSize / this.pdf.internal.scaleFactor;
- this.priorMarginBottom = this.priorMarginBottom || 0;
- paragraphspacing_before = (Math.max((blockstyle["margin-top"] || 0) - this.priorMarginBottom, 0) + (blockstyle["padding-top"] || 0)) * fontToUnitRatio;
- paragraphspacing_after = ((blockstyle["margin-bottom"] || 0) + (blockstyle["padding-bottom"] || 0)) * fontToUnitRatio;
- this.priorMarginBottom = blockstyle["margin-bottom"] || 0;
-
- if (blockstyle['page-break-before'] === 'always') {
- this.pdf.addPage();
- this.y = 0;
- paragraphspacing_before = ((blockstyle["margin-top"] || 0) + (blockstyle["padding-top"] || 0)) * fontToUnitRatio;
- }
-
- out = this.pdf.internal.write;
- i = void 0;
- l = void 0;
- this.y += paragraphspacing_before;
- out("q", "BT 0 g", this.pdf.internal.getCoordinateString(this.x), this.pdf.internal.getVerticalCoordinateString(this.y), "Td");
-
- //stores the current indent of cursor position
- var currentIndent = 0;
-
- while (lines.length) {
- line = lines.shift();
- maxLineHeight = 0;
- i = 0;
- l = line.length;
- while (i !== l) {
- if (line[i][0].trim()) {
- maxLineHeight = Math.max(maxLineHeight, line[i][1]["line-height"], line[i][1]["font-size"]);
- fontSize = line[i][1]["font-size"] * 7;
- }
- i++;
- }
- //if we have to move the cursor to adapt the indent
- var indentMove = 0;
- var wantedIndent = 0;
- //if a margin was added (by e.g. a text-alignment), move the cursor
- if (line[0][1]["margin-left"] !== undefined && line[0][1]["margin-left"] > 0) {
- wantedIndent = this.pdf.internal.getCoordinateString(line[0][1]["margin-left"]);
- indentMove = wantedIndent - currentIndent;
- currentIndent = wantedIndent;
- }
- var indentMore = Math.max(blockstyle["margin-left"] || 0, 0) * fontToUnitRatio;
- //move the cursor
- out(indentMove + indentMore, (-1 * defaultFontSize * maxLineHeight).toFixed(2), "Td");
- i = 0;
- l = line.length;
- while (i !== l) {
- if (line[i][0]) {
- this.RenderTextFragment(line[i][0], line[i][1]);
- }
- i++;
- }
- this.y += maxLineHeight * fontToUnitRatio;
-
- //if some watcher function was executed sucessful, so e.g. margin and widths were changed,
- //reset line drawing and calculate position and lines again
- //e.g. to stop text floating around an image
- if (this.executeWatchFunctions(line[0][1]) && lines.length > 0) {
- var localFragments = [];
- var localStyles = [];
- //create fragement array of
- lines.forEach(function (localLine) {
- var i = 0;
- var l = localLine.length;
- while (i !== l) {
- if (localLine[i][0]) {
- localFragments.push(localLine[i][0] + ' ');
- localStyles.push(localLine[i][1]);
- }
- ++i;
- }
- });
- //split lines again due to possible coordinate changes
- lines = this.splitFragmentsIntoLines(PurgeWhiteSpace(localFragments), localStyles);
- //reposition the current cursor
- out("ET", "Q");
- out("q", "BT 0 g", this.pdf.internal.getCoordinateString(this.x), this.pdf.internal.getVerticalCoordinateString(this.y), "Td");
- }
- }
- if (cb && typeof cb === "function") {
- cb.call(this, this.x - 9, this.y - fontSize / 2);
- }
- out("ET", "Q");
- return this.y += paragraphspacing_after;
- };
- Renderer.prototype.setBlockBoundary = function (cb) {
- return this.renderParagraph(cb);
- };
- Renderer.prototype.setBlockStyle = function (css) {
- return this.paragraph.blockstyle = css;
- };
- Renderer.prototype.addText = function (text, css) {
- this.paragraph.text.push(text);
- return this.paragraph.style.push(css);
- };
- FontNameDB = {
- helvetica: "helvetica",
- "sans-serif": "helvetica",
- "times new roman": "times",
- serif: "times",
- times: "times",
- monospace: "courier",
- courier: "courier"
- };
- FontWeightMap = {
- 100: "normal",
- 200: "normal",
- 300: "normal",
- 400: "normal",
- 500: "bold",
- 600: "bold",
- 700: "bold",
- 800: "bold",
- 900: "bold",
- normal: "normal",
- bold: "bold",
- bolder: "bold",
- lighter: "normal"
- };
- FontStyleMap = {
- normal: "normal",
- italic: "italic",
- oblique: "italic"
- };
- TextAlignMap = {
- left: "left",
- right: "right",
- center: "center",
- justify: "justify"
- };
- FloatMap = {
- none: 'none',
- right: 'right',
- left: 'left'
- };
- ClearMap = {
- none: 'none',
- both: 'both'
- };
- UnitedNumberMap = {
- normal: 1
- };
- /**
- * Converts HTML-formatted text into formatted PDF text.
- *
- * Notes:
- * 2012-07-18
- * Plugin relies on having browser, DOM around. The HTML is pushed into dom and traversed.
- * Plugin relies on jQuery for CSS extraction.
- * Targeting HTML output from Markdown templating, which is a very simple
- * markup - div, span, em, strong, p. No br-based paragraph separation supported explicitly (but still may work.)
- * Images, tables are NOT supported.
- *
- * @public
- * @function
- * @param HTML {String or DOM Element} HTML-formatted text, or pointer to DOM element that is to be rendered into PDF.
- * @param x {Number} starting X coordinate in jsPDF instance's declared units.
- * @param y {Number} starting Y coordinate in jsPDF instance's declared units.
- * @param settings {Object} Additional / optional variables controlling parsing, rendering.
- * @returns {Object} jsPDF instance
- */
- jsPDFAPI.fromHTML = function (HTML, x, y, settings, callback, margins) {
- "use strict";
-
- this.margins_doc = margins || {
- top: 0,
- bottom: 0
- };
- if (!settings) settings = {};
- if (!settings.elementHandlers) settings.elementHandlers = {};
-
- return process(this, HTML, isNaN(x) ? 4 : x, isNaN(y) ? 4 : y, settings, callback);
- };
- })(jsPDF.API);
-
- /** ====================================================================
- * jsPDF JavaScript plugin
- * Copyright (c) 2013 Youssef Beddad, youssef.beddad@gmail.com
- *
- *
- * ====================================================================
- */
+ this.rawData = rawData;
+ data = this.contents = new Data(rawData);
+ this.contents.pos = 4;
- /*global jsPDF */
+ if (data.readString(4) === "ttcf") {
+ if (!name) {
+ throw new Error("Must specify a font name for TTC files.");
+ }
+ throw new Error("Font " + name + " not found in TTC file.");
+ } else {
+ data.pos = 0;
+ this.parse();
+ this.subset = new Subset(this);
+ this.registerTTF();
+ }
+ }
+ /********************************************************/
+
+ /* function : parse */
+
+ /* comment : TTF Parses the file contents by each table.*/
+
+ /********************************************************/
+
+
+ TTFFont.prototype.parse = function () {
+ this.directory = new Directory(this.contents);
+ this.head = new HeadTable(this);
+ this.name = new NameTable(this);
+ this.cmap = new CmapTable(this);
+ this.toUnicode = new Map();
+ this.hhea = new HheaTable(this);
+ this.maxp = new MaxpTable(this);
+ this.hmtx = new HmtxTable(this);
+ this.post = new PostTable(this);
+ this.os2 = new OS2Table(this);
+ this.loca = new LocaTable(this);
+ this.glyf = new GlyfTable(this);
+ this.ascender = this.os2.exists && this.os2.ascender || this.hhea.ascender;
+ this.decender = this.os2.exists && this.os2.decender || this.hhea.decender;
+ this.lineGap = this.os2.exists && this.os2.lineGap || this.hhea.lineGap;
+ return this.bbox = [this.head.xMin, this.head.yMin, this.head.xMax, this.head.yMax];
+ };
+ /***************************************************************/
- (function (jsPDFAPI) {
- 'use strict';
+ /* function : registerTTF */
- var jsNamesObj, jsJsObj, text;
- jsPDFAPI.addJS = function (txt) {
- text = txt;
- this.internal.events.subscribe('postPutResources', function (txt) {
- jsNamesObj = this.internal.newObject();
- this.internal.write('<< /Names [(EmbeddedJS) ' + (jsNamesObj + 1) + ' 0 R] >>', 'endobj');
- jsJsObj = this.internal.newObject();
- this.internal.write('<< /S /JavaScript /JS (', text, ') >>', 'endobj');
- });
- this.internal.events.subscribe('putCatalog', function () {
- if (jsNamesObj !== undefined && jsJsObj !== undefined) {
- this.internal.write('/Names <>');
- }
- });
- return this;
- };
- })(jsPDF.API);
+ /* comment : Get the value to assign pdf font descriptors. */
- /**
- * jsPDF Outline PlugIn
- * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv
- *
- * Licensed under the MIT License.
- * http://opensource.org/licenses/mit-license
- */
+ /***************************************************************/
- /**
- * Generates a PDF Outline
- */
- ;
- (function (jsPDFAPI) {
- 'use strict';
-
- jsPDFAPI.events.push(['postPutResources', function () {
- var pdf = this;
- var rx = /^(\d+) 0 obj$/;
-
- // Write action goto objects for each page
- // this.outline.destsGoto = [];
- // for (var i = 0; i < totalPages; i++) {
- // var id = pdf.internal.newObject();
- // this.outline.destsGoto.push(id);
- // pdf.internal.write("<> endobj");
- // }
- //
- // for (var i = 0; i < dests.length; i++) {
- // pdf.internal.write("(page_" + (i + 1) + ")" + dests[i] + " 0
- // R");
- // }
- //
- if (this.outline.root.children.length > 0) {
- var lines = pdf.outline.render().split(/\r\n/);
- for (var i = 0; i < lines.length; i++) {
- var line = lines[i];
- var m = rx.exec(line);
- if (m != null) {
- var oid = m[1];
- pdf.internal.newObjectDeferredBegin(oid);
- }
- pdf.internal.write(line);
- }
- }
-
- // This code will write named destination for each page reference
- // (page_1, etc)
- if (this.outline.createNamedDestinations) {
- var totalPages = this.internal.pages.length;
- // WARNING: this assumes jsPDF starts on page 3 and pageIDs
- // follow 5, 7, 9, etc
- // Write destination objects for each page
- var dests = [];
- for (var i = 0; i < totalPages; i++) {
- var id = pdf.internal.newObject();
- dests.push(id);
- var info = pdf.internal.getPageInfo(i + 1);
- pdf.internal.write("<< /D[" + info.objId + " 0 R /XYZ null null null]>> endobj");
- }
-
- // assign a name for each destination
- var names2Oid = pdf.internal.newObject();
- pdf.internal.write('<< /Names [ ');
- for (var i = 0; i < dests.length; i++) {
- pdf.internal.write("(page_" + (i + 1) + ")" + dests[i] + " 0 R");
- }
- pdf.internal.write(' ] >>', 'endobj');
-
- // var kids = pdf.internal.newObject();
- // pdf.internal.write('<< /Kids [ ' + names2Oid + ' 0 R');
- // pdf.internal.write(' ] >>', 'endobj');
-
- var namesOid = pdf.internal.newObject();
- pdf.internal.write('<< /Dests ' + names2Oid + " 0 R");
- pdf.internal.write('>>', 'endobj');
- }
- }]);
-
- jsPDFAPI.events.push(['putCatalog', function () {
- var pdf = this;
- if (pdf.outline.root.children.length > 0) {
- pdf.internal.write("/Outlines", this.outline.makeRef(this.outline.root));
- if (this.outline.createNamedDestinations) {
- pdf.internal.write("/Names " + namesOid + " 0 R");
- }
- // Open with Bookmarks showing
- // pdf.internal.write("/PageMode /UseOutlines");
- }
- }]);
-
- jsPDFAPI.events.push(['initialized', function () {
- var pdf = this;
-
- pdf.outline = {
- createNamedDestinations: false,
- root: {
- children: []
- }
- };
-
- var namesOid;
- var destsGoto = [];
-
- /**
- * Options: pageNumber
- */
- pdf.outline.add = function (parent, title, options) {
- var item = {
- title: title,
- options: options,
- children: []
- };
- if (parent == null) {
- parent = this.root;
- }
- parent.children.push(item);
- return item;
- };
-
- pdf.outline.render = function () {
- this.ctx = {};
- this.ctx.val = '';
- this.ctx.pdf = pdf;
-
- this.genIds_r(this.root);
- this.renderRoot(this.root);
- this.renderItems(this.root);
-
- return this.ctx.val;
- };
-
- pdf.outline.genIds_r = function (node) {
- node.id = pdf.internal.newObjectDeferred();
- for (var i = 0; i < node.children.length; i++) {
- this.genIds_r(node.children[i]);
- }
- };
-
- pdf.outline.renderRoot = function (node) {
- this.objStart(node);
- this.line('/Type /Outlines');
- if (node.children.length > 0) {
- this.line('/First ' + this.makeRef(node.children[0]));
- this.line('/Last ' + this.makeRef(node.children[node.children.length - 1]));
- }
- this.line('/Count ' + this.count_r({
- count: 0
- }, node));
- this.objEnd();
- };
-
- pdf.outline.renderItems = function (node) {
- for (var i = 0; i < node.children.length; i++) {
- var item = node.children[i];
- this.objStart(item);
-
- this.line('/Title ' + this.makeString(item.title));
-
- this.line('/Parent ' + this.makeRef(node));
- if (i > 0) {
- this.line('/Prev ' + this.makeRef(node.children[i - 1]));
- }
- if (i < node.children.length - 1) {
- this.line('/Next ' + this.makeRef(node.children[i + 1]));
- }
- if (item.children.length > 0) {
- this.line('/First ' + this.makeRef(item.children[0]));
- this.line('/Last ' + this.makeRef(item.children[item.children.length - 1]));
- }
-
- var count = this.count = this.count_r({
- count: 0
- }, item);
- if (count > 0) {
- this.line('/Count ' + count);
- }
-
- if (item.options) {
- if (item.options.pageNumber) {
- // Explicit Destination
- //WARNING this assumes page ids are 3,5,7, etc.
- var info = pdf.internal.getPageInfo(item.options.pageNumber);
- this.line('/Dest ' + '[' + info.objId + ' 0 R /XYZ 0 ' + this.ctx.pdf.internal.pageSize.height + ' 0]');
- // this line does not work on all clients (pageNumber instead of page ref)
- //this.line('/Dest ' + '[' + (item.options.pageNumber - 1) + ' /XYZ 0 ' + this.ctx.pdf.internal.pageSize.height + ' 0]');
-
- // Named Destination
- // this.line('/Dest (page_' + (item.options.pageNumber) + ')');
-
- // Action Destination
- // var id = pdf.internal.newObject();
- // pdf.internal.write('<> endobj');
- // this.line('/A ' + id + ' 0 R' );
- }
- }
- this.objEnd();
- }
- for (var i = 0; i < node.children.length; i++) {
- var item = node.children[i];
- this.renderItems(item);
- }
- };
-
- pdf.outline.line = function (text) {
- this.ctx.val += text + '\r\n';
- };
-
- pdf.outline.makeRef = function (node) {
- return node.id + ' 0 R';
- };
-
- pdf.outline.makeString = function (val) {
- return '(' + pdf.internal.pdfEscape(val) + ')';
- };
-
- pdf.outline.objStart = function (node) {
- this.ctx.val += '\r\n' + node.id + ' 0 obj' + '\r\n<<\r\n';
- };
-
- pdf.outline.objEnd = function (node) {
- this.ctx.val += '>> \r\n' + 'endobj' + '\r\n';
- };
-
- pdf.outline.count_r = function (ctx, node) {
- for (var i = 0; i < node.children.length; i++) {
- ctx.count++;
- this.count_r(ctx, node.children[i]);
- }
- return ctx.count;
- };
- }]);
-
- return this;
- })(jsPDF.API);
-
- /**@preserve
- * ====================================================================
- * jsPDF PNG PlugIn
- * Copyright (c) 2014 James Robb, https://github.com/jamesbrobb
- *
- *
- * ====================================================================
- */
- (function (jsPDFAPI) {
- 'use strict';
-
- /*
- * @see http://www.w3.org/TR/PNG-Chunks.html
- *
- Color Allowed Interpretation
- Type Bit Depths
-
- 0 1,2,4,8,16 Each pixel is a grayscale sample.
-
- 2 8,16 Each pixel is an R,G,B triple.
-
- 3 1,2,4,8 Each pixel is a palette index;
- a PLTE chunk must appear.
-
- 4 8,16 Each pixel is a grayscale sample,
- followed by an alpha sample.
-
- 6 8,16 Each pixel is an R,G,B triple,
- followed by an alpha sample.
- */
+ TTFFont.prototype.registerTTF = function () {
+ var e, hi, low, raw, _ref;
- /*
- * PNG filter method types
- *
- * @see http://www.w3.org/TR/PNG-Filters.html
- * @see http://www.libpng.org/pub/png/book/chapter09.html
- *
- * This is what the value 'Predictor' in decode params relates to
- *
- * 15 is "optimal prediction", which means the prediction algorithm can change from line to line.
- * In that case, you actually have to read the first byte off each line for the prediction algorthim (which should be 0-4, corresponding to PDF 10-14) and select the appropriate unprediction algorithm based on that byte.
- *
- 0 None
- 1 Sub
- 2 Up
- 3 Average
- 4 Paeth
- */
+ this.scaleFactor = 1000.0 / this.head.unitsPerEm;
- var doesNotHavePngJS = function doesNotHavePngJS() {
- return typeof PNG !== 'function' || typeof FlateStream !== 'function';
- },
- canCompress = function canCompress(value) {
- return value !== jsPDFAPI.image_compression.NONE && hasCompressionJS();
- },
- hasCompressionJS = function hasCompressionJS() {
- var inst = typeof Deflater === 'function';
- if (!inst) throw new Error("requires deflate.js for compression");
- return inst;
- },
- compressBytes = function compressBytes(bytes, lineLength, colorsPerPixel, compression) {
+ this.bbox = function () {
+ var _i, _len, _ref, _results;
- var level = 5,
- filter_method = filterUp;
+ _ref = this.bbox;
+ _results = [];
- switch (compression) {
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ e = _ref[_i];
- case jsPDFAPI.image_compression.FAST:
+ _results.push(Math.round(e * this.scaleFactor));
+ }
- level = 3;
- filter_method = filterSub;
- break;
+ return _results;
+ }.call(this);
- case jsPDFAPI.image_compression.MEDIUM:
+ this.stemV = 0;
- level = 6;
- filter_method = filterAverage;
- break;
+ if (this.post.exists) {
+ raw = this.post.italic_angle;
+ hi = raw >> 16;
+ low = raw & 0xff;
- case jsPDFAPI.image_compression.SLOW:
+ if (hi & 0x8000 !== 0) {
+ hi = -((hi ^ 0xffff) + 1);
+ }
- level = 9;
- filter_method = filterPaeth; //uses to sum to choose best filter for each line
- break;
- }
+ this.italicAngle = +("" + hi + "." + low);
+ } else {
+ this.italicAngle = 0;
+ }
- bytes = applyPngFilterMethod(bytes, lineLength, colorsPerPixel, filter_method);
+ this.ascender = Math.round(this.ascender * this.scaleFactor);
+ this.decender = Math.round(this.decender * this.scaleFactor);
+ this.lineGap = Math.round(this.lineGap * this.scaleFactor);
+ this.capHeight = this.os2.exists && this.os2.capHeight || this.ascender;
+ this.xHeight = this.os2.exists && this.os2.xHeight || 0;
+ this.familyClass = (this.os2.exists && this.os2.familyClass || 0) >> 8;
+ this.isSerif = (_ref = this.familyClass) === 1 || _ref === 2 || _ref === 3 || _ref === 4 || _ref === 5 || _ref === 7;
+ this.isScript = this.familyClass === 10;
+ this.flags = 0;
+
+ if (this.post.isFixedPitch) {
+ this.flags |= 1 << 0;
+ }
- var header = new Uint8Array(createZlibHeader(level));
- var checksum = adler32(bytes);
+ if (this.isSerif) {
+ this.flags |= 1 << 1;
+ }
- var deflate = new Deflater(level);
- var a = deflate.append(bytes);
- var cBytes = deflate.flush();
+ if (this.isScript) {
+ this.flags |= 1 << 3;
+ }
- var len = header.length + a.length + cBytes.length;
+ if (this.italicAngle !== 0) {
+ this.flags |= 1 << 6;
+ }
- var cmpd = new Uint8Array(len + 4);
- cmpd.set(header);
- cmpd.set(a, header.length);
- cmpd.set(cBytes, header.length + a.length);
+ this.flags |= 1 << 5;
- cmpd[len++] = checksum >>> 24 & 0xff;
- cmpd[len++] = checksum >>> 16 & 0xff;
- cmpd[len++] = checksum >>> 8 & 0xff;
- cmpd[len++] = checksum & 0xff;
+ if (!this.cmap.unicode) {
+ throw new Error("No unicode cmap for font");
+ }
+ };
- return jsPDFAPI.arrayBufferToBinaryString(cmpd);
- },
- createZlibHeader = function createZlibHeader(bytes, level) {
- /*
- * @see http://www.ietf.org/rfc/rfc1950.txt for zlib header
- */
- var cm = 8;
- var cinfo = Math.LOG2E * Math.log(0x8000) - 8;
- var cmf = cinfo << 4 | cm;
-
- var hdr = cmf << 8;
- var flevel = Math.min(3, (level - 1 & 0xff) >> 1);
-
- hdr |= flevel << 6;
- hdr |= 0; //FDICT
- hdr += 31 - hdr % 31;
-
- return [cmf, hdr & 0xff & 0xff];
- },
- adler32 = function adler32(array, param) {
- var adler = 1;
- var s1 = adler & 0xffff,
- s2 = adler >>> 16 & 0xffff;
- var len = array.length;
- var tlen;
- var i = 0;
-
- while (len > 0) {
- tlen = len > param ? param : len;
- len -= tlen;
- do {
- s1 += array[i++];
- s2 += s1;
- } while (--tlen);
-
- s1 %= 65521;
- s2 %= 65521;
- }
-
- return (s2 << 16 | s1) >>> 0;
- },
- applyPngFilterMethod = function applyPngFilterMethod(bytes, lineLength, colorsPerPixel, filter_method) {
- var lines = bytes.length / lineLength,
- result = new Uint8Array(bytes.length + lines),
- filter_methods = getFilterMethods(),
- i = 0,
- line,
- prevLine,
- offset;
-
- for (; i < lines; i++) {
- offset = i * lineLength;
- line = bytes.subarray(offset, offset + lineLength);
-
- if (filter_method) {
- result.set(filter_method(line, colorsPerPixel, prevLine), offset + i);
- } else {
-
- var j = 0,
- len = filter_methods.length,
- results = [];
-
- for (; j < len; j++) {
- results[j] = filter_methods[j](line, colorsPerPixel, prevLine);
- }var ind = getIndexOfSmallestSum(results.concat());
-
- result.set(results[ind], offset + i);
- }
-
- prevLine = line;
- }
-
- return result;
- },
- filterNone = function filterNone(line, colorsPerPixel, prevLine) {
- /*var result = new Uint8Array(line.length + 1);
- result[0] = 0;
- result.set(line, 1);*/
+ TTFFont.prototype.characterToGlyph = function (character) {
+ var _ref;
- var result = Array.apply([], line);
- result.unshift(0);
-
- return result;
- },
- filterSub = function filterSub(line, colorsPerPixel, prevLine) {
- var result = [],
- i = 0,
- len = line.length,
- left;
-
- result[0] = 1;
-
- for (; i < len; i++) {
- left = line[i - colorsPerPixel] || 0;
- result[i + 1] = line[i] - left + 0x0100 & 0xff;
- }
-
- return result;
- },
- filterUp = function filterUp(line, colorsPerPixel, prevLine) {
- var result = [],
- i = 0,
- len = line.length,
- up;
-
- result[0] = 2;
-
- for (; i < len; i++) {
- up = prevLine && prevLine[i] || 0;
- result[i + 1] = line[i] - up + 0x0100 & 0xff;
- }
-
- return result;
- },
- filterAverage = function filterAverage(line, colorsPerPixel, prevLine) {
- var result = [],
- i = 0,
- len = line.length,
- left,
- up;
-
- result[0] = 3;
-
- for (; i < len; i++) {
- left = line[i - colorsPerPixel] || 0;
- up = prevLine && prevLine[i] || 0;
- result[i + 1] = line[i] + 0x0100 - (left + up >>> 1) & 0xff;
- }
-
- return result;
- },
- filterPaeth = function filterPaeth(line, colorsPerPixel, prevLine) {
- var result = [],
- i = 0,
- len = line.length,
- left,
- up,
- upLeft,
- paeth;
-
- result[0] = 4;
-
- for (; i < len; i++) {
- left = line[i - colorsPerPixel] || 0;
- up = prevLine && prevLine[i] || 0;
- upLeft = prevLine && prevLine[i - colorsPerPixel] || 0;
- paeth = paethPredictor(left, up, upLeft);
- result[i + 1] = line[i] - paeth + 0x0100 & 0xff;
- }
-
- return result;
- },
- paethPredictor = function paethPredictor(left, up, upLeft) {
-
- var p = left + up - upLeft,
- pLeft = Math.abs(p - left),
- pUp = Math.abs(p - up),
- pUpLeft = Math.abs(p - upLeft);
-
- return pLeft <= pUp && pLeft <= pUpLeft ? left : pUp <= pUpLeft ? up : upLeft;
- },
- getFilterMethods = function getFilterMethods() {
- return [filterNone, filterSub, filterUp, filterAverage, filterPaeth];
- },
- getIndexOfSmallestSum = function getIndexOfSmallestSum(arrays) {
- var i = 0,
- len = arrays.length,
- sum,
- min,
- ind;
-
- while (i < len) {
- sum = absSum(arrays[i].slice(1));
-
- if (sum < min || !min) {
- min = sum;
- ind = i;
- }
-
- i++;
- }
-
- return ind;
- },
- absSum = function absSum(array) {
- var i = 0,
- len = array.length,
- sum = 0;
-
- while (i < len) {
- sum += Math.abs(array[i++]);
- }return sum;
- },
- logImg = function logImg(img) {
- console.log("width: " + img.width);
- console.log("height: " + img.height);
- console.log("bits: " + img.bits);
- console.log("colorType: " + img.colorType);
- console.log("transparency:");
- console.log(img.transparency);
- console.log("text:");
- console.log(img.text);
- console.log("compressionMethod: " + img.compressionMethod);
- console.log("filterMethod: " + img.filterMethod);
- console.log("interlaceMethod: " + img.interlaceMethod);
- console.log("imgData:");
- console.log(img.imgData);
- console.log("palette:");
- console.log(img.palette);
- console.log("colors: " + img.colors);
- console.log("colorSpace: " + img.colorSpace);
- console.log("pixelBitlength: " + img.pixelBitlength);
- console.log("hasAlphaChannel: " + img.hasAlphaChannel);
- };
-
- jsPDFAPI.processPNG = function (imageData, imageIndex, alias, compression, dataAsBinaryString) {
- 'use strict';
-
- var colorSpace = this.color_spaces.DEVICE_RGB,
- decode = this.decode.FLATE_DECODE,
- bpc = 8,
- img,
- dp,
- trns,
- colors,
- pal,
- smask;
-
- /* if(this.isString(imageData)) {
-
- }*/
-
- if (this.isArrayBuffer(imageData)) imageData = new Uint8Array(imageData);
-
- if (this.isArrayBufferView(imageData)) {
-
- if (doesNotHavePngJS()) throw new Error("PNG support requires png.js and zlib.js");
-
- img = new PNG(imageData);
- imageData = img.imgData;
- bpc = img.bits;
- colorSpace = img.colorSpace;
- colors = img.colors;
-
- //logImg(img);
-
- /*
- * colorType 6 - Each pixel is an R,G,B triple, followed by an alpha sample.
- *
- * colorType 4 - Each pixel is a grayscale sample, followed by an alpha sample.
- *
- * Extract alpha to create two separate images, using the alpha as a sMask
- */
- if ([4, 6].indexOf(img.colorType) !== -1) {
+ return ((_ref = this.cmap.unicode) != null ? _ref.codeMap[character] : void 0) || 0;
+ };
- /*
- * processes 8 bit RGBA and grayscale + alpha images
- */
- if (img.bits === 8) {
-
- var pixels = img.pixelBitlength == 32 ? new Uint32Array(img.decodePixels().buffer) : img.pixelBitlength == 16 ? new Uint16Array(img.decodePixels().buffer) : new Uint8Array(img.decodePixels().buffer),
- len = pixels.length,
- imgData = new Uint8Array(len * img.colors),
- alphaData = new Uint8Array(len),
- pDiff = img.pixelBitlength - img.bits,
- i = 0,
- n = 0,
- pixel,
- pbl;
-
- for (; i < len; i++) {
- pixel = pixels[i];
- pbl = 0;
-
- while (pbl < pDiff) {
-
- imgData[n++] = pixel >>> pbl & 0xff;
- pbl = pbl + img.bits;
- }
-
- alphaData[i] = pixel >>> pbl & 0xff;
- }
- }
-
- /*
- * processes 16 bit RGBA and grayscale + alpha images
- */
- if (img.bits === 16) {
+ TTFFont.prototype.widthOfGlyph = function (glyph) {
+ var scale;
+ scale = 1000.0 / this.head.unitsPerEm;
+ return this.hmtx.forGlyph(glyph).advance * scale;
+ };
- var pixels = new Uint32Array(img.decodePixels().buffer),
- len = pixels.length,
- imgData = new Uint8Array(len * (32 / img.pixelBitlength) * img.colors),
- alphaData = new Uint8Array(len * (32 / img.pixelBitlength)),
- hasColors = img.colors > 1,
- i = 0,
- n = 0,
- a = 0,
- pixel;
+ TTFFont.prototype.widthOfString = function (string, size, charSpace) {
+ var charCode, i, scale, width, _i, _ref, charSpace;
- while (i < len) {
- pixel = pixels[i++];
+ string = "" + string;
+ width = 0;
- imgData[n++] = pixel >>> 0 & 0xFF;
+ for (i = _i = 0, _ref = string.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
+ charCode = string.charCodeAt(i);
+ width += this.widthOfGlyph(this.characterToGlyph(charCode)) + charSpace * (1000 / size) || 0;
+ }
- if (hasColors) {
- imgData[n++] = pixel >>> 16 & 0xFF;
+ scale = size / 1000;
+ return width * scale;
+ };
- pixel = pixels[i++];
- imgData[n++] = pixel >>> 0 & 0xFF;
- }
+ TTFFont.prototype.lineHeight = function (size, includeGap) {
+ var gap;
- alphaData[a++] = pixel >>> 16 & 0xFF;
- }
+ if (includeGap == null) {
+ includeGap = false;
+ }
- bpc = 8;
- }
+ gap = includeGap ? this.lineGap : 0;
+ return (this.ascender + gap - this.decender) / 1000 * size;
+ };
- if (canCompress(compression)) {
+ return TTFFont;
+ }();
+ /************************************************************************************************/
- imageData = compressBytes(imgData, img.width * img.colors, img.colors, compression);
- smask = compressBytes(alphaData, img.width, 1, compression);
- } else {
+ /* function : Data */
- imageData = imgData;
- smask = alphaData;
- decode = null;
- }
- }
+ /* comment : The ttf data decoded and stored in an array is read and written to the Data object.*/
- /*
- * Indexed png. Each pixel is a palette index.
- */
- if (img.colorType === 3) {
+ /************************************************************************************************/
- colorSpace = this.color_spaces.INDEXED;
- pal = img.palette;
- if (img.transparency.indexed) {
+ var Data = function () {
+ function Data(data) {
+ this.data = data != null ? data : [];
+ this.pos = 0;
+ this.length = this.data.length;
+ }
- var trans = img.transparency.indexed;
+ Data.prototype.readByte = function () {
+ return this.data[this.pos++];
+ };
- var total = 0,
- i = 0,
- len = trans.length;
+ Data.prototype.writeByte = function (byte) {
+ return this.data[this.pos++] = byte;
+ };
- for (; i < len; ++i) {
- total += trans[i];
- }total = total / 255;
+ Data.prototype.readUInt32 = function () {
+ var b1, b2, b3, b4;
+ b1 = this.readByte() * 0x1000000;
+ b2 = this.readByte() << 16;
+ b3 = this.readByte() << 8;
+ b4 = this.readByte();
+ return b1 + b2 + b3 + b4;
+ };
- /*
- * a single color is specified as 100% transparent (0),
- * so we set trns to use a /Mask with that index
- */
- if (total === len - 1 && trans.indexOf(0) !== -1) {
- trns = [trans.indexOf(0)];
+ Data.prototype.writeUInt32 = function (val) {
+ this.writeByte(val >>> 24 & 0xff);
+ this.writeByte(val >> 16 & 0xff);
+ this.writeByte(val >> 8 & 0xff);
+ return this.writeByte(val & 0xff);
+ };
- /*
- * there's more than one colour within the palette that specifies
- * a transparency value less than 255, so we unroll the pixels to create an image sMask
- */
- } else if (total !== len) {
+ Data.prototype.readInt32 = function () {
+ var int;
+ int = this.readUInt32();
- var pixels = img.decodePixels(),
- alphaData = new Uint8Array(pixels.length),
- i = 0,
- len = pixels.length;
+ if (int >= 0x80000000) {
+ return int - 0x100000000;
+ } else {
+ return int;
+ }
+ };
- for (; i < len; i++) {
- alphaData[i] = trans[pixels[i]];
- }smask = compressBytes(alphaData, img.width, 1);
- }
- }
- }
+ Data.prototype.writeInt32 = function (val) {
+ if (val < 0) {
+ val += 0x100000000;
+ }
- if (decode === this.decode.FLATE_DECODE) dp = '/Predictor 15 /Colors ' + colors + ' /BitsPerComponent ' + bpc + ' /Columns ' + img.width;else
- //remove 'Predictor' as it applies to the type of png filter applied to its IDAT - we only apply with compression
- dp = '/Colors ' + colors + ' /BitsPerComponent ' + bpc + ' /Columns ' + img.width;
+ return this.writeUInt32(val);
+ };
- if (this.isArrayBuffer(imageData) || this.isArrayBufferView(imageData)) imageData = this.arrayBufferToBinaryString(imageData);
+ Data.prototype.readUInt16 = function () {
+ var b1, b2;
+ b1 = this.readByte() << 8;
+ b2 = this.readByte();
+ return b1 | b2;
+ };
- if (smask && this.isArrayBuffer(smask) || this.isArrayBufferView(smask)) smask = this.arrayBufferToBinaryString(smask);
+ Data.prototype.writeUInt16 = function (val) {
+ this.writeByte(val >> 8 & 0xff);
+ return this.writeByte(val & 0xff);
+ };
- return this.createImageInfo(imageData, img.width, img.height, colorSpace, bpc, decode, imageIndex, alias, dp, trns, pal, smask);
- }
+ Data.prototype.readInt16 = function () {
+ var int;
+ int = this.readUInt16();
- throw new Error("Unsupported PNG image data, try using JPEG instead.");
- };
- })(jsPDF.API);
+ if (int >= 0x8000) {
+ return int - 0x10000;
+ } else {
+ return int;
+ }
+ };
- /**
- * jsPDF Autoprint Plugin
- *
- * Licensed under the MIT License.
- * http://opensource.org/licenses/mit-license
- */
+ Data.prototype.writeInt16 = function (val) {
+ if (val < 0) {
+ val += 0x10000;
+ }
- (function (jsPDFAPI) {
- 'use strict';
+ return this.writeUInt16(val);
+ };
- jsPDFAPI.autoPrint = function () {
- 'use strict';
+ Data.prototype.readString = function (length) {
+ var i, ret, _i;
- var refAutoPrintTag;
+ ret = [];
- this.internal.events.subscribe('postPutResources', function () {
- refAutoPrintTag = this.internal.newObject();
- this.internal.write("<< /S/Named /Type/Action /N/Print >>", "endobj");
- });
+ for (i = _i = 0; 0 <= length ? _i < length : _i > length; i = 0 <= length ? ++_i : --_i) {
+ ret[i] = String.fromCharCode(this.readByte());
+ }
- this.internal.events.subscribe("putCatalog", function () {
- this.internal.write("/OpenAction " + refAutoPrintTag + " 0" + " R");
- });
- return this;
+ return ret.join("");
};
- })(jsPDF.API);
- /** @preserve
- * jsPDF split_text_to_size plugin - MIT license.
- * Copyright (c) 2012 Willow Systems Corporation, willow-systems.com
- * 2014 Diego Casorran, https://github.com/diegocr
- */
- /**
- *
- * ====================================================================
- */
+ Data.prototype.writeString = function (val) {
+ var i, _i, _ref, _results;
- ;(function (API) {
- 'use strict';
-
- /**
- Returns an array of length matching length of the 'word' string, with each
- cell ocupied by the width of the char in that position.
-
- @function
- @param word {String}
- @param widths {Object}
- @param kerning {Object}
- @returns {Array}
- */
+ _results = [];
- var getCharWidthsArray = API.getCharWidthsArray = function (text, options) {
-
- if (!options) {
- options = {};
- }
-
- var widths = options.widths ? options.widths : this.internal.getFont().metadata.Unicode.widths,
- widthsFractionOf = widths.fof ? widths.fof : 1,
- kerning = options.kerning ? options.kerning : this.internal.getFont().metadata.Unicode.kerning,
- kerningFractionOf = kerning.fof ? kerning.fof : 1;
-
- // console.log("widths, kergnings", widths, kerning)
-
- var i,
- l,
- char_code,
- prior_char_code = 0 // for kerning
- ,
- default_char_width = widths[0] || widthsFractionOf,
- output = [];
-
- for (i = 0, l = text.length; i < l; i++) {
- char_code = text.charCodeAt(i);
- output.push((widths[char_code] || default_char_width) / widthsFractionOf + (kerning[char_code] && kerning[char_code][prior_char_code] || 0) / kerningFractionOf);
- prior_char_code = char_code;
- }
-
- return output;
- };
- var getArraySum = function getArraySum(array) {
- var i = array.length,
- output = 0;
- while (i) {
- ;i--;
- output += array[i];
- }
- return output;
- };
- /**
- Returns a widths of string in a given font, if the font size is set as 1 point.
-
- In other words, this is "proportional" value. For 1 unit of font size, the length
- of the string will be that much.
-
- Multiply by font size to get actual width in *points*
- Then divide by 72 to get inches or divide by (72/25.6) to get 'mm' etc.
-
- @public
- @function
- @param
- @returns {Type}
- */
- var getStringUnitWidth = API.getStringUnitWidth = function (text, options) {
- return getArraySum(getCharWidthsArray.call(this, text, options));
- };
+ for (i = _i = 0, _ref = val.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
+ _results.push(this.writeByte(val.charCodeAt(i)));
+ }
- /**
- returns array of lines
- */
- var splitLongWord = function splitLongWord(word, widths_array, firstLineMaxLen, maxLen) {
- var answer = [];
-
- // 1st, chop off the piece that can fit on the hanging line.
- var i = 0,
- l = word.length,
- workingLen = 0;
- while (i !== l && workingLen + widths_array[i] < firstLineMaxLen) {
- workingLen += widths_array[i];i++;
- }
- // this is first line.
- answer.push(word.slice(0, i));
-
- // 2nd. Split the rest into maxLen pieces.
- var startOfLine = i;
- workingLen = 0;
- while (i !== l) {
- if (workingLen + widths_array[i] > maxLen) {
- answer.push(word.slice(startOfLine, i));
- workingLen = 0;
- startOfLine = i;
- }
- workingLen += widths_array[i];i++;
- }
- if (startOfLine !== i) {
- answer.push(word.slice(startOfLine, i));
- }
-
- return answer;
- };
-
- // Note, all sizing inputs for this function must be in "font measurement units"
- // By default, for PDF, it's "point".
- var splitParagraphIntoLines = function splitParagraphIntoLines(text, maxlen, options) {
- // at this time works only on Western scripts, ones with space char
- // separating the words. Feel free to expand.
-
- if (!options) {
- options = {};
- }
-
- var line = [],
- lines = [line],
- line_length = options.textIndent || 0,
- separator_length = 0,
- current_word_length = 0,
- word,
- widths_array,
- words = text.split(' '),
- spaceCharWidth = getCharWidthsArray(' ', options)[0],
- i,
- l,
- tmp,
- lineIndent;
-
- if (options.lineIndent === -1) {
- lineIndent = words[0].length + 2;
- } else {
- lineIndent = options.lineIndent || 0;
- }
- if (lineIndent) {
- var pad = Array(lineIndent).join(" "),
- wrds = [];
- words.map(function (wrd) {
- wrd = wrd.split(/\s*\n/);
- if (wrd.length > 1) {
- wrds = wrds.concat(wrd.map(function (wrd, idx) {
- return (idx && wrd.length ? "\n" : "") + wrd;
- }));
- } else {
- wrds.push(wrd[0]);
- }
- });
- words = wrds;
- lineIndent = getStringUnitWidth(pad, options);
- }
-
- for (i = 0, l = words.length; i < l; i++) {
- var force = 0;
-
- word = words[i];
- if (lineIndent && word[0] == "\n") {
- word = word.substr(1);
- force = 1;
- }
- widths_array = getCharWidthsArray(word, options);
- current_word_length = getArraySum(widths_array);
-
- if (line_length + separator_length + current_word_length > maxlen || force) {
- if (current_word_length > maxlen) {
- // this happens when you have space-less long URLs for example.
- // we just chop these to size. We do NOT insert hiphens
- tmp = splitLongWord(word, widths_array, maxlen - (line_length + separator_length), maxlen);
- // first line we add to existing line object
- line.push(tmp.shift()); // it's ok to have extra space indicator there
- // last line we make into new line object
- line = [tmp.pop()];
- // lines in the middle we apped to lines object as whole lines
- while (tmp.length) {
- lines.push([tmp.shift()]); // single fragment occupies whole line
- }
- current_word_length = getArraySum(widths_array.slice(word.length - line[0].length));
- } else {
- // just put it on a new line
- line = [word];
- }
-
- // now we attach new line to lines
- lines.push(line);
- line_length = current_word_length + lineIndent;
- separator_length = spaceCharWidth;
- } else {
- line.push(word);
-
- line_length += separator_length + current_word_length;
- separator_length = spaceCharWidth;
- }
- }
-
- if (lineIndent) {
- var postProcess = function postProcess(ln, idx) {
- return (idx ? pad : '') + ln.join(" ");
- };
- } else {
- var postProcess = function postProcess(ln) {
- return ln.join(" ");
- };
- }
-
- return lines.map(postProcess);
- };
-
- /**
- Splits a given string into an array of strings. Uses 'size' value
- (in measurement units declared as default for the jsPDF instance)
- and the font's "widths" and "Kerning" tables, where availabe, to
- determine display length of a given string for a given font.
-
- We use character's 100% of unit size (height) as width when Width
- table or other default width is not available.
-
- @public
- @function
- @param text {String} Unencoded, regular JavaScript (Unicode, UTF-16 / UCS-2) string.
- @param size {Number} Nominal number, measured in units default to this instance of jsPDF.
- @param options {Object} Optional flags needed for chopper to do the right thing.
- @returns {Array} with strings chopped to size.
- */
- API.splitTextToSize = function (text, maxlen, options) {
- 'use strict';
-
- if (!options) {
- options = {};
- }
-
- var fsize = options.fontSize || this.internal.getFontSize(),
- newOptions = function (options) {
- var widths = { 0: 1 },
- kerning = {};
-
- if (!options.widths || !options.kerning) {
- var f = this.internal.getFont(options.fontName, options.fontStyle),
- encoding = 'Unicode';
- // NOT UTF8, NOT UTF16BE/LE, NOT UCS2BE/LE
- // Actual JavaScript-native String's 16bit char codes used.
- // no multi-byte logic here
-
- if (f.metadata[encoding]) {
- return {
- widths: f.metadata[encoding].widths || widths,
- kerning: f.metadata[encoding].kerning || kerning
- };
- }
- } else {
- return {
- widths: options.widths,
- kerning: options.kerning
- };
- }
-
- // then use default values
- return {
- widths: widths,
- kerning: kerning
- };
- }.call(this, options);
-
- // first we split on end-of-line chars
- var paragraphs;
- if (Array.isArray(text)) {
- paragraphs = text;
- } else {
- paragraphs = text.split(/\r?\n/);
- }
-
- // now we convert size (max length of line) into "font size units"
- // at present time, the "font size unit" is always 'point'
- // 'proportional' means, "in proportion to font size"
- var fontUnit_maxLen = 1.0 * this.internal.scaleFactor * maxlen / fsize;
- // at this time, fsize is always in "points" regardless of the default measurement unit of the doc.
- // this may change in the future?
- // until then, proportional_maxlen is likely to be in 'points'
-
- // If first line is to be indented (shorter or longer) than maxLen
- // we indicate that by using CSS-style "text-indent" option.
- // here it's in font units too (which is likely 'points')
- // it can be negative (which makes the first line longer than maxLen)
- newOptions.textIndent = options.textIndent ? options.textIndent * 1.0 * this.internal.scaleFactor / fsize : 0;
- newOptions.lineIndent = options.lineIndent;
-
- var i,
- l,
- output = [];
- for (i = 0, l = paragraphs.length; i < l; i++) {
- output = output.concat(splitParagraphIntoLines(paragraphs[i], fontUnit_maxLen, newOptions));
- }
-
- return output;
- };
- })(jsPDF.API);
-
- /** @preserve
- jsPDF standard_fonts_metrics plugin
- Copyright (c) 2012 Willow Systems Corporation, willow-systems.com
- MIT license.
- */
- /**
- *
- * ====================================================================
- */
+ return _results;
+ };
+ /*Data.prototype.stringAt = function (pos, length) {
+ this.pos = pos;
+ return this.readString(length);
+ };*/
- ;(function (API) {
- 'use strict';
-
- /*
- # reference (Python) versions of 'compress' and 'uncompress'
- # only 'uncompress' function is featured lower as JavaScript
- # if you want to unit test "roundtrip", just transcribe the reference
- # 'compress' function from Python into JavaScript
-
- def compress(data):
-
- keys = '0123456789abcdef'
- values = 'klmnopqrstuvwxyz'
- mapping = dict(zip(keys, values))
- vals = []
- for key in data.keys():
- value = data[key]
- try:
- keystring = hex(key)[2:]
- keystring = keystring[:-1] + mapping[keystring[-1:]]
- except:
- keystring = key.join(["'","'"])
- #print('Keystring is %s' % keystring)
-
- try:
- if value < 0:
- valuestring = hex(value)[3:]
- numberprefix = '-'
- else:
- valuestring = hex(value)[2:]
- numberprefix = ''
- valuestring = numberprefix + valuestring[:-1] + mapping[valuestring[-1:]]
- except:
- if type(value) == dict:
- valuestring = compress(value)
- else:
- raise Exception("Don't know what to do with value type %s" % type(value))
-
- vals.append(keystring+valuestring)
-
- return '{' + ''.join(vals) + '}'
-
- def uncompress(data):
-
- decoded = '0123456789abcdef'
- encoded = 'klmnopqrstuvwxyz'
- mapping = dict(zip(encoded, decoded))
-
- sign = +1
- stringmode = False
- stringparts = []
-
- output = {}
-
- activeobject = output
- parentchain = []
-
- keyparts = ''
- valueparts = ''
-
- key = None
-
- ending = set(encoded)
-
- i = 1
- l = len(data) - 1 # stripping starting, ending {}
- while i != l: # stripping {}
- # -, {, }, ' are special.
-
- ch = data[i]
- i += 1
-
- if ch == "'":
- if stringmode:
- # end of string mode
- stringmode = False
- key = ''.join(stringparts)
- else:
- # start of string mode
- stringmode = True
- stringparts = []
- elif stringmode == True:
- #print("Adding %s to stringpart" % ch)
- stringparts.append(ch)
-
- elif ch == '{':
- # start of object
- parentchain.append( [activeobject, key] )
- activeobject = {}
- key = None
- #DEBUG = True
- elif ch == '}':
- # end of object
- parent, key = parentchain.pop()
- parent[key] = activeobject
- key = None
- activeobject = parent
- #DEBUG = False
-
- elif ch == '-':
- sign = -1
- else:
- # must be number
- if key == None:
- #debug("In Key. It is '%s', ch is '%s'" % (keyparts, ch))
- if ch in ending:
- #debug("End of key")
- keyparts += mapping[ch]
- key = int(keyparts, 16) * sign
- sign = +1
- keyparts = ''
- else:
- keyparts += ch
- else:
- #debug("In value. It is '%s', ch is '%s'" % (valueparts, ch))
- if ch in ending:
- #debug("End of value")
- valueparts += mapping[ch]
- activeobject[key] = int(valueparts, 16) * sign
- sign = +1
- key = None
- valueparts = ''
- else:
- valueparts += ch
-
- #debug(activeobject)
-
- return output
-
- */
- /**
- Uncompresses data compressed into custom, base16-like format.
- @public
- @function
- @param
- @returns {Type}
- */
+ Data.prototype.readShort = function () {
+ return this.readInt16();
+ };
- var uncompress = function uncompress(data) {
-
- var decoded = '0123456789abcdef',
- encoded = 'klmnopqrstuvwxyz',
- mapping = {};
-
- for (var i = 0; i < encoded.length; i++) {
- mapping[encoded[i]] = decoded[i];
- }
-
- var undef,
- output = {},
- sign = 1,
- stringparts // undef. will be [] in string mode
-
- ,
- activeobject = output,
- parentchain = [],
- parent_key_pair,
- keyparts = '',
- valueparts = '',
- key // undef. will be Truthy when Key is resolved.
- ,
- datalen = data.length - 1 // stripping ending }
- ,
- ch;
-
- i = 1; // stripping starting {
-
- while (i != datalen) {
- // - { } ' are special.
-
- ch = data[i];
- i += 1;
-
- if (ch == "'") {
- if (stringparts) {
- // end of string mode
- key = stringparts.join('');
- stringparts = undef;
- } else {
- // start of string mode
- stringparts = [];
- }
- } else if (stringparts) {
- stringparts.push(ch);
- } else if (ch == '{') {
- // start of object
- parentchain.push([activeobject, key]);
- activeobject = {};
- key = undef;
- } else if (ch == '}') {
- // end of object
- parent_key_pair = parentchain.pop();
- parent_key_pair[0][parent_key_pair[1]] = activeobject;
- key = undef;
- activeobject = parent_key_pair[0];
- } else if (ch == '-') {
- sign = -1;
- } else {
- // must be number
- if (key === undef) {
- if (mapping.hasOwnProperty(ch)) {
- keyparts += mapping[ch];
- key = parseInt(keyparts, 16) * sign;
- sign = +1;
- keyparts = '';
- } else {
- keyparts += ch;
- }
- } else {
- if (mapping.hasOwnProperty(ch)) {
- valueparts += mapping[ch];
- activeobject[key] = parseInt(valueparts, 16) * sign;
- sign = +1;
- key = undef;
- valueparts = '';
- } else {
- valueparts += ch;
- }
- }
- }
- } // end while
-
- return output;
- };
-
- // encoding = 'Unicode'
- // NOT UTF8, NOT UTF16BE/LE, NOT UCS2BE/LE. NO clever BOM behavior
- // Actual 16bit char codes used.
- // no multi-byte logic here
-
- // Unicode characters to WinAnsiEncoding:
- // {402: 131, 8211: 150, 8212: 151, 8216: 145, 8217: 146, 8218: 130, 8220: 147, 8221: 148, 8222: 132, 8224: 134, 8225: 135, 8226: 149, 8230: 133, 8364: 128, 8240:137, 8249: 139, 8250: 155, 710: 136, 8482: 153, 338: 140, 339: 156, 732: 152, 352: 138, 353: 154, 376: 159, 381: 142, 382: 158}
- // as you can see, all Unicode chars are outside of 0-255 range. No char code conflicts.
- // this means that you can give Win cp1252 encoded strings to jsPDF for rendering directly
- // as well as give strings with some (supported by these fonts) Unicode characters and
- // these will be mapped to win cp1252
- // for example, you can send char code (cp1252) 0x80 or (unicode) 0x20AC, getting "Euro" glyph displayed in both cases.
-
- var encodingBlock = {
- 'codePages': ['WinAnsiEncoding'],
- 'WinAnsiEncoding': uncompress("{19m8n201n9q201o9r201s9l201t9m201u8m201w9n201x9o201y8o202k8q202l8r202m9p202q8p20aw8k203k8t203t8v203u9v2cq8s212m9t15m8w15n9w2dw9s16k8u16l9u17s9z17x8y17y9y}")
- },
- encodings = { 'Unicode': {
- 'Courier': encodingBlock,
- 'Courier-Bold': encodingBlock,
- 'Courier-BoldOblique': encodingBlock,
- 'Courier-Oblique': encodingBlock,
- 'Helvetica': encodingBlock,
- 'Helvetica-Bold': encodingBlock,
- 'Helvetica-BoldOblique': encodingBlock,
- 'Helvetica-Oblique': encodingBlock,
- 'Times-Roman': encodingBlock,
- 'Times-Bold': encodingBlock,
- 'Times-BoldItalic': encodingBlock,
- 'Times-Italic': encodingBlock
- // , 'Symbol'
- // , 'ZapfDingbats'
- }
- /**
- Resources:
- Font metrics data is reprocessed derivative of contents of
- "Font Metrics for PDF Core 14 Fonts" package, which exhibits the following copyright and license:
-
- Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
-
- This file and the 14 PostScript(R) AFM files it accompanies may be used,
- copied, and distributed for any purpose and without charge, with or without
- modification, provided that all copyright notices are retained; that the AFM
- files are not distributed without this file; that all modifications to this
- file or any of the AFM files are prominently noted in the modified file(s);
- and that this paragraph is not modified. Adobe Systems has no responsibility
- or obligation to support the use of the AFM files.
-
- */
- },
- fontMetrics = { 'Unicode': {
- // all sizing numbers are n/fontMetricsFractionOf = one font size unit
- // this means that if fontMetricsFractionOf = 1000, and letter A's width is 476, it's
- // width is 476/1000 or 47.6% of its height (regardless of font size)
- // At this time this value applies to "widths" and "kerning" numbers.
-
- // char code 0 represents "default" (average) width - use it for chars missing in this table.
- // key 'fof' represents the "fontMetricsFractionOf" value
-
- 'Courier-Oblique': uncompress("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"),
- 'Times-BoldItalic': uncompress("{'widths'{k3o2q4ycx2r201n3m201o6o201s2l201t2l201u2l201w3m201x3m201y3m2k1t2l2r202m2n2n3m2o3m2p5n202q6o2r1w2s2l2t2l2u3m2v3t2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w3t3x3t3y3t3z3m4k5n4l4m4m4m4n4m4o4s4p4m4q4m4r4s4s4y4t2r4u3m4v4m4w3x4x5t4y4s4z4s5k3x5l4s5m4m5n3r5o3x5p4s5q4m5r5t5s4m5t3x5u3x5v2l5w1w5x2l5y3t5z3m6k2l6l3m6m3m6n2w6o3m6p2w6q2l6r3m6s3r6t1w6u1w6v3m6w1w6x4y6y3r6z3m7k3m7l3m7m2r7n2r7o1w7p3r7q2w7r4m7s3m7t2w7u2r7v2n7w1q7x2n7y3t202l3mcl4mal2ram3man3mao3map3mar3mas2lat4uau1uav3maw3way4uaz2lbk2sbl3t'fof'6obo2lbp3tbq3mbr1tbs2lbu1ybv3mbz3mck4m202k3mcm4mcn4mco4mcp4mcq5ycr4mcs4mct4mcu4mcv4mcw2r2m3rcy2rcz2rdl4sdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek3mel3mem3men3meo3mep3meq4ser2wes2wet2weu2wev2wew1wex1wey1wez1wfl3rfm3mfn3mfo3mfp3mfq3mfr3tfs3mft3rfu3rfv3rfw3rfz2w203k6o212m6o2dw2l2cq2l3t3m3u2l17s3x19m3m}'kerning'{cl{4qu5kt5qt5rs17ss5ts}201s{201ss}201t{cks4lscmscnscoscpscls2wu2yu201ts}201x{2wu2yu}2k{201ts}2w{4qx5kx5ou5qx5rs17su5tu}2x{17su5tu5ou}2y{4qx5kx5ou5qx5rs17ss5ts}'fof'-6ofn{17sw5tw5ou5qw5rs}7t{cksclscmscnscoscps4ls}3u{17su5tu5os5qs}3v{17su5tu5os5qs}7p{17su5tu}ck{4qu5kt5qt5rs17ss5ts}4l{4qu5kt5qt5rs17ss5ts}cm{4qu5kt5qt5rs17ss5ts}cn{4qu5kt5qt5rs17ss5ts}co{4qu5kt5qt5rs17ss5ts}cp{4qu5kt5qt5rs17ss5ts}6l{4qu5ou5qw5rt17su5tu}5q{ckuclucmucnucoucpu4lu}5r{ckuclucmucnucoucpu4lu}7q{cksclscmscnscoscps4ls}6p{4qu5ou5qw5rt17sw5tw}ek{4qu5ou5qw5rt17su5tu}el{4qu5ou5qw5rt17su5tu}em{4qu5ou5qw5rt17su5tu}en{4qu5ou5qw5rt17su5tu}eo{4qu5ou5qw5rt17su5tu}ep{4qu5ou5qw5rt17su5tu}es{17ss5ts5qs4qu}et{4qu5ou5qw5rt17sw5tw}eu{4qu5ou5qw5rt17ss5ts}ev{17ss5ts5qs4qu}6z{17sw5tw5ou5qw5rs}fm{17sw5tw5ou5qw5rs}7n{201ts}fo{17sw5tw5ou5qw5rs}fp{17sw5tw5ou5qw5rs}fq{17sw5tw5ou5qw5rs}7r{cksclscmscnscoscps4ls}fs{17sw5tw5ou5qw5rs}ft{17su5tu}fu{17su5tu}fv{17su5tu}fw{17su5tu}fz{cksclscmscnscoscps4ls}}}"),
- 'Helvetica-Bold': uncompress("{'widths'{k3s2q4scx1w201n3r201o6o201s1w201t1w201u1w201w3m201x3m201y3m2k1w2l2l202m2n2n3r2o3r2p5t202q6o2r1s2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v2l3w3u3x3u3y3u3z3x4k6l4l4s4m4s4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3r4v4s4w3x4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v2l5w1w5x2l5y3u5z3r6k2l6l3r6m3x6n3r6o3x6p3r6q2l6r3x6s3x6t1w6u1w6v3r6w1w6x5t6y3x6z3x7k3x7l3x7m2r7n3r7o2l7p3x7q3r7r4y7s3r7t3r7u3m7v2r7w1w7x2r7y3u202l3rcl4sal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3xbq3rbr1wbs2lbu2obv3rbz3xck4s202k3rcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw1w2m2zcy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3res3ret3reu3rev3rew1wex1wey1wez1wfl3xfm3xfn3xfo3xfp3xfq3xfr3ufs3xft3xfu3xfv3xfw3xfz3r203k6o212m6o2dw2l2cq2l3t3r3u2l17s4m19m3r}'kerning'{cl{4qs5ku5ot5qs17sv5tv}201t{2ww4wy2yw}201w{2ks}201x{2ww4wy2yw}2k{201ts201xs}2w{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}2x{5ow5qs}2y{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}'fof'-6o7p{17su5tu5ot}ck{4qs5ku5ot5qs17sv5tv}4l{4qs5ku5ot5qs17sv5tv}cm{4qs5ku5ot5qs17sv5tv}cn{4qs5ku5ot5qs17sv5tv}co{4qs5ku5ot5qs17sv5tv}cp{4qs5ku5ot5qs17sv5tv}6l{17st5tt5os}17s{2kwclvcmvcnvcovcpv4lv4wwckv}5o{2kucltcmtcntcotcpt4lt4wtckt}5q{2ksclscmscnscoscps4ls4wvcks}5r{2ks4ws}5t{2kwclvcmvcnvcovcpv4lv4wwckv}eo{17st5tt5os}fu{17su5tu5ot}6p{17ss5ts}ek{17st5tt5os}el{17st5tt5os}em{17st5tt5os}en{17st5tt5os}6o{201ts}ep{17st5tt5os}es{17ss5ts}et{17ss5ts}eu{17ss5ts}ev{17ss5ts}6z{17su5tu5os5qt}fm{17su5tu5os5qt}fn{17su5tu5os5qt}fo{17su5tu5os5qt}fp{17su5tu5os5qt}fq{17su5tu5os5qt}fs{17su5tu5os5qt}ft{17su5tu5ot}7m{5os}fv{17su5tu5ot}fw{17su5tu5ot}}}"),
- 'Courier': uncompress("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"),
- 'Courier-BoldOblique': uncompress("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"),
- 'Times-Bold': uncompress("{'widths'{k3q2q5ncx2r201n3m201o6o201s2l201t2l201u2l201w3m201x3m201y3m2k1t2l2l202m2n2n3m2o3m2p6o202q6o2r1w2s2l2t2l2u3m2v3t2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w3t3x3t3y3t3z3m4k5x4l4s4m4m4n4s4o4s4p4m4q3x4r4y4s4y4t2r4u3m4v4y4w4m4x5y4y4s4z4y5k3x5l4y5m4s5n3r5o4m5p4s5q4s5r6o5s4s5t4s5u4m5v2l5w1w5x2l5y3u5z3m6k2l6l3m6m3r6n2w6o3r6p2w6q2l6r3m6s3r6t1w6u2l6v3r6w1w6x5n6y3r6z3m7k3r7l3r7m2w7n2r7o2l7p3r7q3m7r4s7s3m7t3m7u2w7v2r7w1q7x2r7y3o202l3mcl4sal2lam3man3mao3map3mar3mas2lat4uau1yav3maw3tay4uaz2lbk2sbl3t'fof'6obo2lbp3rbr1tbs2lbu2lbv3mbz3mck4s202k3mcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw2r2m3rcy2rcz2rdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3rek3mel3mem3men3meo3mep3meq4ser2wes2wet2weu2wev2wew1wex1wey1wez1wfl3rfm3mfn3mfo3mfp3mfq3mfr3tfs3mft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3m3u2l17s4s19m3m}'kerning'{cl{4qt5ks5ot5qy5rw17sv5tv}201t{cks4lscmscnscoscpscls4wv}2k{201ts}2w{4qu5ku7mu5os5qx5ru17su5tu}2x{17su5tu5ou5qs}2y{4qv5kv7mu5ot5qz5ru17su5tu}'fof'-6o7t{cksclscmscnscoscps4ls}3u{17su5tu5os5qu}3v{17su5tu5os5qu}fu{17su5tu5ou5qu}7p{17su5tu5ou5qu}ck{4qt5ks5ot5qy5rw17sv5tv}4l{4qt5ks5ot5qy5rw17sv5tv}cm{4qt5ks5ot5qy5rw17sv5tv}cn{4qt5ks5ot5qy5rw17sv5tv}co{4qt5ks5ot5qy5rw17sv5tv}cp{4qt5ks5ot5qy5rw17sv5tv}6l{17st5tt5ou5qu}17s{ckuclucmucnucoucpu4lu4wu}5o{ckuclucmucnucoucpu4lu4wu}5q{ckzclzcmzcnzcozcpz4lz4wu}5r{ckxclxcmxcnxcoxcpx4lx4wu}5t{ckuclucmucnucoucpu4lu4wu}7q{ckuclucmucnucoucpu4lu}6p{17sw5tw5ou5qu}ek{17st5tt5qu}el{17st5tt5ou5qu}em{17st5tt5qu}en{17st5tt5qu}eo{17st5tt5qu}ep{17st5tt5ou5qu}es{17ss5ts5qu}et{17sw5tw5ou5qu}eu{17sw5tw5ou5qu}ev{17ss5ts5qu}6z{17sw5tw5ou5qu5rs}fm{17sw5tw5ou5qu5rs}fn{17sw5tw5ou5qu5rs}fo{17sw5tw5ou5qu5rs}fp{17sw5tw5ou5qu5rs}fq{17sw5tw5ou5qu5rs}7r{cktcltcmtcntcotcpt4lt5os}fs{17sw5tw5ou5qu5rs}ft{17su5tu5ou5qu}7m{5os}fv{17su5tu5ou5qu}fw{17su5tu5ou5qu}fz{cksclscmscnscoscps4ls}}}")
- //, 'Symbol': uncompress("{'widths'{k3uaw4r19m3m2k1t2l2l202m2y2n3m2p5n202q6o3k3m2s2l2t2l2v3r2w1t3m3m2y1t2z1wbk2sbl3r'fof'6o3n3m3o3m3p3m3q3m3r3m3s3m3t3m3u1w3v1w3w3r3x3r3y3r3z2wbp3t3l3m5v2l5x2l5z3m2q4yfr3r7v3k7w1o7x3k}'kerning'{'fof'-6o}}")
- , 'Helvetica': uncompress("{'widths'{k3p2q4mcx1w201n3r201o6o201s1q201t1q201u1q201w2l201x2l201y2l2k1w2l1w202m2n2n3r2o3r2p5t202q6o2r1n2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v1w3w3u3x3u3y3u3z3r4k6p4l4m4m4m4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3m4v4m4w3r4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v1w5w1w5x1w5y2z5z3r6k2l6l3r6m3r6n3m6o3r6p3r6q1w6r3r6s3r6t1q6u1q6v3m6w1q6x5n6y3r6z3r7k3r7l3r7m2l7n3m7o1w7p3r7q3m7r4s7s3m7t3m7u3m7v2l7w1u7x2l7y3u202l3rcl4mal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3rbr1wbs2lbu2obv3rbz3xck4m202k3rcm4mcn4mco4mcp4mcq6ocr4scs4mct4mcu4mcv4mcw1w2m2ncy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3mes3ret3reu3rev3rew1wex1wey1wez1wfl3rfm3rfn3rfo3rfp3rfq3rfr3ufs3xft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3r3u1w17s4m19m3r}'kerning'{5q{4wv}cl{4qs5kw5ow5qs17sv5tv}201t{2wu4w1k2yu}201x{2wu4wy2yu}17s{2ktclucmucnu4otcpu4lu4wycoucku}2w{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}2x{17sy5ty5oy5qs}2y{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}'fof'-6o7p{17sv5tv5ow}ck{4qs5kw5ow5qs17sv5tv}4l{4qs5kw5ow5qs17sv5tv}cm{4qs5kw5ow5qs17sv5tv}cn{4qs5kw5ow5qs17sv5tv}co{4qs5kw5ow5qs17sv5tv}cp{4qs5kw5ow5qs17sv5tv}6l{17sy5ty5ow}do{17st5tt}4z{17st5tt}7s{fst}dm{17st5tt}dn{17st5tt}5o{ckwclwcmwcnwcowcpw4lw4wv}dp{17st5tt}dq{17st5tt}7t{5ow}ds{17st5tt}5t{2ktclucmucnu4otcpu4lu4wycoucku}fu{17sv5tv5ow}6p{17sy5ty5ow5qs}ek{17sy5ty5ow}el{17sy5ty5ow}em{17sy5ty5ow}en{5ty}eo{17sy5ty5ow}ep{17sy5ty5ow}es{17sy5ty5qs}et{17sy5ty5ow5qs}eu{17sy5ty5ow5qs}ev{17sy5ty5ow5qs}6z{17sy5ty5ow5qs}fm{17sy5ty5ow5qs}fn{17sy5ty5ow5qs}fo{17sy5ty5ow5qs}fp{17sy5ty5qs}fq{17sy5ty5ow5qs}7r{5ow}fs{17sy5ty5ow5qs}ft{17sv5tv5ow}7m{5ow}fv{17sv5tv5ow}fw{17sv5tv5ow}}}"),
- 'Helvetica-BoldOblique': uncompress("{'widths'{k3s2q4scx1w201n3r201o6o201s1w201t1w201u1w201w3m201x3m201y3m2k1w2l2l202m2n2n3r2o3r2p5t202q6o2r1s2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v2l3w3u3x3u3y3u3z3x4k6l4l4s4m4s4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3r4v4s4w3x4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v2l5w1w5x2l5y3u5z3r6k2l6l3r6m3x6n3r6o3x6p3r6q2l6r3x6s3x6t1w6u1w6v3r6w1w6x5t6y3x6z3x7k3x7l3x7m2r7n3r7o2l7p3x7q3r7r4y7s3r7t3r7u3m7v2r7w1w7x2r7y3u202l3rcl4sal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3xbq3rbr1wbs2lbu2obv3rbz3xck4s202k3rcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw1w2m2zcy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3res3ret3reu3rev3rew1wex1wey1wez1wfl3xfm3xfn3xfo3xfp3xfq3xfr3ufs3xft3xfu3xfv3xfw3xfz3r203k6o212m6o2dw2l2cq2l3t3r3u2l17s4m19m3r}'kerning'{cl{4qs5ku5ot5qs17sv5tv}201t{2ww4wy2yw}201w{2ks}201x{2ww4wy2yw}2k{201ts201xs}2w{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}2x{5ow5qs}2y{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}'fof'-6o7p{17su5tu5ot}ck{4qs5ku5ot5qs17sv5tv}4l{4qs5ku5ot5qs17sv5tv}cm{4qs5ku5ot5qs17sv5tv}cn{4qs5ku5ot5qs17sv5tv}co{4qs5ku5ot5qs17sv5tv}cp{4qs5ku5ot5qs17sv5tv}6l{17st5tt5os}17s{2kwclvcmvcnvcovcpv4lv4wwckv}5o{2kucltcmtcntcotcpt4lt4wtckt}5q{2ksclscmscnscoscps4ls4wvcks}5r{2ks4ws}5t{2kwclvcmvcnvcovcpv4lv4wwckv}eo{17st5tt5os}fu{17su5tu5ot}6p{17ss5ts}ek{17st5tt5os}el{17st5tt5os}em{17st5tt5os}en{17st5tt5os}6o{201ts}ep{17st5tt5os}es{17ss5ts}et{17ss5ts}eu{17ss5ts}ev{17ss5ts}6z{17su5tu5os5qt}fm{17su5tu5os5qt}fn{17su5tu5os5qt}fo{17su5tu5os5qt}fp{17su5tu5os5qt}fq{17su5tu5os5qt}fs{17su5tu5os5qt}ft{17su5tu5ot}7m{5os}fv{17su5tu5ot}fw{17su5tu5ot}}}")
- //, 'ZapfDingbats': uncompress("{'widths'{k4u2k1w'fof'6o}'kerning'{'fof'-6o}}")
- , 'Courier-Bold': uncompress("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"),
- 'Times-Italic': uncompress("{'widths'{k3n2q4ycx2l201n3m201o5t201s2l201t2l201u2l201w3r201x3r201y3r2k1t2l2l202m2n2n3m2o3m2p5n202q5t2r1p2s2l2t2l2u3m2v4n2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w4n3x4n3y4n3z3m4k5w4l3x4m3x4n4m4o4s4p3x4q3x4r4s4s4s4t2l4u2w4v4m4w3r4x5n4y4m4z4s5k3x5l4s5m3x5n3m5o3r5p4s5q3x5r5n5s3x5t3r5u3r5v2r5w1w5x2r5y2u5z3m6k2l6l3m6m3m6n2w6o3m6p2w6q1w6r3m6s3m6t1w6u1w6v2w6w1w6x4s6y3m6z3m7k3m7l3m7m2r7n2r7o1w7p3m7q2w7r4m7s2w7t2w7u2r7v2s7w1v7x2s7y3q202l3mcl3xal2ram3man3mao3map3mar3mas2lat4wau1vav3maw4nay4waz2lbk2sbl4n'fof'6obo2lbp3mbq3obr1tbs2lbu1zbv3mbz3mck3x202k3mcm3xcn3xco3xcp3xcq5tcr4mcs3xct3xcu3xcv3xcw2l2m2ucy2lcz2ldl4mdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek3mel3mem3men3meo3mep3meq4mer2wes2wet2weu2wev2wew1wex1wey1wez1wfl3mfm3mfn3mfo3mfp3mfq3mfr4nfs3mft3mfu3mfv3mfw3mfz2w203k6o212m6m2dw2l2cq2l3t3m3u2l17s3r19m3m}'kerning'{cl{5kt4qw}201s{201sw}201t{201tw2wy2yy6q-t}201x{2wy2yy}2k{201tw}2w{7qs4qy7rs5ky7mw5os5qx5ru17su5tu}2x{17ss5ts5os}2y{7qs4qy7rs5ky7mw5os5qx5ru17su5tu}'fof'-6o6t{17ss5ts5qs}7t{5os}3v{5qs}7p{17su5tu5qs}ck{5kt4qw}4l{5kt4qw}cm{5kt4qw}cn{5kt4qw}co{5kt4qw}cp{5kt4qw}6l{4qs5ks5ou5qw5ru17su5tu}17s{2ks}5q{ckvclvcmvcnvcovcpv4lv}5r{ckuclucmucnucoucpu4lu}5t{2ks}6p{4qs5ks5ou5qw5ru17su5tu}ek{4qs5ks5ou5qw5ru17su5tu}el{4qs5ks5ou5qw5ru17su5tu}em{4qs5ks5ou5qw5ru17su5tu}en{4qs5ks5ou5qw5ru17su5tu}eo{4qs5ks5ou5qw5ru17su5tu}ep{4qs5ks5ou5qw5ru17su5tu}es{5ks5qs4qs}et{4qs5ks5ou5qw5ru17su5tu}eu{4qs5ks5qw5ru17su5tu}ev{5ks5qs4qs}ex{17ss5ts5qs}6z{4qv5ks5ou5qw5ru17su5tu}fm{4qv5ks5ou5qw5ru17su5tu}fn{4qv5ks5ou5qw5ru17su5tu}fo{4qv5ks5ou5qw5ru17su5tu}fp{4qv5ks5ou5qw5ru17su5tu}fq{4qv5ks5ou5qw5ru17su5tu}7r{5os}fs{4qv5ks5ou5qw5ru17su5tu}ft{17su5tu5qs}fu{17su5tu5qs}fv{17su5tu5qs}fw{17su5tu5qs}}}"),
- 'Times-Roman': uncompress("{'widths'{k3n2q4ycx2l201n3m201o6o201s2l201t2l201u2l201w2w201x2w201y2w2k1t2l2l202m2n2n3m2o3m2p5n202q6o2r1m2s2l2t2l2u3m2v3s2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v1w3w3s3x3s3y3s3z2w4k5w4l4s4m4m4n4m4o4s4p3x4q3r4r4s4s4s4t2l4u2r4v4s4w3x4x5t4y4s4z4s5k3r5l4s5m4m5n3r5o3x5p4s5q4s5r5y5s4s5t4s5u3x5v2l5w1w5x2l5y2z5z3m6k2l6l2w6m3m6n2w6o3m6p2w6q2l6r3m6s3m6t1w6u1w6v3m6w1w6x4y6y3m6z3m7k3m7l3m7m2l7n2r7o1w7p3m7q3m7r4s7s3m7t3m7u2w7v3k7w1o7x3k7y3q202l3mcl4sal2lam3man3mao3map3mar3mas2lat4wau1vav3maw3say4waz2lbk2sbl3s'fof'6obo2lbp3mbq2xbr1tbs2lbu1zbv3mbz2wck4s202k3mcm4scn4sco4scp4scq5tcr4mcs3xct3xcu3xcv3xcw2l2m2tcy2lcz2ldl4sdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek2wel2wem2wen2weo2wep2weq4mer2wes2wet2weu2wev2wew1wex1wey1wez1wfl3mfm3mfn3mfo3mfp3mfq3mfr3sfs3mft3mfu3mfv3mfw3mfz3m203k6o212m6m2dw2l2cq2l3t3m3u1w17s4s19m3m}'kerning'{cl{4qs5ku17sw5ou5qy5rw201ss5tw201ws}201s{201ss}201t{ckw4lwcmwcnwcowcpwclw4wu201ts}2k{201ts}2w{4qs5kw5os5qx5ru17sx5tx}2x{17sw5tw5ou5qu}2y{4qs5kw5os5qx5ru17sx5tx}'fof'-6o7t{ckuclucmucnucoucpu4lu5os5rs}3u{17su5tu5qs}3v{17su5tu5qs}7p{17sw5tw5qs}ck{4qs5ku17sw5ou5qy5rw201ss5tw201ws}4l{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cm{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cn{4qs5ku17sw5ou5qy5rw201ss5tw201ws}co{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cp{4qs5ku17sw5ou5qy5rw201ss5tw201ws}6l{17su5tu5os5qw5rs}17s{2ktclvcmvcnvcovcpv4lv4wuckv}5o{ckwclwcmwcnwcowcpw4lw4wu}5q{ckyclycmycnycoycpy4ly4wu5ms}5r{cktcltcmtcntcotcpt4lt4ws}5t{2ktclvcmvcnvcovcpv4lv4wuckv}7q{cksclscmscnscoscps4ls}6p{17su5tu5qw5rs}ek{5qs5rs}el{17su5tu5os5qw5rs}em{17su5tu5os5qs5rs}en{17su5qs5rs}eo{5qs5rs}ep{17su5tu5os5qw5rs}es{5qs}et{17su5tu5qw5rs}eu{17su5tu5qs5rs}ev{5qs}6z{17sv5tv5os5qx5rs}fm{5os5qt5rs}fn{17sv5tv5os5qx5rs}fo{17sv5tv5os5qx5rs}fp{5os5qt5rs}fq{5os5qt5rs}7r{ckuclucmucnucoucpu4lu5os}fs{17sv5tv5os5qx5rs}ft{17ss5ts5qs}fu{17sw5tw5qs}fv{17sw5tw5qs}fw{17ss5ts5qs}fz{ckuclucmucnucoucpu4lu5os5rs}}}"),
- 'Helvetica-Oblique': uncompress("{'widths'{k3p2q4mcx1w201n3r201o6o201s1q201t1q201u1q201w2l201x2l201y2l2k1w2l1w202m2n2n3r2o3r2p5t202q6o2r1n2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v1w3w3u3x3u3y3u3z3r4k6p4l4m4m4m4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3m4v4m4w3r4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v1w5w1w5x1w5y2z5z3r6k2l6l3r6m3r6n3m6o3r6p3r6q1w6r3r6s3r6t1q6u1q6v3m6w1q6x5n6y3r6z3r7k3r7l3r7m2l7n3m7o1w7p3r7q3m7r4s7s3m7t3m7u3m7v2l7w1u7x2l7y3u202l3rcl4mal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3rbr1wbs2lbu2obv3rbz3xck4m202k3rcm4mcn4mco4mcp4mcq6ocr4scs4mct4mcu4mcv4mcw1w2m2ncy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3mes3ret3reu3rev3rew1wex1wey1wez1wfl3rfm3rfn3rfo3rfp3rfq3rfr3ufs3xft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3r3u1w17s4m19m3r}'kerning'{5q{4wv}cl{4qs5kw5ow5qs17sv5tv}201t{2wu4w1k2yu}201x{2wu4wy2yu}17s{2ktclucmucnu4otcpu4lu4wycoucku}2w{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}2x{17sy5ty5oy5qs}2y{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}'fof'-6o7p{17sv5tv5ow}ck{4qs5kw5ow5qs17sv5tv}4l{4qs5kw5ow5qs17sv5tv}cm{4qs5kw5ow5qs17sv5tv}cn{4qs5kw5ow5qs17sv5tv}co{4qs5kw5ow5qs17sv5tv}cp{4qs5kw5ow5qs17sv5tv}6l{17sy5ty5ow}do{17st5tt}4z{17st5tt}7s{fst}dm{17st5tt}dn{17st5tt}5o{ckwclwcmwcnwcowcpw4lw4wv}dp{17st5tt}dq{17st5tt}7t{5ow}ds{17st5tt}5t{2ktclucmucnu4otcpu4lu4wycoucku}fu{17sv5tv5ow}6p{17sy5ty5ow5qs}ek{17sy5ty5ow}el{17sy5ty5ow}em{17sy5ty5ow}en{5ty}eo{17sy5ty5ow}ep{17sy5ty5ow}es{17sy5ty5qs}et{17sy5ty5ow5qs}eu{17sy5ty5ow5qs}ev{17sy5ty5ow5qs}6z{17sy5ty5ow5qs}fm{17sy5ty5ow5qs}fn{17sy5ty5ow5qs}fo{17sy5ty5ow5qs}fp{17sy5ty5qs}fq{17sy5ty5ow5qs}7r{5ow}fs{17sy5ty5ow5qs}ft{17sv5tv5ow}7m{5ow}fv{17sv5tv5ow}fw{17sv5tv5ow}}}")
- } };
-
- /*
- This event handler is fired when a new jsPDF object is initialized
- This event handler appends metrics data to standard fonts within
- that jsPDF instance. The metrics are mapped over Unicode character
- codes, NOT CIDs or other codes matching the StandardEncoding table of the
- standard PDF fonts.
- Future:
- Also included is the encoding maping table, converting Unicode (UCS-2, UTF-16)
- char codes to StandardEncoding character codes. The encoding table is to be used
- somewhere around "pdfEscape" call.
- */
+ Data.prototype.writeShort = function (val) {
+ return this.writeInt16(val);
+ };
- API.events.push(['addFont', function (font) {
- var metrics,
- unicode_section,
- encoding = 'Unicode',
- encodingBlock;
-
- metrics = fontMetrics[encoding][font.PostScriptName];
- if (metrics) {
- if (font.metadata[encoding]) {
- unicode_section = font.metadata[encoding];
- } else {
- unicode_section = font.metadata[encoding] = {};
- }
-
- unicode_section.widths = metrics.widths;
- unicode_section.kerning = metrics.kerning;
- }
-
- encodingBlock = encodings[encoding][font.PostScriptName];
- if (encodingBlock) {
- if (font.metadata[encoding]) {
- unicode_section = font.metadata[encoding];
- } else {
- unicode_section = font.metadata[encoding] = {};
- }
-
- unicode_section.encoding = encodingBlock;
- if (encodingBlock.codePages && encodingBlock.codePages.length) {
- font.encoding = encodingBlock.codePages[0];
- }
- }
- }]); // end of adding event handler
- })(jsPDF.API);
-
- /** @preserve
- jsPDF SVG plugin
- Copyright (c) 2012 Willow Systems Corporation, willow-systems.com
- */
- /**
- *
- * ====================================================================
- */
+ Data.prototype.readLongLong = function () {
+ var b1, b2, b3, b4, b5, b6, b7, b8;
+ b1 = this.readByte();
+ b2 = this.readByte();
+ b3 = this.readByte();
+ b4 = this.readByte();
+ b5 = this.readByte();
+ b6 = this.readByte();
+ b7 = this.readByte();
+ b8 = this.readByte();
+
+ if (b1 & 0x80) {
+ return ((b1 ^ 0xff) * 0x100000000000000 + (b2 ^ 0xff) * 0x1000000000000 + (b3 ^ 0xff) * 0x10000000000 + (b4 ^ 0xff) * 0x100000000 + (b5 ^ 0xff) * 0x1000000 + (b6 ^ 0xff) * 0x10000 + (b7 ^ 0xff) * 0x100 + (b8 ^ 0xff) + 1) * -1;
+ }
- ;(function (jsPDFAPI) {
- 'use strict';
-
- /**
- Parses SVG XML and converts only some of the SVG elements into
- PDF elements.
-
- Supports:
- paths
-
- @public
- @function
- @param
- @returns {Type}
- */
+ return b1 * 0x100000000000000 + b2 * 0x1000000000000 + b3 * 0x10000000000 + b4 * 0x100000000 + b5 * 0x1000000 + b6 * 0x10000 + b7 * 0x100 + b8;
+ };
- jsPDFAPI.addSVG = function (svgtext, x, y, w, h) {
- // 'this' is _jsPDF object returned when jsPDF is inited (new jsPDF())
-
- var undef;
-
- if (x === undef || y === undef) {
- throw new Error("addSVG needs values for 'x' and 'y'");
- }
-
- function InjectCSS(cssbody, document) {
- var styletag = document.createElement('style');
- styletag.type = 'text/css';
- if (styletag.styleSheet) {
- // ie
- styletag.styleSheet.cssText = cssbody;
- } else {
- // others
- styletag.appendChild(document.createTextNode(cssbody));
- }
- document.getElementsByTagName("head")[0].appendChild(styletag);
- }
-
- function createWorkerNode(document) {
-
- var frameID = 'childframe' // Date.now().toString() + '_' + (Math.random() * 100).toString()
- ,
- frame = document.createElement('iframe');
-
- InjectCSS('.jsPDF_sillysvg_iframe {display:none;position:absolute;}', document);
-
- frame.name = frameID;
- frame.setAttribute("width", 0);
- frame.setAttribute("height", 0);
- frame.setAttribute("frameborder", "0");
- frame.setAttribute("scrolling", "no");
- frame.setAttribute("seamless", "seamless");
- frame.setAttribute("class", "jsPDF_sillysvg_iframe");
-
- document.body.appendChild(frame);
-
- return frame;
- }
-
- function attachSVGToWorkerNode(svgtext, frame) {
- var framedoc = (frame.contentWindow || frame.contentDocument).document;
- framedoc.write(svgtext);
- framedoc.close();
- return framedoc.getElementsByTagName('svg')[0];
- }
-
- function convertPathToPDFLinesArgs(path) {
- 'use strict';
- // we will use 'lines' method call. it needs:
- // - starting coordinate pair
- // - array of arrays of vector shifts (2-len for line, 6 len for bezier)
- // - scale array [horizontal, vertical] ratios
- // - style (stroke, fill, both)
-
- var x = parseFloat(path[1]),
- y = parseFloat(path[2]),
- vectors = [],
- position = 3,
- len = path.length;
-
- while (position < len) {
- if (path[position] === 'c') {
- vectors.push([parseFloat(path[position + 1]), parseFloat(path[position + 2]), parseFloat(path[position + 3]), parseFloat(path[position + 4]), parseFloat(path[position + 5]), parseFloat(path[position + 6])]);
- position += 7;
- } else if (path[position] === 'l') {
- vectors.push([parseFloat(path[position + 1]), parseFloat(path[position + 2])]);
- position += 3;
- } else {
- position += 1;
- }
- }
- return [x, y, vectors];
- }
-
- var workernode = createWorkerNode(document),
- svgnode = attachSVGToWorkerNode(svgtext, workernode),
- scale = [1, 1],
- svgw = parseFloat(svgnode.getAttribute('width')),
- svgh = parseFloat(svgnode.getAttribute('height'));
-
- if (svgw && svgh) {
- // setting both w and h makes image stretch to size.
- // this may distort the image, but fits your demanded size
- if (w && h) {
- scale = [w / svgw, h / svgh];
- }
- // if only one is set, that value is set as max and SVG
- // is scaled proportionately.
- else if (w) {
- scale = [w / svgw, w / svgw];
- } else if (h) {
- scale = [h / svgh, h / svgh];
- }
- }
-
- var i,
- l,
- tmp,
- linesargs,
- items = svgnode.childNodes;
- for (i = 0, l = items.length; i < l; i++) {
- tmp = items[i];
- if (tmp.tagName && tmp.tagName.toUpperCase() === 'PATH') {
- linesargs = convertPathToPDFLinesArgs(tmp.getAttribute("d").split(' '));
- // path start x coordinate
- linesargs[0] = linesargs[0] * scale[0] + x; // where x is upper left X of image
- // path start y coordinate
- linesargs[1] = linesargs[1] * scale[1] + y; // where y is upper left Y of image
- // the rest of lines are vectors. these will adjust with scale value auto.
- this.lines.call(this, linesargs[2] // lines
- , linesargs[0] // starting x
- , linesargs[1] // starting y
- , scale);
- }
- }
-
- // clean up
- // workernode.parentNode.removeChild(workernode)
-
- return this;
- };
- })(jsPDF.API);
-
- /** ====================================================================
- * jsPDF total_pages plugin
- * Copyright (c) 2013 Eduardo Menezes de Morais, eduardo.morais@usp.br
- *
- *
- * ====================================================================
- */
+ Data.prototype.writeLongLong = function (val) {
+ var high, low;
+ high = Math.floor(val / 0x100000000);
+ low = val & 0xffffffff;
+ this.writeByte(high >> 24 & 0xff);
+ this.writeByte(high >> 16 & 0xff);
+ this.writeByte(high >> 8 & 0xff);
+ this.writeByte(high & 0xff);
+ this.writeByte(low >> 24 & 0xff);
+ this.writeByte(low >> 16 & 0xff);
+ this.writeByte(low >> 8 & 0xff);
+ return this.writeByte(low & 0xff);
+ };
+
+ Data.prototype.readInt = function () {
+ return this.readInt32();
+ };
- (function (jsPDFAPI) {
- 'use strict';
+ Data.prototype.writeInt = function (val) {
+ return this.writeInt32(val);
+ };
+ /*Data.prototype.slice = function (start, end) {
+ return this.data.slice(start, end);
+ };*/
- jsPDFAPI.putTotalPages = function (pageExpression) {
- 'use strict';
- var replaceExpression = new RegExp(pageExpression, 'g');
- for (var n = 1; n <= this.internal.getNumberOfPages(); n++) {
- for (var i = 0; i < this.internal.pages[n].length; i++) {
- this.internal.pages[n][i] = this.internal.pages[n][i].replace(replaceExpression, this.internal.getNumberOfPages());
- }
+ Data.prototype.read = function (bytes) {
+ var buf, i, _i;
+
+ buf = [];
+
+ for (i = _i = 0; 0 <= bytes ? _i < bytes : _i > bytes; i = 0 <= bytes ? ++_i : --_i) {
+ buf.push(this.readByte());
}
- return this;
+
+ return buf;
};
- })(jsPDF.API);
- /* Blob.js
- * A Blob implementation.
- * 2014-07-24
- *
- * By Eli Grey, http://eligrey.com
- * By Devin Samarin, https://github.com/dsamarin
- * License: X11/MIT
- * See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md
- */
+ Data.prototype.write = function (bytes) {
+ var byte, _i, _len, _results;
- /*global self, unescape */
- /*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
- plusplus: true */
-
- /*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
-
- (function (view) {
- "use strict";
-
- view.URL = view.URL || view.webkitURL;
-
- if (view.Blob && view.URL) {
- try {
- new Blob;
- return;
- } catch (e) {}
- }
-
- // Internally we use a BlobBuilder implementation to base Blob off of
- // in order to support older browsers that only have BlobBuilder
- var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) {
- var
- get_class = function(object) {
- return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
- }
- , FakeBlobBuilder = function BlobBuilder() {
- this.data = [];
- }
- , FakeBlob = function Blob(data, type, encoding) {
- this.data = data;
- this.size = data.length;
- this.type = type;
- this.encoding = encoding;
- }
- , FBB_proto = FakeBlobBuilder.prototype
- , FB_proto = FakeBlob.prototype
- , FileReaderSync = view.FileReaderSync
- , FileException = function(type) {
- this.code = this[this.name = type];
- }
- , file_ex_codes = (
- "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
- + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
- ).split(" ")
- , file_ex_code = file_ex_codes.length
- , real_URL = view.URL || view.webkitURL || view
- , real_create_object_URL = real_URL.createObjectURL
- , real_revoke_object_URL = real_URL.revokeObjectURL
- , URL = real_URL
- , btoa = view.btoa
- , atob = view.atob
-
- , ArrayBuffer = view.ArrayBuffer
- , Uint8Array = view.Uint8Array
-
- , origin = /^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/
- ;
- FakeBlob.fake = FB_proto.fake = true;
- while (file_ex_code--) {
- FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
- }
- // Polyfill URL
- if (!real_URL.createObjectURL) {
- URL = view.URL = function(uri) {
- var
- uri_info = document.createElementNS("http://www.w3.org/1999/xhtml", "a")
- , uri_origin
- ;
- uri_info.href = uri;
- if (!("origin" in uri_info)) {
- if (uri_info.protocol.toLowerCase() === "data:") {
- uri_info.origin = null;
- } else {
- uri_origin = uri.match(origin);
- uri_info.origin = uri_origin && uri_origin[1];
- }
- }
- return uri_info;
- };
- }
- URL.createObjectURL = function(blob) {
- var
- type = blob.type
- , data_URI_header
- ;
- if (type === null) {
- type = "application/octet-stream";
- }
- if (blob instanceof FakeBlob) {
- data_URI_header = "data:" + type;
- if (blob.encoding === "base64") {
- return data_URI_header + ";base64," + blob.data;
- } else if (blob.encoding === "URI") {
- return data_URI_header + "," + decodeURIComponent(blob.data);
- } if (btoa) {
- return data_URI_header + ";base64," + btoa(blob.data);
- } else {
- return data_URI_header + "," + encodeURIComponent(blob.data);
- }
- } else if (real_create_object_URL) {
- return real_create_object_URL.call(real_URL, blob);
- }
- };
- URL.revokeObjectURL = function(object_URL) {
- if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
- real_revoke_object_URL.call(real_URL, object_URL);
- }
- };
- FBB_proto.append = function(data/*, endings*/) {
- var bb = this.data;
- // decode data to a binary string
- if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
- var
- str = ""
- , buf = new Uint8Array(data)
- , i = 0
- , buf_len = buf.length
- ;
- for (; i < buf_len; i++) {
- str += String.fromCharCode(buf[i]);
- }
- bb.push(str);
- } else if (get_class(data) === "Blob" || get_class(data) === "File") {
- if (FileReaderSync) {
- var fr = new FileReaderSync;
- bb.push(fr.readAsBinaryString(data));
- } else {
- // async FileReader won't work as BlobBuilder is sync
- throw new FileException("NOT_READABLE_ERR");
- }
- } else if (data instanceof FakeBlob) {
- if (data.encoding === "base64" && atob) {
- bb.push(atob(data.data));
- } else if (data.encoding === "URI") {
- bb.push(decodeURIComponent(data.data));
- } else if (data.encoding === "raw") {
- bb.push(data.data);
- }
- } else {
- if (typeof data !== "string") {
- data += ""; // convert unsupported types to strings
- }
- // decode UTF-16 to binary string
- bb.push(unescape(encodeURIComponent(data)));
- }
- };
- FBB_proto.getBlob = function(type) {
- if (!arguments.length) {
- type = null;
- }
- return new FakeBlob(this.data.join(""), type, "raw");
- };
- FBB_proto.toString = function() {
- return "[object BlobBuilder]";
- };
- FB_proto.slice = function(start, end, type) {
- var args = arguments.length;
- if (args < 3) {
- type = null;
- }
- return new FakeBlob(
- this.data.slice(start, args > 1 ? end : this.data.length)
- , type
- , this.encoding
- );
- };
- FB_proto.toString = function() {
- return "[object Blob]";
- };
- FB_proto.close = function() {
- this.size = 0;
- delete this.data;
- };
- return FakeBlobBuilder;
- }(view));
-
- view.Blob = function(blobParts, options) {
- var type = options ? (options.type || "") : "";
- var builder = new BlobBuilder();
- if (blobParts) {
- for (var i = 0, len = blobParts.length; i < len; i++) {
- if (Uint8Array && blobParts[i] instanceof Uint8Array) {
- builder.append(blobParts[i].buffer);
- }
- else {
- builder.append(blobParts[i]);
- }
- }
- }
- var blob = builder.getBlob(type);
- if (!blob.slice && blob.webkitSlice) {
- blob.slice = blob.webkitSlice;
- }
- return blob;
- };
-
- var getPrototypeOf = Object.getPrototypeOf || function(object) {
- return object.__proto__;
- };
- view.Blob.prototype = getPrototypeOf(new view.Blob());
- }(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));
-
- /* FileSaver.js
- * A saveAs() FileSaver implementation.
- * 1.1.20151003
- *
- * By Eli Grey, http://eligrey.com
- * License: MIT
- * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
- */
+ _results = [];
- /*global self */
- /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
-
- /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
-
- var saveAs = saveAs || (function(view) {
- "use strict";
- // IE <10 is explicitly unsupported
- if (typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
- return;
- }
- var
- doc = view.document
- // only get URL when necessary in case Blob.js hasn't overridden it yet
- , get_URL = function() {
- return view.URL || view.webkitURL || view;
- }
- , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
- , can_use_save_link = "download" in save_link
- , click = function(node) {
- var event = new MouseEvent("click");
- node.dispatchEvent(event);
- }
- , is_safari = /Version\/[\d\.]+.*Safari/.test(navigator.userAgent)
- , webkit_req_fs = view.webkitRequestFileSystem
- , req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
- , throw_outside = function(ex) {
- (view.setImmediate || view.setTimeout)(function() {
- throw ex;
- }, 0);
- }
- , force_saveable_type = "application/octet-stream"
- , fs_min_size = 0
- // See https://code.google.com/p/chromium/issues/detail?id=375297#c7 and
- // https://github.com/eligrey/FileSaver.js/commit/485930a#commitcomment-8768047
- // for the reasoning behind the timeout and revocation flow
- , arbitrary_revoke_timeout = 500 // in ms
- , revoke = function(file) {
- var revoker = function() {
- if (typeof file === "string") { // file is an object URL
- get_URL().revokeObjectURL(file);
- } else { // file is a File
- file.remove();
- }
- };
- if (view.chrome) {
- revoker();
- } else {
- setTimeout(revoker, arbitrary_revoke_timeout);
- }
- }
- , dispatch = function(filesaver, event_types, event) {
- event_types = [].concat(event_types);
- var i = event_types.length;
- while (i--) {
- var listener = filesaver["on" + event_types[i]];
- if (typeof listener === "function") {
- try {
- listener.call(filesaver, event || filesaver);
- } catch (ex) {
- throw_outside(ex);
- }
- }
- }
- }
- , auto_bom = function(blob) {
- // prepend BOM for UTF-8 XML and text/* types (including HTML)
- if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
- return new Blob(["\ufeff", blob], {type: blob.type});
- }
- return blob;
- }
- , FileSaver = function(blob, name, no_auto_bom) {
- if (!no_auto_bom) {
- blob = auto_bom(blob);
- }
- // First try a.download, then web filesystem, then object URLs
- var
- filesaver = this
- , type = blob.type
- , blob_changed = false
- , object_url
- , target_view
- , dispatch_all = function() {
- dispatch(filesaver, "writestart progress write writeend".split(" "));
- }
- // on any filesys errors revert to saving with object URLs
- , fs_error = function() {
- if (target_view && is_safari && typeof FileReader !== "undefined") {
- // Safari doesn't allow downloading of blob urls
- var reader = new FileReader();
- reader.onloadend = function() {
- var base64Data = reader.result;
- target_view.location.href = "data:attachment/file" + base64Data.slice(base64Data.search(/[,;]/));
- filesaver.readyState = filesaver.DONE;
- dispatch_all();
- };
- reader.readAsDataURL(blob);
- filesaver.readyState = filesaver.INIT;
- return;
- }
- // don't create more object URLs than needed
- if (blob_changed || !object_url) {
- object_url = get_URL().createObjectURL(blob);
- }
- if (target_view) {
- target_view.location.href = object_url;
- } else {
- var new_tab = view.open(object_url, "_blank");
- if (new_tab == undefined && is_safari) {
- //Apple do not allow window.open, see http://bit.ly/1kZffRI
- view.location.href = object_url
- }
- }
- filesaver.readyState = filesaver.DONE;
- dispatch_all();
- revoke(object_url);
- }
- , abortable = function(func) {
- return function() {
- if (filesaver.readyState !== filesaver.DONE) {
- return func.apply(this, arguments);
- }
- };
- }
- , create_if_not_found = {create: true, exclusive: false}
- , slice
- ;
- filesaver.readyState = filesaver.INIT;
- if (!name) {
- name = "download";
- }
- if (can_use_save_link) {
- object_url = get_URL().createObjectURL(blob);
- setTimeout(function() {
- save_link.href = object_url;
- save_link.download = name;
- click(save_link);
- dispatch_all();
- revoke(object_url);
- filesaver.readyState = filesaver.DONE;
- });
- return;
- }
- // Object and web filesystem URLs have a problem saving in Google Chrome when
- // viewed in a tab, so I force save with application/octet-stream
- // http://code.google.com/p/chromium/issues/detail?id=91158
- // Update: Google errantly closed 91158, I submitted it again:
- // https://code.google.com/p/chromium/issues/detail?id=389642
- if (view.chrome && type && type !== force_saveable_type) {
- slice = blob.slice || blob.webkitSlice;
- blob = slice.call(blob, 0, blob.size, force_saveable_type);
- blob_changed = true;
- }
- // Since I can't be sure that the guessed media type will trigger a download
- // in WebKit, I append .download to the filename.
- // https://bugs.webkit.org/show_bug.cgi?id=65440
- if (webkit_req_fs && name !== "download") {
- name += ".download";
- }
- if (type === force_saveable_type || webkit_req_fs) {
- target_view = view;
- }
- if (!req_fs) {
- fs_error();
- return;
- }
- fs_min_size += blob.size;
- req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
- fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
- var save = function() {
- dir.getFile(name, create_if_not_found, abortable(function(file) {
- file.createWriter(abortable(function(writer) {
- writer.onwriteend = function(event) {
- target_view.location.href = file.toURL();
- filesaver.readyState = filesaver.DONE;
- dispatch(filesaver, "writeend", event);
- revoke(file);
- };
- writer.onerror = function() {
- var error = writer.error;
- if (error.code !== error.ABORT_ERR) {
- fs_error();
- }
- };
- "writestart progress write abort".split(" ").forEach(function(event) {
- writer["on" + event] = filesaver["on" + event];
- });
- writer.write(blob);
- filesaver.abort = function() {
- writer.abort();
- filesaver.readyState = filesaver.DONE;
- };
- filesaver.readyState = filesaver.WRITING;
- }), fs_error);
- }), fs_error);
- };
- dir.getFile(name, {create: false}, abortable(function(file) {
- // delete file if it already exists
- file.remove();
- save();
- }), abortable(function(ex) {
- if (ex.code === ex.NOT_FOUND_ERR) {
- save();
- } else {
- fs_error();
- }
- }));
- }), fs_error);
- }), fs_error);
- }
- , FS_proto = FileSaver.prototype
- , saveAs = function(blob, name, no_auto_bom) {
- return new FileSaver(blob, name, no_auto_bom);
- }
- ;
- // IE 10+ (native saveAs)
- if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
- return function(blob, name, no_auto_bom) {
- if (!no_auto_bom) {
- blob = auto_bom(blob);
- }
- return navigator.msSaveOrOpenBlob(blob, name || "download");
- };
- }
-
- FS_proto.abort = function() {
- var filesaver = this;
- filesaver.readyState = filesaver.DONE;
- dispatch(filesaver, "abort");
- };
- FS_proto.readyState = FS_proto.INIT = 0;
- FS_proto.WRITING = 1;
- FS_proto.DONE = 2;
-
- FS_proto.error =
- FS_proto.onwritestart =
- FS_proto.onprogress =
- FS_proto.onwrite =
- FS_proto.onabort =
- FS_proto.onerror =
- FS_proto.onwriteend =
- null;
-
- return saveAs;
- }(
- typeof self !== "undefined" && self
- || typeof window !== "undefined" && window
- || this.content
- ));
- // `self` is undefined in Firefox for Android content script context
- // while `this` is nsIContentFrameMessageManager
- // with an attribute `content` that corresponds to the window
-
- if (typeof module !== "undefined" && module.exports) {
- module.exports.saveAs = saveAs;
- } else if ((typeof define !== "undefined" && define !== null) && (define.amd != null)) {
- define([], function() {
- return saveAs;
- });
- }
+ for (_i = 0, _len = bytes.length; _i < _len; _i++) {
+ byte = bytes[_i];
- /*
- * Copyright (c) 2012 chick307
- *
- * Licensed under the MIT License.
- * http://opensource.org/licenses/mit-license
- */
+ _results.push(this.writeByte(byte));
+ }
- void function(global, callback) {
- if (typeof module === 'object') {
- module.exports = callback();
- } else if (typeof define === 'function') {
- define(callback);
- } else {
- global.adler32cs = callback();
- }
- }(jsPDF, function() {
- var _hasArrayBuffer = typeof ArrayBuffer === 'function' &&
- typeof Uint8Array === 'function';
-
- var _Buffer = null, _isBuffer = (function() {
- if (!_hasArrayBuffer)
- return function _isBuffer() { return false };
-
- try {
- var buffer = {};
- if (typeof buffer.Buffer === 'function')
- _Buffer = buffer.Buffer;
- } catch (error) {}
-
- return function _isBuffer(value) {
- return value instanceof ArrayBuffer ||
- _Buffer !== null && value instanceof _Buffer;
- };
- }());
-
- var _utf8ToBinary = (function() {
- if (_Buffer !== null) {
- return function _utf8ToBinary(utf8String) {
- return new _Buffer(utf8String, 'utf8').toString('binary');
- };
- } else {
- return function _utf8ToBinary(utf8String) {
- return unescape(encodeURIComponent(utf8String));
- };
- }
- }());
-
- var MOD = 65521;
-
- var _update = function _update(checksum, binaryString) {
- var a = checksum & 0xFFFF, b = checksum >>> 16;
- for (var i = 0, length = binaryString.length; i < length; i++) {
- a = (a + (binaryString.charCodeAt(i) & 0xFF)) % MOD;
- b = (b + a) % MOD;
- }
- return (b << 16 | a) >>> 0;
- };
-
- var _updateUint8Array = function _updateUint8Array(checksum, uint8Array) {
- var a = checksum & 0xFFFF, b = checksum >>> 16;
- for (var i = 0, length = uint8Array.length, x; i < length; i++) {
- a = (a + uint8Array[i]) % MOD;
- b = (b + a) % MOD;
- }
- return (b << 16 | a) >>> 0
- };
-
- var exports = {};
-
- var Adler32 = exports.Adler32 = (function() {
- var ctor = function Adler32(checksum) {
- if (!(this instanceof ctor)) {
- throw new TypeError(
- 'Constructor cannot called be as a function.');
- }
- if (!isFinite(checksum = checksum == null ? 1 : +checksum)) {
- throw new Error(
- 'First arguments needs to be a finite number.');
- }
- this.checksum = checksum >>> 0;
- };
-
- var proto = ctor.prototype = {};
- proto.constructor = ctor;
-
- ctor.from = function(from) {
- from.prototype = proto;
- return from;
- }(function from(binaryString) {
- if (!(this instanceof ctor)) {
- throw new TypeError(
- 'Constructor cannot called be as a function.');
- }
- if (binaryString == null)
- throw new Error('First argument needs to be a string.');
- this.checksum = _update(1, binaryString.toString());
- });
-
- ctor.fromUtf8 = function(fromUtf8) {
- fromUtf8.prototype = proto;
- return fromUtf8;
- }(function fromUtf8(utf8String) {
- if (!(this instanceof ctor)) {
- throw new TypeError(
- 'Constructor cannot called be as a function.');
- }
- if (utf8String == null)
- throw new Error('First argument needs to be a string.');
- var binaryString = _utf8ToBinary(utf8String.toString());
- this.checksum = _update(1, binaryString);
- });
-
- if (_hasArrayBuffer) {
- ctor.fromBuffer = function(fromBuffer) {
- fromBuffer.prototype = proto;
- return fromBuffer;
- }(function fromBuffer(buffer) {
- if (!(this instanceof ctor)) {
- throw new TypeError(
- 'Constructor cannot called be as a function.');
- }
- if (!_isBuffer(buffer))
- throw new Error('First argument needs to be ArrayBuffer.');
- var array = new Uint8Array(buffer);
- return this.checksum = _updateUint8Array(1, array);
- });
- }
-
- proto.update = function update(binaryString) {
- if (binaryString == null)
- throw new Error('First argument needs to be a string.');
- binaryString = binaryString.toString();
- return this.checksum = _update(this.checksum, binaryString);
- };
-
- proto.updateUtf8 = function updateUtf8(utf8String) {
- if (utf8String == null)
- throw new Error('First argument needs to be a string.');
- var binaryString = _utf8ToBinary(utf8String.toString());
- return this.checksum = _update(this.checksum, binaryString);
- };
-
- if (_hasArrayBuffer) {
- proto.updateBuffer = function updateBuffer(buffer) {
- if (!_isBuffer(buffer))
- throw new Error('First argument needs to be ArrayBuffer.');
- var array = new Uint8Array(buffer);
- return this.checksum = _updateUint8Array(this.checksum, array);
- };
- }
-
- proto.clone = function clone() {
- return new Adler32(this.checksum);
- };
-
- return ctor;
- }());
-
- exports.from = function from(binaryString) {
- if (binaryString == null)
- throw new Error('First argument needs to be a string.');
- return _update(1, binaryString.toString());
- };
-
- exports.fromUtf8 = function fromUtf8(utf8String) {
- if (utf8String == null)
- throw new Error('First argument needs to be a string.');
- var binaryString = _utf8ToBinary(utf8String.toString());
- return _update(1, binaryString);
- };
-
- if (_hasArrayBuffer) {
- exports.fromBuffer = function fromBuffer(buffer) {
- if (!_isBuffer(buffer))
- throw new Error('First argument need to be ArrayBuffer.');
- var array = new Uint8Array(buffer);
- return _updateUint8Array(1, array);
- };
- }
-
- return exports;
- });
+ return _results;
+ };
- /**
- * CssColors
- * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv
- *
- * Licensed under the MIT License.
- * http://opensource.org/licenses/mit-license
- */
+ return Data;
+ }();
- /**
- * Usage CssColors('red');
- * Returns RGB hex color with '#' prefix
- */
+ var Directory = function () {
+ var checksum;
+ /*****************************************************************************************************/
- var CssColors = {};
- CssColors._colorsTable = {
- "aliceblue" : "#f0f8ff",
- "antiquewhite" : "#faebd7",
- "aqua" : "#00ffff",
- "aquamarine" : "#7fffd4",
- "azure" : "#f0ffff",
- "beige" : "#f5f5dc",
- "bisque" : "#ffe4c4",
- "black" : "#000000",
- "blanchedalmond" : "#ffebcd",
- "blue" : "#0000ff",
- "blueviolet" : "#8a2be2",
- "brown" : "#a52a2a",
- "burlywood" : "#deb887",
- "cadetblue" : "#5f9ea0",
- "chartreuse" : "#7fff00",
- "chocolate" : "#d2691e",
- "coral" : "#ff7f50",
- "cornflowerblue" : "#6495ed",
- "cornsilk" : "#fff8dc",
- "crimson" : "#dc143c",
- "cyan" : "#00ffff",
- "darkblue" : "#00008b",
- "darkcyan" : "#008b8b",
- "darkgoldenrod" : "#b8860b",
- "darkgray" : "#a9a9a9",
- "darkgreen" : "#006400",
- "darkkhaki" : "#bdb76b",
- "darkmagenta" : "#8b008b",
- "darkolivegreen" : "#556b2f",
- "darkorange" : "#ff8c00",
- "darkorchid" : "#9932cc",
- "darkred" : "#8b0000",
- "darksalmon" : "#e9967a",
- "darkseagreen" : "#8fbc8f",
- "darkslateblue" : "#483d8b",
- "darkslategray" : "#2f4f4f",
- "darkturquoise" : "#00ced1",
- "darkviolet" : "#9400d3",
- "deeppink" : "#ff1493",
- "deepskyblue" : "#00bfff",
- "dimgray" : "#696969",
- "dodgerblue" : "#1e90ff",
- "firebrick" : "#b22222",
- "floralwhite" : "#fffaf0",
- "forestgreen" : "#228b22",
- "fuchsia" : "#ff00ff",
- "gainsboro" : "#dcdcdc",
- "ghostwhite" : "#f8f8ff",
- "gold" : "#ffd700",
- "goldenrod" : "#daa520",
- "gray" : "#808080",
- "green" : "#008000",
- "greenyellow" : "#adff2f",
- "honeydew" : "#f0fff0",
- "hotpink" : "#ff69b4",
- "indianred " : "#cd5c5c",
- "indigo" : "#4b0082",
- "ivory" : "#fffff0",
- "khaki" : "#f0e68c",
- "lavender" : "#e6e6fa",
- "lavenderblush" : "#fff0f5",
- "lawngreen" : "#7cfc00",
- "lemonchiffon" : "#fffacd",
- "lightblue" : "#add8e6",
- "lightcoral" : "#f08080",
- "lightcyan" : "#e0ffff",
- "lightgoldenrodyellow" : "#fafad2",
- "lightgrey" : "#d3d3d3",
- "lightgreen" : "#90ee90",
- "lightpink" : "#ffb6c1",
- "lightsalmon" : "#ffa07a",
- "lightseagreen" : "#20b2aa",
- "lightskyblue" : "#87cefa",
- "lightslategray" : "#778899",
- "lightsteelblue" : "#b0c4de",
- "lightyellow" : "#ffffe0",
- "lime" : "#00ff00",
- "limegreen" : "#32cd32",
- "linen" : "#faf0e6",
- "magenta" : "#ff00ff",
- "maroon" : "#800000",
- "mediumaquamarine" : "#66cdaa",
- "mediumblue" : "#0000cd",
- "mediumorchid" : "#ba55d3",
- "mediumpurple" : "#9370d8",
- "mediumseagreen" : "#3cb371",
- "mediumslateblue" : "#7b68ee",
- "mediumspringgreen" : "#00fa9a",
- "mediumturquoise" : "#48d1cc",
- "mediumvioletred" : "#c71585",
- "midnightblue" : "#191970",
- "mintcream" : "#f5fffa",
- "mistyrose" : "#ffe4e1",
- "moccasin" : "#ffe4b5",
- "navajowhite" : "#ffdead",
- "navy" : "#000080",
- "oldlace" : "#fdf5e6",
- "olive" : "#808000",
- "olivedrab" : "#6b8e23",
- "orange" : "#ffa500",
- "orangered" : "#ff4500",
- "orchid" : "#da70d6",
- "palegoldenrod" : "#eee8aa",
- "palegreen" : "#98fb98",
- "paleturquoise" : "#afeeee",
- "palevioletred" : "#d87093",
- "papayawhip" : "#ffefd5",
- "peachpuff" : "#ffdab9",
- "peru" : "#cd853f",
- "pink" : "#ffc0cb",
- "plum" : "#dda0dd",
- "powderblue" : "#b0e0e6",
- "purple" : "#800080",
- "red" : "#ff0000",
- "rosybrown" : "#bc8f8f",
- "royalblue" : "#4169e1",
- "saddlebrown" : "#8b4513",
- "salmon" : "#fa8072",
- "sandybrown" : "#f4a460",
- "seagreen" : "#2e8b57",
- "seashell" : "#fff5ee",
- "sienna" : "#a0522d",
- "silver" : "#c0c0c0",
- "skyblue" : "#87ceeb",
- "slateblue" : "#6a5acd",
- "slategray" : "#708090",
- "snow" : "#fffafa",
- "springgreen" : "#00ff7f",
- "steelblue" : "#4682b4",
- "tan" : "#d2b48c",
- "teal" : "#008080",
- "thistle" : "#d8bfd8",
- "tomato" : "#ff6347",
- "turquoise" : "#40e0d0",
- "violet" : "#ee82ee",
- "wheat" : "#f5deb3",
- "white" : "#ffffff",
- "whitesmoke" : "#f5f5f5",
- "yellow" : "#ffff00",
- "yellowgreen" : "#9acd32"
- };
+ /* function : Directory generator */
- CssColors.colorNameToHex = function(color) {
- color = color.toLowerCase();
- if (typeof this._colorsTable[color] != 'undefined')
- return this._colorsTable[color];
+ /* comment : Initialize the offset, tag, length, and checksum for each table for the font to be used.*/
- return false;
- };
+ /*****************************************************************************************************/
+
+ function Directory(data) {
+ var entry, i, _i, _ref;
+
+ this.scalarType = data.readInt();
+ this.tableCount = data.readShort();
+ this.searchRange = data.readShort();
+ this.entrySelector = data.readShort();
+ this.rangeShift = data.readShort();
+ this.tables = {};
+
+ for (i = _i = 0, _ref = this.tableCount; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
+ entry = {
+ tag: data.readString(4),
+ checksum: data.readInt(),
+ offset: data.readInt(),
+ length: data.readInt()
+ };
+ this.tables[entry.tag] = entry;
+ }
+ }
+ /********************************************************************************************************/
+
+ /* function : encode */
+
+ /* comment : It encodes and stores the font table object and information used for the directory object. */
+
+ /********************************************************************************************************/
+
+
+ Directory.prototype.encode = function (tables) {
+ var adjustment, directory, directoryLength, entrySelector, headOffset, log2, offset, rangeShift, searchRange, sum, table, tableCount, tableData, tag;
+ tableCount = Object.keys(tables).length;
+ log2 = Math.log(2);
+ searchRange = Math.floor(Math.log(tableCount) / log2) * 16;
+ entrySelector = Math.floor(searchRange / log2);
+ rangeShift = tableCount * 16 - searchRange;
+ directory = new Data();
+ directory.writeInt(this.scalarType);
+ directory.writeShort(tableCount);
+ directory.writeShort(searchRange);
+ directory.writeShort(entrySelector);
+ directory.writeShort(rangeShift);
+ directoryLength = tableCount * 16;
+ offset = directory.pos + directoryLength;
+ headOffset = null;
+ tableData = [];
+
+ for (tag in tables) {
+ table = tables[tag];
+ directory.writeString(tag);
+ directory.writeInt(checksum(table));
+ directory.writeInt(offset);
+ directory.writeInt(table.length);
+ tableData = tableData.concat(table);
+
+ if (tag === "head") {
+ headOffset = offset;
+ }
- /*
- Deflate.js - https://github.com/gildas-lormeau/zip.js
- Copyright (c) 2013 Gildas Lormeau. All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
-
- 1. Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
-
- 2. Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in
- the documentation and/or other materials provided with the distribution.
-
- 3. The names of the authors may not be used to endorse or promote products
- derived from this software without specific prior written permission.
-
- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
- FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
- INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
- INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
- OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
- EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
+ offset += table.length;
- /*
- * This program is based on JZlib 1.0.2 ymnk, JCraft,Inc.
- * JZlib is based on zlib-1.1.3, so all credit should go authors
- * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
- * and contributors of zlib.
- */
+ while (offset % 4) {
+ tableData.push(0);
+ offset++;
+ }
+ }
- var Deflater = (function(obj) {
-
- // Global
-
- var MAX_BITS = 15;
- var D_CODES = 30;
- var BL_CODES = 19;
-
- var LENGTH_CODES = 29;
- var LITERALS = 256;
- var L_CODES = (LITERALS + 1 + LENGTH_CODES);
- var HEAP_SIZE = (2 * L_CODES + 1);
-
- var END_BLOCK = 256;
-
- // Bit length codes must not exceed MAX_BL_BITS bits
- var MAX_BL_BITS = 7;
-
- // repeat previous bit length 3-6 times (2 bits of repeat count)
- var REP_3_6 = 16;
-
- // repeat a zero length 3-10 times (3 bits of repeat count)
- var REPZ_3_10 = 17;
-
- // repeat a zero length 11-138 times (7 bits of repeat count)
- var REPZ_11_138 = 18;
-
- // The lengths of the bit length codes are sent in order of decreasing
- // probability, to avoid transmitting the lengths for unused bit
- // length codes.
-
- var Buf_size = 8 * 2;
-
- // JZlib version : "1.0.2"
- var Z_DEFAULT_COMPRESSION = -1;
-
- // compression strategy
- var Z_FILTERED = 1;
- var Z_HUFFMAN_ONLY = 2;
- var Z_DEFAULT_STRATEGY = 0;
-
- var Z_NO_FLUSH = 0;
- var Z_PARTIAL_FLUSH = 1;
- var Z_FULL_FLUSH = 3;
- var Z_FINISH = 4;
-
- var Z_OK = 0;
- var Z_STREAM_END = 1;
- var Z_NEED_DICT = 2;
- var Z_STREAM_ERROR = -2;
- var Z_DATA_ERROR = -3;
- var Z_BUF_ERROR = -5;
-
- // Tree
-
- // see definition of array dist_code below
- var _dist_code = [ 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
- 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
- 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
- 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
- 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
- 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17, 18, 18, 19, 19,
- 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
- 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
- 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
- 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
- 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29,
- 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
- 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 ];
-
- function Tree() {
- var that = this;
-
- // dyn_tree; // the dynamic tree
- // max_code; // largest code with non zero frequency
- // stat_desc; // the corresponding static tree
-
- // Compute the optimal bit lengths for a tree and update the total bit
- // length
- // for the current block.
- // IN assertion: the fields freq and dad are set, heap[heap_max] and
- // above are the tree nodes sorted by increasing frequency.
- // OUT assertions: the field len is set to the optimal bit length, the
- // array bl_count contains the frequencies for each bit length.
- // The length opt_len is updated; static_len is also updated if stree is
- // not null.
- function gen_bitlen(s) {
- var tree = that.dyn_tree;
- var stree = that.stat_desc.static_tree;
- var extra = that.stat_desc.extra_bits;
- var base = that.stat_desc.extra_base;
- var max_length = that.stat_desc.max_length;
- var h; // heap index
- var n, m; // iterate over the tree elements
- var bits; // bit length
- var xbits; // extra bits
- var f; // frequency
- var overflow = 0; // number of elements with bit length too large
-
- for (bits = 0; bits <= MAX_BITS; bits++)
- s.bl_count[bits] = 0;
-
- // In a first pass, compute the optimal bit lengths (which may
- // overflow in the case of the bit length tree).
- tree[s.heap[s.heap_max] * 2 + 1] = 0; // root of the heap
-
- for (h = s.heap_max + 1; h < HEAP_SIZE; h++) {
- n = s.heap[h];
- bits = tree[tree[n * 2 + 1] * 2 + 1] + 1;
- if (bits > max_length) {
- bits = max_length;
- overflow++;
- }
- tree[n * 2 + 1] = bits;
- // We overwrite tree[n*2+1] which is no longer needed
-
- if (n > that.max_code)
- continue; // not a leaf node
-
- s.bl_count[bits]++;
- xbits = 0;
- if (n >= base)
- xbits = extra[n - base];
- f = tree[n * 2];
- s.opt_len += f * (bits + xbits);
- if (stree)
- s.static_len += f * (stree[n * 2 + 1] + xbits);
- }
- if (overflow === 0)
- return;
-
- // This happens for example on obj2 and pic of the Calgary corpus
- // Find the first bit length which could increase:
- do {
- bits = max_length - 1;
- while (s.bl_count[bits] === 0)
- bits--;
- s.bl_count[bits]--; // move one leaf down the tree
- s.bl_count[bits + 1] += 2; // move one overflow item as its brother
- s.bl_count[max_length]--;
- // The brother of the overflow item also moves one step up,
- // but this does not affect bl_count[max_length]
- overflow -= 2;
- } while (overflow > 0);
-
- for (bits = max_length; bits !== 0; bits--) {
- n = s.bl_count[bits];
- while (n !== 0) {
- m = s.heap[--h];
- if (m > that.max_code)
- continue;
- if (tree[m * 2 + 1] != bits) {
- s.opt_len += (bits - tree[m * 2 + 1]) * tree[m * 2];
- tree[m * 2 + 1] = bits;
- }
- n--;
- }
- }
- }
-
- // Reverse the first len bits of a code, using straightforward code (a
- // faster
- // method would use a table)
- // IN assertion: 1 <= len <= 15
- function bi_reverse(code, // the value to invert
- len // its bit length
- ) {
- var res = 0;
- do {
- res |= code & 1;
- code >>>= 1;
- res <<= 1;
- } while (--len > 0);
- return res >>> 1;
- }
-
- // Generate the codes for a given tree and bit counts (which need not be
- // optimal).
- // IN assertion: the array bl_count contains the bit length statistics for
- // the given tree and the field len is set for all tree elements.
- // OUT assertion: the field code is set for all tree elements of non
- // zero code length.
- function gen_codes(tree, // the tree to decorate
- max_code, // largest code with non zero frequency
- bl_count // number of codes at each bit length
- ) {
- var next_code = []; // next code value for each
- // bit length
- var code = 0; // running code value
- var bits; // bit index
- var n; // code index
- var len;
-
- // The distribution counts are first used to generate the code values
- // without bit reversal.
- for (bits = 1; bits <= MAX_BITS; bits++) {
- next_code[bits] = code = ((code + bl_count[bits - 1]) << 1);
- }
-
- // Check that the bit counts in bl_count are consistent. The last code
- // must be all ones.
- // Assert (code + bl_count[MAX_BITS]-1 == (1<= 1; n--)
- s.pqdownheap(tree, n);
-
- // Construct the Huffman tree by repeatedly combining the least two
- // frequent nodes.
-
- node = elems; // next internal node of the tree
- do {
- // n = node of least frequency
- n = s.heap[1];
- s.heap[1] = s.heap[s.heap_len--];
- s.pqdownheap(tree, 1);
- m = s.heap[1]; // m = node of next least frequency
-
- s.heap[--s.heap_max] = n; // keep the nodes sorted by frequency
- s.heap[--s.heap_max] = m;
-
- // Create a new node father of n and m
- tree[node * 2] = (tree[n * 2] + tree[m * 2]);
- s.depth[node] = Math.max(s.depth[n], s.depth[m]) + 1;
- tree[n * 2 + 1] = tree[m * 2 + 1] = node;
-
- // and insert the new node in the heap
- s.heap[1] = node++;
- s.pqdownheap(tree, 1);
- } while (s.heap_len >= 2);
-
- s.heap[--s.heap_max] = s.heap[1];
-
- // At this point, the fields freq and dad are set. We can now
- // generate the bit lengths.
-
- gen_bitlen(s);
-
- // The field len is now set, we can generate the bit codes
- gen_codes(tree, that.max_code, s.bl_count);
- };
-
- }
-
- Tree._length_code = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16,
- 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20,
- 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
- 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
- 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
- 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28 ];
-
- Tree.base_length = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 0 ];
-
- Tree.base_dist = [ 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384,
- 24576 ];
-
- // Mapping from a distance to a distance code. dist is the distance - 1 and
- // must not have side effects. _dist_code[256] and _dist_code[257] are never
- // used.
- Tree.d_code = function(dist) {
- return ((dist) < 256 ? _dist_code[dist] : _dist_code[256 + ((dist) >>> 7)]);
- };
-
- // extra bits for each length code
- Tree.extra_lbits = [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 ];
-
- // extra bits for each distance code
- Tree.extra_dbits = [ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 ];
-
- // extra bits for each bit length code
- Tree.extra_blbits = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7 ];
-
- Tree.bl_order = [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ];
-
- // StaticTree
-
- function StaticTree(static_tree, extra_bits, extra_base, elems, max_length) {
- var that = this;
- that.static_tree = static_tree;
- that.extra_bits = extra_bits;
- that.extra_base = extra_base;
- that.elems = elems;
- that.max_length = max_length;
- }
-
- StaticTree.static_ltree = [ 12, 8, 140, 8, 76, 8, 204, 8, 44, 8, 172, 8, 108, 8, 236, 8, 28, 8, 156, 8, 92, 8, 220, 8, 60, 8, 188, 8, 124, 8, 252, 8, 2, 8,
- 130, 8, 66, 8, 194, 8, 34, 8, 162, 8, 98, 8, 226, 8, 18, 8, 146, 8, 82, 8, 210, 8, 50, 8, 178, 8, 114, 8, 242, 8, 10, 8, 138, 8, 74, 8, 202, 8, 42,
- 8, 170, 8, 106, 8, 234, 8, 26, 8, 154, 8, 90, 8, 218, 8, 58, 8, 186, 8, 122, 8, 250, 8, 6, 8, 134, 8, 70, 8, 198, 8, 38, 8, 166, 8, 102, 8, 230, 8,
- 22, 8, 150, 8, 86, 8, 214, 8, 54, 8, 182, 8, 118, 8, 246, 8, 14, 8, 142, 8, 78, 8, 206, 8, 46, 8, 174, 8, 110, 8, 238, 8, 30, 8, 158, 8, 94, 8,
- 222, 8, 62, 8, 190, 8, 126, 8, 254, 8, 1, 8, 129, 8, 65, 8, 193, 8, 33, 8, 161, 8, 97, 8, 225, 8, 17, 8, 145, 8, 81, 8, 209, 8, 49, 8, 177, 8, 113,
- 8, 241, 8, 9, 8, 137, 8, 73, 8, 201, 8, 41, 8, 169, 8, 105, 8, 233, 8, 25, 8, 153, 8, 89, 8, 217, 8, 57, 8, 185, 8, 121, 8, 249, 8, 5, 8, 133, 8,
- 69, 8, 197, 8, 37, 8, 165, 8, 101, 8, 229, 8, 21, 8, 149, 8, 85, 8, 213, 8, 53, 8, 181, 8, 117, 8, 245, 8, 13, 8, 141, 8, 77, 8, 205, 8, 45, 8,
- 173, 8, 109, 8, 237, 8, 29, 8, 157, 8, 93, 8, 221, 8, 61, 8, 189, 8, 125, 8, 253, 8, 19, 9, 275, 9, 147, 9, 403, 9, 83, 9, 339, 9, 211, 9, 467, 9,
- 51, 9, 307, 9, 179, 9, 435, 9, 115, 9, 371, 9, 243, 9, 499, 9, 11, 9, 267, 9, 139, 9, 395, 9, 75, 9, 331, 9, 203, 9, 459, 9, 43, 9, 299, 9, 171, 9,
- 427, 9, 107, 9, 363, 9, 235, 9, 491, 9, 27, 9, 283, 9, 155, 9, 411, 9, 91, 9, 347, 9, 219, 9, 475, 9, 59, 9, 315, 9, 187, 9, 443, 9, 123, 9, 379,
- 9, 251, 9, 507, 9, 7, 9, 263, 9, 135, 9, 391, 9, 71, 9, 327, 9, 199, 9, 455, 9, 39, 9, 295, 9, 167, 9, 423, 9, 103, 9, 359, 9, 231, 9, 487, 9, 23,
- 9, 279, 9, 151, 9, 407, 9, 87, 9, 343, 9, 215, 9, 471, 9, 55, 9, 311, 9, 183, 9, 439, 9, 119, 9, 375, 9, 247, 9, 503, 9, 15, 9, 271, 9, 143, 9,
- 399, 9, 79, 9, 335, 9, 207, 9, 463, 9, 47, 9, 303, 9, 175, 9, 431, 9, 111, 9, 367, 9, 239, 9, 495, 9, 31, 9, 287, 9, 159, 9, 415, 9, 95, 9, 351, 9,
- 223, 9, 479, 9, 63, 9, 319, 9, 191, 9, 447, 9, 127, 9, 383, 9, 255, 9, 511, 9, 0, 7, 64, 7, 32, 7, 96, 7, 16, 7, 80, 7, 48, 7, 112, 7, 8, 7, 72, 7,
- 40, 7, 104, 7, 24, 7, 88, 7, 56, 7, 120, 7, 4, 7, 68, 7, 36, 7, 100, 7, 20, 7, 84, 7, 52, 7, 116, 7, 3, 8, 131, 8, 67, 8, 195, 8, 35, 8, 163, 8,
- 99, 8, 227, 8 ];
-
- StaticTree.static_dtree = [ 0, 5, 16, 5, 8, 5, 24, 5, 4, 5, 20, 5, 12, 5, 28, 5, 2, 5, 18, 5, 10, 5, 26, 5, 6, 5, 22, 5, 14, 5, 30, 5, 1, 5, 17, 5, 9, 5,
- 25, 5, 5, 5, 21, 5, 13, 5, 29, 5, 3, 5, 19, 5, 11, 5, 27, 5, 7, 5, 23, 5 ];
-
- StaticTree.static_l_desc = new StaticTree(StaticTree.static_ltree, Tree.extra_lbits, LITERALS + 1, L_CODES, MAX_BITS);
-
- StaticTree.static_d_desc = new StaticTree(StaticTree.static_dtree, Tree.extra_dbits, 0, D_CODES, MAX_BITS);
-
- StaticTree.static_bl_desc = new StaticTree(null, Tree.extra_blbits, 0, BL_CODES, MAX_BL_BITS);
-
- // Deflate
-
- var MAX_MEM_LEVEL = 9;
- var DEF_MEM_LEVEL = 8;
-
- function Config(good_length, max_lazy, nice_length, max_chain, func) {
- var that = this;
- that.good_length = good_length;
- that.max_lazy = max_lazy;
- that.nice_length = nice_length;
- that.max_chain = max_chain;
- that.func = func;
- }
-
- var STORED = 0;
- var FAST = 1;
- var SLOW = 2;
- var config_table = [ new Config(0, 0, 0, 0, STORED), new Config(4, 4, 8, 4, FAST), new Config(4, 5, 16, 8, FAST), new Config(4, 6, 32, 32, FAST),
- new Config(4, 4, 16, 16, SLOW), new Config(8, 16, 32, 32, SLOW), new Config(8, 16, 128, 128, SLOW), new Config(8, 32, 128, 256, SLOW),
- new Config(32, 128, 258, 1024, SLOW), new Config(32, 258, 258, 4096, SLOW) ];
-
- var z_errmsg = [ "need dictionary", // Z_NEED_DICT
- // 2
- "stream end", // Z_STREAM_END 1
- "", // Z_OK 0
- "", // Z_ERRNO (-1)
- "stream error", // Z_STREAM_ERROR (-2)
- "data error", // Z_DATA_ERROR (-3)
- "", // Z_MEM_ERROR (-4)
- "buffer error", // Z_BUF_ERROR (-5)
- "",// Z_VERSION_ERROR (-6)
- "" ];
-
- // block not completed, need more input or more output
- var NeedMore = 0;
-
- // block flush performed
- var BlockDone = 1;
-
- // finish started, need only more output at next deflate
- var FinishStarted = 2;
-
- // finish done, accept no more input or output
- var FinishDone = 3;
-
- // preset dictionary flag in zlib header
- var PRESET_DICT = 0x20;
-
- var INIT_STATE = 42;
- var BUSY_STATE = 113;
- var FINISH_STATE = 666;
-
- // The deflate compression method
- var Z_DEFLATED = 8;
-
- var STORED_BLOCK = 0;
- var STATIC_TREES = 1;
- var DYN_TREES = 2;
-
- var MIN_MATCH = 3;
- var MAX_MATCH = 258;
- var MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1);
-
- function smaller(tree, n, m, depth) {
- var tn2 = tree[n * 2];
- var tm2 = tree[m * 2];
- return (tn2 < tm2 || (tn2 == tm2 && depth[n] <= depth[m]));
- }
-
- function Deflate() {
-
- var that = this;
- var strm; // pointer back to this zlib stream
- var status; // as the name implies
- // pending_buf; // output still pending
- var pending_buf_size; // size of pending_buf
- // pending_out; // next pending byte to output to the stream
- // pending; // nb of bytes in the pending buffer
- var method; // STORED (for zip only) or DEFLATED
- var last_flush; // value of flush param for previous deflate call
-
- var w_size; // LZ77 window size (32K by default)
- var w_bits; // log2(w_size) (8..16)
- var w_mask; // w_size - 1
-
- var window;
- // Sliding window. Input bytes are read into the second half of the window,
- // and move to the first half later to keep a dictionary of at least wSize
- // bytes. With this organization, matches are limited to a distance of
- // wSize-MAX_MATCH bytes, but this ensures that IO is always
- // performed with a length multiple of the block size. Also, it limits
- // the window size to 64K, which is quite useful on MSDOS.
- // To do: use the user input buffer as sliding window.
-
- var window_size;
- // Actual size of window: 2*wSize, except when the user input buffer
- // is directly used as sliding window.
-
- var prev;
- // Link to older string with same hash index. To limit the size of this
- // array to 64K, this link is maintained only for the last 32K strings.
- // An index in this array is thus a window index modulo 32K.
-
- var head; // Heads of the hash chains or NIL.
-
- var ins_h; // hash index of string to be inserted
- var hash_size; // number of elements in hash table
- var hash_bits; // log2(hash_size)
- var hash_mask; // hash_size-1
-
- // Number of bits by which ins_h must be shifted at each input
- // step. It must be such that after MIN_MATCH steps, the oldest
- // byte no longer takes part in the hash key, that is:
- // hash_shift * MIN_MATCH >= hash_bits
- var hash_shift;
-
- // Window position at the beginning of the current output block. Gets
- // negative when the window is moved backwards.
-
- var block_start;
-
- var match_length; // length of best match
- var prev_match; // previous match
- var match_available; // set if previous match exists
- var strstart; // start of string to insert
- var match_start; // start of matching string
- var lookahead; // number of valid bytes ahead in window
-
- // Length of the best match at previous step. Matches not greater than this
- // are discarded. This is used in the lazy match evaluation.
- var prev_length;
-
- // To speed up deflation, hash chains are never searched beyond this
- // length. A higher limit improves compression ratio but degrades the speed.
- var max_chain_length;
-
- // Attempt to find a better match only when the current match is strictly
- // smaller than this value. This mechanism is used only for compression
- // levels >= 4.
- var max_lazy_match;
-
- // Insert new strings in the hash table only if the match length is not
- // greater than this length. This saves time but degrades compression.
- // max_insert_length is used only for compression levels <= 3.
-
- var level; // compression level (1..9)
- var strategy; // favor or force Huffman coding
-
- // Use a faster search when the previous match is longer than this
- var good_match;
-
- // Stop searching when current match exceeds this
- var nice_match;
-
- var dyn_ltree; // literal and length tree
- var dyn_dtree; // distance tree
- var bl_tree; // Huffman tree for bit lengths
-
- var l_desc = new Tree(); // desc for literal tree
- var d_desc = new Tree(); // desc for distance tree
- var bl_desc = new Tree(); // desc for bit length tree
-
- // that.heap_len; // number of elements in the heap
- // that.heap_max; // element of largest frequency
- // The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used.
- // The same heap array is used to build all trees.
-
- // Depth of each subtree used as tie breaker for trees of equal frequency
- that.depth = [];
-
- var l_buf; // index for literals or lengths */
-
- // Size of match buffer for literals/lengths. There are 4 reasons for
- // limiting lit_bufsize to 64K:
- // - frequencies can be kept in 16 bit counters
- // - if compression is not successful for the first block, all input
- // data is still in the window so we can still emit a stored block even
- // when input comes from standard input. (This can also be done for
- // all blocks if lit_bufsize is not greater than 32K.)
- // - if compression is not successful for a file smaller than 64K, we can
- // even emit a stored file instead of a stored block (saving 5 bytes).
- // This is applicable only for zip (not gzip or zlib).
- // - creating new Huffman trees less frequently may not provide fast
- // adaptation to changes in the input data statistics. (Take for
- // example a binary file with poorly compressible code followed by
- // a highly compressible string table.) Smaller buffer sizes give
- // fast adaptation but have of course the overhead of transmitting
- // trees more frequently.
- // - I can't count above 4
- var lit_bufsize;
-
- var last_lit; // running index in l_buf
-
- // Buffer for distances. To simplify the code, d_buf and l_buf have
- // the same number of elements. To use different lengths, an extra flag
- // array would be necessary.
-
- var d_buf; // index of pendig_buf
-
- // that.opt_len; // bit length of current block with optimal trees
- // that.static_len; // bit length of current block with static trees
- var matches; // number of string matches in current block
- var last_eob_len; // bit length of EOB code for last block
-
- // Output buffer. bits are inserted starting at the bottom (least
- // significant bits).
- var bi_buf;
-
- // Number of valid bits in bi_buf. All bits above the last valid bit
- // are always zero.
- var bi_valid;
-
- // number of codes at each bit length for an optimal tree
- that.bl_count = [];
-
- // heap used to build the Huffman trees
- that.heap = [];
-
- dyn_ltree = [];
- dyn_dtree = [];
- bl_tree = [];
-
- function lm_init() {
- var i;
- window_size = 2 * w_size;
-
- head[hash_size - 1] = 0;
- for (i = 0; i < hash_size - 1; i++) {
- head[i] = 0;
- }
-
- // Set the default configuration parameters:
- max_lazy_match = config_table[level].max_lazy;
- good_match = config_table[level].good_length;
- nice_match = config_table[level].nice_length;
- max_chain_length = config_table[level].max_chain;
-
- strstart = 0;
- block_start = 0;
- lookahead = 0;
- match_length = prev_length = MIN_MATCH - 1;
- match_available = 0;
- ins_h = 0;
- }
-
- function init_block() {
- var i;
- // Initialize the trees.
- for (i = 0; i < L_CODES; i++)
- dyn_ltree[i * 2] = 0;
- for (i = 0; i < D_CODES; i++)
- dyn_dtree[i * 2] = 0;
- for (i = 0; i < BL_CODES; i++)
- bl_tree[i * 2] = 0;
-
- dyn_ltree[END_BLOCK * 2] = 1;
- that.opt_len = that.static_len = 0;
- last_lit = matches = 0;
- }
-
- // Initialize the tree data structures for a new zlib stream.
- function tr_init() {
-
- l_desc.dyn_tree = dyn_ltree;
- l_desc.stat_desc = StaticTree.static_l_desc;
-
- d_desc.dyn_tree = dyn_dtree;
- d_desc.stat_desc = StaticTree.static_d_desc;
-
- bl_desc.dyn_tree = bl_tree;
- bl_desc.stat_desc = StaticTree.static_bl_desc;
-
- bi_buf = 0;
- bi_valid = 0;
- last_eob_len = 8; // enough lookahead for inflate
-
- // Initialize the first block of the first file:
- init_block();
- }
-
- // Restore the heap property by moving down the tree starting at node k,
- // exchanging a node with the smallest of its two sons if necessary,
- // stopping
- // when the heap property is re-established (each father smaller than its
- // two sons).
- that.pqdownheap = function(tree, // the tree to restore
- k // node to move down
- ) {
- var heap = that.heap;
- var v = heap[k];
- var j = k << 1; // left son of k
- while (j <= that.heap_len) {
- // Set j to the smallest of the two sons:
- if (j < that.heap_len && smaller(tree, heap[j + 1], heap[j], that.depth)) {
- j++;
- }
- // Exit if v is smaller than both sons
- if (smaller(tree, v, heap[j], that.depth))
- break;
-
- // Exchange v with the smallest son
- heap[k] = heap[j];
- k = j;
- // And continue down the tree, setting j to the left son of k
- j <<= 1;
- }
- heap[k] = v;
- };
-
- // Scan a literal or distance tree to determine the frequencies of the codes
- // in the bit length tree.
- function scan_tree(tree,// the tree to be scanned
- max_code // and its largest code of non zero frequency
- ) {
- var n; // iterates over all tree elements
- var prevlen = -1; // last emitted length
- var curlen; // length of current code
- var nextlen = tree[0 * 2 + 1]; // length of next code
- var count = 0; // repeat count of the current code
- var max_count = 7; // max repeat count
- var min_count = 4; // min repeat count
-
- if (nextlen === 0) {
- max_count = 138;
- min_count = 3;
- }
- tree[(max_code + 1) * 2 + 1] = 0xffff; // guard
-
- for (n = 0; n <= max_code; n++) {
- curlen = nextlen;
- nextlen = tree[(n + 1) * 2 + 1];
- if (++count < max_count && curlen == nextlen) {
- continue;
- } else if (count < min_count) {
- bl_tree[curlen * 2] += count;
- } else if (curlen !== 0) {
- if (curlen != prevlen)
- bl_tree[curlen * 2]++;
- bl_tree[REP_3_6 * 2]++;
- } else if (count <= 10) {
- bl_tree[REPZ_3_10 * 2]++;
- } else {
- bl_tree[REPZ_11_138 * 2]++;
- }
- count = 0;
- prevlen = curlen;
- if (nextlen === 0) {
- max_count = 138;
- min_count = 3;
- } else if (curlen == nextlen) {
- max_count = 6;
- min_count = 3;
- } else {
- max_count = 7;
- min_count = 4;
- }
- }
- }
-
- // Construct the Huffman tree for the bit lengths and return the index in
- // bl_order of the last bit length code to send.
- function build_bl_tree() {
- var max_blindex; // index of last bit length code of non zero freq
-
- // Determine the bit length frequencies for literal and distance trees
- scan_tree(dyn_ltree, l_desc.max_code);
- scan_tree(dyn_dtree, d_desc.max_code);
-
- // Build the bit length tree:
- bl_desc.build_tree(that);
- // opt_len now includes the length of the tree representations, except
- // the lengths of the bit lengths codes and the 5+5+4 bits for the
- // counts.
-
- // Determine the number of bit length codes to send. The pkzip format
- // requires that at least 4 bit length codes be sent. (appnote.txt says
- // 3 but the actual value used is 4.)
- for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) {
- if (bl_tree[Tree.bl_order[max_blindex] * 2 + 1] !== 0)
- break;
- }
- // Update opt_len to include the bit length tree and counts
- that.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4;
-
- return max_blindex;
- }
-
- // Output a byte on the stream.
- // IN assertion: there is enough room in pending_buf.
- function put_byte(p) {
- that.pending_buf[that.pending++] = p;
- }
-
- function put_short(w) {
- put_byte(w & 0xff);
- put_byte((w >>> 8) & 0xff);
- }
-
- function putShortMSB(b) {
- put_byte((b >> 8) & 0xff);
- put_byte((b & 0xff) & 0xff);
- }
-
- function send_bits(value, length) {
- var val, len = length;
- if (bi_valid > Buf_size - len) {
- val = value;
- // bi_buf |= (val << bi_valid);
- bi_buf |= ((val << bi_valid) & 0xffff);
- put_short(bi_buf);
- bi_buf = val >>> (Buf_size - bi_valid);
- bi_valid += len - Buf_size;
- } else {
- // bi_buf |= (value) << bi_valid;
- bi_buf |= (((value) << bi_valid) & 0xffff);
- bi_valid += len;
- }
- }
-
- function send_code(c, tree) {
- var c2 = c * 2;
- send_bits(tree[c2] & 0xffff, tree[c2 + 1] & 0xffff);
- }
-
- // Send a literal or distance tree in compressed form, using the codes in
- // bl_tree.
- function send_tree(tree,// the tree to be sent
- max_code // and its largest code of non zero frequency
- ) {
- var n; // iterates over all tree elements
- var prevlen = -1; // last emitted length
- var curlen; // length of current code
- var nextlen = tree[0 * 2 + 1]; // length of next code
- var count = 0; // repeat count of the current code
- var max_count = 7; // max repeat count
- var min_count = 4; // min repeat count
-
- if (nextlen === 0) {
- max_count = 138;
- min_count = 3;
- }
-
- for (n = 0; n <= max_code; n++) {
- curlen = nextlen;
- nextlen = tree[(n + 1) * 2 + 1];
- if (++count < max_count && curlen == nextlen) {
- continue;
- } else if (count < min_count) {
- do {
- send_code(curlen, bl_tree);
- } while (--count !== 0);
- } else if (curlen !== 0) {
- if (curlen != prevlen) {
- send_code(curlen, bl_tree);
- count--;
- }
- send_code(REP_3_6, bl_tree);
- send_bits(count - 3, 2);
- } else if (count <= 10) {
- send_code(REPZ_3_10, bl_tree);
- send_bits(count - 3, 3);
- } else {
- send_code(REPZ_11_138, bl_tree);
- send_bits(count - 11, 7);
- }
- count = 0;
- prevlen = curlen;
- if (nextlen === 0) {
- max_count = 138;
- min_count = 3;
- } else if (curlen == nextlen) {
- max_count = 6;
- min_count = 3;
- } else {
- max_count = 7;
- min_count = 4;
- }
- }
- }
-
- // Send the header for a block using dynamic Huffman trees: the counts, the
- // lengths of the bit length codes, the literal tree and the distance tree.
- // IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
- function send_all_trees(lcodes, dcodes, blcodes) {
- var rank; // index in bl_order
-
- send_bits(lcodes - 257, 5); // not +255 as stated in appnote.txt
- send_bits(dcodes - 1, 5);
- send_bits(blcodes - 4, 4); // not -3 as stated in appnote.txt
- for (rank = 0; rank < blcodes; rank++) {
- send_bits(bl_tree[Tree.bl_order[rank] * 2 + 1], 3);
- }
- send_tree(dyn_ltree, lcodes - 1); // literal tree
- send_tree(dyn_dtree, dcodes - 1); // distance tree
- }
-
- // Flush the bit buffer, keeping at most 7 bits in it.
- function bi_flush() {
- if (bi_valid == 16) {
- put_short(bi_buf);
- bi_buf = 0;
- bi_valid = 0;
- } else if (bi_valid >= 8) {
- put_byte(bi_buf & 0xff);
- bi_buf >>>= 8;
- bi_valid -= 8;
- }
- }
-
- // Send one empty static block to give enough lookahead for inflate.
- // This takes 10 bits, of which 7 may remain in the bit buffer.
- // The current inflate code requires 9 bits of lookahead. If the
- // last two codes for the previous block (real code plus EOB) were coded
- // on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode
- // the last real code. In this case we send two empty static blocks instead
- // of one. (There are no problems if the previous block is stored or fixed.)
- // To simplify the code, we assume the worst case of last real code encoded
- // on one bit only.
- function _tr_align() {
- send_bits(STATIC_TREES << 1, 3);
- send_code(END_BLOCK, StaticTree.static_ltree);
-
- bi_flush();
-
- // Of the 10 bits for the empty block, we have already sent
- // (10 - bi_valid) bits. The lookahead for the last real code (before
- // the EOB of the previous block) was thus at least one plus the length
- // of the EOB plus what we have just sent of the empty static block.
- if (1 + last_eob_len + 10 - bi_valid < 9) {
- send_bits(STATIC_TREES << 1, 3);
- send_code(END_BLOCK, StaticTree.static_ltree);
- bi_flush();
- }
- last_eob_len = 7;
- }
-
- // Save the match info and tally the frequency counts. Return true if
- // the current block must be flushed.
- function _tr_tally(dist, // distance of matched string
- lc // match length-MIN_MATCH or unmatched char (if dist==0)
- ) {
- var out_length, in_length, dcode;
- that.pending_buf[d_buf + last_lit * 2] = (dist >>> 8) & 0xff;
- that.pending_buf[d_buf + last_lit * 2 + 1] = dist & 0xff;
-
- that.pending_buf[l_buf + last_lit] = lc & 0xff;
- last_lit++;
-
- if (dist === 0) {
- // lc is the unmatched char
- dyn_ltree[lc * 2]++;
- } else {
- matches++;
- // Here, lc is the match length - MIN_MATCH
- dist--; // dist = match distance - 1
- dyn_ltree[(Tree._length_code[lc] + LITERALS + 1) * 2]++;
- dyn_dtree[Tree.d_code(dist) * 2]++;
- }
-
- if ((last_lit & 0x1fff) === 0 && level > 2) {
- // Compute an upper bound for the compressed length
- out_length = last_lit * 8;
- in_length = strstart - block_start;
- for (dcode = 0; dcode < D_CODES; dcode++) {
- out_length += dyn_dtree[dcode * 2] * (5 + Tree.extra_dbits[dcode]);
- }
- out_length >>>= 3;
- if ((matches < Math.floor(last_lit / 2)) && out_length < Math.floor(in_length / 2))
- return true;
- }
-
- return (last_lit == lit_bufsize - 1);
- // We avoid equality with lit_bufsize because of wraparound at 64K
- // on 16 bit machines and because stored blocks are restricted to
- // 64K-1 bytes.
- }
-
- // Send the block data compressed using the given Huffman trees
- function compress_block(ltree, dtree) {
- var dist; // distance of matched string
- var lc; // match length or unmatched char (if dist === 0)
- var lx = 0; // running index in l_buf
- var code; // the code to send
- var extra; // number of extra bits to send
-
- if (last_lit !== 0) {
- do {
- dist = ((that.pending_buf[d_buf + lx * 2] << 8) & 0xff00) | (that.pending_buf[d_buf + lx * 2 + 1] & 0xff);
- lc = (that.pending_buf[l_buf + lx]) & 0xff;
- lx++;
-
- if (dist === 0) {
- send_code(lc, ltree); // send a literal byte
- } else {
- // Here, lc is the match length - MIN_MATCH
- code = Tree._length_code[lc];
-
- send_code(code + LITERALS + 1, ltree); // send the length
- // code
- extra = Tree.extra_lbits[code];
- if (extra !== 0) {
- lc -= Tree.base_length[code];
- send_bits(lc, extra); // send the extra length bits
- }
- dist--; // dist is now the match distance - 1
- code = Tree.d_code(dist);
-
- send_code(code, dtree); // send the distance code
- extra = Tree.extra_dbits[code];
- if (extra !== 0) {
- dist -= Tree.base_dist[code];
- send_bits(dist, extra); // send the extra distance bits
- }
- } // literal or match pair ?
-
- // Check that the overlay between pending_buf and d_buf+l_buf is
- // ok:
- } while (lx < last_lit);
- }
-
- send_code(END_BLOCK, ltree);
- last_eob_len = ltree[END_BLOCK * 2 + 1];
- }
-
- // Flush the bit buffer and align the output on a byte boundary
- function bi_windup() {
- if (bi_valid > 8) {
- put_short(bi_buf);
- } else if (bi_valid > 0) {
- put_byte(bi_buf & 0xff);
- }
- bi_buf = 0;
- bi_valid = 0;
- }
-
- // Copy a stored block, storing first the length and its
- // one's complement if requested.
- function copy_block(buf, // the input data
- len, // its length
- header // true if block header must be written
- ) {
- bi_windup(); // align on byte boundary
- last_eob_len = 8; // enough lookahead for inflate
-
- if (header) {
- put_short(len);
- put_short(~len);
- }
-
- that.pending_buf.set(window.subarray(buf, buf + len), that.pending);
- that.pending += len;
- }
-
- // Send a stored block
- function _tr_stored_block(buf, // input block
- stored_len, // length of input block
- eof // true if this is the last block for a file
- ) {
- send_bits((STORED_BLOCK << 1) + (eof ? 1 : 0), 3); // send block type
- copy_block(buf, stored_len, true); // with header
- }
-
- // Determine the best encoding for the current block: dynamic trees, static
- // trees or store, and output the encoded block to the zip file.
- function _tr_flush_block(buf, // input block, or NULL if too old
- stored_len, // length of input block
- eof // true if this is the last block for a file
- ) {
- var opt_lenb, static_lenb;// opt_len and static_len in bytes
- var max_blindex = 0; // index of last bit length code of non zero freq
-
- // Build the Huffman trees unless a stored block is forced
- if (level > 0) {
- // Construct the literal and distance trees
- l_desc.build_tree(that);
-
- d_desc.build_tree(that);
-
- // At this point, opt_len and static_len are the total bit lengths
- // of
- // the compressed block data, excluding the tree representations.
-
- // Build the bit length tree for the above two trees, and get the
- // index
- // in bl_order of the last bit length code to send.
- max_blindex = build_bl_tree();
-
- // Determine the best encoding. Compute first the block length in
- // bytes
- opt_lenb = (that.opt_len + 3 + 7) >>> 3;
- static_lenb = (that.static_len + 3 + 7) >>> 3;
-
- if (static_lenb <= opt_lenb)
- opt_lenb = static_lenb;
- } else {
- opt_lenb = static_lenb = stored_len + 5; // force a stored block
- }
-
- if ((stored_len + 4 <= opt_lenb) && buf != -1) {
- // 4: two words for the lengths
- // The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
- // Otherwise we can't have processed more than WSIZE input bytes
- // since
- // the last block flush, because compression would have been
- // successful. If LIT_BUFSIZE <= WSIZE, it is never too late to
- // transform a block into a stored block.
- _tr_stored_block(buf, stored_len, eof);
- } else if (static_lenb == opt_lenb) {
- send_bits((STATIC_TREES << 1) + (eof ? 1 : 0), 3);
- compress_block(StaticTree.static_ltree, StaticTree.static_dtree);
- } else {
- send_bits((DYN_TREES << 1) + (eof ? 1 : 0), 3);
- send_all_trees(l_desc.max_code + 1, d_desc.max_code + 1, max_blindex + 1);
- compress_block(dyn_ltree, dyn_dtree);
- }
-
- // The above check is made mod 2^32, for files larger than 512 MB
- // and uLong implemented on 32 bits.
-
- init_block();
-
- if (eof) {
- bi_windup();
- }
- }
-
- function flush_block_only(eof) {
- _tr_flush_block(block_start >= 0 ? block_start : -1, strstart - block_start, eof);
- block_start = strstart;
- strm.flush_pending();
- }
-
- // Fill the window when the lookahead becomes insufficient.
- // Updates strstart and lookahead.
- //
- // IN assertion: lookahead < MIN_LOOKAHEAD
- // OUT assertions: strstart <= window_size-MIN_LOOKAHEAD
- // At least one byte has been read, or avail_in === 0; reads are
- // performed for at least two bytes (required for the zip translate_eol
- // option -- not supported here).
- function fill_window() {
- var n, m;
- var p;
- var more; // Amount of free space at the end of the window.
-
- do {
- more = (window_size - lookahead - strstart);
-
- // Deal with !@#$% 64K limit:
- if (more === 0 && strstart === 0 && lookahead === 0) {
- more = w_size;
- } else if (more == -1) {
- // Very unlikely, but possible on 16 bit machine if strstart ==
- // 0
- // and lookahead == 1 (input done one byte at time)
- more--;
-
- // If the window is almost full and there is insufficient
- // lookahead,
- // move the upper half to the lower one to make room in the
- // upper half.
- } else if (strstart >= w_size + w_size - MIN_LOOKAHEAD) {
- window.set(window.subarray(w_size, w_size + w_size), 0);
-
- match_start -= w_size;
- strstart -= w_size; // we now have strstart >= MAX_DIST
- block_start -= w_size;
-
- // Slide the hash table (could be avoided with 32 bit values
- // at the expense of memory usage). We slide even when level ==
- // 0
- // to keep the hash table consistent if we switch back to level
- // > 0
- // later. (Using level 0 permanently is not an optimal usage of
- // zlib, so we don't care about this pathological case.)
-
- n = hash_size;
- p = n;
- do {
- m = (head[--p] & 0xffff);
- head[p] = (m >= w_size ? m - w_size : 0);
- } while (--n !== 0);
-
- n = w_size;
- p = n;
- do {
- m = (prev[--p] & 0xffff);
- prev[p] = (m >= w_size ? m - w_size : 0);
- // If n is not on any hash chain, prev[n] is garbage but
- // its value will never be used.
- } while (--n !== 0);
- more += w_size;
- }
-
- if (strm.avail_in === 0)
- return;
-
- // If there was no sliding:
- // strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 &&
- // more == window_size - lookahead - strstart
- // => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1)
- // => more >= window_size - 2*WSIZE + 2
- // In the BIG_MEM or MMAP case (not yet supported),
- // window_size == input_size + MIN_LOOKAHEAD &&
- // strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD.
- // Otherwise, window_size == 2*WSIZE so more >= 2.
- // If there was sliding, more >= WSIZE. So in all cases, more >= 2.
-
- n = strm.read_buf(window, strstart + lookahead, more);
- lookahead += n;
-
- // Initialize the hash value now that we have some input:
- if (lookahead >= MIN_MATCH) {
- ins_h = window[strstart] & 0xff;
- ins_h = (((ins_h) << hash_shift) ^ (window[strstart + 1] & 0xff)) & hash_mask;
- }
- // If the whole input has less than MIN_MATCH bytes, ins_h is
- // garbage,
- // but this is not important since only literal bytes will be
- // emitted.
- } while (lookahead < MIN_LOOKAHEAD && strm.avail_in !== 0);
- }
-
- // Copy without compression as much as possible from the input stream,
- // return
- // the current block state.
- // This function does not insert new strings in the dictionary since
- // uncompressible data is probably not useful. This function is used
- // only for the level=0 compression option.
- // NOTE: this function should be optimized to avoid extra copying from
- // window to pending_buf.
- function deflate_stored(flush) {
- // Stored blocks are limited to 0xffff bytes, pending_buf is limited
- // to pending_buf_size, and each stored block has a 5 byte header:
-
- var max_block_size = 0xffff;
- var max_start;
-
- if (max_block_size > pending_buf_size - 5) {
- max_block_size = pending_buf_size - 5;
- }
-
- // Copy as much as possible from input to output:
- while (true) {
- // Fill the window as much as possible:
- if (lookahead <= 1) {
- fill_window();
- if (lookahead === 0 && flush == Z_NO_FLUSH)
- return NeedMore;
- if (lookahead === 0)
- break; // flush the current block
- }
-
- strstart += lookahead;
- lookahead = 0;
-
- // Emit a stored block if pending_buf will be full:
- max_start = block_start + max_block_size;
- if (strstart === 0 || strstart >= max_start) {
- // strstart === 0 is possible when wraparound on 16-bit machine
- lookahead = (strstart - max_start);
- strstart = max_start;
-
- flush_block_only(false);
- if (strm.avail_out === 0)
- return NeedMore;
-
- }
-
- // Flush if we may have to slide, otherwise block_start may become
- // negative and the data will be gone:
- if (strstart - block_start >= w_size - MIN_LOOKAHEAD) {
- flush_block_only(false);
- if (strm.avail_out === 0)
- return NeedMore;
- }
- }
-
- flush_block_only(flush == Z_FINISH);
- if (strm.avail_out === 0)
- return (flush == Z_FINISH) ? FinishStarted : NeedMore;
-
- return flush == Z_FINISH ? FinishDone : BlockDone;
- }
-
- function longest_match(cur_match) {
- var chain_length = max_chain_length; // max hash chain length
- var scan = strstart; // current string
- var match; // matched string
- var len; // length of current match
- var best_len = prev_length; // best match length so far
- var limit = strstart > (w_size - MIN_LOOKAHEAD) ? strstart - (w_size - MIN_LOOKAHEAD) : 0;
- var _nice_match = nice_match;
-
- // Stop when cur_match becomes <= limit. To simplify the code,
- // we prevent matches with the string of window index 0.
-
- var wmask = w_mask;
-
- var strend = strstart + MAX_MATCH;
- var scan_end1 = window[scan + best_len - 1];
- var scan_end = window[scan + best_len];
-
- // The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of
- // 16.
- // It is easy to get rid of this optimization if necessary.
-
- // Do not waste too much time if we already have a good match:
- if (prev_length >= good_match) {
- chain_length >>= 2;
- }
-
- // Do not look for matches beyond the end of the input. This is
- // necessary
- // to make deflate deterministic.
- if (_nice_match > lookahead)
- _nice_match = lookahead;
-
- do {
- match = cur_match;
-
- // Skip to next match if the match length cannot increase
- // or if the match length is less than 2:
- if (window[match + best_len] != scan_end || window[match + best_len - 1] != scan_end1 || window[match] != window[scan]
- || window[++match] != window[scan + 1])
- continue;
-
- // The check at best_len-1 can be removed because it will be made
- // again later. (This heuristic is not always a win.)
- // It is not necessary to compare scan[2] and match[2] since they
- // are always equal when the other bytes match, given that
- // the hash keys are equal and that HASH_BITS >= 8.
- scan += 2;
- match++;
-
- // We check for insufficient lookahead only every 8th comparison;
- // the 256th check will be made at strstart+258.
- do {
- } while (window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match]
- && window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match]
- && window[++scan] == window[++match] && window[++scan] == window[++match] && scan < strend);
-
- len = MAX_MATCH - (strend - scan);
- scan = strend - MAX_MATCH;
-
- if (len > best_len) {
- match_start = cur_match;
- best_len = len;
- if (len >= _nice_match)
- break;
- scan_end1 = window[scan + best_len - 1];
- scan_end = window[scan + best_len];
- }
-
- } while ((cur_match = (prev[cur_match & wmask] & 0xffff)) > limit && --chain_length !== 0);
-
- if (best_len <= lookahead)
- return best_len;
- return lookahead;
- }
-
- // Compress as much as possible from the input stream, return the current
- // block state.
- // This function does not perform lazy evaluation of matches and inserts
- // new strings in the dictionary only for unmatched strings or for short
- // matches. It is used only for the fast compression options.
- function deflate_fast(flush) {
- // short hash_head = 0; // head of the hash chain
- var hash_head = 0; // head of the hash chain
- var bflush; // set if current block must be flushed
-
- while (true) {
- // Make sure that we always have enough lookahead, except
- // at the end of the input file. We need MAX_MATCH bytes
- // for the next match, plus MIN_MATCH bytes to insert the
- // string following the next match.
- if (lookahead < MIN_LOOKAHEAD) {
- fill_window();
- if (lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) {
- return NeedMore;
- }
- if (lookahead === 0)
- break; // flush the current block
- }
-
- // Insert the string window[strstart .. strstart+2] in the
- // dictionary, and set hash_head to the head of the hash chain:
- if (lookahead >= MIN_MATCH) {
- ins_h = (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask;
-
- // prev[strstart&w_mask]=hash_head=head[ins_h];
- hash_head = (head[ins_h] & 0xffff);
- prev[strstart & w_mask] = head[ins_h];
- head[ins_h] = strstart;
- }
-
- // Find the longest match, discarding those <= prev_length.
- // At this point we have always match_length < MIN_MATCH
-
- if (hash_head !== 0 && ((strstart - hash_head) & 0xffff) <= w_size - MIN_LOOKAHEAD) {
- // To simplify the code, we prevent matches with the string
- // of window index 0 (in particular we have to avoid a match
- // of the string with itself at the start of the input file).
- if (strategy != Z_HUFFMAN_ONLY) {
- match_length = longest_match(hash_head);
- }
- // longest_match() sets match_start
- }
- if (match_length >= MIN_MATCH) {
- // check_match(strstart, match_start, match_length);
-
- bflush = _tr_tally(strstart - match_start, match_length - MIN_MATCH);
-
- lookahead -= match_length;
-
- // Insert new strings in the hash table only if the match length
- // is not too large. This saves time but degrades compression.
- if (match_length <= max_lazy_match && lookahead >= MIN_MATCH) {
- match_length--; // string at strstart already in hash table
- do {
- strstart++;
-
- ins_h = ((ins_h << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask;
- // prev[strstart&w_mask]=hash_head=head[ins_h];
- hash_head = (head[ins_h] & 0xffff);
- prev[strstart & w_mask] = head[ins_h];
- head[ins_h] = strstart;
-
- // strstart never exceeds WSIZE-MAX_MATCH, so there are
- // always MIN_MATCH bytes ahead.
- } while (--match_length !== 0);
- strstart++;
- } else {
- strstart += match_length;
- match_length = 0;
- ins_h = window[strstart] & 0xff;
-
- ins_h = (((ins_h) << hash_shift) ^ (window[strstart + 1] & 0xff)) & hash_mask;
- // If lookahead < MIN_MATCH, ins_h is garbage, but it does
- // not
- // matter since it will be recomputed at next deflate call.
- }
- } else {
- // No match, output a literal byte
-
- bflush = _tr_tally(0, window[strstart] & 0xff);
- lookahead--;
- strstart++;
- }
- if (bflush) {
-
- flush_block_only(false);
- if (strm.avail_out === 0)
- return NeedMore;
- }
- }
-
- flush_block_only(flush == Z_FINISH);
- if (strm.avail_out === 0) {
- if (flush == Z_FINISH)
- return FinishStarted;
- else
- return NeedMore;
- }
- return flush == Z_FINISH ? FinishDone : BlockDone;
- }
-
- // Same as above, but achieves better compression. We use a lazy
- // evaluation for matches: a match is finally adopted only if there is
- // no better match at the next window position.
- function deflate_slow(flush) {
- // short hash_head = 0; // head of hash chain
- var hash_head = 0; // head of hash chain
- var bflush; // set if current block must be flushed
- var max_insert;
-
- // Process the input block.
- while (true) {
- // Make sure that we always have enough lookahead, except
- // at the end of the input file. We need MAX_MATCH bytes
- // for the next match, plus MIN_MATCH bytes to insert the
- // string following the next match.
-
- if (lookahead < MIN_LOOKAHEAD) {
- fill_window();
- if (lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) {
- return NeedMore;
- }
- if (lookahead === 0)
- break; // flush the current block
- }
-
- // Insert the string window[strstart .. strstart+2] in the
- // dictionary, and set hash_head to the head of the hash chain:
-
- if (lookahead >= MIN_MATCH) {
- ins_h = (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask;
- // prev[strstart&w_mask]=hash_head=head[ins_h];
- hash_head = (head[ins_h] & 0xffff);
- prev[strstart & w_mask] = head[ins_h];
- head[ins_h] = strstart;
- }
-
- // Find the longest match, discarding those <= prev_length.
- prev_length = match_length;
- prev_match = match_start;
- match_length = MIN_MATCH - 1;
-
- if (hash_head !== 0 && prev_length < max_lazy_match && ((strstart - hash_head) & 0xffff) <= w_size - MIN_LOOKAHEAD) {
- // To simplify the code, we prevent matches with the string
- // of window index 0 (in particular we have to avoid a match
- // of the string with itself at the start of the input file).
-
- if (strategy != Z_HUFFMAN_ONLY) {
- match_length = longest_match(hash_head);
- }
- // longest_match() sets match_start
-
- if (match_length <= 5 && (strategy == Z_FILTERED || (match_length == MIN_MATCH && strstart - match_start > 4096))) {
-
- // If prev_match is also MIN_MATCH, match_start is garbage
- // but we will ignore the current match anyway.
- match_length = MIN_MATCH - 1;
- }
- }
-
- // If there was a match at the previous step and the current
- // match is not better, output the previous match:
- if (prev_length >= MIN_MATCH && match_length <= prev_length) {
- max_insert = strstart + lookahead - MIN_MATCH;
- // Do not insert strings in hash table beyond this.
-
- // check_match(strstart-1, prev_match, prev_length);
-
- bflush = _tr_tally(strstart - 1 - prev_match, prev_length - MIN_MATCH);
-
- // Insert in hash table all strings up to the end of the match.
- // strstart-1 and strstart are already inserted. If there is not
- // enough lookahead, the last two strings are not inserted in
- // the hash table.
- lookahead -= prev_length - 1;
- prev_length -= 2;
- do {
- if (++strstart <= max_insert) {
- ins_h = (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask;
- // prev[strstart&w_mask]=hash_head=head[ins_h];
- hash_head = (head[ins_h] & 0xffff);
- prev[strstart & w_mask] = head[ins_h];
- head[ins_h] = strstart;
- }
- } while (--prev_length !== 0);
- match_available = 0;
- match_length = MIN_MATCH - 1;
- strstart++;
-
- if (bflush) {
- flush_block_only(false);
- if (strm.avail_out === 0)
- return NeedMore;
- }
- } else if (match_available !== 0) {
-
- // If there was no match at the previous position, output a
- // single literal. If there was a match but the current match
- // is longer, truncate the previous match to a single literal.
-
- bflush = _tr_tally(0, window[strstart - 1] & 0xff);
-
- if (bflush) {
- flush_block_only(false);
- }
- strstart++;
- lookahead--;
- if (strm.avail_out === 0)
- return NeedMore;
- } else {
- // There is no previous match to compare with, wait for
- // the next step to decide.
-
- match_available = 1;
- strstart++;
- lookahead--;
- }
- }
-
- if (match_available !== 0) {
- bflush = _tr_tally(0, window[strstart - 1] & 0xff);
- match_available = 0;
- }
- flush_block_only(flush == Z_FINISH);
-
- if (strm.avail_out === 0) {
- if (flush == Z_FINISH)
- return FinishStarted;
- else
- return NeedMore;
- }
-
- return flush == Z_FINISH ? FinishDone : BlockDone;
- }
-
- function deflateReset(strm) {
- strm.total_in = strm.total_out = 0;
- strm.msg = null; //
-
- that.pending = 0;
- that.pending_out = 0;
-
- status = BUSY_STATE;
-
- last_flush = Z_NO_FLUSH;
-
- tr_init();
- lm_init();
- return Z_OK;
- }
-
- that.deflateInit = function(strm, _level, bits, _method, memLevel, _strategy) {
- if (!_method)
- _method = Z_DEFLATED;
- if (!memLevel)
- memLevel = DEF_MEM_LEVEL;
- if (!_strategy)
- _strategy = Z_DEFAULT_STRATEGY;
-
- // byte[] my_version=ZLIB_VERSION;
-
- //
- // if (!version || version[0] != my_version[0]
- // || stream_size != sizeof(z_stream)) {
- // return Z_VERSION_ERROR;
- // }
-
- strm.msg = null;
-
- if (_level == Z_DEFAULT_COMPRESSION)
- _level = 6;
-
- if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || _method != Z_DEFLATED || bits < 9 || bits > 15 || _level < 0 || _level > 9 || _strategy < 0
- || _strategy > Z_HUFFMAN_ONLY) {
- return Z_STREAM_ERROR;
- }
-
- strm.dstate = that;
-
- w_bits = bits;
- w_size = 1 << w_bits;
- w_mask = w_size - 1;
-
- hash_bits = memLevel + 7;
- hash_size = 1 << hash_bits;
- hash_mask = hash_size - 1;
- hash_shift = Math.floor((hash_bits + MIN_MATCH - 1) / MIN_MATCH);
-
- window = new Uint8Array(w_size * 2);
- prev = [];
- head = [];
-
- lit_bufsize = 1 << (memLevel + 6); // 16K elements by default
-
- // We overlay pending_buf and d_buf+l_buf. This works since the average
- // output size for (length,distance) codes is <= 24 bits.
- that.pending_buf = new Uint8Array(lit_bufsize * 4);
- pending_buf_size = lit_bufsize * 4;
-
- d_buf = Math.floor(lit_bufsize / 2);
- l_buf = (1 + 2) * lit_bufsize;
-
- level = _level;
-
- strategy = _strategy;
- method = _method & 0xff;
-
- return deflateReset(strm);
- };
-
- that.deflateEnd = function() {
- if (status != INIT_STATE && status != BUSY_STATE && status != FINISH_STATE) {
- return Z_STREAM_ERROR;
- }
- // Deallocate in reverse order of allocations:
- that.pending_buf = null;
- head = null;
- prev = null;
- window = null;
- // free
- that.dstate = null;
- return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK;
- };
-
- that.deflateParams = function(strm, _level, _strategy) {
- var err = Z_OK;
-
- if (_level == Z_DEFAULT_COMPRESSION) {
- _level = 6;
- }
- if (_level < 0 || _level > 9 || _strategy < 0 || _strategy > Z_HUFFMAN_ONLY) {
- return Z_STREAM_ERROR;
- }
-
- if (config_table[level].func != config_table[_level].func && strm.total_in !== 0) {
- // Flush the last buffer:
- err = strm.deflate(Z_PARTIAL_FLUSH);
- }
-
- if (level != _level) {
- level = _level;
- max_lazy_match = config_table[level].max_lazy;
- good_match = config_table[level].good_length;
- nice_match = config_table[level].nice_length;
- max_chain_length = config_table[level].max_chain;
- }
- strategy = _strategy;
- return err;
- };
-
- that.deflateSetDictionary = function(strm, dictionary, dictLength) {
- var length = dictLength;
- var n, index = 0;
-
- if (!dictionary || status != INIT_STATE)
- return Z_STREAM_ERROR;
-
- if (length < MIN_MATCH)
- return Z_OK;
- if (length > w_size - MIN_LOOKAHEAD) {
- length = w_size - MIN_LOOKAHEAD;
- index = dictLength - length; // use the tail of the dictionary
- }
- window.set(dictionary.subarray(index, index + length), 0);
-
- strstart = length;
- block_start = length;
-
- // Insert all strings in the hash table (except for the last two bytes).
- // s->lookahead stays null, so s->ins_h will be recomputed at the next
- // call of fill_window.
-
- ins_h = window[0] & 0xff;
- ins_h = (((ins_h) << hash_shift) ^ (window[1] & 0xff)) & hash_mask;
-
- for (n = 0; n <= length - MIN_MATCH; n++) {
- ins_h = (((ins_h) << hash_shift) ^ (window[(n) + (MIN_MATCH - 1)] & 0xff)) & hash_mask;
- prev[n & w_mask] = head[ins_h];
- head[ins_h] = n;
- }
- return Z_OK;
- };
-
- that.deflate = function(_strm, flush) {
- var i, header, level_flags, old_flush, bstate;
-
- if (flush > Z_FINISH || flush < 0) {
- return Z_STREAM_ERROR;
- }
-
- if (!_strm.next_out || (!_strm.next_in && _strm.avail_in !== 0) || (status == FINISH_STATE && flush != Z_FINISH)) {
- _strm.msg = z_errmsg[Z_NEED_DICT - (Z_STREAM_ERROR)];
- return Z_STREAM_ERROR;
- }
- if (_strm.avail_out === 0) {
- _strm.msg = z_errmsg[Z_NEED_DICT - (Z_BUF_ERROR)];
- return Z_BUF_ERROR;
- }
-
- strm = _strm; // just in case
- old_flush = last_flush;
- last_flush = flush;
-
- // Write the zlib header
- if (status == INIT_STATE) {
- header = (Z_DEFLATED + ((w_bits - 8) << 4)) << 8;
- level_flags = ((level - 1) & 0xff) >> 1;
-
- if (level_flags > 3)
- level_flags = 3;
- header |= (level_flags << 6);
- if (strstart !== 0)
- header |= PRESET_DICT;
- header += 31 - (header % 31);
-
- status = BUSY_STATE;
- putShortMSB(header);
- }
-
- // Flush as much pending output as possible
- if (that.pending !== 0) {
- strm.flush_pending();
- if (strm.avail_out === 0) {
- // console.log(" avail_out==0");
- // Since avail_out is 0, deflate will be called again with
- // more output space, but possibly with both pending and
- // avail_in equal to zero. There won't be anything to do,
- // but this is not an error situation so make sure we
- // return OK instead of BUF_ERROR at next call of deflate:
- last_flush = -1;
- return Z_OK;
- }
-
- // Make sure there is something to do and avoid duplicate
- // consecutive
- // flushes. For repeated and useless calls with Z_FINISH, we keep
- // returning Z_STREAM_END instead of Z_BUFF_ERROR.
- } else if (strm.avail_in === 0 && flush <= old_flush && flush != Z_FINISH) {
- strm.msg = z_errmsg[Z_NEED_DICT - (Z_BUF_ERROR)];
- return Z_BUF_ERROR;
- }
-
- // User must not provide more input after the first FINISH:
- if (status == FINISH_STATE && strm.avail_in !== 0) {
- _strm.msg = z_errmsg[Z_NEED_DICT - (Z_BUF_ERROR)];
- return Z_BUF_ERROR;
- }
-
- // Start a new block or continue the current one.
- if (strm.avail_in !== 0 || lookahead !== 0 || (flush != Z_NO_FLUSH && status != FINISH_STATE)) {
- bstate = -1;
- switch (config_table[level].func) {
- case STORED:
- bstate = deflate_stored(flush);
- break;
- case FAST:
- bstate = deflate_fast(flush);
- break;
- case SLOW:
- bstate = deflate_slow(flush);
- break;
- default:
- }
-
- if (bstate == FinishStarted || bstate == FinishDone) {
- status = FINISH_STATE;
- }
- if (bstate == NeedMore || bstate == FinishStarted) {
- if (strm.avail_out === 0) {
- last_flush = -1; // avoid BUF_ERROR next call, see above
- }
- return Z_OK;
- // If flush != Z_NO_FLUSH && avail_out === 0, the next call
- // of deflate should use the same flush parameter to make sure
- // that the flush is complete. So we don't have to output an
- // empty block here, this will be done at next call. This also
- // ensures that for a very small output buffer, we emit at most
- // one empty block.
- }
-
- if (bstate == BlockDone) {
- if (flush == Z_PARTIAL_FLUSH) {
- _tr_align();
- } else { // FULL_FLUSH or SYNC_FLUSH
- _tr_stored_block(0, 0, false);
- // For a full flush, this empty block will be recognized
- // as a special marker by inflate_sync().
- if (flush == Z_FULL_FLUSH) {
- // state.head[s.hash_size-1]=0;
- for (i = 0; i < hash_size/*-1*/; i++)
- // forget history
- head[i] = 0;
- }
- }
- strm.flush_pending();
- if (strm.avail_out === 0) {
- last_flush = -1; // avoid BUF_ERROR at next call, see above
- return Z_OK;
- }
- }
- }
-
- if (flush != Z_FINISH)
- return Z_OK;
- return Z_STREAM_END;
- };
- }
-
- // ZStream
-
- function ZStream() {
- var that = this;
- that.next_in_index = 0;
- that.next_out_index = 0;
- // that.next_in; // next input byte
- that.avail_in = 0; // number of bytes available at next_in
- that.total_in = 0; // total nb of input bytes read so far
- // that.next_out; // next output byte should be put there
- that.avail_out = 0; // remaining free space at next_out
- that.total_out = 0; // total nb of bytes output so far
- // that.msg;
- // that.dstate;
- }
-
- ZStream.prototype = {
- deflateInit : function(level, bits) {
- var that = this;
- that.dstate = new Deflate();
- if (!bits)
- bits = MAX_BITS;
- return that.dstate.deflateInit(that, level, bits);
- },
-
- deflate : function(flush) {
- var that = this;
- if (!that.dstate) {
- return Z_STREAM_ERROR;
- }
- return that.dstate.deflate(that, flush);
- },
-
- deflateEnd : function() {
- var that = this;
- if (!that.dstate)
- return Z_STREAM_ERROR;
- var ret = that.dstate.deflateEnd();
- that.dstate = null;
- return ret;
- },
-
- deflateParams : function(level, strategy) {
- var that = this;
- if (!that.dstate)
- return Z_STREAM_ERROR;
- return that.dstate.deflateParams(that, level, strategy);
- },
-
- deflateSetDictionary : function(dictionary, dictLength) {
- var that = this;
- if (!that.dstate)
- return Z_STREAM_ERROR;
- return that.dstate.deflateSetDictionary(that, dictionary, dictLength);
- },
-
- // Read a new buffer from the current input stream, update the
- // total number of bytes read. All deflate() input goes through
- // this function so some applications may wish to modify it to avoid
- // allocating a large strm->next_in buffer and copying from it.
- // (See also flush_pending()).
- read_buf : function(buf, start, size) {
- var that = this;
- var len = that.avail_in;
- if (len > size)
- len = size;
- if (len === 0)
- return 0;
- that.avail_in -= len;
- buf.set(that.next_in.subarray(that.next_in_index, that.next_in_index + len), start);
- that.next_in_index += len;
- that.total_in += len;
- return len;
- },
-
- // Flush as much pending output as possible. All deflate() output goes
- // through this function so some applications may wish to modify it
- // to avoid allocating a large strm->next_out buffer and copying into it.
- // (See also read_buf()).
- flush_pending : function() {
- var that = this;
- var len = that.dstate.pending;
-
- if (len > that.avail_out)
- len = that.avail_out;
- if (len === 0)
- return;
-
- // if (that.dstate.pending_buf.length <= that.dstate.pending_out || that.next_out.length <= that.next_out_index
- // || that.dstate.pending_buf.length < (that.dstate.pending_out + len) || that.next_out.length < (that.next_out_index +
- // len)) {
- // console.log(that.dstate.pending_buf.length + ", " + that.dstate.pending_out + ", " + that.next_out.length + ", " +
- // that.next_out_index + ", " + len);
- // console.log("avail_out=" + that.avail_out);
- // }
-
- that.next_out.set(that.dstate.pending_buf.subarray(that.dstate.pending_out, that.dstate.pending_out + len), that.next_out_index);
-
- that.next_out_index += len;
- that.dstate.pending_out += len;
- that.total_out += len;
- that.avail_out -= len;
- that.dstate.pending -= len;
- if (that.dstate.pending === 0) {
- that.dstate.pending_out = 0;
- }
- }
- };
-
- // Deflater
-
- return function Deflater(level) {
- var that = this;
- var z = new ZStream();
- var bufsize = 512;
- var flush = Z_NO_FLUSH;
- var buf = new Uint8Array(bufsize);
-
- if (typeof level == "undefined")
- level = Z_DEFAULT_COMPRESSION;
- z.deflateInit(level);
- z.next_out = buf;
-
- that.append = function(data, onprogress) {
- var err, buffers = [], lastIndex = 0, bufferIndex = 0, bufferSize = 0, array;
- if (!data.length)
- return;
- z.next_in_index = 0;
- z.next_in = data;
- z.avail_in = data.length;
- do {
- z.next_out_index = 0;
- z.avail_out = bufsize;
- err = z.deflate(flush);
- if (err != Z_OK)
- throw "deflating: " + z.msg;
- if (z.next_out_index)
- if (z.next_out_index == bufsize)
- buffers.push(new Uint8Array(buf));
- else
- buffers.push(new Uint8Array(buf.subarray(0, z.next_out_index)));
- bufferSize += z.next_out_index;
- if (onprogress && z.next_in_index > 0 && z.next_in_index != lastIndex) {
- onprogress(z.next_in_index);
- lastIndex = z.next_in_index;
- }
- } while (z.avail_in > 0 || z.avail_out === 0);
- array = new Uint8Array(bufferSize);
- buffers.forEach(function(chunk) {
- array.set(chunk, bufferIndex);
- bufferIndex += chunk.length;
- });
- return array;
- };
- that.flush = function() {
- var err, buffers = [], bufferIndex = 0, bufferSize = 0, array;
- do {
- z.next_out_index = 0;
- z.avail_out = bufsize;
- err = z.deflate(Z_FINISH);
- if (err != Z_STREAM_END && err != Z_OK)
- throw "deflating: " + z.msg;
- if (bufsize - z.avail_out > 0)
- buffers.push(new Uint8Array(buf.subarray(0, z.next_out_index)));
- bufferSize += z.next_out_index;
- } while (z.avail_in > 0 || z.avail_out === 0);
- z.deflateEnd();
- array = new Uint8Array(bufferSize);
- buffers.forEach(function(chunk) {
- array.set(chunk, bufferIndex);
- bufferIndex += chunk.length;
- });
- return array;
- };
- };
- })(this);
-
+ directory.write(tableData);
+ sum = checksum(directory.data);
+ adjustment = 0xb1b0afba - sum;
+ directory.pos = headOffset + 8;
+ directory.writeUInt32(adjustment);
+ return directory.data;
+ };
+ /***************************************************************/
- /*
- html2canvas 0.5.0-alpha
- Copyright (c) 2014 Niklas von Hertzen
+ /* function : checksum */
- Released under MIT License
- */
+ /* comment : Duplicate the table for the tag. */
- (function(window, document, module, exports, global, define, undefined){
+ /***************************************************************/
- /*
- Copyright (c) 2013 Yehuda Katz, Tom Dale, and contributors
-
- */
- !function(){var a,b,c,d;!function(){var e={},f={};a=function(a,b,c){e[a]={deps:b,callback:c}},d=c=b=function(a){function c(b){if("."!==b.charAt(0))return b;for(var c=b.split("/"),d=a.split("/").slice(0,-1),e=0,f=c.length;f>e;e++){var g=c[e];if(".."===g)d.pop();else{if("."===g)continue;d.push(g)}}return d.join("/")}if(d._eak_seen=e,f[a])return f[a];if(f[a]={},!e[a])throw new Error("Could not find module "+a);for(var g,h=e[a],i=h.deps,j=h.callback,k=[],l=0,m=i.length;m>l;l++)"exports"===i[l]?k.push(g={}):k.push(b(c(i[l])));var n=j.apply(this,k);return f[a]=g||n}}(),a("promise/all",["./utils","exports"],function(a,b){"use strict";function c(a){var b=this;if(!d(a))throw new TypeError("You must pass an array to all.");return new b(function(b,c){function d(a){return function(b){f(a,b)}}function f(a,c){h[a]=c,0===--i&&b(h)}var g,h=[],i=a.length;0===i&&b([]);for(var j=0;j= 0x80 (not a basic code point)',
- 'invalid-input': 'Invalid input'
- },
-
- /** Convenience shortcuts */
- baseMinusTMin = base - tMin,
- floor = Math.floor,
- stringFromCharCode = String.fromCharCode,
-
- /** Temporary variable */
- key;
-
- /*--------------------------------------------------------------------------*/
-
- /**
- * A generic error utility function.
- * @private
- * @param {String} type The error type.
- * @returns {Error} Throws a `RangeError` with the applicable error message.
- */
- function error(type) {
- throw RangeError(errors[type]);
- }
-
- /**
- * A generic `Array#map` utility function.
- * @private
- * @param {Array} array The array to iterate over.
- * @param {Function} callback The function that gets called for every array
- * item.
- * @returns {Array} A new array of values returned by the callback function.
- */
- function map(array, fn) {
- var length = array.length;
- var result = [];
- while (length--) {
- result[length] = fn(array[length]);
- }
- return result;
- }
-
- /**
- * A simple `Array#map`-like wrapper to work with domain name strings or email
- * addresses.
- * @private
- * @param {String} domain The domain name or email address.
- * @param {Function} callback The function that gets called for every
- * character.
- * @returns {Array} A new string of characters returned by the callback
- * function.
- */
- function mapDomain(string, fn) {
- var parts = string.split('@');
- var result = '';
- if (parts.length > 1) {
- // In email addresses, only the domain name should be punycoded. Leave
- // the local part (i.e. everything up to `@`) intact.
- result = parts[0] + '@';
- string = parts[1];
- }
- var labels = string.split(regexSeparators);
- var encoded = map(labels, fn).join('.');
- return result + encoded;
- }
-
- /**
- * Creates an array containing the numeric code points of each Unicode
- * character in the string. While JavaScript uses UCS-2 internally,
- * this function will convert a pair of surrogate halves (each of which
- * UCS-2 exposes as separate characters) into a single code point,
- * matching UTF-16.
- * @see `punycode.ucs2.encode`
- * @see
- * @memberOf punycode.ucs2
- * @name decode
- * @param {String} string The Unicode input string (UCS-2).
- * @returns {Array} The new array of code points.
- */
- function ucs2decode(string) {
- var output = [],
- counter = 0,
- length = string.length,
- value,
- extra;
- while (counter < length) {
- value = string.charCodeAt(counter++);
- if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
- // high surrogate, and there is a next character
- extra = string.charCodeAt(counter++);
- if ((extra & 0xFC00) == 0xDC00) { // low surrogate
- output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
- } else {
- // unmatched surrogate; only append this code unit, in case the next
- // code unit is the high surrogate of a surrogate pair
- output.push(value);
- counter--;
- }
- } else {
- output.push(value);
- }
- }
- return output;
- }
-
- /**
- * Creates a string based on an array of numeric code points.
- * @see `punycode.ucs2.decode`
- * @memberOf punycode.ucs2
- * @name encode
- * @param {Array} codePoints The array of numeric code points.
- * @returns {String} The new Unicode string (UCS-2).
- */
- function ucs2encode(array) {
- return map(array, function(value) {
- var output = '';
- if (value > 0xFFFF) {
- value -= 0x10000;
- output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
- value = 0xDC00 | value & 0x3FF;
- }
- output += stringFromCharCode(value);
- return output;
- }).join('');
- }
-
- /**
- * Converts a basic code point into a digit/integer.
- * @see `digitToBasic()`
- * @private
- * @param {Number} codePoint The basic numeric code point value.
- * @returns {Number} The numeric value of a basic code point (for use in
- * representing integers) in the range `0` to `base - 1`, or `base` if
- * the code point does not represent a value.
- */
- function basicToDigit(codePoint) {
- if (codePoint - 48 < 10) {
- return codePoint - 22;
- }
- if (codePoint - 65 < 26) {
- return codePoint - 65;
- }
- if (codePoint - 97 < 26) {
- return codePoint - 97;
- }
- return base;
- }
-
- /**
- * Converts a digit/integer into a basic code point.
- * @see `basicToDigit()`
- * @private
- * @param {Number} digit The numeric value of a basic code point.
- * @returns {Number} The basic code point whose value (when used for
- * representing integers) is `digit`, which needs to be in the range
- * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
- * used; else, the lowercase form is used. The behavior is undefined
- * if `flag` is non-zero and `digit` has no uppercase form.
- */
- function digitToBasic(digit, flag) {
- // 0..25 map to ASCII a..z or A..Z
- // 26..35 map to ASCII 0..9
- return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
- }
-
- /**
- * Bias adaptation function as per section 3.4 of RFC 3492.
- * http://tools.ietf.org/html/rfc3492#section-3.4
- * @private
- */
- function adapt(delta, numPoints, firstTime) {
- var k = 0;
- delta = firstTime ? floor(delta / damp) : delta >> 1;
- delta += floor(delta / numPoints);
- for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
- delta = floor(delta / baseMinusTMin);
- }
- return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
- }
-
- /**
- * Converts a Punycode string of ASCII-only symbols to a string of Unicode
- * symbols.
- * @memberOf punycode
- * @param {String} input The Punycode string of ASCII-only symbols.
- * @returns {String} The resulting string of Unicode symbols.
- */
- function decode(input) {
- // Don't use UCS-2
- var output = [],
- inputLength = input.length,
- out,
- i = 0,
- n = initialN,
- bias = initialBias,
- basic,
- j,
- index,
- oldi,
- w,
- k,
- digit,
- t,
- /** Cached calculation results */
- baseMinusT;
-
- // Handle the basic code points: let `basic` be the number of input code
- // points before the last delimiter, or `0` if there is none, then copy
- // the first basic code points to the output.
-
- basic = input.lastIndexOf(delimiter);
- if (basic < 0) {
- basic = 0;
- }
-
- for (j = 0; j < basic; ++j) {
- // if it's not a basic code point
- if (input.charCodeAt(j) >= 0x80) {
- error('not-basic');
- }
- output.push(input.charCodeAt(j));
- }
-
- // Main decoding loop: start just after the last delimiter if any basic code
- // points were copied; start at the beginning otherwise.
-
- for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
-
- // `index` is the index of the next character to be consumed.
- // Decode a generalized variable-length integer into `delta`,
- // which gets added to `i`. The overflow checking is easier
- // if we increase `i` as we go, then subtract off its starting
- // value at the end to obtain `delta`.
- for (oldi = i, w = 1, k = base; /* no condition */; k += base) {
-
- if (index >= inputLength) {
- error('invalid-input');
- }
-
- digit = basicToDigit(input.charCodeAt(index++));
-
- if (digit >= base || digit > floor((maxInt - i) / w)) {
- error('overflow');
- }
-
- i += digit * w;
- t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
-
- if (digit < t) {
- break;
- }
-
- baseMinusT = base - t;
- if (w > floor(maxInt / baseMinusT)) {
- error('overflow');
- }
-
- w *= baseMinusT;
-
- }
-
- out = output.length + 1;
- bias = adapt(i - oldi, out, oldi == 0);
-
- // `i` was supposed to wrap around from `out` to `0`,
- // incrementing `n` each time, so we'll fix that now:
- if (floor(i / out) > maxInt - n) {
- error('overflow');
- }
-
- n += floor(i / out);
- i %= out;
-
- // Insert `n` at position `i` of the output
- output.splice(i++, 0, n);
-
- }
-
- return ucs2encode(output);
- }
-
- /**
- * Converts a string of Unicode symbols (e.g. a domain name label) to a
- * Punycode string of ASCII-only symbols.
- * @memberOf punycode
- * @param {String} input The string of Unicode symbols.
- * @returns {String} The resulting Punycode string of ASCII-only symbols.
- */
- function encode(input) {
- var n,
- delta,
- handledCPCount,
- basicLength,
- bias,
- j,
- m,
- q,
- k,
- t,
- currentValue,
- output = [],
- /** `inputLength` will hold the number of code points in `input`. */
- inputLength,
- /** Cached calculation results */
- handledCPCountPlusOne,
- baseMinusT,
- qMinusT;
-
- // Convert the input in UCS-2 to Unicode
- input = ucs2decode(input);
-
- // Cache the length
- inputLength = input.length;
-
- // Initialize the state
- n = initialN;
- delta = 0;
- bias = initialBias;
-
- // Handle the basic code points
- for (j = 0; j < inputLength; ++j) {
- currentValue = input[j];
- if (currentValue < 0x80) {
- output.push(stringFromCharCode(currentValue));
- }
- }
-
- handledCPCount = basicLength = output.length;
-
- // `handledCPCount` is the number of code points that have been handled;
- // `basicLength` is the number of basic code points.
-
- // Finish the basic string - if it is not empty - with a delimiter
- if (basicLength) {
- output.push(delimiter);
- }
-
- // Main encoding loop:
- while (handledCPCount < inputLength) {
-
- // All non-basic code points < n have been handled already. Find the next
- // larger one:
- for (m = maxInt, j = 0; j < inputLength; ++j) {
- currentValue = input[j];
- if (currentValue >= n && currentValue < m) {
- m = currentValue;
- }
- }
-
- // Increase `delta` enough to advance the decoder's state to ,
- // but guard against overflow
- handledCPCountPlusOne = handledCPCount + 1;
- if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
- error('overflow');
- }
-
- delta += (m - n) * handledCPCountPlusOne;
- n = m;
-
- for (j = 0; j < inputLength; ++j) {
- currentValue = input[j];
-
- if (currentValue < n && ++delta > maxInt) {
- error('overflow');
- }
-
- if (currentValue == n) {
- // Represent delta as a generalized variable-length integer
- for (q = delta, k = base; /* no condition */; k += base) {
- t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
- if (q < t) {
- break;
- }
- qMinusT = q - t;
- baseMinusT = base - t;
- output.push(
- stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
- );
- q = floor(qMinusT / baseMinusT);
- }
-
- output.push(stringFromCharCode(digitToBasic(q, 0)));
- bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
- delta = 0;
- ++handledCPCount;
- }
- }
-
- ++delta;
- ++n;
-
- }
- return output.join('');
- }
-
- /**
- * Converts a Punycode string representing a domain name or an email address
- * to Unicode. Only the Punycoded parts of the input will be converted, i.e.
- * it doesn't matter if you call it on a string that has already been
- * converted to Unicode.
- * @memberOf punycode
- * @param {String} input The Punycoded domain name or email address to
- * convert to Unicode.
- * @returns {String} The Unicode representation of the given Punycode
- * string.
- */
- function toUnicode(input) {
- return mapDomain(input, function(string) {
- return regexPunycode.test(string)
- ? decode(string.slice(4).toLowerCase())
- : string;
- });
- }
-
- /**
- * Converts a Unicode string representing a domain name or an email address to
- * Punycode. Only the non-ASCII parts of the domain name will be converted,
- * i.e. it doesn't matter if you call it with a domain that's already in
- * ASCII.
- * @memberOf punycode
- * @param {String} input The domain name or email address to convert, as a
- * Unicode string.
- * @returns {String} The Punycode representation of the given domain name or
- * email address.
- */
- function toASCII(input) {
- return mapDomain(input, function(string) {
- return regexNonASCII.test(string)
- ? 'xn--' + encode(string)
- : string;
- });
- }
-
- /*--------------------------------------------------------------------------*/
-
- /** Define the public API */
- punycode = {
- /**
- * A string representing the current Punycode.js version number.
- * @memberOf punycode
- * @type String
- */
- 'version': '1.3.1',
- /**
- * An object of methods to convert from JavaScript's internal character
- * representation (UCS-2) to Unicode code points, and back.
- * @see
- * @memberOf punycode
- * @type Object
- */
- 'ucs2': {
- 'decode': ucs2decode,
- 'encode': ucs2encode
- },
- 'decode': decode,
- 'encode': encode,
- 'toASCII': toASCII,
- 'toUnicode': toUnicode
- };
-
- /** Expose `punycode` */
- // Some AMD build optimizers, like r.js, check for specific condition patterns
- // like the following:
- if (
- typeof define == 'function' &&
- typeof define.amd == 'object' &&
- define.amd
- ) {
- define('punycode', function() {
- return punycode;
- });
- } else if (freeExports && freeModule) {
- if (module.exports == freeExports) { // in Node.js or RingoJS v0.8.0+
- freeModule.exports = punycode;
- } else { // in Narwhal or RingoJS v0.7.0-
- for (key in punycode) {
- punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]);
- }
- }
- } else { // in Rhino or a web browser
- root.punycode = punycode;
- }
-
- }(this));
-
- var html2canvasNodeAttribute = "data-html2canvas-node";
- var html2canvasCanvasCloneAttribute = "data-html2canvas-canvas-clone";
- var html2canvasCanvasCloneIndex = 0;
-
- window.html2canvas = function(nodeList, options) {
- options = options || {};
- if (options.logging) {
- window.html2canvas.logging = true;
- window.html2canvas.start = Date.now();
+ while (data.length % 4) {
+ data.push(0);
}
- options.async = typeof(options.async) === "undefined" ? true : options.async;
- options.allowTaint = typeof(options.allowTaint) === "undefined" ? false : options.allowTaint;
- options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer;
- options.javascriptEnabled = typeof(options.javascriptEnabled) === "undefined" ? false : options.javascriptEnabled;
- options.imageTimeout = typeof(options.imageTimeout) === "undefined" ? 10000 : options.imageTimeout;
+ tmp = new Data(data);
+ sum = 0;
- if (typeof(nodeList) === "string") {
- if (typeof(options.proxy) !== "string") {
- return Promise.reject("Proxy must be used when rendering url");
- }
- return loadUrlDocument(absoluteUrl(nodeList), options.proxy, document, window.innerWidth, window.innerHeight, options).then(function(container) {
- return renderWindow(container.contentWindow.document.documentElement, container, options, window.innerWidth, window.innerHeight);
- });
+ for (i = _i = 0, _ref = data.length; _i < _ref; i = _i += 4) {
+ sum += tmp.readUInt32();
}
- var node = ((nodeList === undefined) ? [document.documentElement] : ((nodeList.length) ? nodeList : [nodeList]))[0];
- node.setAttribute(html2canvasNodeAttribute, "true");
- return renderDocument(node.ownerDocument, options, node.ownerDocument.defaultView.innerWidth, node.ownerDocument.defaultView.innerHeight).then(function(canvas) {
- if (typeof(options.onrendered) === "function") {
- log("options.onrendered is deprecated, html2canvas returns a Promise containing the canvas");
- options.onrendered(canvas);
- }
- return canvas;
- });
+ return sum & 0xffffffff;
+ };
+
+ return Directory;
+ }();
+
+ var Table,
+ __hasProp = {}.hasOwnProperty,
+ __extends = function (child, parent) {
+ for (var key in parent) {
+ if (__hasProp.call(parent, key)) child[key] = parent[key];
+ }
+
+ function ctor() {
+ this.constructor = child;
+ }
+
+ ctor.prototype = parent.prototype;
+ child.prototype = new ctor();
+ child.__super__ = parent.prototype;
+ return child;
};
+ /***************************************************************/
- window.html2canvas.punycode = this.punycode;
- window.html2canvas.proxy = {};
-
- function renderDocument(document, options, windowWidth, windowHeight) {
- return createWindowClone(document, document, windowWidth, windowHeight, options).then(function(container) {
- log("Document cloned");
- var selector = "[" + html2canvasNodeAttribute + "='true']";
- document.querySelector(selector).removeAttribute(html2canvasNodeAttribute);
- var clonedWindow = container.contentWindow;
- var node = clonedWindow.document.querySelector(selector);
- var oncloneHandler = (typeof(options.onclone) === "function") ? Promise.resolve(options.onclone(clonedWindow.document)) : Promise.resolve(true);
- return oncloneHandler.then(function() {
- return renderWindow(node, container, options, windowWidth, windowHeight);
- });
- });
- }
+ /* function : Table */
- function renderWindow(node, container, options, windowWidth, windowHeight) {
- var clonedWindow = container.contentWindow;
- var support = new Support(clonedWindow.document);
- var imageLoader = new ImageLoader(options, support);
- var bounds = getBounds(node);
- var width = options.type === "view" ? windowWidth : documentWidth(clonedWindow.document);
- var height = options.type === "view" ? windowHeight : documentHeight(clonedWindow.document);
- var renderer = new CanvasRenderer(width, height, imageLoader, options, document);
- var parser = new NodeParser(node, renderer, support, imageLoader, options);
- return parser.ready.then(function() {
- log("Finished rendering");
- var canvas;
-
- if (options.type === "view") {
- canvas = crop(renderer.canvas, {width: renderer.canvas.width, height: renderer.canvas.height, top: 0, left: 0, x: 0, y: 0});
- } else if (node === clonedWindow.document.body || node === clonedWindow.document.documentElement || options.canvas != null) {
- canvas = renderer.canvas;
- } else {
- canvas = crop(renderer.canvas, {width: options.width != null ? options.width : bounds.width, height: options.height != null ? options.height : bounds.height, top: bounds.top, left: bounds.left, x: clonedWindow.pageXOffset, y: clonedWindow.pageYOffset});
- }
+ /* comment : Save info for each table, and parse the table. */
- cleanupContainer(container, options);
- return canvas;
- });
- }
+ /***************************************************************/
+
+
+ Table = function () {
+ function Table(file) {
+ var info;
+ this.file = file;
+ info = this.file.directory.tables[this.tag];
+ this.exists = !!info;
- function cleanupContainer(container, options) {
- if (options.removeContainer) {
- container.parentNode.removeChild(container);
- log("Cleaned up container");
+ if (info) {
+ this.offset = info.offset, this.length = info.length;
+ this.parse(this.file.contents);
}
- }
+ }
- function crop(canvas, bounds) {
- var croppedCanvas = document.createElement("canvas");
- var x1 = Math.min(canvas.width - 1, Math.max(0, bounds.left));
- var x2 = Math.min(canvas.width, Math.max(1, bounds.left + bounds.width));
- var y1 = Math.min(canvas.height - 1, Math.max(0, bounds.top));
- var y2 = Math.min(canvas.height, Math.max(1, bounds.top + bounds.height));
- croppedCanvas.width = bounds.width;
- croppedCanvas.height = bounds.height;
- log("Cropping canvas at:", "left:", bounds.left, "top:", bounds.top, "width:", (x2-x1), "height:", (y2-y1));
- log("Resulting crop with width", bounds.width, "and height", bounds.height, " with x", x1, "and y", y1);
- croppedCanvas.getContext("2d").drawImage(canvas, x1, y1, x2-x1, y2-y1, bounds.x, bounds.y, x2-x1, y2-y1);
- return croppedCanvas;
- }
+ Table.prototype.parse = function () {};
- function documentWidth (doc) {
- return Math.max(
- Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth),
- Math.max(doc.body.offsetWidth, doc.documentElement.offsetWidth),
- Math.max(doc.body.clientWidth, doc.documentElement.clientWidth)
- );
- }
+ Table.prototype.encode = function () {};
- function documentHeight (doc) {
- return Math.max(
- Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight),
- Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight),
- Math.max(doc.body.clientHeight, doc.documentElement.clientHeight)
- );
- }
+ Table.prototype.raw = function () {
+ if (!this.exists) {
+ return null;
+ }
- function smallImage() {
- return "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
- }
+ this.file.contents.pos = this.offset;
+ return this.file.contents.read(this.length);
+ };
- function createWindowClone(ownerDocument, containerDocument, width, height, options) {
- labelCanvasElements(ownerDocument);
- var documentElement = ownerDocument.documentElement.cloneNode(true),
- container = containerDocument.createElement("iframe");
-
- container.className = "html2canvas-container";
- container.style.visibility = "hidden";
- container.style.position = "fixed";
- container.style.left = "-10000px";
- container.style.top = "0px";
- container.style.border = "0";
- container.width = width;
- container.height = height;
- container.scrolling = "no"; // ios won't scroll without it
- containerDocument.body.appendChild(container);
-
- return new Promise(function(resolve) {
- var documentClone = container.contentWindow.document;
- /* Chrome doesn't detect relative background-images assigned in inline ' +
- ' ' +
- ' ' +
- '' +
- this.getTable(true) +
- '