FlagsSeries.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  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 './Series.js';
  10. import './SvgRenderer.js';
  11. import onSeriesMixin from '../mixins/on-series.js';
  12. var addEvent = H.addEvent,
  13. merge = H.merge,
  14. noop = H.noop,
  15. defined = H.defined,
  16. Renderer = H.Renderer,
  17. Series = H.Series,
  18. seriesType = H.seriesType,
  19. SVGRenderer = H.SVGRenderer,
  20. TrackerMixin = H.TrackerMixin,
  21. VMLRenderer = H.VMLRenderer,
  22. symbols = SVGRenderer.prototype.symbols;
  23. /**
  24. * The Flags series.
  25. *
  26. * @private
  27. * @class
  28. * @name Highcharts.seriesTypes.flags
  29. *
  30. * @augments Highcharts.Series
  31. */
  32. seriesType(
  33. 'flags',
  34. 'column'
  35. /**
  36. * Flags are used to mark events in stock charts. They can be added on the
  37. * timeline, or attached to a specific series.
  38. *
  39. * @sample stock/demo/flags-general/
  40. * Flags on a line series
  41. *
  42. * @extends plotOptions.column
  43. * @excluding animation, borderColor, borderRadius, borderWidth,
  44. * colorByPoint, dataGrouping, pointPadding, pointWidth,
  45. * turboThreshold
  46. * @product highstock
  47. * @optionparent plotOptions.flags
  48. */
  49. , {
  50. /**
  51. * In case the flag is placed on a series, on what point key to place
  52. * it. Line and columns have one key, `y`. In range or OHLC-type series,
  53. * however, the flag can optionally be placed on the `open`, `high`,
  54. * `low` or `close` key.
  55. *
  56. * @sample {highstock} stock/plotoptions/flags-onkey/
  57. * Range series, flag on high
  58. *
  59. * @type {string}
  60. * @default y
  61. * @since 4.2.2
  62. * @product highstock
  63. * @validvalue ["y", "open", "high", "low", "close"]
  64. * @apioption plotOptions.flags.onKey
  65. */
  66. /**
  67. * The id of the series that the flags should be drawn on. If no id
  68. * is given, the flags are drawn on the x axis.
  69. *
  70. * @sample {highstock} stock/plotoptions/flags/
  71. * Flags on series and on x axis
  72. *
  73. * @type {string}
  74. * @product highstock
  75. * @apioption plotOptions.flags.onSeries
  76. */
  77. pointRange: 0, // #673
  78. /**
  79. * Whether the flags are allowed to overlap sideways. If `false`, the
  80. * flags are moved sideways using an algorithm that seeks to place every
  81. * flag as close as possible to its original position.
  82. *
  83. * @sample {highstock} stock/plotoptions/flags-allowoverlapx
  84. * Allow sideways overlap
  85. *
  86. * @since 6.0.4
  87. */
  88. allowOverlapX: false,
  89. /**
  90. * The shape of the marker. Can be one of "flag", "circlepin",
  91. * "squarepin", or an image of the format `url(/path-to-image.jpg)`.
  92. * Individual shapes can also be set for each point.
  93. *
  94. * @sample {highstock} stock/plotoptions/flags/
  95. * Different shapes
  96. *
  97. * @product highstock
  98. * @validvalue ["flag", "circlepin", "squarepin"]
  99. */
  100. shape: 'flag',
  101. /**
  102. * When multiple flags in the same series fall on the same value, this
  103. * number determines the vertical offset between them.
  104. *
  105. * @sample {highstock} stock/plotoptions/flags-stackdistance/
  106. * A greater stack distance
  107. *
  108. * @product highstock
  109. */
  110. stackDistance: 12,
  111. /**
  112. * Text alignment for the text inside the flag.
  113. *
  114. * @since 5.0.0
  115. * @product highstock
  116. * @validvalue ["left", "center", "right"]
  117. */
  118. textAlign: 'center',
  119. /**
  120. * Specific tooltip options for flag series. Flag series tooltips are
  121. * different from most other types in that a flag doesn't have a data
  122. * value, so the tooltip rather displays the `text` option for each
  123. * point.
  124. *
  125. * @extends plotOptions.series.tooltip
  126. * @excluding changeDecimals, valueDecimals, valuePrefix, valueSuffix
  127. * @product highstock
  128. */
  129. tooltip: {
  130. pointFormat: '{point.text}<br/>'
  131. },
  132. threshold: null,
  133. /**
  134. * The text to display on each flag. This can be defined on series
  135. * level, or individually for each point. Defaults to `"A"`.
  136. *
  137. * @type {string}
  138. * @default A
  139. * @product highstock
  140. * @apioption plotOptions.flags.title
  141. */
  142. /**
  143. * The y position of the top left corner of the flag relative to either
  144. * the series (if onSeries is defined), or the x axis. Defaults to
  145. * `-30`.
  146. *
  147. * @product highstock
  148. */
  149. y: -30,
  150. /**
  151. * Whether to use HTML to render the flag texts. Using HTML allows for
  152. * advanced formatting, images and reliable bi-directional text
  153. * rendering. Note that exported images won't respect the HTML, and that
  154. * HTML won't respect Z-index settings.
  155. *
  156. * @type {boolean}
  157. * @default false
  158. * @since 1.3
  159. * @product highstock
  160. * @apioption plotOptions.flags.useHTML
  161. */
  162. /**
  163. * Fixed width of the flag's shape. By default, width is autocalculated
  164. * according to the flag's title.
  165. *
  166. * @sample {highstock} stock/demo/flags-shapes/
  167. * Flags with fixed width
  168. *
  169. * @type {number}
  170. * @product highstock
  171. * @apioption plotOptions.flags.width
  172. */
  173. /**
  174. * Fixed height of the flag's shape. By default, height is
  175. * autocalculated according to the flag's title.
  176. *
  177. * @type {number}
  178. * @product highstock
  179. * @apioption plotOptions.flags.height
  180. */
  181. /**
  182. * The fill color for the flags.
  183. *
  184. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  185. * @product highstock
  186. */
  187. fillColor: '#ffffff',
  188. /**
  189. * The color of the line/border of the flag.
  190. *
  191. * In styled mode, the stroke is set in the
  192. * `.highcharts-flag-series.highcharts-point` rule.
  193. *
  194. * @type {Highcharts.ColorString}
  195. * @default #000000
  196. * @product highstock
  197. * @apioption plotOptions.flags.lineColor
  198. */
  199. /**
  200. * The pixel width of the flag's line/border.
  201. *
  202. * @product highstock
  203. */
  204. lineWidth: 1,
  205. states: {
  206. /**
  207. * @extends plotOptions.column.states.hover
  208. * @product highstock
  209. */
  210. hover: {
  211. /**
  212. * The color of the line/border of the flag.
  213. *
  214. * @type {Highcharts.ColorString}
  215. * @product highstock
  216. */
  217. lineColor: '#000000',
  218. /**
  219. * The fill or background color of the flag.
  220. *
  221. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  222. * @product highstock
  223. */
  224. fillColor: '#ccd6eb'
  225. }
  226. },
  227. /**
  228. * The text styles of the flag.
  229. *
  230. * In styled mode, the styles are set in the
  231. * `.highcharts-flag-series .highcharts-point` rule.
  232. *
  233. * @type {Highcharts.CSSObject}
  234. * @default {"fontSize": "11px", "fontWeight": "bold"}
  235. * @product highstock
  236. */
  237. style: {
  238. /** @ignore-option */
  239. fontSize: '11px',
  240. /** @ignore-option */
  241. fontWeight: 'bold'
  242. }
  243. }, /** @lends seriesTypes.flags.prototype */ {
  244. sorted: false,
  245. noSharedTooltip: true,
  246. allowDG: false,
  247. takeOrdinalPosition: false, // #1074
  248. trackerGroups: ['markerGroup'],
  249. forceCrop: true,
  250. /**
  251. * Inherit the initialization from base Series.
  252. *
  253. * @private
  254. * @borrows Highcharts.Series#init as Highcharts.seriesTypes.flags#init
  255. */
  256. init: Series.prototype.init,
  257. /**
  258. * Get presentational attributes
  259. *
  260. * @private
  261. * @function Highcharts.seriesTypes.flags#pointAttribs
  262. *
  263. * @param {Highcharts.Point} point
  264. *
  265. * @param {string} [state]
  266. *
  267. * @return {Highcharts.SVGAttributes}
  268. */
  269. pointAttribs: function (point, state) {
  270. var options = this.options,
  271. color = (point && point.color) || this.color,
  272. lineColor = options.lineColor,
  273. lineWidth = (point && point.lineWidth),
  274. fill = (point && point.fillColor) || options.fillColor;
  275. if (state) {
  276. fill = options.states[state].fillColor;
  277. lineColor = options.states[state].lineColor;
  278. lineWidth = options.states[state].lineWidth;
  279. }
  280. return {
  281. 'fill': fill || color,
  282. 'stroke': lineColor || color,
  283. 'stroke-width': lineWidth || options.lineWidth || 0
  284. };
  285. },
  286. translate: onSeriesMixin.translate,
  287. getPlotBox: onSeriesMixin.getPlotBox,
  288. /**
  289. * Draw the markers.
  290. *
  291. * @private
  292. * @function Highcharts.seriesTypes.flags#drawPoints
  293. */
  294. drawPoints: function () {
  295. var series = this,
  296. points = series.points,
  297. chart = series.chart,
  298. renderer = chart.renderer,
  299. plotX,
  300. plotY,
  301. inverted = chart.inverted,
  302. options = series.options,
  303. optionsY = options.y,
  304. shape,
  305. i,
  306. point,
  307. graphic,
  308. stackIndex,
  309. anchorY,
  310. attribs,
  311. outsideRight,
  312. yAxis = series.yAxis,
  313. boxesMap = {},
  314. boxes = [];
  315. i = points.length;
  316. while (i--) {
  317. point = points[i];
  318. outsideRight =
  319. (inverted ? point.plotY : point.plotX) > series.xAxis.len;
  320. plotX = point.plotX;
  321. stackIndex = point.stackIndex;
  322. shape = point.options.shape || options.shape;
  323. plotY = point.plotY;
  324. if (plotY !== undefined) {
  325. plotY = point.plotY + optionsY -
  326. (
  327. stackIndex !== undefined &&
  328. stackIndex * options.stackDistance
  329. );
  330. }
  331. // skip connectors for higher level stacked points
  332. point.anchorX = stackIndex ? undefined : point.plotX;
  333. anchorY = stackIndex ? undefined : point.plotY;
  334. graphic = point.graphic;
  335. // Only draw the point if y is defined and the flag is within
  336. // the visible area
  337. if (plotY !== undefined && plotX >= 0 && !outsideRight) {
  338. // Create the flag
  339. if (!graphic) {
  340. graphic = point.graphic = renderer.label(
  341. '',
  342. null,
  343. null,
  344. shape,
  345. null,
  346. null,
  347. options.useHTML
  348. );
  349. if (!chart.styledMode) {
  350. graphic
  351. .attr(series.pointAttribs(point))
  352. .css(merge(options.style, point.style));
  353. }
  354. graphic.attr({
  355. align: shape === 'flag' ? 'left' : 'center',
  356. width: options.width,
  357. height: options.height,
  358. 'text-align': options.textAlign
  359. })
  360. .addClass('highcharts-point')
  361. .add(series.markerGroup);
  362. // Add reference to the point for tracker (#6303)
  363. if (point.graphic.div) {
  364. point.graphic.div.point = point;
  365. }
  366. if (!chart.styledMode) {
  367. graphic.shadow(options.shadow);
  368. }
  369. graphic.isNew = true;
  370. }
  371. if (plotX > 0) { // #3119
  372. plotX -= graphic.strokeWidth() % 2; // #4285
  373. }
  374. // Plant the flag
  375. attribs = {
  376. y: plotY,
  377. anchorY: anchorY
  378. };
  379. if (options.allowOverlapX) {
  380. attribs.x = plotX;
  381. attribs.anchorX = point.anchorX;
  382. }
  383. graphic.attr({
  384. text: point.options.title || options.title || 'A'
  385. })[graphic.isNew ? 'attr' : 'animate'](attribs);
  386. // Rig for the distribute function
  387. if (!options.allowOverlapX) {
  388. if (!boxesMap[point.plotX]) {
  389. boxesMap[point.plotX] = {
  390. align: 0,
  391. size: graphic.width,
  392. target: plotX,
  393. anchorX: plotX
  394. };
  395. } else {
  396. boxesMap[point.plotX].size = Math.max(
  397. boxesMap[point.plotX].size,
  398. graphic.width
  399. );
  400. }
  401. }
  402. // Set the tooltip anchor position
  403. point.tooltipPos = [
  404. plotX,
  405. plotY + yAxis.pos - chart.plotTop
  406. ]; // #6327
  407. } else if (graphic) {
  408. point.graphic = graphic.destroy();
  409. }
  410. }
  411. // Handle X-dimension overlapping
  412. if (!options.allowOverlapX) {
  413. H.objectEach(boxesMap, function (box) {
  414. box.plotX = box.anchorX;
  415. boxes.push(box);
  416. });
  417. H.distribute(boxes, inverted ? yAxis.len : this.xAxis.len, 100);
  418. points.forEach(function (point) {
  419. var box = point.graphic && boxesMap[point.plotX];
  420. if (box) {
  421. point.graphic[
  422. point.graphic.isNew ? 'attr' : 'animate'
  423. ]({
  424. x: box.pos,
  425. anchorX: point.anchorX
  426. });
  427. // Hide flag when its box position is not specified
  428. // (#8573, #9299)
  429. if (!defined(box.pos)) {
  430. point.graphic.attr({
  431. x: -9999,
  432. anchorX: -9999
  433. });
  434. point.graphic.isNew = true;
  435. } else {
  436. point.graphic.isNew = false;
  437. }
  438. }
  439. });
  440. }
  441. // Can be a mix of SVG and HTML and we need events for both (#6303)
  442. if (options.useHTML) {
  443. H.wrap(series.markerGroup, 'on', function (proceed) {
  444. return H.SVGElement.prototype.on.apply(
  445. // for HTML
  446. proceed.apply(this, [].slice.call(arguments, 1)),
  447. // and for SVG
  448. [].slice.call(arguments, 1)
  449. );
  450. });
  451. }
  452. },
  453. /**
  454. * Extend the column trackers with listeners to expand and contract
  455. * stacks.
  456. *
  457. * @private
  458. * @function Highcharts.seriesTypes.flags#drawTracker
  459. */
  460. drawTracker: function () {
  461. var series = this,
  462. points = series.points;
  463. TrackerMixin.drawTrackerPoint.apply(this);
  464. /* *
  465. * Bring each stacked flag up on mouse over, this allows readability
  466. * of vertically stacked elements as well as tight points on the x
  467. * axis. #1924.
  468. */
  469. points.forEach(function (point) {
  470. var graphic = point.graphic;
  471. if (graphic) {
  472. addEvent(graphic.element, 'mouseover', function () {
  473. // Raise this point
  474. if (point.stackIndex > 0 && !point.raised) {
  475. point._y = graphic.y;
  476. graphic.attr({
  477. y: point._y - 8
  478. });
  479. point.raised = true;
  480. }
  481. // Revert other raised points
  482. points.forEach(function (otherPoint) {
  483. if (
  484. otherPoint !== point &&
  485. otherPoint.raised &&
  486. otherPoint.graphic
  487. ) {
  488. otherPoint.graphic.attr({
  489. y: otherPoint._y
  490. });
  491. otherPoint.raised = false;
  492. }
  493. });
  494. });
  495. }
  496. });
  497. },
  498. /**
  499. * Disable animation, but keep clipping (#8546).
  500. *
  501. * @private
  502. * @function Highcharts.seriesTypes.flags#animate
  503. *
  504. * @param {boolean} [init]
  505. */
  506. animate: function (init) {
  507. if (init) {
  508. this.setClip();
  509. } else {
  510. this.animate = null;
  511. }
  512. },
  513. /**
  514. * @private
  515. * @function Highcharts.seriesTypes.flags#setClip
  516. */
  517. setClip: function () {
  518. Series.prototype.setClip.apply(this, arguments);
  519. if (this.options.clip !== false && this.sharedClipKey) {
  520. this.markerGroup.clip(this.chart[this.sharedClipKey]);
  521. }
  522. },
  523. /**
  524. * @private
  525. * @function Highcharts.seriesTypes.flags#buildKDTree
  526. */
  527. buildKDTree: noop,
  528. /**
  529. * Don't invert the flag marker group (#4960).
  530. *
  531. * @private
  532. * @function Highcharts.seriesTypes.flags#invertGroups
  533. */
  534. invertGroups: noop
  535. }
  536. );
  537. // create the flag icon with anchor
  538. symbols.flag = function (x, y, w, h, options) {
  539. var anchorX = (options && options.anchorX) || x,
  540. anchorY = (options && options.anchorY) || y;
  541. return symbols.circle(anchorX - 1, anchorY - 1, 2, 2).concat(
  542. [
  543. 'M', anchorX, anchorY,
  544. 'L', x, y + h,
  545. x, y,
  546. x + w, y,
  547. x + w, y + h,
  548. x, y + h,
  549. 'Z'
  550. ]
  551. );
  552. };
  553. // Create the circlepin and squarepin icons with anchor
  554. function createPinSymbol(shape) {
  555. symbols[shape + 'pin'] = function (x, y, w, h, options) {
  556. var anchorX = options && options.anchorX,
  557. anchorY = options && options.anchorY,
  558. path,
  559. labelTopOrBottomY;
  560. // For single-letter flags, make sure circular flags are not taller
  561. // than their width
  562. if (shape === 'circle' && h > w) {
  563. x -= Math.round((h - w) / 2);
  564. w = h;
  565. }
  566. path = symbols[shape](x, y, w, h);
  567. if (anchorX && anchorY) {
  568. /**
  569. * If the label is below the anchor, draw the connecting line
  570. * from the top edge of the label
  571. * otherwise start drawing from the bottom edge
  572. */
  573. labelTopOrBottomY = (y > anchorY) ? y : y + h;
  574. path.push(
  575. 'M',
  576. shape === 'circle' ? path[1] - path[4] : path[1] + path[4] / 2,
  577. labelTopOrBottomY,
  578. 'L',
  579. anchorX,
  580. anchorY
  581. );
  582. path = path.concat(
  583. symbols.circle(anchorX - 1, anchorY - 1, 2, 2)
  584. );
  585. }
  586. return path;
  587. };
  588. }
  589. createPinSymbol('circle');
  590. createPinSymbol('square');
  591. /**
  592. * The symbol callbacks are generated on the SVGRenderer object in all browsers.
  593. * Even VML browsers need this in order to generate shapes in export. Now share
  594. * them with the VMLRenderer.
  595. */
  596. if (Renderer === VMLRenderer) {
  597. ['flag', 'circlepin', 'squarepin'].forEach(function (shape) {
  598. VMLRenderer.prototype.symbols[shape] = symbols[shape];
  599. });
  600. }
  601. /**
  602. * A `flags` series. If the [type](#series.flags.type) option is not
  603. * specified, it is inherited from [chart.type](#chart.type).
  604. *
  605. * @extends series,plotOptions.flags
  606. * @excluding dataParser, dataURL
  607. * @product highstock
  608. * @apioption series.flags
  609. */
  610. /**
  611. * An array of data points for the series. For the `flags` series type,
  612. * points can be given in the following ways:
  613. *
  614. * 1. An array of objects with named values. The following snippet shows only a
  615. * few settings, see the complete options set below. If the total number of
  616. * data points exceeds the series'
  617. * [turboThreshold](#series.flags.turboThreshold), this option is not
  618. * available.
  619. * ```js
  620. * data: [{
  621. * x: 1,
  622. * title: "A",
  623. * text: "First event"
  624. * }, {
  625. * x: 1,
  626. * title: "B",
  627. * text: "Second event"
  628. * }]
  629. * ```
  630. *
  631. * @type {Array<*>}
  632. * @extends series.line.data
  633. * @excluding dataLabels, marker, name, y
  634. * @product highstock
  635. * @apioption series.flags.data
  636. */
  637. /**
  638. * The fill color of an individual flag. By default it inherits from
  639. * the series color.
  640. *
  641. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  642. * @product highstock
  643. * @apioption series.flags.data.fillColor
  644. */
  645. /**
  646. * The longer text to be shown in the flag's tooltip.
  647. *
  648. * @type {string}
  649. * @product highstock
  650. * @apioption series.flags.data.text
  651. */
  652. /**
  653. * The short text to be shown on the flag.
  654. *
  655. * @type {string}
  656. * @product highstock
  657. * @apioption series.flags.data.title
  658. */