Scrollbar.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882
  1. /* *
  2. *
  3. * (c) 2010-2019 Torstein Honsi
  4. *
  5. * License: www.highcharts.com/license
  6. *
  7. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  8. *
  9. * */
  10. 'use strict';
  11. import H from './Globals.js';
  12. import U from './Utilities.js';
  13. var correctFloat = U.correctFloat, defined = U.defined, destroyObjectProperties = U.destroyObjectProperties, pick = U.pick;
  14. import './Axis.js';
  15. import './Options.js';
  16. var addEvent = H.addEvent, Axis = H.Axis, defaultOptions = H.defaultOptions, fireEvent = H.fireEvent, hasTouch = H.hasTouch, isTouchDevice = H.isTouchDevice, merge = H.merge, removeEvent = H.removeEvent, swapXY;
  17. /**
  18. *
  19. * The scrollbar is a means of panning over the X axis of a stock chart.
  20. * Scrollbars can also be applied to other types of axes.
  21. *
  22. * Another approach to scrollable charts is the [chart.scrollablePlotArea](
  23. * https://api.highcharts.com/highcharts/chart.scrollablePlotArea) option that
  24. * is especially suitable for simpler cartesian charts on mobile.
  25. *
  26. * In styled mode, all the presentational options for the
  27. * scrollbar are replaced by the classes `.highcharts-scrollbar-thumb`,
  28. * `.highcharts-scrollbar-arrow`, `.highcharts-scrollbar-button`,
  29. * `.highcharts-scrollbar-rifles` and `.highcharts-scrollbar-track`.
  30. *
  31. * @sample stock/yaxis/inverted-bar-scrollbar/
  32. * A scrollbar on a simple bar chart
  33. *
  34. * @product highstock gantt
  35. * @optionparent scrollbar
  36. */
  37. var defaultScrollbarOptions = {
  38. /**
  39. * The height of the scrollbar. The height also applies to the width
  40. * of the scroll arrows so that they are always squares. Defaults to
  41. * 20 for touch devices and 14 for mouse devices.
  42. *
  43. * @sample stock/scrollbar/height/
  44. * A 30px scrollbar
  45. *
  46. * @type {number}
  47. * @default 20/14
  48. */
  49. height: isTouchDevice ? 20 : 14,
  50. /**
  51. * The border rounding radius of the bar.
  52. *
  53. * @sample stock/scrollbar/style/
  54. * Scrollbar styling
  55. */
  56. barBorderRadius: 0,
  57. /**
  58. * The corner radius of the scrollbar buttons.
  59. *
  60. * @sample stock/scrollbar/style/
  61. * Scrollbar styling
  62. */
  63. buttonBorderRadius: 0,
  64. /**
  65. * Enable or disable the scrollbar.
  66. *
  67. * @sample stock/scrollbar/enabled/
  68. * Disable the scrollbar, only use navigator
  69. *
  70. * @type {boolean}
  71. * @default true
  72. * @apioption scrollbar.enabled
  73. */
  74. /**
  75. * Whether to redraw the main chart as the scrollbar or the navigator
  76. * zoomed window is moved. Defaults to `true` for modern browsers and
  77. * `false` for legacy IE browsers as well as mobile devices.
  78. *
  79. * @sample stock/scrollbar/liveredraw
  80. * Setting live redraw to false
  81. *
  82. * @type {boolean}
  83. * @since 1.3
  84. */
  85. liveRedraw: void 0,
  86. /**
  87. * The margin between the scrollbar and its axis when the scrollbar is
  88. * applied directly to an axis.
  89. */
  90. margin: 10,
  91. /**
  92. * The minimum width of the scrollbar.
  93. *
  94. * @since 1.2.5
  95. */
  96. minWidth: 6,
  97. /**
  98. * Whether to show or hide the scrollbar when the scrolled content is
  99. * zoomed out to it full extent.
  100. *
  101. * @type {boolean}
  102. * @default true
  103. * @apioption scrollbar.showFull
  104. */
  105. step: 0.2,
  106. /**
  107. * The z index of the scrollbar group.
  108. */
  109. zIndex: 3,
  110. /**
  111. * The background color of the scrollbar itself.
  112. *
  113. * @sample stock/scrollbar/style/
  114. * Scrollbar styling
  115. *
  116. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  117. */
  118. barBackgroundColor: '#cccccc',
  119. /**
  120. * The width of the bar's border.
  121. *
  122. * @sample stock/scrollbar/style/
  123. * Scrollbar styling
  124. */
  125. barBorderWidth: 1,
  126. /**
  127. * The color of the scrollbar's border.
  128. *
  129. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  130. */
  131. barBorderColor: '#cccccc',
  132. /**
  133. * The color of the small arrow inside the scrollbar buttons.
  134. *
  135. * @sample stock/scrollbar/style/
  136. * Scrollbar styling
  137. *
  138. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  139. */
  140. buttonArrowColor: '#333333',
  141. /**
  142. * The color of scrollbar buttons.
  143. *
  144. * @sample stock/scrollbar/style/
  145. * Scrollbar styling
  146. *
  147. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  148. */
  149. buttonBackgroundColor: '#e6e6e6',
  150. /**
  151. * The color of the border of the scrollbar buttons.
  152. *
  153. * @sample stock/scrollbar/style/
  154. * Scrollbar styling
  155. *
  156. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  157. */
  158. buttonBorderColor: '#cccccc',
  159. /**
  160. * The border width of the scrollbar buttons.
  161. *
  162. * @sample stock/scrollbar/style/
  163. * Scrollbar styling
  164. */
  165. buttonBorderWidth: 1,
  166. /**
  167. * The color of the small rifles in the middle of the scrollbar.
  168. *
  169. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  170. */
  171. rifleColor: '#333333',
  172. /**
  173. * The color of the track background.
  174. *
  175. * @sample stock/scrollbar/style/
  176. * Scrollbar styling
  177. *
  178. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  179. */
  180. trackBackgroundColor: '#f2f2f2',
  181. /**
  182. * The color of the border of the scrollbar track.
  183. *
  184. * @sample stock/scrollbar/style/
  185. * Scrollbar styling
  186. *
  187. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  188. */
  189. trackBorderColor: '#f2f2f2',
  190. /**
  191. * The corner radius of the border of the scrollbar track.
  192. *
  193. * @sample stock/scrollbar/style/
  194. * Scrollbar styling
  195. *
  196. * @type {number}
  197. * @default 0
  198. * @apioption scrollbar.trackBorderRadius
  199. */
  200. /**
  201. * The width of the border of the scrollbar track.
  202. *
  203. * @sample stock/scrollbar/style/
  204. * Scrollbar styling
  205. */
  206. trackBorderWidth: 1
  207. };
  208. defaultOptions.scrollbar = merge(true, defaultScrollbarOptions, defaultOptions.scrollbar);
  209. /**
  210. * When we have vertical scrollbar, rifles and arrow in buttons should be
  211. * rotated. The same method is used in Navigator's handles, to rotate them.
  212. *
  213. * @function Highcharts.swapXY
  214. *
  215. * @param {Highcharts.SVGPathArray} path
  216. * Path to be rotated.
  217. *
  218. * @param {boolean} [vertical]
  219. * If vertical scrollbar, swap x-y values.
  220. *
  221. * @return {Highcharts.SVGPathArray}
  222. * Rotated path.
  223. */
  224. H.swapXY = swapXY = function (path, vertical) {
  225. var i, len = path.length, temp;
  226. if (vertical) {
  227. for (i = 0; i < len; i += 3) {
  228. temp = path[i + 1];
  229. path[i + 1] = path[i + 2];
  230. path[i + 2] = temp;
  231. }
  232. }
  233. return path;
  234. };
  235. /* eslint-disable no-invalid-this, valid-jsdoc */
  236. /**
  237. * A reusable scrollbar, internally used in Highstock's navigator and optionally
  238. * on individual axes.
  239. *
  240. * @private
  241. * @class
  242. * @name Highcharts.Scrollbar
  243. * @param {Highcharts.SVGRenderer} renderer
  244. * @param {Highcharts.ScrollbarOptions} options
  245. * @param {Highcharts.Chart} chart
  246. */
  247. function Scrollbar(renderer, options, chart) {
  248. this.init(renderer, options, chart);
  249. }
  250. Scrollbar.prototype = {
  251. /**
  252. * @private
  253. * @function Highcharts.Scrollbar#init
  254. * @param {Highcharts.SVGRenderer} renderer
  255. * @param {Highcharts.ScrollbarOptions} options
  256. * @param {Highcharts.Chart} chart
  257. * @return {void}
  258. */
  259. init: function (renderer, options, chart) {
  260. this.scrollbarButtons = [];
  261. this.renderer = renderer;
  262. this.userOptions = options;
  263. this.options = merge(defaultScrollbarOptions, options);
  264. this.chart = chart;
  265. // backward compatibility
  266. this.size = pick(this.options.size, this.options.height);
  267. // Init
  268. if (options.enabled) {
  269. this.render();
  270. this.initEvents();
  271. this.addEvents();
  272. }
  273. },
  274. /**
  275. * Render scrollbar with all required items.
  276. *
  277. * @private
  278. * @function Highcharts.Scrollbar#render
  279. */
  280. render: function () {
  281. var scroller = this, renderer = scroller.renderer, options = scroller.options, size = scroller.size, styledMode = this.chart.styledMode, group;
  282. // Draw the scrollbar group
  283. scroller.group = group = renderer.g('scrollbar').attr({
  284. zIndex: options.zIndex,
  285. translateY: -99999
  286. }).add();
  287. // Draw the scrollbar track:
  288. scroller.track = renderer.rect()
  289. .addClass('highcharts-scrollbar-track')
  290. .attr({
  291. x: 0,
  292. r: options.trackBorderRadius || 0,
  293. height: size,
  294. width: size
  295. }).add(group);
  296. if (!styledMode) {
  297. scroller.track.attr({
  298. fill: options.trackBackgroundColor,
  299. stroke: options.trackBorderColor,
  300. 'stroke-width': options.trackBorderWidth
  301. });
  302. }
  303. this.trackBorderWidth = scroller.track.strokeWidth();
  304. scroller.track.attr({
  305. y: -this.trackBorderWidth % 2 / 2
  306. });
  307. // Draw the scrollbar itself
  308. scroller.scrollbarGroup = renderer.g().add(group);
  309. scroller.scrollbar = renderer.rect()
  310. .addClass('highcharts-scrollbar-thumb')
  311. .attr({
  312. height: size,
  313. width: size,
  314. r: options.barBorderRadius || 0
  315. }).add(scroller.scrollbarGroup);
  316. scroller.scrollbarRifles = renderer
  317. .path(swapXY([
  318. 'M',
  319. -3, size / 4,
  320. 'L',
  321. -3, 2 * size / 3,
  322. 'M',
  323. 0, size / 4,
  324. 'L',
  325. 0, 2 * size / 3,
  326. 'M',
  327. 3, size / 4,
  328. 'L',
  329. 3, 2 * size / 3
  330. ], options.vertical))
  331. .addClass('highcharts-scrollbar-rifles')
  332. .add(scroller.scrollbarGroup);
  333. if (!styledMode) {
  334. scroller.scrollbar.attr({
  335. fill: options.barBackgroundColor,
  336. stroke: options.barBorderColor,
  337. 'stroke-width': options.barBorderWidth
  338. });
  339. scroller.scrollbarRifles.attr({
  340. stroke: options.rifleColor,
  341. 'stroke-width': 1
  342. });
  343. }
  344. scroller.scrollbarStrokeWidth = scroller.scrollbar.strokeWidth();
  345. scroller.scrollbarGroup.translate(-scroller.scrollbarStrokeWidth % 2 / 2, -scroller.scrollbarStrokeWidth % 2 / 2);
  346. // Draw the buttons:
  347. scroller.drawScrollbarButton(0);
  348. scroller.drawScrollbarButton(1);
  349. },
  350. /**
  351. * Position the scrollbar, method called from a parent with defined
  352. * dimensions.
  353. *
  354. * @private
  355. * @function Highcharts.Scrollbar#position
  356. * @param {number} x
  357. * x-position on the chart
  358. * @param {number} y
  359. * y-position on the chart
  360. * @param {number} width
  361. * width of the scrollbar
  362. * @param {number} height
  363. * height of the scorllbar
  364. * @return {void}
  365. */
  366. position: function (x, y, width, height) {
  367. var scroller = this, options = scroller.options, vertical = options.vertical, xOffset = height, yOffset = 0, method = scroller.rendered ? 'animate' : 'attr';
  368. scroller.x = x;
  369. scroller.y = y + this.trackBorderWidth;
  370. scroller.width = width; // width with buttons
  371. scroller.height = height;
  372. scroller.xOffset = xOffset;
  373. scroller.yOffset = yOffset;
  374. // If Scrollbar is a vertical type, swap options:
  375. if (vertical) {
  376. scroller.width = scroller.yOffset = width = yOffset = scroller.size;
  377. scroller.xOffset = xOffset = 0;
  378. scroller.barWidth = height - width * 2; // width without buttons
  379. scroller.x = x = x + scroller.options.margin;
  380. }
  381. else {
  382. scroller.height = scroller.xOffset = height = xOffset =
  383. scroller.size;
  384. scroller.barWidth = width - height * 2; // width without buttons
  385. scroller.y = scroller.y + scroller.options.margin;
  386. }
  387. // Set general position for a group:
  388. scroller.group[method]({
  389. translateX: x,
  390. translateY: scroller.y
  391. });
  392. // Resize background/track:
  393. scroller.track[method]({
  394. width: width,
  395. height: height
  396. });
  397. // Move right/bottom button ot it's place:
  398. scroller.scrollbarButtons[1][method]({
  399. translateX: vertical ? 0 : width - xOffset,
  400. translateY: vertical ? height - yOffset : 0
  401. });
  402. },
  403. /**
  404. * Draw the scrollbar buttons with arrows
  405. *
  406. * @private
  407. * @function Highcharts.Scrollbar#drawScrollbarButton
  408. * @param {number} index
  409. * 0 is left, 1 is right
  410. * @return {void}
  411. */
  412. drawScrollbarButton: function (index) {
  413. var scroller = this, renderer = scroller.renderer, scrollbarButtons = scroller.scrollbarButtons, options = scroller.options, size = scroller.size, group, tempElem;
  414. group = renderer.g().add(scroller.group);
  415. scrollbarButtons.push(group);
  416. // Create a rectangle for the scrollbar button
  417. tempElem = renderer.rect()
  418. .addClass('highcharts-scrollbar-button')
  419. .add(group);
  420. // Presentational attributes
  421. if (!this.chart.styledMode) {
  422. tempElem.attr({
  423. stroke: options.buttonBorderColor,
  424. 'stroke-width': options.buttonBorderWidth,
  425. fill: options.buttonBackgroundColor
  426. });
  427. }
  428. // Place the rectangle based on the rendered stroke width
  429. tempElem.attr(tempElem.crisp({
  430. x: -0.5,
  431. y: -0.5,
  432. width: size + 1,
  433. height: size + 1,
  434. r: options.buttonBorderRadius
  435. }, tempElem.strokeWidth()));
  436. // Button arrow
  437. tempElem = renderer
  438. .path(swapXY([
  439. 'M',
  440. size / 2 + (index ? -1 : 1),
  441. size / 2 - 3,
  442. 'L',
  443. size / 2 + (index ? -1 : 1),
  444. size / 2 + 3,
  445. 'L',
  446. size / 2 + (index ? 2 : -2),
  447. size / 2
  448. ], options.vertical))
  449. .addClass('highcharts-scrollbar-arrow')
  450. .add(scrollbarButtons[index]);
  451. if (!this.chart.styledMode) {
  452. tempElem.attr({
  453. fill: options.buttonArrowColor
  454. });
  455. }
  456. },
  457. /**
  458. * Set scrollbar size, with a given scale.
  459. *
  460. * @private
  461. * @function Highcharts.Scrollbar#setRange
  462. * @param {number} from
  463. * scale (0-1) where bar should start
  464. * @param {number} to
  465. * scale (0-1) where bar should end
  466. * @return {void}
  467. */
  468. setRange: function (from, to) {
  469. var scroller = this, options = scroller.options, vertical = options.vertical, minWidth = options.minWidth, fullWidth = scroller.barWidth, fromPX, toPX, newPos, newSize, newRiflesPos, method = (this.rendered &&
  470. !this.hasDragged &&
  471. !(this.chart.navigator && this.chart.navigator.hasDragged)) ? 'animate' : 'attr';
  472. if (!defined(fullWidth)) {
  473. return;
  474. }
  475. from = Math.max(from, 0);
  476. fromPX = Math.ceil(fullWidth * from);
  477. toPX = fullWidth * Math.min(to, 1);
  478. scroller.calculatedWidth = newSize = correctFloat(toPX - fromPX);
  479. // We need to recalculate position, if minWidth is used
  480. if (newSize < minWidth) {
  481. fromPX = (fullWidth - minWidth + newSize) * from;
  482. newSize = minWidth;
  483. }
  484. newPos = Math.floor(fromPX + scroller.xOffset + scroller.yOffset);
  485. newRiflesPos = newSize / 2 - 0.5; // -0.5 -> rifle line width / 2
  486. // Store current position:
  487. scroller.from = from;
  488. scroller.to = to;
  489. if (!vertical) {
  490. scroller.scrollbarGroup[method]({
  491. translateX: newPos
  492. });
  493. scroller.scrollbar[method]({
  494. width: newSize
  495. });
  496. scroller.scrollbarRifles[method]({
  497. translateX: newRiflesPos
  498. });
  499. scroller.scrollbarLeft = newPos;
  500. scroller.scrollbarTop = 0;
  501. }
  502. else {
  503. scroller.scrollbarGroup[method]({
  504. translateY: newPos
  505. });
  506. scroller.scrollbar[method]({
  507. height: newSize
  508. });
  509. scroller.scrollbarRifles[method]({
  510. translateY: newRiflesPos
  511. });
  512. scroller.scrollbarTop = newPos;
  513. scroller.scrollbarLeft = 0;
  514. }
  515. if (newSize <= 12) {
  516. scroller.scrollbarRifles.hide();
  517. }
  518. else {
  519. scroller.scrollbarRifles.show(true);
  520. }
  521. // Show or hide the scrollbar based on the showFull setting
  522. if (options.showFull === false) {
  523. if (from <= 0 && to >= 1) {
  524. scroller.group.hide();
  525. }
  526. else {
  527. scroller.group.show();
  528. }
  529. }
  530. scroller.rendered = true;
  531. },
  532. /**
  533. * Init events methods, so we have an access to the Scrollbar itself
  534. *
  535. * @private
  536. * @function Highcharts.Scrollbar#initEvents
  537. * @return {void}
  538. * @fires Highcharts.Scrollbar#event:changed
  539. */
  540. initEvents: function () {
  541. var scroller = this;
  542. /**
  543. * Event handler for the mouse move event.
  544. * @private
  545. */
  546. scroller.mouseMoveHandler = function (e) {
  547. var normalizedEvent = scroller.chart.pointer.normalize(e), options = scroller.options, direction = options.vertical ? 'chartY' : 'chartX', initPositions = scroller.initPositions, scrollPosition, chartPosition, change;
  548. // In iOS, a mousemove event with e.pageX === 0 is fired when
  549. // holding the finger down in the center of the scrollbar. This
  550. // should be ignored.
  551. if (scroller.grabbedCenter &&
  552. // #4696, scrollbar failed on Android
  553. (!e.touches || e.touches[0][direction] !== 0)) {
  554. chartPosition = scroller.cursorToScrollbarPosition(normalizedEvent)[direction];
  555. scrollPosition = scroller[direction];
  556. change = chartPosition - scrollPosition;
  557. scroller.hasDragged = true;
  558. scroller.updatePosition(initPositions[0] + change, initPositions[1] + change);
  559. if (scroller.hasDragged) {
  560. fireEvent(scroller, 'changed', {
  561. from: scroller.from,
  562. to: scroller.to,
  563. trigger: 'scrollbar',
  564. DOMType: e.type,
  565. DOMEvent: e
  566. });
  567. }
  568. }
  569. };
  570. /**
  571. * Event handler for the mouse up event.
  572. * @private
  573. */
  574. scroller.mouseUpHandler = function (e) {
  575. if (scroller.hasDragged) {
  576. fireEvent(scroller, 'changed', {
  577. from: scroller.from,
  578. to: scroller.to,
  579. trigger: 'scrollbar',
  580. DOMType: e.type,
  581. DOMEvent: e
  582. });
  583. }
  584. scroller.grabbedCenter =
  585. scroller.hasDragged =
  586. scroller.chartX =
  587. scroller.chartY = null;
  588. };
  589. scroller.mouseDownHandler = function (e) {
  590. var normalizedEvent = scroller.chart.pointer.normalize(e), mousePosition = scroller.cursorToScrollbarPosition(normalizedEvent);
  591. scroller.chartX = mousePosition.chartX;
  592. scroller.chartY = mousePosition.chartY;
  593. scroller.initPositions = [scroller.from, scroller.to];
  594. scroller.grabbedCenter = true;
  595. };
  596. scroller.buttonToMinClick = function (e) {
  597. var range = correctFloat(scroller.to - scroller.from) *
  598. scroller.options.step;
  599. scroller.updatePosition(correctFloat(scroller.from - range), correctFloat(scroller.to - range));
  600. fireEvent(scroller, 'changed', {
  601. from: scroller.from,
  602. to: scroller.to,
  603. trigger: 'scrollbar',
  604. DOMEvent: e
  605. });
  606. };
  607. scroller.buttonToMaxClick = function (e) {
  608. var range = (scroller.to - scroller.from) * scroller.options.step;
  609. scroller.updatePosition(scroller.from + range, scroller.to + range);
  610. fireEvent(scroller, 'changed', {
  611. from: scroller.from,
  612. to: scroller.to,
  613. trigger: 'scrollbar',
  614. DOMEvent: e
  615. });
  616. };
  617. scroller.trackClick = function (e) {
  618. var normalizedEvent = scroller.chart.pointer.normalize(e), range = scroller.to - scroller.from, top = scroller.y + scroller.scrollbarTop, left = scroller.x + scroller.scrollbarLeft;
  619. if ((scroller.options.vertical && normalizedEvent.chartY > top) ||
  620. (!scroller.options.vertical && normalizedEvent.chartX > left)) {
  621. // On the top or on the left side of the track:
  622. scroller.updatePosition(scroller.from + range, scroller.to + range);
  623. }
  624. else {
  625. // On the bottom or the right side of the track:
  626. scroller.updatePosition(scroller.from - range, scroller.to - range);
  627. }
  628. fireEvent(scroller, 'changed', {
  629. from: scroller.from,
  630. to: scroller.to,
  631. trigger: 'scrollbar',
  632. DOMEvent: e
  633. });
  634. };
  635. },
  636. /**
  637. * Get normalized (0-1) cursor position over the scrollbar
  638. *
  639. * @private
  640. * @function Highcharts.Scrollbar#cursorToScrollbarPosition
  641. *
  642. * @param {*} normalizedEvent
  643. * normalized event, with chartX and chartY values
  644. *
  645. * @return {Highcharts.Dictionary<number>}
  646. * Local position {chartX, chartY}
  647. */
  648. cursorToScrollbarPosition: function (normalizedEvent) {
  649. var scroller = this, options = scroller.options, minWidthDifference = options.minWidth > scroller.calculatedWidth ?
  650. options.minWidth :
  651. 0; // minWidth distorts translation
  652. return {
  653. chartX: (normalizedEvent.chartX - scroller.x -
  654. scroller.xOffset) /
  655. (scroller.barWidth - minWidthDifference),
  656. chartY: (normalizedEvent.chartY - scroller.y -
  657. scroller.yOffset) /
  658. (scroller.barWidth - minWidthDifference)
  659. };
  660. },
  661. /**
  662. * Update position option in the Scrollbar, with normalized 0-1 scale
  663. *
  664. * @private
  665. * @function Highcharts.Scrollbar#updatePosition
  666. * @param {number} from
  667. * @param {number} to
  668. * @return {void}
  669. */
  670. updatePosition: function (from, to) {
  671. if (to > 1) {
  672. from = correctFloat(1 - correctFloat(to - from));
  673. to = 1;
  674. }
  675. if (from < 0) {
  676. to = correctFloat(to - from);
  677. from = 0;
  678. }
  679. this.from = from;
  680. this.to = to;
  681. },
  682. /**
  683. * Update the scrollbar with new options
  684. *
  685. * @private
  686. * @function Highcharts.Scrollbar#update
  687. * @param {Highcharts.ScrollbarOptions} options
  688. * @return {void}
  689. */
  690. update: function (options) {
  691. this.destroy();
  692. this.init(this.chart.renderer, merge(true, this.options, options), this.chart);
  693. },
  694. /**
  695. * Set up the mouse and touch events for the Scrollbar
  696. *
  697. * @private
  698. * @function Highcharts.Scrollbar#addEvents
  699. * @return {void}
  700. */
  701. addEvents: function () {
  702. var buttonsOrder = this.options.inverted ? [1, 0] : [0, 1], buttons = this.scrollbarButtons, bar = this.scrollbarGroup.element, track = this.track.element, mouseDownHandler = this.mouseDownHandler, mouseMoveHandler = this.mouseMoveHandler, mouseUpHandler = this.mouseUpHandler, _events;
  703. // Mouse events
  704. _events = [
  705. [buttons[buttonsOrder[0]].element, 'click', this.buttonToMinClick],
  706. [buttons[buttonsOrder[1]].element, 'click', this.buttonToMaxClick],
  707. [track, 'click', this.trackClick],
  708. [bar, 'mousedown', mouseDownHandler],
  709. [bar.ownerDocument, 'mousemove', mouseMoveHandler],
  710. [bar.ownerDocument, 'mouseup', mouseUpHandler]
  711. ];
  712. // Touch events
  713. if (hasTouch) {
  714. _events.push([bar, 'touchstart', mouseDownHandler], [bar.ownerDocument, 'touchmove', mouseMoveHandler], [bar.ownerDocument, 'touchend', mouseUpHandler]);
  715. }
  716. // Add them all
  717. _events.forEach(function (args) {
  718. addEvent.apply(null, args);
  719. });
  720. this._events = _events;
  721. },
  722. /**
  723. * Removes the event handlers attached previously with addEvents.
  724. *
  725. * @private
  726. * @function Highcharts.Scrollbar#removeEvents
  727. * @return {void}
  728. */
  729. removeEvents: function () {
  730. this._events.forEach(function (args) {
  731. removeEvent.apply(null, args);
  732. });
  733. this._events.length = 0;
  734. },
  735. /**
  736. * Destroys allocated elements.
  737. *
  738. * @private
  739. * @function Highcharts.Scrollbar#destroy
  740. * @return {void}
  741. */
  742. destroy: function () {
  743. var scroller = this.chart.scroller;
  744. // Disconnect events added in addEvents
  745. this.removeEvents();
  746. // Destroy properties
  747. [
  748. 'track',
  749. 'scrollbarRifles',
  750. 'scrollbar',
  751. 'scrollbarGroup',
  752. 'group'
  753. ].forEach(function (prop) {
  754. if (this[prop] && this[prop].destroy) {
  755. this[prop] = this[prop].destroy();
  756. }
  757. }, this);
  758. // #6421, chart may have more scrollbars
  759. if (scroller && this === scroller.scrollbar) {
  760. scroller.scrollbar = null;
  761. // Destroy elements in collection
  762. destroyObjectProperties(scroller.scrollbarButtons);
  763. }
  764. }
  765. };
  766. if (!H.Scrollbar) {
  767. /* *
  768. * Wrap axis initialization and create scrollbar if enabled:
  769. */
  770. addEvent(Axis, 'afterInit', function () {
  771. var axis = this;
  772. if (axis.options &&
  773. axis.options.scrollbar &&
  774. axis.options.scrollbar.enabled) {
  775. // Predefined options:
  776. axis.options.scrollbar.vertical = !axis.horiz;
  777. axis.options.startOnTick = axis.options.endOnTick = false;
  778. axis.scrollbar = new Scrollbar(axis.chart.renderer, axis.options.scrollbar, axis.chart);
  779. addEvent(axis.scrollbar, 'changed', function (e) {
  780. var unitedMin = Math.min(pick(axis.options.min, axis.min), axis.min, axis.dataMin), unitedMax = Math.max(pick(axis.options.max, axis.max), axis.max, axis.dataMax), range = unitedMax - unitedMin, to, from;
  781. if ((axis.horiz && !axis.reversed) ||
  782. (!axis.horiz && axis.reversed)) {
  783. to = unitedMin + range * this.to;
  784. from = unitedMin + range * this.from;
  785. }
  786. else {
  787. // y-values in browser are reversed, but this also applies
  788. // for reversed horizontal axis:
  789. to = unitedMin + range * (1 - this.from);
  790. from = unitedMin + range * (1 - this.to);
  791. }
  792. if (pick(this.options.liveRedraw, H.svg && !H.isTouchDevice && !this.chart.isBoosting) ||
  793. // Mouseup always should change extremes
  794. e.DOMType === 'mouseup' ||
  795. // Internal events
  796. !defined(e.DOMType)) {
  797. axis.setExtremes(from, to, true, e.DOMType !== 'mousemove', e);
  798. }
  799. else {
  800. // When live redraw is disabled, don't change extremes
  801. // Only change the position of the scollbar thumb
  802. this.setRange(this.from, this.to);
  803. }
  804. });
  805. }
  806. });
  807. /* *
  808. * Wrap rendering axis, and update scrollbar if one is created:
  809. */
  810. addEvent(Axis, 'afterRender', function () {
  811. var axis = this, scrollMin = Math.min(pick(axis.options.min, axis.min), axis.min, pick(axis.dataMin, axis.min) // #6930
  812. ), scrollMax = Math.max(pick(axis.options.max, axis.max), axis.max, pick(axis.dataMax, axis.max) // #6930
  813. ), scrollbar = axis.scrollbar, offset = axis.axisTitleMargin + (axis.titleOffset || 0), scrollbarsOffsets = axis.chart.scrollbarsOffsets, axisMargin = axis.options.margin || 0, offsetsIndex, from, to;
  814. if (scrollbar) {
  815. if (axis.horiz) {
  816. // Reserve space for labels/title
  817. if (!axis.opposite) {
  818. scrollbarsOffsets[1] += offset;
  819. }
  820. scrollbar.position(axis.left, axis.top + axis.height + 2 + scrollbarsOffsets[1] -
  821. (axis.opposite ? axisMargin : 0), axis.width, axis.height);
  822. // Next scrollbar should reserve space for margin (if set)
  823. if (!axis.opposite) {
  824. scrollbarsOffsets[1] += axisMargin;
  825. }
  826. offsetsIndex = 1;
  827. }
  828. else {
  829. // Reserve space for labels/title
  830. if (axis.opposite) {
  831. scrollbarsOffsets[0] += offset;
  832. }
  833. scrollbar.position(axis.left + axis.width + 2 + scrollbarsOffsets[0] -
  834. (axis.opposite ? 0 : axisMargin), axis.top, axis.width, axis.height);
  835. // Next scrollbar should reserve space for margin (if set)
  836. if (axis.opposite) {
  837. scrollbarsOffsets[0] += axisMargin;
  838. }
  839. offsetsIndex = 0;
  840. }
  841. scrollbarsOffsets[offsetsIndex] += scrollbar.size +
  842. scrollbar.options.margin;
  843. if (isNaN(scrollMin) ||
  844. isNaN(scrollMax) ||
  845. !defined(axis.min) ||
  846. !defined(axis.max) ||
  847. axis.min === axis.max // #10733
  848. ) {
  849. // default action: when extremes are the same or there is not
  850. // extremes on the axis, but scrollbar exists, make it full size
  851. scrollbar.setRange(0, 1);
  852. }
  853. else {
  854. from =
  855. (axis.min - scrollMin) / (scrollMax - scrollMin);
  856. to =
  857. (axis.max - scrollMin) / (scrollMax - scrollMin);
  858. if ((axis.horiz && !axis.reversed) ||
  859. (!axis.horiz && axis.reversed)) {
  860. scrollbar.setRange(from, to);
  861. }
  862. else {
  863. // inverse vertical axis
  864. scrollbar.setRange(1 - to, 1 - from);
  865. }
  866. }
  867. }
  868. });
  869. /* *
  870. * Make space for a scrollbar
  871. * @private
  872. */
  873. addEvent(Axis, 'afterGetOffset', function () {
  874. var axis = this, index = axis.horiz ? 2 : 1, scrollbar = axis.scrollbar;
  875. if (scrollbar) {
  876. axis.chart.scrollbarsOffsets = [0, 0]; // reset scrollbars offsets
  877. axis.chart.axisOffset[index] +=
  878. scrollbar.size + scrollbar.options.margin;
  879. }
  880. });
  881. H.Scrollbar = Scrollbar;
  882. }