Legend.js 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254
  1. /* *
  2. *
  3. * (c) 2010-2021 Torstein Honsi
  4. *
  5. * License: www.highcharts.com/license
  6. *
  7. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  8. *
  9. * */
  10. 'use strict';
  11. import A from './Animation/AnimationUtilities.js';
  12. var animObject = A.animObject, setAnimation = A.setAnimation;
  13. import F from './FormatUtilities.js';
  14. var format = F.format;
  15. import H from './Globals.js';
  16. var isFirefox = H.isFirefox, marginNames = H.marginNames, win = H.win;
  17. import Point from './Series/Point.js';
  18. import U from './Utilities.js';
  19. var addEvent = U.addEvent, createElement = U.createElement, css = U.css, defined = U.defined, discardElement = U.discardElement, find = U.find, fireEvent = U.fireEvent, isNumber = U.isNumber, merge = U.merge, pick = U.pick, relativeLength = U.relativeLength, stableSort = U.stableSort, syncTimeout = U.syncTimeout, wrap = U.wrap;
  20. /**
  21. * Gets fired when the legend item belonging to a point is clicked. The default
  22. * action is to toggle the visibility of the point. This can be prevented by
  23. * returning `false` or calling `event.preventDefault()`.
  24. *
  25. * @callback Highcharts.PointLegendItemClickCallbackFunction
  26. *
  27. * @param {Highcharts.Point} this
  28. * The point on which the event occured.
  29. *
  30. * @param {Highcharts.PointLegendItemClickEventObject} event
  31. * The event that occured.
  32. */
  33. /**
  34. * Information about the legend click event.
  35. *
  36. * @interface Highcharts.PointLegendItemClickEventObject
  37. */ /**
  38. * Related browser event.
  39. * @name Highcharts.PointLegendItemClickEventObject#browserEvent
  40. * @type {Highcharts.PointerEvent}
  41. */ /**
  42. * Prevent the default action of toggle the visibility of the point.
  43. * @name Highcharts.PointLegendItemClickEventObject#preventDefault
  44. * @type {Function}
  45. */ /**
  46. * Related point.
  47. * @name Highcharts.PointLegendItemClickEventObject#target
  48. * @type {Highcharts.Point}
  49. */ /**
  50. * Event type.
  51. * @name Highcharts.PointLegendItemClickEventObject#type
  52. * @type {"legendItemClick"}
  53. */
  54. /**
  55. * Gets fired when the legend item belonging to a series is clicked. The default
  56. * action is to toggle the visibility of the series. This can be prevented by
  57. * returning `false` or calling `event.preventDefault()`.
  58. *
  59. * @callback Highcharts.SeriesLegendItemClickCallbackFunction
  60. *
  61. * @param {Highcharts.Series} this
  62. * The series where the event occured.
  63. *
  64. * @param {Highcharts.SeriesLegendItemClickEventObject} event
  65. * The event that occured.
  66. */
  67. /**
  68. * Information about the legend click event.
  69. *
  70. * @interface Highcharts.SeriesLegendItemClickEventObject
  71. */ /**
  72. * Related browser event.
  73. * @name Highcharts.SeriesLegendItemClickEventObject#browserEvent
  74. * @type {Highcharts.PointerEvent}
  75. */ /**
  76. * Prevent the default action of toggle the visibility of the series.
  77. * @name Highcharts.SeriesLegendItemClickEventObject#preventDefault
  78. * @type {Function}
  79. */ /**
  80. * Related series.
  81. * @name Highcharts.SeriesLegendItemClickEventObject#target
  82. * @type {Highcharts.Series}
  83. */ /**
  84. * Event type.
  85. * @name Highcharts.SeriesLegendItemClickEventObject#type
  86. * @type {"legendItemClick"}
  87. */
  88. /* eslint-disable no-invalid-this, valid-jsdoc */
  89. /**
  90. * The overview of the chart's series. The legend object is instanciated
  91. * internally in the chart constructor, and is available from the `chart.legend`
  92. * property. Each chart has only one legend.
  93. *
  94. * @class
  95. * @name Highcharts.Legend
  96. *
  97. * @param {Highcharts.Chart} chart
  98. * The chart instance.
  99. *
  100. * @param {Highcharts.LegendOptions} options
  101. * Legend options.
  102. */
  103. var Legend = /** @class */ (function () {
  104. /* *
  105. *
  106. * Constructors
  107. *
  108. * */
  109. function Legend(chart, options) {
  110. /* *
  111. *
  112. * Properties
  113. *
  114. * */
  115. this.allItems = [];
  116. this.box = void 0;
  117. this.contentGroup = void 0;
  118. this.display = false;
  119. this.group = void 0;
  120. this.initialItemY = 0;
  121. this.itemHeight = 0;
  122. this.itemMarginBottom = 0;
  123. this.itemMarginTop = 0;
  124. this.itemX = 0;
  125. this.itemY = 0;
  126. this.lastItemY = 0;
  127. this.lastLineHeight = 0;
  128. this.legendHeight = 0;
  129. this.legendWidth = 0;
  130. this.maxItemWidth = 0;
  131. this.maxLegendWidth = 0;
  132. this.offsetWidth = 0;
  133. this.options = {};
  134. this.padding = 0;
  135. this.pages = [];
  136. this.proximate = false;
  137. this.scrollGroup = void 0;
  138. this.symbolHeight = 0;
  139. this.symbolWidth = 0;
  140. this.titleHeight = 0;
  141. this.totalItemWidth = 0;
  142. this.widthOption = 0;
  143. this.chart = chart;
  144. this.init(chart, options);
  145. }
  146. /* *
  147. *
  148. * Functions
  149. *
  150. * */
  151. /**
  152. * Initialize the legend.
  153. *
  154. * @private
  155. * @function Highcharts.Legend#init
  156. *
  157. * @param {Highcharts.Chart} chart
  158. * The chart instance.
  159. *
  160. * @param {Highcharts.LegendOptions} options
  161. * Legend options.
  162. */
  163. Legend.prototype.init = function (chart, options) {
  164. /**
  165. * Chart of this legend.
  166. *
  167. * @readonly
  168. * @name Highcharts.Legend#chart
  169. * @type {Highcharts.Chart}
  170. */
  171. this.chart = chart;
  172. this.setOptions(options);
  173. if (options.enabled) {
  174. // Render it
  175. this.render();
  176. // move checkboxes
  177. addEvent(this.chart, 'endResize', function () {
  178. this.legend.positionCheckboxes();
  179. });
  180. if (this.proximate) {
  181. this.unchartrender = addEvent(this.chart, 'render', function () {
  182. this.legend.proximatePositions();
  183. this.legend.positionItems();
  184. });
  185. }
  186. else if (this.unchartrender) {
  187. this.unchartrender();
  188. }
  189. }
  190. };
  191. /**
  192. * @private
  193. * @function Highcharts.Legend#setOptions
  194. * @param {Highcharts.LegendOptions} options
  195. */
  196. Legend.prototype.setOptions = function (options) {
  197. var padding = pick(options.padding, 8);
  198. /**
  199. * Legend options.
  200. *
  201. * @readonly
  202. * @name Highcharts.Legend#options
  203. * @type {Highcharts.LegendOptions}
  204. */
  205. this.options = options;
  206. if (!this.chart.styledMode) {
  207. this.itemStyle = options.itemStyle;
  208. this.itemHiddenStyle = merge(this.itemStyle, options.itemHiddenStyle);
  209. }
  210. this.itemMarginTop = options.itemMarginTop || 0;
  211. this.itemMarginBottom = options.itemMarginBottom || 0;
  212. this.padding = padding;
  213. this.initialItemY = padding - 5; // 5 is pixels above the text
  214. this.symbolWidth = pick(options.symbolWidth, 16);
  215. this.pages = [];
  216. this.proximate = options.layout === 'proximate' && !this.chart.inverted;
  217. this.baseline = void 0; // #12705: baseline has to be reset on every update
  218. };
  219. /**
  220. * Update the legend with new options. Equivalent to running `chart.update`
  221. * with a legend configuration option.
  222. *
  223. * @sample highcharts/legend/legend-update/
  224. * Legend update
  225. *
  226. * @function Highcharts.Legend#update
  227. *
  228. * @param {Highcharts.LegendOptions} options
  229. * Legend options.
  230. *
  231. * @param {boolean} [redraw=true]
  232. * Whether to redraw the chart after the axis is altered. If doing more
  233. * operations on the chart, it is a good idea to set redraw to false and
  234. * call {@link Chart#redraw} after. Whether to redraw the chart.
  235. *
  236. * @fires Highcharts.Legends#event:afterUpdate
  237. */
  238. Legend.prototype.update = function (options, redraw) {
  239. var chart = this.chart;
  240. this.setOptions(merge(true, this.options, options));
  241. this.destroy();
  242. chart.isDirtyLegend = chart.isDirtyBox = true;
  243. if (pick(redraw, true)) {
  244. chart.redraw();
  245. }
  246. fireEvent(this, 'afterUpdate');
  247. };
  248. /**
  249. * Set the colors for the legend item.
  250. *
  251. * @private
  252. * @function Highcharts.Legend#colorizeItem
  253. * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item
  254. * A Series or Point instance
  255. * @param {boolean} [visible=false]
  256. * Dimmed or colored
  257. *
  258. * @todo
  259. * Make events official: Fires the event `afterColorizeItem`.
  260. */
  261. Legend.prototype.colorizeItem = function (item, visible) {
  262. item.legendGroup[visible ? 'removeClass' : 'addClass']('highcharts-legend-item-hidden');
  263. if (!this.chart.styledMode) {
  264. var legend = this, options = legend.options, legendItem = item.legendItem, legendLine = item.legendLine, legendSymbol = item.legendSymbol, hiddenColor = legend.itemHiddenStyle.color, textColor = visible ?
  265. options.itemStyle.color :
  266. hiddenColor, symbolColor = visible ?
  267. (item.color || hiddenColor) :
  268. hiddenColor, markerOptions = item.options && item.options.marker, symbolAttr = { fill: symbolColor };
  269. if (legendItem) {
  270. legendItem.css({
  271. fill: textColor,
  272. color: textColor // #1553, oldIE
  273. });
  274. }
  275. if (legendLine) {
  276. legendLine.attr({ stroke: symbolColor });
  277. }
  278. if (legendSymbol) {
  279. // Apply marker options
  280. if (markerOptions && legendSymbol.isMarker) { // #585
  281. symbolAttr = item.pointAttribs();
  282. if (!visible) {
  283. // #6769
  284. symbolAttr.stroke = symbolAttr.fill = hiddenColor;
  285. }
  286. }
  287. legendSymbol.attr(symbolAttr);
  288. }
  289. }
  290. fireEvent(this, 'afterColorizeItem', { item: item, visible: visible });
  291. };
  292. /**
  293. * @private
  294. * @function Highcharts.Legend#positionItems
  295. */
  296. Legend.prototype.positionItems = function () {
  297. // Now that the legend width and height are established, put the items
  298. // in the final position
  299. this.allItems.forEach(this.positionItem, this);
  300. if (!this.chart.isResizing) {
  301. this.positionCheckboxes();
  302. }
  303. };
  304. /**
  305. * Position the legend item.
  306. *
  307. * @private
  308. * @function Highcharts.Legend#positionItem
  309. * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item
  310. * The item to position
  311. */
  312. Legend.prototype.positionItem = function (item) {
  313. var _this = this;
  314. var legend = this, options = legend.options, symbolPadding = options.symbolPadding, ltr = !options.rtl, legendItemPos = item._legendItemPos, itemX = legendItemPos[0], itemY = legendItemPos[1], checkbox = item.checkbox, legendGroup = item.legendGroup;
  315. if (legendGroup && legendGroup.element) {
  316. var attribs = {
  317. translateX: ltr ?
  318. itemX :
  319. legend.legendWidth - itemX - 2 * symbolPadding - 4,
  320. translateY: itemY
  321. };
  322. var complete = function () {
  323. fireEvent(_this, 'afterPositionItem', { item: item });
  324. };
  325. if (defined(legendGroup.translateY)) {
  326. legendGroup.animate(attribs, void 0, complete);
  327. }
  328. else {
  329. legendGroup.attr(attribs);
  330. complete();
  331. }
  332. }
  333. if (checkbox) {
  334. checkbox.x = itemX;
  335. checkbox.y = itemY;
  336. }
  337. };
  338. /**
  339. * Destroy a single legend item, used internally on removing series items.
  340. *
  341. * @private
  342. * @function Highcharts.Legend#destroyItem
  343. * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item
  344. * The item to remove
  345. */
  346. Legend.prototype.destroyItem = function (item) {
  347. var checkbox = item.checkbox;
  348. // destroy SVG elements
  349. ['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'].forEach(function (key) {
  350. if (item[key]) {
  351. item[key] = item[key].destroy();
  352. }
  353. });
  354. if (checkbox) {
  355. discardElement(item.checkbox);
  356. }
  357. };
  358. /**
  359. * Destroy the legend. Used internally. To reflow objects, `chart.redraw`
  360. * must be called after destruction.
  361. *
  362. * @private
  363. * @function Highcharts.Legend#destroy
  364. */
  365. Legend.prototype.destroy = function () {
  366. /**
  367. * @private
  368. * @param {string} key
  369. * @return {void}
  370. */
  371. function destroyItems(key) {
  372. if (this[key]) {
  373. this[key] = this[key].destroy();
  374. }
  375. }
  376. // Destroy items
  377. this.getAllItems().forEach(function (item) {
  378. ['legendItem', 'legendGroup'].forEach(destroyItems, item);
  379. });
  380. // Destroy legend elements
  381. [
  382. 'clipRect',
  383. 'up',
  384. 'down',
  385. 'pager',
  386. 'nav',
  387. 'box',
  388. 'title',
  389. 'group'
  390. ].forEach(destroyItems, this);
  391. this.display = null; // Reset in .render on update.
  392. };
  393. /**
  394. * Position the checkboxes after the width is determined.
  395. *
  396. * @private
  397. * @function Highcharts.Legend#positionCheckboxes
  398. */
  399. Legend.prototype.positionCheckboxes = function () {
  400. var alignAttr = this.group && this.group.alignAttr, translateY, clipHeight = this.clipHeight || this.legendHeight, titleHeight = this.titleHeight;
  401. if (alignAttr) {
  402. translateY = alignAttr.translateY;
  403. this.allItems.forEach(function (item) {
  404. var checkbox = item.checkbox, top;
  405. if (checkbox) {
  406. top = translateY + titleHeight + checkbox.y +
  407. (this.scrollOffset || 0) + 3;
  408. css(checkbox, {
  409. left: (alignAttr.translateX + item.checkboxOffset +
  410. checkbox.x - 20) + 'px',
  411. top: top + 'px',
  412. display: this.proximate || (top > translateY - 6 &&
  413. top < translateY + clipHeight - 6) ?
  414. '' :
  415. 'none'
  416. });
  417. }
  418. }, this);
  419. }
  420. };
  421. /**
  422. * Render the legend title on top of the legend.
  423. *
  424. * @private
  425. * @function Highcharts.Legend#renderTitle
  426. */
  427. Legend.prototype.renderTitle = function () {
  428. var options = this.options, padding = this.padding, titleOptions = options.title, titleHeight = 0, bBox;
  429. if (titleOptions.text) {
  430. if (!this.title) {
  431. /**
  432. * SVG element of the legend title.
  433. *
  434. * @readonly
  435. * @name Highcharts.Legend#title
  436. * @type {Highcharts.SVGElement}
  437. */
  438. this.title = this.chart.renderer.label(titleOptions.text, padding - 3, padding - 4, null, null, null, options.useHTML, null, 'legend-title')
  439. .attr({ zIndex: 1 });
  440. if (!this.chart.styledMode) {
  441. this.title.css(titleOptions.style);
  442. }
  443. this.title.add(this.group);
  444. }
  445. // Set the max title width (#7253)
  446. if (!titleOptions.width) {
  447. this.title.css({
  448. width: this.maxLegendWidth + 'px'
  449. });
  450. }
  451. bBox = this.title.getBBox();
  452. titleHeight = bBox.height;
  453. this.offsetWidth = bBox.width; // #1717
  454. this.contentGroup.attr({ translateY: titleHeight });
  455. }
  456. this.titleHeight = titleHeight;
  457. };
  458. /**
  459. * Set the legend item text.
  460. *
  461. * @function Highcharts.Legend#setText
  462. * @param {Highcharts.Point|Highcharts.Series} item
  463. * The item for which to update the text in the legend.
  464. */
  465. Legend.prototype.setText = function (item) {
  466. var options = this.options;
  467. item.legendItem.attr({
  468. text: options.labelFormat ?
  469. format(options.labelFormat, item, this.chart) :
  470. options.labelFormatter.call(item)
  471. });
  472. };
  473. /**
  474. * Render a single specific legend item. Called internally from the `render`
  475. * function.
  476. *
  477. * @private
  478. * @function Highcharts.Legend#renderItem
  479. * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item
  480. * The item to render.
  481. */
  482. Legend.prototype.renderItem = function (item) {
  483. var legend = this, chart = legend.chart, renderer = chart.renderer, options = legend.options, horizontal = options.layout === 'horizontal', symbolWidth = legend.symbolWidth, symbolPadding = options.symbolPadding || 0, itemStyle = legend.itemStyle, itemHiddenStyle = legend.itemHiddenStyle, itemDistance = horizontal ? pick(options.itemDistance, 20) : 0, ltr = !options.rtl, bBox, li = item.legendItem, isSeries = !item.series, series = !isSeries && item.series.drawLegendSymbol ?
  484. item.series :
  485. item, seriesOptions = series.options, showCheckbox = legend.createCheckboxForItem &&
  486. seriesOptions &&
  487. seriesOptions.showCheckbox,
  488. // full width minus text width
  489. itemExtraWidth = symbolWidth + symbolPadding +
  490. itemDistance + (showCheckbox ? 20 : 0), useHTML = options.useHTML, itemClassName = item.options.className;
  491. if (!li) { // generate it once, later move it
  492. // Generate the group box, a group to hold the symbol and text. Text
  493. // is to be appended in Legend class.
  494. item.legendGroup = renderer
  495. .g('legend-item')
  496. .addClass('highcharts-' + series.type + '-series ' +
  497. 'highcharts-color-' + item.colorIndex +
  498. (itemClassName ? ' ' + itemClassName : '') +
  499. (isSeries ?
  500. ' highcharts-series-' + item.index :
  501. ''))
  502. .attr({ zIndex: 1 })
  503. .add(legend.scrollGroup);
  504. // Generate the list item text and add it to the group
  505. item.legendItem = li = renderer.text('', ltr ?
  506. symbolWidth + symbolPadding :
  507. -symbolPadding, legend.baseline || 0, useHTML);
  508. if (!chart.styledMode) {
  509. // merge to prevent modifying original (#1021)
  510. li.css(merge(item.visible ?
  511. itemStyle :
  512. itemHiddenStyle));
  513. }
  514. li
  515. .attr({
  516. align: ltr ? 'left' : 'right',
  517. zIndex: 2
  518. })
  519. .add(item.legendGroup);
  520. // Get the baseline for the first item - the font size is equal for
  521. // all
  522. if (!legend.baseline) {
  523. legend.fontMetrics = renderer.fontMetrics(chart.styledMode ? 12 : itemStyle.fontSize, li);
  524. legend.baseline =
  525. legend.fontMetrics.f + 3 + legend.itemMarginTop;
  526. li.attr('y', legend.baseline);
  527. legend.symbolHeight =
  528. options.symbolHeight || legend.fontMetrics.f;
  529. if (options.squareSymbol) {
  530. legend.symbolWidth = pick(options.symbolWidth, Math.max(legend.symbolHeight, 16));
  531. itemExtraWidth = legend.symbolWidth + symbolPadding +
  532. itemDistance + (showCheckbox ? 20 : 0);
  533. if (ltr) {
  534. li.attr('x', legend.symbolWidth + symbolPadding);
  535. }
  536. }
  537. }
  538. // Draw the legend symbol inside the group box
  539. series.drawLegendSymbol(legend, item);
  540. if (legend.setItemEvents) {
  541. legend.setItemEvents(item, li, useHTML);
  542. }
  543. }
  544. // Add the HTML checkbox on top
  545. if (showCheckbox && !item.checkbox && legend.createCheckboxForItem) {
  546. legend.createCheckboxForItem(item);
  547. }
  548. // Colorize the items
  549. legend.colorizeItem(item, item.visible);
  550. // Take care of max width and text overflow (#6659)
  551. if (chart.styledMode || !itemStyle.width) {
  552. li.css({
  553. width: ((options.itemWidth ||
  554. legend.widthOption ||
  555. chart.spacingBox.width) - itemExtraWidth) + 'px'
  556. });
  557. }
  558. // Always update the text
  559. legend.setText(item);
  560. // calculate the positions for the next line
  561. bBox = li.getBBox();
  562. item.itemWidth = item.checkboxOffset =
  563. options.itemWidth ||
  564. item.legendItemWidth ||
  565. bBox.width + itemExtraWidth;
  566. legend.maxItemWidth = Math.max(legend.maxItemWidth, item.itemWidth);
  567. legend.totalItemWidth += item.itemWidth;
  568. legend.itemHeight = item.itemHeight = Math.round(item.legendItemHeight || bBox.height || legend.symbolHeight);
  569. };
  570. /**
  571. * Get the position of the item in the layout. We now know the
  572. * maxItemWidth from the previous loop.
  573. *
  574. * @private
  575. * @function Highcharts.Legend#layoutItem
  576. * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item
  577. */
  578. Legend.prototype.layoutItem = function (item) {
  579. var options = this.options, padding = this.padding, horizontal = options.layout === 'horizontal', itemHeight = item.itemHeight, itemMarginBottom = this.itemMarginBottom, itemMarginTop = this.itemMarginTop, itemDistance = horizontal ? pick(options.itemDistance, 20) : 0, maxLegendWidth = this.maxLegendWidth, itemWidth = (options.alignColumns &&
  580. this.totalItemWidth > maxLegendWidth) ?
  581. this.maxItemWidth :
  582. item.itemWidth;
  583. // If the item exceeds the width, start a new line
  584. if (horizontal &&
  585. this.itemX - padding + itemWidth > maxLegendWidth) {
  586. this.itemX = padding;
  587. if (this.lastLineHeight) { // Not for the first line (#10167)
  588. this.itemY += (itemMarginTop +
  589. this.lastLineHeight +
  590. itemMarginBottom);
  591. }
  592. this.lastLineHeight = 0; // reset for next line (#915, #3976)
  593. }
  594. // Set the edge positions
  595. this.lastItemY = itemMarginTop + this.itemY + itemMarginBottom;
  596. this.lastLineHeight = Math.max(// #915
  597. itemHeight, this.lastLineHeight);
  598. // cache the position of the newly generated or reordered items
  599. item._legendItemPos = [this.itemX, this.itemY];
  600. // advance
  601. if (horizontal) {
  602. this.itemX += itemWidth;
  603. }
  604. else {
  605. this.itemY +=
  606. itemMarginTop + itemHeight + itemMarginBottom;
  607. this.lastLineHeight = itemHeight;
  608. }
  609. // the width of the widest item
  610. this.offsetWidth = this.widthOption || Math.max((horizontal ? this.itemX - padding - (item.checkbox ?
  611. // decrease by itemDistance only when no checkbox #4853
  612. 0 :
  613. itemDistance) : itemWidth) + padding, this.offsetWidth);
  614. };
  615. /**
  616. * Get all items, which is one item per series for most series and one
  617. * item per point for pie series and its derivatives. Fires the event
  618. * `afterGetAllItems`.
  619. *
  620. * @private
  621. * @function Highcharts.Legend#getAllItems
  622. * @return {Array<(Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series)>}
  623. * The current items in the legend.
  624. * @fires Highcharts.Legend#event:afterGetAllItems
  625. */
  626. Legend.prototype.getAllItems = function () {
  627. var allItems = [];
  628. this.chart.series.forEach(function (series) {
  629. var seriesOptions = series && series.options;
  630. // Handle showInLegend. If the series is linked to another series,
  631. // defaults to false.
  632. if (series && pick(seriesOptions.showInLegend, !defined(seriesOptions.linkedTo) ? void 0 : false, true)) {
  633. // Use points or series for the legend item depending on
  634. // legendType
  635. allItems = allItems.concat(series.legendItems ||
  636. (seriesOptions.legendType === 'point' ?
  637. series.data :
  638. series));
  639. }
  640. });
  641. fireEvent(this, 'afterGetAllItems', { allItems: allItems });
  642. return allItems;
  643. };
  644. /**
  645. * Get a short, three letter string reflecting the alignment and layout.
  646. *
  647. * @private
  648. * @function Highcharts.Legend#getAlignment
  649. * @return {string}
  650. * The alignment, empty string if floating
  651. */
  652. Legend.prototype.getAlignment = function () {
  653. var options = this.options;
  654. // Use the first letter of each alignment option in order to detect
  655. // the side. (#4189 - use charAt(x) notation instead of [x] for IE7)
  656. if (this.proximate) {
  657. return options.align.charAt(0) + 'tv';
  658. }
  659. return options.floating ? '' : (options.align.charAt(0) +
  660. options.verticalAlign.charAt(0) +
  661. options.layout.charAt(0));
  662. };
  663. /**
  664. * Adjust the chart margins by reserving space for the legend on only one
  665. * side of the chart. If the position is set to a corner, top or bottom is
  666. * reserved for horizontal legends and left or right for vertical ones.
  667. *
  668. * @private
  669. * @function Highcharts.Legend#adjustMargins
  670. * @param {Array<number>} margin
  671. * @param {Array<number>} spacing
  672. */
  673. Legend.prototype.adjustMargins = function (margin, spacing) {
  674. var chart = this.chart, options = this.options, alignment = this.getAlignment();
  675. if (alignment) {
  676. ([
  677. /(lth|ct|rth)/,
  678. /(rtv|rm|rbv)/,
  679. /(rbh|cb|lbh)/,
  680. /(lbv|lm|ltv)/
  681. ]).forEach(function (alignments, side) {
  682. if (alignments.test(alignment) && !defined(margin[side])) {
  683. // Now we have detected on which side of the chart we should
  684. // reserve space for the legend
  685. chart[marginNames[side]] = Math.max(chart[marginNames[side]], (chart.legend[(side + 1) % 2 ? 'legendHeight' : 'legendWidth'] +
  686. [1, -1, -1, 1][side] * options[(side % 2) ? 'x' : 'y'] +
  687. pick(options.margin, 12) +
  688. spacing[side] +
  689. (chart.titleOffset[side] || 0)));
  690. }
  691. });
  692. }
  693. };
  694. /**
  695. * @private
  696. * @function Highcharts.Legend#proximatePositions
  697. */
  698. Legend.prototype.proximatePositions = function () {
  699. var chart = this.chart, boxes = [], alignLeft = this.options.align === 'left';
  700. this.allItems.forEach(function (item) {
  701. var lastPoint, height, useFirstPoint = alignLeft, target, top;
  702. if (item.yAxis) {
  703. if (item.xAxis.options.reversed) {
  704. useFirstPoint = !useFirstPoint;
  705. }
  706. if (item.points) {
  707. lastPoint = find(useFirstPoint ?
  708. item.points :
  709. item.points.slice(0).reverse(), function (item) {
  710. return isNumber(item.plotY);
  711. });
  712. }
  713. height = this.itemMarginTop +
  714. item.legendItem.getBBox().height +
  715. this.itemMarginBottom;
  716. top = item.yAxis.top - chart.plotTop;
  717. if (item.visible) {
  718. target = lastPoint ?
  719. lastPoint.plotY :
  720. item.yAxis.height;
  721. target += top - 0.3 * height;
  722. }
  723. else {
  724. target = top + item.yAxis.height;
  725. }
  726. boxes.push({
  727. target: target,
  728. size: height,
  729. item: item
  730. });
  731. }
  732. }, this);
  733. H.distribute(boxes, chart.plotHeight);
  734. boxes.forEach(function (box) {
  735. box.item._legendItemPos[1] =
  736. chart.plotTop - chart.spacing[0] + box.pos;
  737. });
  738. };
  739. /**
  740. * Render the legend. This method can be called both before and after
  741. * `chart.render`. If called after, it will only rearrange items instead
  742. * of creating new ones. Called internally on initial render and after
  743. * redraws.
  744. *
  745. * @private
  746. * @function Highcharts.Legend#render
  747. */
  748. Legend.prototype.render = function () {
  749. var legend = this, chart = legend.chart, renderer = chart.renderer, legendGroup = legend.group, allItems, display, legendWidth, legendHeight, box = legend.box, options = legend.options, padding = legend.padding, allowedWidth;
  750. legend.itemX = padding;
  751. legend.itemY = legend.initialItemY;
  752. legend.offsetWidth = 0;
  753. legend.lastItemY = 0;
  754. legend.widthOption = relativeLength(options.width, chart.spacingBox.width - padding);
  755. // Compute how wide the legend is allowed to be
  756. allowedWidth =
  757. chart.spacingBox.width - 2 * padding - options.x;
  758. if (['rm', 'lm'].indexOf(legend.getAlignment().substring(0, 2)) > -1) {
  759. allowedWidth /= 2;
  760. }
  761. legend.maxLegendWidth = legend.widthOption || allowedWidth;
  762. if (!legendGroup) {
  763. /**
  764. * SVG group of the legend.
  765. *
  766. * @readonly
  767. * @name Highcharts.Legend#group
  768. * @type {Highcharts.SVGElement}
  769. */
  770. legend.group = legendGroup = renderer.g('legend')
  771. .attr({ zIndex: 7 })
  772. .add();
  773. legend.contentGroup = renderer.g()
  774. .attr({ zIndex: 1 }) // above background
  775. .add(legendGroup);
  776. legend.scrollGroup = renderer.g()
  777. .add(legend.contentGroup);
  778. }
  779. legend.renderTitle();
  780. // add each series or point
  781. allItems = legend.getAllItems();
  782. // sort by legendIndex
  783. stableSort(allItems, function (a, b) {
  784. return ((a.options && a.options.legendIndex) || 0) -
  785. ((b.options && b.options.legendIndex) || 0);
  786. });
  787. // reversed legend
  788. if (options.reversed) {
  789. allItems.reverse();
  790. }
  791. /**
  792. * All items for the legend, which is an array of series for most series
  793. * and an array of points for pie series and its derivatives.
  794. *
  795. * @readonly
  796. * @name Highcharts.Legend#allItems
  797. * @type {Array<(Highcharts.Point|Highcharts.Series)>}
  798. */
  799. legend.allItems = allItems;
  800. legend.display = display = !!allItems.length;
  801. // Render the items. First we run a loop to set the text and properties
  802. // and read all the bounding boxes. The next loop computes the item
  803. // positions based on the bounding boxes.
  804. legend.lastLineHeight = 0;
  805. legend.maxItemWidth = 0;
  806. legend.totalItemWidth = 0;
  807. legend.itemHeight = 0;
  808. allItems.forEach(legend.renderItem, legend);
  809. allItems.forEach(legend.layoutItem, legend);
  810. // Get the box
  811. legendWidth = (legend.widthOption || legend.offsetWidth) + padding;
  812. legendHeight = legend.lastItemY + legend.lastLineHeight +
  813. legend.titleHeight;
  814. legendHeight = legend.handleOverflow(legendHeight);
  815. legendHeight += padding;
  816. // Draw the border and/or background
  817. if (!box) {
  818. /**
  819. * SVG element of the legend box.
  820. *
  821. * @readonly
  822. * @name Highcharts.Legend#box
  823. * @type {Highcharts.SVGElement}
  824. */
  825. legend.box = box = renderer.rect()
  826. .addClass('highcharts-legend-box')
  827. .attr({
  828. r: options.borderRadius
  829. })
  830. .add(legendGroup);
  831. box.isNew = true;
  832. }
  833. // Presentational
  834. if (!chart.styledMode) {
  835. box
  836. .attr({
  837. stroke: options.borderColor,
  838. 'stroke-width': options.borderWidth || 0,
  839. fill: options.backgroundColor || 'none'
  840. })
  841. .shadow(options.shadow);
  842. }
  843. if (legendWidth > 0 && legendHeight > 0) {
  844. box[box.isNew ? 'attr' : 'animate'](box.crisp.call({}, {
  845. x: 0,
  846. y: 0,
  847. width: legendWidth,
  848. height: legendHeight
  849. }, box.strokeWidth()));
  850. box.isNew = false;
  851. }
  852. // hide the border if no items
  853. box[display ? 'show' : 'hide']();
  854. // Open for responsiveness
  855. if (chart.styledMode && legendGroup.getStyle('display') === 'none') {
  856. legendWidth = legendHeight = 0;
  857. }
  858. legend.legendWidth = legendWidth;
  859. legend.legendHeight = legendHeight;
  860. if (display) {
  861. legend.align();
  862. }
  863. if (!this.proximate) {
  864. this.positionItems();
  865. }
  866. fireEvent(this, 'afterRender');
  867. };
  868. /**
  869. * Align the legend to chart's box.
  870. *
  871. * @private
  872. * @function Highcharts.align
  873. * @param {Highcharts.BBoxObject} alignTo
  874. * @return {void}
  875. */
  876. Legend.prototype.align = function (alignTo) {
  877. if (alignTo === void 0) { alignTo = this.chart.spacingBox; }
  878. var chart = this.chart, options = this.options;
  879. // If aligning to the top and the layout is horizontal, adjust for
  880. // the title (#7428)
  881. var y = alignTo.y;
  882. if (/(lth|ct|rth)/.test(this.getAlignment()) &&
  883. chart.titleOffset[0] > 0) {
  884. y += chart.titleOffset[0];
  885. }
  886. else if (/(lbh|cb|rbh)/.test(this.getAlignment()) &&
  887. chart.titleOffset[2] > 0) {
  888. y -= chart.titleOffset[2];
  889. }
  890. if (y !== alignTo.y) {
  891. alignTo = merge(alignTo, { y: y });
  892. }
  893. this.group.align(merge(options, {
  894. width: this.legendWidth,
  895. height: this.legendHeight,
  896. verticalAlign: this.proximate ? 'top' : options.verticalAlign
  897. }), true, alignTo);
  898. };
  899. /**
  900. * Set up the overflow handling by adding navigation with up and down arrows
  901. * below the legend.
  902. *
  903. * @private
  904. * @function Highcharts.Legend#handleOverflow
  905. * @param {number} legendHeight
  906. * @return {number}
  907. */
  908. Legend.prototype.handleOverflow = function (legendHeight) {
  909. var legend = this, chart = this.chart, renderer = chart.renderer, options = this.options, optionsY = options.y, alignTop = options.verticalAlign === 'top', padding = this.padding, spaceHeight = (chart.spacingBox.height +
  910. (alignTop ? -optionsY : optionsY) - padding), maxHeight = options.maxHeight, clipHeight, clipRect = this.clipRect, navOptions = options.navigation, animation = pick(navOptions.animation, true), arrowSize = navOptions.arrowSize || 12, nav = this.nav, pages = this.pages, lastY, allItems = this.allItems, clipToHeight = function (height) {
  911. if (typeof height === 'number') {
  912. clipRect.attr({
  913. height: height
  914. });
  915. }
  916. else if (clipRect) { // Reset (#5912)
  917. legend.clipRect = clipRect.destroy();
  918. legend.contentGroup.clip();
  919. }
  920. // useHTML
  921. if (legend.contentGroup.div) {
  922. legend.contentGroup.div.style.clip = height ?
  923. 'rect(' + padding + 'px,9999px,' +
  924. (padding + height) + 'px,0)' :
  925. 'auto';
  926. }
  927. }, addTracker = function (key) {
  928. legend[key] = renderer
  929. .circle(0, 0, arrowSize * 1.3)
  930. .translate(arrowSize / 2, arrowSize / 2)
  931. .add(nav);
  932. if (!chart.styledMode) {
  933. legend[key].attr('fill', 'rgba(0,0,0,0.0001)');
  934. }
  935. return legend[key];
  936. };
  937. // Adjust the height
  938. if (options.layout === 'horizontal' &&
  939. options.verticalAlign !== 'middle' &&
  940. !options.floating) {
  941. spaceHeight /= 2;
  942. }
  943. if (maxHeight) {
  944. spaceHeight = Math.min(spaceHeight, maxHeight);
  945. }
  946. // Reset the legend height and adjust the clipping rectangle
  947. pages.length = 0;
  948. if (legendHeight &&
  949. spaceHeight > 0 &&
  950. legendHeight > spaceHeight &&
  951. navOptions.enabled !== false) {
  952. this.clipHeight = clipHeight =
  953. Math.max(spaceHeight - 20 - this.titleHeight - padding, 0);
  954. this.currentPage = pick(this.currentPage, 1);
  955. this.fullHeight = legendHeight;
  956. // Fill pages with Y positions so that the top of each a legend item
  957. // defines the scroll top for each page (#2098)
  958. allItems.forEach(function (item, i) {
  959. var y = item._legendItemPos[1], h = Math.round(item.legendItem.getBBox().height), len = pages.length;
  960. if (!len || (y - pages[len - 1] > clipHeight &&
  961. (lastY || y) !== pages[len - 1])) {
  962. pages.push(lastY || y);
  963. len++;
  964. }
  965. // Keep track of which page each item is on
  966. item.pageIx = len - 1;
  967. if (lastY) {
  968. allItems[i - 1].pageIx = len - 1;
  969. }
  970. if (i === allItems.length - 1 &&
  971. y + h - pages[len - 1] > clipHeight &&
  972. y !== lastY // #2617
  973. ) {
  974. pages.push(y);
  975. item.pageIx = len;
  976. }
  977. if (y !== lastY) {
  978. lastY = y;
  979. }
  980. });
  981. // Only apply clipping if needed. Clipping causes blurred legend in
  982. // PDF export (#1787)
  983. if (!clipRect) {
  984. clipRect = legend.clipRect =
  985. renderer.clipRect(0, padding, 9999, 0);
  986. legend.contentGroup.clip(clipRect);
  987. }
  988. clipToHeight(clipHeight);
  989. // Add navigation elements
  990. if (!nav) {
  991. this.nav = nav = renderer.g()
  992. .attr({ zIndex: 1 })
  993. .add(this.group);
  994. this.up = renderer
  995. .symbol('triangle', 0, 0, arrowSize, arrowSize)
  996. .add(nav);
  997. addTracker('upTracker')
  998. .on('click', function () {
  999. legend.scroll(-1, animation);
  1000. });
  1001. this.pager = renderer.text('', 15, 10)
  1002. .addClass('highcharts-legend-navigation');
  1003. if (!chart.styledMode) {
  1004. this.pager.css(navOptions.style);
  1005. }
  1006. this.pager.add(nav);
  1007. this.down = renderer
  1008. .symbol('triangle-down', 0, 0, arrowSize, arrowSize)
  1009. .add(nav);
  1010. addTracker('downTracker')
  1011. .on('click', function () {
  1012. legend.scroll(1, animation);
  1013. });
  1014. }
  1015. // Set initial position
  1016. legend.scroll(0);
  1017. legendHeight = spaceHeight;
  1018. // Reset
  1019. }
  1020. else if (nav) {
  1021. clipToHeight();
  1022. this.nav = nav.destroy(); // #6322
  1023. this.scrollGroup.attr({
  1024. translateY: 1
  1025. });
  1026. this.clipHeight = 0; // #1379
  1027. }
  1028. return legendHeight;
  1029. };
  1030. /**
  1031. * Scroll the legend by a number of pages.
  1032. *
  1033. * @private
  1034. * @function Highcharts.Legend#scroll
  1035. *
  1036. * @param {number} scrollBy
  1037. * The number of pages to scroll.
  1038. *
  1039. * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
  1040. * Whether and how to apply animation.
  1041. *
  1042. * @return {void}
  1043. */
  1044. Legend.prototype.scroll = function (scrollBy, animation) {
  1045. var _this = this;
  1046. var chart = this.chart, pages = this.pages, pageCount = pages.length, currentPage = this.currentPage + scrollBy, clipHeight = this.clipHeight, navOptions = this.options.navigation, pager = this.pager, padding = this.padding;
  1047. // When resizing while looking at the last page
  1048. if (currentPage > pageCount) {
  1049. currentPage = pageCount;
  1050. }
  1051. if (currentPage > 0) {
  1052. if (typeof animation !== 'undefined') {
  1053. setAnimation(animation, chart);
  1054. }
  1055. this.nav.attr({
  1056. translateX: padding,
  1057. translateY: clipHeight + this.padding + 7 + this.titleHeight,
  1058. visibility: 'visible'
  1059. });
  1060. [this.up, this.upTracker].forEach(function (elem) {
  1061. elem.attr({
  1062. 'class': currentPage === 1 ?
  1063. 'highcharts-legend-nav-inactive' :
  1064. 'highcharts-legend-nav-active'
  1065. });
  1066. });
  1067. pager.attr({
  1068. text: currentPage + '/' + pageCount
  1069. });
  1070. [this.down, this.downTracker].forEach(function (elem) {
  1071. elem.attr({
  1072. // adjust to text width
  1073. x: 18 + this.pager.getBBox().width,
  1074. 'class': currentPage === pageCount ?
  1075. 'highcharts-legend-nav-inactive' :
  1076. 'highcharts-legend-nav-active'
  1077. });
  1078. }, this);
  1079. if (!chart.styledMode) {
  1080. this.up
  1081. .attr({
  1082. fill: currentPage === 1 ?
  1083. navOptions.inactiveColor :
  1084. navOptions.activeColor
  1085. });
  1086. this.upTracker
  1087. .css({
  1088. cursor: currentPage === 1 ? 'default' : 'pointer'
  1089. });
  1090. this.down
  1091. .attr({
  1092. fill: currentPage === pageCount ?
  1093. navOptions.inactiveColor :
  1094. navOptions.activeColor
  1095. });
  1096. this.downTracker
  1097. .css({
  1098. cursor: currentPage === pageCount ?
  1099. 'default' :
  1100. 'pointer'
  1101. });
  1102. }
  1103. this.scrollOffset = -pages[currentPage - 1] + this.initialItemY;
  1104. this.scrollGroup.animate({
  1105. translateY: this.scrollOffset
  1106. });
  1107. this.currentPage = currentPage;
  1108. this.positionCheckboxes();
  1109. // Fire event after scroll animation is complete
  1110. var animOptions = animObject(pick(animation, chart.renderer.globalAnimation, true));
  1111. syncTimeout(function () {
  1112. fireEvent(_this, 'afterScroll', { currentPage: currentPage });
  1113. }, animOptions.duration);
  1114. }
  1115. };
  1116. /**
  1117. * @private
  1118. * @function Highcharts.Legend#setItemEvents
  1119. * @param {Highcharts.BubbleLegend|Point|Highcharts.Series} item
  1120. * @param {Highcharts.SVGElement} legendItem
  1121. * @param {boolean} [useHTML=false]
  1122. * @fires Highcharts.Point#event:legendItemClick
  1123. * @fires Highcharts.Series#event:legendItemClick
  1124. */
  1125. Legend.prototype.setItemEvents = function (item, legendItem, useHTML) {
  1126. var legend = this, boxWrapper = legend.chart.renderer.boxWrapper, isPoint = item instanceof Point, activeClass = 'highcharts-legend-' +
  1127. (isPoint ? 'point' : 'series') + '-active', styledMode = legend.chart.styledMode,
  1128. // When `useHTML`, the symbol is rendered in other group, so
  1129. // we need to apply events listeners to both places
  1130. legendItems = useHTML ?
  1131. [legendItem, item.legendSymbol] :
  1132. [item.legendGroup];
  1133. // Set the events on the item group, or in case of useHTML, the item
  1134. // itself (#1249)
  1135. legendItems.forEach(function (element) {
  1136. if (element) {
  1137. element
  1138. .on('mouseover', function () {
  1139. if (item.visible) {
  1140. legend.allItems.forEach(function (inactiveItem) {
  1141. if (item !== inactiveItem) {
  1142. inactiveItem.setState('inactive', !isPoint);
  1143. }
  1144. });
  1145. }
  1146. item.setState('hover');
  1147. // A CSS class to dim or hide other than the hovered
  1148. // series.
  1149. // Works only if hovered series is visible (#10071).
  1150. if (item.visible) {
  1151. boxWrapper.addClass(activeClass);
  1152. }
  1153. if (!styledMode) {
  1154. legendItem.css(legend.options.itemHoverStyle);
  1155. }
  1156. })
  1157. .on('mouseout', function () {
  1158. if (!legend.chart.styledMode) {
  1159. legendItem.css(merge(item.visible ?
  1160. legend.itemStyle :
  1161. legend.itemHiddenStyle));
  1162. }
  1163. legend.allItems.forEach(function (inactiveItem) {
  1164. if (item !== inactiveItem) {
  1165. inactiveItem.setState('', !isPoint);
  1166. }
  1167. });
  1168. // A CSS class to dim or hide other than the hovered
  1169. // series.
  1170. boxWrapper.removeClass(activeClass);
  1171. item.setState();
  1172. })
  1173. .on('click', function (event) {
  1174. var strLegendItemClick = 'legendItemClick', fnLegendItemClick = function () {
  1175. if (item.setVisible) {
  1176. item.setVisible();
  1177. }
  1178. // Reset inactive state
  1179. legend.allItems.forEach(function (inactiveItem) {
  1180. if (item !== inactiveItem) {
  1181. inactiveItem.setState(item.visible ? 'inactive' : '', !isPoint);
  1182. }
  1183. });
  1184. };
  1185. // A CSS class to dim or hide other than the hovered
  1186. // series. Event handling in iOS causes the activeClass
  1187. // to be added prior to click in some cases (#7418).
  1188. boxWrapper.removeClass(activeClass);
  1189. // Pass over the click/touch event. #4.
  1190. event = {
  1191. browserEvent: event
  1192. };
  1193. // click the name or symbol
  1194. if (item.firePointEvent) { // point
  1195. item.firePointEvent(strLegendItemClick, event, fnLegendItemClick);
  1196. }
  1197. else {
  1198. fireEvent(item, strLegendItemClick, event, fnLegendItemClick);
  1199. }
  1200. });
  1201. }
  1202. });
  1203. };
  1204. /**
  1205. * @private
  1206. * @function Highcharts.Legend#createCheckboxForItem
  1207. * @param {Highcharts.BubbleLegend|Point|Highcharts.Series} item
  1208. * @fires Highcharts.Series#event:checkboxClick
  1209. */
  1210. Legend.prototype.createCheckboxForItem = function (item) {
  1211. var legend = this;
  1212. item.checkbox = createElement('input', {
  1213. type: 'checkbox',
  1214. className: 'highcharts-legend-checkbox',
  1215. checked: item.selected,
  1216. defaultChecked: item.selected // required by IE7
  1217. }, legend.options.itemCheckboxStyle, legend.chart.container);
  1218. addEvent(item.checkbox, 'click', function (event) {
  1219. var target = event.target;
  1220. fireEvent(item.series || item, 'checkboxClick', {
  1221. checked: target.checked,
  1222. item: item
  1223. }, function () {
  1224. item.select();
  1225. });
  1226. });
  1227. };
  1228. return Legend;
  1229. }());
  1230. // Workaround for #2030, horizontal legend items not displaying in IE11 Preview,
  1231. // and for #2580, a similar drawing flaw in Firefox 26.
  1232. // Explore if there's a general cause for this. The problem may be related
  1233. // to nested group elements, as the legend item texts are within 4 group
  1234. // elements.
  1235. if (/Trident\/7\.0/.test(win.navigator && win.navigator.userAgent) ||
  1236. isFirefox) {
  1237. wrap(Legend.prototype, 'positionItem', function (proceed, item) {
  1238. var legend = this,
  1239. // If chart destroyed in sync, this is undefined (#2030)
  1240. runPositionItem = function () {
  1241. if (item._legendItemPos) {
  1242. proceed.call(legend, item);
  1243. }
  1244. };
  1245. // Do it now, for export and to get checkbox placement
  1246. runPositionItem();
  1247. // Do it after to work around the core issue
  1248. if (!legend.bubbleLegend) {
  1249. setTimeout(runPositionItem);
  1250. }
  1251. });
  1252. }
  1253. H.Legend = Legend;
  1254. export default H.Legend;