DragPanes.js 19 KB

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