| | |
| | | // ==ClosureCompiler== |
| | | // @compilation_level SIMPLE_OPTIMIZATIONS |
| | | |
| | | /** |
| | | * @license Highcharts JS v3.0.6 (2013-10-04) |
| | | * |
| | | * (c) 2009-2013 Torstein Hønsi |
| | | * |
| | | * License: www.highcharts.com/license |
| | | */ |
| | | |
| | | // JSLint options: |
| | | /*global Highcharts, HighchartsAdapter, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console */ |
| | | |
| | | (function (Highcharts, UNDEFINED) { |
| | | var arrayMin = Highcharts.arrayMin, |
| | | arrayMax = Highcharts.arrayMax, |
| | | each = Highcharts.each, |
| | | extend = Highcharts.extend, |
| | | merge = Highcharts.merge, |
| | | map = Highcharts.map, |
| | | pick = Highcharts.pick, |
| | | pInt = Highcharts.pInt, |
| | | defaultPlotOptions = Highcharts.getOptions().plotOptions, |
| | | seriesTypes = Highcharts.seriesTypes, |
| | | extendClass = Highcharts.extendClass, |
| | | splat = Highcharts.splat, |
| | | wrap = Highcharts.wrap, |
| | | Axis = Highcharts.Axis, |
| | | Tick = Highcharts.Tick, |
| | | Series = Highcharts.Series, |
| | | colProto = seriesTypes.column.prototype, |
| | | math = Math, |
| | | mathRound = math.round, |
| | | mathFloor = math.floor, |
| | | mathMax = math.max, |
| | | noop = function () {};/** |
| | | * 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. |
| | | */ |
| | | function Pane(options, chart, firstAxis) { |
| | | this.init.call(this, options, chart, firstAxis); |
| | | } |
| | | |
| | | // Extend the Pane prototype |
| | | extend(Pane.prototype, { |
| | | |
| | | /** |
| | | * Initiate the Pane object |
| | | */ |
| | | init: function (options, chart, firstAxis) { |
| | | var pane = this, |
| | | backgroundOption, |
| | | defaultOptions = pane.defaultOptions; |
| | | |
| | | pane.chart = chart; |
| | | |
| | | // Set options |
| | | if (chart.angular) { // gauges |
| | | defaultOptions.background = {}; // gets extended by this.defaultBackgroundOptions |
| | | } |
| | | pane.options = options = merge(defaultOptions, options); |
| | | |
| | | backgroundOption = options.background; |
| | | |
| | | // To avoid having weighty logic to place, update and remove the backgrounds, |
| | | // push them to the first axis' plot bands and borrow the existing logic there. |
| | | if (backgroundOption) { |
| | | each([].concat(splat(backgroundOption)).reverse(), function (config) { |
| | | var backgroundColor = config.backgroundColor; // if defined, replace the old one (specific for gradients) |
| | | config = merge(pane.defaultBackgroundOptions, config); |
| | | if (backgroundColor) { |
| | | config.backgroundColor = backgroundColor; |
| | | } |
| | | config.color = config.backgroundColor; // due to naming in plotBands |
| | | firstAxis.options.plotBands.unshift(config); |
| | | }); |
| | | } |
| | | }, |
| | | |
| | | /** |
| | | * The default options object |
| | | */ |
| | | defaultOptions: { |
| | | // background: {conditional}, |
| | | center: ['50%', '50%'], |
| | | size: '85%', |
| | | startAngle: 0 |
| | | //endAngle: startAngle + 360 |
| | | }, |
| | | |
| | | /** |
| | | * The default background options |
| | | */ |
| | | defaultBackgroundOptions: { |
| | | shape: 'circle', |
| | | borderWidth: 1, |
| | | borderColor: 'silver', |
| | | backgroundColor: { |
| | | linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, |
| | | stops: [ |
| | | [0, '#FFF'], |
| | | [1, '#DDD'] |
| | | ] |
| | | }, |
| | | from: Number.MIN_VALUE, // corrected to axis min |
| | | innerRadius: 0, |
| | | to: Number.MAX_VALUE, // corrected to axis max |
| | | outerRadius: '105%' |
| | | } |
| | | |
| | | }); |
| | | var axisProto = Axis.prototype, |
| | | tickProto = Tick.prototype; |
| | | |
| | | /** |
| | | * Augmented methods for the x axis in order to hide it completely, used for the X axis in gauges |
| | | */ |
| | | var 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 |
| | | */ |
| | | /*jslint unparam: true*/ |
| | | var radialAxisMixin = { |
| | | isRadial: true, |
| | | |
| | | /** |
| | | * The default options extend defaultYAxisOptions |
| | | */ |
| | | defaultRadialGaugeOptions: { |
| | | labels: { |
| | | align: 'center', |
| | | x: 0, |
| | | y: null // auto |
| | | }, |
| | | minorGridLineWidth: 0, |
| | | minorTickInterval: 'auto', |
| | | minorTickLength: 10, |
| | | minorTickPosition: 'inside', |
| | | minorTickWidth: 1, |
| | | plotBands: [], |
| | | 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 |
| | | }, |
| | | maxPadding: 0, |
| | | minPadding: 0, |
| | | plotBands: [], |
| | | showLastLabel: false, |
| | | tickLength: 0 |
| | | }, |
| | | |
| | | // Radial axis, like a spoke in a polar chart |
| | | defaultRadialYOptions: { |
| | | gridLineInterpolation: 'circle', |
| | | labels: { |
| | | align: 'right', |
| | | x: -3, |
| | | y: -2 |
| | | }, |
| | | plotBands: [], |
| | | showLastLabel: false, |
| | | title: { |
| | | x: 4, |
| | | text: null, |
| | | rotation: 90 |
| | | } |
| | | }, |
| | | |
| | | /** |
| | | * Merge and set options |
| | | */ |
| | | setOptions: function (userOptions) { |
| | | |
| | | this.options = merge( |
| | | this.defaultOptions, |
| | | this.defaultRadialOptions, |
| | | userOptions |
| | | ); |
| | | |
| | | }, |
| | | |
| | | /** |
| | | * 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; |
| | | radius = pick(radius, center[2] / 2 - this.offset); |
| | | |
| | | return this.chart.renderer.symbols.arc( |
| | | this.left + center[0], |
| | | this.top + center[1], |
| | | radius, |
| | | radius, |
| | | { |
| | | start: this.startAngleRad, |
| | | end: this.endAngleRad, |
| | | open: true, |
| | | innerR: 0 |
| | | } |
| | | ); |
| | | }, |
| | | |
| | | /** |
| | | * Override setAxisTranslation by setting the translation to the difference |
| | | * in rotation. This allows the translate method to return angle for |
| | | * any given value. |
| | | */ |
| | | setAxisTranslation: function () { |
| | | |
| | | // 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 + |
| | | (this.reversed ? (this.endAngleRad - this.startAngleRad) / 4 : 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 (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.center = this.pane.center = seriesTypes.pie.prototype.getCenter.call(this.pane); |
| | | |
| | | this.len = this.width = this.height = this.isCircular ? |
| | | this.center[2] * (this.endAngleRad - this.startAngleRad) / 2 : |
| | | this.center[2] / 2; |
| | | } |
| | | }, |
| | | |
| | | /** |
| | | * Returns the x, y coordinate of a point given by a value and a pixel distance |
| | | * from center |
| | | */ |
| | | getPosition: function (value, length) { |
| | | if (!this.isCircular) { |
| | | length = this.translate(value); |
| | | value = this.min; |
| | | } |
| | | |
| | | return this.postTranslate( |
| | | this.translate(value), |
| | | pick(length, 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) |
| | | ], |
| | | 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 { |
| | | |
| | | // 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 = map(radii, 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); |
| | | } |
| | | |
| | | |
| | | ret = this.chart.renderer.symbols.arc( |
| | | this.left + center[0], |
| | | this.top + center[1], |
| | | radii[0], |
| | | radii[0], |
| | | { |
| | | start: start, |
| | | end: 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); |
| | | if (value) { // a value of 0 is in the center |
| | | ret = axis.getLinePath(0, value); |
| | | } |
| | | // Concentric polygons |
| | | } else { |
| | | xAxis = chart.xAxis[0]; |
| | | ret = []; |
| | | value = axis.translate(value); |
| | | tickPositions = xAxis.tickPositions; |
| | | if (xAxis.autoConnect) { |
| | | tickPositions = tickPositions.concat([tickPositions[0]]); |
| | | } |
| | | // Reverse the positions for concatenation of polygonal plot bands |
| | | if (reverse) { |
| | | tickPositions = [].concat(tickPositions).reverse(); |
| | | } |
| | | |
| | | each(tickPositions, function (pos, i) { |
| | | xy = xAxis.getPosition(pos, value); |
| | | ret.push(i ? 'L' : 'M', xy.x, xy.y); |
| | | }); |
| | | |
| | | } |
| | | 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) |
| | | }; |
| | | } |
| | | |
| | | }; |
| | | /*jslint unparam: false*/ |
| | | |
| | | /** |
| | | * Override axisProto.init to mix in special axis instance functions and function overrides |
| | | */ |
| | | wrap(axisProto, 'init', function (proceed, chart, userOptions) { |
| | | var axis = this, |
| | | angular = chart.angular, |
| | | polar = chart.polar, |
| | | isX = userOptions.isX, |
| | | isHidden = angular && isX, |
| | | isCircular, |
| | | startAngleRad, |
| | | endAngleRad, |
| | | options, |
| | | chartOptions = chart.options, |
| | | paneIndex = userOptions.pane || 0, |
| | | pane, |
| | | paneOptions; |
| | | |
| | | // Before prototype.init |
| | | if (angular) { |
| | | extend(this, isHidden ? hiddenAxisMixin : radialAxisMixin); |
| | | isCircular = !isX; |
| | | if (isCircular) { |
| | | this.defaultRadialOptions = this.defaultRadialGaugeOptions; |
| | | } |
| | | |
| | | } else if (polar) { |
| | | //extend(this, userOptions.isX ? radialAxisMixin : radialAxisMixin); |
| | | extend(this, radialAxisMixin); |
| | | isCircular = isX; |
| | | this.defaultRadialOptions = isX ? this.defaultRadialXOptions : merge(this.defaultYAxisOptions, this.defaultRadialYOptions); |
| | | |
| | | } |
| | | |
| | | // Run prototype.init |
| | | proceed.call(this, chart, userOptions); |
| | | |
| | | if (!isHidden && (angular || polar)) { |
| | | options = this.options; |
| | | |
| | | // Create the pane and set the pane options. |
| | | if (!chart.panes) { |
| | | chart.panes = []; |
| | | } |
| | | this.pane = pane = chart.panes[paneIndex] = chart.panes[paneIndex] || new Pane( |
| | | splat(chartOptions.pane)[paneIndex], |
| | | chart, |
| | | axis |
| | | ); |
| | | paneOptions = pane.options; |
| | | |
| | | |
| | | // Disable certain features on angular and polar axes |
| | | chart.inverted = false; |
| | | chartOptions.chart.zoomType = null; |
| | | |
| | | // Start and end angle options are |
| | | // given in degrees relative to top, while internal computations are |
| | | // in radians relative to right (like SVG). |
| | | this.startAngleRad = startAngleRad = (paneOptions.startAngle - 90) * Math.PI / 180; |
| | | this.endAngleRad = endAngleRad = (pick(paneOptions.endAngle, paneOptions.startAngle + 360) - 90) * Math.PI / 180; |
| | | this.offset = options.offset || 0; |
| | | |
| | | this.isCircular = isCircular; |
| | | |
| | | // Automatically connect grid lines? |
| | | if (isCircular && userOptions.max === UNDEFINED && endAngleRad - startAngleRad === 2 * Math.PI) { |
| | | this.autoConnect = true; |
| | | } |
| | | } |
| | | |
| | | }); |
| | | |
| | | /** |
| | | * Add special cases within the Tick class' methods for radial axes. |
| | | */ |
| | | wrap(tickProto, 'getPosition', function (proceed, horiz, pos, tickmarkOffset, old) { |
| | | var axis = this.axis; |
| | | |
| | | return axis.getPosition ? |
| | | axis.getPosition(pos) : |
| | | proceed.call(this, horiz, pos, tickmarkOffset, old); |
| | | }); |
| | | |
| | | /** |
| | | * Wrap the getLabelPosition function to find the center position of the label |
| | | * based on the distance option |
| | | */ |
| | | wrap(tickProto, 'getLabelPosition', function (proceed, x, y, label, horiz, labelOptions, tickmarkOffset, index, step) { |
| | | var axis = this.axis, |
| | | optionsY = labelOptions.y, |
| | | ret, |
| | | align = labelOptions.align, |
| | | angle = ((axis.translate(this.pos) + axis.startAngleRad + Math.PI / 2) / Math.PI * 180) % 360; |
| | | |
| | | if (axis.isRadial) { |
| | | 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 = pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2; |
| | | |
| | | } |
| | | |
| | | // Automatic alignment |
| | | if (align === null) { |
| | | if (axis.isCircular) { |
| | | 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 |
| | | } |
| | | } else { |
| | | align = 'center'; |
| | | } |
| | | label.attr({ |
| | | align: align |
| | | }); |
| | | } |
| | | |
| | | ret.x += labelOptions.x; |
| | | ret.y += optionsY; |
| | | |
| | | } else { |
| | | ret = proceed.call(this, x, y, label, horiz, labelOptions, tickmarkOffset, index, step); |
| | | } |
| | | return ret; |
| | | }); |
| | | |
| | | /** |
| | | * Wrap the getMarkPath function to return the path of the radial marker |
| | | */ |
| | | wrap(tickProto, 'getMarkPath', function (proceed, x, y, tickLength, tickWidth, horiz, renderer) { |
| | | var axis = this.axis, |
| | | endPoint, |
| | | ret; |
| | | |
| | | if (axis.isRadial) { |
| | | endPoint = axis.getPosition(this.pos, axis.center[2] / 2 + tickLength); |
| | | ret = [ |
| | | 'M', |
| | | x, |
| | | y, |
| | | 'L', |
| | | endPoint.x, |
| | | endPoint.y |
| | | ]; |
| | | } else { |
| | | ret = proceed.call(this, x, y, tickLength, tickWidth, horiz, renderer); |
| | | } |
| | | return ret; |
| | | });/* |
| | | * The AreaRangeSeries class |
| | | * |
| | | */ |
| | | |
| | | /** |
| | | * Extend the default options with map options |
| | | */ |
| | | defaultPlotOptions.arearange = merge(defaultPlotOptions.area, { |
| | | lineWidth: 1, |
| | | marker: null, |
| | | threshold: null, |
| | | tooltip: { |
| | | pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.low}</b> - <b>{point.high}</b><br/>' |
| | | }, |
| | | trackByArea: true, |
| | | dataLabels: { |
| | | verticalAlign: null, |
| | | xLow: 0, |
| | | xHigh: 0, |
| | | yLow: 0, |
| | | yHigh: 0 |
| | | } |
| | | }); |
| | | |
| | | /** |
| | | * Add the series type |
| | | */ |
| | | seriesTypes.arearange = Highcharts.extendClass(seriesTypes.area, { |
| | | type: 'arearange', |
| | | pointArrayMap: ['low', 'high'], |
| | | toYData: function (point) { |
| | | return [point.low, point.high]; |
| | | }, |
| | | pointValKey: 'low', |
| | | |
| | | /** |
| | | * Extend getSegments to force null points if the higher value is null. #1703. |
| | | */ |
| | | getSegments: function () { |
| | | var series = this; |
| | | |
| | | each(series.points, function (point) { |
| | | if (!series.options.connectNulls && (point.low === null || point.high === null)) { |
| | | point.y = null; |
| | | } else if (point.low === null && point.high !== null) { |
| | | point.y = point.high; |
| | | } |
| | | }); |
| | | Series.prototype.getSegments.call(this); |
| | | }, |
| | | |
| | | /** |
| | | * Translate data points from raw values x and y to plotX and plotY |
| | | */ |
| | | translate: function () { |
| | | var series = this, |
| | | yAxis = series.yAxis; |
| | | |
| | | seriesTypes.area.prototype.translate.apply(series); |
| | | |
| | | // Set plotLow and plotHigh |
| | | each(series.points, function (point) { |
| | | |
| | | var low = point.low, |
| | | high = point.high, |
| | | plotY = point.plotY; |
| | | |
| | | if (high === null && low === null) { |
| | | point.y = null; |
| | | } else if (low === null) { |
| | | point.plotLow = point.plotY = null; |
| | | point.plotHigh = yAxis.translate(high, 0, 1, 0, 1); |
| | | } else if (high === null) { |
| | | point.plotLow = plotY; |
| | | point.plotHigh = null; |
| | | } else { |
| | | point.plotLow = plotY; |
| | | point.plotHigh = yAxis.translate(high, 0, 1, 0, 1); |
| | | } |
| | | }); |
| | | }, |
| | | |
| | | /** |
| | | * Extend the line series' getSegmentPath method by applying the segment |
| | | * path to both lower and higher values of the range |
| | | */ |
| | | getSegmentPath: function (segment) { |
| | | |
| | | var lowSegment, |
| | | highSegment = [], |
| | | i = segment.length, |
| | | baseGetSegmentPath = Series.prototype.getSegmentPath, |
| | | point, |
| | | linePath, |
| | | lowerPath, |
| | | options = this.options, |
| | | step = options.step, |
| | | higherPath; |
| | | |
| | | // Remove nulls from low segment |
| | | lowSegment = HighchartsAdapter.grep(segment, function (point) { |
| | | return point.plotLow !== null; |
| | | }); |
| | | |
| | | // Make a segment with plotX and plotY for the top values |
| | | while (i--) { |
| | | point = segment[i]; |
| | | if (point.plotHigh !== null) { |
| | | highSegment.push({ |
| | | plotX: point.plotX, |
| | | plotY: point.plotHigh |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // Get the paths |
| | | lowerPath = baseGetSegmentPath.call(this, lowSegment); |
| | | if (step) { |
| | | if (step === true) { |
| | | step = 'left'; |
| | | } |
| | | options.step = { left: 'right', center: 'center', right: 'left' }[step]; // swap for reading in getSegmentPath |
| | | } |
| | | higherPath = baseGetSegmentPath.call(this, highSegment); |
| | | 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' |
| | | higherPath[0] = 'L'; // this probably doesn't work for spline |
| | | this.areaPath = this.areaPath.concat(lowerPath, higherPath); |
| | | |
| | | return linePath; |
| | | }, |
| | | |
| | | /** |
| | | * Extend the basic drawDataLabels method by running it for both lower and higher |
| | | * values. |
| | | */ |
| | | drawDataLabels: function () { |
| | | |
| | | var data = this.data, |
| | | length = data.length, |
| | | i, |
| | | originalDataLabels = [], |
| | | seriesProto = Series.prototype, |
| | | dataLabelOptions = this.options.dataLabels, |
| | | point, |
| | | inverted = this.chart.inverted; |
| | | |
| | | if (dataLabelOptions.enabled || this._hasPointLabels) { |
| | | |
| | | // Step 1: set preliminary values for plotY and dataLabel and draw the upper labels |
| | | i = length; |
| | | while (i--) { |
| | | point = data[i]; |
| | | |
| | | // Set preliminary values |
| | | point.y = point.high; |
| | | 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 = false; |
| | | if (inverted) { |
| | | dataLabelOptions.align = 'left'; |
| | | dataLabelOptions.x = dataLabelOptions.xHigh; |
| | | } else { |
| | | dataLabelOptions.y = dataLabelOptions.yHigh; |
| | | } |
| | | } |
| | | seriesProto.drawDataLabels.apply(this, arguments); // #1209 |
| | | |
| | | // Step 2: reorganize and handle data labels for the lower values |
| | | i = length; |
| | | while (i--) { |
| | | point = data[i]; |
| | | |
| | | // Move the generated labels from step 1, and reassign the original data labels |
| | | point.dataLabelUpper = point.dataLabel; |
| | | point.dataLabel = originalDataLabels[i]; |
| | | |
| | | // Reset values |
| | | point.y = point.low; |
| | | point.plotY = point.plotLow; |
| | | |
| | | // Set the default offset |
| | | point.below = true; |
| | | if (inverted) { |
| | | dataLabelOptions.align = 'right'; |
| | | dataLabelOptions.x = dataLabelOptions.xLow; |
| | | } else { |
| | | dataLabelOptions.y = dataLabelOptions.yLow; |
| | | } |
| | | } |
| | | seriesProto.drawDataLabels.apply(this, arguments); |
| | | } |
| | | |
| | | }, |
| | | |
| | | alignDataLabel: seriesTypes.column.prototype.alignDataLabel, |
| | | |
| | | getSymbol: seriesTypes.column.prototype.getSymbol, |
| | | |
| | | drawPoints: noop |
| | | });/** |
| | | * The AreaSplineRangeSeries class |
| | | */ |
| | | |
| | | defaultPlotOptions.areasplinerange = merge(defaultPlotOptions.arearange); |
| | | |
| | | /** |
| | | * AreaSplineRangeSeries object |
| | | */ |
| | | seriesTypes.areasplinerange = extendClass(seriesTypes.arearange, { |
| | | type: 'areasplinerange', |
| | | getPointSpline: seriesTypes.spline.prototype.getPointSpline |
| | | });/** |
| | | * The ColumnRangeSeries class |
| | | */ |
| | | defaultPlotOptions.columnrange = merge(defaultPlotOptions.column, defaultPlotOptions.arearange, { |
| | | lineWidth: 1, |
| | | pointRange: null |
| | | }); |
| | | |
| | | /** |
| | | * ColumnRangeSeries object |
| | | */ |
| | | seriesTypes.columnrange = extendClass(seriesTypes.arearange, { |
| | | type: 'columnrange', |
| | | /** |
| | | * Translate data points from raw values x and y to plotX and plotY |
| | | */ |
| | | translate: function () { |
| | | var series = this, |
| | | yAxis = series.yAxis, |
| | | plotHigh; |
| | | |
| | | colProto.translate.apply(series); |
| | | |
| | | // Set plotLow and plotHigh |
| | | each(series.points, function (point) { |
| | | var shapeArgs = point.shapeArgs, |
| | | minPointLength = series.options.minPointLength, |
| | | heightDifference, |
| | | height, |
| | | y; |
| | | |
| | | point.plotHigh = plotHigh = yAxis.translate(point.high, 0, 1, 0, 1); |
| | | point.plotLow = point.plotY; |
| | | |
| | | // adjust shape |
| | | y = plotHigh; |
| | | height = point.plotY - plotHigh; |
| | | |
| | | if (height < minPointLength) { |
| | | heightDifference = (minPointLength - height); |
| | | height += heightDifference; |
| | | y -= heightDifference / 2; |
| | | } |
| | | shapeArgs.height = height; |
| | | shapeArgs.y = y; |
| | | }); |
| | | }, |
| | | trackerGroups: ['group', 'dataLabels'], |
| | | drawGraph: noop, |
| | | pointAttrToOptions: colProto.pointAttrToOptions, |
| | | drawPoints: colProto.drawPoints, |
| | | drawTracker: colProto.drawTracker, |
| | | animate: colProto.animate, |
| | | getColumnMetrics: colProto.getColumnMetrics |
| | | }); |
| | | /* |
| | | * The GaugeSeries class |
| | | */ |
| | | |
| | | |
| | | |
| | | /** |
| | | * Extend the default options |
| | | */ |
| | | defaultPlotOptions.gauge = merge(defaultPlotOptions.line, { |
| | | dataLabels: { |
| | | enabled: true, |
| | | y: 15, |
| | | borderWidth: 1, |
| | | borderColor: 'silver', |
| | | borderRadius: 3, |
| | | style: { |
| | | fontWeight: 'bold' |
| | | }, |
| | | verticalAlign: 'top', |
| | | zIndex: 2 |
| | | }, |
| | | dial: { |
| | | // radius: '80%', |
| | | // backgroundColor: 'black', |
| | | // borderColor: 'silver', |
| | | // borderWidth: 0, |
| | | // baseWidth: 3, |
| | | // topWidth: 1, |
| | | // baseLength: '70%' // of radius |
| | | // rearLength: '10%' |
| | | }, |
| | | pivot: { |
| | | //radius: 5, |
| | | //borderWidth: 0 |
| | | //borderColor: 'silver', |
| | | //backgroundColor: 'black' |
| | | }, |
| | | tooltip: { |
| | | headerFormat: '' |
| | | }, |
| | | showInLegend: false |
| | | }); |
| | | |
| | | /** |
| | | * Extend the point object |
| | | */ |
| | | var GaugePoint = Highcharts.extendClass(Highcharts.Point, { |
| | | /** |
| | | * Don't do any hover colors or anything |
| | | */ |
| | | setState: function (state) { |
| | | this.state = state; |
| | | } |
| | | }); |
| | | |
| | | |
| | | /** |
| | | * Add the series type |
| | | */ |
| | | var GaugeSeries = { |
| | | type: 'gauge', |
| | | pointClass: GaugePoint, |
| | | |
| | | // chart.angular will be set to true when a gauge series is present, and this will |
| | | // be used on the axes |
| | | angular: true, |
| | | drawGraph: noop, |
| | | fixedBox: true, |
| | | trackerGroups: ['group', 'dataLabels'], |
| | | |
| | | /** |
| | | * Calculate paths etc |
| | | */ |
| | | translate: function () { |
| | | |
| | | var series = this, |
| | | yAxis = series.yAxis, |
| | | options = series.options, |
| | | center = yAxis.center; |
| | | |
| | | series.generatePoints(); |
| | | |
| | | each(series.points, 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, |
| | | rotation = yAxis.startAngleRad + yAxis.translate(point.y, null, null, null, true); |
| | | |
| | | // Handle the wrap option |
| | | 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, |
| | | center = series.yAxis.center, |
| | | pivot = series.pivot, |
| | | options = series.options, |
| | | pivotOptions = options.pivot, |
| | | renderer = series.chart.renderer; |
| | | |
| | | each(series.points, 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({ |
| | | stroke: dialOptions.borderColor || 'none', |
| | | 'stroke-width': dialOptions.borderWidth || 0, |
| | | fill: dialOptions.backgroundColor || 'black', |
| | | rotation: shapeArgs.rotation // required by VML when animation is false |
| | | }) |
| | | .add(series.group); |
| | | } |
| | | }); |
| | | |
| | | // 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({ |
| | | 'stroke-width': pivotOptions.borderWidth || 0, |
| | | stroke: pivotOptions.borderColor || 'silver', |
| | | fill: pivotOptions.backgroundColor || 'black' |
| | | }) |
| | | .translate(center[0], center[1]) |
| | | .add(series.group); |
| | | } |
| | | }, |
| | | |
| | | /** |
| | | * Animate the arrow up from startAngle |
| | | */ |
| | | animate: function (init) { |
| | | var series = this; |
| | | |
| | | if (!init) { |
| | | each(series.points, 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 |
| | | ); |
| | | seriesTypes.pie.prototype.render.call(this); |
| | | this.group.clip(this.chart.clipRect); |
| | | }, |
| | | |
| | | setData: seriesTypes.pie.prototype.setData, |
| | | drawTracker: seriesTypes.column.prototype.drawTracker |
| | | }; |
| | | seriesTypes.gauge = Highcharts.extendClass(seriesTypes.line, GaugeSeries);/* **************************************************************************** |
| | | * Start Box plot series code * |
| | | *****************************************************************************/ |
| | | |
| | | // Set default options |
| | | defaultPlotOptions.boxplot = merge(defaultPlotOptions.column, { |
| | | fillColor: '#FFFFFF', |
| | | lineWidth: 1, |
| | | //medianColor: null, |
| | | medianWidth: 2, |
| | | states: { |
| | | hover: { |
| | | brightness: -0.3 |
| | | } |
| | | }, |
| | | //stemColor: null, |
| | | //stemDashStyle: 'solid' |
| | | //stemWidth: null, |
| | | threshold: null, |
| | | tooltip: { |
| | | pointFormat: '<span style="color:{series.color};font-weight:bold">{series.name}</span><br/>' + |
| | | 'Maximum: {point.high}<br/>' + |
| | | 'Upper quartile: {point.q3}<br/>' + |
| | | 'Median: {point.median}<br/>' + |
| | | 'Lower quartile: {point.q1}<br/>' + |
| | | 'Minimum: {point.low}<br/>' |
| | | |
| | | }, |
| | | //whiskerColor: null, |
| | | whiskerLength: '50%', |
| | | whiskerWidth: 2 |
| | | }); |
| | | |
| | | // Create the series object |
| | | seriesTypes.boxplot = extendClass(seriesTypes.column, { |
| | | type: 'boxplot', |
| | | pointArrayMap: ['low', 'q1', 'median', 'q3', 'high'], // array point configs are mapped to this |
| | | toYData: function (point) { // return a plain array for speedy calculation |
| | | return [point.low, point.q1, point.median, point.q3, point.high]; |
| | | }, |
| | | pointValKey: 'high', // defines the top of the tracker |
| | | |
| | | /** |
| | | * One-to-one mapping from options to SVG attributes |
| | | */ |
| | | pointAttrToOptions: { // mapping between SVG attributes and the corresponding options |
| | | fill: 'fillColor', |
| | | stroke: 'color', |
| | | 'stroke-width': 'lineWidth' |
| | | }, |
| | | |
| | | /** |
| | | * 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 |
| | | each(series.points, function (point) { |
| | | each(pointArrayMap, 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, //state = series.state, |
| | | points = series.points, |
| | | options = series.options, |
| | | chart = series.chart, |
| | | renderer = chart.renderer, |
| | | pointAttr, |
| | | q1Plot, |
| | | q3Plot, |
| | | highPlot, |
| | | lowPlot, |
| | | medianPlot, |
| | | crispCorr, |
| | | crispX, |
| | | graphic, |
| | | stemPath, |
| | | stemAttr, |
| | | boxPath, |
| | | whiskersPath, |
| | | whiskersAttr, |
| | | medianPath, |
| | | medianAttr, |
| | | width, |
| | | left, |
| | | right, |
| | | halfWidth, |
| | | shapeArgs, |
| | | color, |
| | | doQuartiles = series.doQuartiles !== false, // error bar inherits this series type but doesn't do quartiles |
| | | whiskerLength = parseInt(series.options.whiskerLength, 10) / 100; |
| | | |
| | | |
| | | each(points, function (point) { |
| | | |
| | | graphic = point.graphic; |
| | | shapeArgs = point.shapeArgs; // the box |
| | | stemAttr = {}; |
| | | whiskersAttr = {}; |
| | | medianAttr = {}; |
| | | color = point.color || series.color; |
| | | |
| | | if (point.plotY !== UNDEFINED) { |
| | | |
| | | pointAttr = point.pointAttr[point.selected ? 'selected' : '']; |
| | | |
| | | // crisp vector coordinates |
| | | width = shapeArgs.width; |
| | | left = mathFloor(shapeArgs.x); |
| | | right = left + width; |
| | | halfWidth = mathRound(width / 2); |
| | | //crispX = mathRound(left + halfWidth) + crispCorr; |
| | | q1Plot = mathFloor(doQuartiles ? point.q1Plot : point.lowPlot);// + crispCorr; |
| | | q3Plot = mathFloor(doQuartiles ? point.q3Plot : point.lowPlot);// + crispCorr; |
| | | highPlot = mathFloor(point.highPlot);// + crispCorr; |
| | | lowPlot = mathFloor(point.lowPlot);// + crispCorr; |
| | | |
| | | // 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; |
| | | |
| | | // Whiskers attributes |
| | | whiskersAttr.stroke = point.whiskerColor || options.whiskerColor || color; |
| | | whiskersAttr['stroke-width'] = pick(point.whiskerWidth, options.whiskerWidth, options.lineWidth); |
| | | |
| | | // Median attributes |
| | | medianAttr.stroke = point.medianColor || options.medianColor || color; |
| | | medianAttr['stroke-width'] = pick(point.medianWidth, options.medianWidth, options.lineWidth); |
| | | |
| | | |
| | | // The stem |
| | | crispCorr = (stemAttr['stroke-width'] % 2) / 2; |
| | | crispX = left + halfWidth + crispCorr; |
| | | stemPath = [ |
| | | // stem up |
| | | 'M', |
| | | crispX, q3Plot, |
| | | 'L', |
| | | crispX, highPlot, |
| | | |
| | | // stem down |
| | | 'M', |
| | | crispX, q1Plot, |
| | | 'L', |
| | | crispX, lowPlot, |
| | | 'z' |
| | | ]; |
| | | |
| | | // The box |
| | | if (doQuartiles) { |
| | | crispCorr = (pointAttr['stroke-width'] % 2) / 2; |
| | | crispX = mathFloor(crispX) + crispCorr; |
| | | q1Plot = mathFloor(q1Plot) + crispCorr; |
| | | q3Plot = mathFloor(q3Plot) + crispCorr; |
| | | left += crispCorr; |
| | | right += crispCorr; |
| | | boxPath = [ |
| | | 'M', |
| | | left, q3Plot, |
| | | 'L', |
| | | left, q1Plot, |
| | | 'L', |
| | | right, q1Plot, |
| | | 'L', |
| | | right, q3Plot, |
| | | 'L', |
| | | left, q3Plot, |
| | | 'z' |
| | | ]; |
| | | } |
| | | |
| | | // The whiskers |
| | | if (whiskerLength) { |
| | | crispCorr = (whiskersAttr['stroke-width'] % 2) / 2; |
| | | highPlot = highPlot + crispCorr; |
| | | lowPlot = lowPlot + crispCorr; |
| | | whiskersPath = [ |
| | | // High whisker |
| | | 'M', |
| | | crispX - halfWidth * whiskerLength, |
| | | highPlot, |
| | | 'L', |
| | | crispX + halfWidth * whiskerLength, |
| | | highPlot, |
| | | |
| | | // Low whisker |
| | | 'M', |
| | | crispX - halfWidth * whiskerLength, |
| | | lowPlot, |
| | | 'L', |
| | | crispX + halfWidth * whiskerLength, |
| | | lowPlot |
| | | ]; |
| | | } |
| | | |
| | | // The median |
| | | crispCorr = (medianAttr['stroke-width'] % 2) / 2; |
| | | medianPlot = mathRound(point.medianPlot) + crispCorr; |
| | | medianPath = [ |
| | | 'M', |
| | | left, |
| | | medianPlot, |
| | | 'L', |
| | | right, |
| | | medianPlot, |
| | | 'z' |
| | | ]; |
| | | |
| | | // Create or update the graphics |
| | | if (graphic) { // update |
| | | |
| | | point.stem.animate({ d: stemPath }); |
| | | if (whiskerLength) { |
| | | point.whiskers.animate({ d: whiskersPath }); |
| | | } |
| | | if (doQuartiles) { |
| | | point.box.animate({ d: boxPath }); |
| | | } |
| | | point.medianShape.animate({ d: medianPath }); |
| | | |
| | | } else { // create new |
| | | point.graphic = graphic = renderer.g() |
| | | .add(series.group); |
| | | |
| | | point.stem = renderer.path(stemPath) |
| | | .attr(stemAttr) |
| | | .add(graphic); |
| | | |
| | | if (whiskerLength) { |
| | | point.whiskers = renderer.path(whiskersPath) |
| | | .attr(whiskersAttr) |
| | | .add(graphic); |
| | | } |
| | | if (doQuartiles) { |
| | | point.box = renderer.path(boxPath) |
| | | .attr(pointAttr) |
| | | .add(graphic); |
| | | } |
| | | point.medianShape = renderer.path(medianPath) |
| | | .attr(medianAttr) |
| | | .add(graphic); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | } |
| | | |
| | | |
| | | }); |
| | | |
| | | /* **************************************************************************** |
| | | * End Box plot series code * |
| | | *****************************************************************************/ |
| | | /* **************************************************************************** |
| | | * Start error bar series code * |
| | | *****************************************************************************/ |
| | | |
| | | // 1 - set default options |
| | | defaultPlotOptions.errorbar = merge(defaultPlotOptions.boxplot, { |
| | | color: '#000000', |
| | | grouping: false, |
| | | linkedTo: ':previous', |
| | | tooltip: { |
| | | pointFormat: defaultPlotOptions.arearange.tooltip.pointFormat |
| | | }, |
| | | whiskerWidth: null |
| | | }); |
| | | |
| | | // 2 - Create the series object |
| | | seriesTypes.errorbar = extendClass(seriesTypes.boxplot, { |
| | | 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, |
| | | |
| | | /** |
| | | * 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); |
| | | } |
| | | }); |
| | | |
| | | /* **************************************************************************** |
| | | * End error bar series code * |
| | | *****************************************************************************/ |
| | | /* **************************************************************************** |
| | | * Start Waterfall series code * |
| | | *****************************************************************************/ |
| | | |
| | | // 1 - set default options |
| | | defaultPlotOptions.waterfall = merge(defaultPlotOptions.column, { |
| | | lineWidth: 1, |
| | | lineColor: '#333', |
| | | dashStyle: 'dot', |
| | | borderColor: '#333' |
| | | }); |
| | | |
| | | |
| | | // 2 - Create the series object |
| | | seriesTypes.waterfall = extendClass(seriesTypes.column, { |
| | | type: 'waterfall', |
| | | |
| | | upColorProp: 'fill', |
| | | |
| | | pointArrayMap: ['low', 'y'], |
| | | |
| | | pointValKey: 'y', |
| | | |
| | | /** |
| | | * Init waterfall series, force stacking |
| | | */ |
| | | init: function (chart, options) { |
| | | // force stacking |
| | | options.stacking = true; |
| | | |
| | | seriesTypes.column.prototype.init.call(this, chart, options); |
| | | }, |
| | | |
| | | |
| | | /** |
| | | * Translate data points from raw values |
| | | */ |
| | | translate: function () { |
| | | var series = this, |
| | | options = series.options, |
| | | axis = series.yAxis, |
| | | len, |
| | | i, |
| | | points, |
| | | point, |
| | | shapeArgs, |
| | | stack, |
| | | y, |
| | | previousY, |
| | | stackPoint, |
| | | threshold = options.threshold, |
| | | crispCorr = (options.borderWidth % 2) / 2; |
| | | |
| | | // run column series translate |
| | | seriesTypes.column.prototype.translate.apply(this); |
| | | |
| | | previousY = threshold; |
| | | points = series.points; |
| | | |
| | | for (i = 0, len = points.length; i < len; i++) { |
| | | // cache current point object |
| | | point = points[i]; |
| | | shapeArgs = point.shapeArgs; |
| | | |
| | | // get current stack |
| | | stack = series.getStack(i); |
| | | stackPoint = stack.points[series.index]; |
| | | |
| | | // override point value for sums |
| | | if (isNaN(point.y)) { |
| | | point.y = series.yData[i]; |
| | | } |
| | | |
| | | // up points |
| | | y = mathMax(previousY, previousY + point.y) + stackPoint[0]; |
| | | shapeArgs.y = axis.translate(y, 0, 1); |
| | | |
| | | |
| | | // sum points |
| | | if (point.isSum || point.isIntermediateSum) { |
| | | shapeArgs.y = axis.translate(stackPoint[1], 0, 1); |
| | | shapeArgs.height = axis.translate(stackPoint[0], 0, 1) - shapeArgs.y; |
| | | |
| | | // if it's not the sum point, update previous stack end position |
| | | } else { |
| | | previousY += stack.total; |
| | | } |
| | | |
| | | // negative points |
| | | if (shapeArgs.height < 0) { |
| | | shapeArgs.y += shapeArgs.height; |
| | | shapeArgs.height *= -1; |
| | | } |
| | | |
| | | point.plotY = shapeArgs.y = mathRound(shapeArgs.y) - crispCorr; |
| | | shapeArgs.height = mathRound(shapeArgs.height); |
| | | point.yBottom = shapeArgs.y + shapeArgs.height; |
| | | } |
| | | }, |
| | | |
| | | /** |
| | | * Call default processData then override yData to reflect waterfall's extremes on yAxis |
| | | */ |
| | | processData: function (force) { |
| | | var series = this, |
| | | options = series.options, |
| | | yData = series.yData, |
| | | points = series.points, |
| | | 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] = sum; |
| | | } else if (y === "intermediateSum" || point.isIntermediateSum) { |
| | | yData[i] = subSum; |
| | | subSum = threshold; |
| | | } else { |
| | | sum += y; |
| | | subSum += y; |
| | | } |
| | | dataMin = Math.min(sum, dataMin); |
| | | dataMax = Math.max(sum, dataMax); |
| | | } |
| | | |
| | | Series.prototype.processData.call(this, force); |
| | | |
| | | // Record extremes |
| | | series.dataMin = dataMin; |
| | | series.dataMax = dataMax; |
| | | }, |
| | | |
| | | /** |
| | | * Return y value or string if point is sum |
| | | */ |
| | | toYData: function (pt) { |
| | | if (pt.isSum) { |
| | | return "sum"; |
| | | } else if (pt.isIntermediateSum) { |
| | | return "intermediateSum"; |
| | | } |
| | | |
| | | return pt.y; |
| | | }, |
| | | |
| | | /** |
| | | * Postprocess mapping between options and SVG attributes |
| | | */ |
| | | getAttribs: function () { |
| | | seriesTypes.column.prototype.getAttribs.apply(this, arguments); |
| | | |
| | | var series = this, |
| | | options = series.options, |
| | | stateOptions = options.states, |
| | | upColor = options.upColor || series.color, |
| | | hoverColor = Highcharts.Color(upColor).brighten(0.1).get(), |
| | | seriesDownPointAttr = merge(series.pointAttr), |
| | | upColorProp = series.upColorProp; |
| | | |
| | | seriesDownPointAttr[''][upColorProp] = upColor; |
| | | seriesDownPointAttr.hover[upColorProp] = stateOptions.hover.upColor || hoverColor; |
| | | seriesDownPointAttr.select[upColorProp] = stateOptions.select.upColor || upColor; |
| | | |
| | | each(series.points, function (point) { |
| | | if (point.y > 0 && !point.color) { |
| | | point.pointAttr = seriesDownPointAttr; |
| | | point.color = upColor; |
| | | } |
| | | }); |
| | | }, |
| | | |
| | | /** |
| | | * Draw columns' connector lines |
| | | */ |
| | | getGraphPath: function () { |
| | | |
| | | var data = this.data, |
| | | length = data.length, |
| | | lineWidth = this.options.lineWidth + this.options.borderWidth, |
| | | normalizer = mathRound(lineWidth) % 2 / 2, |
| | | path = [], |
| | | M = 'M', |
| | | L = 'L', |
| | | prevArgs, |
| | | pointArgs, |
| | | i, |
| | | d; |
| | | |
| | | for (i = 1; i < length; i++) { |
| | | pointArgs = data[i].shapeArgs; |
| | | prevArgs = data[i - 1].shapeArgs; |
| | | |
| | | d = [ |
| | | M, |
| | | prevArgs.x + prevArgs.width, prevArgs.y + normalizer, |
| | | L, |
| | | pointArgs.x, prevArgs.y + normalizer |
| | | ]; |
| | | |
| | | if (data[i - 1].y < 0) { |
| | | d[2] += prevArgs.height; |
| | | d[5] += prevArgs.height; |
| | | } |
| | | |
| | | path = path.concat(d); |
| | | } |
| | | |
| | | return path; |
| | | }, |
| | | |
| | | /** |
| | | * Extremes are recorded in processData |
| | | */ |
| | | getExtremes: noop, |
| | | |
| | | /** |
| | | * Return stack for given index |
| | | */ |
| | | getStack: function (i) { |
| | | var axis = this.yAxis, |
| | | stacks = axis.stacks, |
| | | key = this.stackKey; |
| | | |
| | | if (this.processedYData[i] < this.options.threshold) { |
| | | key = '-' + key; |
| | | } |
| | | |
| | | return stacks[key][i]; |
| | | }, |
| | | |
| | | drawGraph: Series.prototype.drawGraph |
| | | }); |
| | | |
| | | /* **************************************************************************** |
| | | * End Waterfall series code * |
| | | *****************************************************************************/ |
| | | /* **************************************************************************** |
| | | * Start Bubble series code * |
| | | *****************************************************************************/ |
| | | |
| | | // 1 - set default options |
| | | defaultPlotOptions.bubble = merge(defaultPlotOptions.scatter, { |
| | | dataLabels: { |
| | | inside: true, |
| | | style: { |
| | | color: 'white', |
| | | textShadow: '0px 0px 3px black' |
| | | }, |
| | | verticalAlign: 'middle' |
| | | }, |
| | | // displayNegative: true, |
| | | marker: { |
| | | // fillOpacity: 0.5, |
| | | lineColor: null, // inherit from series.color |
| | | lineWidth: 1 |
| | | }, |
| | | minSize: 8, |
| | | maxSize: '20%', |
| | | // negativeColor: null, |
| | | tooltip: { |
| | | pointFormat: '({point.x}, {point.y}), Size: {point.z}' |
| | | }, |
| | | turboThreshold: 0, |
| | | zThreshold: 0 |
| | | }); |
| | | |
| | | // 2 - Create the series object |
| | | seriesTypes.bubble = extendClass(seriesTypes.scatter, { |
| | | type: 'bubble', |
| | | pointArrayMap: ['y', 'z'], |
| | | trackerGroups: ['group', 'dataLabelsGroup'], |
| | | |
| | | /** |
| | | * Mapping between SVG attributes and the corresponding options |
| | | */ |
| | | pointAttrToOptions: { |
| | | stroke: 'lineColor', |
| | | 'stroke-width': 'lineWidth', |
| | | fill: 'fillColor' |
| | | }, |
| | | |
| | | /** |
| | | * Apply the fillOpacity to all fill positions |
| | | */ |
| | | applyOpacity: function (fill) { |
| | | var markerOptions = this.options.marker, |
| | | fillOpacity = pick(markerOptions.fillOpacity, 0.5); |
| | | |
| | | // When called from Legend.colorizeItem, the fill isn't predefined |
| | | fill = fill || markerOptions.fillColor || this.color; |
| | | |
| | | if (fillOpacity !== 1) { |
| | | fill = Highcharts.Color(fill).setOpacity(fillOpacity).get('rgba'); |
| | | } |
| | | return fill; |
| | | }, |
| | | |
| | | /** |
| | | * Extend the convertAttribs method by applying opacity to the fill |
| | | */ |
| | | convertAttribs: function () { |
| | | var obj = Series.prototype.convertAttribs.apply(this, arguments); |
| | | |
| | | obj.fill = this.applyOpacity(obj.fill); |
| | | |
| | | return obj; |
| | | }, |
| | | |
| | | /** |
| | | * Get the radius for each point based on the minSize, maxSize and each point's Z value. This |
| | | * must be done prior to Series.translate because the axis needs to add padding in |
| | | * accordance with the point sizes. |
| | | */ |
| | | getRadii: function (zMin, zMax, minSize, maxSize) { |
| | | var len, |
| | | i, |
| | | pos, |
| | | zData = this.zData, |
| | | radii = [], |
| | | zRange; |
| | | |
| | | // Set the shape type and arguments to be picked up in drawPoints |
| | | for (i = 0, len = zData.length; i < len; i++) { |
| | | zRange = zMax - zMin; |
| | | pos = zRange > 0 ? // relative size, a number between 0 and 1 |
| | | (zData[i] - zMin) / (zMax - zMin) : |
| | | 0.5; |
| | | radii.push(math.ceil(minSize + pos * (maxSize - minSize)) / 2); |
| | | } |
| | | this.radii = radii; |
| | | }, |
| | | |
| | | /** |
| | | * Perform animation on the bubbles |
| | | */ |
| | | animate: function (init) { |
| | | var animation = this.options.animation; |
| | | |
| | | if (!init) { // run the animation |
| | | each(this.points, function (point) { |
| | | var graphic = point.graphic, |
| | | shapeArgs = point.shapeArgs; |
| | | |
| | | if (graphic && shapeArgs) { |
| | | // start values |
| | | graphic.attr('r', 1); |
| | | |
| | | // animate |
| | | graphic.animate({ |
| | | r: shapeArgs.r |
| | | }, animation); |
| | | } |
| | | }); |
| | | |
| | | // 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 |
| | | |
| | | // Flag for negativeColor to be applied in Series.js |
| | | point.negative = point.z < (this.options.zThreshold || 0); |
| | | |
| | | if (radius >= this.minPxSize / 2) { |
| | | // Shape arguments |
| | | point.shapeType = 'circle'; |
| | | point.shapeArgs = { |
| | | x: point.plotX, |
| | | y: point.plotY, |
| | | r: 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 |
| | | point.shapeArgs = point.plotY = point.dlBox = UNDEFINED; // #1691 |
| | | } |
| | | } |
| | | }, |
| | | |
| | | /** |
| | | * Get the series' symbol in the legend |
| | | * |
| | | * @param {Object} legend The legend object |
| | | * @param {Object} item The series (this) or point |
| | | */ |
| | | drawLegendSymbol: function (legend, item) { |
| | | var radius = pInt(legend.itemStyle.fontSize) / 2; |
| | | |
| | | item.legendSymbol = this.chart.renderer.circle( |
| | | radius, |
| | | legend.baseline - radius, |
| | | radius |
| | | ).attr({ |
| | | zIndex: 3 |
| | | }).add(item.legendGroup); |
| | | item.legendSymbol.isMarker = true; |
| | | |
| | | }, |
| | | |
| | | drawPoints: seriesTypes.column.prototype.drawPoints, |
| | | alignDataLabel: seriesTypes.column.prototype.alignDataLabel |
| | | }); |
| | | |
| | | /** |
| | | * 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 |
| | | if (this.tickPositions) { |
| | | each(this.series, function (series) { |
| | | |
| | | var seriesOptions = series.options, |
| | | zData; |
| | | |
| | | if (series.type === 'bubble' && series.visible) { |
| | | |
| | | // 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 |
| | | each(['minSize', 'maxSize'], function (prop) { |
| | | var length = seriesOptions[prop], |
| | | isPercent = /%$/.test(length); |
| | | |
| | | length = pInt(length); |
| | | extremes[prop] = isPercent ? |
| | | smallestSize * length / 100 : |
| | | length; |
| | | |
| | | }); |
| | | series.minPxSize = extremes.minSize; |
| | | |
| | | // Find the min and max Z |
| | | zData = series.zData; |
| | | if (zData.length) { // #1735 |
| | | zMin = math.min( |
| | | zMin, |
| | | math.max( |
| | | arrayMin(zData), |
| | | seriesOptions.displayNegative === false ? seriesOptions.zThreshold : -Number.MAX_VALUE |
| | | ) |
| | | ); |
| | | zMax = math.max(zMax, arrayMax(zData)); |
| | | } |
| | | } |
| | | } |
| | | }); |
| | | |
| | | each(activeSeries, function (series) { |
| | | |
| | | var data = series[dataKey], |
| | | i = data.length, |
| | | radius; |
| | | |
| | | if (isXAxis) { |
| | | series.getRadii(zMin, zMax, extremes.minSize, extremes.maxSize); |
| | | } |
| | | |
| | | if (range > 0) { |
| | | while (i--) { |
| | | radius = series.radii[i]; |
| | | pxMin = Math.min(((data[i] - min) * transA) - radius, pxMin); |
| | | pxMax = Math.max(((data[i] - min) * transA) + radius, pxMax); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | if (activeSeries.length && range > 0 && pick(this.options.min, this.userMin) === UNDEFINED && pick(this.options.max, this.userMax) === UNDEFINED) { |
| | | pxMax -= axisLength; |
| | | transA *= (axisLength + pxMin - pxMax) / axisLength; |
| | | this.min += pxMin / transA; |
| | | this.max += pxMax / transA; |
| | | } |
| | | } |
| | | }; |
| | | |
| | | /* **************************************************************************** |
| | | * End Bubble series code * |
| | | *****************************************************************************/ |
| | | /** |
| | | * Extensions for polar charts. Additionally, much of the geometry required for polar charts is |
| | | * gathered in RadialAxes.js. |
| | | * |
| | | */ |
| | | |
| | | var seriesProto = Series.prototype, |
| | | pointerProto = Highcharts.Pointer.prototype; |
| | | |
| | | |
| | | |
| | | /** |
| | | * 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; |
| | | |
| | | // Save rectangular plotX, plotY for later computation |
| | | point.rectPlotX = plotX; |
| | | point.rectPlotY = plotY; |
| | | |
| | | // Record the angle in degrees for use in tooltip |
| | | point.clientX = ((plotX / Math.PI * 180) + this.xAxis.pane.options.startAngle) % 360; |
| | | |
| | | // 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; |
| | | }; |
| | | |
| | | /** |
| | | * Order the tooltip points to get the mouse capture ranges correct. #1915. |
| | | */ |
| | | seriesProto.orderTooltipPoints = function (points) { |
| | | if (this.chart.polar) { |
| | | points.sort(function (a, b) { |
| | | return a.clientX - b.clientX; |
| | | }); |
| | | |
| | | // Wrap mouse tracking around to capture movement on the segment to the left |
| | | // of the north point (#1469, #2093). |
| | | if (points[0]) { |
| | | points[0].wrappedClientX = points[0].clientX + 360; |
| | | points.push(points[0]); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | |
| | | /** |
| | | * Add some special init logic to areas and areasplines |
| | | */ |
| | | function initArea(proceed, chart, options) { |
| | | proceed.call(this, chart, options); |
| | | if (this.chart.polar) { |
| | | |
| | | /** |
| | | * Overridden method to close a segment path. While in a cartesian plane the area |
| | | * goes down to the threshold, in the polar chart it goes to the center. |
| | | */ |
| | | this.closeSegment = function (path) { |
| | | var center = this.xAxis.center; |
| | | path.push( |
| | | 'L', |
| | | center[0], |
| | | center[1] |
| | | ); |
| | | }; |
| | | |
| | | // Instead of complicated logic to draw an area around the inner area in a stack, |
| | | // just draw it behind |
| | | this.closedStacks = true; |
| | | } |
| | | } |
| | | wrap(seriesTypes.area.prototype, 'init', initArea); |
| | | wrap(seriesTypes.areaspline.prototype, 'init', initArea); |
| | | |
| | | |
| | | /** |
| | | * Overridden method for calculating a spline from one point to the next |
| | | */ |
| | | wrap(seriesTypes.spline.prototype, 'getPointSpline', function (proceed, segment, point, i) { |
| | | |
| | | var ret, |
| | | smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc; |
| | | denom = smoothing + 1, |
| | | plotX, |
| | | plotY, |
| | | lastPoint, |
| | | nextPoint, |
| | | lastX, |
| | | lastY, |
| | | nextX, |
| | | nextY, |
| | | leftContX, |
| | | leftContY, |
| | | rightContX, |
| | | rightContY, |
| | | distanceLeftControlPoint, |
| | | distanceRightControlPoint, |
| | | leftContAngle, |
| | | rightContAngle, |
| | | jointAngle; |
| | | |
| | | |
| | | if (this.chart.polar) { |
| | | |
| | | plotX = point.plotX; |
| | | plotY = point.plotY; |
| | | lastPoint = segment[i - 1]; |
| | | nextPoint = segment[i + 1]; |
| | | |
| | | // Connect ends |
| | | if (this.connectEnds) { |
| | | if (!lastPoint) { |
| | | lastPoint = segment[segment.length - 2]; // not the last but the second last, because the segment is already connected |
| | | } |
| | | if (!nextPoint) { |
| | | nextPoint = segment[1]; |
| | | } |
| | | } |
| | | |
| | | // find control points |
| | | if (lastPoint && nextPoint) { |
| | | |
| | | lastX = lastPoint.plotX; |
| | | lastY = lastPoint.plotY; |
| | | nextX = nextPoint.plotX; |
| | | nextY = nextPoint.plotY; |
| | | leftContX = (smoothing * plotX + lastX) / denom; |
| | | leftContY = (smoothing * plotY + lastY) / denom; |
| | | rightContX = (smoothing * plotX + nextX) / denom; |
| | | rightContY = (smoothing * plotY + nextY) / denom; |
| | | distanceLeftControlPoint = Math.sqrt(Math.pow(leftContX - plotX, 2) + Math.pow(leftContY - plotY, 2)); |
| | | distanceRightControlPoint = 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) * distanceLeftControlPoint; |
| | | leftContY = plotY + Math.sin(jointAngle) * distanceLeftControlPoint; |
| | | rightContX = plotX + Math.cos(Math.PI + jointAngle) * distanceRightControlPoint; |
| | | rightContY = plotY + Math.sin(Math.PI + jointAngle) * distanceRightControlPoint; |
| | | |
| | | // Record for drawing in next point |
| | | point.rightContX = rightContX; |
| | | point.rightContY = rightContY; |
| | | |
| | | } |
| | | |
| | | |
| | | // moveTo or lineTo |
| | | if (!i) { |
| | | ret = ['M', plotX, plotY]; |
| | | } else { // curve from last point to this |
| | | ret = [ |
| | | 'C', |
| | | lastPoint.rightContX || lastPoint.plotX, |
| | | lastPoint.rightContY || lastPoint.plotY, |
| | | leftContX || plotX, |
| | | leftContY || plotY, |
| | | plotX, |
| | | plotY |
| | | ]; |
| | | lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later |
| | | } |
| | | |
| | | |
| | | } else { |
| | | ret = proceed.call(this, segment, point, i); |
| | | } |
| | | return ret; |
| | | }); |
| | | |
| | | /** |
| | | * Extend translate. The plotX and plotY values are computed as if the polar chart were a |
| | | * cartesian plane, where plotX denotes the angle in radians and (yAxis.len - plotY) is the pixel distance from |
| | | * center. |
| | | */ |
| | | wrap(seriesProto, 'translate', function (proceed) { |
| | | |
| | | // Run uber method |
| | | proceed.call(this); |
| | | |
| | | // Postprocess plot coordinates |
| | | if (this.chart.polar && !this.preventPostTranslate) { |
| | | var points = this.points, |
| | | i = points.length; |
| | | while (i--) { |
| | | // Translate plotX, plotY from angle and radius to true plot coordinates |
| | | this.toXY(points[i]); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | /** |
| | | * Extend getSegmentPath to allow connecting ends across 0 to provide a closed circle in |
| | | * line-like series. |
| | | */ |
| | | wrap(seriesProto, 'getSegmentPath', function (proceed, segment) { |
| | | |
| | | var points = this.points; |
| | | |
| | | // Connect the path |
| | | if (this.chart.polar && this.options.connectEnds !== false && |
| | | segment[segment.length - 1] === points[points.length - 1] && points[0].y !== null) { |
| | | this.connectEnds = true; // re-used in splines |
| | | segment = [].concat(segment, [points[0]]); |
| | | } |
| | | |
| | | // Run uber method |
| | | return proceed.call(this, segment); |
| | | |
| | | }); |
| | | |
| | | |
| | | function polarAnimate(proceed, init) { |
| | | var chart = this.chart, |
| | | animation = this.options.animation, |
| | | group = this.group, |
| | | markerGroup = this.markerGroup, |
| | | center = this.xAxis.center, |
| | | plotLeft = chart.plotLeft, |
| | | plotTop = chart.plotTop, |
| | | attribs; |
| | | |
| | | // 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.attrSetters = group.attrSetters; |
| | | 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 both regular series and column series and their derivatives |
| | | wrap(seriesProto, 'animate', polarAnimate); |
| | | wrap(colProto, 'animate', polarAnimate); |
| | | |
| | | |
| | | /** |
| | | * Throw in a couple of properties to let setTooltipPoints know we're indexing the points |
| | | * in degrees (0-360), not plot pixel width. |
| | | */ |
| | | wrap(seriesProto, 'setTooltipPoints', function (proceed, renew) { |
| | | |
| | | if (this.chart.polar) { |
| | | extend(this.xAxis, { |
| | | tooltipLen: 360 // degrees are the resolution unit of the tooltipPoints array |
| | | }); |
| | | } |
| | | |
| | | // Run uber method |
| | | return proceed.call(this, renew); |
| | | }); |
| | | |
| | | |
| | | /** |
| | | * Extend the column prototype's translate method |
| | | */ |
| | | wrap(colProto, 'translate', function (proceed) { |
| | | |
| | | var xAxis = this.xAxis, |
| | | len = this.yAxis.len, |
| | | center = xAxis.center, |
| | | startAngleRad = xAxis.startAngleRad, |
| | | renderer = this.chart.renderer, |
| | | 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: renderer.symbols.arc( |
| | | center[0], |
| | | center[1], |
| | | len - point.plotY, |
| | | null, |
| | | { |
| | | start: start, |
| | | end: start + point.pointWidth, |
| | | innerR: len - pick(point.yBottom, len) |
| | | } |
| | | ) |
| | | }; |
| | | this.toXY(point); // provide correct plotX, plotY for tooltip |
| | | } |
| | | } |
| | | }); |
| | | |
| | | |
| | | /** |
| | | * 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 the mouse tracker to return the tooltip position index in terms of |
| | | * degrees rather than pixels |
| | | */ |
| | | wrap(pointerProto, 'getIndex', function (proceed, e) { |
| | | var ret, |
| | | chart = this.chart, |
| | | center, |
| | | x, |
| | | y; |
| | | |
| | | if (chart.polar) { |
| | | center = chart.xAxis[0].center; |
| | | x = e.chartX - center[0] - chart.plotLeft; |
| | | y = e.chartY - center[1] - chart.plotTop; |
| | | |
| | | ret = 180 - Math.round(Math.atan2(x, y) / Math.PI * 180); |
| | | |
| | | } else { |
| | | |
| | | // Run uber method |
| | | ret = proceed.call(this, e); |
| | | } |
| | | return ret; |
| | | }); |
| | | |
| | | /** |
| | | * Extend getCoordinates to prepare for polar axis values |
| | | */ |
| | | wrap(pointerProto, 'getCoordinates', function (proceed, e) { |
| | | var chart = this.chart, |
| | | ret = { |
| | | xAxis: [], |
| | | yAxis: [] |
| | | }; |
| | | |
| | | if (chart.polar) { |
| | | |
| | | each(chart.axes, function (axis) { |
| | | var isXAxis = axis.isXAxis, |
| | | center = axis.center, |
| | | x = e.chartX - center[0] - chart.plotLeft, |
| | | y = e.chartY - center[1] - chart.plotTop; |
| | | |
| | | ret[isXAxis ? 'xAxis' : 'yAxis'].push({ |
| | | axis: axis, |
| | | value: axis.translate( |
| | | isXAxis ? |
| | | Math.PI - Math.atan2(x, y) : // angle |
| | | Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)), // distance from center |
| | | true |
| | | ) |
| | | }); |
| | | }); |
| | | |
| | | } else { |
| | | ret = proceed.call(this, e); |
| | | } |
| | | |
| | | return ret; |
| | | }); |
| | | }(Highcharts)); |
| | | // ==ClosureCompiler==
|
| | | // @compilation_level SIMPLE_OPTIMIZATIONS
|
| | |
|
| | | /**
|
| | | * @license Highcharts JS v3.0.6 (2013-10-04)
|
| | | *
|
| | | * (c) 2009-2013 Torstein Hønsi
|
| | | *
|
| | | * License: www.highcharts.com/license
|
| | | */
|
| | |
|
| | | // JSLint options:
|
| | | /*global Highcharts, HighchartsAdapter, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console */
|
| | |
|
| | | (function (Highcharts, UNDEFINED) {
|
| | | var arrayMin = Highcharts.arrayMin,
|
| | | arrayMax = Highcharts.arrayMax,
|
| | | each = Highcharts.each,
|
| | | extend = Highcharts.extend,
|
| | | merge = Highcharts.merge,
|
| | | map = Highcharts.map,
|
| | | pick = Highcharts.pick,
|
| | | pInt = Highcharts.pInt,
|
| | | defaultPlotOptions = Highcharts.getOptions().plotOptions,
|
| | | seriesTypes = Highcharts.seriesTypes,
|
| | | extendClass = Highcharts.extendClass,
|
| | | splat = Highcharts.splat,
|
| | | wrap = Highcharts.wrap,
|
| | | Axis = Highcharts.Axis,
|
| | | Tick = Highcharts.Tick,
|
| | | Series = Highcharts.Series,
|
| | | colProto = seriesTypes.column.prototype,
|
| | | math = Math,
|
| | | mathRound = math.round,
|
| | | mathFloor = math.floor,
|
| | | mathMax = math.max,
|
| | | noop = function () {};/**
|
| | | * 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.
|
| | | */
|
| | | function Pane(options, chart, firstAxis) {
|
| | | this.init.call(this, options, chart, firstAxis);
|
| | | }
|
| | |
|
| | | // Extend the Pane prototype
|
| | | extend(Pane.prototype, {
|
| | | |
| | | /**
|
| | | * Initiate the Pane object
|
| | | */
|
| | | init: function (options, chart, firstAxis) {
|
| | | var pane = this,
|
| | | backgroundOption,
|
| | | defaultOptions = pane.defaultOptions;
|
| | | |
| | | pane.chart = chart;
|
| | | |
| | | // Set options
|
| | | if (chart.angular) { // gauges
|
| | | defaultOptions.background = {}; // gets extended by this.defaultBackgroundOptions
|
| | | }
|
| | | pane.options = options = merge(defaultOptions, options);
|
| | | |
| | | backgroundOption = options.background;
|
| | | |
| | | // To avoid having weighty logic to place, update and remove the backgrounds,
|
| | | // push them to the first axis' plot bands and borrow the existing logic there.
|
| | | if (backgroundOption) {
|
| | | each([].concat(splat(backgroundOption)).reverse(), function (config) {
|
| | | var backgroundColor = config.backgroundColor; // if defined, replace the old one (specific for gradients)
|
| | | config = merge(pane.defaultBackgroundOptions, config);
|
| | | if (backgroundColor) {
|
| | | config.backgroundColor = backgroundColor;
|
| | | }
|
| | | config.color = config.backgroundColor; // due to naming in plotBands
|
| | | firstAxis.options.plotBands.unshift(config);
|
| | | });
|
| | | }
|
| | | },
|
| | | |
| | | /**
|
| | | * The default options object
|
| | | */
|
| | | defaultOptions: {
|
| | | // background: {conditional},
|
| | | center: ['50%', '50%'],
|
| | | size: '85%',
|
| | | startAngle: 0
|
| | | //endAngle: startAngle + 360
|
| | | }, |
| | | |
| | | /**
|
| | | * The default background options
|
| | | */
|
| | | defaultBackgroundOptions: {
|
| | | shape: 'circle',
|
| | | borderWidth: 1,
|
| | | borderColor: 'silver',
|
| | | backgroundColor: {
|
| | | linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
|
| | | stops: [
|
| | | [0, '#FFF'],
|
| | | [1, '#DDD']
|
| | | ]
|
| | | },
|
| | | from: Number.MIN_VALUE, // corrected to axis min
|
| | | innerRadius: 0,
|
| | | to: Number.MAX_VALUE, // corrected to axis max
|
| | | outerRadius: '105%'
|
| | | }
|
| | | |
| | | });
|
| | | var axisProto = Axis.prototype,
|
| | | tickProto = Tick.prototype;
|
| | | |
| | | /**
|
| | | * Augmented methods for the x axis in order to hide it completely, used for the X axis in gauges
|
| | | */
|
| | | var 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
|
| | | */
|
| | | /*jslint unparam: true*/
|
| | | var radialAxisMixin = {
|
| | | isRadial: true,
|
| | | |
| | | /**
|
| | | * The default options extend defaultYAxisOptions
|
| | | */
|
| | | defaultRadialGaugeOptions: {
|
| | | labels: {
|
| | | align: 'center',
|
| | | x: 0,
|
| | | y: null // auto
|
| | | },
|
| | | minorGridLineWidth: 0,
|
| | | minorTickInterval: 'auto',
|
| | | minorTickLength: 10,
|
| | | minorTickPosition: 'inside',
|
| | | minorTickWidth: 1,
|
| | | plotBands: [],
|
| | | 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
|
| | | },
|
| | | maxPadding: 0,
|
| | | minPadding: 0,
|
| | | plotBands: [],
|
| | | showLastLabel: false, |
| | | tickLength: 0
|
| | | },
|
| | | |
| | | // Radial axis, like a spoke in a polar chart
|
| | | defaultRadialYOptions: {
|
| | | gridLineInterpolation: 'circle',
|
| | | labels: {
|
| | | align: 'right',
|
| | | x: -3,
|
| | | y: -2
|
| | | },
|
| | | plotBands: [],
|
| | | showLastLabel: false,
|
| | | title: {
|
| | | x: 4,
|
| | | text: null,
|
| | | rotation: 90
|
| | | }
|
| | | },
|
| | | |
| | | /**
|
| | | * Merge and set options
|
| | | */
|
| | | setOptions: function (userOptions) {
|
| | | |
| | | this.options = merge(
|
| | | this.defaultOptions,
|
| | | this.defaultRadialOptions,
|
| | | userOptions
|
| | | );
|
| | | |
| | | },
|
| | | |
| | | /**
|
| | | * 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;
|
| | | radius = pick(radius, center[2] / 2 - this.offset);
|
| | | |
| | | return this.chart.renderer.symbols.arc(
|
| | | this.left + center[0],
|
| | | this.top + center[1],
|
| | | radius,
|
| | | radius, |
| | | {
|
| | | start: this.startAngleRad,
|
| | | end: this.endAngleRad,
|
| | | open: true,
|
| | | innerR: 0
|
| | | }
|
| | | );
|
| | | },
|
| | |
|
| | | /**
|
| | | * Override setAxisTranslation by setting the translation to the difference
|
| | | * in rotation. This allows the translate method to return angle for |
| | | * any given value.
|
| | | */
|
| | | setAxisTranslation: function () {
|
| | | |
| | | // 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 +
|
| | | (this.reversed ? (this.endAngleRad - this.startAngleRad) / 4 : 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 (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.center = this.pane.center = seriesTypes.pie.prototype.getCenter.call(this.pane);
|
| | | |
| | | this.len = this.width = this.height = this.isCircular ?
|
| | | this.center[2] * (this.endAngleRad - this.startAngleRad) / 2 :
|
| | | this.center[2] / 2;
|
| | | }
|
| | | },
|
| | | |
| | | /**
|
| | | * Returns the x, y coordinate of a point given by a value and a pixel distance
|
| | | * from center
|
| | | */
|
| | | getPosition: function (value, length) {
|
| | | if (!this.isCircular) {
|
| | | length = this.translate(value);
|
| | | value = this.min; |
| | | }
|
| | | |
| | | return this.postTranslate(
|
| | | this.translate(value),
|
| | | pick(length, 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)
|
| | | ],
|
| | | 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 {
|
| | | |
| | | // 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 = map(radii, 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);
|
| | | }
|
| | | |
| | | |
| | | ret = this.chart.renderer.symbols.arc(
|
| | | this.left + center[0],
|
| | | this.top + center[1],
|
| | | radii[0],
|
| | | radii[0],
|
| | | {
|
| | | start: start,
|
| | | end: 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);
|
| | | if (value) { // a value of 0 is in the center
|
| | | ret = axis.getLinePath(0, value);
|
| | | }
|
| | | // Concentric polygons |
| | | } else {
|
| | | xAxis = chart.xAxis[0];
|
| | | ret = [];
|
| | | value = axis.translate(value);
|
| | | tickPositions = xAxis.tickPositions;
|
| | | if (xAxis.autoConnect) {
|
| | | tickPositions = tickPositions.concat([tickPositions[0]]);
|
| | | }
|
| | | // Reverse the positions for concatenation of polygonal plot bands
|
| | | if (reverse) {
|
| | | tickPositions = [].concat(tickPositions).reverse();
|
| | | }
|
| | | |
| | | each(tickPositions, function (pos, i) {
|
| | | xy = xAxis.getPosition(pos, value);
|
| | | ret.push(i ? 'L' : 'M', xy.x, xy.y);
|
| | | });
|
| | | |
| | | }
|
| | | 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) |
| | | };
|
| | | }
|
| | | |
| | | };
|
| | | /*jslint unparam: false*/
|
| | |
|
| | | /**
|
| | | * Override axisProto.init to mix in special axis instance functions and function overrides
|
| | | */
|
| | | wrap(axisProto, 'init', function (proceed, chart, userOptions) {
|
| | | var axis = this,
|
| | | angular = chart.angular,
|
| | | polar = chart.polar,
|
| | | isX = userOptions.isX,
|
| | | isHidden = angular && isX,
|
| | | isCircular,
|
| | | startAngleRad,
|
| | | endAngleRad,
|
| | | options,
|
| | | chartOptions = chart.options,
|
| | | paneIndex = userOptions.pane || 0,
|
| | | pane,
|
| | | paneOptions;
|
| | | |
| | | // Before prototype.init
|
| | | if (angular) {
|
| | | extend(this, isHidden ? hiddenAxisMixin : radialAxisMixin);
|
| | | isCircular = !isX;
|
| | | if (isCircular) {
|
| | | this.defaultRadialOptions = this.defaultRadialGaugeOptions;
|
| | | }
|
| | | |
| | | } else if (polar) {
|
| | | //extend(this, userOptions.isX ? radialAxisMixin : radialAxisMixin);
|
| | | extend(this, radialAxisMixin);
|
| | | isCircular = isX;
|
| | | this.defaultRadialOptions = isX ? this.defaultRadialXOptions : merge(this.defaultYAxisOptions, this.defaultRadialYOptions);
|
| | | |
| | | }
|
| | | |
| | | // Run prototype.init
|
| | | proceed.call(this, chart, userOptions);
|
| | | |
| | | if (!isHidden && (angular || polar)) {
|
| | | options = this.options;
|
| | | |
| | | // Create the pane and set the pane options.
|
| | | if (!chart.panes) {
|
| | | chart.panes = [];
|
| | | }
|
| | | this.pane = pane = chart.panes[paneIndex] = chart.panes[paneIndex] || new Pane(
|
| | | splat(chartOptions.pane)[paneIndex],
|
| | | chart,
|
| | | axis
|
| | | );
|
| | | paneOptions = pane.options;
|
| | | |
| | | |
| | | // Disable certain features on angular and polar axes
|
| | | chart.inverted = false;
|
| | | chartOptions.chart.zoomType = null;
|
| | | |
| | | // Start and end angle options are
|
| | | // given in degrees relative to top, while internal computations are
|
| | | // in radians relative to right (like SVG).
|
| | | this.startAngleRad = startAngleRad = (paneOptions.startAngle - 90) * Math.PI / 180;
|
| | | this.endAngleRad = endAngleRad = (pick(paneOptions.endAngle, paneOptions.startAngle + 360) - 90) * Math.PI / 180;
|
| | | this.offset = options.offset || 0;
|
| | | |
| | | this.isCircular = isCircular;
|
| | | |
| | | // Automatically connect grid lines?
|
| | | if (isCircular && userOptions.max === UNDEFINED && endAngleRad - startAngleRad === 2 * Math.PI) {
|
| | | this.autoConnect = true;
|
| | | }
|
| | | }
|
| | | |
| | | });
|
| | |
|
| | | /**
|
| | | * Add special cases within the Tick class' methods for radial axes.
|
| | | */ |
| | | wrap(tickProto, 'getPosition', function (proceed, horiz, pos, tickmarkOffset, old) {
|
| | | var axis = this.axis;
|
| | | |
| | | return axis.getPosition ? |
| | | axis.getPosition(pos) :
|
| | | proceed.call(this, horiz, pos, tickmarkOffset, old); |
| | | });
|
| | |
|
| | | /**
|
| | | * Wrap the getLabelPosition function to find the center position of the label
|
| | | * based on the distance option
|
| | | */ |
| | | wrap(tickProto, 'getLabelPosition', function (proceed, x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
|
| | | var axis = this.axis,
|
| | | optionsY = labelOptions.y,
|
| | | ret,
|
| | | align = labelOptions.align,
|
| | | angle = ((axis.translate(this.pos) + axis.startAngleRad + Math.PI / 2) / Math.PI * 180) % 360;
|
| | | |
| | | if (axis.isRadial) {
|
| | | 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 = pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;
|
| | | |
| | | }
|
| | | |
| | | // Automatic alignment
|
| | | if (align === null) {
|
| | | if (axis.isCircular) {
|
| | | 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
|
| | | }
|
| | | } else {
|
| | | align = 'center';
|
| | | }
|
| | | label.attr({
|
| | | align: align
|
| | | });
|
| | | }
|
| | | |
| | | ret.x += labelOptions.x;
|
| | | ret.y += optionsY;
|
| | | |
| | | } else {
|
| | | ret = proceed.call(this, x, y, label, horiz, labelOptions, tickmarkOffset, index, step);
|
| | | }
|
| | | return ret;
|
| | | });
|
| | |
|
| | | /**
|
| | | * Wrap the getMarkPath function to return the path of the radial marker
|
| | | */
|
| | | wrap(tickProto, 'getMarkPath', function (proceed, x, y, tickLength, tickWidth, horiz, renderer) {
|
| | | var axis = this.axis,
|
| | | endPoint,
|
| | | ret;
|
| | | |
| | | if (axis.isRadial) {
|
| | | endPoint = axis.getPosition(this.pos, axis.center[2] / 2 + tickLength);
|
| | | ret = [
|
| | | 'M',
|
| | | x,
|
| | | y,
|
| | | 'L',
|
| | | endPoint.x,
|
| | | endPoint.y
|
| | | ];
|
| | | } else {
|
| | | ret = proceed.call(this, x, y, tickLength, tickWidth, horiz, renderer);
|
| | | }
|
| | | return ret;
|
| | | });/* |
| | | * The AreaRangeSeries class
|
| | | * |
| | | */
|
| | |
|
| | | /**
|
| | | * Extend the default options with map options
|
| | | */
|
| | | defaultPlotOptions.arearange = merge(defaultPlotOptions.area, {
|
| | | lineWidth: 1,
|
| | | marker: null,
|
| | | threshold: null,
|
| | | tooltip: {
|
| | | pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.low}</b> - <b>{point.high}</b><br/>' |
| | | },
|
| | | trackByArea: true,
|
| | | dataLabels: {
|
| | | verticalAlign: null,
|
| | | xLow: 0,
|
| | | xHigh: 0,
|
| | | yLow: 0,
|
| | | yHigh: 0 |
| | | }
|
| | | });
|
| | |
|
| | | /**
|
| | | * Add the series type
|
| | | */
|
| | | seriesTypes.arearange = Highcharts.extendClass(seriesTypes.area, {
|
| | | type: 'arearange',
|
| | | pointArrayMap: ['low', 'high'],
|
| | | toYData: function (point) {
|
| | | return [point.low, point.high];
|
| | | },
|
| | | pointValKey: 'low',
|
| | | |
| | | /**
|
| | | * Extend getSegments to force null points if the higher value is null. #1703.
|
| | | */
|
| | | getSegments: function () {
|
| | | var series = this;
|
| | |
|
| | | each(series.points, function (point) {
|
| | | if (!series.options.connectNulls && (point.low === null || point.high === null)) {
|
| | | point.y = null;
|
| | | } else if (point.low === null && point.high !== null) {
|
| | | point.y = point.high;
|
| | | }
|
| | | });
|
| | | Series.prototype.getSegments.call(this);
|
| | | },
|
| | | |
| | | /**
|
| | | * Translate data points from raw values x and y to plotX and plotY
|
| | | */
|
| | | translate: function () {
|
| | | var series = this,
|
| | | yAxis = series.yAxis;
|
| | |
|
| | | seriesTypes.area.prototype.translate.apply(series);
|
| | |
|
| | | // Set plotLow and plotHigh
|
| | | each(series.points, function (point) {
|
| | |
|
| | | var low = point.low,
|
| | | high = point.high,
|
| | | plotY = point.plotY;
|
| | |
|
| | | if (high === null && low === null) {
|
| | | point.y = null;
|
| | | } else if (low === null) {
|
| | | point.plotLow = point.plotY = null;
|
| | | point.plotHigh = yAxis.translate(high, 0, 1, 0, 1);
|
| | | } else if (high === null) {
|
| | | point.plotLow = plotY;
|
| | | point.plotHigh = null;
|
| | | } else {
|
| | | point.plotLow = plotY;
|
| | | point.plotHigh = yAxis.translate(high, 0, 1, 0, 1);
|
| | | }
|
| | | });
|
| | | },
|
| | | |
| | | /**
|
| | | * Extend the line series' getSegmentPath method by applying the segment
|
| | | * path to both lower and higher values of the range
|
| | | */
|
| | | getSegmentPath: function (segment) {
|
| | | |
| | | var lowSegment,
|
| | | highSegment = [],
|
| | | i = segment.length,
|
| | | baseGetSegmentPath = Series.prototype.getSegmentPath,
|
| | | point,
|
| | | linePath,
|
| | | lowerPath,
|
| | | options = this.options,
|
| | | step = options.step,
|
| | | higherPath;
|
| | | |
| | | // Remove nulls from low segment
|
| | | lowSegment = HighchartsAdapter.grep(segment, function (point) {
|
| | | return point.plotLow !== null;
|
| | | });
|
| | | |
| | | // Make a segment with plotX and plotY for the top values
|
| | | while (i--) {
|
| | | point = segment[i];
|
| | | if (point.plotHigh !== null) {
|
| | | highSegment.push({
|
| | | plotX: point.plotX,
|
| | | plotY: point.plotHigh
|
| | | });
|
| | | }
|
| | | }
|
| | | |
| | | // Get the paths
|
| | | lowerPath = baseGetSegmentPath.call(this, lowSegment);
|
| | | if (step) {
|
| | | if (step === true) {
|
| | | step = 'left';
|
| | | }
|
| | | options.step = { left: 'right', center: 'center', right: 'left' }[step]; // swap for reading in getSegmentPath
|
| | | }
|
| | | higherPath = baseGetSegmentPath.call(this, highSegment);
|
| | | 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'
|
| | | higherPath[0] = 'L'; // this probably doesn't work for spline |
| | | this.areaPath = this.areaPath.concat(lowerPath, higherPath);
|
| | | |
| | | return linePath;
|
| | | },
|
| | | |
| | | /**
|
| | | * Extend the basic drawDataLabels method by running it for both lower and higher
|
| | | * values.
|
| | | */
|
| | | drawDataLabels: function () {
|
| | | |
| | | var data = this.data,
|
| | | length = data.length,
|
| | | i,
|
| | | originalDataLabels = [],
|
| | | seriesProto = Series.prototype,
|
| | | dataLabelOptions = this.options.dataLabels,
|
| | | point,
|
| | | inverted = this.chart.inverted;
|
| | | |
| | | if (dataLabelOptions.enabled || this._hasPointLabels) {
|
| | | |
| | | // Step 1: set preliminary values for plotY and dataLabel and draw the upper labels
|
| | | i = length;
|
| | | while (i--) {
|
| | | point = data[i];
|
| | | |
| | | // Set preliminary values
|
| | | point.y = point.high;
|
| | | 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 = false;
|
| | | if (inverted) {
|
| | | dataLabelOptions.align = 'left';
|
| | | dataLabelOptions.x = dataLabelOptions.xHigh; |
| | | } else {
|
| | | dataLabelOptions.y = dataLabelOptions.yHigh;
|
| | | }
|
| | | }
|
| | | seriesProto.drawDataLabels.apply(this, arguments); // #1209
|
| | | |
| | | // Step 2: reorganize and handle data labels for the lower values
|
| | | i = length;
|
| | | while (i--) {
|
| | | point = data[i];
|
| | | |
| | | // Move the generated labels from step 1, and reassign the original data labels
|
| | | point.dataLabelUpper = point.dataLabel;
|
| | | point.dataLabel = originalDataLabels[i];
|
| | | |
| | | // Reset values
|
| | | point.y = point.low;
|
| | | point.plotY = point.plotLow;
|
| | | |
| | | // Set the default offset
|
| | | point.below = true;
|
| | | if (inverted) {
|
| | | dataLabelOptions.align = 'right';
|
| | | dataLabelOptions.x = dataLabelOptions.xLow;
|
| | | } else {
|
| | | dataLabelOptions.y = dataLabelOptions.yLow;
|
| | | }
|
| | | }
|
| | | seriesProto.drawDataLabels.apply(this, arguments);
|
| | | }
|
| | | |
| | | },
|
| | | |
| | | alignDataLabel: seriesTypes.column.prototype.alignDataLabel,
|
| | | |
| | | getSymbol: seriesTypes.column.prototype.getSymbol,
|
| | | |
| | | drawPoints: noop
|
| | | });/**
|
| | | * The AreaSplineRangeSeries class
|
| | | */
|
| | |
|
| | | defaultPlotOptions.areasplinerange = merge(defaultPlotOptions.arearange);
|
| | |
|
| | | /**
|
| | | * AreaSplineRangeSeries object
|
| | | */
|
| | | seriesTypes.areasplinerange = extendClass(seriesTypes.arearange, {
|
| | | type: 'areasplinerange',
|
| | | getPointSpline: seriesTypes.spline.prototype.getPointSpline
|
| | | });/**
|
| | | * The ColumnRangeSeries class
|
| | | */
|
| | | defaultPlotOptions.columnrange = merge(defaultPlotOptions.column, defaultPlotOptions.arearange, {
|
| | | lineWidth: 1,
|
| | | pointRange: null
|
| | | });
|
| | |
|
| | | /**
|
| | | * ColumnRangeSeries object
|
| | | */
|
| | | seriesTypes.columnrange = extendClass(seriesTypes.arearange, {
|
| | | type: 'columnrange',
|
| | | /**
|
| | | * Translate data points from raw values x and y to plotX and plotY
|
| | | */
|
| | | translate: function () {
|
| | | var series = this,
|
| | | yAxis = series.yAxis,
|
| | | plotHigh;
|
| | |
|
| | | colProto.translate.apply(series);
|
| | |
|
| | | // Set plotLow and plotHigh
|
| | | each(series.points, function (point) {
|
| | | var shapeArgs = point.shapeArgs,
|
| | | minPointLength = series.options.minPointLength,
|
| | | heightDifference,
|
| | | height,
|
| | | y;
|
| | |
|
| | | point.plotHigh = plotHigh = yAxis.translate(point.high, 0, 1, 0, 1);
|
| | | point.plotLow = point.plotY;
|
| | |
|
| | | // adjust shape
|
| | | y = plotHigh;
|
| | | height = point.plotY - plotHigh;
|
| | |
|
| | | if (height < minPointLength) {
|
| | | heightDifference = (minPointLength - height);
|
| | | height += heightDifference;
|
| | | y -= heightDifference / 2;
|
| | | }
|
| | | shapeArgs.height = height;
|
| | | shapeArgs.y = y;
|
| | | });
|
| | | },
|
| | | trackerGroups: ['group', 'dataLabels'],
|
| | | drawGraph: noop,
|
| | | pointAttrToOptions: colProto.pointAttrToOptions,
|
| | | drawPoints: colProto.drawPoints,
|
| | | drawTracker: colProto.drawTracker,
|
| | | animate: colProto.animate,
|
| | | getColumnMetrics: colProto.getColumnMetrics
|
| | | });
|
| | | /* |
| | | * The GaugeSeries class
|
| | | */
|
| | |
|
| | |
|
| | |
|
| | | /**
|
| | | * Extend the default options
|
| | | */
|
| | | defaultPlotOptions.gauge = merge(defaultPlotOptions.line, {
|
| | | dataLabels: {
|
| | | enabled: true,
|
| | | y: 15,
|
| | | borderWidth: 1,
|
| | | borderColor: 'silver',
|
| | | borderRadius: 3,
|
| | | style: {
|
| | | fontWeight: 'bold'
|
| | | },
|
| | | verticalAlign: 'top',
|
| | | zIndex: 2
|
| | | },
|
| | | dial: {
|
| | | // radius: '80%',
|
| | | // backgroundColor: 'black',
|
| | | // borderColor: 'silver',
|
| | | // borderWidth: 0,
|
| | | // baseWidth: 3,
|
| | | // topWidth: 1,
|
| | | // baseLength: '70%' // of radius
|
| | | // rearLength: '10%'
|
| | | },
|
| | | pivot: {
|
| | | //radius: 5,
|
| | | //borderWidth: 0
|
| | | //borderColor: 'silver',
|
| | | //backgroundColor: 'black'
|
| | | },
|
| | | tooltip: {
|
| | | headerFormat: ''
|
| | | },
|
| | | showInLegend: false
|
| | | });
|
| | |
|
| | | /**
|
| | | * Extend the point object
|
| | | */
|
| | | var GaugePoint = Highcharts.extendClass(Highcharts.Point, {
|
| | | /**
|
| | | * Don't do any hover colors or anything
|
| | | */
|
| | | setState: function (state) {
|
| | | this.state = state;
|
| | | }
|
| | | });
|
| | |
|
| | |
|
| | | /**
|
| | | * Add the series type
|
| | | */
|
| | | var GaugeSeries = {
|
| | | type: 'gauge',
|
| | | pointClass: GaugePoint,
|
| | | |
| | | // chart.angular will be set to true when a gauge series is present, and this will
|
| | | // be used on the axes
|
| | | angular: true, |
| | | drawGraph: noop,
|
| | | fixedBox: true,
|
| | | trackerGroups: ['group', 'dataLabels'],
|
| | | |
| | | /**
|
| | | * Calculate paths etc
|
| | | */
|
| | | translate: function () {
|
| | | |
| | | var series = this,
|
| | | yAxis = series.yAxis,
|
| | | options = series.options,
|
| | | center = yAxis.center;
|
| | | |
| | | series.generatePoints();
|
| | | |
| | | each(series.points, 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,
|
| | | rotation = yAxis.startAngleRad + yAxis.translate(point.y, null, null, null, true);
|
| | |
|
| | | // Handle the wrap option
|
| | | 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,
|
| | | center = series.yAxis.center,
|
| | | pivot = series.pivot,
|
| | | options = series.options,
|
| | | pivotOptions = options.pivot,
|
| | | renderer = series.chart.renderer;
|
| | | |
| | | each(series.points, 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({
|
| | | stroke: dialOptions.borderColor || 'none',
|
| | | 'stroke-width': dialOptions.borderWidth || 0,
|
| | | fill: dialOptions.backgroundColor || 'black',
|
| | | rotation: shapeArgs.rotation // required by VML when animation is false
|
| | | })
|
| | | .add(series.group);
|
| | | }
|
| | | });
|
| | | |
| | | // 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({
|
| | | 'stroke-width': pivotOptions.borderWidth || 0,
|
| | | stroke: pivotOptions.borderColor || 'silver',
|
| | | fill: pivotOptions.backgroundColor || 'black'
|
| | | })
|
| | | .translate(center[0], center[1])
|
| | | .add(series.group);
|
| | | }
|
| | | },
|
| | | |
| | | /**
|
| | | * Animate the arrow up from startAngle
|
| | | */
|
| | | animate: function (init) {
|
| | | var series = this;
|
| | |
|
| | | if (!init) {
|
| | | each(series.points, 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
|
| | | );
|
| | | seriesTypes.pie.prototype.render.call(this);
|
| | | this.group.clip(this.chart.clipRect);
|
| | | },
|
| | | |
| | | setData: seriesTypes.pie.prototype.setData,
|
| | | drawTracker: seriesTypes.column.prototype.drawTracker
|
| | | };
|
| | | seriesTypes.gauge = Highcharts.extendClass(seriesTypes.line, GaugeSeries);/* ****************************************************************************
|
| | | * Start Box plot series code *
|
| | | *****************************************************************************/
|
| | |
|
| | | // Set default options
|
| | | defaultPlotOptions.boxplot = merge(defaultPlotOptions.column, {
|
| | | fillColor: '#FFFFFF',
|
| | | lineWidth: 1,
|
| | | //medianColor: null,
|
| | | medianWidth: 2,
|
| | | states: {
|
| | | hover: {
|
| | | brightness: -0.3
|
| | | }
|
| | | },
|
| | | //stemColor: null,
|
| | | //stemDashStyle: 'solid'
|
| | | //stemWidth: null,
|
| | | threshold: null,
|
| | | tooltip: {
|
| | | pointFormat: '<span style="color:{series.color};font-weight:bold">{series.name}</span><br/>' +
|
| | | 'Maximum: {point.high}<br/>' +
|
| | | 'Upper quartile: {point.q3}<br/>' +
|
| | | 'Median: {point.median}<br/>' +
|
| | | 'Lower quartile: {point.q1}<br/>' +
|
| | | 'Minimum: {point.low}<br/>'
|
| | | |
| | | },
|
| | | //whiskerColor: null,
|
| | | whiskerLength: '50%',
|
| | | whiskerWidth: 2
|
| | | });
|
| | |
|
| | | // Create the series object
|
| | | seriesTypes.boxplot = extendClass(seriesTypes.column, {
|
| | | type: 'boxplot',
|
| | | pointArrayMap: ['low', 'q1', 'median', 'q3', 'high'], // array point configs are mapped to this
|
| | | toYData: function (point) { // return a plain array for speedy calculation
|
| | | return [point.low, point.q1, point.median, point.q3, point.high];
|
| | | },
|
| | | pointValKey: 'high', // defines the top of the tracker
|
| | | |
| | | /**
|
| | | * One-to-one mapping from options to SVG attributes
|
| | | */
|
| | | pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
|
| | | fill: 'fillColor',
|
| | | stroke: 'color',
|
| | | 'stroke-width': 'lineWidth'
|
| | | },
|
| | | |
| | | /**
|
| | | * 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
|
| | | each(series.points, function (point) {
|
| | | each(pointArrayMap, 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, //state = series.state,
|
| | | points = series.points,
|
| | | options = series.options,
|
| | | chart = series.chart,
|
| | | renderer = chart.renderer,
|
| | | pointAttr,
|
| | | q1Plot,
|
| | | q3Plot,
|
| | | highPlot,
|
| | | lowPlot,
|
| | | medianPlot,
|
| | | crispCorr,
|
| | | crispX,
|
| | | graphic,
|
| | | stemPath,
|
| | | stemAttr,
|
| | | boxPath,
|
| | | whiskersPath,
|
| | | whiskersAttr,
|
| | | medianPath,
|
| | | medianAttr,
|
| | | width,
|
| | | left,
|
| | | right,
|
| | | halfWidth,
|
| | | shapeArgs,
|
| | | color,
|
| | | doQuartiles = series.doQuartiles !== false, // error bar inherits this series type but doesn't do quartiles
|
| | | whiskerLength = parseInt(series.options.whiskerLength, 10) / 100;
|
| | |
|
| | |
|
| | | each(points, function (point) {
|
| | |
|
| | | graphic = point.graphic;
|
| | | shapeArgs = point.shapeArgs; // the box
|
| | | stemAttr = {};
|
| | | whiskersAttr = {};
|
| | | medianAttr = {};
|
| | | color = point.color || series.color;
|
| | | |
| | | if (point.plotY !== UNDEFINED) {
|
| | |
|
| | | pointAttr = point.pointAttr[point.selected ? 'selected' : ''];
|
| | |
|
| | | // crisp vector coordinates
|
| | | width = shapeArgs.width;
|
| | | left = mathFloor(shapeArgs.x);
|
| | | right = left + width;
|
| | | halfWidth = mathRound(width / 2);
|
| | | //crispX = mathRound(left + halfWidth) + crispCorr;
|
| | | q1Plot = mathFloor(doQuartiles ? point.q1Plot : point.lowPlot);// + crispCorr;
|
| | | q3Plot = mathFloor(doQuartiles ? point.q3Plot : point.lowPlot);// + crispCorr;
|
| | | highPlot = mathFloor(point.highPlot);// + crispCorr;
|
| | | lowPlot = mathFloor(point.lowPlot);// + crispCorr;
|
| | | |
| | | // 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;
|
| | | |
| | | // Whiskers attributes
|
| | | whiskersAttr.stroke = point.whiskerColor || options.whiskerColor || color;
|
| | | whiskersAttr['stroke-width'] = pick(point.whiskerWidth, options.whiskerWidth, options.lineWidth);
|
| | | |
| | | // Median attributes
|
| | | medianAttr.stroke = point.medianColor || options.medianColor || color;
|
| | | medianAttr['stroke-width'] = pick(point.medianWidth, options.medianWidth, options.lineWidth);
|
| | | |
| | | |
| | | // The stem
|
| | | crispCorr = (stemAttr['stroke-width'] % 2) / 2;
|
| | | crispX = left + halfWidth + crispCorr; |
| | | stemPath = [
|
| | | // stem up
|
| | | 'M',
|
| | | crispX, q3Plot,
|
| | | 'L',
|
| | | crispX, highPlot,
|
| | | |
| | | // stem down
|
| | | 'M',
|
| | | crispX, q1Plot,
|
| | | 'L',
|
| | | crispX, lowPlot,
|
| | | 'z'
|
| | | ];
|
| | | |
| | | // The box
|
| | | if (doQuartiles) {
|
| | | crispCorr = (pointAttr['stroke-width'] % 2) / 2;
|
| | | crispX = mathFloor(crispX) + crispCorr;
|
| | | q1Plot = mathFloor(q1Plot) + crispCorr;
|
| | | q3Plot = mathFloor(q3Plot) + crispCorr;
|
| | | left += crispCorr;
|
| | | right += crispCorr;
|
| | | boxPath = [
|
| | | 'M',
|
| | | left, q3Plot,
|
| | | 'L',
|
| | | left, q1Plot,
|
| | | 'L',
|
| | | right, q1Plot,
|
| | | 'L',
|
| | | right, q3Plot,
|
| | | 'L',
|
| | | left, q3Plot,
|
| | | 'z'
|
| | | ];
|
| | | }
|
| | | |
| | | // The whiskers
|
| | | if (whiskerLength) {
|
| | | crispCorr = (whiskersAttr['stroke-width'] % 2) / 2;
|
| | | highPlot = highPlot + crispCorr;
|
| | | lowPlot = lowPlot + crispCorr;
|
| | | whiskersPath = [
|
| | | // High whisker
|
| | | 'M',
|
| | | crispX - halfWidth * whiskerLength, |
| | | highPlot,
|
| | | 'L',
|
| | | crispX + halfWidth * whiskerLength, |
| | | highPlot,
|
| | | |
| | | // Low whisker
|
| | | 'M',
|
| | | crispX - halfWidth * whiskerLength, |
| | | lowPlot,
|
| | | 'L',
|
| | | crispX + halfWidth * whiskerLength, |
| | | lowPlot
|
| | | ];
|
| | | }
|
| | | |
| | | // The median
|
| | | crispCorr = (medianAttr['stroke-width'] % 2) / 2; |
| | | medianPlot = mathRound(point.medianPlot) + crispCorr;
|
| | | medianPath = [
|
| | | 'M',
|
| | | left, |
| | | medianPlot,
|
| | | 'L',
|
| | | right, |
| | | medianPlot,
|
| | | 'z'
|
| | | ];
|
| | | |
| | | // Create or update the graphics
|
| | | if (graphic) { // update
|
| | | |
| | | point.stem.animate({ d: stemPath });
|
| | | if (whiskerLength) {
|
| | | point.whiskers.animate({ d: whiskersPath });
|
| | | }
|
| | | if (doQuartiles) {
|
| | | point.box.animate({ d: boxPath });
|
| | | }
|
| | | point.medianShape.animate({ d: medianPath });
|
| | | |
| | | } else { // create new
|
| | | point.graphic = graphic = renderer.g()
|
| | | .add(series.group);
|
| | | |
| | | point.stem = renderer.path(stemPath)
|
| | | .attr(stemAttr)
|
| | | .add(graphic);
|
| | | |
| | | if (whiskerLength) {
|
| | | point.whiskers = renderer.path(whiskersPath) |
| | | .attr(whiskersAttr)
|
| | | .add(graphic);
|
| | | }
|
| | | if (doQuartiles) {
|
| | | point.box = renderer.path(boxPath)
|
| | | .attr(pointAttr)
|
| | | .add(graphic);
|
| | | } |
| | | point.medianShape = renderer.path(medianPath)
|
| | | .attr(medianAttr)
|
| | | .add(graphic);
|
| | | }
|
| | | }
|
| | | });
|
| | |
|
| | | }
|
| | |
|
| | |
|
| | | });
|
| | |
|
| | | /* ****************************************************************************
|
| | | * End Box plot series code *
|
| | | *****************************************************************************/
|
| | | /* ****************************************************************************
|
| | | * Start error bar series code *
|
| | | *****************************************************************************/
|
| | |
|
| | | // 1 - set default options
|
| | | defaultPlotOptions.errorbar = merge(defaultPlotOptions.boxplot, {
|
| | | color: '#000000',
|
| | | grouping: false,
|
| | | linkedTo: ':previous',
|
| | | tooltip: {
|
| | | pointFormat: defaultPlotOptions.arearange.tooltip.pointFormat
|
| | | },
|
| | | whiskerWidth: null
|
| | | });
|
| | |
|
| | | // 2 - Create the series object
|
| | | seriesTypes.errorbar = extendClass(seriesTypes.boxplot, {
|
| | | 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,
|
| | |
|
| | | /**
|
| | | * 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);
|
| | | }
|
| | | });
|
| | |
|
| | | /* ****************************************************************************
|
| | | * End error bar series code *
|
| | | *****************************************************************************/
|
| | | /* ****************************************************************************
|
| | | * Start Waterfall series code *
|
| | | *****************************************************************************/
|
| | |
|
| | | // 1 - set default options
|
| | | defaultPlotOptions.waterfall = merge(defaultPlotOptions.column, {
|
| | | lineWidth: 1,
|
| | | lineColor: '#333',
|
| | | dashStyle: 'dot',
|
| | | borderColor: '#333'
|
| | | });
|
| | |
|
| | |
|
| | | // 2 - Create the series object
|
| | | seriesTypes.waterfall = extendClass(seriesTypes.column, {
|
| | | type: 'waterfall',
|
| | |
|
| | | upColorProp: 'fill',
|
| | |
|
| | | pointArrayMap: ['low', 'y'],
|
| | |
|
| | | pointValKey: 'y',
|
| | |
|
| | | /**
|
| | | * Init waterfall series, force stacking
|
| | | */
|
| | | init: function (chart, options) {
|
| | | // force stacking
|
| | | options.stacking = true;
|
| | |
|
| | | seriesTypes.column.prototype.init.call(this, chart, options);
|
| | | },
|
| | |
|
| | |
|
| | | /**
|
| | | * Translate data points from raw values
|
| | | */
|
| | | translate: function () {
|
| | | var series = this,
|
| | | options = series.options,
|
| | | axis = series.yAxis,
|
| | | len,
|
| | | i,
|
| | | points,
|
| | | point,
|
| | | shapeArgs,
|
| | | stack,
|
| | | y,
|
| | | previousY,
|
| | | stackPoint,
|
| | | threshold = options.threshold,
|
| | | crispCorr = (options.borderWidth % 2) / 2;
|
| | |
|
| | | // run column series translate
|
| | | seriesTypes.column.prototype.translate.apply(this);
|
| | |
|
| | | previousY = threshold;
|
| | | points = series.points;
|
| | |
|
| | | for (i = 0, len = points.length; i < len; i++) {
|
| | | // cache current point object
|
| | | point = points[i];
|
| | | shapeArgs = point.shapeArgs;
|
| | |
|
| | | // get current stack
|
| | | stack = series.getStack(i);
|
| | | stackPoint = stack.points[series.index];
|
| | |
|
| | | // override point value for sums
|
| | | if (isNaN(point.y)) {
|
| | | point.y = series.yData[i];
|
| | | }
|
| | |
|
| | | // up points
|
| | | y = mathMax(previousY, previousY + point.y) + stackPoint[0];
|
| | | shapeArgs.y = axis.translate(y, 0, 1);
|
| | |
|
| | |
|
| | | // sum points
|
| | | if (point.isSum || point.isIntermediateSum) {
|
| | | shapeArgs.y = axis.translate(stackPoint[1], 0, 1);
|
| | | shapeArgs.height = axis.translate(stackPoint[0], 0, 1) - shapeArgs.y;
|
| | |
|
| | | // if it's not the sum point, update previous stack end position
|
| | | } else {
|
| | | previousY += stack.total;
|
| | | }
|
| | |
|
| | | // negative points
|
| | | if (shapeArgs.height < 0) {
|
| | | shapeArgs.y += shapeArgs.height;
|
| | | shapeArgs.height *= -1;
|
| | | }
|
| | |
|
| | | point.plotY = shapeArgs.y = mathRound(shapeArgs.y) - crispCorr;
|
| | | shapeArgs.height = mathRound(shapeArgs.height);
|
| | | point.yBottom = shapeArgs.y + shapeArgs.height;
|
| | | }
|
| | | },
|
| | |
|
| | | /**
|
| | | * Call default processData then override yData to reflect waterfall's extremes on yAxis
|
| | | */
|
| | | processData: function (force) {
|
| | | var series = this,
|
| | | options = series.options,
|
| | | yData = series.yData,
|
| | | points = series.points,
|
| | | 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] = sum;
|
| | | } else if (y === "intermediateSum" || point.isIntermediateSum) {
|
| | | yData[i] = subSum;
|
| | | subSum = threshold;
|
| | | } else {
|
| | | sum += y;
|
| | | subSum += y;
|
| | | }
|
| | | dataMin = Math.min(sum, dataMin);
|
| | | dataMax = Math.max(sum, dataMax);
|
| | | }
|
| | |
|
| | | Series.prototype.processData.call(this, force);
|
| | |
|
| | | // Record extremes
|
| | | series.dataMin = dataMin;
|
| | | series.dataMax = dataMax;
|
| | | },
|
| | |
|
| | | /**
|
| | | * Return y value or string if point is sum
|
| | | */
|
| | | toYData: function (pt) {
|
| | | if (pt.isSum) {
|
| | | return "sum";
|
| | | } else if (pt.isIntermediateSum) {
|
| | | return "intermediateSum";
|
| | | }
|
| | |
|
| | | return pt.y;
|
| | | },
|
| | |
|
| | | /**
|
| | | * Postprocess mapping between options and SVG attributes
|
| | | */
|
| | | getAttribs: function () {
|
| | | seriesTypes.column.prototype.getAttribs.apply(this, arguments);
|
| | |
|
| | | var series = this,
|
| | | options = series.options,
|
| | | stateOptions = options.states,
|
| | | upColor = options.upColor || series.color,
|
| | | hoverColor = Highcharts.Color(upColor).brighten(0.1).get(),
|
| | | seriesDownPointAttr = merge(series.pointAttr),
|
| | | upColorProp = series.upColorProp;
|
| | |
|
| | | seriesDownPointAttr[''][upColorProp] = upColor;
|
| | | seriesDownPointAttr.hover[upColorProp] = stateOptions.hover.upColor || hoverColor;
|
| | | seriesDownPointAttr.select[upColorProp] = stateOptions.select.upColor || upColor;
|
| | |
|
| | | each(series.points, function (point) {
|
| | | if (point.y > 0 && !point.color) {
|
| | | point.pointAttr = seriesDownPointAttr;
|
| | | point.color = upColor;
|
| | | }
|
| | | });
|
| | | },
|
| | |
|
| | | /**
|
| | | * Draw columns' connector lines
|
| | | */
|
| | | getGraphPath: function () {
|
| | |
|
| | | var data = this.data,
|
| | | length = data.length,
|
| | | lineWidth = this.options.lineWidth + this.options.borderWidth,
|
| | | normalizer = mathRound(lineWidth) % 2 / 2,
|
| | | path = [],
|
| | | M = 'M',
|
| | | L = 'L',
|
| | | prevArgs,
|
| | | pointArgs,
|
| | | i,
|
| | | d;
|
| | |
|
| | | for (i = 1; i < length; i++) {
|
| | | pointArgs = data[i].shapeArgs;
|
| | | prevArgs = data[i - 1].shapeArgs;
|
| | |
|
| | | d = [
|
| | | M,
|
| | | prevArgs.x + prevArgs.width, prevArgs.y + normalizer,
|
| | | L,
|
| | | pointArgs.x, prevArgs.y + normalizer
|
| | | ];
|
| | |
|
| | | if (data[i - 1].y < 0) {
|
| | | d[2] += prevArgs.height;
|
| | | d[5] += prevArgs.height;
|
| | | }
|
| | |
|
| | | path = path.concat(d);
|
| | | }
|
| | |
|
| | | return path;
|
| | | },
|
| | |
|
| | | /**
|
| | | * Extremes are recorded in processData
|
| | | */
|
| | | getExtremes: noop,
|
| | |
|
| | | /**
|
| | | * Return stack for given index
|
| | | */
|
| | | getStack: function (i) {
|
| | | var axis = this.yAxis,
|
| | | stacks = axis.stacks,
|
| | | key = this.stackKey;
|
| | |
|
| | | if (this.processedYData[i] < this.options.threshold) {
|
| | | key = '-' + key;
|
| | | }
|
| | |
|
| | | return stacks[key][i];
|
| | | },
|
| | |
|
| | | drawGraph: Series.prototype.drawGraph
|
| | | });
|
| | |
|
| | | /* ****************************************************************************
|
| | | * End Waterfall series code *
|
| | | *****************************************************************************/
|
| | | /* ****************************************************************************
|
| | | * Start Bubble series code *
|
| | | *****************************************************************************/
|
| | |
|
| | | // 1 - set default options
|
| | | defaultPlotOptions.bubble = merge(defaultPlotOptions.scatter, {
|
| | | dataLabels: {
|
| | | inside: true,
|
| | | style: {
|
| | | color: 'white',
|
| | | textShadow: '0px 0px 3px black'
|
| | | },
|
| | | verticalAlign: 'middle'
|
| | | },
|
| | | // displayNegative: true,
|
| | | marker: {
|
| | | // fillOpacity: 0.5,
|
| | | lineColor: null, // inherit from series.color
|
| | | lineWidth: 1
|
| | | },
|
| | | minSize: 8,
|
| | | maxSize: '20%',
|
| | | // negativeColor: null,
|
| | | tooltip: {
|
| | | pointFormat: '({point.x}, {point.y}), Size: {point.z}'
|
| | | },
|
| | | turboThreshold: 0,
|
| | | zThreshold: 0
|
| | | });
|
| | |
|
| | | // 2 - Create the series object
|
| | | seriesTypes.bubble = extendClass(seriesTypes.scatter, {
|
| | | type: 'bubble',
|
| | | pointArrayMap: ['y', 'z'],
|
| | | trackerGroups: ['group', 'dataLabelsGroup'],
|
| | | |
| | | /**
|
| | | * Mapping between SVG attributes and the corresponding options
|
| | | */
|
| | | pointAttrToOptions: { |
| | | stroke: 'lineColor',
|
| | | 'stroke-width': 'lineWidth',
|
| | | fill: 'fillColor'
|
| | | },
|
| | | |
| | | /**
|
| | | * Apply the fillOpacity to all fill positions
|
| | | */
|
| | | applyOpacity: function (fill) {
|
| | | var markerOptions = this.options.marker,
|
| | | fillOpacity = pick(markerOptions.fillOpacity, 0.5);
|
| | | |
| | | // When called from Legend.colorizeItem, the fill isn't predefined
|
| | | fill = fill || markerOptions.fillColor || this.color; |
| | | |
| | | if (fillOpacity !== 1) {
|
| | | fill = Highcharts.Color(fill).setOpacity(fillOpacity).get('rgba');
|
| | | }
|
| | | return fill;
|
| | | },
|
| | | |
| | | /**
|
| | | * Extend the convertAttribs method by applying opacity to the fill
|
| | | */
|
| | | convertAttribs: function () {
|
| | | var obj = Series.prototype.convertAttribs.apply(this, arguments);
|
| | | |
| | | obj.fill = this.applyOpacity(obj.fill);
|
| | | |
| | | return obj;
|
| | | },
|
| | |
|
| | | /**
|
| | | * Get the radius for each point based on the minSize, maxSize and each point's Z value. This
|
| | | * must be done prior to Series.translate because the axis needs to add padding in |
| | | * accordance with the point sizes.
|
| | | */
|
| | | getRadii: function (zMin, zMax, minSize, maxSize) {
|
| | | var len,
|
| | | i,
|
| | | pos,
|
| | | zData = this.zData,
|
| | | radii = [],
|
| | | zRange;
|
| | | |
| | | // Set the shape type and arguments to be picked up in drawPoints
|
| | | for (i = 0, len = zData.length; i < len; i++) {
|
| | | zRange = zMax - zMin;
|
| | | pos = zRange > 0 ? // relative size, a number between 0 and 1
|
| | | (zData[i] - zMin) / (zMax - zMin) : |
| | | 0.5;
|
| | | radii.push(math.ceil(minSize + pos * (maxSize - minSize)) / 2);
|
| | | }
|
| | | this.radii = radii;
|
| | | },
|
| | | |
| | | /**
|
| | | * Perform animation on the bubbles
|
| | | */
|
| | | animate: function (init) {
|
| | | var animation = this.options.animation;
|
| | | |
| | | if (!init) { // run the animation
|
| | | each(this.points, function (point) {
|
| | | var graphic = point.graphic,
|
| | | shapeArgs = point.shapeArgs;
|
| | |
|
| | | if (graphic && shapeArgs) {
|
| | | // start values
|
| | | graphic.attr('r', 1);
|
| | |
|
| | | // animate
|
| | | graphic.animate({
|
| | | r: shapeArgs.r
|
| | | }, animation);
|
| | | }
|
| | | });
|
| | |
|
| | | // 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
|
| | |
|
| | | // Flag for negativeColor to be applied in Series.js
|
| | | point.negative = point.z < (this.options.zThreshold || 0);
|
| | | |
| | | if (radius >= this.minPxSize / 2) {
|
| | | // Shape arguments
|
| | | point.shapeType = 'circle';
|
| | | point.shapeArgs = {
|
| | | x: point.plotX,
|
| | | y: point.plotY,
|
| | | r: 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
|
| | | point.shapeArgs = point.plotY = point.dlBox = UNDEFINED; // #1691
|
| | | }
|
| | | }
|
| | | },
|
| | | |
| | | /**
|
| | | * Get the series' symbol in the legend
|
| | | * |
| | | * @param {Object} legend The legend object
|
| | | * @param {Object} item The series (this) or point
|
| | | */
|
| | | drawLegendSymbol: function (legend, item) {
|
| | | var radius = pInt(legend.itemStyle.fontSize) / 2;
|
| | | |
| | | item.legendSymbol = this.chart.renderer.circle(
|
| | | radius,
|
| | | legend.baseline - radius,
|
| | | radius
|
| | | ).attr({
|
| | | zIndex: 3
|
| | | }).add(item.legendGroup);
|
| | | item.legendSymbol.isMarker = true; |
| | | |
| | | },
|
| | | |
| | | drawPoints: seriesTypes.column.prototype.drawPoints,
|
| | | alignDataLabel: seriesTypes.column.prototype.alignDataLabel
|
| | | });
|
| | |
|
| | | /**
|
| | | * 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
|
| | | if (this.tickPositions) {
|
| | | each(this.series, function (series) {
|
| | |
|
| | | var seriesOptions = series.options,
|
| | | zData;
|
| | |
|
| | | if (series.type === 'bubble' && series.visible) {
|
| | |
|
| | | // 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
|
| | | each(['minSize', 'maxSize'], function (prop) {
|
| | | var length = seriesOptions[prop],
|
| | | isPercent = /%$/.test(length);
|
| | | |
| | | length = pInt(length);
|
| | | extremes[prop] = isPercent ?
|
| | | smallestSize * length / 100 :
|
| | | length;
|
| | | |
| | | });
|
| | | series.minPxSize = extremes.minSize;
|
| | | |
| | | // Find the min and max Z
|
| | | zData = series.zData;
|
| | | if (zData.length) { // #1735
|
| | | zMin = math.min(
|
| | | zMin,
|
| | | math.max(
|
| | | arrayMin(zData), |
| | | seriesOptions.displayNegative === false ? seriesOptions.zThreshold : -Number.MAX_VALUE
|
| | | )
|
| | | );
|
| | | zMax = math.max(zMax, arrayMax(zData));
|
| | | }
|
| | | }
|
| | | }
|
| | | });
|
| | |
|
| | | each(activeSeries, function (series) {
|
| | |
|
| | | var data = series[dataKey],
|
| | | i = data.length,
|
| | | radius;
|
| | |
|
| | | if (isXAxis) {
|
| | | series.getRadii(zMin, zMax, extremes.minSize, extremes.maxSize);
|
| | | }
|
| | | |
| | | if (range > 0) {
|
| | | while (i--) {
|
| | | radius = series.radii[i];
|
| | | pxMin = Math.min(((data[i] - min) * transA) - radius, pxMin);
|
| | | pxMax = Math.max(((data[i] - min) * transA) + radius, pxMax);
|
| | | }
|
| | | }
|
| | | });
|
| | | |
| | | if (activeSeries.length && range > 0 && pick(this.options.min, this.userMin) === UNDEFINED && pick(this.options.max, this.userMax) === UNDEFINED) {
|
| | | pxMax -= axisLength;
|
| | | transA *= (axisLength + pxMin - pxMax) / axisLength;
|
| | | this.min += pxMin / transA;
|
| | | this.max += pxMax / transA;
|
| | | }
|
| | | }
|
| | | };
|
| | |
|
| | | /* ****************************************************************************
|
| | | * End Bubble series code *
|
| | | *****************************************************************************/
|
| | | /**
|
| | | * Extensions for polar charts. Additionally, much of the geometry required for polar charts is
|
| | | * gathered in RadialAxes.js.
|
| | | * |
| | | */
|
| | |
|
| | | var seriesProto = Series.prototype,
|
| | | pointerProto = Highcharts.Pointer.prototype;
|
| | |
|
| | |
|
| | |
|
| | | /**
|
| | | * 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;
|
| | | |
| | | // Save rectangular plotX, plotY for later computation
|
| | | point.rectPlotX = plotX;
|
| | | point.rectPlotY = plotY;
|
| | | |
| | | // Record the angle in degrees for use in tooltip
|
| | | point.clientX = ((plotX / Math.PI * 180) + this.xAxis.pane.options.startAngle) % 360;
|
| | | |
| | | // 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;
|
| | | };
|
| | |
|
| | | /** |
| | | * Order the tooltip points to get the mouse capture ranges correct. #1915. |
| | | */
|
| | | seriesProto.orderTooltipPoints = function (points) {
|
| | | if (this.chart.polar) {
|
| | | points.sort(function (a, b) {
|
| | | return a.clientX - b.clientX;
|
| | | });
|
| | |
|
| | | // Wrap mouse tracking around to capture movement on the segment to the left
|
| | | // of the north point (#1469, #2093).
|
| | | if (points[0]) {
|
| | | points[0].wrappedClientX = points[0].clientX + 360;
|
| | | points.push(points[0]);
|
| | | }
|
| | | }
|
| | | };
|
| | |
|
| | |
|
| | | /**
|
| | | * Add some special init logic to areas and areasplines
|
| | | */
|
| | | function initArea(proceed, chart, options) {
|
| | | proceed.call(this, chart, options);
|
| | | if (this.chart.polar) {
|
| | | |
| | | /**
|
| | | * Overridden method to close a segment path. While in a cartesian plane the area |
| | | * goes down to the threshold, in the polar chart it goes to the center.
|
| | | */
|
| | | this.closeSegment = function (path) {
|
| | | var center = this.xAxis.center;
|
| | | path.push(
|
| | | 'L',
|
| | | center[0],
|
| | | center[1]
|
| | | ); |
| | | };
|
| | | |
| | | // Instead of complicated logic to draw an area around the inner area in a stack,
|
| | | // just draw it behind
|
| | | this.closedStacks = true;
|
| | | }
|
| | | }
|
| | | wrap(seriesTypes.area.prototype, 'init', initArea);
|
| | | wrap(seriesTypes.areaspline.prototype, 'init', initArea);
|
| | | |
| | |
|
| | | /**
|
| | | * Overridden method for calculating a spline from one point to the next
|
| | | */
|
| | | wrap(seriesTypes.spline.prototype, 'getPointSpline', function (proceed, segment, point, i) {
|
| | | |
| | | var ret,
|
| | | smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc;
|
| | | denom = smoothing + 1,
|
| | | plotX, |
| | | plotY,
|
| | | lastPoint,
|
| | | nextPoint,
|
| | | lastX,
|
| | | lastY,
|
| | | nextX,
|
| | | nextY,
|
| | | leftContX,
|
| | | leftContY,
|
| | | rightContX,
|
| | | rightContY,
|
| | | distanceLeftControlPoint,
|
| | | distanceRightControlPoint,
|
| | | leftContAngle,
|
| | | rightContAngle,
|
| | | jointAngle;
|
| | | |
| | | |
| | | if (this.chart.polar) {
|
| | | |
| | | plotX = point.plotX;
|
| | | plotY = point.plotY;
|
| | | lastPoint = segment[i - 1];
|
| | | nextPoint = segment[i + 1];
|
| | | |
| | | // Connect ends
|
| | | if (this.connectEnds) {
|
| | | if (!lastPoint) {
|
| | | lastPoint = segment[segment.length - 2]; // not the last but the second last, because the segment is already connected
|
| | | }
|
| | | if (!nextPoint) {
|
| | | nextPoint = segment[1];
|
| | | } |
| | | }
|
| | |
|
| | | // find control points
|
| | | if (lastPoint && nextPoint) {
|
| | | |
| | | lastX = lastPoint.plotX;
|
| | | lastY = lastPoint.plotY;
|
| | | nextX = nextPoint.plotX;
|
| | | nextY = nextPoint.plotY;
|
| | | leftContX = (smoothing * plotX + lastX) / denom;
|
| | | leftContY = (smoothing * plotY + lastY) / denom;
|
| | | rightContX = (smoothing * plotX + nextX) / denom;
|
| | | rightContY = (smoothing * plotY + nextY) / denom;
|
| | | distanceLeftControlPoint = Math.sqrt(Math.pow(leftContX - plotX, 2) + Math.pow(leftContY - plotY, 2));
|
| | | distanceRightControlPoint = 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) * distanceLeftControlPoint;
|
| | | leftContY = plotY + Math.sin(jointAngle) * distanceLeftControlPoint;
|
| | | rightContX = plotX + Math.cos(Math.PI + jointAngle) * distanceRightControlPoint;
|
| | | rightContY = plotY + Math.sin(Math.PI + jointAngle) * distanceRightControlPoint;
|
| | | |
| | | // Record for drawing in next point
|
| | | point.rightContX = rightContX;
|
| | | point.rightContY = rightContY;
|
| | |
|
| | | }
|
| | | |
| | | |
| | | // moveTo or lineTo
|
| | | if (!i) {
|
| | | ret = ['M', plotX, plotY];
|
| | | } else { // curve from last point to this
|
| | | ret = [
|
| | | 'C',
|
| | | lastPoint.rightContX || lastPoint.plotX,
|
| | | lastPoint.rightContY || lastPoint.plotY,
|
| | | leftContX || plotX,
|
| | | leftContY || plotY,
|
| | | plotX,
|
| | | plotY
|
| | | ];
|
| | | lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
|
| | | }
|
| | | |
| | | |
| | | } else {
|
| | | ret = proceed.call(this, segment, point, i);
|
| | | }
|
| | | return ret;
|
| | | });
|
| | |
|
| | | /**
|
| | | * Extend translate. The plotX and plotY values are computed as if the polar chart were a
|
| | | * cartesian plane, where plotX denotes the angle in radians and (yAxis.len - plotY) is the pixel distance from
|
| | | * center. |
| | | */
|
| | | wrap(seriesProto, 'translate', function (proceed) {
|
| | | |
| | | // Run uber method
|
| | | proceed.call(this);
|
| | | |
| | | // Postprocess plot coordinates
|
| | | if (this.chart.polar && !this.preventPostTranslate) {
|
| | | var points = this.points,
|
| | | i = points.length;
|
| | | while (i--) {
|
| | | // Translate plotX, plotY from angle and radius to true plot coordinates
|
| | | this.toXY(points[i]);
|
| | | }
|
| | | }
|
| | | });
|
| | |
|
| | | /** |
| | | * Extend getSegmentPath to allow connecting ends across 0 to provide a closed circle in |
| | | * line-like series.
|
| | | */
|
| | | wrap(seriesProto, 'getSegmentPath', function (proceed, segment) {
|
| | | |
| | | var points = this.points;
|
| | | |
| | | // Connect the path
|
| | | if (this.chart.polar && this.options.connectEnds !== false && |
| | | segment[segment.length - 1] === points[points.length - 1] && points[0].y !== null) {
|
| | | this.connectEnds = true; // re-used in splines
|
| | | segment = [].concat(segment, [points[0]]);
|
| | | }
|
| | | |
| | | // Run uber method
|
| | | return proceed.call(this, segment);
|
| | | |
| | | });
|
| | |
|
| | |
|
| | | function polarAnimate(proceed, init) {
|
| | | var chart = this.chart,
|
| | | animation = this.options.animation,
|
| | | group = this.group,
|
| | | markerGroup = this.markerGroup,
|
| | | center = this.xAxis.center,
|
| | | plotLeft = chart.plotLeft,
|
| | | plotTop = chart.plotTop,
|
| | | attribs;
|
| | |
|
| | | // 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.attrSetters = group.attrSetters;
|
| | | 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 both regular series and column series and their derivatives
|
| | | wrap(seriesProto, 'animate', polarAnimate);
|
| | | wrap(colProto, 'animate', polarAnimate);
|
| | |
|
| | |
|
| | | /**
|
| | | * Throw in a couple of properties to let setTooltipPoints know we're indexing the points
|
| | | * in degrees (0-360), not plot pixel width.
|
| | | */
|
| | | wrap(seriesProto, 'setTooltipPoints', function (proceed, renew) {
|
| | | |
| | | if (this.chart.polar) {
|
| | | extend(this.xAxis, {
|
| | | tooltipLen: 360 // degrees are the resolution unit of the tooltipPoints array
|
| | | }); |
| | | }
|
| | | |
| | | // Run uber method
|
| | | return proceed.call(this, renew);
|
| | | });
|
| | |
|
| | |
|
| | | /**
|
| | | * Extend the column prototype's translate method
|
| | | */
|
| | | wrap(colProto, 'translate', function (proceed) {
|
| | | |
| | | var xAxis = this.xAxis,
|
| | | len = this.yAxis.len,
|
| | | center = xAxis.center,
|
| | | startAngleRad = xAxis.startAngleRad,
|
| | | renderer = this.chart.renderer,
|
| | | 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: renderer.symbols.arc(
|
| | | center[0],
|
| | | center[1],
|
| | | len - point.plotY,
|
| | | null, |
| | | {
|
| | | start: start,
|
| | | end: start + point.pointWidth,
|
| | | innerR: len - pick(point.yBottom, len)
|
| | | }
|
| | | )
|
| | | };
|
| | | this.toXY(point); // provide correct plotX, plotY for tooltip
|
| | | }
|
| | | }
|
| | | });
|
| | |
|
| | |
|
| | | /**
|
| | | * 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 the mouse tracker to return the tooltip position index in terms of
|
| | | * degrees rather than pixels
|
| | | */
|
| | | wrap(pointerProto, 'getIndex', function (proceed, e) {
|
| | | var ret,
|
| | | chart = this.chart,
|
| | | center,
|
| | | x,
|
| | | y;
|
| | | |
| | | if (chart.polar) {
|
| | | center = chart.xAxis[0].center;
|
| | | x = e.chartX - center[0] - chart.plotLeft;
|
| | | y = e.chartY - center[1] - chart.plotTop;
|
| | | |
| | | ret = 180 - Math.round(Math.atan2(x, y) / Math.PI * 180);
|
| | | |
| | | } else {
|
| | | |
| | | // Run uber method
|
| | | ret = proceed.call(this, e);
|
| | | }
|
| | | return ret;
|
| | | });
|
| | |
|
| | | /**
|
| | | * Extend getCoordinates to prepare for polar axis values
|
| | | */
|
| | | wrap(pointerProto, 'getCoordinates', function (proceed, e) {
|
| | | var chart = this.chart,
|
| | | ret = {
|
| | | xAxis: [],
|
| | | yAxis: []
|
| | | };
|
| | | |
| | | if (chart.polar) { |
| | |
|
| | | each(chart.axes, function (axis) {
|
| | | var isXAxis = axis.isXAxis,
|
| | | center = axis.center,
|
| | | x = e.chartX - center[0] - chart.plotLeft,
|
| | | y = e.chartY - center[1] - chart.plotTop;
|
| | | |
| | | ret[isXAxis ? 'xAxis' : 'yAxis'].push({
|
| | | axis: axis,
|
| | | value: axis.translate(
|
| | | isXAxis ?
|
| | | Math.PI - Math.atan2(x, y) : // angle |
| | | Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)), // distance from center
|
| | | true
|
| | | )
|
| | | });
|
| | | });
|
| | | |
| | | } else {
|
| | | ret = proceed.call(this, e);
|
| | | }
|
| | | |
| | | return ret;
|
| | | });
|
| | | }(Highcharts));
|