}
+ * The current items in the legend.
+ */
+ getAllItems: function () {
+ var allItems = [];
+ each(this.chart.series, function (series) {
+ var seriesOptions = series && series.options;
+
+ // Handle showInLegend. If the series is linked to another series,
+ // defaults to false.
+ if (series && pick(
+ seriesOptions.showInLegend,
+ !defined(seriesOptions.linkedTo) ? undefined : false, true
+ )) {
+
+ // Use points or series for the legend item depending on
+ // legendType
+ allItems = allItems.concat(
+ series.legendItems ||
+ (
+ seriesOptions.legendType === 'point' ?
+ series.data :
+ series
+ )
+ );
+ }
+ });
+
+ fireEvent(this, 'afterGetAllItems', { allItems: allItems });
+
+ return allItems;
+ },
+
+ /**
+ * Get a short, three letter string reflecting the alignment and layout.
+ *
+ * @private
+ * @return {String} The alignment, empty string if floating
+ */
+ getAlignment: function () {
+ var options = this.options;
+
+ // Use the first letter of each alignment option in order to detect
+ // the side. (#4189 - use charAt(x) notation instead of [x] for IE7)
+ return options.floating ? '' : (
+ options.align.charAt(0) +
+ options.verticalAlign.charAt(0) +
+ options.layout.charAt(0)
+ );
+ },
+
+ /**
+ * Adjust the chart margins by reserving space for the legend on only one
+ * side of the chart. If the position is set to a corner, top or bottom is
+ * reserved for horizontal legends and left or right for vertical ones.
+ *
+ * @private
+ */
+ adjustMargins: function (margin, spacing) {
+ var chart = this.chart,
+ options = this.options,
+ alignment = this.getAlignment();
+
+ if (alignment) {
+
+ each([
+ /(lth|ct|rth)/,
+ /(rtv|rm|rbv)/,
+ /(rbh|cb|lbh)/,
+ /(lbv|lm|ltv)/
+ ], function (alignments, side) {
+ if (alignments.test(alignment) && !defined(margin[side])) {
+
+ // Now we have detected on which side of the chart we should
+ // reserve space for the legend
+ chart[marginNames[side]] = Math.max(
+ chart[marginNames[side]],
+ (
+ chart.legend[
+ (side + 1) % 2 ? 'legendHeight' : 'legendWidth'
+ ] +
+ [1, -1, -1, 1][side] * options[
+ (side % 2) ? 'x' : 'y'
+ ] +
+ pick(options.margin, 12) +
+ spacing[side] +
+ (
+ side === 0 &&
+ chart.options.title.margin !== undefined ?
+ chart.titleOffset +
+ chart.options.title.margin :
+ 0
+ ) // #7428, #7894
+ )
+ );
+ }
+ });
+ }
+ },
+
+ /**
+ * Render the legend. This method can be called both before and after
+ * `chart.render`. If called after, it will only rearrange items instead
+ * of creating new ones. Called internally on initial render and after
+ * redraws.
+ */
+ render: function () {
+ var legend = this,
+ chart = legend.chart,
+ renderer = chart.renderer,
+ legendGroup = legend.group,
+ allItems,
+ display,
+ legendWidth,
+ legendHeight,
+ box = legend.box,
+ options = legend.options,
+ padding = legend.padding,
+ alignTo;
+
+ legend.itemX = padding;
+ legend.itemY = legend.initialItemY;
+ legend.offsetWidth = 0;
+ legend.lastItemY = 0;
+
+ if (!legendGroup) {
+ legend.group = legendGroup = renderer.g('legend')
+ .attr({ zIndex: 7 })
+ .add();
+ legend.contentGroup = renderer.g()
+ .attr({ zIndex: 1 }) // above background
+ .add(legendGroup);
+ legend.scrollGroup = renderer.g()
+ .add(legend.contentGroup);
+ }
+
+ legend.renderTitle();
+
+ // add each series or point
+ allItems = legend.getAllItems();
+
+ // sort by legendIndex
+ stableSort(allItems, function (a, b) {
+ return ((a.options && a.options.legendIndex) || 0) -
+ ((b.options && b.options.legendIndex) || 0);
+ });
+
+ // reversed legend
+ if (options.reversed) {
+ allItems.reverse();
+ }
+
+ legend.allItems = allItems;
+ legend.display = display = !!allItems.length;
+
+ // Render the items. First we run a loop to set the text and properties
+ // and read all the bounding boxes. The next loop computes the item
+ // positions based on the bounding boxes.
+ legend.lastLineHeight = 0;
+ legend.maxItemWidth = 0;
+ legend.totalItemWidth = 0;
+ legend.itemHeight = 0;
+ each(allItems, legend.renderItem, legend);
+ each(allItems, legend.layoutItem, legend);
+
+ // Get the box
+ legendWidth = (options.width || legend.offsetWidth) + padding;
+ legendHeight = legend.lastItemY + legend.lastLineHeight +
+ legend.titleHeight;
+ legendHeight = legend.handleOverflow(legendHeight);
+ legendHeight += padding;
+
+ // Draw the border and/or background
+ if (!box) {
+ legend.box = box = renderer.rect()
+ .addClass('highcharts-legend-box')
+ .attr({
+ r: options.borderRadius
+ })
+ .add(legendGroup);
+ box.isNew = true;
+ }
+
+ /*= if (build.classic) { =*/
+ // Presentational
+ box
+ .attr({
+ stroke: options.borderColor,
+ 'stroke-width': options.borderWidth || 0,
+ fill: options.backgroundColor || 'none'
+ })
+ .shadow(options.shadow);
+ /*= } =*/
+
+ if (legendWidth > 0 && legendHeight > 0) {
+ box[box.isNew ? 'attr' : 'animate'](
+ box.crisp.call({}, { // #7260
+ x: 0,
+ y: 0,
+ width: legendWidth,
+ height: legendHeight
+ }, box.strokeWidth())
+ );
+ box.isNew = false;
+ }
+
+ // hide the border if no items
+ box[display ? 'show' : 'hide']();
+
+ /*= if (!build.classic) { =*/
+ // Open for responsiveness
+ if (legendGroup.getStyle('display') === 'none') {
+ legendWidth = legendHeight = 0;
+ }
+ /*= } =*/
+
+ legend.legendWidth = legendWidth;
+ legend.legendHeight = legendHeight;
+
+ // Now that the legend width and height are established, put the items
+ // in the final position
+ each(allItems, legend.positionItem, legend);
+
+ if (display) {
+ // If aligning to the top and the layout is horizontal, adjust for
+ // the title (#7428)
+ alignTo = chart.spacingBox;
+ if (/(lth|ct|rth)/.test(legend.getAlignment())) {
+ alignTo = merge(alignTo, {
+ y: alignTo.y + chart.titleOffset +
+ chart.options.title.margin
+ });
+ }
+
+ legendGroup.align(merge(options, {
+ width: legendWidth,
+ height: legendHeight
+ }), true, alignTo);
+ }
+
+ if (!chart.isResizing) {
+ this.positionCheckboxes();
+ }
+ },
+
+ /**
+ * Set up the overflow handling by adding navigation with up and down arrows
+ * below the legend.
+ *
+ * @private
+ */
+ handleOverflow: function (legendHeight) {
+ var legend = this,
+ chart = this.chart,
+ renderer = chart.renderer,
+ options = this.options,
+ optionsY = options.y,
+ alignTop = options.verticalAlign === 'top',
+ padding = this.padding,
+ spaceHeight = chart.spacingBox.height +
+ (alignTop ? -optionsY : optionsY) - padding,
+ maxHeight = options.maxHeight,
+ clipHeight,
+ clipRect = this.clipRect,
+ navOptions = options.navigation,
+ animation = pick(navOptions.animation, true),
+ arrowSize = navOptions.arrowSize || 12,
+ nav = this.nav,
+ pages = this.pages,
+ lastY,
+ allItems = this.allItems,
+ clipToHeight = function (height) {
+ if (typeof height === 'number') {
+ clipRect.attr({
+ height: height
+ });
+ } else if (clipRect) { // Reset (#5912)
+ legend.clipRect = clipRect.destroy();
+ legend.contentGroup.clip();
+ }
+
+ // useHTML
+ if (legend.contentGroup.div) {
+ legend.contentGroup.div.style.clip = height ?
+ 'rect(' + padding + 'px,9999px,' +
+ (padding + height) + 'px,0)' :
+ 'auto';
+ }
+ };
+
+
+ // Adjust the height
+ if (
+ options.layout === 'horizontal' &&
+ options.verticalAlign !== 'middle' &&
+ !options.floating
+ ) {
+ spaceHeight /= 2;
+ }
+ if (maxHeight) {
+ spaceHeight = Math.min(spaceHeight, maxHeight);
+ }
+
+ // Reset the legend height and adjust the clipping rectangle
+ pages.length = 0;
+ if (legendHeight > spaceHeight && navOptions.enabled !== false) {
+
+ this.clipHeight = clipHeight =
+ Math.max(spaceHeight - 20 - this.titleHeight - padding, 0);
+ this.currentPage = pick(this.currentPage, 1);
+ this.fullHeight = legendHeight;
+
+ // Fill pages with Y positions so that the top of each a legend item
+ // defines the scroll top for each page (#2098)
+ each(allItems, function (item, i) {
+ var y = item._legendItemPos[1],
+ h = Math.round(item.legendItem.getBBox().height),
+ len = pages.length;
+
+ if (!len || (y - pages[len - 1] > clipHeight &&
+ (lastY || y) !== pages[len - 1])) {
+ pages.push(lastY || y);
+ len++;
+ }
+
+ // Keep track of which page each item is on
+ item.pageIx = len - 1;
+ if (lastY) {
+ allItems[i - 1].pageIx = len - 1;
+ }
+
+ if (i === allItems.length - 1 &&
+ y + h - pages[len - 1] > clipHeight) {
+ pages.push(y);
+ item.pageIx = len;
+ }
+ if (y !== lastY) {
+ lastY = y;
+ }
+ });
+
+ // Only apply clipping if needed. Clipping causes blurred legend in
+ // PDF export (#1787)
+ if (!clipRect) {
+ clipRect = legend.clipRect =
+ renderer.clipRect(0, padding, 9999, 0);
+ legend.contentGroup.clip(clipRect);
+ }
+
+ clipToHeight(clipHeight);
+
+ // Add navigation elements
+ if (!nav) {
+ this.nav = nav = renderer.g()
+ .attr({ zIndex: 1 })
+ .add(this.group);
+
+ this.up = renderer
+ .symbol(
+ 'triangle',
+ 0,
+ 0,
+ arrowSize,
+ arrowSize
+ )
+ .on('click', function () {
+ legend.scroll(-1, animation);
+ })
+ .add(nav);
+
+ this.pager = renderer.text('', 15, 10)
+ .addClass('highcharts-legend-navigation')
+ /*= if (build.classic) { =*/
+ .css(navOptions.style)
+ /*= } =*/
+ .add(nav);
+
+ this.down = renderer
+ .symbol(
+ 'triangle-down',
+ 0,
+ 0,
+ arrowSize,
+ arrowSize
+ )
+ .on('click', function () {
+ legend.scroll(1, animation);
+ })
+ .add(nav);
+ }
+
+ // Set initial position
+ legend.scroll(0);
+
+ legendHeight = spaceHeight;
+
+ // Reset
+ } else if (nav) {
+ clipToHeight();
+ this.nav = nav.destroy(); // #6322
+ this.scrollGroup.attr({
+ translateY: 1
+ });
+ this.clipHeight = 0; // #1379
+ }
+
+ return legendHeight;
+ },
+
+ /**
+ * Scroll the legend by a number of pages.
+ * @param {Number} scrollBy
+ * The number of pages to scroll.
+ * @param {AnimationOptions} animation
+ * Whether and how to apply animation.
+ */
+ scroll: function (scrollBy, animation) {
+ var pages = this.pages,
+ pageCount = pages.length,
+ currentPage = this.currentPage + scrollBy,
+ clipHeight = this.clipHeight,
+ navOptions = this.options.navigation,
+ pager = this.pager,
+ padding = this.padding;
+
+ // When resizing while looking at the last page
+ if (currentPage > pageCount) {
+ currentPage = pageCount;
+ }
+
+ if (currentPage > 0) {
+
+ if (animation !== undefined) {
+ setAnimation(animation, this.chart);
+ }
+
+ this.nav.attr({
+ translateX: padding,
+ translateY: clipHeight + this.padding + 7 + this.titleHeight,
+ visibility: 'visible'
+ });
+ this.up.attr({
+ 'class': currentPage === 1 ?
+ 'highcharts-legend-nav-inactive' :
+ 'highcharts-legend-nav-active'
+ });
+ pager.attr({
+ text: currentPage + '/' + pageCount
+ });
+ this.down.attr({
+ 'x': 18 + this.pager.getBBox().width, // adjust to text width
+ 'class': currentPage === pageCount ?
+ 'highcharts-legend-nav-inactive' :
+ 'highcharts-legend-nav-active'
+ });
+
+ /*= if (build.classic) { =*/
+ this.up
+ .attr({
+ fill: currentPage === 1 ?
+ navOptions.inactiveColor :
+ navOptions.activeColor
+ })
+ .css({
+ cursor: currentPage === 1 ? 'default' : 'pointer'
+ });
+ this.down
+ .attr({
+ fill: currentPage === pageCount ?
+ navOptions.inactiveColor :
+ navOptions.activeColor
+ })
+ .css({
+ cursor: currentPage === pageCount ? 'default' : 'pointer'
+ });
+ /*= } =*/
+
+ this.scrollOffset = -pages[currentPage - 1] + this.initialItemY;
+
+ this.scrollGroup.animate({
+ translateY: this.scrollOffset
+ });
+
+ this.currentPage = currentPage;
+ this.positionCheckboxes();
+ }
+
+ }
};
@@ -1031,107 +1031,107 @@ Highcharts.Legend.prototype = {
H.LegendSymbolMixin = {
- /**
- * Get the series' symbol in the legend
- *
- * @param {Object} legend The legend object
- * @param {Object} item The series (this) or point
- */
- drawRectangle: function (legend, item) {
- var options = legend.options,
- symbolHeight = legend.symbolHeight,
- square = options.squareSymbol,
- symbolWidth = square ? symbolHeight : legend.symbolWidth;
-
- item.legendSymbol = this.chart.renderer.rect(
- square ? (legend.symbolWidth - symbolHeight) / 2 : 0,
- legend.baseline - symbolHeight + 1, // #3988
- symbolWidth,
- symbolHeight,
- pick(legend.options.symbolRadius, symbolHeight / 2)
- )
- .addClass('highcharts-point')
- .attr({
- zIndex: 3
- }).add(item.legendGroup);
-
- },
-
- /**
- * Get the series' symbol in the legend. This method should be overridable
- * to create custom symbols through
- * Highcharts.seriesTypes[type].prototype.drawLegendSymbols.
- *
- * @param {Object} legend The legend object
- */
- drawLineMarker: function (legend) {
-
- var options = this.options,
- markerOptions = options.marker,
- radius,
- legendSymbol,
- symbolWidth = legend.symbolWidth,
- symbolHeight = legend.symbolHeight,
- generalRadius = symbolHeight / 2,
- renderer = this.chart.renderer,
- legendItemGroup = this.legendGroup,
- verticalCenter = legend.baseline -
- Math.round(legend.fontMetrics.b * 0.3),
- attr = {};
-
- // Draw the line
- /*= if (build.classic) { =*/
- attr = {
- 'stroke-width': options.lineWidth || 0
- };
- if (options.dashStyle) {
- attr.dashstyle = options.dashStyle;
- }
- /*= } =*/
-
- this.legendLine = renderer.path([
- 'M',
- 0,
- verticalCenter,
- 'L',
- symbolWidth,
- verticalCenter
- ])
- .addClass('highcharts-graph')
- .attr(attr)
- .add(legendItemGroup);
-
- // Draw the marker
- if (markerOptions && markerOptions.enabled !== false) {
-
- // Do not allow the marker to be larger than the symbolHeight
- radius = Math.min(
- pick(markerOptions.radius, generalRadius),
- generalRadius
- );
-
- // Restrict symbol markers size
- if (this.symbol.indexOf('url') === 0) {
- markerOptions = merge(markerOptions, {
- width: symbolHeight,
- height: symbolHeight
- });
- radius = 0;
- }
-
- this.legendSymbol = legendSymbol = renderer.symbol(
- this.symbol,
- (symbolWidth / 2) - radius,
- verticalCenter - radius,
- 2 * radius,
- 2 * radius,
- markerOptions
- )
- .addClass('highcharts-point')
- .add(legendItemGroup);
- legendSymbol.isMarker = true;
- }
- }
+ /**
+ * Get the series' symbol in the legend
+ *
+ * @param {Object} legend The legend object
+ * @param {Object} item The series (this) or point
+ */
+ drawRectangle: function (legend, item) {
+ var options = legend.options,
+ symbolHeight = legend.symbolHeight,
+ square = options.squareSymbol,
+ symbolWidth = square ? symbolHeight : legend.symbolWidth;
+
+ item.legendSymbol = this.chart.renderer.rect(
+ square ? (legend.symbolWidth - symbolHeight) / 2 : 0,
+ legend.baseline - symbolHeight + 1, // #3988
+ symbolWidth,
+ symbolHeight,
+ pick(legend.options.symbolRadius, symbolHeight / 2)
+ )
+ .addClass('highcharts-point')
+ .attr({
+ zIndex: 3
+ }).add(item.legendGroup);
+
+ },
+
+ /**
+ * Get the series' symbol in the legend. This method should be overridable
+ * to create custom symbols through
+ * Highcharts.seriesTypes[type].prototype.drawLegendSymbols.
+ *
+ * @param {Object} legend The legend object
+ */
+ drawLineMarker: function (legend) {
+
+ var options = this.options,
+ markerOptions = options.marker,
+ radius,
+ legendSymbol,
+ symbolWidth = legend.symbolWidth,
+ symbolHeight = legend.symbolHeight,
+ generalRadius = symbolHeight / 2,
+ renderer = this.chart.renderer,
+ legendItemGroup = this.legendGroup,
+ verticalCenter = legend.baseline -
+ Math.round(legend.fontMetrics.b * 0.3),
+ attr = {};
+
+ // Draw the line
+ /*= if (build.classic) { =*/
+ attr = {
+ 'stroke-width': options.lineWidth || 0
+ };
+ if (options.dashStyle) {
+ attr.dashstyle = options.dashStyle;
+ }
+ /*= } =*/
+
+ this.legendLine = renderer.path([
+ 'M',
+ 0,
+ verticalCenter,
+ 'L',
+ symbolWidth,
+ verticalCenter
+ ])
+ .addClass('highcharts-graph')
+ .attr(attr)
+ .add(legendItemGroup);
+
+ // Draw the marker
+ if (markerOptions && markerOptions.enabled !== false) {
+
+ // Do not allow the marker to be larger than the symbolHeight
+ radius = Math.min(
+ pick(markerOptions.radius, generalRadius),
+ generalRadius
+ );
+
+ // Restrict symbol markers size
+ if (this.symbol.indexOf('url') === 0) {
+ markerOptions = merge(markerOptions, {
+ width: symbolHeight,
+ height: symbolHeight
+ });
+ radius = 0;
+ }
+
+ this.legendSymbol = legendSymbol = renderer.symbol(
+ this.symbol,
+ (symbolWidth / 2) - radius,
+ verticalCenter - radius,
+ 2 * radius,
+ 2 * radius,
+ markerOptions
+ )
+ .addClass('highcharts-point')
+ .add(legendItemGroup);
+ legendSymbol.isMarker = true;
+ }
+ }
};
// Workaround for #2030, horizontal legend items not displaying in IE11 Preview,
@@ -1140,19 +1140,19 @@ H.LegendSymbolMixin = {
// to nested group elements, as the legend item texts are within 4 group
// elements.
if (/Trident\/7\.0/.test(win.navigator.userAgent) || isFirefox) {
- wrap(Highcharts.Legend.prototype, 'positionItem', function (proceed, item) {
- var legend = this,
- // If chart destroyed in sync, this is undefined (#2030)
- runPositionItem = function () {
- if (item._legendItemPos) {
- proceed.call(legend, item);
- }
- };
-
- // Do it now, for export and to get checkbox placement
- runPositionItem();
-
- // Do it after to work around the core issue
- setTimeout(runPositionItem);
- });
+ wrap(Highcharts.Legend.prototype, 'positionItem', function (proceed, item) {
+ var legend = this,
+ // If chart destroyed in sync, this is undefined (#2030)
+ runPositionItem = function () {
+ if (item._legendItemPos) {
+ proceed.call(legend, item);
+ }
+ };
+
+ // Do it now, for export and to get checkbox placement
+ runPositionItem();
+
+ // Do it after to work around the core issue
+ setTimeout(runPositionItem);
+ });
}
diff --git a/js/parts/LogarithmicAxis.js b/js/parts/LogarithmicAxis.js
index 13026a41432..17b107f2abc 100644
--- a/js/parts/LogarithmicAxis.js
+++ b/js/parts/LogarithmicAxis.js
@@ -8,10 +8,10 @@
import H from './Globals.js';
import './Utilities.js';
var Axis = H.Axis,
- getMagnitude = H.getMagnitude,
- map = H.map,
- normalizeTickInterval = H.normalizeTickInterval,
- pick = H.pick;
+ getMagnitude = H.getMagnitude,
+ map = H.map,
+ normalizeTickInterval = H.normalizeTickInterval,
+ pick = H.pick;
/**
* Methods defined on the Axis prototype
*/
@@ -20,107 +20,107 @@ var Axis = H.Axis,
* Set the tick positions of a logarithmic axis
*/
Axis.prototype.getLogTickPositions = function (interval, min, max, minor) {
- var axis = this,
- options = axis.options,
- axisLength = axis.len,
- lin2log = axis.lin2log,
- log2lin = axis.log2lin,
- // Since we use this method for both major and minor ticks,
- // use a local variable and return the result
- positions = [];
+ var axis = this,
+ options = axis.options,
+ axisLength = axis.len,
+ lin2log = axis.lin2log,
+ log2lin = axis.log2lin,
+ // Since we use this method for both major and minor ticks,
+ // use a local variable and return the result
+ positions = [];
- // Reset
- if (!minor) {
- axis._minorAutoInterval = null;
- }
+ // Reset
+ if (!minor) {
+ axis._minorAutoInterval = null;
+ }
- // First case: All ticks fall on whole logarithms: 1, 10, 100 etc.
- if (interval >= 0.5) {
- interval = Math.round(interval);
- positions = axis.getLinearTickPositions(interval, min, max);
+ // First case: All ticks fall on whole logarithms: 1, 10, 100 etc.
+ if (interval >= 0.5) {
+ interval = Math.round(interval);
+ positions = axis.getLinearTickPositions(interval, min, max);
- // Second case: We need intermediary ticks. For example
- // 1, 2, 4, 6, 8, 10, 20, 40 etc.
- } else if (interval >= 0.08) {
- var roundedMin = Math.floor(min),
- intermediate,
- i,
- j,
- len,
- pos,
- lastPos,
- break2;
+ // Second case: We need intermediary ticks. For example
+ // 1, 2, 4, 6, 8, 10, 20, 40 etc.
+ } else if (interval >= 0.08) {
+ var roundedMin = Math.floor(min),
+ intermediate,
+ i,
+ j,
+ len,
+ pos,
+ lastPos,
+ break2;
- if (interval > 0.3) {
- intermediate = [1, 2, 4];
- } else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc
- intermediate = [1, 2, 4, 6, 8];
- } else { // 0.1 equals ten minor ticks per 1, 10, 100 etc
- intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
- }
+ if (interval > 0.3) {
+ intermediate = [1, 2, 4];
+ } else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc
+ intermediate = [1, 2, 4, 6, 8];
+ } else { // 0.1 equals ten minor ticks per 1, 10, 100 etc
+ intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
+ }
- for (i = roundedMin; i < max + 1 && !break2; i++) {
- len = intermediate.length;
- for (j = 0; j < len && !break2; j++) {
- pos = log2lin(lin2log(i) * intermediate[j]);
- if (pos > min && (!minor || lastPos <= max) && lastPos !== undefined) { // #1670, lastPos is #3113
- positions.push(lastPos);
- }
+ for (i = roundedMin; i < max + 1 && !break2; i++) {
+ len = intermediate.length;
+ for (j = 0; j < len && !break2; j++) {
+ pos = log2lin(lin2log(i) * intermediate[j]);
+ if (pos > min && (!minor || lastPos <= max) && lastPos !== undefined) { // #1670, lastPos is #3113
+ positions.push(lastPos);
+ }
- if (lastPos > max) {
- break2 = true;
- }
- lastPos = pos;
- }
- }
+ if (lastPos > max) {
+ break2 = true;
+ }
+ lastPos = pos;
+ }
+ }
- // Third case: We are so deep in between whole logarithmic values that
- // we might as well handle the tick positions like a linear axis. For
- // example 1.01, 1.02, 1.03, 1.04.
- } else {
- var realMin = lin2log(min),
- realMax = lin2log(max),
- tickIntervalOption = minor ?
- this.getMinorTickInterval() :
- options.tickInterval,
- filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption,
- tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1),
- totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength;
+ // Third case: We are so deep in between whole logarithmic values that
+ // we might as well handle the tick positions like a linear axis. For
+ // example 1.01, 1.02, 1.03, 1.04.
+ } else {
+ var realMin = lin2log(min),
+ realMax = lin2log(max),
+ tickIntervalOption = minor ?
+ this.getMinorTickInterval() :
+ options.tickInterval,
+ filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption,
+ tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1),
+ totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength;
- interval = pick(
- filteredTickIntervalOption,
- axis._minorAutoInterval,
- (realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1)
- );
+ interval = pick(
+ filteredTickIntervalOption,
+ axis._minorAutoInterval,
+ (realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1)
+ );
- interval = normalizeTickInterval(
- interval,
- null,
- getMagnitude(interval)
- );
+ interval = normalizeTickInterval(
+ interval,
+ null,
+ getMagnitude(interval)
+ );
- positions = map(axis.getLinearTickPositions(
- interval,
- realMin,
- realMax
- ), log2lin);
+ positions = map(axis.getLinearTickPositions(
+ interval,
+ realMin,
+ realMax
+ ), log2lin);
- if (!minor) {
- axis._minorAutoInterval = interval / 5;
- }
- }
+ if (!minor) {
+ axis._minorAutoInterval = interval / 5;
+ }
+ }
- // Set the axis-level tickInterval variable
- if (!minor) {
- axis.tickInterval = interval;
- }
- return positions;
+ // Set the axis-level tickInterval variable
+ if (!minor) {
+ axis.tickInterval = interval;
+ }
+ return positions;
};
Axis.prototype.log2lin = function (num) {
- return Math.log(num) / Math.LN10;
+ return Math.log(num) / Math.LN10;
};
Axis.prototype.lin2log = function (num) {
- return Math.pow(10, num);
+ return Math.pow(10, num);
};
diff --git a/js/parts/MSPointer.js b/js/parts/MSPointer.js
index aa23b8b2aff..a0c0f1ede3a 100644
--- a/js/parts/MSPointer.js
+++ b/js/parts/MSPointer.js
@@ -9,104 +9,104 @@ import H from './Globals.js';
import './Utilities.js';
import './Pointer.js';
var addEvent = H.addEvent,
- charts = H.charts,
- css = H.css,
- doc = H.doc,
- extend = H.extend,
- hasTouch = H.hasTouch,
- noop = H.noop,
- Pointer = H.Pointer,
- removeEvent = H.removeEvent,
- win = H.win,
- wrap = H.wrap;
+ charts = H.charts,
+ css = H.css,
+ doc = H.doc,
+ extend = H.extend,
+ hasTouch = H.hasTouch,
+ noop = H.noop,
+ Pointer = H.Pointer,
+ removeEvent = H.removeEvent,
+ win = H.win,
+ wrap = H.wrap;
if (!hasTouch && (win.PointerEvent || win.MSPointerEvent)) {
-
- // The touches object keeps track of the points being touched at all times
- var touches = {},
- hasPointerEvent = !!win.PointerEvent,
- getWebkitTouches = function () {
- var fake = [];
- fake.item = function (i) {
- return this[i];
- };
- H.objectEach(touches, function (touch) {
- fake.push({
- pageX: touch.pageX,
- pageY: touch.pageY,
- target: touch.target
- });
- });
- return fake;
- },
- translateMSPointer = function (e, method, wktype, func) {
- var p;
- if ((e.pointerType === 'touch' || e.pointerType === e.MSPOINTER_TYPE_TOUCH) && charts[H.hoverChartIndex]) {
- func(e);
- p = charts[H.hoverChartIndex].pointer;
- p[method]({
- type: wktype,
- target: e.currentTarget,
- preventDefault: noop,
- touches: getWebkitTouches()
- });
- }
- };
- /**
- * Extend the Pointer prototype with methods for each event handler and more
- */
- extend(Pointer.prototype, /** @lends Pointer.prototype */ {
- onContainerPointerDown: function (e) {
- translateMSPointer(e, 'onContainerTouchStart', 'touchstart', function (e) {
- touches[e.pointerId] = { pageX: e.pageX, pageY: e.pageY, target: e.currentTarget };
- });
- },
- onContainerPointerMove: function (e) {
- translateMSPointer(e, 'onContainerTouchMove', 'touchmove', function (e) {
- touches[e.pointerId] = { pageX: e.pageX, pageY: e.pageY };
- if (!touches[e.pointerId].target) {
- touches[e.pointerId].target = e.currentTarget;
- }
- });
- },
- onDocumentPointerUp: function (e) {
- translateMSPointer(e, 'onDocumentTouchEnd', 'touchend', function (e) {
- delete touches[e.pointerId];
- });
- },
+ // The touches object keeps track of the points being touched at all times
+ var touches = {},
+ hasPointerEvent = !!win.PointerEvent,
+ getWebkitTouches = function () {
+ var fake = [];
+ fake.item = function (i) {
+ return this[i];
+ };
+ H.objectEach(touches, function (touch) {
+ fake.push({
+ pageX: touch.pageX,
+ pageY: touch.pageY,
+ target: touch.target
+ });
+ });
+ return fake;
+ },
+ translateMSPointer = function (e, method, wktype, func) {
+ var p;
+ if ((e.pointerType === 'touch' || e.pointerType === e.MSPOINTER_TYPE_TOUCH) && charts[H.hoverChartIndex]) {
+ func(e);
+ p = charts[H.hoverChartIndex].pointer;
+ p[method]({
+ type: wktype,
+ target: e.currentTarget,
+ preventDefault: noop,
+ touches: getWebkitTouches()
+ });
+ }
+ };
- /**
- * Add or remove the MS Pointer specific events
- */
- batchMSEvents: function (fn) {
- fn(this.chart.container, hasPointerEvent ? 'pointerdown' : 'MSPointerDown', this.onContainerPointerDown);
- fn(this.chart.container, hasPointerEvent ? 'pointermove' : 'MSPointerMove', this.onContainerPointerMove);
- fn(doc, hasPointerEvent ? 'pointerup' : 'MSPointerUp', this.onDocumentPointerUp);
- }
- });
+ /**
+ * Extend the Pointer prototype with methods for each event handler and more
+ */
+ extend(Pointer.prototype, /** @lends Pointer.prototype */ {
+ onContainerPointerDown: function (e) {
+ translateMSPointer(e, 'onContainerTouchStart', 'touchstart', function (e) {
+ touches[e.pointerId] = { pageX: e.pageX, pageY: e.pageY, target: e.currentTarget };
+ });
+ },
+ onContainerPointerMove: function (e) {
+ translateMSPointer(e, 'onContainerTouchMove', 'touchmove', function (e) {
+ touches[e.pointerId] = { pageX: e.pageX, pageY: e.pageY };
+ if (!touches[e.pointerId].target) {
+ touches[e.pointerId].target = e.currentTarget;
+ }
+ });
+ },
+ onDocumentPointerUp: function (e) {
+ translateMSPointer(e, 'onDocumentTouchEnd', 'touchend', function (e) {
+ delete touches[e.pointerId];
+ });
+ },
- // Disable default IE actions for pinch and such on chart element
- wrap(Pointer.prototype, 'init', function (proceed, chart, options) {
- proceed.call(this, chart, options);
- if (this.hasZoom) { // #4014
- css(chart.container, {
- '-ms-touch-action': 'none',
- 'touch-action': 'none'
- });
- }
- });
+ /**
+ * Add or remove the MS Pointer specific events
+ */
+ batchMSEvents: function (fn) {
+ fn(this.chart.container, hasPointerEvent ? 'pointerdown' : 'MSPointerDown', this.onContainerPointerDown);
+ fn(this.chart.container, hasPointerEvent ? 'pointermove' : 'MSPointerMove', this.onContainerPointerMove);
+ fn(doc, hasPointerEvent ? 'pointerup' : 'MSPointerUp', this.onDocumentPointerUp);
+ }
+ });
- // Add IE specific touch events to chart
- wrap(Pointer.prototype, 'setDOMEvents', function (proceed) {
- proceed.apply(this);
- if (this.hasZoom || this.followTouchMove) {
- this.batchMSEvents(addEvent);
- }
- });
- // Destroy MS events also
- wrap(Pointer.prototype, 'destroy', function (proceed) {
- this.batchMSEvents(removeEvent);
- proceed.call(this);
- });
+ // Disable default IE actions for pinch and such on chart element
+ wrap(Pointer.prototype, 'init', function (proceed, chart, options) {
+ proceed.call(this, chart, options);
+ if (this.hasZoom) { // #4014
+ css(chart.container, {
+ '-ms-touch-action': 'none',
+ 'touch-action': 'none'
+ });
+ }
+ });
+
+ // Add IE specific touch events to chart
+ wrap(Pointer.prototype, 'setDOMEvents', function (proceed) {
+ proceed.apply(this);
+ if (this.hasZoom || this.followTouchMove) {
+ this.batchMSEvents(addEvent);
+ }
+ });
+ // Destroy MS events also
+ wrap(Pointer.prototype, 'destroy', function (proceed) {
+ this.batchMSEvents(removeEvent);
+ proceed.call(this);
+ });
}
diff --git a/js/parts/Navigator.js b/js/parts/Navigator.js
index b8c7f83d6e2..766a6b9ceb1 100644
--- a/js/parts/Navigator.js
+++ b/js/parts/Navigator.js
@@ -3,7 +3,7 @@
*
* License: www.highcharts.com/license
*/
-
+
'use strict';
import H from './Globals.js';
import './Utilities.js';
@@ -45,456 +45,456 @@ import './Scrollbar.js';
*/
var addEvent = H.addEvent,
- Axis = H.Axis,
- Chart = H.Chart,
- color = H.color,
- defaultDataGroupingUnits = H.defaultDataGroupingUnits,
- defaultOptions = H.defaultOptions,
- defined = H.defined,
- destroyObjectProperties = H.destroyObjectProperties,
- 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,
- Scrollbar = H.Scrollbar,
- Series = H.Series,
- seriesTypes = H.seriesTypes,
- wrap = H.wrap,
-
- units = [].concat(defaultDataGroupingUnits), // copy
- defaultSeriesType,
-
- // Finding the min or max of a set of variables where we don't know if they
- // are defined, is a pattern that is repeated several places in Highcharts.
- // Consider making this a global utility method.
- numExt = function (extreme) {
- var numbers = grep(arguments, isNumber);
- if (numbers.length) {
- return Math[extreme].apply(0, numbers);
- }
- };
+ Axis = H.Axis,
+ Chart = H.Chart,
+ color = H.color,
+ defaultDataGroupingUnits = H.defaultDataGroupingUnits,
+ defaultOptions = H.defaultOptions,
+ defined = H.defined,
+ destroyObjectProperties = H.destroyObjectProperties,
+ 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,
+ Scrollbar = H.Scrollbar,
+ Series = H.Series,
+ seriesTypes = H.seriesTypes,
+ wrap = H.wrap,
+
+ units = [].concat(defaultDataGroupingUnits), // copy
+ defaultSeriesType,
+
+ // Finding the min or max of a set of variables where we don't know if they
+ // are defined, is a pattern that is repeated several places in Highcharts.
+ // Consider making this a global utility method.
+ numExt = function (extreme) {
+ var numbers = grep(arguments, isNumber);
+ if (numbers.length) {
+ return Math[extreme].apply(0, numbers);
+ }
+ };
// add more resolution to units
units[4] = ['day', [1, 2, 3, 4]]; // allow more days
units[5] = ['week', [1, 2, 3]]; // allow more weeks
defaultSeriesType = seriesTypes.areaspline === undefined ?
- 'line' :
- 'areaspline';
+ '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.
- *
- * @product highstock
- * @optionparent navigator
- */
- navigator: {
- /**
- * 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.
- *
- * @type {Object}
- * @sample {highstock} stock/navigator/handles/ Colored handles
- * @product highstock
- */
- handles: {
- /**
- * Width for handles.
- *
- * @type {Number}
- * @default 7
- * @product highstock
- * @sample {highstock} stock/navigator/styled-handles/
- * Styled handles
- * @since 6.0.0
- */
- width: 7,
-
- /**
- * Height for handles.
- *
- * @type {Number}
- * @default 15
- * @product highstock
- * @sample {highstock} stock/navigator/styled-handles/
- * Styled handles
- * @since 6.0.0
- */
- height: 15,
-
- /**
- * Array to define shapes of handles. 0-index for left, 1-index for
- * right.
- *
- * Additionally, the URL to a graphic can be given on this form:
- * `url(graphic.png)`. Note that for the image to be applied to
- * exported charts, its URL needs to be accessible by the export
- * server.
- *
- * Custom callbacks for symbol path generation can also be added to
- * `Highcharts.SVGRenderer.prototype.symbols`. The callback is then
- * used by its method name, as shown in the demo.
- *
- * @type {Array}
- * @default ['navigator-handle', 'navigator-handle']
- * @product highstock
- * @sample {highstock} stock/navigator/styled-handles/
- * Styled handles
- * @since 6.0.0
- */
- symbols: ['navigator-handle', 'navigator-handle'],
-
- /**
- * Allows to enable/disable handles.
- *
- * @type {Boolean}
- * @default true
- * @product highstock
- * @since 6.0.0
- */
- enabled: true,
-
- /*= if (build.classic) { =*/
- /**
- * The width for the handle border and the stripes inside.
- *
- * @type {Number}
- * @default 7
- * @product highstock
- * @sample {highstock} stock/navigator/styled-handles/
- * Styled handles
- * @since 6.0.0
- */
- lineWidth: 1,
-
- /**
- * The fill for the handle.
- *
- * @type {Color}
- * @product highstock
- */
- backgroundColor: '${palette.neutralColor5}',
-
- /**
- * The stroke for the handle border and the stripes inside.
- *
- * @type {Color}
- * @product highstock
- */
- borderColor: '${palette.neutralColor40}'
-
- /*= } =*/
- },
-
- /*= if (build.classic) { =*/
-
- /**
- * 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, 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('${palette.highlightColor60}').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: '${palette.neutralColor20}',
-
- /**
- * The width of the line marking the currently zoomed area in the
- * navigator.
- *
- * @type {Number}
- * @see In styled mode, 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:
- *
- * series: {
- * type: 'areaspline',
- * fillOpacity: 0.05,
- * dataGrouping: {
- * smoothed: true
- * },
- * lineWidth: 1,
- * marker: {
- * enabled: false
- * }
- * }
- *
- * @type {Object}
- * @see In styled mode, 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: {
-
- /**
- * The type of the navigator series. Defaults to `areaspline` if
- * defined, otherwise `line`.
- *
- * @type {String}
- */
- type: defaultSeriesType,
- /*= if (build.classic) { =*/
-
-
- /**
- * The fill opacity of the navigator series.
- */
- fillOpacity: 0.05,
-
- /**
- * The pixel line width of the navigator series.
- */
- lineWidth: 1,
- /*= } =*/
-
- /**
- * @ignore
- */
- compare: null,
-
- /**
- * Data grouping options for the navigator series.
- *
- * @extends {plotOptions.series.dataGrouping}
- */
- dataGrouping: {
- approximation: 'average',
- enabled: true,
- groupPixelWidth: 2,
- smoothed: true,
- units: units
- },
-
- /**
- * Data label options for the navigator series. Data labels are
- * disabled by default on the navigator series.
- *
- * @extends {plotOptions.series.dataLabels}
- */
- dataLabels: {
- enabled: false,
- zIndex: 2 // #1839
- },
-
- id: 'highcharts-navigator-series',
- className: 'highcharts-navigator-series',
-
- /**
- * Line color for the navigator series. Allows setting the color
- * while disallowing the default candlestick setting.
- *
- * @type {Color}
- */
- lineColor: null, // #4602
-
- marker: {
- enabled: false
- },
-
- pointRange: 0,
- /**
- * The threshold option. Setting it to 0 will make the default
- * navigator area series draw its area from the 0 value and up.
- * @type {Number}
- */
- threshold: null
- },
-
- /**
- * Options for the navigator X axis. Default series options
- * for the navigator xAxis are:
- *
- * xAxis: {
- * tickWidth: 0,
- * lineWidth: 0,
- * gridLineWidth: 1,
- * tickPixelInterval: 200,
- * labels: {
- * align: 'left',
- * style: {
- * color: '#888'
- * },
- * x: 3,
- * y: -4
- * }
- * }
- *
- * @type {Object}
- * @extends {xAxis}
- * @excluding linkedTo,maxZoom,minRange,opposite,range,scrollbar,
- * showEmpty,maxRange
- * @product highstock
- */
- xAxis: {
- /**
- * Additional range on the right side of the xAxis. Works similar to
- * xAxis.maxPadding, but value is set in milliseconds.
- * Can be set for both, main xAxis and navigator's xAxis.
- *
- * @type {Number}
- * @default 0
- * @since 6.0.0
- * @product highstock
- * @apioption xAxis.overscroll
- */
- overscroll: 0,
-
- className: 'highcharts-navigator-xaxis',
- tickLength: 0,
-
- /*= if (build.classic) { =*/
- lineWidth: 0,
- gridLineColor: '${palette.neutralColor10}',
- gridLineWidth: 1,
- /*= } =*/
-
- tickPixelInterval: 200,
-
- labels: {
- align: 'left',
-
- /*= if (build.classic) { =*/
- style: {
- color: '${palette.neutralColor40}'
- },
- /*= } =*/
-
- x: 3,
- y: -4
- },
-
- crosshair: false
- },
-
- /**
- * Options for the navigator Y axis. Default series options
- * for the navigator yAxis are:
- *
- * yAxis: {
- * gridLineWidth: 0,
- * startOnTick: false,
- * endOnTick: false,
- * minPadding: 0.1,
- * maxPadding: 0.1,
- * labels: {
- * enabled: false
- * },
- * title: {
- * text: null
- * },
- * tickWidth: 0
- * }
- *
- * @type {Object}
- * @extends {yAxis}
- * @excluding height,linkedTo,maxZoom,minRange,ordinal,range,showEmpty,
- * scrollbar,top,units,maxRange,minLength,maxLength,resize
- * @product highstock
- */
- yAxis: {
-
- className: 'highcharts-navigator-yaxis',
-
- /*= if (build.classic) { =*/
- gridLineWidth: 0,
- /*= } =*/
-
- startOnTick: false,
- endOnTick: false,
- minPadding: 0.1,
- maxPadding: 0.1,
- labels: {
- enabled: false
- },
- crosshair: false,
- title: {
- text: null
- },
- tickLength: 0,
- tickWidth: 0
- }
- }
+ /**
+ * 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.
+ *
+ * @product highstock
+ * @optionparent navigator
+ */
+ navigator: {
+ /**
+ * 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.
+ *
+ * @type {Object}
+ * @sample {highstock} stock/navigator/handles/ Colored handles
+ * @product highstock
+ */
+ handles: {
+ /**
+ * Width for handles.
+ *
+ * @type {Number}
+ * @default 7
+ * @product highstock
+ * @sample {highstock} stock/navigator/styled-handles/
+ * Styled handles
+ * @since 6.0.0
+ */
+ width: 7,
+
+ /**
+ * Height for handles.
+ *
+ * @type {Number}
+ * @default 15
+ * @product highstock
+ * @sample {highstock} stock/navigator/styled-handles/
+ * Styled handles
+ * @since 6.0.0
+ */
+ height: 15,
+
+ /**
+ * Array to define shapes of handles. 0-index for left, 1-index for
+ * right.
+ *
+ * Additionally, the URL to a graphic can be given on this form:
+ * `url(graphic.png)`. Note that for the image to be applied to
+ * exported charts, its URL needs to be accessible by the export
+ * server.
+ *
+ * Custom callbacks for symbol path generation can also be added to
+ * `Highcharts.SVGRenderer.prototype.symbols`. The callback is then
+ * used by its method name, as shown in the demo.
+ *
+ * @type {Array}
+ * @default ['navigator-handle', 'navigator-handle']
+ * @product highstock
+ * @sample {highstock} stock/navigator/styled-handles/
+ * Styled handles
+ * @since 6.0.0
+ */
+ symbols: ['navigator-handle', 'navigator-handle'],
+
+ /**
+ * Allows to enable/disable handles.
+ *
+ * @type {Boolean}
+ * @default true
+ * @product highstock
+ * @since 6.0.0
+ */
+ enabled: true,
+
+ /*= if (build.classic) { =*/
+ /**
+ * The width for the handle border and the stripes inside.
+ *
+ * @type {Number}
+ * @default 7
+ * @product highstock
+ * @sample {highstock} stock/navigator/styled-handles/
+ * Styled handles
+ * @since 6.0.0
+ */
+ lineWidth: 1,
+
+ /**
+ * The fill for the handle.
+ *
+ * @type {Color}
+ * @product highstock
+ */
+ backgroundColor: '${palette.neutralColor5}',
+
+ /**
+ * The stroke for the handle border and the stripes inside.
+ *
+ * @type {Color}
+ * @product highstock
+ */
+ borderColor: '${palette.neutralColor40}'
+
+ /*= } =*/
+ },
+
+ /*= if (build.classic) { =*/
+
+ /**
+ * 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, 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('${palette.highlightColor60}').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: '${palette.neutralColor20}',
+
+ /**
+ * The width of the line marking the currently zoomed area in the
+ * navigator.
+ *
+ * @type {Number}
+ * @see In styled mode, 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:
+ *
+ * series: {
+ * type: 'areaspline',
+ * fillOpacity: 0.05,
+ * dataGrouping: {
+ * smoothed: true
+ * },
+ * lineWidth: 1,
+ * marker: {
+ * enabled: false
+ * }
+ * }
+ *
+ * @type {Object}
+ * @see In styled mode, 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: {
+
+ /**
+ * The type of the navigator series. Defaults to `areaspline` if
+ * defined, otherwise `line`.
+ *
+ * @type {String}
+ */
+ type: defaultSeriesType,
+ /*= if (build.classic) { =*/
+
+
+ /**
+ * The fill opacity of the navigator series.
+ */
+ fillOpacity: 0.05,
+
+ /**
+ * The pixel line width of the navigator series.
+ */
+ lineWidth: 1,
+ /*= } =*/
+
+ /**
+ * @ignore
+ */
+ compare: null,
+
+ /**
+ * Data grouping options for the navigator series.
+ *
+ * @extends {plotOptions.series.dataGrouping}
+ */
+ dataGrouping: {
+ approximation: 'average',
+ enabled: true,
+ groupPixelWidth: 2,
+ smoothed: true,
+ units: units
+ },
+
+ /**
+ * Data label options for the navigator series. Data labels are
+ * disabled by default on the navigator series.
+ *
+ * @extends {plotOptions.series.dataLabels}
+ */
+ dataLabels: {
+ enabled: false,
+ zIndex: 2 // #1839
+ },
+
+ id: 'highcharts-navigator-series',
+ className: 'highcharts-navigator-series',
+
+ /**
+ * Line color for the navigator series. Allows setting the color
+ * while disallowing the default candlestick setting.
+ *
+ * @type {Color}
+ */
+ lineColor: null, // #4602
+
+ marker: {
+ enabled: false
+ },
+
+ pointRange: 0,
+ /**
+ * The threshold option. Setting it to 0 will make the default
+ * navigator area series draw its area from the 0 value and up.
+ * @type {Number}
+ */
+ threshold: null
+ },
+
+ /**
+ * Options for the navigator X axis. Default series options
+ * for the navigator xAxis are:
+ *
+ * xAxis: {
+ * tickWidth: 0,
+ * lineWidth: 0,
+ * gridLineWidth: 1,
+ * tickPixelInterval: 200,
+ * labels: {
+ * align: 'left',
+ * style: {
+ * color: '#888'
+ * },
+ * x: 3,
+ * y: -4
+ * }
+ * }
+ *
+ * @type {Object}
+ * @extends {xAxis}
+ * @excluding linkedTo,maxZoom,minRange,opposite,range,scrollbar,
+ * showEmpty,maxRange
+ * @product highstock
+ */
+ xAxis: {
+ /**
+ * Additional range on the right side of the xAxis. Works similar to
+ * xAxis.maxPadding, but value is set in milliseconds.
+ * Can be set for both, main xAxis and navigator's xAxis.
+ *
+ * @type {Number}
+ * @default 0
+ * @since 6.0.0
+ * @product highstock
+ * @apioption xAxis.overscroll
+ */
+ overscroll: 0,
+
+ className: 'highcharts-navigator-xaxis',
+ tickLength: 0,
+
+ /*= if (build.classic) { =*/
+ lineWidth: 0,
+ gridLineColor: '${palette.neutralColor10}',
+ gridLineWidth: 1,
+ /*= } =*/
+
+ tickPixelInterval: 200,
+
+ labels: {
+ align: 'left',
+
+ /*= if (build.classic) { =*/
+ style: {
+ color: '${palette.neutralColor40}'
+ },
+ /*= } =*/
+
+ x: 3,
+ y: -4
+ },
+
+ crosshair: false
+ },
+
+ /**
+ * Options for the navigator Y axis. Default series options
+ * for the navigator yAxis are:
+ *
+ * yAxis: {
+ * gridLineWidth: 0,
+ * startOnTick: false,
+ * endOnTick: false,
+ * minPadding: 0.1,
+ * maxPadding: 0.1,
+ * labels: {
+ * enabled: false
+ * },
+ * title: {
+ * text: null
+ * },
+ * tickWidth: 0
+ * }
+ *
+ * @type {Object}
+ * @extends {yAxis}
+ * @excluding height,linkedTo,maxZoom,minRange,ordinal,range,showEmpty,
+ * scrollbar,top,units,maxRange,minLength,maxLength,resize
+ * @product highstock
+ */
+ yAxis: {
+
+ className: 'highcharts-navigator-yaxis',
+
+ /*= if (build.classic) { =*/
+ gridLineWidth: 0,
+ /*= } =*/
+
+ startOnTick: false,
+ endOnTick: false,
+ minPadding: 0.1,
+ maxPadding: 0.1,
+ labels: {
+ enabled: false
+ },
+ crosshair: false,
+ title: {
+ text: null
+ },
+ tickLength: 0,
+ tickWidth: 0
+ }
+ }
});
/**
@@ -503,36 +503,36 @@ extend(defaultOptions, {
* @returns {Array} Path to be used in a handle
*/
H.Renderer.prototype.symbols['navigator-handle'] = function (
- x,
- y,
- w,
- h,
- options
+ x,
+ y,
+ w,
+ h,
+ options
) {
- var halfWidth = options.width / 2,
- markerPosition = Math.round(halfWidth / 3) + 0.5,
- height = options.height;
-
- return [
- 'M',
- -halfWidth - 1, 0.5,
- 'L',
- halfWidth, 0.5,
- 'L',
- halfWidth, height + 0.5,
- 'L',
- -halfWidth - 1, height + 0.5,
- 'L',
- -halfWidth - 1, 0.5,
- 'M',
- -markerPosition, 4,
- 'L',
- -markerPosition, height - 3,
- 'M',
- markerPosition - 1, 4,
- 'L',
- markerPosition - 1, height - 3
- ];
+ var halfWidth = options.width / 2,
+ markerPosition = Math.round(halfWidth / 3) + 0.5,
+ height = options.height;
+
+ return [
+ 'M',
+ -halfWidth - 1, 0.5,
+ 'L',
+ halfWidth, 0.5,
+ 'L',
+ halfWidth, height + 0.5,
+ 'L',
+ -halfWidth - 1, height + 0.5,
+ 'L',
+ -halfWidth - 1, 0.5,
+ 'M',
+ -markerPosition, 4,
+ 'L',
+ -markerPosition, height - 3,
+ 'M',
+ markerPosition - 1, 4,
+ 'L',
+ markerPosition - 1, height - 3
+ ];
};
/**
@@ -541,1490 +541,1490 @@ H.Renderer.prototype.symbols['navigator-handle'] = function (
* @class
*/
function Navigator(chart) {
- this.init(chart);
+ this.init(chart);
}
Navigator.prototype = {
- /**
- * Draw one of the handles on the side of the zoomed range in the navigator
- * @param {Number} x The x center for the handle
- * @param {Number} index 0 for left and 1 for right
- * @param {Boolean} inverted flag for chart.inverted
- * @param {String} verb use 'animate' or 'attr'
- */
- drawHandle: function (x, index, inverted, verb) {
- var navigator = this,
- height = navigator.navigatorOptions.handles.height;
-
- // Place it
- navigator.handles[index][verb](inverted ? {
- translateX: Math.round(navigator.left + navigator.height / 2),
- translateY: Math.round(
- navigator.top + parseInt(x, 10) + 0.5 - height
- )
- } : {
- translateX: Math.round(navigator.left + parseInt(x, 10)),
- translateY: Math.round(
- navigator.top + navigator.height / 2 - height / 2 - 1
- )
- });
- },
-
- /**
- * Render outline around the zoomed range
- * @param {Number} zoomedMin in pixels position where zoomed range starts
- * @param {Number} zoomedMax in pixels position where zoomed range ends
- * @param {Boolean} inverted flag if chart is inverted
- * @param {String} verb use 'animate' or 'attr'
- */
- drawOutline: function (zoomedMin, zoomedMax, inverted, verb) {
- var navigator = this,
- maskInside = navigator.navigatorOptions.maskInside,
- outlineWidth = navigator.outline.strokeWidth(),
- halfOutline = outlineWidth / 2,
- outlineCorrection = (outlineWidth % 2) / 2, // #5800
- outlineHeight = navigator.outlineHeight,
- scrollbarHeight = navigator.scrollbarHeight,
- navigatorSize = navigator.size,
- left = navigator.left - scrollbarHeight,
- navigatorTop = navigator.top,
- verticalMin,
- path;
-
- if (inverted) {
- left -= halfOutline;
- verticalMin = navigatorTop + zoomedMax + outlineCorrection;
- zoomedMax = navigatorTop + zoomedMin + outlineCorrection;
-
- path = [
- 'M',
- left + outlineHeight,
- navigatorTop - scrollbarHeight - outlineCorrection, // top edge
- 'L',
- left + outlineHeight,
- verticalMin, // top right of zoomed range
- 'L',
- left,
- verticalMin, // top left of z.r.
- 'L',
- left,
- zoomedMax, // bottom left of z.r.
- 'L',
- left + outlineHeight,
- zoomedMax, // bottom right of z.r.
- 'L',
- left + outlineHeight,
- navigatorTop + navigatorSize + scrollbarHeight // bottom edge
- ].concat(maskInside ? [
- 'M',
- left + outlineHeight,
- verticalMin - halfOutline, // upper left of zoomed range
- 'L',
- left + outlineHeight,
- zoomedMax + halfOutline // upper right of z.r.
- ] : []);
- } else {
- zoomedMin += left + scrollbarHeight - outlineCorrection;
- zoomedMax += left + scrollbarHeight - outlineCorrection;
- navigatorTop += halfOutline;
-
- path = [
- 'M',
- left,
- navigatorTop, // left
- 'L',
- zoomedMin,
- navigatorTop, // upper left of zoomed range
- 'L',
- zoomedMin,
- navigatorTop + outlineHeight, // lower left of z.r.
- 'L',
- zoomedMax,
- navigatorTop + outlineHeight, // lower right of z.r.
- 'L',
- zoomedMax,
- navigatorTop, // upper right of z.r.
- 'L',
- left + navigatorSize + scrollbarHeight * 2,
- navigatorTop // right
- ].concat(maskInside ? [
- 'M',
- zoomedMin - halfOutline,
- navigatorTop, // upper left of zoomed range
- 'L',
- zoomedMax + halfOutline,
- navigatorTop // upper right of z.r.
- ] : []);
- }
- navigator.outline[verb]({
- d: path
- });
- },
-
- /**
- * Render outline around the zoomed range
- * @param {Number} zoomedMin in pixels position where zoomed range starts
- * @param {Number} zoomedMax in pixels position where zoomed range ends
- * @param {Boolean} inverted flag if chart is inverted
- * @param {String} verb use 'animate' or 'attr'
- */
- drawMasks: function (zoomedMin, zoomedMax, inverted, verb) {
- var navigator = this,
- left = navigator.left,
- top = navigator.top,
- navigatorHeight = navigator.height,
- height,
- width,
- x,
- y;
-
- // Determine rectangle position & size
- // According to (non)inverted position:
- if (inverted) {
- x = [left, left, left];
- y = [top, top + zoomedMin, top + zoomedMax];
- width = [navigatorHeight, navigatorHeight, navigatorHeight];
- height = [
- zoomedMin,
- zoomedMax - zoomedMin,
- navigator.size - zoomedMax
- ];
- } else {
- x = [left, left + zoomedMin, left + zoomedMax];
- y = [top, top, top];
- width = [
- zoomedMin,
- zoomedMax - zoomedMin,
- navigator.size - zoomedMax
- ];
- height = [navigatorHeight, navigatorHeight, navigatorHeight];
- }
- each(navigator.shades, function (shade, i) {
- shade[verb]({
- x: x[i],
- y: y[i],
- width: width[i],
- height: height[i]
- });
- });
- },
-
- /**
- * Generate DOM elements for a navigator:
- * - main navigator group
- * - all shades
- * - outline
- * - handles
- */
- renderElements: function () {
- var navigator = this,
- navigatorOptions = navigator.navigatorOptions,
- maskInside = navigatorOptions.maskInside,
- chart = navigator.chart,
- inverted = chart.inverted,
- renderer = chart.renderer,
- navigatorGroup;
-
- // Create the main navigator group
- navigator.navigatorGroup = navigatorGroup = renderer.g('navigator')
- .attr({
- zIndex: 8,
- visibility: 'hidden'
- })
- .add();
-
-
- /*= if (build.classic) { =*/
- var mouseCursor = {
- cursor: inverted ? 'ns-resize' : 'ew-resize'
- };
- /*= } =*/
-
- // Create masks, each mask will get events and fill:
- each([!maskInside, maskInside, !maskInside], function (hasMask, index) {
- navigator.shades[index] = renderer.rect()
- .addClass('highcharts-navigator-mask' +
- (index === 1 ? '-inside' : '-outside'))
- /*= if (build.classic) { =*/
- .attr({
- fill: hasMask ? navigatorOptions.maskFill : 'rgba(0,0,0,0)'
- })
- .css(index === 1 && mouseCursor)
- /*= } =*/
- .add(navigatorGroup);
- });
-
- // Create the outline:
- navigator.outline = renderer.path()
- .addClass('highcharts-navigator-outline')
- /*= if (build.classic) { =*/
- .attr({
- 'stroke-width': navigatorOptions.outlineWidth,
- stroke: navigatorOptions.outlineColor
- })
- /*= } =*/
- .add(navigatorGroup);
-
- // Create the handlers:
- if (navigatorOptions.handles.enabled) {
- each([0, 1], function (index) {
- navigatorOptions.handles.inverted = chart.inverted;
- navigator.handles[index] = renderer.symbol(
- navigatorOptions.handles.symbols[index],
- -navigatorOptions.handles.width / 2 - 1,
- 0,
- navigatorOptions.handles.width,
- navigatorOptions.handles.height,
- navigatorOptions.handles
- );
- // zIndex = 6 for right handle, 7 for left.
- // Can't be 10, because of the tooltip in inverted chart #2908
- navigator.handles[index].attr({ zIndex: 7 - index })
- .addClass(
- 'highcharts-navigator-handle ' +
- 'highcharts-navigator-handle-' +
- ['left', 'right'][index]
- ).add(navigatorGroup);
-
- /*= if (build.classic) { =*/
- var handlesOptions = navigatorOptions.handles;
- navigator.handles[index]
- .attr({
- fill: handlesOptions.backgroundColor,
- stroke: handlesOptions.borderColor,
- 'stroke-width': handlesOptions.lineWidth
- })
- .css(mouseCursor);
- /*= } =*/
- });
- }
- },
-
- /**
- * 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);
- },
-
- /**
- * Render the navigator
- * @param {Number} min X axis value minimum
- * @param {Number} max X axis value maximum
- * @param {Number} pxMin Pixel value minimum
- * @param {Number} pxMax Pixel value maximum
- */
- render: function (min, max, pxMin, pxMax) {
-
- var navigator = this,
- chart = navigator.chart,
- navigatorWidth,
- scrollbarLeft,
- scrollbarTop,
- scrollbarHeight = navigator.scrollbarHeight,
- navigatorSize,
- xAxis = navigator.xAxis,
- scrollbarXAxis = xAxis.fake ? chart.xAxis[0] : xAxis,
- navigatorEnabled = navigator.navigatorEnabled,
- zoomedMin,
- zoomedMax,
- rendered = navigator.rendered,
- inverted = chart.inverted,
- verb,
- newMin,
- newMax,
- currentRange,
- minRange = chart.xAxis[0].minRange,
- maxRange = chart.xAxis[0].options.maxRange;
-
- // Don't redraw while moving the handles (#4703).
- if (this.hasDragged && !defined(pxMin)) {
- return;
- }
-
- // Don't render the navigator until we have data (#486, #4202, #5172).
- if (!isNumber(min) || !isNumber(max)) {
- // However, if navigator was already rendered, we may need to resize
- // it. For example hidden series, but visible navigator (#6022).
- if (rendered) {
- pxMin = 0;
- pxMax = pick(xAxis.width, scrollbarXAxis.width);
- } else {
- return;
- }
- }
-
- navigator.left = pick(
- xAxis.left,
- // in case of scrollbar only, without navigator
- chart.plotLeft + scrollbarHeight + (inverted ? chart.plotWidth : 0)
- );
-
- navigator.size = zoomedMax = navigatorSize = pick(
- xAxis.len,
- (inverted ? chart.plotHeight : chart.plotWidth) -
- 2 * scrollbarHeight
- );
-
- if (inverted) {
- navigatorWidth = scrollbarHeight;
- } else {
- navigatorWidth = navigatorSize + 2 * scrollbarHeight;
- }
-
- // Get the pixel position of the handles
- pxMin = pick(pxMin, xAxis.toPixels(min, true));
- pxMax = pick(pxMax, xAxis.toPixels(max, true));
-
- // Verify (#1851, #2238)
- if (!isNumber(pxMin) || Math.abs(pxMin) === Infinity) {
- pxMin = 0;
- pxMax = navigatorWidth;
- }
-
- // Are we below the minRange? (#2618, #6191)
- newMin = xAxis.toValue(pxMin, true);
- newMax = xAxis.toValue(pxMax, true);
- currentRange = Math.abs(H.correctFloat(newMax - newMin));
- if (currentRange < minRange) {
- if (this.grabbedLeft) {
- pxMin = xAxis.toPixels(newMax - minRange, true);
- } else if (this.grabbedRight) {
- pxMax = xAxis.toPixels(newMin + minRange, true);
- }
- } else if (defined(maxRange) && currentRange > maxRange) {
- /**
- * Maximum range which can be set using the navigator's handles.
- * Opposite of [xAxis.minRange](#xAxis.minRange).
- *
- * @type {Number}
- * @default undefined
- * @product highstock
- * @sample {highstock} stock/navigator/maxrange/
- * Defined max and min range
- * @since 6.0.0
- * @apioption xAxis.maxRange
- */
- if (this.grabbedLeft) {
- pxMin = xAxis.toPixels(newMax - maxRange, true);
- } else if (this.grabbedRight) {
- pxMax = xAxis.toPixels(newMin + maxRange, true);
- }
- }
-
- // Handles are allowed to cross, but never exceed the plot area
- navigator.zoomedMax = Math.min(Math.max(pxMin, pxMax, 0), zoomedMax);
- navigator.zoomedMin = Math.min(
- Math.max(
- navigator.fixedWidth ?
- navigator.zoomedMax - navigator.fixedWidth :
- Math.min(pxMin, pxMax),
- 0
- ),
- zoomedMax
- );
-
- navigator.range = navigator.zoomedMax - navigator.zoomedMin;
-
- zoomedMax = Math.round(navigator.zoomedMax);
- zoomedMin = Math.round(navigator.zoomedMin);
-
- if (navigatorEnabled) {
- navigator.navigatorGroup.attr({
- visibility: 'visible'
- });
- // Place elements
- verb = rendered && !navigator.hasDragged ? 'animate' : 'attr';
-
- navigator.drawMasks(zoomedMin, zoomedMax, inverted, verb);
- navigator.drawOutline(zoomedMin, zoomedMax, inverted, verb);
-
- if (navigator.navigatorOptions.handles.enabled) {
- navigator.drawHandle(zoomedMin, 0, inverted, verb);
- navigator.drawHandle(zoomedMax, 1, inverted, verb);
- }
- }
-
- if (navigator.scrollbar) {
- if (inverted) {
- scrollbarTop = navigator.top - scrollbarHeight;
- scrollbarLeft = navigator.left - scrollbarHeight +
- (navigatorEnabled || !scrollbarXAxis.opposite ? 0 :
- // Multiple axes has offsets:
- (scrollbarXAxis.titleOffset || 0) +
- // Self margin from the axis.title
- scrollbarXAxis.axisTitleMargin
- );
- scrollbarHeight = navigatorSize + 2 * scrollbarHeight;
- } else {
- scrollbarTop = navigator.top +
- (navigatorEnabled ? navigator.height : -scrollbarHeight);
- scrollbarLeft = navigator.left - scrollbarHeight;
- }
- // Reposition scrollbar
- navigator.scrollbar.position(
- scrollbarLeft,
- scrollbarTop,
- navigatorWidth,
- scrollbarHeight
- );
- // Keep scale 0-1
- navigator.scrollbar.setRange(
- // Use real value, not rounded because range can be very small
- // (#1716)
- navigator.zoomedMin / navigatorSize,
- navigator.zoomedMax / navigatorSize
- );
- }
- navigator.rendered = true;
- },
-
- /**
- * Set up the mouse and touch events for the navigator
- */
- addMouseEvents: function () {
- var navigator = this,
- chart = navigator.chart,
- container = chart.container,
- eventsToUnbind = [],
- mouseMoveHandler,
- mouseUpHandler;
-
- /**
- * Create mouse events' handlers.
- * Make them as separate functions to enable wrapping them:
- */
- navigator.mouseMoveHandler = mouseMoveHandler = function (e) {
- navigator.onMouseMove(e);
- };
- navigator.mouseUpHandler = mouseUpHandler = function (e) {
- navigator.onMouseUp(e);
- };
-
- // Add shades and handles mousedown events
- 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(container.ownerDocument, 'mouseup', mouseUpHandler)
- );
-
- // Touch events
- if (hasTouch) {
- eventsToUnbind.push(
- addEvent(container, 'touchmove', mouseMoveHandler),
- addEvent(container.ownerDocument, 'touchend', mouseUpHandler)
- );
- eventsToUnbind.concat(navigator.getPartsEvents('touchstart'));
- }
-
- navigator.eventsToUnbind = eventsToUnbind;
-
- // Data events
- if (navigator.series && navigator.series[0]) {
- eventsToUnbind.push(
- addEvent(
- navigator.series[0].xAxis,
- 'foundExtremes',
- function () {
- chart.navigator.modifyNavigatorAxisExtremes();
- }
- )
- );
- }
- },
-
- /**
- * Generate events for handles and masks
- * @param {String} eventName Event name handler, 'mousedown' or 'touchstart'
- * @returns {Array} An array of arrays: [DOMElement, eventName, callback].
- */
- getPartsEvents: function (eventName) {
- var navigator = this,
- events = [];
- each(['shades', 'handles'], function (name) {
- each(navigator[name], function (navigatorItem, index) {
- events.push(
- addEvent(
- navigatorItem.element,
- eventName,
- function (e) {
- navigator[name + 'Mousedown'](e, index);
- }
- )
- );
- });
- });
- return events;
- },
-
- /**
- * Mousedown on a shaded mask, either:
- * - will be stored for future drag&drop
- * - will directly shift to a new range
- *
- * @param {Object} e Mouse event
- * @param {Number} index Index of a mask in Navigator.shades array
- */
- shadesMousedown: function (e, index) {
- e = this.chart.pointer.normalize(e);
-
- var navigator = this,
- chart = navigator.chart,
- xAxis = navigator.xAxis,
- zoomedMin = navigator.zoomedMin,
- navigatorPosition = navigator.left,
- navigatorSize = navigator.size,
- range = navigator.range,
- chartX = e.chartX,
- fixedMax,
- fixedMin,
- ext,
- left;
-
- // For inverted chart, swap some options:
- if (chart.inverted) {
- chartX = e.chartY;
- navigatorPosition = navigator.top;
- }
-
- if (index === 1) {
- // Store information for drag&drop
- navigator.grabbedCenter = chartX;
- navigator.fixedWidth = range;
- navigator.dragOffset = chartX - zoomedMin;
- } else {
- // Shift the range by clicking on shaded areas
- left = chartX - navigatorPosition - range / 2;
- if (index === 0) {
- left = Math.max(0, left);
- } else if (index === 2 && left + range >= navigatorSize) {
- left = navigatorSize - range;
- if (xAxis.reversed) {
- // #7713
- left -= range;
- fixedMin = navigator.getUnionExtremes().dataMin;
- } else {
- // #2293, #3543
- fixedMax = navigator.getUnionExtremes().dataMax;
- }
- }
- if (left !== zoomedMin) { // it has actually moved
- navigator.fixedWidth = range; // #1370
-
- ext = xAxis.toFixedRange(
- left,
- left + range,
- fixedMin,
- fixedMax
- );
- if (defined(ext.min)) { // #7411
- chart.xAxis[0].setExtremes(
- Math.min(ext.min, ext.max),
- Math.max(ext.min, ext.max),
- true,
- null, // auto animation
- { trigger: 'navigator' }
- );
- }
- }
- }
- },
-
- /**
- * Mousedown on a handle mask.
- * Will store necessary information for drag&drop.
- *
- * @param {Object} e Mouse event
- * @param {Number} index Index of a handle in Navigator.handles array
- */
- handlesMousedown: function (e, index) {
- e = this.chart.pointer.normalize(e);
-
- var navigator = this,
- chart = navigator.chart,
- baseXAxis = chart.xAxis[0],
- // For reversed axes, min and max are chagned,
- // so the other extreme should be stored
- reverse = (chart.inverted && !baseXAxis.reversed) ||
- (!chart.inverted && baseXAxis.reversed);
-
- if (index === 0) {
- // Grab the left handle
- navigator.grabbedLeft = true;
- navigator.otherHandlePos = navigator.zoomedMax;
- navigator.fixedExtreme = reverse ? baseXAxis.min : baseXAxis.max;
- } else {
- // Grab the right handle
- navigator.grabbedRight = true;
- navigator.otherHandlePos = navigator.zoomedMin;
- navigator.fixedExtreme = reverse ? baseXAxis.max : baseXAxis.min;
- }
-
- chart.fixedRange = null;
- },
- /**
- * Mouse move event based on x/y mouse position.
- * @param {Object} e Mouse event
- */
- onMouseMove: function (e) {
- var navigator = this,
- chart = navigator.chart,
- left = navigator.left,
- navigatorSize = navigator.navigatorSize,
- range = navigator.range,
- dragOffset = navigator.dragOffset,
- inverted = chart.inverted,
- chartX;
-
-
- // In iOS, a mousemove event with e.pageX === 0 is fired when holding
- // the finger down in the center of the scrollbar. This should be
- // ignored.
- if (!e.touches || e.touches[0].pageX !== 0) { // #4696
-
- e = chart.pointer.normalize(e);
- chartX = e.chartX;
-
- // Swap some options for inverted chart
- if (inverted) {
- left = navigator.top;
- chartX = e.chartY;
- }
-
- // Drag left handle or top handle
- if (navigator.grabbedLeft) {
- navigator.hasDragged = true;
- navigator.render(
- 0,
- 0,
- chartX - left,
- navigator.otherHandlePos
- );
- // Drag right handle or bottom handle
- } else if (navigator.grabbedRight) {
- navigator.hasDragged = true;
- navigator.render(
- 0,
- 0,
- navigator.otherHandlePos,
- chartX - left
- );
- // Drag scrollbar or open area in navigator
- } else if (navigator.grabbedCenter) {
- navigator.hasDragged = true;
- if (chartX < dragOffset) { // outside left
- chartX = dragOffset;
- // outside right
- } else if (chartX > navigatorSize + dragOffset - range) {
- chartX = navigatorSize + dragOffset - range;
- }
-
- navigator.render(
- 0,
- 0,
- chartX - dragOffset,
- chartX - dragOffset + range
- );
- }
- if (
- navigator.hasDragged &&
- navigator.scrollbar &&
- navigator.scrollbar.options.liveRedraw
- ) {
- e.DOMType = e.type; // DOMType is for IE8
- setTimeout(function () {
- navigator.onMouseUp(e);
- }, 0);
- }
- }
- },
-
- /**
- * Mouse up event based on x/y mouse position.
- * @param {Object} e Mouse event
- */
- onMouseUp: function (e) {
- var navigator = this,
- chart = navigator.chart,
- xAxis = navigator.xAxis,
- reversed = xAxis && xAxis.reversed,
- scrollbar = navigator.scrollbar,
- unionExtremes,
- fixedMin,
- fixedMax,
- ext,
- DOMEvent = e.DOMEvent || e;
-
- if (
- // MouseUp is called for both, navigator and scrollbar (that order),
- // which causes calling afterSetExtremes twice. Prevent first call
- // by checking if scrollbar is going to set new extremes (#6334)
- (navigator.hasDragged && (!scrollbar || !scrollbar.hasDragged)) ||
- e.trigger === 'scrollbar'
- ) {
- unionExtremes = navigator.getUnionExtremes();
-
- // When dragging one handle, make sure the other one doesn't change
- if (navigator.zoomedMin === navigator.otherHandlePos) {
- fixedMin = navigator.fixedExtreme;
- } else if (navigator.zoomedMax === navigator.otherHandlePos) {
- fixedMax = navigator.fixedExtreme;
- }
- // Snap to right edge (#4076)
- if (navigator.zoomedMax === navigator.size) {
- fixedMax = reversed ?
- unionExtremes.dataMin : unionExtremes.dataMax;
- }
-
- // Snap to left edge (#7576)
- if (navigator.zoomedMin === 0) {
- fixedMin = reversed ?
- unionExtremes.dataMax : unionExtremes.dataMin;
- }
-
- ext = xAxis.toFixedRange(
- navigator.zoomedMin,
- navigator.zoomedMax,
- fixedMin,
- fixedMax
- );
-
- if (defined(ext.min)) {
- chart.xAxis[0].setExtremes(
- Math.min(ext.min, ext.max),
- Math.max(ext.min, ext.max),
- true,
- // Run animation when clicking buttons, scrollbar track etc,
- // but not when dragging handles or scrollbar
- navigator.hasDragged ? false : null,
- {
- trigger: 'navigator',
- triggerOp: 'navigator-drag',
- DOMEvent: DOMEvent // #1838
- }
- );
- }
- }
-
- if (e.DOMType !== 'mousemove') {
- navigator.grabbedLeft = navigator.grabbedRight =
- navigator.grabbedCenter = navigator.fixedWidth =
- navigator.fixedExtreme = navigator.otherHandlePos =
- navigator.hasDragged = navigator.dragOffset = null;
- }
- },
-
- /**
- * Removes the event handlers attached previously with addEvents.
- */
- removeEvents: function () {
- if (this.eventsToUnbind) {
- each(this.eventsToUnbind, function (unbind) {
- unbind();
- });
- this.eventsToUnbind = undefined;
- }
- this.removeBaseSeriesEvents();
- },
-
- /**
- * Remove data events.
- */
- removeBaseSeriesEvents: function () {
- var baseSeries = this.baseSeries || [];
- 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
- );
- }
- }
- },
-
- /**
- * Initiate the Navigator object
- */
- init: function (chart) {
- var chartOptions = chart.options,
- navigatorOptions = chartOptions.navigator,
- navigatorEnabled = navigatorOptions.enabled,
- scrollbarOptions = chartOptions.scrollbar,
- scrollbarEnabled = scrollbarOptions.enabled,
- height = navigatorEnabled ? navigatorOptions.height : 0,
- scrollbarHeight = scrollbarEnabled ? scrollbarOptions.height : 0;
-
- this.handles = [];
- this.shades = [];
-
- this.chart = chart;
- this.setBaseSeries();
-
- this.height = height;
- this.scrollbarHeight = scrollbarHeight;
- this.scrollbarEnabled = scrollbarEnabled;
- this.navigatorEnabled = navigatorEnabled;
- this.navigatorOptions = navigatorOptions;
- this.scrollbarOptions = scrollbarOptions;
- this.outlineHeight = height + scrollbarHeight;
-
- this.opposite = pick(
- navigatorOptions.opposite,
- !navigatorEnabled && chart.inverted
- ); // #6262
-
- var navigator = this,
- baseSeries = navigator.baseSeries,
- xAxisIndex = chart.xAxis.length,
- yAxisIndex = chart.yAxis.length,
- baseXaxis = baseSeries && baseSeries[0] && baseSeries[0].xAxis ||
- chart.xAxis[0] || { options: {} };
-
- // Make room for the navigator, can be placed around the chart:
- chart.extraMargin = {
- type: navigator.opposite ? 'plotTop' : 'marginBottom',
- value: (
- navigatorEnabled || !chart.inverted ?
- navigator.outlineHeight :
- 0
- ) + navigatorOptions.margin
- };
- if (chart.inverted) {
- chart.extraMargin.type = navigator.opposite ?
- 'marginRight' :
- 'plotLeft';
- }
- chart.isDirtyBox = true;
-
- if (navigator.navigatorEnabled) {
- // an x axis is required for scrollbar also
- navigator.xAxis = new Axis(chart, merge({
- // inherit base xAxis' break and ordinal options
- breaks: baseXaxis.options.breaks,
- ordinal: baseXaxis.options.ordinal
- }, navigatorOptions.xAxis, {
- id: 'navigator-x-axis',
- yAxis: 'navigator-y-axis',
- isX: true,
- type: 'datetime',
- index: xAxisIndex,
- offset: 0,
- keepOrdinalPadding: true, // #2436
- startOnTick: false,
- endOnTick: false,
- minPadding: 0,
- maxPadding: 0,
- zoomEnabled: false
- }, chart.inverted ? {
- offsets: [scrollbarHeight, 0, -scrollbarHeight, 0],
- width: height
- } : {
- offsets: [0, -scrollbarHeight, 0, scrollbarHeight],
- height: height
- }));
-
- navigator.yAxis = new Axis(chart, merge(navigatorOptions.yAxis, {
- id: 'navigator-y-axis',
- alignTicks: false,
- offset: 0,
- index: yAxisIndex,
- zoomEnabled: false
- }, chart.inverted ? {
- width: height
- } : {
- height: height
- }));
-
- // If we have a base series, initialize the navigator series
- if (baseSeries || navigatorOptions.series.data) {
- navigator.updateNavigatorSeries(false);
-
- // If not, set up an event to listen for added series
- } else if (chart.series.length === 0) {
-
- navigator.unbindRedraw = addEvent(
- chart,
- 'beforeRedraw',
- function () {
- // We've got one, now add it as base
- if (chart.series.length > 0 && !navigator.series) {
- navigator.setBaseSeries();
- navigator.unbindRedraw(); // reset
- }
- }
- );
- }
-
- // Render items, so we can bind events to them:
- navigator.renderElements();
- // Add mouse events
- navigator.addMouseEvents();
-
- // in case of scrollbar only, fake an x axis to get translation
- } else {
- navigator.xAxis = {
- translate: function (value, reverse) {
- var axis = chart.xAxis[0],
- ext = axis.getExtremes(),
- scrollTrackWidth = axis.len - 2 * scrollbarHeight,
- min = numExt('min', axis.options.min, ext.dataMin),
- valueRange = numExt(
- 'max',
- axis.options.max,
- ext.dataMax
- ) - min;
-
- return reverse ?
- // from pixel to value
- (value * valueRange / scrollTrackWidth) + min :
- // from value to pixel
- scrollTrackWidth * (value - min) / valueRange;
- },
- toPixels: function (value) {
- return this.translate(value);
- },
- toValue: function (value) {
- return this.translate(value, true);
- },
- toFixedRange: Axis.prototype.toFixedRange,
- fake: true
- };
- }
-
-
- // Initialize the scrollbar
- if (chart.options.scrollbar.enabled) {
- chart.scrollbar = navigator.scrollbar = new Scrollbar(
- chart.renderer,
- merge(chart.options.scrollbar, {
- margin: navigator.navigatorEnabled ? 0 : 10,
- vertical: chart.inverted
- }),
- chart
- );
- addEvent(navigator.scrollbar, 'changed', function (e) {
- var range = navigator.size,
- to = range * this.to,
- from = range * this.from;
-
- navigator.hasDragged = navigator.scrollbar.hasDragged;
- navigator.render(0, 0, from, to);
-
- if (
- chart.options.scrollbar.liveRedraw ||
- (
- e.DOMType !== 'mousemove' &&
- e.DOMType !== 'touchmove'
- )
- ) {
- setTimeout(function () {
- navigator.onMouseUp(e);
- });
- }
- });
- }
-
- // Add data events
- navigator.addBaseSeriesEvents();
- // Add redraw events
- navigator.addChartEvents();
- },
-
- /**
- * Get the union data extremes of the chart - the outer data extremes of the
- * base X axis and the navigator axis.
- * @param {boolean} returnFalseOnNoBaseSeries - as the param says.
- */
- getUnionExtremes: function (returnFalseOnNoBaseSeries) {
- var baseAxis = this.chart.xAxis[0],
- navAxis = this.xAxis,
- navAxisOptions = navAxis.options,
- baseAxisOptions = baseAxis.options,
- ret;
-
- if (!returnFalseOnNoBaseSeries || baseAxis.dataMin !== null) {
- ret = {
- dataMin: pick( // #4053
- navAxisOptions && navAxisOptions.min,
- numExt(
- 'min',
- baseAxisOptions.min,
- baseAxis.dataMin,
- navAxis.dataMin,
- navAxis.min
- )
- ),
- dataMax: pick(
- navAxisOptions && navAxisOptions.max,
- numExt(
- 'max',
- baseAxisOptions.max,
- baseAxis.dataMax,
- navAxis.dataMax,
- navAxis.max
- )
- )
- };
- }
- return ret;
- },
-
- /**
- * 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
- * @param {Boolean} [redraw]
- * Whether to redraw after update.
- */
- setBaseSeries: function (baseSeriesOptions, redraw) {
- var chart = this.chart,
- baseSeries = this.baseSeries = [];
-
- baseSeriesOptions = (
- baseSeriesOptions ||
- chart.options && chart.options.navigator.baseSeries ||
- 0
- );
-
- // Iterate through series and add the ones that should be shown in
- // navigator.
- each(chart.series || [], function (series, i) {
- if (
- // Don't include existing nav series
- !series.options.isInternal &&
- (
- 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.updateNavigatorSeries(true, redraw);
- }
- },
-
- /*
- * Update series in the navigator from baseSeries, adding new if does not
- * exist.
- */
- updateNavigatorSeries: function (addEvents, redraw) {
- var navigator = this,
- chart = navigator.chart,
- baseSeries = navigator.baseSeries,
- baseOptions,
- mergedNavSeriesOptions,
- 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 && baseSeries.length) {
- each(baseSeries, function eachBaseSeries(base) {
- var linkedNavSeries = base.navigatorSeries,
- userNavOptions = extend(
- // Grab color from base as default
- {
- color: base.color
- },
- !isArray(chartNavigatorSeriesOptions) ?
- chartNavigatorSeriesOptions :
- defaultOptions.navigator.series
- );
-
- // Don't update if the series exists in nav and we have disabled
- // adaptToUpdatedData.
- if (
- linkedNavSeries &&
- navigator.navigatorOptions.adaptToUpdatedData === false
- ) {
- return;
- }
-
- navSeriesMixin.name = 'Navigator ' + baseSeries.length;
-
- baseOptions = base.options || {};
- baseNavigatorOptions = baseOptions.navigatorOptions || {};
- 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 || userNavOptions.data;
- navigator.hasNavigatorData =
- navigator.hasNavigatorData || !!navigatorSeriesData;
- mergedNavSeriesOptions.data =
- navigatorSeriesData ||
- baseOptions.data && baseOptions.data.slice(0);
-
- // Update or add the series
- if (linkedNavSeries && linkedNavSeries.options) {
- linkedNavSeries.update(mergedNavSeriesOptions, redraw);
- } else {
- base.navigatorSeries = chart.initSeries(
- mergedNavSeriesOptions
- );
- base.navigatorSeries.baseSeries = base; // Store ref
- navigatorSeries.push(base.navigatorSeries);
- }
- });
- }
-
- // 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) {
- navSeriesMixin.name =
- 'Navigator ' + (navigatorSeries.length + 1);
- mergedNavSeriesOptions = merge(
- defaultOptions.navigator.series,
- {
- // 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]
- },
- navSeriesMixin,
- userSeriesOptions
- );
- mergedNavSeriesOptions.data = userSeriesOptions.data;
- if (mergedNavSeriesOptions.data) {
- navigator.hasNavigatorData = true;
- navigatorSeries.push(
- chart.initSeries(mergedNavSeriesOptions)
- );
- }
- });
- }
-
- if (addEvents) {
- this.addBaseSeriesEvents();
- }
- },
-
- /**
- * Add data events.
- * For example when main series is updated we need to recalculate extremes
- */
- 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.
- // 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
- );
- }
-
- each(baseSeries, function (base) {
- // Link base series show/hide to navigator series visibility
- addEvent(base, 'show', function () {
- if (this.navigatorSeries) {
- this.navigatorSeries.setVisible(true, false);
- }
- });
- addEvent(base, 'hide', function () {
- if (this.navigatorSeries) {
- this.navigatorSeries.setVisible(false, false);
- }
- });
-
- // 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);
- if (defined(this.navigatorSeries.options)) {
- 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 well as the navigator series.
- */
- modifyNavigatorAxisExtremes: function () {
- var xAxis = this.xAxis,
- unionExtremes;
-
- if (xAxis.getExtremes) {
- unionExtremes = this.getUnionExtremes(true);
- if (
- unionExtremes &&
- (
- unionExtremes.dataMin !== xAxis.min ||
- unionExtremes.dataMax !== xAxis.max
- )
- ) {
- xAxis.min = unionExtremes.dataMin;
- xAxis.max = unionExtremes.dataMax;
- }
- }
- },
-
- /**
- * Hook to modify the base axis extremes with information from the Navigator
- */
- modifyBaseAxisExtremes: function () {
- var baseXAxis = this,
- navigator = baseXAxis.chart.navigator,
- baseExtremes = baseXAxis.getExtremes(),
- baseMin = baseExtremes.min,
- baseMax = baseExtremes.max,
- baseDataMin = baseExtremes.dataMin,
- baseDataMax = baseExtremes.dataMax,
- range = baseMax - baseMin,
- stickToMin = navigator.stickToMin,
- stickToMax = navigator.stickToMax,
- overscroll = pick(baseXAxis.options.overscroll, 0),
- newMax,
- newMin,
- navigatorSeries = navigator.series && navigator.series[0],
- hasSetExtremes = !!baseXAxis.setExtremes,
-
- // When the extremes have been set by range selector button, don't
- // stick to min or max. The range selector buttons will handle the
- // extremes. (#5489)
- unmutable = baseXAxis.eventArgs &&
- baseXAxis.eventArgs.trigger === 'rangeSelectorButton';
-
- if (!unmutable) {
-
- // If the zoomed range is already at the min, move it to the right
- // as new data comes in
- if (stickToMin) {
- newMin = baseDataMin;
- newMax = newMin + range;
- }
-
- // If the zoomed range is already at the max, move it to the right
- // as new data comes in
- if (stickToMax) {
- newMax = baseDataMax + overscroll;
-
- // if stickToMin is true, the new min value is set above
- if (!stickToMin) {
- newMin = Math.max(
- newMax - range,
- navigatorSeries && navigatorSeries.xData ?
- navigatorSeries.xData[0] : -Number.MAX_VALUE
- );
- }
- }
-
- // Update the extremes
- if (hasSetExtremes && (stickToMin || stickToMax)) {
- if (isNumber(newMin)) {
- baseXAxis.min = baseXAxis.userMin = newMin;
- baseXAxis.max = baseXAxis.userMax = newMax;
- }
- }
- }
-
- // Reset
- navigator.stickToMin = navigator.stickToMax = null;
- },
-
- /**
- * Handler for updated data on the base series. When data is modified, the
- * navigator series must reflect it. This is called from the Chart.redraw
- * function before axis and series extremes are computed.
- */
- updatedDataHandler: function () {
- var navigator = this.chart.navigator,
- baseSeries = this,
- navigatorSeries = this.navigatorSeries;
-
- // If the scrollbar is scrolled all the way to the right, keep right as
- // new data comes in.
- navigator.stickToMax = navigator.xAxis.reversed ?
- Math.round(navigator.zoomedMin) === 0 :
- 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
- }
- },
-
- /**
- * Add chart events, like redrawing navigator, when chart requires that.
- */
- addChartEvents: function () {
- addEvent(this.chart, 'redraw', function () {
- // Move the scrollbar after redraw, like after data updata even if
- // axes don't redraw
- var navigator = this.navigator,
- xAxis = navigator && (
- navigator.baseSeries &&
- navigator.baseSeries[0] &&
- navigator.baseSeries[0].xAxis ||
- navigator.scrollbar && this.xAxis[0]
- ); // #5709
-
- if (xAxis) {
- navigator.render(xAxis.min, xAxis.max);
- }
- });
- },
-
- /**
- * Destroys allocated elements.
- */
- destroy: function () {
-
- // Disconnect events added in addEvents
- this.removeEvents();
-
- if (this.xAxis) {
- erase(this.chart.xAxis, this.xAxis);
- erase(this.chart.axes, this.xAxis);
- }
- if (this.yAxis) {
- erase(this.chart.yAxis, this.yAxis);
- erase(this.chart.axes, this.yAxis);
- }
- // Destroy series
- each(this.series || [], function (s) {
- if (s.destroy) {
- s.destroy();
- }
- });
-
- // Destroy properties
- each([
- 'series', 'xAxis', 'yAxis', 'shades', 'outline', 'scrollbarTrack',
- 'scrollbarRifles', 'scrollbarGroup', 'scrollbar', 'navigatorGroup',
- 'rendered'
- ], function (prop) {
- if (this[prop] && this[prop].destroy) {
- this[prop].destroy();
- }
- this[prop] = null;
- }, this);
-
- // Destroy elements in collection
- each([this.handles], function (coll) {
- destroyObjectProperties(coll);
- }, this);
- }
+ /**
+ * Draw one of the handles on the side of the zoomed range in the navigator
+ * @param {Number} x The x center for the handle
+ * @param {Number} index 0 for left and 1 for right
+ * @param {Boolean} inverted flag for chart.inverted
+ * @param {String} verb use 'animate' or 'attr'
+ */
+ drawHandle: function (x, index, inverted, verb) {
+ var navigator = this,
+ height = navigator.navigatorOptions.handles.height;
+
+ // Place it
+ navigator.handles[index][verb](inverted ? {
+ translateX: Math.round(navigator.left + navigator.height / 2),
+ translateY: Math.round(
+ navigator.top + parseInt(x, 10) + 0.5 - height
+ )
+ } : {
+ translateX: Math.round(navigator.left + parseInt(x, 10)),
+ translateY: Math.round(
+ navigator.top + navigator.height / 2 - height / 2 - 1
+ )
+ });
+ },
+
+ /**
+ * Render outline around the zoomed range
+ * @param {Number} zoomedMin in pixels position where zoomed range starts
+ * @param {Number} zoomedMax in pixels position where zoomed range ends
+ * @param {Boolean} inverted flag if chart is inverted
+ * @param {String} verb use 'animate' or 'attr'
+ */
+ drawOutline: function (zoomedMin, zoomedMax, inverted, verb) {
+ var navigator = this,
+ maskInside = navigator.navigatorOptions.maskInside,
+ outlineWidth = navigator.outline.strokeWidth(),
+ halfOutline = outlineWidth / 2,
+ outlineCorrection = (outlineWidth % 2) / 2, // #5800
+ outlineHeight = navigator.outlineHeight,
+ scrollbarHeight = navigator.scrollbarHeight,
+ navigatorSize = navigator.size,
+ left = navigator.left - scrollbarHeight,
+ navigatorTop = navigator.top,
+ verticalMin,
+ path;
+
+ if (inverted) {
+ left -= halfOutline;
+ verticalMin = navigatorTop + zoomedMax + outlineCorrection;
+ zoomedMax = navigatorTop + zoomedMin + outlineCorrection;
+
+ path = [
+ 'M',
+ left + outlineHeight,
+ navigatorTop - scrollbarHeight - outlineCorrection, // top edge
+ 'L',
+ left + outlineHeight,
+ verticalMin, // top right of zoomed range
+ 'L',
+ left,
+ verticalMin, // top left of z.r.
+ 'L',
+ left,
+ zoomedMax, // bottom left of z.r.
+ 'L',
+ left + outlineHeight,
+ zoomedMax, // bottom right of z.r.
+ 'L',
+ left + outlineHeight,
+ navigatorTop + navigatorSize + scrollbarHeight // bottom edge
+ ].concat(maskInside ? [
+ 'M',
+ left + outlineHeight,
+ verticalMin - halfOutline, // upper left of zoomed range
+ 'L',
+ left + outlineHeight,
+ zoomedMax + halfOutline // upper right of z.r.
+ ] : []);
+ } else {
+ zoomedMin += left + scrollbarHeight - outlineCorrection;
+ zoomedMax += left + scrollbarHeight - outlineCorrection;
+ navigatorTop += halfOutline;
+
+ path = [
+ 'M',
+ left,
+ navigatorTop, // left
+ 'L',
+ zoomedMin,
+ navigatorTop, // upper left of zoomed range
+ 'L',
+ zoomedMin,
+ navigatorTop + outlineHeight, // lower left of z.r.
+ 'L',
+ zoomedMax,
+ navigatorTop + outlineHeight, // lower right of z.r.
+ 'L',
+ zoomedMax,
+ navigatorTop, // upper right of z.r.
+ 'L',
+ left + navigatorSize + scrollbarHeight * 2,
+ navigatorTop // right
+ ].concat(maskInside ? [
+ 'M',
+ zoomedMin - halfOutline,
+ navigatorTop, // upper left of zoomed range
+ 'L',
+ zoomedMax + halfOutline,
+ navigatorTop // upper right of z.r.
+ ] : []);
+ }
+ navigator.outline[verb]({
+ d: path
+ });
+ },
+
+ /**
+ * Render outline around the zoomed range
+ * @param {Number} zoomedMin in pixels position where zoomed range starts
+ * @param {Number} zoomedMax in pixels position where zoomed range ends
+ * @param {Boolean} inverted flag if chart is inverted
+ * @param {String} verb use 'animate' or 'attr'
+ */
+ drawMasks: function (zoomedMin, zoomedMax, inverted, verb) {
+ var navigator = this,
+ left = navigator.left,
+ top = navigator.top,
+ navigatorHeight = navigator.height,
+ height,
+ width,
+ x,
+ y;
+
+ // Determine rectangle position & size
+ // According to (non)inverted position:
+ if (inverted) {
+ x = [left, left, left];
+ y = [top, top + zoomedMin, top + zoomedMax];
+ width = [navigatorHeight, navigatorHeight, navigatorHeight];
+ height = [
+ zoomedMin,
+ zoomedMax - zoomedMin,
+ navigator.size - zoomedMax
+ ];
+ } else {
+ x = [left, left + zoomedMin, left + zoomedMax];
+ y = [top, top, top];
+ width = [
+ zoomedMin,
+ zoomedMax - zoomedMin,
+ navigator.size - zoomedMax
+ ];
+ height = [navigatorHeight, navigatorHeight, navigatorHeight];
+ }
+ each(navigator.shades, function (shade, i) {
+ shade[verb]({
+ x: x[i],
+ y: y[i],
+ width: width[i],
+ height: height[i]
+ });
+ });
+ },
+
+ /**
+ * Generate DOM elements for a navigator:
+ * - main navigator group
+ * - all shades
+ * - outline
+ * - handles
+ */
+ renderElements: function () {
+ var navigator = this,
+ navigatorOptions = navigator.navigatorOptions,
+ maskInside = navigatorOptions.maskInside,
+ chart = navigator.chart,
+ inverted = chart.inverted,
+ renderer = chart.renderer,
+ navigatorGroup;
+
+ // Create the main navigator group
+ navigator.navigatorGroup = navigatorGroup = renderer.g('navigator')
+ .attr({
+ zIndex: 8,
+ visibility: 'hidden'
+ })
+ .add();
+
+
+ /*= if (build.classic) { =*/
+ var mouseCursor = {
+ cursor: inverted ? 'ns-resize' : 'ew-resize'
+ };
+ /*= } =*/
+
+ // Create masks, each mask will get events and fill:
+ each([!maskInside, maskInside, !maskInside], function (hasMask, index) {
+ navigator.shades[index] = renderer.rect()
+ .addClass('highcharts-navigator-mask' +
+ (index === 1 ? '-inside' : '-outside'))
+ /*= if (build.classic) { =*/
+ .attr({
+ fill: hasMask ? navigatorOptions.maskFill : 'rgba(0,0,0,0)'
+ })
+ .css(index === 1 && mouseCursor)
+ /*= } =*/
+ .add(navigatorGroup);
+ });
+
+ // Create the outline:
+ navigator.outline = renderer.path()
+ .addClass('highcharts-navigator-outline')
+ /*= if (build.classic) { =*/
+ .attr({
+ 'stroke-width': navigatorOptions.outlineWidth,
+ stroke: navigatorOptions.outlineColor
+ })
+ /*= } =*/
+ .add(navigatorGroup);
+
+ // Create the handlers:
+ if (navigatorOptions.handles.enabled) {
+ each([0, 1], function (index) {
+ navigatorOptions.handles.inverted = chart.inverted;
+ navigator.handles[index] = renderer.symbol(
+ navigatorOptions.handles.symbols[index],
+ -navigatorOptions.handles.width / 2 - 1,
+ 0,
+ navigatorOptions.handles.width,
+ navigatorOptions.handles.height,
+ navigatorOptions.handles
+ );
+ // zIndex = 6 for right handle, 7 for left.
+ // Can't be 10, because of the tooltip in inverted chart #2908
+ navigator.handles[index].attr({ zIndex: 7 - index })
+ .addClass(
+ 'highcharts-navigator-handle ' +
+ 'highcharts-navigator-handle-' +
+ ['left', 'right'][index]
+ ).add(navigatorGroup);
+
+ /*= if (build.classic) { =*/
+ var handlesOptions = navigatorOptions.handles;
+ navigator.handles[index]
+ .attr({
+ fill: handlesOptions.backgroundColor,
+ stroke: handlesOptions.borderColor,
+ 'stroke-width': handlesOptions.lineWidth
+ })
+ .css(mouseCursor);
+ /*= } =*/
+ });
+ }
+ },
+
+ /**
+ * 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);
+ },
+
+ /**
+ * Render the navigator
+ * @param {Number} min X axis value minimum
+ * @param {Number} max X axis value maximum
+ * @param {Number} pxMin Pixel value minimum
+ * @param {Number} pxMax Pixel value maximum
+ */
+ render: function (min, max, pxMin, pxMax) {
+
+ var navigator = this,
+ chart = navigator.chart,
+ navigatorWidth,
+ scrollbarLeft,
+ scrollbarTop,
+ scrollbarHeight = navigator.scrollbarHeight,
+ navigatorSize,
+ xAxis = navigator.xAxis,
+ scrollbarXAxis = xAxis.fake ? chart.xAxis[0] : xAxis,
+ navigatorEnabled = navigator.navigatorEnabled,
+ zoomedMin,
+ zoomedMax,
+ rendered = navigator.rendered,
+ inverted = chart.inverted,
+ verb,
+ newMin,
+ newMax,
+ currentRange,
+ minRange = chart.xAxis[0].minRange,
+ maxRange = chart.xAxis[0].options.maxRange;
+
+ // Don't redraw while moving the handles (#4703).
+ if (this.hasDragged && !defined(pxMin)) {
+ return;
+ }
+
+ // Don't render the navigator until we have data (#486, #4202, #5172).
+ if (!isNumber(min) || !isNumber(max)) {
+ // However, if navigator was already rendered, we may need to resize
+ // it. For example hidden series, but visible navigator (#6022).
+ if (rendered) {
+ pxMin = 0;
+ pxMax = pick(xAxis.width, scrollbarXAxis.width);
+ } else {
+ return;
+ }
+ }
+
+ navigator.left = pick(
+ xAxis.left,
+ // in case of scrollbar only, without navigator
+ chart.plotLeft + scrollbarHeight + (inverted ? chart.plotWidth : 0)
+ );
+
+ navigator.size = zoomedMax = navigatorSize = pick(
+ xAxis.len,
+ (inverted ? chart.plotHeight : chart.plotWidth) -
+ 2 * scrollbarHeight
+ );
+
+ if (inverted) {
+ navigatorWidth = scrollbarHeight;
+ } else {
+ navigatorWidth = navigatorSize + 2 * scrollbarHeight;
+ }
+
+ // Get the pixel position of the handles
+ pxMin = pick(pxMin, xAxis.toPixels(min, true));
+ pxMax = pick(pxMax, xAxis.toPixels(max, true));
+
+ // Verify (#1851, #2238)
+ if (!isNumber(pxMin) || Math.abs(pxMin) === Infinity) {
+ pxMin = 0;
+ pxMax = navigatorWidth;
+ }
+
+ // Are we below the minRange? (#2618, #6191)
+ newMin = xAxis.toValue(pxMin, true);
+ newMax = xAxis.toValue(pxMax, true);
+ currentRange = Math.abs(H.correctFloat(newMax - newMin));
+ if (currentRange < minRange) {
+ if (this.grabbedLeft) {
+ pxMin = xAxis.toPixels(newMax - minRange, true);
+ } else if (this.grabbedRight) {
+ pxMax = xAxis.toPixels(newMin + minRange, true);
+ }
+ } else if (defined(maxRange) && currentRange > maxRange) {
+ /**
+ * Maximum range which can be set using the navigator's handles.
+ * Opposite of [xAxis.minRange](#xAxis.minRange).
+ *
+ * @type {Number}
+ * @default undefined
+ * @product highstock
+ * @sample {highstock} stock/navigator/maxrange/
+ * Defined max and min range
+ * @since 6.0.0
+ * @apioption xAxis.maxRange
+ */
+ if (this.grabbedLeft) {
+ pxMin = xAxis.toPixels(newMax - maxRange, true);
+ } else if (this.grabbedRight) {
+ pxMax = xAxis.toPixels(newMin + maxRange, true);
+ }
+ }
+
+ // Handles are allowed to cross, but never exceed the plot area
+ navigator.zoomedMax = Math.min(Math.max(pxMin, pxMax, 0), zoomedMax);
+ navigator.zoomedMin = Math.min(
+ Math.max(
+ navigator.fixedWidth ?
+ navigator.zoomedMax - navigator.fixedWidth :
+ Math.min(pxMin, pxMax),
+ 0
+ ),
+ zoomedMax
+ );
+
+ navigator.range = navigator.zoomedMax - navigator.zoomedMin;
+
+ zoomedMax = Math.round(navigator.zoomedMax);
+ zoomedMin = Math.round(navigator.zoomedMin);
+
+ if (navigatorEnabled) {
+ navigator.navigatorGroup.attr({
+ visibility: 'visible'
+ });
+ // Place elements
+ verb = rendered && !navigator.hasDragged ? 'animate' : 'attr';
+
+ navigator.drawMasks(zoomedMin, zoomedMax, inverted, verb);
+ navigator.drawOutline(zoomedMin, zoomedMax, inverted, verb);
+
+ if (navigator.navigatorOptions.handles.enabled) {
+ navigator.drawHandle(zoomedMin, 0, inverted, verb);
+ navigator.drawHandle(zoomedMax, 1, inverted, verb);
+ }
+ }
+
+ if (navigator.scrollbar) {
+ if (inverted) {
+ scrollbarTop = navigator.top - scrollbarHeight;
+ scrollbarLeft = navigator.left - scrollbarHeight +
+ (navigatorEnabled || !scrollbarXAxis.opposite ? 0 :
+ // Multiple axes has offsets:
+ (scrollbarXAxis.titleOffset || 0) +
+ // Self margin from the axis.title
+ scrollbarXAxis.axisTitleMargin
+ );
+ scrollbarHeight = navigatorSize + 2 * scrollbarHeight;
+ } else {
+ scrollbarTop = navigator.top +
+ (navigatorEnabled ? navigator.height : -scrollbarHeight);
+ scrollbarLeft = navigator.left - scrollbarHeight;
+ }
+ // Reposition scrollbar
+ navigator.scrollbar.position(
+ scrollbarLeft,
+ scrollbarTop,
+ navigatorWidth,
+ scrollbarHeight
+ );
+ // Keep scale 0-1
+ navigator.scrollbar.setRange(
+ // Use real value, not rounded because range can be very small
+ // (#1716)
+ navigator.zoomedMin / navigatorSize,
+ navigator.zoomedMax / navigatorSize
+ );
+ }
+ navigator.rendered = true;
+ },
+
+ /**
+ * Set up the mouse and touch events for the navigator
+ */
+ addMouseEvents: function () {
+ var navigator = this,
+ chart = navigator.chart,
+ container = chart.container,
+ eventsToUnbind = [],
+ mouseMoveHandler,
+ mouseUpHandler;
+
+ /**
+ * Create mouse events' handlers.
+ * Make them as separate functions to enable wrapping them:
+ */
+ navigator.mouseMoveHandler = mouseMoveHandler = function (e) {
+ navigator.onMouseMove(e);
+ };
+ navigator.mouseUpHandler = mouseUpHandler = function (e) {
+ navigator.onMouseUp(e);
+ };
+
+ // Add shades and handles mousedown events
+ 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(container.ownerDocument, 'mouseup', mouseUpHandler)
+ );
+
+ // Touch events
+ if (hasTouch) {
+ eventsToUnbind.push(
+ addEvent(container, 'touchmove', mouseMoveHandler),
+ addEvent(container.ownerDocument, 'touchend', mouseUpHandler)
+ );
+ eventsToUnbind.concat(navigator.getPartsEvents('touchstart'));
+ }
+
+ navigator.eventsToUnbind = eventsToUnbind;
+
+ // Data events
+ if (navigator.series && navigator.series[0]) {
+ eventsToUnbind.push(
+ addEvent(
+ navigator.series[0].xAxis,
+ 'foundExtremes',
+ function () {
+ chart.navigator.modifyNavigatorAxisExtremes();
+ }
+ )
+ );
+ }
+ },
+
+ /**
+ * Generate events for handles and masks
+ * @param {String} eventName Event name handler, 'mousedown' or 'touchstart'
+ * @returns {Array} An array of arrays: [DOMElement, eventName, callback].
+ */
+ getPartsEvents: function (eventName) {
+ var navigator = this,
+ events = [];
+ each(['shades', 'handles'], function (name) {
+ each(navigator[name], function (navigatorItem, index) {
+ events.push(
+ addEvent(
+ navigatorItem.element,
+ eventName,
+ function (e) {
+ navigator[name + 'Mousedown'](e, index);
+ }
+ )
+ );
+ });
+ });
+ return events;
+ },
+
+ /**
+ * Mousedown on a shaded mask, either:
+ * - will be stored for future drag&drop
+ * - will directly shift to a new range
+ *
+ * @param {Object} e Mouse event
+ * @param {Number} index Index of a mask in Navigator.shades array
+ */
+ shadesMousedown: function (e, index) {
+ e = this.chart.pointer.normalize(e);
+
+ var navigator = this,
+ chart = navigator.chart,
+ xAxis = navigator.xAxis,
+ zoomedMin = navigator.zoomedMin,
+ navigatorPosition = navigator.left,
+ navigatorSize = navigator.size,
+ range = navigator.range,
+ chartX = e.chartX,
+ fixedMax,
+ fixedMin,
+ ext,
+ left;
+
+ // For inverted chart, swap some options:
+ if (chart.inverted) {
+ chartX = e.chartY;
+ navigatorPosition = navigator.top;
+ }
+
+ if (index === 1) {
+ // Store information for drag&drop
+ navigator.grabbedCenter = chartX;
+ navigator.fixedWidth = range;
+ navigator.dragOffset = chartX - zoomedMin;
+ } else {
+ // Shift the range by clicking on shaded areas
+ left = chartX - navigatorPosition - range / 2;
+ if (index === 0) {
+ left = Math.max(0, left);
+ } else if (index === 2 && left + range >= navigatorSize) {
+ left = navigatorSize - range;
+ if (xAxis.reversed) {
+ // #7713
+ left -= range;
+ fixedMin = navigator.getUnionExtremes().dataMin;
+ } else {
+ // #2293, #3543
+ fixedMax = navigator.getUnionExtremes().dataMax;
+ }
+ }
+ if (left !== zoomedMin) { // it has actually moved
+ navigator.fixedWidth = range; // #1370
+
+ ext = xAxis.toFixedRange(
+ left,
+ left + range,
+ fixedMin,
+ fixedMax
+ );
+ if (defined(ext.min)) { // #7411
+ chart.xAxis[0].setExtremes(
+ Math.min(ext.min, ext.max),
+ Math.max(ext.min, ext.max),
+ true,
+ null, // auto animation
+ { trigger: 'navigator' }
+ );
+ }
+ }
+ }
+ },
+
+ /**
+ * Mousedown on a handle mask.
+ * Will store necessary information for drag&drop.
+ *
+ * @param {Object} e Mouse event
+ * @param {Number} index Index of a handle in Navigator.handles array
+ */
+ handlesMousedown: function (e, index) {
+ e = this.chart.pointer.normalize(e);
+
+ var navigator = this,
+ chart = navigator.chart,
+ baseXAxis = chart.xAxis[0],
+ // For reversed axes, min and max are chagned,
+ // so the other extreme should be stored
+ reverse = (chart.inverted && !baseXAxis.reversed) ||
+ (!chart.inverted && baseXAxis.reversed);
+
+ if (index === 0) {
+ // Grab the left handle
+ navigator.grabbedLeft = true;
+ navigator.otherHandlePos = navigator.zoomedMax;
+ navigator.fixedExtreme = reverse ? baseXAxis.min : baseXAxis.max;
+ } else {
+ // Grab the right handle
+ navigator.grabbedRight = true;
+ navigator.otherHandlePos = navigator.zoomedMin;
+ navigator.fixedExtreme = reverse ? baseXAxis.max : baseXAxis.min;
+ }
+
+ chart.fixedRange = null;
+ },
+ /**
+ * Mouse move event based on x/y mouse position.
+ * @param {Object} e Mouse event
+ */
+ onMouseMove: function (e) {
+ var navigator = this,
+ chart = navigator.chart,
+ left = navigator.left,
+ navigatorSize = navigator.navigatorSize,
+ range = navigator.range,
+ dragOffset = navigator.dragOffset,
+ inverted = chart.inverted,
+ chartX;
+
+
+ // In iOS, a mousemove event with e.pageX === 0 is fired when holding
+ // the finger down in the center of the scrollbar. This should be
+ // ignored.
+ if (!e.touches || e.touches[0].pageX !== 0) { // #4696
+
+ e = chart.pointer.normalize(e);
+ chartX = e.chartX;
+
+ // Swap some options for inverted chart
+ if (inverted) {
+ left = navigator.top;
+ chartX = e.chartY;
+ }
+
+ // Drag left handle or top handle
+ if (navigator.grabbedLeft) {
+ navigator.hasDragged = true;
+ navigator.render(
+ 0,
+ 0,
+ chartX - left,
+ navigator.otherHandlePos
+ );
+ // Drag right handle or bottom handle
+ } else if (navigator.grabbedRight) {
+ navigator.hasDragged = true;
+ navigator.render(
+ 0,
+ 0,
+ navigator.otherHandlePos,
+ chartX - left
+ );
+ // Drag scrollbar or open area in navigator
+ } else if (navigator.grabbedCenter) {
+ navigator.hasDragged = true;
+ if (chartX < dragOffset) { // outside left
+ chartX = dragOffset;
+ // outside right
+ } else if (chartX > navigatorSize + dragOffset - range) {
+ chartX = navigatorSize + dragOffset - range;
+ }
+
+ navigator.render(
+ 0,
+ 0,
+ chartX - dragOffset,
+ chartX - dragOffset + range
+ );
+ }
+ if (
+ navigator.hasDragged &&
+ navigator.scrollbar &&
+ navigator.scrollbar.options.liveRedraw
+ ) {
+ e.DOMType = e.type; // DOMType is for IE8
+ setTimeout(function () {
+ navigator.onMouseUp(e);
+ }, 0);
+ }
+ }
+ },
+
+ /**
+ * Mouse up event based on x/y mouse position.
+ * @param {Object} e Mouse event
+ */
+ onMouseUp: function (e) {
+ var navigator = this,
+ chart = navigator.chart,
+ xAxis = navigator.xAxis,
+ reversed = xAxis && xAxis.reversed,
+ scrollbar = navigator.scrollbar,
+ unionExtremes,
+ fixedMin,
+ fixedMax,
+ ext,
+ DOMEvent = e.DOMEvent || e;
+
+ if (
+ // MouseUp is called for both, navigator and scrollbar (that order),
+ // which causes calling afterSetExtremes twice. Prevent first call
+ // by checking if scrollbar is going to set new extremes (#6334)
+ (navigator.hasDragged && (!scrollbar || !scrollbar.hasDragged)) ||
+ e.trigger === 'scrollbar'
+ ) {
+ unionExtremes = navigator.getUnionExtremes();
+
+ // When dragging one handle, make sure the other one doesn't change
+ if (navigator.zoomedMin === navigator.otherHandlePos) {
+ fixedMin = navigator.fixedExtreme;
+ } else if (navigator.zoomedMax === navigator.otherHandlePos) {
+ fixedMax = navigator.fixedExtreme;
+ }
+ // Snap to right edge (#4076)
+ if (navigator.zoomedMax === navigator.size) {
+ fixedMax = reversed ?
+ unionExtremes.dataMin : unionExtremes.dataMax;
+ }
+
+ // Snap to left edge (#7576)
+ if (navigator.zoomedMin === 0) {
+ fixedMin = reversed ?
+ unionExtremes.dataMax : unionExtremes.dataMin;
+ }
+
+ ext = xAxis.toFixedRange(
+ navigator.zoomedMin,
+ navigator.zoomedMax,
+ fixedMin,
+ fixedMax
+ );
+
+ if (defined(ext.min)) {
+ chart.xAxis[0].setExtremes(
+ Math.min(ext.min, ext.max),
+ Math.max(ext.min, ext.max),
+ true,
+ // Run animation when clicking buttons, scrollbar track etc,
+ // but not when dragging handles or scrollbar
+ navigator.hasDragged ? false : null,
+ {
+ trigger: 'navigator',
+ triggerOp: 'navigator-drag',
+ DOMEvent: DOMEvent // #1838
+ }
+ );
+ }
+ }
+
+ if (e.DOMType !== 'mousemove') {
+ navigator.grabbedLeft = navigator.grabbedRight =
+ navigator.grabbedCenter = navigator.fixedWidth =
+ navigator.fixedExtreme = navigator.otherHandlePos =
+ navigator.hasDragged = navigator.dragOffset = null;
+ }
+ },
+
+ /**
+ * Removes the event handlers attached previously with addEvents.
+ */
+ removeEvents: function () {
+ if (this.eventsToUnbind) {
+ each(this.eventsToUnbind, function (unbind) {
+ unbind();
+ });
+ this.eventsToUnbind = undefined;
+ }
+ this.removeBaseSeriesEvents();
+ },
+
+ /**
+ * Remove data events.
+ */
+ removeBaseSeriesEvents: function () {
+ var baseSeries = this.baseSeries || [];
+ 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
+ );
+ }
+ }
+ },
+
+ /**
+ * Initiate the Navigator object
+ */
+ init: function (chart) {
+ var chartOptions = chart.options,
+ navigatorOptions = chartOptions.navigator,
+ navigatorEnabled = navigatorOptions.enabled,
+ scrollbarOptions = chartOptions.scrollbar,
+ scrollbarEnabled = scrollbarOptions.enabled,
+ height = navigatorEnabled ? navigatorOptions.height : 0,
+ scrollbarHeight = scrollbarEnabled ? scrollbarOptions.height : 0;
+
+ this.handles = [];
+ this.shades = [];
+
+ this.chart = chart;
+ this.setBaseSeries();
+
+ this.height = height;
+ this.scrollbarHeight = scrollbarHeight;
+ this.scrollbarEnabled = scrollbarEnabled;
+ this.navigatorEnabled = navigatorEnabled;
+ this.navigatorOptions = navigatorOptions;
+ this.scrollbarOptions = scrollbarOptions;
+ this.outlineHeight = height + scrollbarHeight;
+
+ this.opposite = pick(
+ navigatorOptions.opposite,
+ !navigatorEnabled && chart.inverted
+ ); // #6262
+
+ var navigator = this,
+ baseSeries = navigator.baseSeries,
+ xAxisIndex = chart.xAxis.length,
+ yAxisIndex = chart.yAxis.length,
+ baseXaxis = baseSeries && baseSeries[0] && baseSeries[0].xAxis ||
+ chart.xAxis[0] || { options: {} };
+
+ // Make room for the navigator, can be placed around the chart:
+ chart.extraMargin = {
+ type: navigator.opposite ? 'plotTop' : 'marginBottom',
+ value: (
+ navigatorEnabled || !chart.inverted ?
+ navigator.outlineHeight :
+ 0
+ ) + navigatorOptions.margin
+ };
+ if (chart.inverted) {
+ chart.extraMargin.type = navigator.opposite ?
+ 'marginRight' :
+ 'plotLeft';
+ }
+ chart.isDirtyBox = true;
+
+ if (navigator.navigatorEnabled) {
+ // an x axis is required for scrollbar also
+ navigator.xAxis = new Axis(chart, merge({
+ // inherit base xAxis' break and ordinal options
+ breaks: baseXaxis.options.breaks,
+ ordinal: baseXaxis.options.ordinal
+ }, navigatorOptions.xAxis, {
+ id: 'navigator-x-axis',
+ yAxis: 'navigator-y-axis',
+ isX: true,
+ type: 'datetime',
+ index: xAxisIndex,
+ offset: 0,
+ keepOrdinalPadding: true, // #2436
+ startOnTick: false,
+ endOnTick: false,
+ minPadding: 0,
+ maxPadding: 0,
+ zoomEnabled: false
+ }, chart.inverted ? {
+ offsets: [scrollbarHeight, 0, -scrollbarHeight, 0],
+ width: height
+ } : {
+ offsets: [0, -scrollbarHeight, 0, scrollbarHeight],
+ height: height
+ }));
+
+ navigator.yAxis = new Axis(chart, merge(navigatorOptions.yAxis, {
+ id: 'navigator-y-axis',
+ alignTicks: false,
+ offset: 0,
+ index: yAxisIndex,
+ zoomEnabled: false
+ }, chart.inverted ? {
+ width: height
+ } : {
+ height: height
+ }));
+
+ // If we have a base series, initialize the navigator series
+ if (baseSeries || navigatorOptions.series.data) {
+ navigator.updateNavigatorSeries(false);
+
+ // If not, set up an event to listen for added series
+ } else if (chart.series.length === 0) {
+
+ navigator.unbindRedraw = addEvent(
+ chart,
+ 'beforeRedraw',
+ function () {
+ // We've got one, now add it as base
+ if (chart.series.length > 0 && !navigator.series) {
+ navigator.setBaseSeries();
+ navigator.unbindRedraw(); // reset
+ }
+ }
+ );
+ }
+
+ // Render items, so we can bind events to them:
+ navigator.renderElements();
+ // Add mouse events
+ navigator.addMouseEvents();
+
+ // in case of scrollbar only, fake an x axis to get translation
+ } else {
+ navigator.xAxis = {
+ translate: function (value, reverse) {
+ var axis = chart.xAxis[0],
+ ext = axis.getExtremes(),
+ scrollTrackWidth = axis.len - 2 * scrollbarHeight,
+ min = numExt('min', axis.options.min, ext.dataMin),
+ valueRange = numExt(
+ 'max',
+ axis.options.max,
+ ext.dataMax
+ ) - min;
+
+ return reverse ?
+ // from pixel to value
+ (value * valueRange / scrollTrackWidth) + min :
+ // from value to pixel
+ scrollTrackWidth * (value - min) / valueRange;
+ },
+ toPixels: function (value) {
+ return this.translate(value);
+ },
+ toValue: function (value) {
+ return this.translate(value, true);
+ },
+ toFixedRange: Axis.prototype.toFixedRange,
+ fake: true
+ };
+ }
+
+
+ // Initialize the scrollbar
+ if (chart.options.scrollbar.enabled) {
+ chart.scrollbar = navigator.scrollbar = new Scrollbar(
+ chart.renderer,
+ merge(chart.options.scrollbar, {
+ margin: navigator.navigatorEnabled ? 0 : 10,
+ vertical: chart.inverted
+ }),
+ chart
+ );
+ addEvent(navigator.scrollbar, 'changed', function (e) {
+ var range = navigator.size,
+ to = range * this.to,
+ from = range * this.from;
+
+ navigator.hasDragged = navigator.scrollbar.hasDragged;
+ navigator.render(0, 0, from, to);
+
+ if (
+ chart.options.scrollbar.liveRedraw ||
+ (
+ e.DOMType !== 'mousemove' &&
+ e.DOMType !== 'touchmove'
+ )
+ ) {
+ setTimeout(function () {
+ navigator.onMouseUp(e);
+ });
+ }
+ });
+ }
+
+ // Add data events
+ navigator.addBaseSeriesEvents();
+ // Add redraw events
+ navigator.addChartEvents();
+ },
+
+ /**
+ * Get the union data extremes of the chart - the outer data extremes of the
+ * base X axis and the navigator axis.
+ * @param {boolean} returnFalseOnNoBaseSeries - as the param says.
+ */
+ getUnionExtremes: function (returnFalseOnNoBaseSeries) {
+ var baseAxis = this.chart.xAxis[0],
+ navAxis = this.xAxis,
+ navAxisOptions = navAxis.options,
+ baseAxisOptions = baseAxis.options,
+ ret;
+
+ if (!returnFalseOnNoBaseSeries || baseAxis.dataMin !== null) {
+ ret = {
+ dataMin: pick( // #4053
+ navAxisOptions && navAxisOptions.min,
+ numExt(
+ 'min',
+ baseAxisOptions.min,
+ baseAxis.dataMin,
+ navAxis.dataMin,
+ navAxis.min
+ )
+ ),
+ dataMax: pick(
+ navAxisOptions && navAxisOptions.max,
+ numExt(
+ 'max',
+ baseAxisOptions.max,
+ baseAxis.dataMax,
+ navAxis.dataMax,
+ navAxis.max
+ )
+ )
+ };
+ }
+ return ret;
+ },
+
+ /**
+ * 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
+ * @param {Boolean} [redraw]
+ * Whether to redraw after update.
+ */
+ setBaseSeries: function (baseSeriesOptions, redraw) {
+ var chart = this.chart,
+ baseSeries = this.baseSeries = [];
+
+ baseSeriesOptions = (
+ baseSeriesOptions ||
+ chart.options && chart.options.navigator.baseSeries ||
+ 0
+ );
+
+ // Iterate through series and add the ones that should be shown in
+ // navigator.
+ each(chart.series || [], function (series, i) {
+ if (
+ // Don't include existing nav series
+ !series.options.isInternal &&
+ (
+ 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.updateNavigatorSeries(true, redraw);
+ }
+ },
+
+ /*
+ * Update series in the navigator from baseSeries, adding new if does not
+ * exist.
+ */
+ updateNavigatorSeries: function (addEvents, redraw) {
+ var navigator = this,
+ chart = navigator.chart,
+ baseSeries = navigator.baseSeries,
+ baseOptions,
+ mergedNavSeriesOptions,
+ 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 && baseSeries.length) {
+ each(baseSeries, function eachBaseSeries(base) {
+ var linkedNavSeries = base.navigatorSeries,
+ userNavOptions = extend(
+ // Grab color from base as default
+ {
+ color: base.color
+ },
+ !isArray(chartNavigatorSeriesOptions) ?
+ chartNavigatorSeriesOptions :
+ defaultOptions.navigator.series
+ );
+
+ // Don't update if the series exists in nav and we have disabled
+ // adaptToUpdatedData.
+ if (
+ linkedNavSeries &&
+ navigator.navigatorOptions.adaptToUpdatedData === false
+ ) {
+ return;
+ }
+
+ navSeriesMixin.name = 'Navigator ' + baseSeries.length;
+
+ baseOptions = base.options || {};
+ baseNavigatorOptions = baseOptions.navigatorOptions || {};
+ 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 || userNavOptions.data;
+ navigator.hasNavigatorData =
+ navigator.hasNavigatorData || !!navigatorSeriesData;
+ mergedNavSeriesOptions.data =
+ navigatorSeriesData ||
+ baseOptions.data && baseOptions.data.slice(0);
+
+ // Update or add the series
+ if (linkedNavSeries && linkedNavSeries.options) {
+ linkedNavSeries.update(mergedNavSeriesOptions, redraw);
+ } else {
+ base.navigatorSeries = chart.initSeries(
+ mergedNavSeriesOptions
+ );
+ base.navigatorSeries.baseSeries = base; // Store ref
+ navigatorSeries.push(base.navigatorSeries);
+ }
+ });
+ }
+
+ // 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) {
+ navSeriesMixin.name =
+ 'Navigator ' + (navigatorSeries.length + 1);
+ mergedNavSeriesOptions = merge(
+ defaultOptions.navigator.series,
+ {
+ // 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]
+ },
+ navSeriesMixin,
+ userSeriesOptions
+ );
+ mergedNavSeriesOptions.data = userSeriesOptions.data;
+ if (mergedNavSeriesOptions.data) {
+ navigator.hasNavigatorData = true;
+ navigatorSeries.push(
+ chart.initSeries(mergedNavSeriesOptions)
+ );
+ }
+ });
+ }
+
+ if (addEvents) {
+ this.addBaseSeriesEvents();
+ }
+ },
+
+ /**
+ * Add data events.
+ * For example when main series is updated we need to recalculate extremes
+ */
+ 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.
+ // 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
+ );
+ }
+
+ each(baseSeries, function (base) {
+ // Link base series show/hide to navigator series visibility
+ addEvent(base, 'show', function () {
+ if (this.navigatorSeries) {
+ this.navigatorSeries.setVisible(true, false);
+ }
+ });
+ addEvent(base, 'hide', function () {
+ if (this.navigatorSeries) {
+ this.navigatorSeries.setVisible(false, false);
+ }
+ });
+
+ // 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);
+ if (defined(this.navigatorSeries.options)) {
+ 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 well as the navigator series.
+ */
+ modifyNavigatorAxisExtremes: function () {
+ var xAxis = this.xAxis,
+ unionExtremes;
+
+ if (xAxis.getExtremes) {
+ unionExtremes = this.getUnionExtremes(true);
+ if (
+ unionExtremes &&
+ (
+ unionExtremes.dataMin !== xAxis.min ||
+ unionExtremes.dataMax !== xAxis.max
+ )
+ ) {
+ xAxis.min = unionExtremes.dataMin;
+ xAxis.max = unionExtremes.dataMax;
+ }
+ }
+ },
+
+ /**
+ * Hook to modify the base axis extremes with information from the Navigator
+ */
+ modifyBaseAxisExtremes: function () {
+ var baseXAxis = this,
+ navigator = baseXAxis.chart.navigator,
+ baseExtremes = baseXAxis.getExtremes(),
+ baseMin = baseExtremes.min,
+ baseMax = baseExtremes.max,
+ baseDataMin = baseExtremes.dataMin,
+ baseDataMax = baseExtremes.dataMax,
+ range = baseMax - baseMin,
+ stickToMin = navigator.stickToMin,
+ stickToMax = navigator.stickToMax,
+ overscroll = pick(baseXAxis.options.overscroll, 0),
+ newMax,
+ newMin,
+ navigatorSeries = navigator.series && navigator.series[0],
+ hasSetExtremes = !!baseXAxis.setExtremes,
+
+ // When the extremes have been set by range selector button, don't
+ // stick to min or max. The range selector buttons will handle the
+ // extremes. (#5489)
+ unmutable = baseXAxis.eventArgs &&
+ baseXAxis.eventArgs.trigger === 'rangeSelectorButton';
+
+ if (!unmutable) {
+
+ // If the zoomed range is already at the min, move it to the right
+ // as new data comes in
+ if (stickToMin) {
+ newMin = baseDataMin;
+ newMax = newMin + range;
+ }
+
+ // If the zoomed range is already at the max, move it to the right
+ // as new data comes in
+ if (stickToMax) {
+ newMax = baseDataMax + overscroll;
+
+ // if stickToMin is true, the new min value is set above
+ if (!stickToMin) {
+ newMin = Math.max(
+ newMax - range,
+ navigatorSeries && navigatorSeries.xData ?
+ navigatorSeries.xData[0] : -Number.MAX_VALUE
+ );
+ }
+ }
+
+ // Update the extremes
+ if (hasSetExtremes && (stickToMin || stickToMax)) {
+ if (isNumber(newMin)) {
+ baseXAxis.min = baseXAxis.userMin = newMin;
+ baseXAxis.max = baseXAxis.userMax = newMax;
+ }
+ }
+ }
+
+ // Reset
+ navigator.stickToMin = navigator.stickToMax = null;
+ },
+
+ /**
+ * Handler for updated data on the base series. When data is modified, the
+ * navigator series must reflect it. This is called from the Chart.redraw
+ * function before axis and series extremes are computed.
+ */
+ updatedDataHandler: function () {
+ var navigator = this.chart.navigator,
+ baseSeries = this,
+ navigatorSeries = this.navigatorSeries;
+
+ // If the scrollbar is scrolled all the way to the right, keep right as
+ // new data comes in.
+ navigator.stickToMax = navigator.xAxis.reversed ?
+ Math.round(navigator.zoomedMin) === 0 :
+ 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
+ }
+ },
+
+ /**
+ * Add chart events, like redrawing navigator, when chart requires that.
+ */
+ addChartEvents: function () {
+ addEvent(this.chart, 'redraw', function () {
+ // Move the scrollbar after redraw, like after data updata even if
+ // axes don't redraw
+ var navigator = this.navigator,
+ xAxis = navigator && (
+ navigator.baseSeries &&
+ navigator.baseSeries[0] &&
+ navigator.baseSeries[0].xAxis ||
+ navigator.scrollbar && this.xAxis[0]
+ ); // #5709
+
+ if (xAxis) {
+ navigator.render(xAxis.min, xAxis.max);
+ }
+ });
+ },
+
+ /**
+ * Destroys allocated elements.
+ */
+ destroy: function () {
+
+ // Disconnect events added in addEvents
+ this.removeEvents();
+
+ if (this.xAxis) {
+ erase(this.chart.xAxis, this.xAxis);
+ erase(this.chart.axes, this.xAxis);
+ }
+ if (this.yAxis) {
+ erase(this.chart.yAxis, this.yAxis);
+ erase(this.chart.axes, this.yAxis);
+ }
+ // Destroy series
+ each(this.series || [], function (s) {
+ if (s.destroy) {
+ s.destroy();
+ }
+ });
+
+ // Destroy properties
+ each([
+ 'series', 'xAxis', 'yAxis', 'shades', 'outline', 'scrollbarTrack',
+ 'scrollbarRifles', 'scrollbarGroup', 'scrollbar', 'navigatorGroup',
+ 'rendered'
+ ], function (prop) {
+ if (this[prop] && this[prop].destroy) {
+ this[prop].destroy();
+ }
+ this[prop] = null;
+ }, this);
+
+ // Destroy elements in collection
+ each([this.handles], function (coll) {
+ destroyObjectProperties(coll);
+ }, this);
+ }
};
H.Navigator = Navigator;
@@ -2035,55 +2035,55 @@ H.Navigator = Navigator;
* selector.
*/
wrap(Axis.prototype, 'zoom', function (proceed, newMin, newMax) {
- var chart = this.chart,
- chartOptions = chart.options,
- zoomType = chartOptions.chart.zoomType,
- pinchType = chartOptions.chart.pinchType,
- previousZoom,
- navigator = chartOptions.navigator,
- rangeSelector = chartOptions.rangeSelector,
- ret;
-
- if (this.isXAxis && ((navigator && navigator.enabled) ||
- (rangeSelector && rangeSelector.enabled))) {
- // For x only zooming, fool the chart.zoom method not to create the zoom
- // button because the property already exists
- if (zoomType === 'x' || pinchType === 'x') {
- chart.resetZoomButton = 'blocked';
-
- // For y only zooming, ignore the X axis completely
- } else if (zoomType === 'y') {
- ret = false;
-
- // For xy zooming, record the state of the zoom before zoom selection,
- // then when the reset button is pressed, revert to this state. This
- // should apply only if the chart is initialized with a range (#6612),
- // otherwise zoom all the way out.
- } else if (
- (zoomType === 'xy' || pinchType === 'xy') &&
- this.options.range
- ) {
-
- previousZoom = this.previousZoom;
- if (defined(newMin)) {
- this.previousZoom = [this.min, this.max];
- } else if (previousZoom) {
- newMin = previousZoom[0];
- newMax = previousZoom[1];
- delete this.previousZoom;
- }
- }
-
- }
- return ret !== undefined ? ret : proceed.call(this, newMin, newMax);
+ var chart = this.chart,
+ chartOptions = chart.options,
+ zoomType = chartOptions.chart.zoomType,
+ pinchType = chartOptions.chart.pinchType,
+ previousZoom,
+ navigator = chartOptions.navigator,
+ rangeSelector = chartOptions.rangeSelector,
+ ret;
+
+ if (this.isXAxis && ((navigator && navigator.enabled) ||
+ (rangeSelector && rangeSelector.enabled))) {
+ // For x only zooming, fool the chart.zoom method not to create the zoom
+ // button because the property already exists
+ if (zoomType === 'x' || pinchType === 'x') {
+ chart.resetZoomButton = 'blocked';
+
+ // For y only zooming, ignore the X axis completely
+ } else if (zoomType === 'y') {
+ ret = false;
+
+ // For xy zooming, record the state of the zoom before zoom selection,
+ // then when the reset button is pressed, revert to this state. This
+ // should apply only if the chart is initialized with a range (#6612),
+ // otherwise zoom all the way out.
+ } else if (
+ (zoomType === 'xy' || pinchType === 'xy') &&
+ this.options.range
+ ) {
+
+ previousZoom = this.previousZoom;
+ if (defined(newMin)) {
+ this.previousZoom = [this.min, this.max];
+ } else if (previousZoom) {
+ newMin = previousZoom[0];
+ newMax = previousZoom[1];
+ delete this.previousZoom;
+ }
+ }
+
+ }
+ return ret !== undefined ? ret : proceed.call(this, newMin, newMax);
});
// Initialize navigator for stock charts
addEvent(Chart, 'beforeRender', function () {
- var options = this.options;
- if (options.navigator.enabled || options.scrollbar.enabled) {
- this.scroller = this.navigator = new Navigator(this);
- }
+ var options = this.options;
+ if (options.navigator.enabled || options.scrollbar.enabled) {
+ this.scroller = this.navigator = new Navigator(this);
+ }
});
/**
@@ -2094,106 +2094,106 @@ addEvent(Chart, 'beforeRender', function () {
*/
addEvent(Chart, 'afterSetChartSize', function () {
- var legend = this.legend,
- navigator = this.navigator,
- scrollbarHeight,
- legendOptions,
- xAxis,
- yAxis;
-
- if (navigator) {
- legendOptions = legend && legend.options;
- xAxis = navigator.xAxis;
- yAxis = navigator.yAxis;
- scrollbarHeight = navigator.scrollbarHeight;
-
- // Compute the top position
- if (this.inverted) {
- navigator.left = navigator.opposite ?
- this.chartWidth - scrollbarHeight - navigator.height :
- this.spacing[3] + scrollbarHeight;
- navigator.top = this.plotTop + scrollbarHeight;
- } else {
- navigator.left = this.plotLeft + scrollbarHeight;
- navigator.top = navigator.navigatorOptions.top ||
- this.chartHeight -
- navigator.height -
- scrollbarHeight -
- this.spacing[2] -
- (
- this.rangeSelector && this.extraBottomMargin ?
- this.rangeSelector.getHeight() :
- 0
- ) -
- (
- (
- legendOptions &&
- legendOptions.verticalAlign === 'bottom' &&
- legendOptions.enabled &&
- !legendOptions.floating
- ) ?
- legend.legendHeight + pick(legendOptions.margin, 10) :
- 0
- );
- }
-
- if (xAxis && yAxis) { // false if navigator is disabled (#904)
-
- if (this.inverted) {
- xAxis.options.left = yAxis.options.left = navigator.left;
- } else {
- xAxis.options.top = yAxis.options.top = navigator.top;
- }
-
- xAxis.setAxisSize();
- yAxis.setAxisSize();
- }
- }
+ var legend = this.legend,
+ navigator = this.navigator,
+ scrollbarHeight,
+ legendOptions,
+ xAxis,
+ yAxis;
+
+ if (navigator) {
+ legendOptions = legend && legend.options;
+ xAxis = navigator.xAxis;
+ yAxis = navigator.yAxis;
+ scrollbarHeight = navigator.scrollbarHeight;
+
+ // Compute the top position
+ if (this.inverted) {
+ navigator.left = navigator.opposite ?
+ this.chartWidth - scrollbarHeight - navigator.height :
+ this.spacing[3] + scrollbarHeight;
+ navigator.top = this.plotTop + scrollbarHeight;
+ } else {
+ navigator.left = this.plotLeft + scrollbarHeight;
+ navigator.top = navigator.navigatorOptions.top ||
+ this.chartHeight -
+ navigator.height -
+ scrollbarHeight -
+ this.spacing[2] -
+ (
+ this.rangeSelector && this.extraBottomMargin ?
+ this.rangeSelector.getHeight() :
+ 0
+ ) -
+ (
+ (
+ legendOptions &&
+ legendOptions.verticalAlign === 'bottom' &&
+ legendOptions.enabled &&
+ !legendOptions.floating
+ ) ?
+ legend.legendHeight + pick(legendOptions.margin, 10) :
+ 0
+ );
+ }
+
+ if (xAxis && yAxis) { // false if navigator is disabled (#904)
+
+ if (this.inverted) {
+ xAxis.options.left = yAxis.options.left = navigator.left;
+ } else {
+ xAxis.options.top = yAxis.options.top = navigator.top;
+ }
+
+ xAxis.setAxisSize();
+ yAxis.setAxisSize();
+ }
+ }
});
// Pick up badly formatted point options to addPoint
wrap(Series.prototype, 'addPoint', function (
- proceed,
- options,
- redraw,
- shift,
- animation
+ proceed,
+ options,
+ redraw,
+ shift,
+ animation
) {
- var turboThreshold = this.options.turboThreshold;
- if (
- turboThreshold &&
- this.xData.length > turboThreshold &&
- isObject(options, true) &&
- this.chart.navigator
- ) {
- error(20, true);
- }
- proceed.call(this, options, redraw, shift, animation);
+ var turboThreshold = this.options.turboThreshold;
+ if (
+ turboThreshold &&
+ this.xData.length > turboThreshold &&
+ isObject(options, true) &&
+ this.chart.navigator
+ ) {
+ error(20, true);
+ }
+ proceed.call(this, options, redraw, shift, animation);
});
// Handle adding new series
addEvent(Chart, 'afterAddSeries', function () {
- if (this.navigator) {
- // Recompute which series should be shown in navigator, and add them
- this.navigator.setBaseSeries(null, false);
- }
+ if (this.navigator) {
+ // Recompute which series should be shown in navigator, and add them
+ this.navigator.setBaseSeries(null, false);
+ }
});
// Handle updating series
addEvent(Series, 'afterUpdate', function () {
- if (this.chart.navigator && !this.options.isInternal) {
- this.chart.navigator.setBaseSeries(null, false);
- }
+ if (this.chart.navigator && !this.options.isInternal) {
+ this.chart.navigator.setBaseSeries(null, false);
+ }
});
Chart.prototype.callbacks.push(function (chart) {
- var extremes,
- navigator = chart.navigator;
-
- // Initiate the navigator
- if (navigator && chart.xAxis[0]) {
- extremes = chart.xAxis[0].getExtremes();
- navigator.render(extremes.min, extremes.max);
- }
+ var extremes,
+ navigator = chart.navigator;
+
+ // Initiate the navigator
+ if (navigator && chart.xAxis[0]) {
+ extremes = chart.xAxis[0].getExtremes();
+ navigator.render(extremes.min, extremes.max);
+ }
});
diff --git a/js/parts/OHLCSeries.js b/js/parts/OHLCSeries.js
index 19b63e156bb..70ad19cbc5b 100644
--- a/js/parts/OHLCSeries.js
+++ b/js/parts/OHLCSeries.js
@@ -8,9 +8,9 @@ import H from './Globals.js';
import './Utilities.js';
import './Point.js';
var each = H.each,
- Point = H.Point,
- seriesType = H.seriesType,
- seriesTypes = H.seriesTypes;
+ Point = H.Point,
+ seriesType = H.seriesType,
+ seriesTypes = H.seriesTypes;
/**
* The ohlc series type.
@@ -31,261 +31,261 @@ var each = H.each,
*/
seriesType('ohlc', 'column', {
- /**
- * The approximate pixel width of each group. If for example a series
- * with 30 points is displayed over a 600 pixel wide plot area, no grouping
- * is performed. If however the series contains so many points that
- * the spacing is less than the groupPixelWidth, Highcharts will try
- * to group it into appropriate groups so that each is more or less
- * two pixels wide. Defaults to `5`.
- *
- * @type {Number}
- * @default 5
- * @product highstock
- * @apioption plotOptions.ohlc.dataGrouping.groupPixelWidth
- */
-
- /**
- * 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: {
- /*= if (!build.classic) { =*/
- pointFormat: '\u25CF {series.name}
' +
- 'Open: {point.open}
' +
- 'High: {point.high}
' +
- 'Low: {point.low}
' +
- 'Close: {point.close}
',
- /*= } else { =*/
-
- pointFormat: '\u25CF {series.name}
' +
- 'Open: {point.open}
' +
- 'High: {point.high}
' +
- 'Low: {point.low}
' +
- 'Close: {point.close}
'
- /*= } =*/
- },
-
- threshold: null,
- /*= if (build.classic) { =*/
-
- states: {
-
- /**
- * @extends plotOptions.column.states.hover
- * @product highstock
- */
- hover: {
-
- /**
- * The pixel width of the line representing the OHLC point.
- *
- * @type {Number}
- * @default 3
- * @product highstock
- */
- lineWidth: 3
- }
- },
-
-
- /**
- * Line color for up points.
- *
- * @type {Color}
- * @product highstock
- * @apioption plotOptions.ohlc.upColor
- */
-
- /*= } =*/
-
- stickyTracking: true
+ /**
+ * The approximate pixel width of each group. If for example a series
+ * with 30 points is displayed over a 600 pixel wide plot area, no grouping
+ * is performed. If however the series contains so many points that
+ * the spacing is less than the groupPixelWidth, Highcharts will try
+ * to group it into appropriate groups so that each is more or less
+ * two pixels wide. Defaults to `5`.
+ *
+ * @type {Number}
+ * @default 5
+ * @product highstock
+ * @apioption plotOptions.ohlc.dataGrouping.groupPixelWidth
+ */
+
+ /**
+ * 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: {
+ /*= if (!build.classic) { =*/
+ pointFormat: '\u25CF {series.name}
' +
+ 'Open: {point.open}
' +
+ 'High: {point.high}
' +
+ 'Low: {point.low}
' +
+ 'Close: {point.close}
',
+ /*= } else { =*/
+
+ pointFormat: '\u25CF {series.name}
' +
+ 'Open: {point.open}
' +
+ 'High: {point.high}
' +
+ 'Low: {point.low}
' +
+ 'Close: {point.close}
'
+ /*= } =*/
+ },
+
+ threshold: null,
+ /*= if (build.classic) { =*/
+
+ states: {
+
+ /**
+ * @extends plotOptions.column.states.hover
+ * @product highstock
+ */
+ hover: {
+
+ /**
+ * The pixel width of the line representing the OHLC point.
+ *
+ * @type {Number}
+ * @default 3
+ * @product highstock
+ */
+ lineWidth: 3
+ }
+ },
+
+
+ /**
+ * Line color for up points.
+ *
+ * @type {Color}
+ * @product highstock
+ * @apioption plotOptions.ohlc.upColor
+ */
+
+ /*= } =*/
+
+ stickyTracking: true
}, /** @lends seriesTypes.ohlc */ {
- directTouch: false,
- pointArrayMap: ['open', 'high', 'low', 'close'],
- toYData: function (point) { // return a plain array for speedy calculation
- return [point.open, point.high, point.low, point.close];
- },
- pointValKey: 'close',
-
- /*= if (build.classic) { =*/
- pointAttrToOptions: {
- 'stroke': 'color',
- 'stroke-width': 'lineWidth'
- },
-
- /**
- * Postprocess mapping between options and SVG attributes
- */
- pointAttribs: function (point, state) {
- var attribs = seriesTypes.column.prototype.pointAttribs.call(
- this,
- point,
- state
- ),
- options = this.options;
-
- delete attribs.fill;
-
- if (
- !point.options.color &&
- options.upColor &&
- point.open < point.close
- ) {
- attribs.stroke = options.upColor;
- }
-
- return attribs;
- },
- /*= } =*/
-
- /**
- * Translate data points from raw values x and y to plotX and plotY
- */
- translate: function () {
- var series = this,
- yAxis = series.yAxis,
- hasModifyValue = !!series.modifyValue,
- translated = [
- 'plotOpen',
- 'plotHigh',
- 'plotLow',
- 'plotClose',
- 'yBottom'
- ]; // translate OHLC for
-
- seriesTypes.column.prototype.translate.apply(series);
-
- // Do the translation
- each(series.points, function (point) {
- each(
- [point.open, point.high, point.low, point.close, point.low],
- function (value, i) {
- if (value !== null) {
- if (hasModifyValue) {
- value = series.modifyValue(value);
- }
- point[translated[i]] = yAxis.toPixels(value, true);
- }
- }
- );
-
- // Align the tooltip to the high value to avoid covering the point
- point.tooltipPos[1] =
- point.plotHigh + yAxis.pos - series.chart.plotTop;
- });
- },
-
- /**
- * Draw the data points
- */
- drawPoints: function () {
- var series = this,
- points = series.points,
- chart = series.chart;
-
-
- each(points, function (point) {
- var plotOpen,
- plotClose,
- crispCorr,
- halfWidth,
- path,
- graphic = point.graphic,
- crispX,
- isNew = !graphic;
-
- if (point.plotY !== undefined) {
-
- // Create and/or update the graphic
- if (!graphic) {
- point.graphic = graphic = chart.renderer.path()
- .add(series.group);
- }
-
- /*= if (build.classic) { =*/
- graphic.attr(
- series.pointAttribs(point, point.selected && 'select')
- ); // #3897
- /*= } =*/
-
- // crisp vector coordinates
- crispCorr = (graphic.strokeWidth() % 2) / 2;
- crispX = Math.round(point.plotX) - crispCorr; // #2596
- halfWidth = Math.round(point.shapeArgs.width / 2);
-
- // the vertical stem
- path = [
- 'M',
- crispX, Math.round(point.yBottom),
- 'L',
- crispX, Math.round(point.plotHigh)
- ];
-
- // open
- if (point.open !== null) {
- plotOpen = Math.round(point.plotOpen) + crispCorr;
- path.push(
- 'M',
- crispX,
- plotOpen,
- 'L',
- crispX - halfWidth,
- plotOpen
- );
- }
-
- // close
- if (point.close !== null) {
- plotClose = Math.round(point.plotClose) + crispCorr;
- path.push(
- 'M',
- crispX,
- plotClose,
- 'L',
- crispX + halfWidth,
- plotClose
- );
- }
-
- graphic[isNew ? 'attr' : 'animate']({ d: path })
- .addClass(point.getClassName(), true);
-
- }
-
-
- });
-
- },
-
- animate: null // Disable animation
+ directTouch: false,
+ pointArrayMap: ['open', 'high', 'low', 'close'],
+ toYData: function (point) { // return a plain array for speedy calculation
+ return [point.open, point.high, point.low, point.close];
+ },
+ pointValKey: 'close',
+
+ /*= if (build.classic) { =*/
+ pointAttrToOptions: {
+ 'stroke': 'color',
+ 'stroke-width': 'lineWidth'
+ },
+
+ /**
+ * Postprocess mapping between options and SVG attributes
+ */
+ pointAttribs: function (point, state) {
+ var attribs = seriesTypes.column.prototype.pointAttribs.call(
+ this,
+ point,
+ state
+ ),
+ options = this.options;
+
+ delete attribs.fill;
+
+ if (
+ !point.options.color &&
+ options.upColor &&
+ point.open < point.close
+ ) {
+ attribs.stroke = options.upColor;
+ }
+
+ return attribs;
+ },
+ /*= } =*/
+
+ /**
+ * Translate data points from raw values x and y to plotX and plotY
+ */
+ translate: function () {
+ var series = this,
+ yAxis = series.yAxis,
+ hasModifyValue = !!series.modifyValue,
+ translated = [
+ 'plotOpen',
+ 'plotHigh',
+ 'plotLow',
+ 'plotClose',
+ 'yBottom'
+ ]; // translate OHLC for
+
+ seriesTypes.column.prototype.translate.apply(series);
+
+ // Do the translation
+ each(series.points, function (point) {
+ each(
+ [point.open, point.high, point.low, point.close, point.low],
+ function (value, i) {
+ if (value !== null) {
+ if (hasModifyValue) {
+ value = series.modifyValue(value);
+ }
+ point[translated[i]] = yAxis.toPixels(value, true);
+ }
+ }
+ );
+
+ // Align the tooltip to the high value to avoid covering the point
+ point.tooltipPos[1] =
+ point.plotHigh + yAxis.pos - series.chart.plotTop;
+ });
+ },
+
+ /**
+ * Draw the data points
+ */
+ drawPoints: function () {
+ var series = this,
+ points = series.points,
+ chart = series.chart;
+
+
+ each(points, function (point) {
+ var plotOpen,
+ plotClose,
+ crispCorr,
+ halfWidth,
+ path,
+ graphic = point.graphic,
+ crispX,
+ isNew = !graphic;
+
+ if (point.plotY !== undefined) {
+
+ // Create and/or update the graphic
+ if (!graphic) {
+ point.graphic = graphic = chart.renderer.path()
+ .add(series.group);
+ }
+
+ /*= if (build.classic) { =*/
+ graphic.attr(
+ series.pointAttribs(point, point.selected && 'select')
+ ); // #3897
+ /*= } =*/
+
+ // crisp vector coordinates
+ crispCorr = (graphic.strokeWidth() % 2) / 2;
+ crispX = Math.round(point.plotX) - crispCorr; // #2596
+ halfWidth = Math.round(point.shapeArgs.width / 2);
+
+ // the vertical stem
+ path = [
+ 'M',
+ crispX, Math.round(point.yBottom),
+ 'L',
+ crispX, Math.round(point.plotHigh)
+ ];
+
+ // open
+ if (point.open !== null) {
+ plotOpen = Math.round(point.plotOpen) + crispCorr;
+ path.push(
+ 'M',
+ crispX,
+ plotOpen,
+ 'L',
+ crispX - halfWidth,
+ plotOpen
+ );
+ }
+
+ // close
+ if (point.close !== null) {
+ plotClose = Math.round(point.plotClose) + crispCorr;
+ path.push(
+ 'M',
+ crispX,
+ plotClose,
+ 'L',
+ crispX + halfWidth,
+ plotClose
+ );
+ }
+
+ graphic[isNew ? 'attr' : 'animate']({ d: path })
+ .addClass(point.getClassName(), true);
+
+ }
+
+
+ });
+
+ },
+
+ animate: null // Disable animation
}, /** @lends seriesTypes.ohlc.prototype.pointClass.prototype */ {
- /**
- * Extend the parent method by adding up or down to the class name.
- */
- getClassName: function () {
- return Point.prototype.getClassName.call(this) +
- (
- this.open < this.close ?
- ' highcharts-point-up' :
- ' highcharts-point-down'
- );
- }
+ /**
+ * Extend the parent method by adding up or down to the class name.
+ */
+ getClassName: function () {
+ return Point.prototype.getClassName.call(this) +
+ (
+ this.open < this.close ?
+ ' highcharts-point-up' :
+ ' highcharts-point-down'
+ );
+ }
});
/**
* A `ohlc` series. If the [type](#series.ohlc.type) option is not
* specified, it is inherited from [chart.type](#chart.type).
- *
+ *
* @type {Object}
* @extends series,plotOptions.ohlc
* @excluding dataParser,dataURL
@@ -296,7 +296,7 @@ seriesType('ohlc', 'column', {
/**
* An array of data points for the series. For the `ohlc` series type,
* points can be given in the following ways:
- *
+ *
* 1. An array of arrays with 5 or 4 values. In this case, the values
* correspond to `x,open,high,low,close`. If the first value is a string,
* it is applied as the name of the point, and the `x` value is inferred.
@@ -304,7 +304,7 @@ seriesType('ohlc', 'column', {
* should be of length 4\. Then the `x` value is automatically calculated,
* either starting at 0 and incremented by 1, or from `pointStart`
* and `pointInterval` given in the series options.
- *
+ *
* ```js
* data: [
* [0, 6, 5, 6, 7],
@@ -312,12 +312,12 @@ seriesType('ohlc', 'column', {
* [2, 6, 3, 4, 10]
* ]
* ```
- *
+ *
* 2. An array of objects with named values. The objects are point
* configuration objects as seen below. If the total number of data
* points exceeds the series' [turboThreshold](#series.ohlc.turboThreshold),
* this option is not available.
- *
+ *
* ```js
* data: [{
* x: 1,
@@ -337,7 +337,7 @@ seriesType('ohlc', 'column', {
* color: "#FF00FF"
* }]
* ```
- *
+ *
* @type {Array