Scrollbar.js 32 KB

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