stock-tools-gui.js 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456
  1. /**
  2. * GUI generator for Stock tools
  3. *
  4. * (c) 2009-2017 Sebastian Bochan
  5. *
  6. * License: www.highcharts.com/license
  7. */
  8. 'use strict';
  9. import H from '../parts/Globals.js';
  10. var addEvent = H.addEvent,
  11. createElement = H.createElement,
  12. pick = H.pick,
  13. isArray = H.isArray,
  14. fireEvent = H.fireEvent,
  15. getStyle = H.getStyle,
  16. merge = H.merge,
  17. css = H.css,
  18. win = H.win,
  19. DIV = 'div',
  20. SPAN = 'span',
  21. UL = 'ul',
  22. LI = 'li',
  23. PREFIX = 'highcharts-',
  24. activeClass = PREFIX + 'active';
  25. H.setOptions({
  26. /**
  27. * @optionparent lang
  28. */
  29. lang: {
  30. /**
  31. * Configure the stockTools GUI titles(hints) in the chart. Requires
  32. * the `stock-tools.js` module to be loaded.
  33. *
  34. * @product highstock
  35. * @since 7.0.0
  36. * @type {Object}
  37. */
  38. stockTools: {
  39. gui: {
  40. // Main buttons:
  41. simpleShapes: 'Simple shapes',
  42. lines: 'Lines',
  43. crookedLines: 'Crooked lines',
  44. measure: 'Measure',
  45. advanced: 'Advanced',
  46. toggleAnnotations: 'Toggle annotations',
  47. verticalLabels: 'Vertical labels',
  48. flags: 'Flags',
  49. zoomChange: 'Zoom change',
  50. typeChange: 'Type change',
  51. saveChart: 'Save chart',
  52. indicators: 'Indicators',
  53. currentPriceIndicator: 'Current Price Indicators',
  54. // Other features:
  55. zoomX: 'Zoom X',
  56. zoomY: 'Zoom Y',
  57. zoomXY: 'Zooom XY',
  58. fullScreen: 'Fullscreen',
  59. typeOHLC: 'OHLC',
  60. typeLine: 'Line',
  61. typeCandlestick: 'Candlestick',
  62. // Basic shapes:
  63. circle: 'Circle',
  64. label: 'Label',
  65. rectangle: 'Rectangle',
  66. // Flags:
  67. flagCirclepin: 'Flag circle',
  68. flagDiamondpin: 'Flag diamond',
  69. flagSquarepin: 'Flag square',
  70. flagSimplepin: 'Flag simple',
  71. // Measures:
  72. measureXY: 'Measure XY',
  73. measureX: 'Measure X',
  74. measureY: 'Measure Y',
  75. // Segment, ray and line:
  76. segment: 'Segment',
  77. arrowSegment: 'Arrow segment',
  78. ray: 'Ray',
  79. arrowRay: 'Arrow ray',
  80. line: 'Line',
  81. arrowLine: 'Arrow line',
  82. horizontalLine: 'Horizontal line',
  83. verticalLine: 'Vertical line',
  84. infinityLine: 'Infinity line',
  85. // Crooked lines:
  86. crooked3: 'Crooked 3 line',
  87. crooked5: 'Crooked 5 line',
  88. elliott3: 'Elliott 3 line',
  89. elliott5: 'Elliott 5 line',
  90. // Counters:
  91. verticalCounter: 'Vertical counter',
  92. verticalLabel: 'Vertical label',
  93. verticalArrow: 'Vertical arrow',
  94. // Advanced:
  95. fibonacci: 'Fibonacci',
  96. pitchfork: 'Pitchfork',
  97. parallelChannel: 'Parallel channel'
  98. }
  99. },
  100. navigation: {
  101. popup: {
  102. // Annotations:
  103. circle: 'Circle',
  104. rectangle: 'Rectangle',
  105. label: 'Label',
  106. segment: 'Segment',
  107. arrowSegment: 'Arrow segment',
  108. ray: 'Ray',
  109. arrowRay: 'Arrow ray',
  110. line: 'Line',
  111. arrowLine: 'Arrow line',
  112. horizontalLine: 'Horizontal line',
  113. verticalLine: 'Vertical line',
  114. crooked3: 'Crooked 3 line',
  115. crooked5: 'Crooked 5 line',
  116. elliott3: 'Elliott 3 line',
  117. elliott5: 'Elliott 5 line',
  118. verticalCounter: 'Vertical counter',
  119. verticalLabel: 'Vertical label',
  120. verticalArrow: 'Vertical arrow',
  121. fibonacci: 'Fibonacci',
  122. pitchfork: 'Pitchfork',
  123. parallelChannel: 'Parallel channel',
  124. infinityLine: 'Infinity line',
  125. measure: 'Measure',
  126. measureXY: 'Measure XY',
  127. measureX: 'Measure X',
  128. measureY: 'Measure Y',
  129. // Flags:
  130. flags: 'Flags',
  131. // GUI elements:
  132. addButton: 'add',
  133. saveButton: 'save',
  134. editButton: 'edit',
  135. removeButton: 'remove',
  136. series: 'Series',
  137. volume: 'Volume',
  138. connector: 'Connector',
  139. // Field names:
  140. innerBackground: 'Inner background',
  141. outerBackground: 'Outer background',
  142. crosshairX: 'Crosshair X',
  143. crosshairY: 'Crosshair Y',
  144. tunnel: 'Tunnel',
  145. background: 'Background'
  146. }
  147. }
  148. },
  149. /**
  150. * Configure the stockTools gui strings in the chart. Requires the
  151. * [stockTools module]() to be loaded. For a description of the module
  152. * and information on its features, see [Highcharts StockTools]().
  153. *
  154. * @product highstock
  155. *
  156. * @sample stock/demo/stock-tools-gui Stock Tools GUI
  157. *
  158. * @sample stock/demo/stock-tools-custom-gui Stock Tools customized GUI
  159. *
  160. * @since 7.0.0
  161. * @type {Object}
  162. * @optionparent stockTools
  163. */
  164. stockTools: {
  165. /**
  166. * Definitions of buttons in Stock Tools GUI.
  167. */
  168. gui: {
  169. /**
  170. * Enable or disable the stockTools gui.
  171. *
  172. * @type {boolean}
  173. * @default true
  174. */
  175. enabled: true,
  176. /**
  177. * A CSS class name to apply to the stocktools' div,
  178. * allowing unique CSS styling for each chart.
  179. *
  180. * @type {string}
  181. * @default 'highcharts-bindings-wrapper'
  182. *
  183. */
  184. className: 'highcharts-bindings-wrapper',
  185. /**
  186. * A CSS class name to apply to the container of buttons,
  187. * allowing unique CSS styling for each chart.
  188. *
  189. * @type {string}
  190. * @default 'stocktools-toolbar'
  191. *
  192. */
  193. toolbarClassName: 'stocktools-toolbar',
  194. /**
  195. * Path where Highcharts will look for icons. Change this to use
  196. * icons from a different server.
  197. */
  198. iconsURL: 'https://code.highcharts.com/@product.version@/gfx/stock-icons/',
  199. /**
  200. * A collection of strings pointing to config options for the
  201. * toolbar items. Each name refers to unique key from definitions
  202. * object.
  203. *
  204. * @type {array}
  205. *
  206. * @default [
  207. * 'indicators',
  208. * 'separator',
  209. * 'simpleShapes',
  210. * 'lines',
  211. * 'crookedLines',
  212. * 'measure',
  213. * 'advanced',
  214. * 'toggleAnnotations',
  215. * 'separator',
  216. * 'verticalLabels',
  217. * 'flags',
  218. * 'separator',
  219. * 'zoomChange',
  220. * 'fullScreen',
  221. * 'typeChange',
  222. * 'separator',
  223. * 'currentPriceIndicator',
  224. * 'saveChart'
  225. * ]
  226. */
  227. buttons: [
  228. 'indicators',
  229. 'separator',
  230. 'simpleShapes',
  231. 'lines',
  232. 'crookedLines',
  233. 'measure',
  234. 'advanced',
  235. 'toggleAnnotations',
  236. 'separator',
  237. 'verticalLabels',
  238. 'flags',
  239. 'separator',
  240. 'zoomChange',
  241. 'fullScreen',
  242. 'typeChange',
  243. 'separator',
  244. 'currentPriceIndicator',
  245. 'saveChart'
  246. ],
  247. /**
  248. * An options object of the buttons definitions. Each name refers to
  249. * unique key from buttons array.
  250. *
  251. * @type {object}
  252. *
  253. */
  254. definitions: {
  255. separator: {
  256. /**
  257. * A predefined background symbol for the button.
  258. *
  259. * @type {string}
  260. */
  261. symbol: 'separator.svg'
  262. },
  263. simpleShapes: {
  264. /**
  265. * A collection of strings pointing to config options for
  266. * the items.
  267. *
  268. * @type {array}
  269. * @default [
  270. * 'label',
  271. * 'circle',
  272. * 'rectangle'
  273. * ]
  274. *
  275. */
  276. items: [
  277. 'label',
  278. 'circle',
  279. 'rectangle'
  280. ],
  281. circle: {
  282. /**
  283. * A predefined background symbol for the button.
  284. *
  285. * @type {string}
  286. *
  287. */
  288. symbol: 'circle.svg'
  289. },
  290. rectangle: {
  291. /**
  292. * A predefined background symbol for the button.
  293. *
  294. * @type {string}
  295. *
  296. */
  297. symbol: 'rectangle.svg'
  298. },
  299. label: {
  300. /**
  301. * A predefined background symbol for the button.
  302. *
  303. * @type {string}
  304. *
  305. */
  306. symbol: 'label.svg'
  307. }
  308. },
  309. flags: {
  310. /**
  311. * A collection of strings pointing to config options for
  312. * the items.
  313. *
  314. * @type {array}
  315. * @default [
  316. * 'flagCirclepin',
  317. * 'flagDiamondpin',
  318. * 'flagSquarepin',
  319. * 'flagSimplepin'
  320. * ]
  321. *
  322. */
  323. items: [
  324. 'flagCirclepin',
  325. 'flagDiamondpin',
  326. 'flagSquarepin',
  327. 'flagSimplepin'
  328. ],
  329. flagSimplepin: {
  330. /**
  331. * A predefined background symbol for the button.
  332. *
  333. * @type {string}
  334. *
  335. */
  336. symbol: 'flag-basic.svg'
  337. },
  338. flagDiamondpin: {
  339. /**
  340. * A predefined background symbol for the button.
  341. *
  342. * @type {string}
  343. *
  344. */
  345. symbol: 'flag-diamond.svg'
  346. },
  347. flagSquarepin: {
  348. /**
  349. * A predefined background symbol for the button.
  350. *
  351. * @type {string}
  352. */
  353. symbol: 'flag-trapeze.svg'
  354. },
  355. flagCirclepin: {
  356. /**
  357. * A predefined background symbol for the button.
  358. *
  359. * @type {string}
  360. */
  361. symbol: 'flag-elipse.svg'
  362. }
  363. },
  364. lines: {
  365. /**
  366. * A collection of strings pointing to config options for
  367. * the items.
  368. *
  369. * @type {array}
  370. * @default [
  371. * 'segment',
  372. * 'arrowSegment',
  373. * 'ray',
  374. * 'arrowRay',
  375. * 'line',
  376. * 'arrowLine',
  377. * 'horizontalLine',
  378. * 'verticalLine'
  379. * ]
  380. */
  381. items: [
  382. 'segment',
  383. 'arrowSegment',
  384. 'ray',
  385. 'arrowRay',
  386. 'line',
  387. 'arrowLine',
  388. 'horizontalLine',
  389. 'verticalLine'
  390. ],
  391. segment: {
  392. /**
  393. * A predefined background symbol for the button.
  394. *
  395. * @type {string}
  396. */
  397. symbol: 'segment.svg'
  398. },
  399. arrowSegment: {
  400. /**
  401. * A predefined background symbol for the button.
  402. *
  403. * @type {string}
  404. */
  405. symbol: 'arrow-segment.svg'
  406. },
  407. ray: {
  408. /**
  409. * A predefined background symbol for the button.
  410. *
  411. * @type {string}
  412. */
  413. symbol: 'ray.svg'
  414. },
  415. arrowRay: {
  416. /**
  417. * A predefined background symbol for the button.
  418. *
  419. * @type {string}
  420. */
  421. symbol: 'arrow-ray.svg'
  422. },
  423. line: {
  424. /**
  425. * A predefined background symbol for the button.
  426. *
  427. * @type {string}
  428. */
  429. symbol: 'line.svg'
  430. },
  431. arrowLine: {
  432. /**
  433. * A predefined background symbol for the button.
  434. *
  435. * @type {string}
  436. */
  437. symbol: 'arrow-line.svg'
  438. },
  439. verticalLine: {
  440. /**
  441. * A predefined background symbol for the button.
  442. *
  443. * @type {string}
  444. */
  445. symbol: 'vertical-line.svg'
  446. },
  447. horizontalLine: {
  448. /**
  449. * A predefined background symbol for the button.
  450. *
  451. * @type {string}
  452. */
  453. symbol: 'horizontal-line.svg'
  454. }
  455. },
  456. crookedLines: {
  457. /**
  458. * A collection of strings pointing to config options for
  459. * the items.
  460. *
  461. * @type {array}
  462. * @default [
  463. * 'elliott3',
  464. * 'elliott5',
  465. * 'crooked3',
  466. * 'crooked5'
  467. * ]
  468. *
  469. */
  470. items: [
  471. 'elliott3',
  472. 'elliott5',
  473. 'crooked3',
  474. 'crooked5'
  475. ],
  476. crooked3: {
  477. /**
  478. * A predefined background symbol for the button.
  479. *
  480. * @type {string}
  481. */
  482. symbol: 'crooked-3.svg'
  483. },
  484. crooked5: {
  485. /**
  486. * A predefined background symbol for the button.
  487. *
  488. * @type {string}
  489. */
  490. symbol: 'crooked-5.svg'
  491. },
  492. elliott3: {
  493. /**
  494. * A predefined background symbol for the button.
  495. *
  496. * @type {string}
  497. */
  498. symbol: 'elliott-3.svg'
  499. },
  500. elliott5: {
  501. /**
  502. * A predefined background symbol for the button.
  503. *
  504. * @type {string}
  505. */
  506. symbol: 'elliott-5.svg'
  507. }
  508. },
  509. verticalLabels: {
  510. /**
  511. * A collection of strings pointing to config options for
  512. * the items.
  513. *
  514. * @type {array}
  515. * @default [
  516. * 'verticalCounter',
  517. * 'verticalLabel',
  518. * 'verticalArrow'
  519. * ]
  520. */
  521. items: [
  522. 'verticalCounter',
  523. 'verticalLabel',
  524. 'verticalArrow'
  525. ],
  526. verticalCounter: {
  527. /**
  528. * A predefined background symbol for the button.
  529. *
  530. * @type {string}
  531. */
  532. symbol: 'vertical-counter.svg'
  533. },
  534. verticalLabel: {
  535. /**
  536. * A predefined background symbol for the button.
  537. *
  538. * @type {string}
  539. */
  540. symbol: 'vertical-label.svg'
  541. },
  542. verticalArrow: {
  543. /**
  544. * A predefined background symbol for the button.
  545. *
  546. * @type {string}
  547. */
  548. symbol: 'vertical-arrow.svg'
  549. }
  550. },
  551. advanced: {
  552. /**
  553. * A collection of strings pointing to config options for
  554. * the items.
  555. *
  556. * @type {array}
  557. * @default [
  558. * 'fibonacci',
  559. * 'pitchfork',
  560. * 'parallelChannel'
  561. * ]
  562. */
  563. items: [
  564. 'fibonacci',
  565. 'pitchfork',
  566. 'parallelChannel'
  567. ],
  568. pitchfork: {
  569. /**
  570. * A predefined background symbol for the button.
  571. *
  572. * @type {string}
  573. */
  574. symbol: 'pitchfork.svg'
  575. },
  576. fibonacci: {
  577. /**
  578. * A predefined background symbol for the button.
  579. *
  580. * @type {string}
  581. */
  582. symbol: 'fibonacci.svg'
  583. },
  584. parallelChannel: {
  585. /**
  586. * A predefined background symbol for the button.
  587. *
  588. * @type {string}
  589. */
  590. symbol: 'parallel-channel.svg'
  591. }
  592. },
  593. measure: {
  594. /**
  595. * A collection of strings pointing to config options for
  596. * the items.
  597. *
  598. * @type {array}
  599. * @default [
  600. * 'measureXY',
  601. * 'measureX',
  602. * 'measureY'
  603. * ]
  604. */
  605. items: [
  606. 'measureXY',
  607. 'measureX',
  608. 'measureY'
  609. ],
  610. measureX: {
  611. /**
  612. * A predefined background symbol for the button.
  613. *
  614. * @type {string}
  615. */
  616. symbol: 'measure-x.svg'
  617. },
  618. measureY: {
  619. /**
  620. * A predefined background symbol for the button.
  621. *
  622. * @type {string}
  623. */
  624. symbol: 'measure-y.svg'
  625. },
  626. measureXY: {
  627. /**
  628. * A predefined background symbol for the button.
  629. *
  630. * @type {string}
  631. */
  632. symbol: 'measure-xy.svg'
  633. }
  634. },
  635. toggleAnnotations: {
  636. /**
  637. * A predefined background symbol for the button.
  638. *
  639. * @type {string}
  640. */
  641. symbol: 'annotations-visible.svg'
  642. },
  643. currentPriceIndicator: {
  644. /**
  645. * A predefined background symbol for the button.
  646. *
  647. * @type {string}
  648. */
  649. symbol: 'current-price-show.svg'
  650. },
  651. indicators: {
  652. /**
  653. * A predefined background symbol for the button.
  654. *
  655. * @type {string}
  656. */
  657. symbol: 'indicators.svg'
  658. },
  659. zoomChange: {
  660. /**
  661. * A collection of strings pointing to config options for
  662. * the items.
  663. *
  664. * @type {array}
  665. * @default [
  666. * 'zoomX',
  667. * 'zoomY',
  668. * 'zoomXY'
  669. * ]
  670. */
  671. items: [
  672. 'zoomX',
  673. 'zoomY',
  674. 'zoomXY'
  675. ],
  676. zoomX: {
  677. /**
  678. * A predefined background symbol for the button.
  679. *
  680. * @type {string}
  681. */
  682. symbol: 'zoom-x.svg'
  683. },
  684. zoomY: {
  685. /**
  686. * A predefined background symbol for the button.
  687. *
  688. * @type {string}
  689. */
  690. symbol: 'zoom-y.svg'
  691. },
  692. zoomXY: {
  693. /**
  694. * A predefined background symbol for the button.
  695. *
  696. * @type {string}
  697. */
  698. symbol: 'zoom-xy.svg'
  699. }
  700. },
  701. typeChange: {
  702. /**
  703. * A collection of strings pointing to config options for
  704. * the items.
  705. *
  706. * @type {array}
  707. * @default [
  708. * 'typeOHLC',
  709. * 'typeLine',
  710. * 'typeCandlestick'
  711. * ]
  712. */
  713. items: [
  714. 'typeOHLC',
  715. 'typeLine',
  716. 'typeCandlestick'
  717. ],
  718. typeOHLC: {
  719. /**
  720. * A predefined background symbol for the button.
  721. *
  722. * @type {string}
  723. */
  724. symbol: 'series-ohlc.svg'
  725. },
  726. typeLine: {
  727. /**
  728. * A predefined background symbol for the button.
  729. *
  730. * @type {string}
  731. */
  732. symbol: 'series-line.svg'
  733. },
  734. typeCandlestick: {
  735. /**
  736. * A predefined background symbol for the button.
  737. *
  738. * @type {string}
  739. */
  740. symbol: 'series-candlestick.svg'
  741. }
  742. },
  743. fullScreen: {
  744. /**
  745. * A predefined background symbol for the button.
  746. *
  747. * @type {string}
  748. */
  749. symbol: 'fullscreen.svg'
  750. },
  751. saveChart: {
  752. /**
  753. * A predefined background symbol for the button.
  754. *
  755. * @type {string}
  756. */
  757. symbol: 'save-chart.svg'
  758. }
  759. }
  760. }
  761. }
  762. });
  763. // Run HTML generator
  764. addEvent(H.Chart, 'afterGetContainer', function () {
  765. this.setStockTools();
  766. });
  767. addEvent(H.Chart, 'getMargins', function () {
  768. var offsetWidth = (
  769. this.stockTools &&
  770. this.stockTools.listWrapper &&
  771. this.stockTools.listWrapper.offsetWidth
  772. );
  773. if (offsetWidth && offsetWidth < this.plotWidth) {
  774. this.plotLeft += offsetWidth;
  775. }
  776. });
  777. addEvent(H.Chart, 'destroy', function () {
  778. if (this.stockTools) {
  779. this.stockTools.destroy();
  780. }
  781. });
  782. addEvent(H.Chart, 'redraw', function () {
  783. if (this.stockTools && this.stockTools.guiEnabled) {
  784. this.stockTools.redraw();
  785. }
  786. });
  787. /*
  788. * Toolbar Class
  789. *
  790. * @param {Object} - options of toolbar
  791. * @param {Chart} - Reference to chart
  792. *
  793. */
  794. H.Toolbar = function (options, langOptions, chart) {
  795. this.chart = chart;
  796. this.options = options;
  797. this.lang = langOptions;
  798. this.guiEnabled = options.enabled;
  799. this.visible = pick(options.visible, true);
  800. this.placed = pick(options.placed, false);
  801. // General events collection which should be removed upon destroy/update:
  802. this.eventsToUnbind = [];
  803. if (this.guiEnabled) {
  804. this.createHTML();
  805. this.init();
  806. this.showHideNavigatorion();
  807. }
  808. fireEvent(this, 'afterInit');
  809. };
  810. H.extend(H.Chart.prototype, {
  811. /*
  812. * Verify if Toolbar should be added.
  813. *
  814. * @param {Object} - chart options
  815. *
  816. */
  817. setStockTools: function (options) {
  818. var chartOptions = this.options,
  819. lang = chartOptions.lang,
  820. guiOptions = merge(
  821. chartOptions.stockTools && chartOptions.stockTools.gui,
  822. options && options.gui
  823. ),
  824. langOptions = lang.stockTools && lang.stockTools.gui;
  825. this.stockTools = new H.Toolbar(guiOptions, langOptions, this);
  826. if (this.stockTools.guiEnabled) {
  827. this.isDirtyBox = true;
  828. }
  829. }
  830. });
  831. H.Toolbar.prototype = {
  832. /*
  833. * Initialize the toolbar. Create buttons and submenu for each option
  834. * defined in `stockTools.gui`.
  835. *
  836. */
  837. init: function () {
  838. var _self = this,
  839. lang = this.lang,
  840. guiOptions = this.options,
  841. toolbar = this.toolbar,
  842. addSubmenu = _self.addSubmenu,
  843. buttons = guiOptions.buttons,
  844. defs = guiOptions.definitions,
  845. allButtons = toolbar.childNodes,
  846. inIframe = this.inIframe(),
  847. button;
  848. // create buttons
  849. buttons.forEach(function (btnName) {
  850. button = _self.addButton(toolbar, defs, btnName, lang);
  851. if (inIframe && btnName === 'fullScreen') {
  852. button.buttonWrapper.className += ' ' + PREFIX + 'disabled-btn';
  853. }
  854. ['click', 'touchstart'].forEach(function (eventName) {
  855. addEvent(button.buttonWrapper, eventName, function () {
  856. _self.eraseActiveButtons(
  857. allButtons,
  858. button.buttonWrapper
  859. );
  860. });
  861. });
  862. if (isArray(defs[btnName].items)) {
  863. // create submenu buttons
  864. addSubmenu.call(_self, button, defs[btnName]);
  865. }
  866. });
  867. },
  868. /*
  869. * Create submenu (list of buttons) for the option. In example main button
  870. * is Line, in submenu will be buttons with types of lines.
  871. *
  872. * @param {Object} - button which has submenu
  873. * @param {Array} - list of all buttons
  874. *
  875. */
  876. addSubmenu: function (parentBtn, button) {
  877. var _self = this,
  878. submenuArrow = parentBtn.submenuArrow,
  879. buttonWrapper = parentBtn.buttonWrapper,
  880. buttonWidth = getStyle(buttonWrapper, 'width'),
  881. wrapper = this.wrapper,
  882. menuWrapper = this.listWrapper,
  883. allButtons = this.toolbar.childNodes,
  884. topMargin = 0,
  885. submenuWrapper;
  886. // create submenu container
  887. this.submenu = submenuWrapper = createElement(UL, {
  888. className: PREFIX + 'submenu-wrapper'
  889. }, null, buttonWrapper);
  890. // create submenu buttons and select the first one
  891. this.addSubmenuItems(buttonWrapper, button);
  892. // show / hide submenu
  893. ['click', 'touchstart'].forEach(function (eventName) {
  894. addEvent(submenuArrow, eventName, function (e) {
  895. e.stopPropagation();
  896. // Erase active class on all other buttons
  897. _self.eraseActiveButtons(allButtons, buttonWrapper);
  898. // hide menu
  899. if (buttonWrapper.className.indexOf(PREFIX + 'current') >= 0) {
  900. menuWrapper.style.width = menuWrapper.startWidth + 'px';
  901. buttonWrapper.classList.remove(PREFIX + 'current');
  902. submenuWrapper.style.display = 'none';
  903. } else {
  904. // show menu
  905. // to calculate height of element
  906. submenuWrapper.style.display = 'block';
  907. topMargin = submenuWrapper.offsetHeight -
  908. buttonWrapper.offsetHeight - 3;
  909. // calculate position of submenu in the box
  910. // if submenu is inside, reset top margin
  911. if (
  912. // cut on the bottom
  913. !(submenuWrapper.offsetHeight +
  914. buttonWrapper.offsetTop >
  915. wrapper.offsetHeight &&
  916. // cut on the top
  917. buttonWrapper.offsetTop > topMargin)
  918. ) {
  919. topMargin = 0;
  920. }
  921. // apply calculated styles
  922. css(submenuWrapper, {
  923. top: -topMargin + 'px',
  924. left: buttonWidth + 3 + 'px'
  925. });
  926. buttonWrapper.className += ' ' + PREFIX + 'current';
  927. menuWrapper.startWidth = wrapper.offsetWidth;
  928. menuWrapper.style.width = menuWrapper.startWidth +
  929. H.getStyle(menuWrapper, 'padding-left') +
  930. submenuWrapper.offsetWidth + 3 + 'px';
  931. }
  932. });
  933. });
  934. },
  935. /*
  936. * Create buttons in submenu
  937. *
  938. * @param {HTMLDOMElement} - button where submenu is placed
  939. * @param {Array} - list of all buttons options
  940. *
  941. */
  942. addSubmenuItems: function (buttonWrapper, button) {
  943. var _self = this,
  944. submenuWrapper = this.submenu,
  945. lang = this.lang,
  946. menuWrapper = this.listWrapper,
  947. items = button.items,
  948. firstSubmenuItem,
  949. submenuBtn;
  950. // add items to submenu
  951. items.forEach(function (btnName) {
  952. // add buttons to submenu
  953. submenuBtn = _self.addButton(
  954. submenuWrapper,
  955. button,
  956. btnName,
  957. lang
  958. );
  959. ['click', 'touchstart'].forEach(function (eventName) {
  960. addEvent(submenuBtn.mainButton, eventName, function () {
  961. _self.switchSymbol(this, buttonWrapper, true);
  962. menuWrapper.style.width = menuWrapper.startWidth + 'px';
  963. submenuWrapper.style.display = 'none';
  964. });
  965. });
  966. });
  967. // select first submenu item
  968. firstSubmenuItem = submenuWrapper
  969. .querySelectorAll('li > .' + PREFIX + 'menu-item-btn')[0];
  970. // replace current symbol, in main button, with submenu's button style
  971. _self.switchSymbol(firstSubmenuItem, false);
  972. },
  973. /*
  974. * Erase active class on all other buttons.
  975. *
  976. * @param {Array} - Array of HTML buttons
  977. * @param {HTMLDOMElement} - Current HTML button
  978. *
  979. */
  980. eraseActiveButtons: function (buttons, currentButton, submenuItems) {
  981. [].forEach.call(buttons, function (btn) {
  982. if (btn !== currentButton) {
  983. btn.classList.remove(PREFIX + 'current');
  984. btn.classList.remove(PREFIX + 'active');
  985. submenuItems =
  986. btn.querySelectorAll('.' + PREFIX + 'submenu-wrapper');
  987. // hide submenu
  988. if (submenuItems.length > 0) {
  989. submenuItems[0].style.display = 'none';
  990. }
  991. }
  992. });
  993. },
  994. /*
  995. * Create single button. Consist of `<li>` , `<span>` and (if exists)
  996. * submenu container.
  997. *
  998. * @param {HTMLDOMElement} - HTML reference, where button should be added
  999. * @param {Object} - all options, by btnName refer to particular button
  1000. * @param {String} - name of functionality mapped for specific class
  1001. * @param {Object} - All titles, by btnName refer to particular button
  1002. *
  1003. * @return {Object} - references to all created HTML elements
  1004. */
  1005. addButton: function (target, options, btnName, lang) {
  1006. var guiOptions = this.options,
  1007. btnOptions = options[btnName],
  1008. items = btnOptions.items,
  1009. classMapping = H.Toolbar.prototype.classMapping,
  1010. userClassName = btnOptions.className || '',
  1011. mainButton,
  1012. submenuArrow,
  1013. buttonWrapper;
  1014. // main button wrapper
  1015. buttonWrapper = createElement(LI, {
  1016. className: pick(classMapping[btnName], '') + ' ' + userClassName,
  1017. title: lang[btnName] || btnName
  1018. }, null, target);
  1019. // single button
  1020. mainButton = createElement(SPAN, {
  1021. className: PREFIX + 'menu-item-btn'
  1022. }, null, buttonWrapper);
  1023. // submenu
  1024. if (items && items.length > 1) {
  1025. // arrow is a hook to show / hide submenu
  1026. submenuArrow = createElement(SPAN, {
  1027. className: PREFIX + 'submenu-item-arrow ' +
  1028. PREFIX + 'arrow-right'
  1029. }, null, buttonWrapper);
  1030. } else {
  1031. mainButton.style['background-image'] = 'url(' +
  1032. guiOptions.iconsURL + btnOptions.symbol + ')';
  1033. }
  1034. return {
  1035. buttonWrapper: buttonWrapper,
  1036. mainButton: mainButton,
  1037. submenuArrow: submenuArrow
  1038. };
  1039. },
  1040. /*
  1041. * Create navigation's HTML elements: container and arrows.
  1042. *
  1043. */
  1044. addNavigation: function () {
  1045. var stockToolbar = this,
  1046. wrapper = stockToolbar.wrapper;
  1047. // arrow wrapper
  1048. stockToolbar.arrowWrapper = createElement(DIV, {
  1049. className: PREFIX + 'arrow-wrapper'
  1050. });
  1051. stockToolbar.arrowUp = createElement(DIV, {
  1052. className: PREFIX + 'arrow-up'
  1053. }, null, stockToolbar.arrowWrapper);
  1054. stockToolbar.arrowDown = createElement(DIV, {
  1055. className: PREFIX + 'arrow-down'
  1056. }, null, stockToolbar.arrowWrapper);
  1057. wrapper.insertBefore(
  1058. stockToolbar.arrowWrapper,
  1059. wrapper.childNodes[0]
  1060. );
  1061. // attach scroll events
  1062. stockToolbar.scrollButtons();
  1063. },
  1064. /*
  1065. * Add events to navigation (two arrows) which allows user to scroll
  1066. * top/down GUI buttons, if container's height is not enough.
  1067. *
  1068. */
  1069. scrollButtons: function () {
  1070. var targetY = 0,
  1071. _self = this,
  1072. wrapper = _self.wrapper,
  1073. toolbar = _self.toolbar,
  1074. step = 0.1 * wrapper.offsetHeight; // 0.1 = 10%
  1075. ['click', 'touchstart'].forEach(function (eventName) {
  1076. addEvent(_self.arrowUp, eventName, function () {
  1077. if (targetY > 0) {
  1078. targetY -= step;
  1079. toolbar.style['margin-top'] = -targetY + 'px';
  1080. }
  1081. });
  1082. addEvent(_self.arrowDown, eventName, function () {
  1083. if (
  1084. wrapper.offsetHeight + targetY <=
  1085. toolbar.offsetHeight + step
  1086. ) {
  1087. targetY += step;
  1088. toolbar.style['margin-top'] = -targetY + 'px';
  1089. }
  1090. });
  1091. });
  1092. },
  1093. /*
  1094. * Create stockTools HTML main elements.
  1095. *
  1096. */
  1097. createHTML: function () {
  1098. var stockToolbar = this,
  1099. chart = stockToolbar.chart,
  1100. guiOptions = stockToolbar.options,
  1101. container = chart.container,
  1102. listWrapper,
  1103. toolbar,
  1104. wrapper;
  1105. // create main container
  1106. stockToolbar.wrapper = wrapper = createElement(DIV, {
  1107. className: PREFIX + 'stocktools-wrapper ' +
  1108. guiOptions.className
  1109. });
  1110. container.parentNode.insertBefore(wrapper, container);
  1111. // toolbar
  1112. stockToolbar.toolbar = toolbar = createElement(UL, {
  1113. className: PREFIX + 'stocktools-toolbar ' +
  1114. guiOptions.toolbarClassName
  1115. });
  1116. // add container for list of buttons
  1117. stockToolbar.listWrapper = listWrapper = createElement(DIV, {
  1118. className: PREFIX + 'menu-wrapper'
  1119. });
  1120. wrapper.insertBefore(listWrapper, wrapper.childNodes[0]);
  1121. listWrapper.insertBefore(toolbar, listWrapper.childNodes[0]);
  1122. stockToolbar.showHideToolbar();
  1123. // add navigation which allows user to scroll down / top GUI buttons
  1124. stockToolbar.addNavigation();
  1125. },
  1126. /*
  1127. * Function called in redraw verifies if the navigation should be visible.
  1128. *
  1129. */
  1130. showHideNavigatorion: function () {
  1131. // arrows
  1132. // 50px space for arrows
  1133. if (
  1134. this.visible &&
  1135. this.toolbar.offsetHeight > (this.wrapper.offsetHeight - 50)
  1136. ) {
  1137. this.arrowWrapper.style.display = 'block';
  1138. } else {
  1139. // reset margin if whole toolbar is visible
  1140. this.toolbar.style.marginTop = '0px';
  1141. // hide arrows
  1142. this.arrowWrapper.style.display = 'none';
  1143. }
  1144. },
  1145. /*
  1146. * Create button which shows or hides GUI toolbar.
  1147. *
  1148. */
  1149. showHideToolbar: function () {
  1150. var stockToolbar = this,
  1151. chart = this.chart,
  1152. wrapper = stockToolbar.wrapper,
  1153. toolbar = this.listWrapper,
  1154. submenu = this.submenu,
  1155. visible = this.visible,
  1156. showhideBtn;
  1157. // Show hide toolbar
  1158. this.showhideBtn = showhideBtn = createElement(DIV, {
  1159. className: PREFIX + 'toggle-toolbar ' + PREFIX + 'arrow-left'
  1160. }, null, wrapper);
  1161. if (!visible) {
  1162. // hide
  1163. if (submenu) {
  1164. submenu.style.display = 'none';
  1165. }
  1166. showhideBtn.style.left = '0px';
  1167. stockToolbar.visible = visible = false;
  1168. toolbar.classList.add(PREFIX + 'hide');
  1169. showhideBtn.classList.toggle(PREFIX + 'arrow-right');
  1170. } else {
  1171. showhideBtn.style.top = H.getStyle(toolbar, 'padding-top') + 'px';
  1172. showhideBtn.style.left = (
  1173. wrapper.offsetWidth +
  1174. H.getStyle(toolbar, 'padding-left')
  1175. ) + 'px';
  1176. }
  1177. // toggle menu
  1178. ['click', 'touchstart'].forEach(function (eventName) {
  1179. addEvent(showhideBtn, eventName, function () {
  1180. chart.update({
  1181. stockTools: {
  1182. gui: {
  1183. visible: !visible,
  1184. placed: true
  1185. }
  1186. }
  1187. });
  1188. });
  1189. });
  1190. },
  1191. /*
  1192. * In main GUI button, replace icon and class with submenu button's
  1193. * class / symbol.
  1194. *
  1195. * @param {HTMLDOMElement} - submenu button
  1196. * @param {Boolean} - true or false
  1197. *
  1198. */
  1199. switchSymbol: function (button, redraw) {
  1200. var buttonWrapper = button.parentNode,
  1201. buttonWrapperClass = buttonWrapper.classList.value,
  1202. // main button in first level og GUI
  1203. mainNavButton = buttonWrapper.parentNode.parentNode;
  1204. // set class
  1205. mainNavButton.className = '';
  1206. if (buttonWrapperClass) {
  1207. mainNavButton.classList.add(buttonWrapperClass.trim());
  1208. }
  1209. // set icon
  1210. mainNavButton.querySelectorAll('.' + PREFIX + 'menu-item-btn')[0]
  1211. .style['background-image'] = button.style['background-image'];
  1212. // set active class
  1213. if (redraw) {
  1214. this.selectButton(mainNavButton);
  1215. }
  1216. },
  1217. /*
  1218. * Set select state (active class) on button.
  1219. *
  1220. * @param {HTMLDOMElement} - button
  1221. *
  1222. */
  1223. selectButton: function (btn) {
  1224. if (btn.className.indexOf(activeClass) >= 0) {
  1225. btn.classList.remove(activeClass);
  1226. } else {
  1227. btn.classList.add(activeClass);
  1228. }
  1229. },
  1230. /*
  1231. * Remove active class from all buttons except defined.
  1232. *
  1233. * @param {HTMLDOMElement} - button which should not be deactivated
  1234. *
  1235. */
  1236. unselectAllButtons: function (btn) {
  1237. var activeButtons = btn.parentNode.querySelectorAll('.' + activeClass);
  1238. [].forEach.call(activeButtons, function (activeBtn) {
  1239. if (activeBtn !== btn) {
  1240. activeBtn.classList.remove(activeClass);
  1241. }
  1242. });
  1243. },
  1244. /*
  1245. * Verify if chart is in iframe.
  1246. *
  1247. * @return {Object} - elements translations.
  1248. */
  1249. inIframe: function () {
  1250. try {
  1251. return win.self !== win.top;
  1252. } catch (e) {
  1253. return true;
  1254. }
  1255. },
  1256. /*
  1257. * Update GUI with given options.
  1258. *
  1259. * @param {Object} - general options for Stock Tools
  1260. */
  1261. update: function (options) {
  1262. merge(true, this.chart.options.stockTools, options);
  1263. this.destroy();
  1264. this.chart.setStockTools(options);
  1265. // If Stock Tools are updated, then bindings should be updated too:
  1266. if (this.chart.navigationBindings) {
  1267. this.chart.navigationBindings.update();
  1268. }
  1269. },
  1270. /*
  1271. * Destroy all HTML GUI elements.
  1272. *
  1273. */
  1274. destroy: function () {
  1275. var stockToolsDiv = this.wrapper,
  1276. parent = stockToolsDiv && stockToolsDiv.parentNode;
  1277. this.eventsToUnbind.forEach(function (unbinder) {
  1278. unbinder();
  1279. });
  1280. // Remove the empty element
  1281. if (parent) {
  1282. parent.removeChild(stockToolsDiv);
  1283. }
  1284. // redraw
  1285. this.chart.isDirtyBox = true;
  1286. this.chart.redraw();
  1287. },
  1288. /*
  1289. * Redraw, GUI requires to verify if the navigation should be visible.
  1290. *
  1291. */
  1292. redraw: function () {
  1293. this.showHideNavigatorion();
  1294. },
  1295. /*
  1296. * Mapping JSON fields to CSS classes.
  1297. *
  1298. */
  1299. classMapping: {
  1300. circle: PREFIX + 'circle-annotation',
  1301. rectangle: PREFIX + 'rectangle-annotation',
  1302. label: PREFIX + 'label-annotation',
  1303. segment: PREFIX + 'segment',
  1304. arrowSegment: PREFIX + 'arrow-segment',
  1305. ray: PREFIX + 'ray',
  1306. arrowRay: PREFIX + 'arrow-ray',
  1307. line: PREFIX + 'infinity-line',
  1308. arrowLine: PREFIX + 'arrow-infinity-line',
  1309. verticalLine: PREFIX + 'vertical-line',
  1310. horizontalLine: PREFIX + 'horizontal-line',
  1311. crooked3: PREFIX + 'crooked3',
  1312. crooked5: PREFIX + 'crooked5',
  1313. elliott3: PREFIX + 'elliott3',
  1314. elliott5: PREFIX + 'elliott5',
  1315. pitchfork: PREFIX + 'pitchfork',
  1316. fibonacci: PREFIX + 'fibonacci',
  1317. parallelChannel: PREFIX + 'parallel-channel',
  1318. measureX: PREFIX + 'measure-x',
  1319. measureY: PREFIX + 'measure-y',
  1320. measureXY: PREFIX + 'measure-xy',
  1321. verticalCounter: PREFIX + 'vertical-counter',
  1322. verticalLabel: PREFIX + 'vertical-label',
  1323. verticalArrow: PREFIX + 'vertical-arrow',
  1324. currentPriceIndicator: PREFIX + 'current-price-indicator',
  1325. indicators: PREFIX + 'indicators',
  1326. flagCirclepin: PREFIX + 'flag-circlepin',
  1327. flagDiamondpin: PREFIX + 'flag-diamondpin',
  1328. flagSquarepin: PREFIX + 'flag-squarepin',
  1329. flagSimplepin: PREFIX + 'flag-simplepin',
  1330. zoomX: PREFIX + 'zoom-x',
  1331. zoomY: PREFIX + 'zoom-y',
  1332. zoomXY: PREFIX + 'zoom-xy',
  1333. typeLine: PREFIX + 'series-type-line',
  1334. typeOHLC: PREFIX + 'series-type-ohlc',
  1335. typeCandlestick: PREFIX + 'series-type-candlestick',
  1336. fullScreen: PREFIX + 'full-screen',
  1337. toggleAnnotations: PREFIX + 'toggle-annotations',
  1338. saveChart: PREFIX + 'save-chart',
  1339. separator: PREFIX + 'separator'
  1340. }
  1341. };
  1342. // Comunication with bindings:
  1343. addEvent(H.NavigationBindings, 'selectButton', function (event) {
  1344. var button = event.button,
  1345. className = PREFIX + 'submenu-wrapper',
  1346. gui = this.chart.stockTools;
  1347. if (gui && gui.guiEnabled) {
  1348. // Unslect other active buttons
  1349. gui.unselectAllButtons(event.button);
  1350. // If clicked on a submenu, select state for it's parent
  1351. if (button.parentNode.className.indexOf(className) >= 0) {
  1352. button = button.parentNode.parentNode;
  1353. }
  1354. // Set active class on the current button
  1355. gui.selectButton(button);
  1356. }
  1357. });
  1358. addEvent(H.NavigationBindings, 'deselectButton', function (event) {
  1359. var button = event.button,
  1360. className = PREFIX + 'submenu-wrapper',
  1361. gui = this.chart.stockTools;
  1362. if (gui && gui.guiEnabled) {
  1363. // If deselecting a button from a submenu, select state for it's parent
  1364. if (button.parentNode.className.indexOf(className) >= 0) {
  1365. button = button.parentNode.parentNode;
  1366. }
  1367. gui.selectButton(button);
  1368. }
  1369. });