app/assets/javascripts/highcharts/modules/stock.js in highcharts-rails-5.0.12 vs app/assets/javascripts/highcharts/modules/stock.js in highcharts-rails-5.0.13

- old
+ new

@@ -1,7 +1,7 @@ /** - * @license Highcharts JS v5.0.12 (2017-05-24) + * @license Highcharts JS v5.0.13 (2017-07-27) * Highstock as a plugin for Highcharts * * (c) 2017 Torstein Honsi * * License: www.highcharts.com/license @@ -1024,23 +1024,84 @@ * module as of #5045. */ H.Series.prototype.gappedPath = function() { var gapSize = this.options.gapSize, points = this.points.slice(), - i = points.length - 1; + i = points.length - 1, + yAxis = this.yAxis, + xRange, + stack; + /** + * Defines when to display a gap in the graph, together with the `gapUnit` + * option. + * + * When the `gapUnit` is `relative` (default), a gap size of 5 means + * that if the distance between two points is greater than five times + * that of the two closest points, the graph will be broken. + * + * When the `gapUnit` is `value`, the gap is based on absolute axis values, + * which on a datetime axis is milliseconds. + * + * In practice, this option is most often used to visualize gaps in + * time series. In a stock chart, intraday data is available for daytime + * hours, while gaps will appear in nights and weekends. + * + * @type {Number} + * @see [xAxis.breaks](#xAxis.breaks) + * @sample {highstock} stock/plotoptions/series-gapsize/ + * Setting the gap size to 2 introduces gaps for weekends in daily + * datasets. + * @default 0 + * @product highstock + * @apioption plotOptions.series.gapSize + */ + + /** + * Together with `gapSize`, this option defines where to draw gaps in the + * graph. + * + * @type {String} + * @see [gapSize](plotOptions.series.gapSize) + * @default relative + * @validvalues ["relative", "value"] + * @since 5.0.13 + * @product highstock + * @apioption plotOptions.series.gapUnit + */ + if (gapSize && i > 0) { // #5008 + // Gap unit is relative + if (this.options.gapUnit !== 'value') { + gapSize *= this.closestPointRange; + } + // extension for ordinal breaks while (i--) { - if (points[i + 1].x - points[i].x > this.closestPointRange * gapSize) { + if (points[i + 1].x - points[i].x > gapSize) { + xRange = (points[i].x + points[i + 1].x) / 2; + points.splice( // insert after this one i + 1, 0, { - isNull: true + isNull: true, + x: xRange } ); + + // For stacked chart generate empty stack items, #6546 + if (this.options.stacking) { + stack = yAxis.stacks[this.stackKey][xRange] = new H.StackItem( + yAxis, + yAxis.options.stackLabels, + false, + xRange, + this.stack + ); + stack.total = 0; + } } } } // Call base method @@ -1084,10 +1145,13 @@ var seriesProto = Series.prototype, baseProcessData = seriesProto.processData, baseGeneratePoints = seriesProto.generatePoints, baseDestroy = seriesProto.destroy, + /** + * + */ commonOptions = { approximation: 'average', // average, open, high, low, close, sum //enabled: null, // (true for stock charts, false for basic), //forced: undefined, groupPixelWidth: 2, @@ -1747,28 +1811,70 @@ * The ohlc series type. * * @constructor seriesTypes.ohlc * @augments seriesTypes.column */ + /** + * @extends {plotOptions.column} + * @optionparent plotOptions.ohlc + */ seriesType('ohlc', 'column', { + + /** + * The pixel width of the line/border. Defaults to `1`. + * + * @type {Number} + * @sample {highstock} stock/plotoptions/ohlc-linewidth/ A greater line width + * @default 1 + * @product highstock + */ lineWidth: 1, + + /** + */ tooltip: { + + /** + */ pointFormat: '<span style="color:{point.color}">\u25CF</span> <b> {series.name}</b><br/>' + 'Open: {point.open}<br/>' + 'High: {point.high}<br/>' + 'Low: {point.low}<br/>' + 'Close: {point.close}<br/>' }, + + /** + */ threshold: null, + + /** + */ states: { + + /** + * @extends plotOptions.column.states.hover + * @product highstock + */ hover: { + + /** + * The pixel width of the line representing the OHLC point. Defaults + * to `3`. + * + * @type {Number} + * @default 3 + * @product highstock + */ lineWidth: 3 } }, + + /** + */ stickyTracking: true //upColor: undefined }, /** @lends seriesTypes.ohlc */ { @@ -1950,33 +2056,110 @@ merge = H.merge, seriesType = H.seriesType, seriesTypes = H.seriesTypes; /** - * The candlestick series type. - * - * @constructor seriesTypes.candlestick - * @augments seriesTypes.ohlc + * @extends {plotOptions.ohlc} + * @products highstock + * @optionparent plotOptions.candlestick */ - seriesType('candlestick', 'ohlc', merge(defaultPlotOptions.column, { + var candlestickOptions = { + + /** + */ states: { + + /** + * @extends plotOptions.column.states.hover + * @product highstock + */ hover: { + + /** + * The pixel width of the line/border around the candlestick. Defaults + * to `2`. + * + * @type {Number} + * @default 2 + * @product highstock + */ lineWidth: 2 } }, + + /** + */ tooltip: defaultPlotOptions.ohlc.tooltip, + + /** + */ threshold: null, + + /** + * The color of the line/border of the candlestick. + * + * In [styled mode](http://www.highcharts.com/docs/chart-design-and- + * style/style-by-css), the line stroke can be set with the `.highcharts- + * candlestick-series .highcahrts-point` rule. + * + * @type {Color} + * @see [upLineColor](#plotOptions.candlestick.upLineColor) + * @sample {highstock} stock/plotoptions/candlestick-linecolor/ Candlestick line colors + * @default #000000 + * @product highstock + */ lineColor: '#000000', + + /** + * The pixel width of the candlestick line/border. Defaults to `1`. + * + * + * In [styled mode](http://www.highcharts.com/docs/chart-design-and- + * style/style-by-css), the line stroke width can be set with the `. + * highcharts-candlestick-series .highcahrts-point` rule. + * + * @type {Number} + * @default 1 + * @product highstock + */ lineWidth: 1, + + /** + * The fill color of the candlestick when values are rising. + * + * In [styled mode](http://www.highcharts.com/docs/chart-design-and- + * style/style-by-css), the up color can be set with the `.highcharts- + * candlestick-series .highcharts-point-up` rule. + * + * @type {Color} + * @sample {highstock} stock/plotoptions/candlestick-color/ Custom colors + * @sample {highstock} highcharts/css/candlestick/ Colors in styled mode + * @default #ffffff + * @product highstock + */ upColor: '#ffffff', + + /** + */ stickyTracking: true // upLineColor: null - }), /** @lends seriesTypes.candlestick */ { + }; + /** + * The candlestick series type. + * + * @constructor seriesTypes.candlestick + * @augments seriesTypes.ohlc + */ + seriesType('candlestick', 'ohlc', merge( + defaultPlotOptions.column, + candlestickOptions + ), /** @lends seriesTypes.candlestick */ { + /** * Postprocess mapping between options and SVG attributes */ pointAttribs: function(point, state) { var attribs = seriesTypes.column.prototype.pointAttribs.call(this, point, state), @@ -2120,33 +2303,153 @@ * The flags series type. * * @constructor seriesTypes.flags * @augments seriesTypes.column */ + /** + * @extends {plotOptions.column} + * @optionparent plotOptions.flags + */ seriesType('flags', 'column', { + + /** + */ pointRange: 0, // #673 //radius: 2, + + /** + * The shape of the marker. Can be one of "flag", "circlepin", "squarepin", + * or an image on the format `url(/path-to-image.jpg)`. Individual + * shapes can also be set for each point. + * + * @validvalue ["flag", "circlepin", "squarepin"] + * @type {String} + * @sample {highstock} stock/plotoptions/flags/ Different shapes + * @default flag + * @product highstock + */ shape: 'flag', + + /** + * When multiple flags in the same series fall on the same value, this + * number determines the vertical offset between them. + * + * @type {Number} + * @sample {highstock} stock/plotoptions/flags-stackdistance/ A greater stack distance + * @default 12 + * @product highstock + */ stackDistance: 12, + + /** + * Text alignment for the text inside the flag. + * + * @validvalue ["left", "center", "right"] + * @type {String} + * @default center + * @since 5.0.0 + * @product highstock + */ textAlign: 'center', + + /** + * Specific tooltip options for flag series. Flag series tooltips are + * different from most other types in that a flag doesn't have a data + * value, so the tooltip rather displays the `text` option for each + * point. + * + * @type {Object} + * @extends plotOptions.series.tooltip + * @excluding changeDecimals,valueDecimals,valuePrefix,valueSuffix + * @product highstock + */ tooltip: { + + /** + */ pointFormat: '{point.text}<br/>' }, + + /** + */ threshold: null, + + /** + * The y position of the top left corner of the flag relative to either + * the series (if onSeries is defined), or the x axis. Defaults to + * `-30`. + * + * @type {Number} + * @default -30 + * @product highstock + */ y: -30, + + /** + */ fillColor: '#ffffff', // lineColor: color, + + /** + * The pixel width of the candlestick line/border. Defaults to `1`. + * + * @type {Number} + * @default 1 + * @product highstock + */ lineWidth: 1, + + /** + */ states: { + + /** + * @extends plotOptions.column.states.hover + * @product highstock + */ hover: { + + /** + * The color of the line/border of the flag Defaults to `"black"`. + * + * @type {String} + * @default "black" + * @product highstock + */ lineColor: '#000000', + + /** + * The fill or background color of the flag Defaults to `"#FCFFC5"`. + * + * @type {String} + * @default "#FCFFC5" + * @product highstock + */ fillColor: '#ccd6eb' } }, + + /** + * The text styles of the flag. + * + * In [styled mode](http://www.highcharts.com/docs/chart-design-and- + * style/style-by-css), the styles are set in the `.highcharts-flag- + * series .highcharts-point` rule. + * + * @type {CSSObject} + * @default { "fontSize": "11px", "fontWeight": "bold" } + * @product highstock + */ style: { + + /** + */ fontSize: '11px', + + /** + */ fontWeight: 'bold' } }, /** @lends seriesTypes.flags.prototype */ { @@ -2511,11 +2814,10 @@ Axis = H.Axis, correctFloat = H.correctFloat, defaultOptions = H.defaultOptions, defined = H.defined, destroyObjectProperties = H.destroyObjectProperties, - doc = H.doc, each = H.each, fireEvent = H.fireEvent, hasTouch = H.hasTouch, isTouchDevice = H.isTouchDevice, merge = H.merge, @@ -2523,34 +2825,201 @@ removeEvent = H.removeEvent, svg = H.svg, wrap = H.wrap, swapXY; + /** + * + * The scrollbar is a means of panning over the X axis of a chart. + * + * In [styled mode](http://www.highcharts.com/docs/chart-design- + * and-style/style-by-css), all the presentational options for the + * scrollbar are replaced by the classes `.highcharts-scrollbar- + * thumb`, `.highcharts-scrollbar-arrow`, `.highcharts-scrollbar- + * button`, `.highcharts-scrollbar-rifles` and `.highcharts-scrollbar- + * track`. + * + * @product highstock + * @optionparent scrollbar + */ var defaultScrollbarOptions = { //enabled: true + + /** + * The height of the scrollbar. The height also applies to the width + * of the scroll arrows so that they are always squares. Defaults to + * 20 for touch devices and 14 for mouse devices. + * + * @type {Number} + * @sample {highstock} stock/scrollbar/height/ A 30px scrollbar + * @product highstock + */ height: isTouchDevice ? 20 : 14, // trackBorderRadius: 0 + + /** + * The border rounding radius of the bar. + * + * @type {Number} + * @sample {highstock} stock/scrollbar/style/ Scrollbar styling + * @default 0 + * @product highstock + */ barBorderRadius: 0, + + /** + * The corner radius of the scrollbar buttons. + * + * @type {Number} + * @sample {highstock} stock/scrollbar/style/ Scrollbar styling + * @default 0 + * @product highstock + */ buttonBorderRadius: 0, + + /** + * Whether to redraw the main chart as the scrollbar or the navigator + * zoomed window is moved. Defaults to `true` for modern browsers and + * `false` for legacy IE browsers as well as mobile devices. + * + * @type {Boolean} + * @since 1.3 + * @product highstock + */ liveRedraw: svg && !isTouchDevice, + + /** + */ margin: 10, + + /** + * The minimum width of the scrollbar. + * + * @type {Number} + * @default 6 + * @since 1.2.5 + * @product highstock + */ minWidth: 6, //showFull: true, //size: null, + + /** + */ step: 0.2, + + /** + */ zIndex: 3, + + /** + * The background color of the scrollbar itself. + * + * @type {Color} + * @sample {highstock} stock/scrollbar/style/ Scrollbar styling + * @default #cccccc + * @product highstock + */ barBackgroundColor: '#cccccc', + + /** + * The width of the bar's border. + * + * @type {Number} + * @sample {highstock} stock/scrollbar/style/ Scrollbar styling + * @default 1 + * @product highstock + */ barBorderWidth: 1, + + /** + * The color of the scrollbar's border. + * + * @type {Color} + * @default #cccccc + * @product highstock + */ barBorderColor: '#cccccc', + + /** + * The color of the small arrow inside the scrollbar buttons. + * + * @type {Color} + * @sample {highstock} stock/scrollbar/style/ Scrollbar styling + * @default #333333 + * @product highstock + */ buttonArrowColor: '#333333', + + /** + * The color of scrollbar buttons. + * + * @type {Color} + * @sample {highstock} stock/scrollbar/style/ Scrollbar styling + * @default #e6e6e6 + * @product highstock + */ buttonBackgroundColor: '#e6e6e6', + + /** + * The color of the border of the scrollbar buttons. + * + * @type {Color} + * @sample {highstock} stock/scrollbar/style/ Scrollbar styling + * @default #cccccc + * @product highstock + */ buttonBorderColor: '#cccccc', + + /** + * The border width of the scrollbar buttons. + * + * @type {Number} + * @sample {highstock} stock/scrollbar/style/ Scrollbar styling + * @default 1 + * @product highstock + */ buttonBorderWidth: 1, + + /** + * The color of the small rifles in the middle of the scrollbar. + * + * @type {Color} + * @default #333333 + * @product highstock + */ rifleColor: '#333333', + + /** + * The color of the track background. + * + * @type {Color} + * @sample {highstock} stock/scrollbar/style/ Scrollbar styling + * @default #f2f2f2 + * @product highstock + */ trackBackgroundColor: '#f2f2f2', + + /** + * The color of the border of the scrollbar track. + * + * @type {Color} + * @sample {highstock} stock/scrollbar/style/ Scrollbar styling + * @default #f2f2f2 + * @product highstock + */ trackBorderColor: '#f2f2f2', + + /** + * The width of the border of the scrollbar track. + * + * @type {Number} + * @sample {highstock} stock/scrollbar/style/ Scrollbar styling + * @default 1 + * @product highstock + */ trackBorderWidth: 1 }; defaultOptions.scrollbar = merge(true, defaultScrollbarOptions, defaultOptions.scrollbar); @@ -3068,18 +3537,18 @@ _events = [ [buttons[buttonsOrder[0]].element, 'click', this.buttonToMinClick], [buttons[buttonsOrder[1]].element, 'click', this.buttonToMaxClick], [track, 'click', this.trackClick], [bar, 'mousedown', mouseDownHandler], - [doc, 'mousemove', mouseMoveHandler], - [doc, 'mouseup', mouseUpHandler] + [bar.ownerDocument, 'mousemove', mouseMoveHandler], + [bar.ownerDocument, 'mouseup', mouseUpHandler] ]; // Touch events if (hasTouch) { _events.push( - [bar, 'touchstart', mouseDownHandler], [doc, 'touchmove', mouseMoveHandler], [doc, 'touchend', mouseUpHandler] + [bar, 'touchstart', mouseDownHandler], [bar.ownerDocument, 'touchmove', mouseMoveHandler], [bar.ownerDocument, 'touchend', mouseUpHandler] ); } // Add them all each(_events, function(args) { @@ -3162,12 +3631,20 @@ /** * Wrap rendering axis, and update scrollbar if one is created: */ wrap(Axis.prototype, 'render', function(proceed) { var axis = this, - scrollMin = Math.min(pick(axis.options.min, axis.min), axis.min, axis.dataMin), - scrollMax = Math.max(pick(axis.options.max, axis.max), axis.max, axis.dataMax), + scrollMin = Math.min( + pick(axis.options.min, axis.min), + axis.min, + pick(axis.dataMin, axis.min) // #6930 + ), + scrollMax = Math.max( + pick(axis.options.max, axis.max), + axis.max, + pick(axis.dataMax, axis.max) // #6930 + ), scrollbar = axis.scrollbar, titleOffset = axis.titleOffset || 0, offsetsIndex, from, to; @@ -3267,17 +3744,17 @@ color = H.color, defaultDataGroupingUnits = H.defaultDataGroupingUnits, defaultOptions = H.defaultOptions, defined = H.defined, destroyObjectProperties = H.destroyObjectProperties, - doc = H.doc, each = H.each, erase = H.erase, error = H.error, extend = H.extend, grep = H.grep, hasTouch = H.hasTouch, + isArray = H.isArray, isNumber = H.isNumber, isObject = H.isObject, merge = H.merge, pick = H.pick, removeEvent = H.removeEvent, @@ -3305,93 +3782,419 @@ units[5] = ['week', [1, 2, 3]]; // allow more weeks defaultSeriesType = seriesTypes.areaspline === undefined ? 'line' : 'areaspline'; extend(defaultOptions, { + + /** + * The navigator is a small series below the main series, displaying + * a view of the entire data set. It provides tools to zoom in and + * out on parts of the data as well as panning across the dataset. + * + * @optionparent navigator + * @product highstock + */ navigator: { //enabled: true, + + /** + * The height of the navigator. + * + * @type {Number} + * @sample {highstock} stock/navigator/height/ A higher navigator + * @default 40 + * @product highstock + */ height: 40, + + /** + * The distance from the nearest element, the X axis or X axis labels. + * + * @type {Number} + * @sample {highstock} stock/navigator/margin/ A margin of 2 draws the navigator closer to the X axis labels + * @default 25 + * @product highstock + */ margin: 25, + + /** + * Whether the mask should be inside the range marking the zoomed + * range, or outside. In Highstock 1.x it was always `false`. + * + * @type {Boolean} + * @sample {highstock} stock/navigator/maskinside-false/ False, mask outside + * @default true + * @since 2.0 + * @product highstock + */ maskInside: true, + + /** + * Options for the handles for dragging the zoomed area. Available + * options are `backgroundColor` (defaults to `#ebe7e8`) and `borderColor` + * (defaults to `#b2b1b6`). + * + * @type {Object} + * @sample {highstock} stock/navigator/handles/ Colored handles + * @sample {highstock} stock/navigator/handles/ Colored handles + * @product highstock + */ handles: { + + /** + * The fill for the handle. + * + * @type {Color} + * @default #f2f2f2 + * @product highstock + */ backgroundColor: '#f2f2f2', + + /** + * The stroke for the handle border and the stripes inside. + * + * @type {Color} + * @default #999999 + * @product highstock + */ borderColor: '#999999' }, + + /** + * The color of the mask covering the areas of the navigator series + * that are currently not visible in the main series. The default + * color is bluish with an opacity of 0.3 to see the series below. + * + * @type {Color} + * @see In [styled mode](http://www.highcharts.com/docs/chart-design-and- + * style/style-by-css), the mask is styled with the `.highcharts-navigator- + * mask` and `.highcharts-navigator-mask-inside` classes. + * @sample {highstock} stock/navigator/maskfill/ Blue, semi transparent mask + * @default rgba(102,133,194,0.3) + * @product highstock + */ maskFill: color('#6685c2').setOpacity(0.3).get(), + + /** + * The color of the line marking the currently zoomed area in the + * navigator. + * + * @type {Color} + * @sample {highstock} stock/navigator/outline/ 2px blue outline + * @default #cccccc + * @product highstock + */ outlineColor: '#cccccc', + + /** + * The width of the line marking the currently zoomed area in the + * navigator. + * + * @type {Number} + * @see In [styled mode](http://www.highcharts.com/docs/chart-design-and- + * style/style-by-css), the outline stroke width is set with the `. + * highcharts-navigator-outline` class. + * @sample {highstock} stock/navigator/outline/ 2px blue outline + * @default 2 + * @product highstock + */ outlineWidth: 1, + + /** + * Options for the navigator series. Available options are the same + * as any series, documented at [plotOptions](#plotOptions.series) + * and [series](#series). + * + * Unless data is explicitly defined on navigator.series, the data + * is borrowed from the first series in the chart. + * + * Default series options for the navigator series are: + * + * <pre>series: { + * type: 'areaspline', + * color: '#4572A7', + * fillOpacity: 0.05, + * dataGrouping: { + * smoothed: true + * }, + * lineWidth: 1, + * marker: { + * enabled: false + * } + * }</pre> + * + * @type {Object} + * @see In [styled mode](http://www.highcharts.com/docs/chart-design-and- + * style/style-by-css), the navigator series is styled with the `. + * highcharts-navigator-series` class. + * @sample {highstock} stock/navigator/series-data/ Using a separate data set for the navigator + * @sample {highstock} stock/navigator/series/ A green navigator series + * @product highstock + */ series: { + + /** + */ type: defaultSeriesType, + + /** + */ color: '#335cad', + + /** + */ fillOpacity: 0.05, + + /** + */ lineWidth: 1, + + /** + */ compare: null, + + /** + */ dataGrouping: { + + /** + */ approximation: 'average', + + /** + */ enabled: true, + + /** + */ groupPixelWidth: 2, + + /** + */ smoothed: true, + + /** + */ units: units }, + + /** + */ dataLabels: { + + /** + */ enabled: false, + + /** + */ zIndex: 2 // #1839 }, + + /** + */ id: 'highcharts-navigator-series', + + /** + */ className: 'highcharts-navigator-series', + + /** + */ lineColor: null, // Allow color setting while disallowing default candlestick setting (#4602) + + /** + */ marker: { + + /** + */ enabled: false }, + + /** + */ pointRange: 0, + + /** + */ shadow: false, + + /** + */ threshold: null }, //top: undefined, //opposite: undefined, + + /** + * Options for the navigator X axis. Available options are the same + * as any X axis, documented at [xAxis](#xAxis). Default series options + * for the navigator xAxis are: + * + * <pre>xAxis: { + * tickWidth: 0, + * lineWidth: 0, + * gridLineWidth: 1, + * tickPixelInterval: 200, + * labels: { + * align: 'left', + * style: { + * color: '#888' + * }, + * x: 3, + * y: -4 + * } + * }</pre> + * + * @type {Object} + * @product highstock + */ xAxis: { + + /** + */ className: 'highcharts-navigator-xaxis', + + /** + */ tickLength: 0, + + /** + */ lineWidth: 0, + + /** + */ gridLineColor: '#e6e6e6', + + /** + */ gridLineWidth: 1, + + /** + */ tickPixelInterval: 200, + + /** + */ labels: { + + /** + */ align: 'left', + + /** + */ style: { + + /** + */ color: '#999999' }, + + /** + */ x: 3, + + /** + */ y: -4 }, + + /** + */ crosshair: false }, + + /** + * Options for the navigator Y axis. Available options are the same + * as any y axis, documented at [yAxis](#yAxis). Default series options + * for the navigator yAxis are: + * + * <pre>yAxis: { + * gridLineWidth: 0, + * startOnTick: false, + * endOnTick: false, + * minPadding: 0.1, + * maxPadding: 0.1, + * labels: { + * enabled: false + * }, + * title: { + * text: null + * }, + * tickWidth: 0 + * }</pre> + * + * @type {Object} + * @product highstock + */ yAxis: { + + /** + */ className: 'highcharts-navigator-yaxis', + + /** + */ gridLineWidth: 0, + + /** + */ startOnTick: false, + + /** + */ endOnTick: false, + + /** + */ minPadding: 0.1, + + /** + */ maxPadding: 0.1, + + /** + */ labels: { + + /** + */ enabled: false }, + + /** + */ crosshair: false, + + /** + */ title: { + + /** + */ text: null }, + + /** + */ tickLength: 0, + + /** + */ tickWidth: 0 } } }); @@ -3671,10 +4474,17 @@ /** * Update navigator * @param {Object} options Options to merge in when updating navigator */ update: function(options) { + // Remove references to old navigator series in base series + each(this.series || [], function(series) { + if (series.baseSeries) { + delete series.baseSeries.navigatorSeries; + } + }); + // Destroy and rebuild navigator this.destroy(); var chartOptions = this.chart.options; merge(true, chartOptions.navigator, this.options, options); this.init(this.chart); }, @@ -3851,18 +4661,18 @@ eventsToUnbind = navigator.getPartsEvents('mousedown'); // Add mouse move and mouseup events. These are bind to doc/container, // because Navigator.grabbedSomething flags are stored in mousedown events: eventsToUnbind.push( addEvent(container, 'mousemove', mouseMoveHandler), - addEvent(doc, 'mouseup', mouseUpHandler) + addEvent(container.ownerDocument, 'mouseup', mouseUpHandler) ); // Touch events if (hasTouch) { eventsToUnbind.push( addEvent(container, 'touchmove', mouseMoveHandler), - addEvent(doc, 'touchend', mouseUpHandler) + addEvent(container.ownerDocument, 'touchend', mouseUpHandler) ); eventsToUnbind.concat(navigator.getPartsEvents('touchstart')); } navigator.eventsToUnbind = eventsToUnbind; @@ -4141,14 +4951,16 @@ /** * Remove data events. */ removeBaseSeriesEvents: function() { var baseSeries = this.baseSeries || []; - if (this.navigatorEnabled && baseSeries[0] && this.navigatorOptions.adaptToUpdatedData !== false) { - each(baseSeries, function(series) { - removeEvent(series, 'updatedData', this.updatedDataHandler); - }, this); + if (this.navigatorEnabled && baseSeries[0]) { + if (this.navigatorOptions.adaptToUpdatedData !== false) { + each(baseSeries, function(series) { + removeEvent(series, 'updatedData', this.updatedDataHandler); + }, this); + } // We only listen for extremes-events on the first baseSeries if (baseSeries[0].xAxis) { removeEvent(baseSeries[0].xAxis, 'foundExtremes', this.modifyBaseAxisExtremes); } @@ -4238,11 +5050,11 @@ height: height })); // If we have a base series, initialize the navigator series if (baseSeries || navigatorOptions.series.data) { - navigator.addBaseSeries(); + navigator.updateNavigatorSeries(); // If not, set up an event to listen for added series } else if (chart.series.length === 0) { wrap(chart, 'redraw', function(proceed, animation) { @@ -4358,95 +5170,171 @@ } return ret; }, /** - * Set the base series. With a bit of modification we should be able to make - * this an API method to be called from the outside - * @param {Object} baseSeriesOptions - series options for a navigator + * Set the base series and update the navigator series from this. With a bit + * of modification we should be able to make this an API method to be called + * from the outside + * @param {Object} baseSeriesOptions - additional series options for a navigator */ setBaseSeries: function(baseSeriesOptions) { var chart = this.chart, - baseSeries; + baseSeries = this.baseSeries = []; baseSeriesOptions = baseSeriesOptions || chart.options && chart.options.navigator.baseSeries || 0; - // If we're resetting, remove the existing series - if (this.series) { - this.removeBaseSeriesEvents(); - each(this.series, function(s) { - s.destroy(); - }); - } - - baseSeries = this.baseSeries = []; - - // Iterate through series and add the ones that should be shown in navigator + // Iterate through series and add the ones that should be shown in navigator. each(chart.series || [], function(series, i) { - if (series.options.showInNavigator || (i === baseSeriesOptions || series.options.id === baseSeriesOptions) && - series.options.showInNavigator !== false) { + if (!series.options.isInternal && // Don't include existing nav series + ( + series.options.showInNavigator || + ( + i === baseSeriesOptions || + series.options.id === baseSeriesOptions + ) && + series.options.showInNavigator !== false + ) + ) { baseSeries.push(series); } }); // When run after render, this.xAxis already exists if (this.xAxis && !this.xAxis.fake) { - this.addBaseSeries(); + this.updateNavigatorSeries(); } }, /* - * Add base series to the navigator. + * Update series in the navigator from baseSeries, adding new if does not + * exist. */ - addBaseSeries: function() { + updateNavigatorSeries: function() { var navigator = this, chart = navigator.chart, - navigatorSeries = navigator.series = [], baseSeries = navigator.baseSeries, baseOptions, mergedNavSeriesOptions, - chartNavigatorOptions = navigator.navigatorOptions.series, + chartNavigatorSeriesOptions = navigator.navigatorOptions.series, baseNavigatorOptions, navSeriesMixin = { enableMouseTracking: false, index: null, // #6162 + linkedTo: null, // #6734 group: 'nav', // for columns padXAxis: false, xAxis: 'navigator-x-axis', yAxis: 'navigator-y-axis', showInLegend: false, stacking: false, // #4823 isInternal: true, visible: true - }; + }, + // Remove navigator series that are no longer in the baseSeries + navigatorSeries = navigator.series = H.grep( + navigator.series || [], + function(navSeries) { + var base = navSeries.baseSeries; + if (H.inArray(base, baseSeries) < 0) { // Not in array + // If there is still a base series connected to this series, + // remove event handler and reference. + if (base) { + removeEvent( + base, + 'updatedData', + navigator.updatedDataHandler + ); + delete base.navigatorSeries; + } + // Kill the nav series + navSeries.destroy(); + return false; + } + return true; + } + ); // Go through each base series and merge the options to create new series - if (baseSeries) { + if (baseSeries && baseSeries.length) { each(baseSeries, function(base, i) { + var linkedNavSeries = base.navigatorSeries, + userNavOptions = !isArray(chartNavigatorSeriesOptions) ? + chartNavigatorSeriesOptions : {}; + + // Don't update if the series exists in nav and we have disabled + // adaptToUpdatedData. + if ( + linkedNavSeries && + navigator.navigatorOptions.adaptToUpdatedData === false + ) { + return; + } + navSeriesMixin.name = 'Navigator ' + (i + 1); baseOptions = base.options || {}; baseNavigatorOptions = baseOptions.navigatorOptions || {}; - mergedNavSeriesOptions = merge(baseOptions, navSeriesMixin, chartNavigatorOptions, baseNavigatorOptions); + mergedNavSeriesOptions = merge( + baseOptions, + navSeriesMixin, + userNavOptions, + baseNavigatorOptions + ); // Merge data separately. Do a slice to avoid mutating the navigator options from base series (#4923). - var navigatorSeriesData = baseNavigatorOptions.data || chartNavigatorOptions.data; + var navigatorSeriesData = baseNavigatorOptions.data || userNavOptions.data; navigator.hasNavigatorData = navigator.hasNavigatorData || !!navigatorSeriesData; mergedNavSeriesOptions.data = navigatorSeriesData || baseOptions.data && baseOptions.data.slice(0); - // Add the series - base.navigatorSeries = chart.initSeries(mergedNavSeriesOptions); - navigatorSeries.push(base.navigatorSeries); + // Update or add the series + if (linkedNavSeries) { + linkedNavSeries.update(mergedNavSeriesOptions); + } else { + base.navigatorSeries = chart.initSeries(mergedNavSeriesOptions); + base.navigatorSeries.baseSeries = base; // Store ref + navigatorSeries.push(base.navigatorSeries); + } }); - } else { - // No base series, build from mixin and chart wide options - mergedNavSeriesOptions = merge(chartNavigatorOptions, navSeriesMixin); - mergedNavSeriesOptions.data = chartNavigatorOptions.data; - navigator.hasNavigatorData = !!mergedNavSeriesOptions.data; - navigatorSeries.push(chart.initSeries(mergedNavSeriesOptions)); } + // If user has defined data (and no base series) or explicitly defined + // navigator.series as an array, we create these series on top of any + // base series. + if ( + chartNavigatorSeriesOptions.data && + !(baseSeries && baseSeries.length) || + isArray(chartNavigatorSeriesOptions) + ) { + navigator.hasNavigatorData = false; + // Allow navigator.series to be an array + chartNavigatorSeriesOptions = H.splat(chartNavigatorSeriesOptions); + each(chartNavigatorSeriesOptions, function(userSeriesOptions, i) { + mergedNavSeriesOptions = merge({ + // Since we don't have a base series to pull color from, + // try to fake it by using color from series with same + // index. Otherwise pull from the colors array. We need + // an explicit color as otherwise updates will increment + // color counter and we'll get a new color for each + // update of the nav series. + color: chart.series[i] && + !chart.series[i].options.isInternal && + chart.series[i].color || + chart.options.colors[i] || + chart.options.colors[0] + }, + userSeriesOptions, + navSeriesMixin + ); + mergedNavSeriesOptions.data = userSeriesOptions.data; + if (mergedNavSeriesOptions.data) { + navigator.hasNavigatorData = true; + navigatorSeries.push(chart.initSeries(mergedNavSeriesOptions)); + } + }); + } + this.addBaseSeriesEvents(); }, /** * Add data events. @@ -4454,33 +5342,48 @@ */ addBaseSeriesEvents: function() { var navigator = this, baseSeries = navigator.baseSeries || []; - // Bind modified extremes event to first base's xAxis only. In event of > 1 base-xAxes, the navigator will ignore those. + // Bind modified extremes event to first base's xAxis only. + // In event of > 1 base-xAxes, the navigator will ignore those. + // Adding this multiple times to the same axis is no problem, as + // duplicates should be discarded by the browser. if (baseSeries[0] && baseSeries[0].xAxis) { addEvent(baseSeries[0].xAxis, 'foundExtremes', this.modifyBaseAxisExtremes); } - if (this.navigatorOptions.adaptToUpdatedData !== false) { - // Respond to updated data in the base series. - // Abort if lazy-loading data from the server. - each(baseSeries, function(base) { + each(baseSeries, function(base) { + // Link base series show/hide to navigator series visibility + addEvent(base, 'show', function() { + if (this.navigatorSeries) { + this.navigatorSeries.show(); + } + }); + addEvent(base, 'hide', function() { + if (this.navigatorSeries) { + this.navigatorSeries.hide(); + } + }); + + // Respond to updated data in the base series, unless explicitily + // not adapting to data changes. + if (this.navigatorOptions.adaptToUpdatedData !== false) { if (base.xAxis) { addEvent(base, 'updatedData', this.updatedDataHandler); } + } - // Handle series removal - addEvent(base, 'remove', function() { - if (this.navigatorSeries) { - erase(navigator.series, this.navigatorSeries); - this.navigatorSeries.remove(false); - delete this.navigatorSeries; - } - }); - }, this); - } + // Handle series removal + addEvent(base, 'remove', function() { + if (this.navigatorSeries) { + erase(navigator.series, this.navigatorSeries); + this.navigatorSeries.remove(false); + delete this.navigatorSeries; + } + }); + }, this); }, /** * Set the navigator x axis extremes to reflect the total. The navigator extremes * should always be the extremes of the union of all series in the chart as @@ -4565,17 +5468,20 @@ updatedDataHandler: function() { var navigator = this.chart.navigator, baseSeries = this, navigatorSeries = this.navigatorSeries; - // Detect whether the zoomed area should stick to the minimum or maximum. If the current - // axis minimum falls outside the new updated dataset, we must adjust. - navigator.stickToMin = isNumber(baseSeries.xAxis.min) && (baseSeries.xAxis.min <= baseSeries.xData[0]); // If the scrollbar is scrolled all the way to the right, keep right as new data // comes in. navigator.stickToMax = Math.round(navigator.zoomedMax) >= Math.round(navigator.size); + // Detect whether the zoomed area should stick to the minimum or maximum. If the current + // axis minimum falls outside the new updated dataset, we must adjust. + navigator.stickToMin = isNumber(baseSeries.xAxis.min) && + (baseSeries.xAxis.min <= baseSeries.xData[0]) && + (!this.chart.fixedRange || !navigator.stickToMax); + // Set the navigator series data to the new data of the base series if (navigatorSeries && !navigator.hasNavigatorData) { navigatorSeries.options.pointStart = baseSeries.xData[0]; navigatorSeries.setData(baseSeries.options.data, false, null, false); // #5414 } @@ -4773,11 +5679,11 @@ }); // Handle updating series wrap(Series.prototype, 'update', function(proceed, newOptions, redraw) { proceed.call(this, newOptions, false); - if (this.chart.navigator) { + if (this.chart.navigator && !this.options.isInternal) { this.chart.navigator.setBaseSeries(); } if (pick(redraw, true)) { this.chart.redraw(); } @@ -4829,44 +5735,174 @@ /* **************************************************************************** * Start Range Selector code * *****************************************************************************/ extend(defaultOptions, { + + /** + * The range selector is a tool for selecting ranges to display within + * the chart. It provides buttons to select preconfigured ranges in + * the chart, like 1 day, 1 week, 1 month etc. It also provides input + * boxes where min and max dates can be manually input. + * + * @optionparent rangeSelector + * @product highstock + */ rangeSelector: { // allButtonsEnabled: false, // enabled: true, // buttons: {Object} // buttonSpacing: 0, + + /** + * A collection of attributes for the buttons. The object takes SVG + * attributes like `fill`, `stroke`, `stroke-width`, as well as `style`, + * a collection of CSS properties for the text. + * + * The object can also be extended with states, so you can set presentational + * options for `hover`, `select` or `disabled` button states. + * + * CSS styles for the text label. + * + * In [styled mode](http://www.highcharts.com/docs/chart-design-and- + * style/style-by-css), the buttons are styled by the `.highcharts- + * range-selector-buttons .highcharts-button` rule with its different + * states. + * + * @type {Object} + * @sample {highstock} stock/rangeselector/styling/ Styling the buttons and inputs + * @product highstock + */ buttonTheme: { + + /** + */ 'stroke-width': 0, + + /** + */ width: 28, + + /** + */ height: 18, + + /** + */ padding: 2, + + /** + */ zIndex: 7 // #484, #852 }, + + /** + * The height of the range selector, used to reserve space for buttons + * and input. + * + * @type {Number} + * @default 35 + * @since 2.1.9 + * @product highstock + */ height: 35, // reserved space for buttons and input + + /** + * Positioning for the input boxes. Allowed properties are `align`, + * `verticalAlign`, `x` and `y`. + * + * @type {Object} + * @default { align: "right" } + * @since 1.2.5 + * @product highstock + */ inputPosition: { + + /** + */ align: 'right' }, // inputDateFormat: '%b %e, %Y', // inputEditDateFormat: '%Y-%m-%d', // inputEnabled: true, // selected: undefined, // inputStyle: {}, + + /** + * CSS styles for the labels - the Zoom, From and To texts. + * + * In [styled mode](http://www.highcharts.com/docs/chart-design-and- + * style/style-by-css), the labels are styled by the `.highcharts- + * range-label` class. + * + * @type {CSSObject} + * @sample {highstock} stock/rangeselector/styling/ Styling the buttons and inputs + * @product highstock + */ labelStyle: { + + /** + */ color: '#666666' } } }); - defaultOptions.lang = merge(defaultOptions.lang, { - rangeSelectorZoom: 'Zoom', - rangeSelectorFrom: 'From', - rangeSelectorTo: 'To' - }); + defaultOptions.lang = merge( + defaultOptions.lang, + /** + * Language object. The language object is global and it can't be set + * on each chart initiation. Instead, use `Highcharts.setOptions` to + * set it before any chart is initialized. + * + * <pre>Highcharts.setOptions({ + * lang: { + * months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', + * 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'], + * + * weekdays: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', + * 'Samedi'] + * } + * });</pre> + * + * @optionparent lang + * @product highstock + */ + { + + /** + * The text for the label for the range selector buttons. + * + * @type {String} + * @default Zoom + * @product highstock + */ + rangeSelectorZoom: 'Zoom', + + /** + * The text for the label for the "from" input box in the range + * selector. + * + * @type {String} + * @default From + * @product highstock + */ + rangeSelectorFrom: 'From', + + /** + * The text for the label for the "to" input box in the range selector. + * + * @type {String} + * @default To + * @product highstock + */ + rangeSelectorTo: 'To' + } + ); + /** * The range selector. * @class * @param {Object} chart */ @@ -5812,11 +6848,11 @@ * The chart options structure as described in the {@link * https://api.highcharts.com/highstock|options reference}. * @param {Function} callback * A function to execute when the chart object is finished loading and * rendering. In most cases the chart is built in one thread, but in - * Internet Explorer version 8 or less the chart is sometimes initiated + * Internet Explorer version 8 or less the chart is sometimes initialized * before the document is ready, and in these cases the chart object * will not be finished synchronously. As a consequence, code that * relies on the newly built Chart object should always run in the * callback. Defining a {@link https://api.highcharts.com/highstock/chart.events.load| * chart.event.load} handler is equivalent. @@ -5983,11 +7019,11 @@ } panes[key] = this; return 'right'; } } - return proceed.call(this, [].slice.call(arguments, 1)); + return proceed.apply(this, [].slice.call(arguments, 1)); }); // Clear axis from label panes (#6071) wrap(Axis.prototype, 'destroy', function(proceed) { var chart = this.chart, @@ -5995,10 +7031,10 @@ if (key && chart._labelPanes && chart._labelPanes[key] === this) { delete chart._labelPanes[key]; } - return proceed.call(this, Array.prototype.slice.call(arguments, 1)); + return proceed.apply(this, Array.prototype.slice.call(arguments, 1)); }); // Override getPlotLinePath to allow for multipane charts wrap(Axis.prototype, 'getPlotLinePath', function(proceed, value, lineWidth, old, force, translatedValue) { var axis = this,