/** * @license Highcharts JS v6.0.1 (2017-10-05) * Drag-panes module * * (c) 2010-2017 Highsoft AS * Author: Kacper Madej * * License: www.highcharts.com/license */ 'use strict'; (function(factory) { if (typeof module === 'object' && module.exports) { module.exports = factory; } else { factory(Highcharts); } }(function(Highcharts) { (function(H) { /** * Plugin for resizing axes / panes in a chart. * * (c) 2010-2017 Highsoft AS * Author: Kacper Madej * * License: www.highcharts.com/license */ var hasTouch = H.hasTouch, merge = H.merge, wrap = H.wrap, each = H.each, isNumber = H.isNumber, addEvent = H.addEvent, relativeLength = H.relativeLength, objectEach = H.objectEach, Axis = H.Axis, Pointer = H.Pointer, /** * Default options for AxisResizer. */ resizerOptions = { /** * Minimal size of a resizable axis. Could be set as a percent * of plot area or pixel size. * * This feature requires the `drag-panes.js` module. * * @product highstock * @default 10% * * @type {Number|String} * @sample {highstock} stock/yaxis/resize-min-max-length minLength and maxLength * @apioption yAxis.minLength */ minLength: '10%', /** * Maximal size of a resizable axis. Could be set as a percent * of plot area or pixel size. * * This feature requires the `drag-panes.js` module. * * @product highstock * @default 100% * * @type {String|Number} * @sample {highstock} stock/yaxis/resize-min-max-length minLength and maxLength * @apioption yAxis.maxLength */ maxLength: '100%', /** * Options for axis resizing for Drag Panes module. * * This feature requires the `drag-panes.js` module. * * @product highstock * @optionparent yAxis.resize */ resize: { /** * Contains two arrays of axes that are controlled by control line * of the axis. * * This feature requires the `drag-panes.js` module. */ controlledAxis: { /** * Array of axes that should move out of the way of resizing * being done for the current axis. If not set, the next axis * will be used. * * This feature requires the `drag-panes.js` module. * * @type {Array.} * @default [] * @sample {highstock} stock/yaxis/multiple-resizers Three panes with resizers * @sample {highstock} stock/yaxis/resize-multiple-axes One resizer controlling multiple axes */ next: [], /** * Array of axes that should move with the current axis * while resizing. * * This feature requires the `drag-panes.js` module. * * @type {Array.} * @default [] * @sample {highstock} stock/yaxis/multiple-resizers Three panes with resizers * @sample {highstock} stock/yaxis/resize-multiple-axes One resizer controlling multiple axes */ prev: [] }, /** * Enable or disable resize by drag for the axis. * * This feature requires the `drag-panes.js` module. * * @sample {highstock} stock/demo/candlestick-and-volume Enabled resizer */ enabled: false, /** * Cursor style for the control line. * * In styled mode use class `highcharts-axis-resizer` instead. * * This feature requires the `drag-panes.js` module. */ cursor: 'ns-resize', /** * Color of the control line. * * In styled mode use class `highcharts-axis-resizer` instead. * * This feature requires the `drag-panes.js` module. * * @type {Color} * @sample {highstock} stock/yaxis/styled-resizer Styled resizer */ lineColor: '#cccccc', /** * Dash style of the control line. * * In styled mode use class `highcharts-axis-resizer` instead. * * This feature requires the `drag-panes.js` module. * * @default Solid * @sample {highstock} stock/yaxis/styled-resizer Styled resizer * @see For supported options check * [dashStyle](#plotOptions.series.dashStyle) */ lineDashStyle: 'Solid', /** * Width of the control line. * * In styled mode use class `highcharts-axis-resizer` instead. * * This feature requires the `drag-panes.js` module. * * @sample {highstock} stock/yaxis/styled-resizer Styled resizer */ lineWidth: 4, /** * Horizontal offset of the control line. * * This feature requires the `drag-panes.js` module. * * @sample {highstock} stock/yaxis/styled-resizer Styled resizer */ x: 0, /** * Vertical offset of the control line. * * This feature requires the `drag-panes.js` module. * * @sample {highstock} stock/yaxis/styled-resizer Styled resizer */ y: 0 } }; merge(true, Axis.prototype.defaultYAxisOptions, resizerOptions); /** * The AxisResizer class. * @param {Object} axis - main axis for the AxisResizer. * @class */ H.AxisResizer = function(axis) { this.init(axis); }; H.AxisResizer.prototype = { /** * Initiate the AxisResizer object. * @param {Object} axis - main axis for the AxisResizer. */ init: function(axis, update) { this.axis = axis; this.options = axis.options.resize; this.render(); if (!update) { // Add mouse events. this.addMouseEvents(); } }, /** * Render the AxisResizer */ render: function() { var resizer = this, axis = resizer.axis, chart = axis.chart, options = resizer.options, x = options.x, y = options.y, // Normalize control line position according to the plot area pos = Math.min( Math.max( axis.top + axis.height + y, chart.plotTop ), chart.plotTop + chart.plotHeight ), attr = {}, lineWidth; attr = { cursor: options.cursor, stroke: options.lineColor, 'stroke-width': options.lineWidth, dashstyle: options.lineDashStyle }; // Register current position for future reference. resizer.lastPos = pos - y; if (!resizer.controlLine) { resizer.controlLine = chart.renderer.path() .addClass('highcharts-axis-resizer'); } // Add to axisGroup after axis update, because the group is recreated resizer.controlLine.add(axis.axisGroup); lineWidth = options.lineWidth; attr.d = chart.renderer.crispLine( [ 'M', axis.left + x, pos, 'L', axis.left + axis.width + x, pos ], lineWidth ); resizer.controlLine.attr(attr); }, /** * Set up the mouse and touch events for the control line. */ addMouseEvents: function() { var resizer = this, ctrlLineElem = resizer.controlLine.element, container = resizer.axis.chart.container, eventsToUnbind = [], mouseMoveHandler, mouseUpHandler, mouseDownHandler; /** * Create mouse events' handlers. * Make them as separate functions to enable wrapping them: */ resizer.mouseMoveHandler = mouseMoveHandler = function(e) { resizer.onMouseMove(e); }; resizer.mouseUpHandler = mouseUpHandler = function(e) { resizer.onMouseUp(e); }; resizer.mouseDownHandler = mouseDownHandler = function(e) { resizer.onMouseDown(e); }; /** * Add mouse move and mouseup events. These are bind to doc/container, * because resizer.grabbed flag is stored in mousedown events. */ eventsToUnbind.push( addEvent(container, 'mousemove', mouseMoveHandler), addEvent(container.ownerDocument, 'mouseup', mouseUpHandler), addEvent(ctrlLineElem, 'mousedown', mouseDownHandler) ); // Touch events. if (hasTouch) { eventsToUnbind.push( addEvent(container, 'touchmove', mouseMoveHandler), addEvent(container.ownerDocument, 'touchend', mouseUpHandler), addEvent(ctrlLineElem, 'touchstart', mouseDownHandler) ); } resizer.eventsToUnbind = eventsToUnbind; }, /** * Mouse move event based on x/y mouse position. * @param {Object} e - mouse event. */ onMouseMove: function(e) { /* * 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. Borrowed from Navigator. */ if (!e.touches || e.touches[0].pageX !== 0) { // Drag the control line if (this.grabbed) { this.hasDragged = true; this.updateAxes(this.axis.chart.pointer.normalize(e).chartY - this.options.y); } } }, /** * Mouse up event based on x/y mouse position. * @param {Object} e - mouse event. */ onMouseUp: function(e) { if (this.hasDragged) { this.updateAxes(this.axis.chart.pointer.normalize(e).chartY - this.options.y); } // Restore runPointActions. this.grabbed = this.hasDragged = this.axis.chart.activeResizer = null; }, /** * Mousedown on a control line. * Will store necessary information for drag&drop. */ onMouseDown: function() { // Clear all hover effects. this.axis.chart.pointer.reset(false, 0); // Disable runPointActions. this.grabbed = this.axis.chart.activeResizer = true; }, /** * Update all connected axes after a change of control line position */ updateAxes: function(chartY) { var resizer = this, chart = resizer.axis.chart, axes = resizer.options.controlledAxis, nextAxes = axes.next.length === 0 ? [H.inArray(resizer.axis, chart.yAxis) + 1] : axes.next, // Main axis is included in the prev array by default prevAxes = [resizer.axis].concat(axes.prev), axesConfigs = [], // prev and next configs stopDrag = false, plotTop = chart.plotTop, plotHeight = chart.plotHeight, plotBottom = plotTop + plotHeight, yDelta, normalize = function(val, min, max) { return Math.round(Math.min(Math.max(val, min), max)); }; // Normalize chartY to plot area limits chartY = Math.max(Math.min(chartY, plotBottom), plotTop); yDelta = chartY - resizer.lastPos; // Update on changes of at least 1 pixel in the desired direction if (yDelta * yDelta < 1) { return; } // First gather info how axes should behave each([prevAxes, nextAxes], function(axesGroup, isNext) { each(axesGroup, function(axisInfo, i) { // Axes given as array index, axis object or axis id var axis = isNumber(axisInfo) ? // If it's a number - it's an index chart.yAxis[axisInfo] : ( // If it's first elem. in first group (!isNext && !i) ? // then it's an Axis object axisInfo : // else it should be an id chart.get(axisInfo) ), axisOptions = axis && axis.options, optionsToUpdate = {}, hDelta = 0, height, top, minLength, maxLength; // Skip if axis is not found if (!axisOptions) { return; } top = axis.top; minLength = Math.round( relativeLength( axisOptions.minLength, plotHeight ) ); maxLength = Math.round( relativeLength( axisOptions.maxLength, plotHeight ) ); if (isNext) { // Try to change height first. yDelta could had changed yDelta = chartY - resizer.lastPos; // Normalize height to option limits height = normalize(axis.len - yDelta, minLength, maxLength); // Adjust top, so the axis looks like shrinked from top top = axis.top + yDelta; // Check for plot area limits if (top + height > plotBottom) { hDelta = plotBottom - height - top; chartY += hDelta; top += hDelta; } // Fit to plot - when overflowing on top if (top < plotTop) { top = plotTop; if (top + height > plotBottom) { height = plotHeight; } } // If next axis meets min length, stop dragging: if (height === minLength) { stopDrag = true; } axesConfigs.push({ axis: axis, options: { top: Math.round(top), height: height } }); } else { // Normalize height to option limits height = normalize(chartY - top, minLength, maxLength); // If prev axis meets max length, stop dragging: if (height === maxLength) { stopDrag = true; } // Check axis size limits chartY = top + height; axesConfigs.push({ axis: axis, options: { height: height } }); } optionsToUpdate.height = height; }); }); // If we hit the min/maxLength with dragging, don't do anything: if (!stopDrag) { // Now update axes: each(axesConfigs, function(config) { config.axis.update(config.options, false); }); chart.redraw(false); } }, /** * Destroy AxisResizer. Clear outside references, clear events, * destroy elements, nullify properties. */ destroy: function() { var resizer = this, axis = resizer.axis; // Clear resizer in axis delete axis.resizer; // Clear control line events if (this.eventsToUnbind) { each(this.eventsToUnbind, function(unbind) { unbind(); }); } // Destroy AxisResizer elements resizer.controlLine.destroy(); // Nullify properties objectEach(resizer, function(val, key) { resizer[key] = null; }); } }; // Keep resizer reference on axis update Axis.prototype.keepProps.push('resizer'); // Add new AxisResizer, update or remove it wrap(Axis.prototype, 'render', function(proceed) { proceed.apply(this, Array.prototype.slice.call(arguments, 1)); var axis = this, resizer = axis.resizer, resizerOptions = axis.options.resize, enabled; if (resizerOptions) { enabled = resizerOptions.enabled !== false; if (resizer) { // Resizer present and enabled if (enabled) { // Update options resizer.init(axis, true); // Resizer present, but disabled } else { // Destroy the resizer resizer.destroy(); } } else { // Resizer not present and enabled if (enabled) { // Add new resizer axis.resizer = new H.AxisResizer(axis); } // Resizer not present and disabled, so do nothing } } }); // Clear resizer on axis remove. wrap(Axis.prototype, 'destroy', function(proceed, keepEvents) { if (!keepEvents && this.resizer) { this.resizer.destroy(); } proceed.apply(this, Array.prototype.slice.call(arguments, 1)); }); // Prevent any hover effects while dragging a control line of AxisResizer. wrap(Pointer.prototype, 'runPointActions', function(proceed) { if (!this.chart.activeResizer) { proceed.apply(this, Array.prototype.slice.call(arguments, 1)); } }); }(Highcharts)); }));