| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127 |
- /* *
- *
- * (c) 2010-2020 Torstein Honsi
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- 'use strict';
- import H from './Globals.js';
- /**
- * Gets fired when the legend item belonging to a point is clicked. The default
- * action is to toggle the visibility of the point. This can be prevented by
- * returning `false` or calling `event.preventDefault()`.
- *
- * @callback Highcharts.PointLegendItemClickCallbackFunction
- *
- * @param {Highcharts.Point} this
- * The point on which the event occured.
- *
- * @param {Highcharts.PointLegendItemClickEventObject} event
- * The event that occured.
- */
- /**
- * Information about the legend click event.
- *
- * @interface Highcharts.PointLegendItemClickEventObject
- */ /**
- * Related browser event.
- * @name Highcharts.PointLegendItemClickEventObject#browserEvent
- * @type {Highcharts.PointerEvent}
- */ /**
- * Prevent the default action of toggle the visibility of the point.
- * @name Highcharts.PointLegendItemClickEventObject#preventDefault
- * @type {Function}
- */ /**
- * Related point.
- * @name Highcharts.PointLegendItemClickEventObject#target
- * @type {Highcharts.Point}
- */ /**
- * Event type.
- * @name Highcharts.PointLegendItemClickEventObject#type
- * @type {"legendItemClick"}
- */
- /**
- * Gets fired when the legend item belonging to a series is clicked. The default
- * action is to toggle the visibility of the series. This can be prevented by
- * returning `false` or calling `event.preventDefault()`.
- *
- * @callback Highcharts.SeriesLegendItemClickCallbackFunction
- *
- * @param {Highcharts.Series} this
- * The series where the event occured.
- *
- * @param {Highcharts.SeriesLegendItemClickEventObject} event
- * The event that occured.
- */
- /**
- * Information about the legend click event.
- *
- * @interface Highcharts.SeriesLegendItemClickEventObject
- */ /**
- * Related browser event.
- * @name Highcharts.SeriesLegendItemClickEventObject#browserEvent
- * @type {Highcharts.PointerEvent}
- */ /**
- * Prevent the default action of toggle the visibility of the series.
- * @name Highcharts.SeriesLegendItemClickEventObject#preventDefault
- * @type {Function}
- */ /**
- * Related series.
- * @name Highcharts.SeriesLegendItemClickEventObject#target
- * @type {Highcharts.Series}
- */ /**
- * Event type.
- * @name Highcharts.SeriesLegendItemClickEventObject#type
- * @type {"legendItemClick"}
- */
- import U from './Utilities.js';
- var addEvent = U.addEvent, animObject = U.animObject, css = U.css, defined = U.defined, discardElement = U.discardElement, find = U.find, fireEvent = U.fireEvent, format = U.format, isNumber = U.isNumber, merge = U.merge, pick = U.pick, relativeLength = U.relativeLength, setAnimation = U.setAnimation, stableSort = U.stableSort, syncTimeout = U.syncTimeout, wrap = U.wrap;
- var isFirefox = H.isFirefox, marginNames = H.marginNames, win = H.win;
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * The overview of the chart's series. The legend object is instanciated
- * internally in the chart constructor, and is available from the `chart.legend`
- * property. Each chart has only one legend.
- *
- * @class
- * @name Highcharts.Legend
- *
- * @param {Highcharts.Chart} chart
- * The chart instance.
- *
- * @param {Highcharts.LegendOptions} options
- * Legend options.
- */
- var Legend = /** @class */ (function () {
- /* *
- *
- * Constructors
- *
- * */
- function Legend(chart, options) {
- /* *
- *
- * Properties
- *
- * */
- this.allItems = [];
- this.box = void 0;
- this.contentGroup = void 0;
- this.display = false;
- this.group = void 0;
- this.initialItemY = 0;
- this.itemHeight = 0;
- this.itemMarginBottom = 0;
- this.itemMarginTop = 0;
- this.itemX = 0;
- this.itemY = 0;
- this.lastItemY = 0;
- this.lastLineHeight = 0;
- this.legendHeight = 0;
- this.legendWidth = 0;
- this.maxItemWidth = 0;
- this.maxLegendWidth = 0;
- this.offsetWidth = 0;
- this.options = {};
- this.padding = 0;
- this.pages = [];
- this.proximate = false;
- this.scrollGroup = void 0;
- this.symbolHeight = 0;
- this.symbolWidth = 0;
- this.titleHeight = 0;
- this.totalItemWidth = 0;
- this.widthOption = 0;
- this.chart = chart;
- this.init(chart, options);
- }
- /* *
- *
- * Functions
- *
- * */
- /**
- * Initialize the legend.
- *
- * @private
- * @function Highcharts.Legend#init
- *
- * @param {Highcharts.Chart} chart
- * The chart instance.
- *
- * @param {Highcharts.LegendOptions} options
- * Legend options.
- */
- Legend.prototype.init = function (chart, options) {
- /**
- * Chart of this legend.
- *
- * @readonly
- * @name Highcharts.Legend#chart
- * @type {Highcharts.Chart}
- */
- this.chart = chart;
- this.setOptions(options);
- if (options.enabled) {
- // Render it
- this.render();
- // move checkboxes
- addEvent(this.chart, 'endResize', function () {
- this.legend.positionCheckboxes();
- });
- if (this.proximate) {
- this.unchartrender = addEvent(this.chart, 'render', function () {
- this.legend.proximatePositions();
- this.legend.positionItems();
- });
- }
- else if (this.unchartrender) {
- this.unchartrender();
- }
- }
- };
- /**
- * @private
- * @function Highcharts.Legend#setOptions
- * @param {Highcharts.LegendOptions} options
- */
- Legend.prototype.setOptions = function (options) {
- var padding = pick(options.padding, 8);
- /**
- * Legend options.
- *
- * @readonly
- * @name Highcharts.Legend#options
- * @type {Highcharts.LegendOptions}
- */
- this.options = options;
- if (!this.chart.styledMode) {
- this.itemStyle = options.itemStyle;
- this.itemHiddenStyle = merge(this.itemStyle, options.itemHiddenStyle);
- }
- this.itemMarginTop = options.itemMarginTop || 0;
- this.itemMarginBottom = options.itemMarginBottom || 0;
- this.padding = padding;
- this.initialItemY = padding - 5; // 5 is pixels above the text
- this.symbolWidth = pick(options.symbolWidth, 16);
- this.pages = [];
- this.proximate = options.layout === 'proximate' && !this.chart.inverted;
- this.baseline = void 0; // #12705: baseline has to be reset on every update
- };
- /**
- * Update the legend with new options. Equivalent to running `chart.update`
- * with a legend configuration option.
- *
- * @sample highcharts/legend/legend-update/
- * Legend update
- *
- * @function Highcharts.Legend#update
- *
- * @param {Highcharts.LegendOptions} options
- * Legend options.
- *
- * @param {boolean} [redraw=true]
- * Whether to redraw the chart after the axis is altered. If doing more
- * operations on the chart, it is a good idea to set redraw to false and
- * call {@link Chart#redraw} after. Whether to redraw the chart.
- *
- * @fires Highcharts.Legends#event:afterUpdate
- */
- Legend.prototype.update = function (options, redraw) {
- var chart = this.chart;
- this.setOptions(merge(true, this.options, options));
- this.destroy();
- chart.isDirtyLegend = chart.isDirtyBox = true;
- if (pick(redraw, true)) {
- chart.redraw();
- }
- fireEvent(this, 'afterUpdate');
- };
- /**
- * Set the colors for the legend item.
- *
- * @private
- * @function Highcharts.Legend#colorizeItem
- * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item
- * A Series or Point instance
- * @param {boolean} [visible=false]
- * Dimmed or colored
- *
- * @todo
- * Make events official: Fires the event `afterColorizeItem`.
- */
- Legend.prototype.colorizeItem = function (item, visible) {
- item.legendGroup[visible ? 'removeClass' : 'addClass']('highcharts-legend-item-hidden');
- if (!this.chart.styledMode) {
- var legend = this, options = legend.options, legendItem = item.legendItem, legendLine = item.legendLine, legendSymbol = item.legendSymbol, hiddenColor = legend.itemHiddenStyle.color, textColor = visible ?
- options.itemStyle.color :
- hiddenColor, symbolColor = visible ?
- (item.color || hiddenColor) :
- hiddenColor, markerOptions = item.options && item.options.marker, symbolAttr = { fill: symbolColor };
- if (legendItem) {
- legendItem.css({
- fill: textColor,
- color: textColor // #1553, oldIE
- });
- }
- if (legendLine) {
- legendLine.attr({ stroke: symbolColor });
- }
- if (legendSymbol) {
- // Apply marker options
- if (markerOptions && legendSymbol.isMarker) { // #585
- symbolAttr = item.pointAttribs();
- if (!visible) {
- // #6769
- symbolAttr.stroke = symbolAttr.fill = hiddenColor;
- }
- }
- legendSymbol.attr(symbolAttr);
- }
- }
- fireEvent(this, 'afterColorizeItem', { item: item, visible: visible });
- };
- /**
- * @private
- * @function Highcharts.Legend#positionItems
- */
- Legend.prototype.positionItems = function () {
- // Now that the legend width and height are established, put the items
- // in the final position
- this.allItems.forEach(this.positionItem, this);
- if (!this.chart.isResizing) {
- this.positionCheckboxes();
- }
- };
- /**
- * Position the legend item.
- *
- * @private
- * @function Highcharts.Legend#positionItem
- * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item
- * The item to position
- */
- Legend.prototype.positionItem = function (item) {
- var _this = this;
- 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;
- if (legendGroup && legendGroup.element) {
- var attribs = {
- translateX: ltr ?
- itemX :
- legend.legendWidth - itemX - 2 * symbolPadding - 4,
- translateY: itemY
- };
- var complete = function () {
- fireEvent(_this, 'afterPositionItem', { item: item });
- };
- if (defined(legendGroup.translateY)) {
- legendGroup.animate(attribs, void 0, complete);
- }
- else {
- legendGroup.attr(attribs);
- complete();
- }
- }
- if (checkbox) {
- checkbox.x = itemX;
- checkbox.y = itemY;
- }
- };
- /**
- * Destroy a single legend item, used internally on removing series items.
- *
- * @private
- * @function Highcharts.Legend#destroyItem
- * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item
- * The item to remove
- */
- Legend.prototype.destroyItem = function (item) {
- var checkbox = item.checkbox;
- // destroy SVG elements
- ['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'].forEach(function (key) {
- if (item[key]) {
- item[key] = item[key].destroy();
- }
- });
- if (checkbox) {
- discardElement(item.checkbox);
- }
- };
- /**
- * Destroy the legend. Used internally. To reflow objects, `chart.redraw`
- * must be called after destruction.
- *
- * @private
- * @function Highcharts.Legend#destroy
- */
- Legend.prototype.destroy = function () {
- /**
- * @private
- * @param {string} key
- * @return {void}
- */
- function destroyItems(key) {
- if (this[key]) {
- this[key] = this[key].destroy();
- }
- }
- // Destroy items
- this.getAllItems().forEach(function (item) {
- ['legendItem', 'legendGroup'].forEach(destroyItems, item);
- });
- // Destroy legend elements
- [
- 'clipRect',
- 'up',
- 'down',
- 'pager',
- 'nav',
- 'box',
- 'title',
- 'group'
- ].forEach(destroyItems, this);
- this.display = null; // Reset in .render on update.
- };
- /**
- * Position the checkboxes after the width is determined.
- *
- * @private
- * @function Highcharts.Legend#positionCheckboxes
- */
- Legend.prototype.positionCheckboxes = function () {
- var alignAttr = this.group && this.group.alignAttr, translateY, clipHeight = this.clipHeight || this.legendHeight, titleHeight = this.titleHeight;
- if (alignAttr) {
- translateY = alignAttr.translateY;
- this.allItems.forEach(function (item) {
- var checkbox = item.checkbox, top;
- if (checkbox) {
- top = translateY + titleHeight + checkbox.y +
- (this.scrollOffset || 0) + 3;
- css(checkbox, {
- left: (alignAttr.translateX + item.checkboxOffset +
- checkbox.x - 20) + 'px',
- top: top + 'px',
- display: this.proximate || (top > translateY - 6 &&
- top < translateY + clipHeight - 6) ?
- '' :
- 'none'
- });
- }
- }, this);
- }
- };
- /**
- * Render the legend title on top of the legend.
- *
- * @private
- * @function Highcharts.Legend#renderTitle
- */
- Legend.prototype.renderTitle = function () {
- var options = this.options, padding = this.padding, titleOptions = options.title, titleHeight = 0, bBox;
- if (titleOptions.text) {
- if (!this.title) {
- /**
- * SVG element of the legend title.
- *
- * @readonly
- * @name Highcharts.Legend#title
- * @type {Highcharts.SVGElement}
- */
- this.title = this.chart.renderer.label(titleOptions.text, padding - 3, padding - 4, null, null, null, options.useHTML, null, 'legend-title')
- .attr({ zIndex: 1 });
- if (!this.chart.styledMode) {
- this.title.css(titleOptions.style);
- }
- this.title.add(this.group);
- }
- // Set the max title width (#7253)
- if (!titleOptions.width) {
- this.title.css({
- width: this.maxLegendWidth + 'px'
- });
- }
- bBox = this.title.getBBox();
- titleHeight = bBox.height;
- this.offsetWidth = bBox.width; // #1717
- this.contentGroup.attr({ translateY: titleHeight });
- }
- this.titleHeight = titleHeight;
- };
- /**
- * Set the legend item text.
- *
- * @function Highcharts.Legend#setText
- * @param {Highcharts.Point|Highcharts.Series} item
- * The item for which to update the text in the legend.
- */
- Legend.prototype.setText = function (item) {
- var options = this.options;
- item.legendItem.attr({
- text: options.labelFormat ?
- format(options.labelFormat, item, this.chart) :
- options.labelFormatter.call(item)
- });
- };
- /**
- * Render a single specific legend item. Called internally from the `render`
- * function.
- *
- * @private
- * @function Highcharts.Legend#renderItem
- * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item
- * The item to render.
- */
- Legend.prototype.renderItem = function (item) {
- var legend = this, chart = legend.chart, renderer = chart.renderer, options = legend.options, horizontal = options.layout === 'horizontal', symbolWidth = legend.symbolWidth, symbolPadding = options.symbolPadding, 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 ?
- item.series :
- item, seriesOptions = series.options, showCheckbox = legend.createCheckboxForItem &&
- seriesOptions &&
- seriesOptions.showCheckbox,
- // full width minus text width
- itemExtraWidth = symbolWidth + symbolPadding +
- itemDistance + (showCheckbox ? 20 : 0), useHTML = options.useHTML, itemClassName = item.options.className;
- if (!li) { // generate it once, later move it
- // Generate the group box, a group to hold the symbol and text. Text
- // is to be appended in Legend class.
- item.legendGroup = renderer
- .g('legend-item')
- .addClass('highcharts-' + series.type + '-series ' +
- 'highcharts-color-' + item.colorIndex +
- (itemClassName ? ' ' + itemClassName : '') +
- (isSeries ?
- ' highcharts-series-' + item.index :
- ''))
- .attr({ zIndex: 1 })
- .add(legend.scrollGroup);
- // Generate the list item text and add it to the group
- item.legendItem = li = renderer.text('', ltr ?
- symbolWidth + symbolPadding :
- -symbolPadding, legend.baseline || 0, useHTML);
- if (!chart.styledMode) {
- // merge to prevent modifying original (#1021)
- li.css(merge(item.visible ?
- itemStyle :
- itemHiddenStyle));
- }
- li
- .attr({
- align: ltr ? 'left' : 'right',
- zIndex: 2
- })
- .add(item.legendGroup);
- // Get the baseline for the first item - the font size is equal for
- // all
- if (!legend.baseline) {
- legend.fontMetrics = renderer.fontMetrics(chart.styledMode ? 12 : itemStyle.fontSize, li);
- legend.baseline =
- legend.fontMetrics.f + 3 + legend.itemMarginTop;
- li.attr('y', legend.baseline);
- }
- // Draw the legend symbol inside the group box
- legend.symbolHeight =
- options.symbolHeight || legend.fontMetrics.f;
- series.drawLegendSymbol(legend, item);
- if (legend.setItemEvents) {
- legend.setItemEvents(item, li, useHTML);
- }
- }
- // Add the HTML checkbox on top
- if (showCheckbox && !item.checkbox && legend.createCheckboxForItem) {
- legend.createCheckboxForItem(item);
- }
- // Colorize the items
- legend.colorizeItem(item, item.visible);
- // Take care of max width and text overflow (#6659)
- if (chart.styledMode || !itemStyle.width) {
- li.css({
- width: ((options.itemWidth ||
- legend.widthOption ||
- chart.spacingBox.width) - itemExtraWidth) + 'px'
- });
- }
- // Always update the text
- legend.setText(item);
- // calculate the positions for the next line
- bBox = li.getBBox();
- item.itemWidth = item.checkboxOffset =
- options.itemWidth ||
- item.legendItemWidth ||
- bBox.width + itemExtraWidth;
- legend.maxItemWidth = Math.max(legend.maxItemWidth, item.itemWidth);
- legend.totalItemWidth += item.itemWidth;
- legend.itemHeight = item.itemHeight = Math.round(item.legendItemHeight || bBox.height || legend.symbolHeight);
- };
- /**
- * Get the position of the item in the layout. We now know the
- * maxItemWidth from the previous loop.
- *
- * @private
- * @function Highcharts.Legend#layoutItem
- * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item
- */
- Legend.prototype.layoutItem = function (item) {
- 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 &&
- this.totalItemWidth > maxLegendWidth) ?
- this.maxItemWidth :
- item.itemWidth;
- // If the item exceeds the width, start a new line
- if (horizontal &&
- this.itemX - padding + itemWidth > maxLegendWidth) {
- this.itemX = padding;
- if (this.lastLineHeight) { // Not for the first line (#10167)
- this.itemY += (itemMarginTop +
- this.lastLineHeight +
- itemMarginBottom);
- }
- this.lastLineHeight = 0; // reset for next line (#915, #3976)
- }
- // Set the edge positions
- this.lastItemY = itemMarginTop + this.itemY + itemMarginBottom;
- this.lastLineHeight = Math.max(// #915
- itemHeight, this.lastLineHeight);
- // cache the position of the newly generated or reordered items
- item._legendItemPos = [this.itemX, this.itemY];
- // advance
- if (horizontal) {
- this.itemX += itemWidth;
- }
- else {
- this.itemY +=
- itemMarginTop + itemHeight + itemMarginBottom;
- this.lastLineHeight = itemHeight;
- }
- // the width of the widest item
- this.offsetWidth = this.widthOption || Math.max((horizontal ? this.itemX - padding - (item.checkbox ?
- // decrease by itemDistance only when no checkbox #4853
- 0 :
- itemDistance) : itemWidth) + padding, this.offsetWidth);
- };
- /**
- * Get all items, which is one item per series for most series and one
- * item per point for pie series and its derivatives. Fires the event
- * `afterGetAllItems`.
- *
- * @private
- * @function Highcharts.Legend#getAllItems
- * @return {Array<(Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series)>}
- * The current items in the legend.
- * @fires Highcharts.Legend#event:afterGetAllItems
- */
- Legend.prototype.getAllItems = function () {
- var allItems = [];
- this.chart.series.forEach(function (series) {
- var seriesOptions = series && series.options;
- // Handle showInLegend. If the series is linked to another series,
- // defaults to false.
- if (series && pick(seriesOptions.showInLegend, !defined(seriesOptions.linkedTo) ? void 0 : false, true)) {
- // Use points or series for the legend item depending on
- // legendType
- allItems = allItems.concat(series.legendItems ||
- (seriesOptions.legendType === 'point' ?
- series.data :
- series));
- }
- });
- fireEvent(this, 'afterGetAllItems', { allItems: allItems });
- return allItems;
- };
- /**
- * Get a short, three letter string reflecting the alignment and layout.
- *
- * @private
- * @function Highcharts.Legend#getAlignment
- * @return {string}
- * The alignment, empty string if floating
- */
- Legend.prototype.getAlignment = function () {
- var options = this.options;
- // Use the first letter of each alignment option in order to detect
- // the side. (#4189 - use charAt(x) notation instead of [x] for IE7)
- if (this.proximate) {
- return options.align.charAt(0) + 'tv';
- }
- return options.floating ? '' : (options.align.charAt(0) +
- options.verticalAlign.charAt(0) +
- options.layout.charAt(0));
- };
- /**
- * Adjust the chart margins by reserving space for the legend on only one
- * side of the chart. If the position is set to a corner, top or bottom is
- * reserved for horizontal legends and left or right for vertical ones.
- *
- * @private
- * @function Highcharts.Legend#adjustMargins
- * @param {Array<number>} margin
- * @param {Array<number>} spacing
- */
- Legend.prototype.adjustMargins = function (margin, spacing) {
- var chart = this.chart, options = this.options, alignment = this.getAlignment();
- if (alignment) {
- ([
- /(lth|ct|rth)/,
- /(rtv|rm|rbv)/,
- /(rbh|cb|lbh)/,
- /(lbv|lm|ltv)/
- ]).forEach(function (alignments, side) {
- if (alignments.test(alignment) && !defined(margin[side])) {
- // Now we have detected on which side of the chart we should
- // reserve space for the legend
- chart[marginNames[side]] = Math.max(chart[marginNames[side]], (chart.legend[(side + 1) % 2 ? 'legendHeight' : 'legendWidth'] +
- [1, -1, -1, 1][side] * options[(side % 2) ? 'x' : 'y'] +
- pick(options.margin, 12) +
- spacing[side] +
- (chart.titleOffset[side] || 0)));
- }
- });
- }
- };
- /**
- * @private
- * @function Highcharts.Legend#proximatePositions
- */
- Legend.prototype.proximatePositions = function () {
- var chart = this.chart, boxes = [], alignLeft = this.options.align === 'left';
- this.allItems.forEach(function (item) {
- var lastPoint, height, useFirstPoint = alignLeft, target, top;
- if (item.yAxis) {
- if (item.xAxis.options.reversed) {
- useFirstPoint = !useFirstPoint;
- }
- if (item.points) {
- lastPoint = find(useFirstPoint ?
- item.points :
- item.points.slice(0).reverse(), function (item) {
- return isNumber(item.plotY);
- });
- }
- height = this.itemMarginTop +
- item.legendItem.getBBox().height +
- this.itemMarginBottom;
- top = item.yAxis.top - chart.plotTop;
- if (item.visible) {
- target = lastPoint ?
- lastPoint.plotY :
- item.yAxis.height;
- target += top - 0.3 * height;
- }
- else {
- target = top + item.yAxis.height;
- }
- boxes.push({
- target: target,
- size: height,
- item: item
- });
- }
- }, this);
- H.distribute(boxes, chart.plotHeight);
- boxes.forEach(function (box) {
- box.item._legendItemPos[1] =
- chart.plotTop - chart.spacing[0] + box.pos;
- });
- };
- /**
- * Render the legend. This method can be called both before and after
- * `chart.render`. If called after, it will only rearrange items instead
- * of creating new ones. Called internally on initial render and after
- * redraws.
- *
- * @private
- * @function Highcharts.Legend#render
- */
- Legend.prototype.render = function () {
- 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;
- legend.itemX = padding;
- legend.itemY = legend.initialItemY;
- legend.offsetWidth = 0;
- legend.lastItemY = 0;
- legend.widthOption = relativeLength(options.width, chart.spacingBox.width - padding);
- // Compute how wide the legend is allowed to be
- allowedWidth =
- chart.spacingBox.width - 2 * padding - options.x;
- if (['rm', 'lm'].indexOf(legend.getAlignment().substring(0, 2)) > -1) {
- allowedWidth /= 2;
- }
- legend.maxLegendWidth = legend.widthOption || allowedWidth;
- if (!legendGroup) {
- /**
- * SVG group of the legend.
- *
- * @readonly
- * @name Highcharts.Legend#group
- * @type {Highcharts.SVGElement}
- */
- legend.group = legendGroup = renderer.g('legend')
- .attr({ zIndex: 7 })
- .add();
- legend.contentGroup = renderer.g()
- .attr({ zIndex: 1 }) // above background
- .add(legendGroup);
- legend.scrollGroup = renderer.g()
- .add(legend.contentGroup);
- }
- legend.renderTitle();
- // add each series or point
- allItems = legend.getAllItems();
- // sort by legendIndex
- stableSort(allItems, function (a, b) {
- return ((a.options && a.options.legendIndex) || 0) -
- ((b.options && b.options.legendIndex) || 0);
- });
- // reversed legend
- if (options.reversed) {
- allItems.reverse();
- }
- /**
- * All items for the legend, which is an array of series for most series
- * and an array of points for pie series and its derivatives.
- *
- * @readonly
- * @name Highcharts.Legend#allItems
- * @type {Array<(Highcharts.Point|Highcharts.Series)>}
- */
- legend.allItems = allItems;
- legend.display = display = !!allItems.length;
- // Render the items. First we run a loop to set the text and properties
- // and read all the bounding boxes. The next loop computes the item
- // positions based on the bounding boxes.
- legend.lastLineHeight = 0;
- legend.maxItemWidth = 0;
- legend.totalItemWidth = 0;
- legend.itemHeight = 0;
- allItems.forEach(legend.renderItem, legend);
- allItems.forEach(legend.layoutItem, legend);
- // Get the box
- legendWidth = (legend.widthOption || legend.offsetWidth) + padding;
- legendHeight = legend.lastItemY + legend.lastLineHeight +
- legend.titleHeight;
- legendHeight = legend.handleOverflow(legendHeight);
- legendHeight += padding;
- // Draw the border and/or background
- if (!box) {
- /**
- * SVG element of the legend box.
- *
- * @readonly
- * @name Highcharts.Legend#box
- * @type {Highcharts.SVGElement}
- */
- legend.box = box = renderer.rect()
- .addClass('highcharts-legend-box')
- .attr({
- r: options.borderRadius
- })
- .add(legendGroup);
- box.isNew = true;
- }
- // Presentational
- if (!chart.styledMode) {
- box
- .attr({
- stroke: options.borderColor,
- 'stroke-width': options.borderWidth || 0,
- fill: options.backgroundColor || 'none'
- })
- .shadow(options.shadow);
- }
- if (legendWidth > 0 && legendHeight > 0) {
- box[box.isNew ? 'attr' : 'animate'](box.crisp.call({}, {
- x: 0,
- y: 0,
- width: legendWidth,
- height: legendHeight
- }, box.strokeWidth()));
- box.isNew = false;
- }
- // hide the border if no items
- box[display ? 'show' : 'hide']();
- // Open for responsiveness
- if (chart.styledMode && legendGroup.getStyle('display') === 'none') {
- legendWidth = legendHeight = 0;
- }
- legend.legendWidth = legendWidth;
- legend.legendHeight = legendHeight;
- if (display) {
- legend.align();
- }
- if (!this.proximate) {
- this.positionItems();
- }
- fireEvent(this, 'afterRender');
- };
- /**
- * Align the legend to chart's box.
- *
- * @private
- * @function Highcharts.align
- * @param {Highcharts.BBoxObject} alignTo
- * @return {void}
- */
- Legend.prototype.align = function (alignTo) {
- if (alignTo === void 0) { alignTo = this.chart.spacingBox; }
- var chart = this.chart, options = this.options;
- // If aligning to the top and the layout is horizontal, adjust for
- // the title (#7428)
- var y = alignTo.y;
- if (/(lth|ct|rth)/.test(this.getAlignment()) &&
- chart.titleOffset[0] > 0) {
- y += chart.titleOffset[0];
- }
- else if (/(lbh|cb|rbh)/.test(this.getAlignment()) &&
- chart.titleOffset[2] > 0) {
- y -= chart.titleOffset[2];
- }
- if (y !== alignTo.y) {
- alignTo = merge(alignTo, { y: y });
- }
- this.group.align(merge(options, {
- width: this.legendWidth,
- height: this.legendHeight,
- verticalAlign: this.proximate ? 'top' : options.verticalAlign
- }), true, alignTo);
- };
- /**
- * Set up the overflow handling by adding navigation with up and down arrows
- * below the legend.
- *
- * @private
- * @function Highcharts.Legend#handleOverflow
- * @param {number} legendHeight
- * @return {number}
- */
- Legend.prototype.handleOverflow = function (legendHeight) {
- 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 +
- (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) {
- if (typeof height === 'number') {
- clipRect.attr({
- height: height
- });
- }
- else if (clipRect) { // Reset (#5912)
- legend.clipRect = clipRect.destroy();
- legend.contentGroup.clip();
- }
- // useHTML
- if (legend.contentGroup.div) {
- legend.contentGroup.div.style.clip = height ?
- 'rect(' + padding + 'px,9999px,' +
- (padding + height) + 'px,0)' :
- 'auto';
- }
- }, addTracker = function (key) {
- legend[key] = renderer
- .circle(0, 0, arrowSize * 1.3)
- .translate(arrowSize / 2, arrowSize / 2)
- .add(nav);
- if (!chart.styledMode) {
- legend[key].attr('fill', 'rgba(0,0,0,0.0001)');
- }
- return legend[key];
- };
- // Adjust the height
- if (options.layout === 'horizontal' &&
- options.verticalAlign !== 'middle' &&
- !options.floating) {
- spaceHeight /= 2;
- }
- if (maxHeight) {
- spaceHeight = Math.min(spaceHeight, maxHeight);
- }
- // Reset the legend height and adjust the clipping rectangle
- pages.length = 0;
- if (legendHeight > spaceHeight &&
- navOptions.enabled !== false) {
- this.clipHeight = clipHeight =
- Math.max(spaceHeight - 20 - this.titleHeight - padding, 0);
- this.currentPage = pick(this.currentPage, 1);
- this.fullHeight = legendHeight;
- // Fill pages with Y positions so that the top of each a legend item
- // defines the scroll top for each page (#2098)
- allItems.forEach(function (item, i) {
- var y = item._legendItemPos[1], h = Math.round(item.legendItem.getBBox().height), len = pages.length;
- if (!len || (y - pages[len - 1] > clipHeight &&
- (lastY || y) !== pages[len - 1])) {
- pages.push(lastY || y);
- len++;
- }
- // Keep track of which page each item is on
- item.pageIx = len - 1;
- if (lastY) {
- allItems[i - 1].pageIx = len - 1;
- }
- if (i === allItems.length - 1 &&
- y + h - pages[len - 1] > clipHeight &&
- y !== lastY // #2617
- ) {
- pages.push(y);
- item.pageIx = len;
- }
- if (y !== lastY) {
- lastY = y;
- }
- });
- // Only apply clipping if needed. Clipping causes blurred legend in
- // PDF export (#1787)
- if (!clipRect) {
- clipRect = legend.clipRect =
- renderer.clipRect(0, padding, 9999, 0);
- legend.contentGroup.clip(clipRect);
- }
- clipToHeight(clipHeight);
- // Add navigation elements
- if (!nav) {
- this.nav = nav = renderer.g()
- .attr({ zIndex: 1 })
- .add(this.group);
- this.up = renderer
- .symbol('triangle', 0, 0, arrowSize, arrowSize)
- .add(nav);
- addTracker('upTracker')
- .on('click', function () {
- legend.scroll(-1, animation);
- });
- this.pager = renderer.text('', 15, 10)
- .addClass('highcharts-legend-navigation');
- if (!chart.styledMode) {
- this.pager.css(navOptions.style);
- }
- this.pager.add(nav);
- this.down = renderer
- .symbol('triangle-down', 0, 0, arrowSize, arrowSize)
- .add(nav);
- addTracker('downTracker')
- .on('click', function () {
- legend.scroll(1, animation);
- });
- }
- // Set initial position
- legend.scroll(0);
- legendHeight = spaceHeight;
- // Reset
- }
- else if (nav) {
- clipToHeight();
- this.nav = nav.destroy(); // #6322
- this.scrollGroup.attr({
- translateY: 1
- });
- this.clipHeight = 0; // #1379
- }
- return legendHeight;
- };
- /**
- * Scroll the legend by a number of pages.
- *
- * @private
- * @function Highcharts.Legend#scroll
- *
- * @param {number} scrollBy
- * The number of pages to scroll.
- *
- * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
- * Whether and how to apply animation.
- *
- * @return {void}
- */
- Legend.prototype.scroll = function (scrollBy, animation) {
- var _this = this;
- 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;
- // When resizing while looking at the last page
- if (currentPage > pageCount) {
- currentPage = pageCount;
- }
- if (currentPage > 0) {
- if (typeof animation !== 'undefined') {
- setAnimation(animation, chart);
- }
- this.nav.attr({
- translateX: padding,
- translateY: clipHeight + this.padding + 7 + this.titleHeight,
- visibility: 'visible'
- });
- [this.up, this.upTracker].forEach(function (elem) {
- elem.attr({
- 'class': currentPage === 1 ?
- 'highcharts-legend-nav-inactive' :
- 'highcharts-legend-nav-active'
- });
- });
- pager.attr({
- text: currentPage + '/' + pageCount
- });
- [this.down, this.downTracker].forEach(function (elem) {
- elem.attr({
- // adjust to text width
- x: 18 + this.pager.getBBox().width,
- 'class': currentPage === pageCount ?
- 'highcharts-legend-nav-inactive' :
- 'highcharts-legend-nav-active'
- });
- }, this);
- if (!chart.styledMode) {
- this.up
- .attr({
- fill: currentPage === 1 ?
- navOptions.inactiveColor :
- navOptions.activeColor
- });
- this.upTracker
- .css({
- cursor: currentPage === 1 ? 'default' : 'pointer'
- });
- this.down
- .attr({
- fill: currentPage === pageCount ?
- navOptions.inactiveColor :
- navOptions.activeColor
- });
- this.downTracker
- .css({
- cursor: currentPage === pageCount ?
- 'default' :
- 'pointer'
- });
- }
- this.scrollOffset = -pages[currentPage - 1] + this.initialItemY;
- this.scrollGroup.animate({
- translateY: this.scrollOffset
- });
- this.currentPage = currentPage;
- this.positionCheckboxes();
- // Fire event after scroll animation is complete
- var animOptions = animObject(pick(animation, chart.renderer.globalAnimation, true));
- syncTimeout(function () {
- fireEvent(_this, 'afterScroll', { currentPage: currentPage });
- }, animOptions.duration);
- }
- };
- return Legend;
- }());
- // Workaround for #2030, horizontal legend items not displaying in IE11 Preview,
- // and for #2580, a similar drawing flaw in Firefox 26.
- // Explore if there's a general cause for this. The problem may be related
- // to nested group elements, as the legend item texts are within 4 group
- // elements.
- if (/Trident\/7\.0/.test(win.navigator && win.navigator.userAgent) ||
- isFirefox) {
- wrap(Legend.prototype, 'positionItem', function (proceed, item) {
- var legend = this,
- // If chart destroyed in sync, this is undefined (#2030)
- runPositionItem = function () {
- if (item._legendItemPos) {
- proceed.call(legend, item);
- }
- };
- // Do it now, for export and to get checkbox placement
- runPositionItem();
- // Do it after to work around the core issue
- if (!legend.bubbleLegend) {
- setTimeout(runPositionItem);
- }
- });
- }
- H.Legend = Legend;
- export default H.Legend;
|