DragPanes.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. /* *
  2. *
  3. * Plugin for resizing axes / panes in a chart.
  4. *
  5. * (c) 2010-2017 Highsoft AS
  6. *
  7. * Author: Kacper Madej
  8. *
  9. * License: www.highcharts.com/license
  10. *
  11. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  12. *
  13. * */
  14. 'use strict';
  15. import H from '../Core/Globals.js';
  16. var hasTouch = H.hasTouch;
  17. import Axis from '../Core/Axis/Axis.js';
  18. import Pointer from '../Core/Pointer.js';
  19. import U from '../Core/Utilities.js';
  20. var addEvent = U.addEvent, clamp = U.clamp, isNumber = U.isNumber, merge = U.merge, objectEach = U.objectEach, relativeLength = U.relativeLength, wrap = U.wrap;
  21. /* eslint-disable no-invalid-this, valid-jsdoc */
  22. /**
  23. * The AxisResizer class.
  24. *
  25. * @private
  26. * @class
  27. * @name Highcharts.AxisResizer
  28. *
  29. * @param {Highcharts.Axis} axis
  30. * Main axis for the AxisResizer.
  31. */
  32. var AxisResizer = /** @class */ (function () {
  33. function AxisResizer(axis) {
  34. /* eslint-enable no-invalid-this */
  35. this.axis = void 0;
  36. this.controlLine = void 0;
  37. this.lastPos = void 0;
  38. this.options = void 0;
  39. this.init(axis);
  40. }
  41. /**
  42. * Initialize the AxisResizer object.
  43. *
  44. * @function Highcharts.AxisResizer#init
  45. *
  46. * @param {Highcharts.Axis} axis
  47. * Main axis for the AxisResizer.
  48. */
  49. AxisResizer.prototype.init = function (axis, update) {
  50. this.axis = axis;
  51. this.options = axis.options.resize;
  52. this.render();
  53. if (!update) {
  54. // Add mouse events.
  55. this.addMouseEvents();
  56. }
  57. };
  58. /**
  59. * Render the AxisResizer
  60. *
  61. * @function Highcharts.AxisResizer#render
  62. */
  63. AxisResizer.prototype.render = function () {
  64. var resizer = this, axis = resizer.axis, chart = axis.chart, options = resizer.options, x = options.x || 0, y = options.y,
  65. // Normalize control line position according to the plot area
  66. pos = clamp(axis.top + axis.height + y, chart.plotTop, chart.plotTop + chart.plotHeight), attr = {}, lineWidth;
  67. if (!chart.styledMode) {
  68. attr = {
  69. cursor: options.cursor,
  70. stroke: options.lineColor,
  71. 'stroke-width': options.lineWidth,
  72. dashstyle: options.lineDashStyle
  73. };
  74. }
  75. // Register current position for future reference.
  76. resizer.lastPos = pos - y;
  77. if (!resizer.controlLine) {
  78. resizer.controlLine = chart.renderer.path()
  79. .addClass('highcharts-axis-resizer');
  80. }
  81. // Add to axisGroup after axis update, because the group is recreated
  82. // Do .add() before path is calculated because strokeWidth() needs it.
  83. resizer.controlLine.add(axis.axisGroup);
  84. lineWidth = chart.styledMode ?
  85. resizer.controlLine.strokeWidth() :
  86. options.lineWidth;
  87. attr.d = chart.renderer.crispLine([
  88. ['M', axis.left + x, pos],
  89. ['L', axis.left + axis.width + x, pos]
  90. ], lineWidth);
  91. resizer.controlLine.attr(attr);
  92. };
  93. /**
  94. * Set up the mouse and touch events for the control line.
  95. *
  96. * @function Highcharts.AxisResizer#addMouseEvents
  97. */
  98. AxisResizer.prototype.addMouseEvents = function () {
  99. var resizer = this, ctrlLineElem = resizer.controlLine.element, container = resizer.axis.chart.container, eventsToUnbind = [], mouseMoveHandler, mouseUpHandler, mouseDownHandler;
  100. // Create mouse events' handlers.
  101. // Make them as separate functions to enable wrapping them:
  102. resizer.mouseMoveHandler = mouseMoveHandler = function (e) {
  103. resizer.onMouseMove(e);
  104. };
  105. resizer.mouseUpHandler = mouseUpHandler = function (e) {
  106. resizer.onMouseUp(e);
  107. };
  108. resizer.mouseDownHandler = mouseDownHandler = function (e) {
  109. resizer.onMouseDown(e);
  110. };
  111. // Add mouse move and mouseup events. These are bind to doc/container,
  112. // because resizer.grabbed flag is stored in mousedown events.
  113. eventsToUnbind.push(addEvent(container, 'mousemove', mouseMoveHandler), addEvent(container.ownerDocument, 'mouseup', mouseUpHandler), addEvent(ctrlLineElem, 'mousedown', mouseDownHandler));
  114. // Touch events.
  115. if (hasTouch) {
  116. eventsToUnbind.push(addEvent(container, 'touchmove', mouseMoveHandler), addEvent(container.ownerDocument, 'touchend', mouseUpHandler), addEvent(ctrlLineElem, 'touchstart', mouseDownHandler));
  117. }
  118. resizer.eventsToUnbind = eventsToUnbind;
  119. };
  120. /**
  121. * Mouse move event based on x/y mouse position.
  122. *
  123. * @function Highcharts.AxisResizer#onMouseMove
  124. *
  125. * @param {Highcharts.PointerEventObject} e
  126. * Mouse event.
  127. */
  128. AxisResizer.prototype.onMouseMove = function (e) {
  129. /*
  130. * In iOS, a mousemove event with e.pageX === 0 is fired when holding
  131. * the finger down in the center of the scrollbar. This should
  132. * be ignored. Borrowed from Navigator.
  133. */
  134. if (!e.touches || e.touches[0].pageX !== 0) {
  135. // Drag the control line
  136. if (this.grabbed) {
  137. this.hasDragged = true;
  138. this.updateAxes(this.axis.chart.pointer.normalize(e).chartY -
  139. this.options.y);
  140. }
  141. }
  142. };
  143. /**
  144. * Mouse up event based on x/y mouse position.
  145. *
  146. * @function Highcharts.AxisResizer#onMouseUp
  147. *
  148. * @param {Highcharts.PointerEventObject} e
  149. * Mouse event.
  150. */
  151. AxisResizer.prototype.onMouseUp = function (e) {
  152. if (this.hasDragged) {
  153. this.updateAxes(this.axis.chart.pointer.normalize(e).chartY -
  154. this.options.y);
  155. }
  156. // Restore runPointActions.
  157. this.grabbed = this.hasDragged = this.axis.chart.activeResizer =
  158. null;
  159. };
  160. /**
  161. * Mousedown on a control line.
  162. * Will store necessary information for drag&drop.
  163. *
  164. * @function Highcharts.AxisResizer#onMouseDown
  165. */
  166. AxisResizer.prototype.onMouseDown = function (e) {
  167. // Clear all hover effects.
  168. this.axis.chart.pointer.reset(false, 0);
  169. // Disable runPointActions.
  170. this.grabbed = this.axis.chart.activeResizer = true;
  171. };
  172. /**
  173. * Update all connected axes after a change of control line position
  174. *
  175. * @function Highcharts.AxisResizer#updateAxes
  176. *
  177. * @param {number} chartY
  178. */
  179. AxisResizer.prototype.updateAxes = function (chartY) {
  180. var resizer = this, chart = resizer.axis.chart, axes = resizer.options.controlledAxis, nextAxes = axes.next.length === 0 ?
  181. [chart.yAxis.indexOf(resizer.axis) + 1] : axes.next,
  182. // Main axis is included in the prev array by default
  183. prevAxes = [resizer.axis].concat(axes.prev),
  184. // prev and next configs
  185. axesConfigs = [], stopDrag = false, plotTop = chart.plotTop, plotHeight = chart.plotHeight, plotBottom = plotTop + plotHeight, yDelta, calculatePercent = function (value) {
  186. return value * 100 / plotHeight + '%';
  187. }, normalize = function (val, min, max) {
  188. return Math.round(clamp(val, min, max));
  189. };
  190. // Normalize chartY to plot area limits
  191. chartY = clamp(chartY, plotTop, plotBottom);
  192. yDelta = chartY - resizer.lastPos;
  193. // Update on changes of at least 1 pixel in the desired direction
  194. if (yDelta * yDelta < 1) {
  195. return;
  196. }
  197. // First gather info how axes should behave
  198. [prevAxes, nextAxes].forEach(function (axesGroup, isNext) {
  199. axesGroup.forEach(function (axisInfo, i) {
  200. // Axes given as array index, axis object or axis id
  201. var axis = isNumber(axisInfo) ?
  202. // If it's a number - it's an index
  203. chart.yAxis[axisInfo] :
  204. (
  205. // If it's first elem. in first group
  206. (!isNext && !i) ?
  207. // then it's an Axis object
  208. axisInfo :
  209. // else it should be an id
  210. chart.get(axisInfo)), axisOptions = axis && axis.options, optionsToUpdate = {}, hDelta = 0, height, top, minLength, maxLength;
  211. // Skip if axis is not found
  212. // or it is navigator's yAxis (#7732)
  213. if (!axisOptions ||
  214. axisOptions.id === 'navigator-y-axis') {
  215. return;
  216. }
  217. top = axis.top;
  218. minLength = Math.round(relativeLength(axisOptions.minLength, plotHeight));
  219. maxLength = Math.round(relativeLength(axisOptions.maxLength, plotHeight));
  220. if (isNext) {
  221. // Try to change height first. yDelta could had changed
  222. yDelta = chartY - resizer.lastPos;
  223. // Normalize height to option limits
  224. height = normalize(axis.len - yDelta, minLength, maxLength);
  225. // Adjust top, so the axis looks like shrinked from top
  226. top = axis.top + yDelta;
  227. // Check for plot area limits
  228. if (top + height > plotBottom) {
  229. hDelta = plotBottom - height - top;
  230. chartY += hDelta;
  231. top += hDelta;
  232. }
  233. // Fit to plot - when overflowing on top
  234. if (top < plotTop) {
  235. top = plotTop;
  236. if (top + height > plotBottom) {
  237. height = plotHeight;
  238. }
  239. }
  240. // If next axis meets min length, stop dragging:
  241. if (height === minLength) {
  242. stopDrag = true;
  243. }
  244. axesConfigs.push({
  245. axis: axis,
  246. options: {
  247. top: calculatePercent(top - plotTop),
  248. height: calculatePercent(height)
  249. }
  250. });
  251. }
  252. else {
  253. // Normalize height to option limits
  254. height = normalize(chartY - top, minLength, maxLength);
  255. // If prev axis meets max length, stop dragging:
  256. if (height === maxLength) {
  257. stopDrag = true;
  258. }
  259. // Check axis size limits
  260. chartY = top + height;
  261. axesConfigs.push({
  262. axis: axis,
  263. options: {
  264. height: calculatePercent(height)
  265. }
  266. });
  267. }
  268. optionsToUpdate.height = height;
  269. });
  270. });
  271. // If we hit the min/maxLength with dragging, don't do anything:
  272. if (!stopDrag) {
  273. // Now update axes:
  274. axesConfigs.forEach(function (config) {
  275. config.axis.update(config.options, false);
  276. });
  277. chart.redraw(false);
  278. }
  279. };
  280. /**
  281. * Destroy AxisResizer. Clear outside references, clear events,
  282. * destroy elements, nullify properties.
  283. *
  284. * @function Highcharts.AxisResizer#destroy
  285. */
  286. AxisResizer.prototype.destroy = function () {
  287. var resizer = this, axis = resizer.axis;
  288. // Clear resizer in axis
  289. delete axis.resizer;
  290. // Clear control line events
  291. if (this.eventsToUnbind) {
  292. this.eventsToUnbind.forEach(function (unbind) {
  293. unbind();
  294. });
  295. }
  296. // Destroy AxisResizer elements
  297. resizer.controlLine.destroy();
  298. // Nullify properties
  299. objectEach(resizer, function (val, key) {
  300. resizer[key] = null;
  301. });
  302. };
  303. // Default options for AxisResizer.
  304. AxisResizer.resizerOptions = {
  305. /**
  306. * Minimal size of a resizable axis. Could be set as a percent
  307. * of plot area or pixel size.
  308. *
  309. * @sample {highstock} stock/yaxis/resize-min-max-length
  310. * minLength and maxLength
  311. *
  312. * @type {number|string}
  313. * @product highstock
  314. * @requires modules/drag-panes
  315. * @apioption yAxis.minLength
  316. */
  317. minLength: '10%',
  318. /**
  319. * Maximal size of a resizable axis. Could be set as a percent
  320. * of plot area or pixel size.
  321. *
  322. * @sample {highstock} stock/yaxis/resize-min-max-length
  323. * minLength and maxLength
  324. *
  325. * @type {number|string}
  326. * @product highstock
  327. * @requires modules/drag-panes
  328. * @apioption yAxis.maxLength
  329. */
  330. maxLength: '100%',
  331. /**
  332. * Options for axis resizing. It adds a thick line between panes which
  333. * the user can drag in order to resize the panes.
  334. *
  335. * @sample {highstock} stock/demo/candlestick-and-volume
  336. * Axis resizing enabled
  337. *
  338. * @product highstock
  339. * @requires modules/drag-panes
  340. * @optionparent yAxis.resize
  341. */
  342. resize: {
  343. /**
  344. * Contains two arrays of axes that are controlled by control line
  345. * of the axis.
  346. *
  347. * @requires modules/drag-panes
  348. */
  349. controlledAxis: {
  350. /**
  351. * Array of axes that should move out of the way of resizing
  352. * being done for the current axis. If not set, the next axis
  353. * will be used.
  354. *
  355. * @sample {highstock} stock/yaxis/multiple-resizers
  356. * Three panes with resizers
  357. * @sample {highstock} stock/yaxis/resize-multiple-axes
  358. * One resizer controlling multiple axes
  359. *
  360. * @type {Array<number|string>}
  361. * @default []
  362. * @requires modules/drag-panes
  363. */
  364. next: [],
  365. /**
  366. * Array of axes that should move with the current axis
  367. * while resizing.
  368. *
  369. * @sample {highstock} stock/yaxis/multiple-resizers
  370. * Three panes with resizers
  371. * @sample {highstock} stock/yaxis/resize-multiple-axes
  372. * One resizer controlling multiple axes
  373. *
  374. * @type {Array<number|string>}
  375. * @default []
  376. * @requires modules/drag-panes
  377. */
  378. prev: []
  379. },
  380. /**
  381. * Enable or disable resize by drag for the axis.
  382. *
  383. * @sample {highstock} stock/demo/candlestick-and-volume
  384. * Enabled resizer
  385. *
  386. * @requires modules/drag-panes
  387. */
  388. enabled: false,
  389. /**
  390. * Cursor style for the control line.
  391. *
  392. * In styled mode use class `highcharts-axis-resizer` instead.
  393. *
  394. * @requires modules/drag-panes
  395. */
  396. cursor: 'ns-resize',
  397. /**
  398. * Color of the control line.
  399. *
  400. * In styled mode use class `highcharts-axis-resizer` instead.
  401. *
  402. * @sample {highstock} stock/yaxis/styled-resizer
  403. * Styled resizer
  404. *
  405. * @type {Highcharts.ColorString}
  406. * @requires modules/drag-panes
  407. */
  408. lineColor: '#cccccc',
  409. /**
  410. * Dash style of the control line.
  411. *
  412. * In styled mode use class `highcharts-axis-resizer` instead.
  413. *
  414. * @see For supported options check [dashStyle](#plotOptions.series.dashStyle)
  415. *
  416. * @sample {highstock} stock/yaxis/styled-resizer
  417. * Styled resizer
  418. *
  419. * @requires modules/drag-panes
  420. */
  421. lineDashStyle: 'Solid',
  422. /**
  423. * Width of the control line.
  424. *
  425. * In styled mode use class `highcharts-axis-resizer` instead.
  426. *
  427. * @sample {highstock} stock/yaxis/styled-resizer
  428. * Styled resizer
  429. *
  430. * @requires modules/drag-panes
  431. */
  432. lineWidth: 4,
  433. /**
  434. * Horizontal offset of the control line.
  435. *
  436. * @sample {highstock} stock/yaxis/styled-resizer
  437. * Styled resizer
  438. *
  439. * @requires modules/drag-panes
  440. */
  441. x: 0,
  442. /**
  443. * Vertical offset of the control line.
  444. *
  445. * @sample {highstock} stock/yaxis/styled-resizer
  446. * Styled resizer
  447. *
  448. * @requires modules/drag-panes
  449. */
  450. y: 0
  451. }
  452. };
  453. return AxisResizer;
  454. }());
  455. // Keep resizer reference on axis update
  456. Axis.keepProps.push('resizer');
  457. /* eslint-disable no-invalid-this */
  458. // Add new AxisResizer, update or remove it
  459. addEvent(Axis, 'afterRender', function () {
  460. var axis = this, resizer = axis.resizer, resizerOptions = axis.options.resize, enabled;
  461. if (resizerOptions) {
  462. enabled = resizerOptions.enabled !== false;
  463. if (resizer) {
  464. // Resizer present and enabled
  465. if (enabled) {
  466. // Update options
  467. resizer.init(axis, true);
  468. // Resizer present, but disabled
  469. }
  470. else {
  471. // Destroy the resizer
  472. resizer.destroy();
  473. }
  474. }
  475. else {
  476. // Resizer not present and enabled
  477. if (enabled) {
  478. // Add new resizer
  479. axis.resizer = new H.AxisResizer(axis);
  480. }
  481. // Resizer not present and disabled, so do nothing
  482. }
  483. }
  484. });
  485. // Clear resizer on axis remove.
  486. addEvent(Axis, 'destroy', function (e) {
  487. if (!e.keepEvents && this.resizer) {
  488. this.resizer.destroy();
  489. }
  490. });
  491. // Prevent any hover effects while dragging a control line of AxisResizer.
  492. wrap(Pointer.prototype, 'runPointActions', function (proceed) {
  493. if (!this.chart.activeResizer) {
  494. proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  495. }
  496. });
  497. // Prevent default drag action detection while dragging a control line of
  498. // AxisResizer. (#7563)
  499. wrap(Pointer.prototype, 'drag', function (proceed) {
  500. if (!this.chart.activeResizer) {
  501. proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  502. }
  503. });
  504. merge(true, Axis.defaultYAxisOptions, AxisResizer.resizerOptions);
  505. H.AxisResizer = AxisResizer;
  506. export default H.AxisResizer;