/** * @license Highcharts JS v7.0.0 (2018-12-11) * * (c) 2009-2018 Torstein Honsi * * License: www.highcharts.com/license */ 'use strict'; (function (factory) { if (typeof module === 'object' && module.exports) { module.exports = factory; } else if (typeof define === 'function' && define.amd) { define(function () { return factory; }); } else { factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined); } }(function (Highcharts) { (function (H) { /** * (c) 2010-2018 Torstein Honsi * * License: www.highcharts.com/license */ var CenteredSeriesMixin = H.CenteredSeriesMixin, 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); } // Extend the Pane prototype extend(Pane.prototype, { coll: 'pane', // Member of chart.pane /** * Initiate the Pane object * * @private * @function Highcharts.Pane#init * * @param {Highcharts.PaneOptions} options * * @param {Highcharts.Chart} chart */ init: function (options, chart) { this.chart = chart; this.background = []; chart.pane.push(this); this.setOptions(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, options ); }, /** * Render the pane with its backgrounds. * * @private * @function Highcharts.Pane#render */ render: function () { var options = this.options, backgroundOption = this.options.background, renderer = this.chart.renderer, len, i; if (!this.group) { this.group = renderer.g('pane-group') .attr({ zIndex: options.zIndex || 0 }) .add(); } this.updateCenter(); // Render the backgrounds if (backgroundOption) { backgroundOption = splat(backgroundOption); len = Math.max( backgroundOption.length, this.background.length || 0 ); for (i = 0; i < len; i++) { // #6641 - if axis exists, chart is circular and apply // background if (backgroundOption[i] && this.axis) { this.renderBackground( merge( this.defaultBackgroundOptions, backgroundOption[i] ), i ); } else if (this.background[i]) { this.background[i] = this.background[i].destroy(); this.background.splice(i, 1); } } } }, /** * Render an individual pane background. * * @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', 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() .add(this.group); method = 'attr'; } this.background[i][method]({ 'd': this.axis.getPlotBandPath( backgroundOptions.from, backgroundOptions.to, backgroundOptions ) }).attr(attribs); }, /** * The pane serves as a container for axes and backgrounds for circular * gauges and polar charts. * * @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. * * @sample {highcharts} highcharts/demo/gauge-vu-meter/ * Two gauges with different center * * @type {Array} * @default ["50%", "50%"] * @since 2.3.0 * @product highcharts */ center: ['50%', '50%'], /** * The size of the pane, either as a number defining pixels, or a * percentage defining a percentage of the plot are. * * @sample {highcharts} highcharts/demo/gauge-vu-meter/ * Smaller size * * @type {number|string} * @product highcharts */ size: '85%', /** * The start angle of the polar X axis or gauge axis, given in degrees * where 0 is north. Defaults to 0. * * @sample {highcharts} highcharts/demo/gauge-vu-meter/ * VU-meter with custom start and end angle * * @since 2.3.0 * @product highcharts */ startAngle: 0 }, /** * An array of background items for the pane. * * @sample {highcharts} highcharts/demo/gauge-speedometer/ * Speedometer gauge with multiple backgrounds * * @type {Array<*>} * @optionparent pane.background */ defaultBackgroundOptions: { /** * The class name for this background. * * @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 */ /** * 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. * * @type {string} * @since 2.3.0 * @validvalue ["arc", "circle", "solid"] * @product highcharts */ shape: 'circle', /** * The pixel border width of the pane background. * * @since 2.3.0 * @product highcharts */ borderWidth: 1, /** * The pane background border color. * * @type {Highcharts.ColorString} * @since 2.3.0 * @product highcharts */ borderColor: '#cccccc', /** * The background color or gradient for the pane. * * @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: { /** * @ignore */ linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, /** * @ignore */ stops: [ [0, '#ffffff'], [1, '#e6e6e6'] ] }, /** @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} * @since 2.3.0 * @product highcharts */ innerRadius: 0, /** * @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} * @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) { 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); this.background.forEach(function (background) { background.destroy(); }); this.background.length = 0; this.group = this.group.destroy(); }, */ /** * Update the pane item with new options * * @private * @function Highcharts.Pane#update * * @param {Highcharts.PaneOptions} options * New pane options * * @param {boolean} redraw */ update: function (options, redraw) { merge(true, this.options, options); this.setOptions(this.options); this.render(); this.chart.axes.forEach(function (axis) { if (axis.pane === this) { axis.pane = null; axis.update({}, redraw); } }, this); } }); H.Pane = Pane; }(Highcharts)); (function (H) { /** * (c) 2010-2018 Torstein Honsi * * License: www.highcharts.com/license */ var addEvent = H.addEvent, Axis = H.Axis, extend = H.extend, 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 radialAxisMixin, // @todo Extract this to a new file axisProto = Axis.prototype, tickProto = Tick.prototype; if (!H.radialAxisExtended) { H.radialAxisExtended = true; // 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 = { // 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 }, // 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 }, // 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 } }, // Merge and set options setOptions: function (userOptions) { 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 = []; } H.fireEvent(this, 'afterSetOptions'); }, // 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; }, /* * * 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 () { // Call uber method axisProto.setAxisTranslation.call(this); // Set transA and minPixelPadding if (this.center) { // it's not defined the first time if (this.isCircular) { this.transA = (this.endAngleRad - this.startAngleRad) / ((this.max - this.min) || 1); } else { this.transA = ( (this.center[2] / 2) / ((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 && correctFloat(this.endAngleRad - this.startAngleRad) === correctFloat(2 * Math.PI) ); if (this.autoConnect) { this.max += ( (this.categories && 1) || this.pointRange || this.closestPointRange || 0 ); // #1197, #2260 } }, /* * * 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 () { axisProto.setAxisSize.call(this); 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; } // Axis len is used to lay out the ticks this.len = this.width = this.height = this.center[2] * pick(this.sector, 1) / 2; } }, /* * * 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 ); }, /* * * Translate from intermediate plotX (angle), plotY (axis.len - radius) * to final chart coordinates. */ postTranslate: function (angle, radius) { var chart = this.chart, center = this.center; angle = this.startAngleRad + angle; return { x: chart.plotLeft + center[0] + Math.cos(angle) * radius, y: chart.plotTop + center[1] + Math.sin(angle) * radius }; }, /* * * 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) ); // Circular grid bands } else { // Keep within bounds from = Math.max(from, this.min); to = Math.min(to, this.max); // Plot bands on Y axis (radial axis) - inner and outer radius // depend on to and from if (!isCircular) { radii[0] = this.translate(from); radii[1] = this.translate(to); } // Convert percentages to pixel values radii = radii.map(function (radius) { if (percentRegex.test(radius)) { radius = (pInt(radius, 10) * fullRadius) / 100; } return radius; }); // Handle full circle if (options.shape === 'circle' || !isCircular) { start = -Math.PI / 2; end = Math.PI * 1.5; open = true; } else { start = startAngleRad + this.translate(from); end = startAngleRad + this.translate(to); } radii[0] -= offset; // #5283 radii[2] -= offset; // #5283 ret = this.chart.renderer.symbols.arc( this.left + center[0], this.top + center[1], radii[0], radii[0], { // 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 } ); } return ret; }, /* * * 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); // 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 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(); } tickPositions.forEach(function (pos, i) { xy = xAxis.getPosition(pos, value); ret.push(i ? 'L' : 'M', xy.x, xy.y); }); } return ret; }, /* * * 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 chart = this.chart, 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); } // 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; } // A pointer back to this axis to borrow geometry if (pane && isCircular) { pane.axis = this; } this.isCircular = isCircular; }); addEvent(Axis, 'afterInit', function () { var chart = this.chart, options = this.options, isHidden = chart.angular && this.isXAxis, pane = this.pane, paneOptions = pane && pane.options; if (!isHidden && pane && (chart.angular || chart.polar)) { // 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. addEvent(Tick, 'afterGetPosition', function (e) { if (this.axis.getPosition) { extend(e.pos, this.axis.getPosition(this.pos)); } }); // 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 && 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 } else { align = 'center'; // top or bottom } } else { align = 'center'; } label.attr({ align: align }); } e.pos.x = ret.x + labelOptions.x; e.pos.y = ret.y + optionsY; } }); // 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-2018 Torstein Honsi * * License: www.highcharts.com/license */ var noop = H.noop, pick = H.pick, extend = H.extend, isArray = H.isArray, defined = H.defined, Series = H.Series, seriesType = H.seriesType, seriesTypes = H.seriesTypes, seriesProto = Series.prototype, 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. * Requires `highcharts-more.js`. * * @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. * * @since 2.3.0 * @product highcharts highstock */ lineWidth: 1, threshold: null, tooltip: { 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. * * @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. * * @extends plotOptions.series.dataLabels * @since 2.3.0 * @excluding x, y * @product highcharts highstock */ dataLabels: { align: null, verticalAlign: null, /** * X offset of the lower data labels relative to the point value. * * @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. * * @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. * * @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. * * @sample {highcharts|highstock} highcharts/plotoptions/arearange-datalabels/ * Data labels on range series * * @since 2.3.0 * @product highcharts highstock */ yHigh: 0 } // Prototype members }, { pointArrayMap: ['low', 'high'], 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) { // Find the polar plotX and plotY var chart = this.chart, 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 () { var series = this, yAxis = series.yAxis, hasModifyValue = !!series.modifyValue; seriesTypes.area.prototype.translate.apply(series); // Set plotLow and plotHigh series.points.forEach(function (point) { var low = point.low, high = point.high, plotY = point.plotY; if (high === null || low === null) { point.isNull = true; point.plotY = null; } else { point.plotLow = plotY; point.plotHigh = yAxis.translate( hasModifyValue ? series.modifyValue(high, point) : high, 0, 1, 0, 1 ); if (hasModifyValue) { point.yBottom = point.plotHigh; } } }); // Postprocess plotHigh if (this.chart.polar) { this.points.forEach(function (point) { series.highToXY(point); point.tooltipPos = [ (point.plotHighX + point.plotLowX) / 2, (point.plotHigh + point.plotLow) / 2 ]; }); } }, // 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 = [], i, getGraphPath = seriesTypes.area.prototype.getGraphPath, point, pointShim, linePath, lowerPath, options = this.options, connectEnds = this.chart.polar && options.connectEnds !== false, connectNulls = options.connectNulls, step = options.step, higherPath, higherAreaPath; 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. i = points.length; while (i--) { point = points[i]; if ( !point.isNull && !connectEnds && !connectNulls && (!points[i + 1] || points[i + 1].isNull) ) { highAreaPoints.push({ plotX: point.plotX, plotY: point.plotY, doCurve: false // #5186, gaps in areasplinerange fill }); } pointShim = { polarPlotY: point.polarPlotY, rectPlotX: point.rectPlotX, yBottom: point.yBottom, // plotHighX is for polar charts plotX: pick(point.plotHighX, point.plotX), plotY: point.plotHigh, isNull: point.isNull }; highAreaPoints.push(pointShim); highPoints.push(pointShim); if ( !point.isNull && !connectEnds && !connectNulls && (!points[i - 1] || points[i - 1].isNull) ) { highAreaPoints.push({ plotX: point.plotX, plotY: point.plotY, doCurve: false // #5186, gaps in areasplinerange fill }); } } // Get the paths lowerPath = getGraphPath.call(this, points); if (step) { if (step === true) { step = 'left'; } options.step = { left: 'right', center: 'center', right: 'left' }[step]; // swap for reading in getGraphPath } higherPath = getGraphPath.call(this, highPoints); higherAreaPath = getGraphPath.call(this, highAreaPoints); options.step = step; // 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' if (!this.chart.polar && higherAreaPath[0] === 'M') { higherAreaPath[0] = 'L'; // this probably doesn't work for spline } this.graphPath = linePath; this.areaPath = lowerPath.concat(higherAreaPath); // Prepare for sideways animation linePath.isArea = true; linePath.xMap = lowerPath.xMap; this.areaPath.xMap = lowerPath.xMap; return linePath; }, // Extend the basic drawDataLabels method by running it for both lower and // higher values. drawDataLabels: function () { var data = this.points, length = data.length, i, originalDataLabels = [], dataLabelOptions = this.options.dataLabels, point, up, 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; } // 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 = upperDataLabelOptions.inside ? point.plotHigh < point.plotLow : point.plotHigh > point.plotLow; 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 originalDataLabels[i] = point.dataLabel; point.dataLabel = point.dataLabelUpper; // Set the default offset point.below = up; if (inverted) { if (!upperDataLabelOptions.align) { upperDataLabelOptions.align = up ? 'right' : 'left'; } } else { if (!upperDataLabelOptions.verticalAlign) { upperDataLabelOptions.verticalAlign = up ? 'top' : 'bottom'; } } } } this.options.dataLabels = upperDataLabelOptions; if (seriesProto.drawDataLabels) { seriesProto.drawDataLabels.apply(this, arguments); // #1209 } // 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) { point.dataLabelUpper = point.dataLabel; point.dataLabel = originalDataLabels[i]; 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 (!lowerDataLabelOptions.align) { lowerDataLabelOptions.align = up ? 'left' : 'right'; } } else { if (!lowerDataLabelOptions.verticalAlign) { lowerDataLabelOptions.verticalAlign = up ? 'bottom' : 'top'; } } } } this.options.dataLabels = lowerDataLabelOptions; if (seriesProto.drawDataLabels) { seriesProto.drawDataLabels.apply(this, arguments); } } // 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.dataLabel, point.dataLabelUpper] .filter(function (label) { return !!label; }); } } } // Reset options this.options.dataLabels = dataLabelOptions; }, alignDataLabel: function () { seriesTypes.column.prototype.alignDataLabel.apply(this, arguments); }, drawPoints: function () { var series = this, pointLength = series.points.length, point, i; // 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.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; H.extend(point, point.origProps); delete point.origProps; i++; } }, setStackedPoints: noop }, { setState: function () { var prevState = this.state, series = this.series, isPolar = series.chart.polar; if (!defined(this.plotHigh)) { // Boost doesn't calculate plotHigh this.plotHigh = series.yAxis.toPixels(this.high, true); } if (!defined(this.plotLow)) { // Boost doesn't calculate plotLow this.plotLow = this.plotY = series.yAxis.toPixels(this.low, true); } if (series.stateMarkerGraphic) { series.lowerStateMarkerGraphic = series.stateMarkerGraphic; series.stateMarkerGraphic = series.upperStateMarkerGraphic; } // Change state also for the top marker this.graphic = this.upperGraphic; this.plotY = this.plotHigh; if (isPolar) { this.plotX = this.plotHighX; } // Top state: pointProto.setState.apply(this, arguments); this.state = prevState; // Now restore defaults this.plotY = this.plotLow; this.graphic = this.lowerGraphic; if (isPolar) { this.plotX = this.plotLowX; } if (series.stateMarkerGraphic) { series.upperStateMarkerGraphic = series.stateMarkerGraphic; series.stateMarkerGraphic = series.lowerStateMarkerGraphic; // Lower marker is stored at stateMarkerGraphic // to avoid reference duplication (#7021) series.lowerStateMarkerGraphic = undefined; } pointProto.setState.apply(this, arguments); }, haloPath: function () { var isPolar = this.series.chart.polar, path = []; // Bottom halo this.plotY = this.plotLow; if (isPolar) { this.plotX = this.plotLowX; } if (this.isInside) { path = pointProto.haloPath.apply(this, arguments); } // Top halo this.plotY = this.plotHigh; if (isPolar) { this.plotX = this.plotHighX; } if (this.isTopInside) { path = path.concat( pointProto.haloPath.apply(this, arguments) ); } return path; }, 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). * * * @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: * * 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, 8, 3], * [1, 1, 1], * [2, 6, 8] * ] * ``` * * 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, * high: 0, * name: "Point2", * color: "#00FF00" * }, { * x: 1, * low: 3, * high: 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 * @excluding marker, y * @product highcharts highstock * @apioption series.arearange.data */ /** * The high or maximum value for each data point. * * @type {number} * @product highcharts highstock * @apioption series.arearange.data.high */ /** * The low or minimum value for each data point. * * @type {number} * @product highcharts highstock * @apioption series.arearange.data.low */ /** * @excluding x, y * @product highcharts highstock * @apioption series.arearange.dataLabels */ }(Highcharts)); (function (H) { /** * (c) 2010-2018 Torstein Honsi * * License: www.highcharts.com/license */ var seriesType = H.seriesType, seriesTypes = H.seriesTypes; /** * The area spline range is a cartesian series type with higher and * 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`. * * @sample {highstock|highstock} stock/demo/areasplinerange/ * Area spline range * * @extends plotOptions.arearange * @since 2.3.0 * @excluding step * @product highcharts highstock * @apioption plotOptions.areasplinerange */ seriesType('areasplinerange', 'arearange', null, { getPointSpline: seriesTypes.spline.prototype.getPointSpline }); /** * 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 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/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.arearange.data * @product highcharts highstock * @apioption series.areasplinerange.data */ }(Highcharts)); (function (H) { /** * (c) 2010-2018 Torstein Honsi * * License: www.highcharts.com/license */ var defaultPlotOptions = H.defaultPlotOptions, merge = H.merge, noop = H.noop, pick = H.pick, seriesType = H.seriesType, 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`. * * @sample {highcharts|highstock} highcharts/demo/columnrange/ * Inverted column range * * @extends plotOptions.column * @since 2.3.0 * @excluding negativeColor, stacking, softThreshold, threshold * @product highcharts highstock * @optionparent plotOptions.columnrange */ var columnRangeOptions = { /** * 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. * * @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, defaultPlotOptions.arearange, columnRangeOptions ), { // Translate data points from raw values x and y to plotX and plotY translate: function () { var series = this, yAxis = series.yAxis, xAxis = series.xAxis, startAngleRad = xAxis.startAngleRad, start, chart = series.chart, isRadial = series.xAxis.isRadial, safeDistance = Math.max(chart.chartWidth, chart.chartHeight) + 999, plotHigh; // Don't draw too far outside plot area (#6835) function safeBounds(pixelPos) { return Math.min(Math.max( -safeDistance, pixelPos ), safeDistance); } colProto.translate.apply(series); // Set plotLow and plotHigh series.points.forEach(function (point) { var shapeArgs = point.shapeArgs, minPointLength = series.options.minPointLength, heightDifference, height, y; point.plotHigh = plotHigh = safeBounds( yAxis.translate(point.high, 0, 1, 0, 1) ); point.plotLow = safeBounds(point.plotY); // adjust shape y = plotHigh; height = pick(point.rectPlotY, point.plotY) - plotHigh; // Adjust for minPointLength if (Math.abs(height) < minPointLength) { heightDifference = (minPointLength - height); height += heightDifference; y -= heightDifference / 2; // Adjust for negative ranges or reversed Y axis (#1457) } else if (height < 0) { height *= -1; y -= height; } if (isRadial) { start = point.barX + startAngleRad; point.shapeType = 'path'; point.shapeArgs = { 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 } }); }, directTouch: true, trackerGroups: ['group', 'dataLabelsGroup'], drawGraph: noop, getSymbol: noop, // Overrides from modules that may be loaded after this module 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 () { return colProto.polarArc.apply(this, arguments); }, translate3dPoints: function () { return colProto.translate3dPoints.apply(this, arguments); }, translate3dShapes: function () { return colProto.translate3dShapes.apply(this, arguments); } }, { setState: colProto.pointClass.prototype.setState }); /** * A `columnrange` series. If the [type](#series.columnrange.type) * 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 */ /** * 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] * ] * ``` * * 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" * }] * ``` * * @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.arearange.data * @excluding marker * @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) { /* * * * (c) 2010-2018 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 */ /** * 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-2018 Torstein Honsi * * License: www.highcharts.com/license */ var isNumber = H.isNumber, merge = H.merge, noop = H.noop, pick = H.pick, pInt = H.pInt, Series = H.Series, 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, 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. * * @extends plotOptions.series.dataLabels * @since 2.3.0 * @product highcharts */ dataLabels: { /** * Enable or disable the data labels. * * @since 2.3.0 * @product highcharts highmaps */ enabled: true, defer: false, /** * The y position offset of the label relative to the center of the * gauge. * * @since 2.3.0 * @product highcharts highmaps */ y: 15, /** * The border radius in pixels for the gauge's data label. * * @since 2.3.0 * @product highcharts highmaps */ borderRadius: 3, crop: false, /** * The vertical alignment of the data label. * * @product highcharts highmaps */ verticalAlign: 'top', /** * The Z index of the data labels. A value of 2 display them behind * the dial. * * @since 2.1.5 * @product highcharts highmaps */ zIndex: 2, /** * The border width in pixels for the gauge data label. * * @since 2.3.0 * @product highcharts highmaps */ borderWidth: 1, /** * The border color for the data label. * * @type {Highcharts.ColorString} * @default #cccccc * @since 2.3.0 * @product highcharts highmaps */ borderColor: '#cccccc' }, /** * 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. * * @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 */ /** * 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 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 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 background or fill color of the gauge's dial. * * @sample {highcharts} highcharts/plotoptions/gauge-dial/ * Dial options demonstrated * * @type {Highcharts.ColorString|Highcharts.GradientColorObject} * @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 when this * option is set to 5. * * @see [wrap](#plotOptions.gauge.wrap) * * @sample {highcharts} highcharts/plotoptions/gauge-overshoot/ * Allow 5 degrees overshoot * * @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. * * @sample {highcharts} highcharts/css/gauge/ * Styled mode * * @since 2.3.0 * @product highcharts */ pivot: {}, /** * 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 background color or fill of the pivot. * * @sample {highcharts} highcharts/plotoptions/gauge-pivot/ * Pivot options demonstrated * * @type {Highcharts.ColorString|Highcharts.GradientColorObject} * @default #000000 * @since 2.3.0 * @product highcharts * @apioption plotOptions.gauge.pivot.backgroundColor */ tooltip: { headerFormat: '' }, /** * Whether to display this particular series or series type in the * legend. Defaults to false for gauge series. * * @since 2.3.0 * @product highcharts */ showInLegend: false // Prototype members }, { // 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, fixedBox: true, forceDL: true, noSharedTooltip: true, trackerGroups: ['group', 'dataLabelsGroup'], // Calculate paths etc translate: function () { var series = this, yAxis = series.yAxis, options = series.options, center = yAxis.center; series.generatePoints(); 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, baseWidth = dialOptions.baseWidth || 3, topWidth = dialOptions.topWidth || 1, overshoot = options.overshoot, 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) ); } else if (options.wrap === false) { rotation = Math.max( yAxis.startAngleRad, Math.min(yAxis.endAngleRad, rotation) ); } rotation = rotation * 180 / Math.PI; point.shapeType = 'path'; point.shapeArgs = { d: dialOptions.path || [ 'M', -rearLength, -baseWidth / 2, 'L', baseLength, -baseWidth / 2, radius, -topWidth / 2, radius, topWidth / 2, baseLength, baseWidth / 2, -rearLength, baseWidth / 2, 'z' ], translateX: center[0], translateY: center[1], rotation: rotation }; // Positions for data label point.plotX = center[0]; point.plotY = center[1]; }); }, // 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 = chart.renderer; series.points.forEach(function (point) { var graphic = point.graphic, shapeArgs = point.shapeArgs, d = shapeArgs.d, dialOptions = merge(options.dial, point.dial); // #1233 if (graphic) { graphic.animate(shapeArgs); shapeArgs.d = d; // animate alters it } else { point.graphic = renderer[point.shapeType](shapeArgs) .attr({ // required by VML when animation is false rotation: shapeArgs.rotation, zIndex: 1 }) .addClass('highcharts-dial') .add(series.group); // Presentational attributes if (!chart.styledMode) { point.graphic.attr({ stroke: dialOptions.borderColor || 'none', 'stroke-width': dialOptions.borderWidth || 0, fill: dialOptions.backgroundColor || '#000000' }); } } }); // Add or move the pivot if (pivot) { pivot.animate({ // #1235 translateX: center[0], translateY: center[1] }); } else { series.pivot = renderer.circle(0, 0, pick(pivotOptions.radius, 5)) .attr({ zIndex: 2 }) .addClass('highcharts-pivot') .translate(center[0], center[1]) .add(series.group); // Presentational attributes 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) { var series = this; if (!init) { series.points.forEach(function (point) { var graphic = point.graphic; if (graphic) { // start value graphic.attr({ rotation: series.yAxis.startAngleRad * 180 / Math.PI }); // animate graphic.animate({ rotation: point.shapeArgs.rotation }, series.options.animation); } }); // delete this function to allow it only once series.animate = null; } }, render: function () { this.group = this.plotGroup( 'group', 'series', this.visible ? 'visible' : 'hidden', this.options.zIndex, this.chart.seriesGroup ); Series.prototype.render.call(this); 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) { Series.prototype.setData.call(this, data, false); this.processData(); this.generatePoints(); if (pick(redraw, true)) { this.chart.redraw(); } }, // If the tracking module is loaded, add the point tracker drawTracker: TrackerMixin && TrackerMixin.drawTrackerPoint // Point members }, { // Don't do any hover colors or anything setState: function (state) { this.state = state; } }); /** * A `gauge` series. If the [type](#series.gauge.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @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 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. * * @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 drilldown, marker, x * @product highcharts * @apioption series.gauge.data */ }(Highcharts)); (function (H) { /** * (c) 2010-2018 Torstein Honsi * * License: www.highcharts.com/license */ var noop = H.noop, pick = H.pick, seriesType = H.seriesType, seriesTypes = H.seriesTypes; /** * The boxplot series type. * * @private * @class * @name Highcharts.seriesTypes#boxplot * * @augments Highcharts.Series */ /** * A box plot is a convenient way of depicting groups of data through their * 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 * @excluding borderColor, borderRadius, borderWidth, groupZPadding, states * @product highcharts * @optionparent plotOptions.boxplot */ seriesType('boxplot', 'column', { threshold: null, tooltip: { 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. * * @sample {highcharts} highcharts/plotoptions/box-plot-styling/ * True by default * * @type {number|string} * @since 3.0 * @product highcharts */ whiskerLength: '50%', /** * The fill color of the box. * * 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} * @default #ffffff * @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) * or [whiskerWidth](#plotOptions.boxplot.whiskerWidth) are `null`, * 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 * * @since 3.0 * @product highcharts */ lineWidth: 1, /** * The color of the median line. If `undefined`, the general series color * applies. * * 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 * * @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. * * 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 * * @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 `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 * * @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. * * @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 * * @type {string} * @default Solid * @since 3.0 * @product highcharts * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot", * "ShortDashDotDot", "Dot", "Dash" ,"LongDash", "DashDot", * "LongDashDot", "LongDashDotDot"] * @apioption plotOptions.boxplot.stemDashStyle */ /** * The width of the stem, the vertical line extending from the box to * 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 * * @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 `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 * * @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 `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 * @product highcharts */ whiskerWidth: 2 }, /** @lends Highcharts.seriesTypes.boxplot */ { // 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]; }, // defines the top of the tracker pointValKey: 'high', // Get presentational attributes pointAttribs: function () { // No attributes should be set on point.graphic which is the group return {}; }, // Disable data labels for box plot drawDataLabels: noop, // Translate data points from raw values x and y to plotX and plotY translate: function () { var series = this, yAxis = series.yAxis, pointArrayMap = series.pointArrayMap; seriesTypes.column.prototype.translate.apply(series); // do the translation on each point dimension series.points.forEach(function (point) { pointArrayMap.forEach(function (key) { if (point[key] !== null) { point[key + 'Plot'] = yAxis.translate( point[key], 0, 1, 0, 1 ); } }); }); }, // Draw the data points drawPoints: function () { var series = this, points = series.points, options = series.options, chart = series.chart, renderer = chart.renderer, q1Plot, q3Plot, highPlot, lowPlot, medianPlot, medianPath, crispCorr, crispX = 0, boxPath, width, left, right, halfWidth, // error bar inherits this series type but doesn't do quartiles doQuartiles = series.doQuartiles !== false, pointWiskerLength, whiskerLength = series.options.whiskerLength; points.forEach(function (point) { var graphic = point.graphic, verb = graphic ? 'animate' : 'attr', shapeArgs = point.shapeArgs, boxAttr = {}, stemAttr = {}, whiskersAttr = {}, medianAttr = {}, color = point.color || series.color; if (point.plotY !== undefined) { // crisp vector coordinates width = shapeArgs.width; left = Math.floor(shapeArgs.x); right = left + width; halfWidth = Math.round(width / 2); q1Plot = Math.floor(doQuartiles ? point.q1Plot : point.lowPlot); q3Plot = Math.floor(doQuartiles ? point.q3Plot : point.lowPlot); highPlot = Math.floor(point.highPlot); lowPlot = Math.floor(point.lowPlot); if (!graphic) { point.graphic = graphic = renderer.g('point') .add(series.group); point.stem = renderer.path() .addClass('highcharts-boxplot-stem') .add(graphic); if (whiskerLength) { point.whiskers = renderer.path() .addClass('highcharts-boxplot-whisker') .add(graphic); } if (doQuartiles) { point.box = renderer.path(boxPath) .addClass('highcharts-boxplot-box') .add(graphic); } point.medianShape = renderer.path(medianPath) .addClass('highcharts-boxplot-median') .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); } // 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 'M', crispX, q3Plot, 'L', crispX, highPlot, // stem down 'M', crispX, q1Plot, 'L', crispX, lowPlot ] }); // The box if (doQuartiles) { crispCorr = (point.box.strokeWidth() % 2) / 2; q1Plot = Math.floor(q1Plot) + crispCorr; q3Plot = Math.floor(q3Plot) + crispCorr; left += crispCorr; right += crispCorr; point.box[verb]({ d: [ 'M', left, q3Plot, 'L', left, q1Plot, 'L', right, q1Plot, 'L', right, q3Plot, 'L', left, q3Plot, 'z' ] }); } // The whiskers if (whiskerLength) { crispCorr = (point.whiskers.strokeWidth() % 2) / 2; highPlot = highPlot + crispCorr; lowPlot = lowPlot + crispCorr; pointWiskerLength = (/%$/).test(whiskerLength) ? halfWidth * parseFloat(whiskerLength) / 100 : whiskerLength / 2; point.whiskers[verb]({ d: [ // High whisker 'M', crispX - pointWiskerLength, highPlot, 'L', crispX + pointWiskerLength, highPlot, // Low whisker 'M', crispX - pointWiskerLength, lowPlot, 'L', crispX + pointWiskerLength, lowPlot ] }); } // The median medianPlot = Math.round(point.medianPlot); crispCorr = (point.medianShape.strokeWidth() % 2) / 2; medianPlot = medianPlot + crispCorr; point.medianShape[verb]({ d: [ 'M', left, medianPlot, 'L', right, medianPlot ] }); } }); }, setStackedPoints: noop // #3890 }); /** * A `boxplot` series. If the [type](#series.boxplot.type) option is * not specified, it is inherited from [chart.type](#chart.type). * * @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 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/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 * @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 * @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 * @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 * @apioption series.boxplot.data.median */ /** * The lower quartile for each data point. This is the bottom of the * box. * * @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 * @apioption series.boxplot.data.q3 */ }(Highcharts)); (function (H) { /** * (c) 2010-2018 Torstein Honsi * * License: www.highcharts.com/license */ 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 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. * * @sample {highcharts} highcharts/plotoptions/error-bar-styling/ * Error bar styling * * @type {Highcharts.ColorString|Highcharts.GradientColorObject} * @default #000000 * @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. * * @since 3.0 * @product highcharts */ linkedTo: ':previous', tooltip: { pointFormat: '\u25CF {series.name}: {point.low} - {point.high}
' }, /** * The line width of the whiskers, the horizontal lines marking low * 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 }, { type: 'errorbar', pointArrayMap: ['low', 'high'], // array point configs are mapped to this 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) 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 () { return (this.linkedParent && this.linkedParent.columnMetrics) || seriesTypes.column.prototype.getColumnMetrics.call(this); } }); /** * A `errorbar` series. If the [type](#series.errorbar.type) option * is not specified, it is inherited from [chart.type](#chart.type). * * @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 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/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.arearange.data * @excluding dataLabels, drilldown, marker, states * @product highcharts * @apioption series.errorbar.data */ }(Highcharts)); (function (H) { /** * (c) 2010-2018 Torstein Honsi * * License: www.highcharts.com/license */ var correctFloat = H.correctFloat, isNumber = H.isNumber, pick = H.pick, Point = H.Point, Series = H.Series, seriesType = H.seriesType, seriesTypes = H.seriesTypes; /** * A waterfall chart displays sequentially introduced positive or negative * values in cumulative columns. * * @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', { /** * 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} * @product highcharts * @apioption plotOptions.waterfall.upColor */ dataLabels: { inside: true }, /** * The width of the line connecting waterfall columns. * * @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 {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: 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 {string} * @since 3.0 * @validvalue ["Dash", "DashDot", "Dot", "LongDash", "LongDashDot", * "LongDashDotDot", "ShortDash", "ShortDashDot", * "ShortDashDotDot", "ShortDot", "Solid"] * @product highcharts */ 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 {Highcharts.ColorString} * @since 3.0 * @product highcharts */ borderColor: '#333333', states: { hover: { lineWidthPlus: 0 // #3126 } } // Prototype members }, { pointValKey: 'y', // 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 previousIntermediate = this.options.threshold, point, len, i, 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.isSum) { point.y = correctFloat(y); } else if (point.isIntermediateSum) { point.y = correctFloat(y - previousIntermediate); // #3840 previousIntermediate = y; } } }, // Translate data points from raw values translate: function () { var series = this, options = series.options, yAxis = series.yAxis, len, i, points, point, shapeArgs, stack, y, yValue, previousY, previousIntermediate, range, minPointLength = pick(options.minPointLength, 5), halfMinPointLength = minPointLength / 2, threshold = options.threshold, stacking = options.stacking, stackIndicator, tooltipY; // run column series translate seriesTypes.column.prototype.translate.apply(series); previousY = previousIntermediate = threshold; points = series.points; for (i = 0, len = points.length; i < len; i++) { // cache current point object point = points[i]; 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 = pick( stack && stack[point.x].points[stackIndicator.key], [0, yValue] ); // 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) } 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 += stack && stack[point.x] ? stack[point.x].total : 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; // #3151 shapeArgs.height = Math.max(Math.round(shapeArgs.height), 0.001); point.yBottom = shapeArgs.y + shapeArgs.height; if (shapeArgs.height <= minPointLength && !point.isNull) { shapeArgs.height = minPointLength; shapeArgs.y -= halfMinPointLength; point.plotY = shapeArgs.y; if (point.y < 0) { point.minPointLengthOffset = -halfMinPointLength; } else { point.minPointLengthOffset = halfMinPointLength; } } else { if (point.isNull) { shapeArgs.width = 0; } point.minPointLengthOffset = 0; } // Correct tooltip placement (#3014) tooltipY = point.plotY + (point.negative ? shapeArgs.height : 0); if (series.chart.inverted) { point.tooltipPos[0] = yAxis.len - tooltipY; } else { point.tooltipPos[1] = tooltipY; } } }, // 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, // #3710 Update point does not propagate to sum points = series.options.data, point, dataLength = yData.length, threshold = options.threshold || 0, subSum, sum, dataMin, dataMax, y, i; sum = subSum = dataMin = dataMax = threshold; for (i = 0; i < dataLength; i++) { y = yData[i]; point = points && points[i] ? points[i] : {}; if (y === 'sum' || point.isSum) { yData[i] = correctFloat(sum); } else if (y === 'intermediateSum' || point.isIntermediateSum) { yData[i] = correctFloat(subSum); } else { sum += y; subSum += y; } dataMin = Math.min(sum, dataMin); dataMax = Math.max(sum, dataMax); } Series.prototype.processData.call(this, force); // Record extremes only if stacking was not set: if (!series.options.stacking) { series.dataMin = dataMin; series.dataMax = dataMax; } }, // Return y value or string if point is sum toYData: function (pt) { if (pt.isSum) { // #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 } return pt.y; }, // Postprocess mapping between options and SVG attributes pointAttribs: function (point, state) { var upColor = this.options.upColor, attr; // Set or reset up color (#3710, update to negative) if (upColor && !point.options.color) { point.color = point.y > 0 ? upColor : null; } attr = seriesTypes.column.prototype.pointAttribs.call( this, point, state ); // The dashStyle option in waterfall applies to the graph, not // the points delete attr.dashstyle; 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 ['M', 0, 0]; }, // Draw columns' connector lines getCrispPath: function () { var data = this.data, length = data.length, lineWidth = this.graph.strokeWidth() + this.borderWidth, normalizer = Math.round(lineWidth) % 2 / 2, reversedXAxis = this.xAxis.reversed, reversedYAxis = this.yAxis.reversed, path = [], prevArgs, pointArgs, i, d; for (i = 1; i < length; i++) { pointArgs = data[i].shapeArgs; prevArgs = data[i - 1].shapeArgs; d = [ 'M', prevArgs.x + (reversedXAxis ? 0 : prevArgs.width), prevArgs.y + data[i - 1].minPointLengthOffset + normalizer, 'L', pointArgs.x + (reversedXAxis ? prevArgs.width : 0), prevArgs.y + data[i - 1].minPointLengthOffset + normalizer ]; if ( (data[i - 1].y < 0 && !reversedYAxis) || (data[i - 1].y > 0 && reversedYAxis) ) { d[2] += prevArgs.height; d[5] += prevArgs.height; } path = path.concat(d); } return path; }, // 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 () { var series = this, options = series.options, stackedYLength, i; Series.prototype.setStackedPoints.apply(series, arguments); stackedYLength = series.stackedYData ? series.stackedYData.length : 0; // 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]; } } }, // 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); } } // Point members }, { getClassName: function () { var className = Point.prototype.getClassName.call(this); if (this.isSum) { className += ' highcharts-sum'; } else if (this.isIntermediateSum) { className += ' highcharts-intermediate-sum'; } return className; }, // Pass the null test in ColumnSeries.translate. isValid: function () { return isNumber(this.y, true) || this.isSum || this.isIntermediateSum; } }); /** * A `waterfall` series. If the [type](#series.waterfall.type) option * is not specified, it is inherited from [chart.type](#chart.type). * * @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 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 * @product highcharts * @apioption series.waterfall.data */ /** * 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. * * @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. * * @sample {highcharts} highcharts/demo/waterfall/ * Waterfall * * @type {boolean} * @default false * @product highcharts * @apioption series.waterfall.data.isSum */ }(Highcharts)); (function (H) { /** * (c) 2010-2018 Torstein Honsi * * License: www.highcharts.com/license */ var LegendSymbolMixin = H.LegendSymbolMixin, noop = H.noop, Series = H.Series, seriesType = H.seriesType, seriesTypes = H.seriesTypes; /** * A polygon series can be used to draw any freeform shape in the cartesian * 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. * * @sample {highcharts} highcharts/demo/polygon/ * Polygon * @sample {highstock} highcharts/demo/polygon/ * Polygon * * @extends plotOptions.scatter * @since 4.1.0 * @excluding softThreshold, threshold * @product highcharts highstock * @optionparent plotOptions.polygon */ seriesType('polygon', 'scatter', { marker: { enabled: false, states: { hover: { enabled: false } } }, stickyTracking: false, tooltip: { followPointer: true, pointFormat: '' }, trackByArea: true // Prototype members }, { type: 'polygon', getGraphPath: function () { var graphPath = Series.prototype.getGraphPath.call(this), i = graphPath.length + 1; // Close all segments while (i--) { if ((i === graphPath.length || graphPath[i] === 'M') && i > 0) { graphPath.splice(i, 0, 'z'); } } this.areaPath = graphPath; return graphPath; }, drawGraph: function () { // Hack into the fill logic in area.drawGraph this.options.fillColor = this.color; seriesTypes.area.prototype.drawGraph.call(this); }, drawLegendSymbol: LegendSymbolMixin.drawRectangle, drawTracker: Series.prototype.drawTracker, setStackedPoints: noop // No stacking points on polygons (#5310) }); /** * A `polygon` series. If the [type](#series.polygon.type) option is * not specified, it is inherited from [chart.type](#chart.type). * * @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 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-2018 Highsoft AS * * Author: Paweł Potaczek * * License: www.highcharts.com/license * * */ /** * @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, isNumber = H.isNumber, numberFormat = H.numberFormat, objectEach = H.objectEach, merge = H.merge, noop = H.noop, pick = H.pick, 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 } } }); /** * BubbleLegend class. * * @private * @class * @name Highcharts.BubbleLegend * * @param {Highcharts.LegendBubbleLegendOptions} config * Bubble legend options * * @param {Highcharts.LegendOptions} config * Legend options */ H.BubbleLegend = function (options, legend) { this.init(options, legend); }; 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, /** * 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); }, /** * 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 ? true : false; } // 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-2018 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. */ fillOpacity: 0.5, /** * In bubble charts, the radius is overridden and determined based on * the point's data value. * * @ignore */ radius: null, states: { hover: { radiusPlus: 0 } }, /** * 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. * * @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' }, /** * Minimum bubble size. Bubbles will automatically size between the * `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. * * @sample {highcharts} highcharts/plotoptions/bubble-size/ * Bubble size * * @type {number|string} * @since 3.0 * @product highcharts highstock */ minSize: 8, /** * Maximum bubble size. Bubbles will automatically size between the * `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. * * @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. * * @sample {highcharts} highcharts/plotoptions/bubble-negative/ * Negative bubbles * * @type {Highcharts.ColorString|Highcharts.GradientColorObject} * @since 3.0 * @product highcharts * @apioption plotOptions.bubble.negativeColor */ /** * 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. * * @sample {highcharts} highcharts/plotoptions/bubble-sizeby/ * Comparison of area and size * * @type {string} * @default area * @since 3.0.7 * @validvalue ["area", "width"] * @apioption plotOptions.bubble.sizeBy */ /** * 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`. * * @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 */ /** * 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 */ softThreshold: false, states: { hover: { halo: { size: 5 } } }, tooltip: { pointFormat: '({point.x}, {point.y}), Size: {point.z}' }, turboThreshold: 0, /** * The minimum for the Z value range. Defaults to the highest Z value * in the data. * * @see [zMin](#plotOptions.bubble.zMin) * * @sample {highcharts} highcharts/plotoptions/bubble-zmin-zmax/ * Z has a possible range of 0-100 * * @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. * * @see [zMax](#plotOptions.bubble.zMax) * * @sample {highcharts} highcharts/plotoptions/bubble-zmin-zmax/ * Z has a possible range of 0-100 * * @type {number} * @since 4.0.3 * @product highcharts * @apioption plotOptions.bubble.zMin */ /** * 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'], trackerGroups: ['group', 'dataLabelsGroup'], specialGroup: 'group', // To allow clipping (#6296) bubblePadding: true, zoneAxis: 'z', directTouch: true, isBubble: true, pointAttribs: function (point, state) { var markerOptions = this.options.marker, fillOpacity = markerOptions.fillOpacity, attr = Series.prototype.pointAttribs.call(this, point, state); if (fillOpacity !== 1) { attr.fill = color(attr.fill).setOpacity(fillOpacity).get('rgba'); } 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, series) { var len, i, zData = this.zData, minSize = series.minPxSize, maxSize = series.maxPxSize, radii = [], 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; }, // 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; // 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 (!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); } radius = Math.ceil(minSize + pos * (maxSize - minSize)) / 2; } return radius; }, // 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; if (graphic && graphic.width) { // URL symbols don't have width animationTarget = { x: graphic.x, y: graphic.y, width: graphic.width, height: graphic.height }; // Start values graphic.attr({ x: point.plotX, y: point.plotY, width: 1, height: 1 }); // Run 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 () { var i, data = this.data, point, radius, radii = this.radii; // Run the parent method seriesTypes.scatter.prototype.translate.call(this); // Set the shape type and arguments to be picked up in drawPoints i = data.length; while (i--) { point = data[i]; radius = radii ? radii[i] : 0; // #1737 if (isNumber(radius) && radius >= this.minPxSize / 2) { // Shape arguments point.marker = H.extend(point.marker, { radius: radius, width: 2 * radius, height: 2 * radius }); // Alignment box for the data label point.dlBox = { x: point.plotX - radius, y: point.plotY - radius, width: 2 * radius, height: 2 * radius }; } else { // below zThreshold // #1691 point.shapeArgs = point.plotY = point.dlBox = undefined; } } }, alignDataLabel: seriesTypes.column.prototype.alignDataLabel, buildKDTree: noop, applyZones: noop // Point class }, { haloPath: function (size) { return Point.prototype.haloPath.call( this, // #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 () { var axis = this, axisLength = this.len, chart = this.chart, pxMin = 0, pxMax = axisLength, isXAxis = this.isXAxis, dataKey = isXAxis ? 'xData' : 'yData', min = this.min, extremes = {}, smallestSize = Math.min(chart.plotWidth, chart.plotHeight), zMin = Number.MAX_VALUE, zMax = -Number.MAX_VALUE, range = this.max - min, transA = axisLength / range, activeSeries = []; // Handle padding on the second pass, or on redraw this.series.forEach(function (series) { var seriesOptions = series.options, zData; if ( series.bubblePadding && (series.visible || !chart.options.chart.ignoreHiddenSeries) ) { // Correction for #1673 axis.allowZoomOutside = true; // Cache it activeSeries.push(series); if (isXAxis) { // because X axis is evaluated first // For each series, translate the size extremes to pixel values ['minSize', 'maxSize'].forEach(function (prop) { var length = seriesOptions[prop], isPercent = /%$/.test(length); length = pInt(length); extremes[prop] = isPercent ? smallestSize * length / 100 : length; }); series.minPxSize = extremes.minSize; // Prioritize min size if conflict to make sure bubbles are // always visible. #5873 series.maxPxSize = Math.max(extremes.maxSize, extremes.minSize); // Find the min and max Z 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 ) )); zMax = pick( seriesOptions.zMax, Math.max(zMax, arrayMax(zData)) ); } } } }); activeSeries.forEach(function (series) { var data = series[dataKey], i = data.length, radius; if (isXAxis) { series.getRadii(zMin, zMax, series); } if (range > 0) { while (i--) { 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 ); } } } }); // Apply the padding to the min and max properties if (activeSeries.length && range > 0 && !this.isLog) { pxMax -= axisLength; 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; } } ); } }; /** * A `bubble` series. If the [type](#series.bubble.type) option is * not specified, it is inherited from [chart.type](#chart.type). * * @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 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/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 * @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 */ /** * @excluding enabled, enabledThreshold, height, radius, width * @apioption series.bubble.marker */ }(Highcharts)); (function (H) { /* * * * (c) 2010-2018 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, keys, maxSize, minSize, sizeBy, * sizeByAbsoluteValue, step, zMax, zMin * @optionparent plotOptions.packedbubble */ { /** * Minimum bubble size. Bubbles will automatically size between the * `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. * * @sample {highcharts} highcharts/plotoptions/bubble-size/ * Bubble size * * @type {number|string} * @since 3.0 * @product highcharts highstock */ minSize: '10%', /** * Maximum bubble size. Bubbles will automatically size between the * `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. * * @sample {highcharts} highcharts/plotoptions/bubble-size/ * Bubble size * * @type {number|string} * @since 3.0 * @product highcharts highstock */ 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 []; } else 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 highstock * @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 `y` 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: [{ * y: 1, * name: "Point2", * color: "#00FF00" * }, { * y: 5, * name: "Point1", * color: "#FF00FF" * }] * ``` * * @sample {highcharts} highcharts/series/data-array-of-objects/ * Config objects * * @type {Array} * @extends series.line.data * @excluding marker * @product highcharts * @apioption series.packedbubble.data */ /** * @excluding enabled, enabledThreshold, height, radius, width * @apioption series.packedbubble.marker */ }(Highcharts)); (function (H) { /** * (c) 2010-2018 Torstein Honsi * * License: www.highcharts.com/license */ // 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, wrap = H.wrap, seriesProto = Series.prototype, pointerProto = Pointer.prototype, colProto; if (!H.polarExtended) { H.polarExtended = true; /** * 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)) }); }; /** * #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, // 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 { i = 0; } 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; }; /** * 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; } else { this.options.findNearestPointBy = 'xy'; } } 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; } point.clientX = clientX; } else { point.clientX = point.plotX; } }; 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 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; if (chart.polar) { // Postprocess plot coordinates this.kdByAngle = chart.tooltip && chart.tooltip.shared; if (!this.preventPostTranslate) { points = this.points; i = points.length; while (i--) { // Translate plotX, plotY from angle and radius to true plot // coordinates this.toXY(points[i]); } } // 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 /** * 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; // Connect the path if (this.chart.polar) { points = points || this.points; // Append first valid point in order to connect the ends for (i = 0; i < points.length; i++) { if (!points[i].isNull) { firstValid = i; break; } } /** * 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; } // 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); } }); } // 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; }); 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); } // 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); if (seriesTypes.column) { colProto = seriesTypes.column.prototype; colProto.polarArc = function (low, high, start, end) { var center = this.xAxis.center, len = this.yAxis.len; 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; 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]; } } }); /** * 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; } seriesProto.alignDataLabel.call( this, point, dataLabel, options, alignTo, isNew ); } else { proceed.call(this, point, dataLabel, options, alignTo, isNew); } }); } /** * Extend getCoordinates to prepare for polar axis values */ wrap(pointerProto, 'getCoordinates', function (proceed, e) { var chart = this.chart, ret = { xAxis: [], yAxis: [] }; 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 ) }); }); } else { ret = proceed.call(this, e); } return ret; }); H.SVGRenderer.prototype.clipCircle = function (x, y, r) { var wrapper, id = H.uniqueKey(), clipPath = this.createElement('clipPath').attr({ id: id }).add(this.defs); wrapper = this.circle(x, y, r).add(clipPath); wrapper.id = id; wrapper.clipPath = clipPath; return wrapper; }; H.addEvent(H.Chart, 'getAxes', function () { if (!this.pane) { this.pane = []; } H.splat(this.options.pane).forEach(function (paneOptions) { new H.Pane( // eslint-disable-line no-new paneOptions, this ); }, this); }); 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); }); } }(Highcharts)); return (function () { }()); }));