123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167 |
- /* *
- * (c) 2010-2019 Highsoft AS
- *
- * Author: Paweł Potaczek
- *
- * License: www.highcharts.com/license
- */
- /**
- * @interface Highcharts.LegendBubbleLegendFormatterContextObject
- *//**
- * The center y position of the range.
- * @name Highcharts.LegendBubbleLegendFormatterContextObject#center
- * @type {number}
- *//**
- * The radius of the bubble range.
- * @name Highcharts.LegendBubbleLegendFormatterContextObject#radius
- * @type {number}
- *//**
- * The bubble value.
- * @name Highcharts.LegendBubbleLegendFormatterContextObject#value
- * @type {number}
- */
- 'use strict';
- import H from '../parts/Globals.js';
- var Series = H.Series,
- Legend = H.Legend,
- Chart = H.Chart,
- addEvent = H.addEvent,
- wrap = H.wrap,
- color = H.color,
- isNumber = H.isNumber,
- numberFormat = H.numberFormat,
- objectEach = H.objectEach,
- merge = H.merge,
- noop = H.noop,
- pick = H.pick,
- stableSort = H.stableSort,
- setOptions = H.setOptions,
- arrayMin = H.arrayMin,
- arrayMax = H.arrayMax;
- setOptions({ // Set default bubble legend options
- legend: {
- /**
- * The bubble legend is an additional element in legend which presents
- * the scale of the bubble series. Individual bubble ranges can be
- * defined by user or calculated from series. In the case of
- * automatically calculated ranges, a 1px margin of error is permitted.
- * Requires `highcharts-more.js`.
- *
- * @since 7.0.0
- * @product highcharts highstock highmaps
- * @optionparent legend.bubbleLegend
- */
- bubbleLegend: {
- /**
- * The color of the ranges borders, can be also defined for an
- * individual range.
- *
- * @sample highcharts/bubble-legend/similartoseries/
- * Similat look to the bubble series
- * @sample highcharts/bubble-legend/bordercolor/
- * Individual bubble border color
- *
- * @type {Highcharts.ColorString}
- */
- borderColor: undefined,
- /**
- * The width of the ranges borders in pixels, can be also defined
- * for an individual range.
- */
- borderWidth: 2,
- /**
- * An additional class name to apply to the bubble legend' circle
- * graphical elements. This option does not replace default class
- * names of the graphical element.
- *
- * @sample {highcharts} highcharts/css/bubble-legend/
- * Styling by CSS
- *
- * @type {string}
- */
- className: undefined,
- /**
- * The main color of the bubble legend. Applies to ranges, if
- * individual color is not defined.
- *
- * @sample highcharts/bubble-legend/similartoseries/
- * Similat look to the bubble series
- * @sample highcharts/bubble-legend/color/
- * Individual bubble color
- *
- * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
- */
- color: undefined,
- /**
- * An additional class name to apply to the bubble legend's
- * connector graphical elements. This option does not replace
- * default class names of the graphical element.
- *
- * @sample {highcharts} highcharts/css/bubble-legend/
- * Styling by CSS
- *
- * @type {string}
- */
- connectorClassName: undefined,
- /**
- * The color of the connector, can be also defined
- * for an individual range.
- *
- * @type {Highcharts.ColorString}
- */
- connectorColor: undefined,
- /**
- * The length of the connectors in pixels. If labels are centered,
- * the distance is reduced to 0.
- *
- * @sample highcharts/bubble-legend/connectorandlabels/
- * Increased connector length
- */
- connectorDistance: 60,
- /**
- * The width of the connectors in pixels.
- *
- * @sample highcharts/bubble-legend/connectorandlabels/
- * Increased connector width
- */
- connectorWidth: 1,
- /**
- * Enable or disable the bubble legend.
- */
- enabled: false,
- /**
- * Options for the bubble legend labels.
- */
- labels: {
- /**
- * An additional class name to apply to the bubble legend
- * label graphical elements. This option does not replace
- * default class names of the graphical element.
- *
- * @sample {highcharts} highcharts/css/bubble-legend/
- * Styling by CSS
- *
- * @type {string}
- */
- className: undefined,
- /**
- * Whether to allow data labels to overlap.
- */
- allowOverlap: false,
- /**
- * A [format string](http://docs.highcharts.com/#formatting)
- * for the bubble legend labels. Available variables are the
- * same as for `formatter`.
- *
- * @sample highcharts/bubble-legend/format/
- * Add a unit
- *
- * @type {string}
- */
- format: '',
- /**
- * Available `this` properties are:
- *
- * - `this.value`: The bubble value.
- *
- * - `this.radius`: The radius of the bubble range.
- *
- * - `this.center`: The center y position of the range.
- *
- * @type {Highcharts.FormatterCallbackFunction<Highcharts.LegendBubbleLegendFormatterContextObject>}
- */
- formatter: undefined,
- /**
- * The alignment of the labels compared to the bubble legend.
- * Can be one of `left`, `center` or `right`.
- * @validvalue ["left", "center", "right"]
- *
- * @sample highcharts/bubble-legend/connectorandlabels/
- * Labels on left
- *
- * @validvalue ["left", "center", "right"]
- */
- align: 'right',
- /**
- * CSS styles for the labels.
- *
- * @type {Highcharts.CSSObject}
- */
- style: {
- /** @ignore-option */
- fontSize: 10,
- /** @ignore-option */
- color: undefined
- },
- /**
- * The x position offset of the label relative to the
- * connector.
- */
- x: 0,
- /**
- * The y position offset of the label relative to the
- * connector.
- */
- y: 0
- },
- /**
- * Miximum bubble legend range size. If values for ranges are not
- * specified, the `minSize` and the `maxSize` are calculated from
- * bubble series.
- */
- maxSize: 60, // Number
- /**
- * Minimum bubble legend range size. If values for ranges are not
- * specified, the `minSize` and the `maxSize` are calculated from
- * bubble series.
- */
- minSize: 10, // Number
- /**
- * The position of the bubble legend in the legend.
- * @sample highcharts/bubble-legend/connectorandlabels/
- * Bubble legend as last item in legend
- */
- legendIndex: 0, // Number
- /**
- * Options for specific range. One range consists of bubble, label
- * and connector.
- *
- * @sample highcharts/bubble-legend/ranges/
- * Manually defined ranges
- * @sample highcharts/bubble-legend/autoranges/
- * Auto calculated ranges
- *
- * @type {Array<*>}
- */
- ranges: {
- /**
- * Range size value, similar to bubble Z data.
- */
- value: undefined,
- /**
- * The color of the border for individual range.
- * @type {Highcharts.ColorString}
- */
- borderColor: undefined,
- /**
- * The color of the bubble for individual range.
- * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
- */
- color: undefined,
- /**
- * The color of the connector for individual range.
- * @type {Highcharts.ColorString}
- */
- connectorColor: undefined
- },
- /**
- * Whether the bubble legend range value should be represented by
- * the area or the width of the bubble. The default, area,
- * corresponds best to the human perception of the size of each
- * bubble.
- *
- * @sample highcharts/bubble-legend/ranges/
- * Size by width
- *
- * @validvalue ["area", "width"]
- */
- sizeBy: 'area',
- /**
- * When this is true, the absolute value of z determines the size of
- * the bubble. This means that with the default zThreshold of 0, a
- * bubble of value -1 will have the same size as a bubble of value
- * 1, while a bubble of value 0 will have a smaller size according
- * to minSize.
- */
- sizeByAbsoluteValue: false,
- /**
- * Define the visual z index of the bubble legend.
- */
- zIndex: 1,
- /**
- * Ranges with with lower value than zThreshold, are skipped.
- */
- zThreshold: 0
- }
- }
- });
- /**
- * BubbleLegend class.
- *
- * @private
- * @class
- * @name Highcharts.BubbleLegend
- *
- * @param {Highcharts.LegendBubbleLegendOptions} config
- * Bubble legend options
- *
- * @param {Highcharts.LegendOptions} config
- * Legend options
- */
- H.BubbleLegend = function (options, legend) {
- this.init(options, legend);
- };
- H.BubbleLegend.prototype = {
- /**
- * Create basic bubbleLegend properties similar to item in legend.
- *
- * @private
- * @function Highcharts.BubbleLegend#init
- *
- * @param {Highcharts.LegendBubbleLegendOptions} config
- * Bubble legend options
- *
- * @param {Highcharts.LegendOptions} config
- * Legend options
- */
- init: function (options, legend) {
- this.options = options;
- this.visible = true;
- this.chart = legend.chart;
- this.legend = legend;
- },
- setState: noop,
- /**
- * Depending on the position option, add bubbleLegend to legend items.
- *
- * @private
- * @function Highcharts.BubbleLegend#addToLegend
- *
- * @param {Array<*>}
- * All legend items
- */
- addToLegend: function (items) {
- // Insert bubbleLegend into legend items
- items.splice(this.options.legendIndex, 0, this);
- },
- /**
- * Calculate ranges, sizes and call the next steps of bubbleLegend creation.
- *
- * @private
- * @function Highcharts.BubbleLegend#drawLegendSymbol
- *
- * @param {Highcharts.Legend} legend
- * Legend instance
- */
- drawLegendSymbol: function (legend) {
- var bubbleLegend = this,
- chart = bubbleLegend.chart,
- options = bubbleLegend.options,
- size,
- itemDistance = pick(legend.options.itemDistance, 20),
- connectorSpace,
- ranges = options.ranges,
- radius,
- maxLabel,
- connectorDistance = options.connectorDistance;
- // Predict label dimensions
- bubbleLegend.fontMetrics = chart.renderer.fontMetrics(
- options.labels.style.fontSize.toString() + 'px'
- );
- // Do not create bubbleLegend now if ranges or ranges valeus are not
- // specified or if are empty array.
- if (!ranges || !ranges.length || !isNumber(ranges[0].value)) {
- legend.options.bubbleLegend.autoRanges = true;
- return;
- }
- // Sort ranges to right render order
- stableSort(ranges, function (a, b) {
- return b.value - a.value;
- });
- bubbleLegend.ranges = ranges;
- bubbleLegend.setOptions();
- bubbleLegend.render();
- // Get max label size
- maxLabel = bubbleLegend.getMaxLabelSize();
- radius = bubbleLegend.ranges[0].radius;
- size = radius * 2;
- // Space for connectors and labels.
- connectorSpace = connectorDistance - radius + maxLabel.width;
- connectorSpace = connectorSpace > 0 ? connectorSpace : 0;
- bubbleLegend.maxLabel = maxLabel;
- bubbleLegend.movementX = options.labels.align === 'left' ?
- connectorSpace : 0;
- bubbleLegend.legendItemWidth = size + connectorSpace + itemDistance;
- bubbleLegend.legendItemHeight = size + bubbleLegend.fontMetrics.h / 2;
- },
- /**
- * Set style options for each bubbleLegend range.
- *
- * @private
- * @function Highcharts.BubbleLegend#setOptions
- */
- setOptions: function () {
- var bubbleLegend = this,
- ranges = bubbleLegend.ranges,
- options = bubbleLegend.options,
- series = bubbleLegend.chart.series[options.seriesIndex],
- baseline = bubbleLegend.legend.baseline,
- bubbleStyle = {
- 'z-index': options.zIndex,
- 'stroke-width': options.borderWidth
- },
- connectorStyle = {
- 'z-index': options.zIndex,
- 'stroke-width': options.connectorWidth
- },
- labelStyle = bubbleLegend.getLabelStyles(),
- fillOpacity = series.options.marker.fillOpacity,
- styledMode = bubbleLegend.chart.styledMode;
- // Allow to parts of styles be used individually for range
- ranges.forEach(function (range, i) {
- if (!styledMode) {
- bubbleStyle.stroke = pick(
- range.borderColor,
- options.borderColor,
- series.color
- );
- bubbleStyle.fill = pick(
- range.color,
- options.color,
- fillOpacity !== 1 ?
- color(series.color).setOpacity(fillOpacity)
- .get('rgba') :
- series.color
- );
- connectorStyle.stroke = pick(
- range.connectorColor,
- options.connectorColor,
- series.color
- );
- }
- // Set options needed for rendering each range
- ranges[i].radius = bubbleLegend.getRangeRadius(range.value);
- ranges[i] = merge(ranges[i], {
- center: ranges[0].radius - ranges[i].radius + baseline
- });
- if (!styledMode) {
- merge(true, ranges[i], {
- bubbleStyle: merge(false, bubbleStyle),
- connectorStyle: merge(false, connectorStyle),
- labelStyle: labelStyle
- });
- }
- });
- },
- /**
- * Merge options for bubbleLegend labels.
- *
- * @private
- * @function Highcharts.BubbleLegend#getLabelStyles
- */
- getLabelStyles: function () {
- var options = this.options,
- additionalLabelsStyle = {},
- labelsOnLeft = options.labels.align === 'left',
- rtl = this.legend.options.rtl;
- // To separate additional style options
- objectEach(options.labels.style, function (value, key) {
- if (key !== 'color' && key !== 'fontSize' && key !== 'z-index') {
- additionalLabelsStyle[key] = value;
- }
- });
- return merge(false, additionalLabelsStyle, {
- 'font-size': options.labels.style.fontSize,
- fill: pick(
- options.labels.style.color,
- '#000000'
- ),
- 'z-index': options.zIndex,
- align: rtl || labelsOnLeft ? 'right' : 'left'
- });
- },
- /**
- * Calculate radius for each bubble range,
- * used code from BubbleSeries.js 'getRadius' method.
- *
- * @private
- * @function Highcharts.BubbleLegend#getRangeRadius
- *
- * @param {number} value
- * Range value
- *
- * @return {number}
- * Radius for one range
- */
- getRangeRadius: function (value) {
- var bubbleLegend = this,
- options = bubbleLegend.options,
- seriesIndex = bubbleLegend.options.seriesIndex,
- bubbleSeries = bubbleLegend.chart.series[seriesIndex],
- zMax = options.ranges[0].value,
- zMin = options.ranges[options.ranges.length - 1].value,
- minSize = options.minSize,
- maxSize = options.maxSize;
- return bubbleSeries.getRadius.call(
- this,
- zMin,
- zMax,
- minSize,
- maxSize,
- value
- );
- },
- /**
- * Render the legendSymbol group.
- *
- * @private
- * @function Highcharts.BubbleLegend#render
- */
- render: function () {
- var bubbleLegend = this,
- renderer = bubbleLegend.chart.renderer,
- zThreshold = bubbleLegend.options.zThreshold;
- if (!bubbleLegend.symbols) {
- bubbleLegend.symbols = {
- connectors: [],
- bubbleItems: [],
- labels: []
- };
- }
- // Nesting SVG groups to enable handleOverflow
- bubbleLegend.legendSymbol = renderer.g('bubble-legend');
- bubbleLegend.legendItem = renderer.g('bubble-legend-item');
- // To enable default 'hideOverlappingLabels' method
- bubbleLegend.legendSymbol.translateX = 0;
- bubbleLegend.legendSymbol.translateY = 0;
- bubbleLegend.ranges.forEach(function (range) {
- if (range.value >= zThreshold) {
- bubbleLegend.renderRange(range);
- }
- });
- // To use handleOverflow method
- bubbleLegend.legendSymbol.add(bubbleLegend.legendItem);
- bubbleLegend.legendItem.add(bubbleLegend.legendGroup);
- bubbleLegend.hideOverlappingLabels();
- },
- /**
- * Render one range, consisting of bubble symbol, connector and label.
- *
- * @private
- * @function Highcharts.BubbleLegend#renderRange
- *
- * @param {Highcharts.LegendBubbleLegendRangesOptions} config
- * Range options
- *
- * @private
- */
- renderRange: function (range) {
- var bubbleLegend = this,
- mainRange = bubbleLegend.ranges[0],
- legend = bubbleLegend.legend,
- options = bubbleLegend.options,
- labelsOptions = options.labels,
- chart = bubbleLegend.chart,
- renderer = chart.renderer,
- symbols = bubbleLegend.symbols,
- labels = symbols.labels,
- label,
- elementCenter = range.center,
- absoluteRadius = Math.abs(range.radius),
- connectorDistance = options.connectorDistance,
- labelsAlign = labelsOptions.align,
- rtl = legend.options.rtl,
- fontSize = labelsOptions.style.fontSize,
- connectorLength = rtl || labelsAlign === 'left' ?
- -connectorDistance : connectorDistance,
- borderWidth = options.borderWidth,
- connectorWidth = options.connectorWidth,
- posX = mainRange.radius,
- posY = elementCenter - absoluteRadius - borderWidth / 2 +
- connectorWidth / 2,
- labelY,
- labelX,
- fontMetrics = bubbleLegend.fontMetrics,
- labelMovement = fontSize / 2 - (fontMetrics.h - fontSize) / 2,
- crispMovement = (posY % 1 ? 1 : 0.5) -
- (connectorWidth % 2 ? 0 : 0.5),
- styledMode = renderer.styledMode;
- // Set options for centered labels
- if (labelsAlign === 'center') {
- connectorLength = 0; // do not use connector
- options.connectorDistance = 0;
- range.labelStyle.align = 'center';
- }
- labelY = posY + options.labels.y;
- labelX = posX + connectorLength + options.labels.x;
- // Render bubble symbol
- symbols.bubbleItems.push(
- renderer
- .circle(
- posX,
- elementCenter + crispMovement,
- absoluteRadius
- )
- .attr(
- styledMode ? {} : range.bubbleStyle
- )
- .addClass(
- (
- styledMode ?
- 'highcharts-color-' +
- bubbleLegend.options.seriesIndex + ' ' :
- ''
- ) +
- 'highcharts-bubble-legend-symbol ' +
- (options.className || '')
- ).add(
- bubbleLegend.legendSymbol
- )
- );
- // Render connector
- symbols.connectors.push(
- renderer
- .path(renderer.crispLine(
- ['M', posX, posY, 'L', posX + connectorLength, posY],
- options.connectorWidth
- ))
- .attr(
- styledMode ? {} : range.connectorStyle
- )
- .addClass(
- (
- styledMode ?
- 'highcharts-color-' +
- bubbleLegend.options.seriesIndex + ' ' :
- ''
- ) +
- 'highcharts-bubble-legend-connectors ' +
- (options.connectorClassName || '')
- ).add(
- bubbleLegend.legendSymbol
- )
- );
- // Render label
- label = renderer
- .text(
- bubbleLegend.formatLabel(range),
- labelX,
- labelY + labelMovement
- )
- .attr(
- styledMode ? {} : range.labelStyle
- )
- .addClass(
- 'highcharts-bubble-legend-labels ' +
- (options.labels.className || '')
- ).add(
- bubbleLegend.legendSymbol
- );
- labels.push(label);
- // To enable default 'hideOverlappingLabels' method
- label.placed = true;
- label.alignAttr = {
- x: labelX,
- y: labelY + labelMovement
- };
- },
- /**
- * Get the label which takes up the most space.
- *
- * @private
- * @function Highcharts.BubbleLegend#getMaxLabelSize
- */
- getMaxLabelSize: function () {
- var labels = this.symbols.labels,
- maxLabel,
- labelSize;
- labels.forEach(function (label) {
- labelSize = label.getBBox(true);
- if (maxLabel) {
- maxLabel = labelSize.width > maxLabel.width ?
- labelSize : maxLabel;
- } else {
- maxLabel = labelSize;
- }
- });
- return maxLabel || {};
- },
- /**
- * Get formatted label for range.
- *
- * @private
- * @function Highcharts.BubbleLegend#formatLabel
- *
- * @param {Highcharts.LegendBubbleLegendRangesOptions} range
- * Range options
- *
- * @return {string}
- * Range label text
- */
- formatLabel: function (range) {
- var options = this.options,
- formatter = options.labels.formatter,
- format = options.labels.format;
- return format ? H.format(format, range) :
- formatter ? formatter.call(range) :
- numberFormat(range.value, 1);
- },
- /**
- * By using default chart 'hideOverlappingLabels' method, hide or show
- * labels and connectors.
- *
- * @private
- * @function Highcharts.BubbleLegend#hideOverlappingLabels
- */
- hideOverlappingLabels: function () {
- var bubbleLegend = this,
- chart = this.chart,
- allowOverlap = bubbleLegend.options.labels.allowOverlap,
- symbols = bubbleLegend.symbols;
- if (!allowOverlap && symbols) {
- chart.hideOverlappingLabels(symbols.labels);
- // Hide or show connectors
- symbols.labels.forEach(function (label, index) {
- if (!label.newOpacity) {
- symbols.connectors[index].hide();
- } else if (label.newOpacity !== label.oldOpacity) {
- symbols.connectors[index].show();
- }
- });
- }
- },
- /**
- * Calculate ranges from created series.
- *
- * @private
- * @function Highcharts.BubbleLegend#getRanges
- *
- * @return {Array<Highcharts.LegendBubbleLegendRangesOptions>}
- * Array of range objects
- */
- getRanges: function () {
- var bubbleLegend = this.legend.bubbleLegend,
- series = bubbleLegend.chart.series,
- ranges,
- rangesOptions = bubbleLegend.options.ranges,
- zData,
- minZ = Number.MAX_VALUE,
- maxZ = -Number.MAX_VALUE;
- series.forEach(function (s) {
- // Find the min and max Z, like in bubble series
- if (s.isBubble && !s.ignoreSeries) {
- zData = s.zData.filter(isNumber);
- if (zData.length) {
- minZ = pick(s.options.zMin, Math.min(
- minZ,
- Math.max(
- arrayMin(zData),
- s.options.displayNegative === false ?
- s.options.zThreshold :
- -Number.MAX_VALUE
- )
- ));
- maxZ = pick(
- s.options.zMax,
- Math.max(maxZ, arrayMax(zData))
- );
- }
- }
- });
- // Set values for ranges
- if (minZ === maxZ) {
- // Only one range if min and max values are the same.
- ranges = [{ value: maxZ }];
- } else {
- ranges = [
- { value: minZ },
- { value: (minZ + maxZ) / 2 },
- { value: maxZ, autoRanges: true }
- ];
- }
- // Prevent reverse order of ranges after redraw
- if (rangesOptions.length && rangesOptions[0].radius) {
- ranges.reverse();
- }
- // Merge ranges values with user options
- ranges.forEach(function (range, i) {
- if (rangesOptions && rangesOptions[i]) {
- ranges[i] = merge(false, rangesOptions[i], range);
- }
- });
- return ranges;
- },
- /**
- * Calculate bubble legend sizes from rendered series.
- *
- * @private
- * @function Highcharts.BubbleLegend#predictBubbleSizes
- *
- * @return {Array<number,number>}
- * Calculated min and max bubble sizes
- */
- predictBubbleSizes: function () {
- var chart = this.chart,
- fontMetrics = this.fontMetrics,
- legendOptions = chart.legend.options,
- floating = legendOptions.floating,
- horizontal = legendOptions.layout === 'horizontal',
- lastLineHeight = horizontal ? chart.legend.lastLineHeight : 0,
- plotSizeX = chart.plotSizeX,
- plotSizeY = chart.plotSizeY,
- bubbleSeries = chart.series[this.options.seriesIndex],
- minSize = Math.ceil(bubbleSeries.minPxSize),
- maxPxSize = Math.ceil(bubbleSeries.maxPxSize),
- maxSize = bubbleSeries.options.maxSize,
- plotSize = Math.min(plotSizeY, plotSizeX),
- calculatedSize;
- // Calculate prediceted max size of bubble
- if (floating || !(/%$/.test(maxSize))) {
- calculatedSize = maxPxSize;
- } else {
- maxSize = parseFloat(maxSize);
- calculatedSize = ((plotSize + lastLineHeight - fontMetrics.h / 2) *
- maxSize / 100) / (maxSize / 100 + 1);
- // Get maxPxSize from bubble series if calculated bubble legend
- // size will not affect to bubbles series.
- if (
- (horizontal && plotSizeY - calculatedSize >=
- plotSizeX) || (!horizontal && plotSizeX -
- calculatedSize >= plotSizeY)
- ) {
- calculatedSize = maxPxSize;
- }
- }
- return [minSize, Math.ceil(calculatedSize)];
- },
- /**
- * Correct ranges with calculated sizes.
- *
- * @private
- * @function Highcharts.BubbleLegend#updateRanges
- *
- * @param {number} min
- *
- * @param {number} max
- */
- updateRanges: function (min, max) {
- var bubbleLegendOptions = this.legend.options.bubbleLegend;
- bubbleLegendOptions.minSize = min;
- bubbleLegendOptions.maxSize = max;
- bubbleLegendOptions.ranges = this.getRanges();
- },
- /**
- * Because of the possibility of creating another legend line, predicted
- * bubble legend sizes may differ by a few pixels, so it is necessary to
- * correct them.
- *
- * @private
- * @function Highcharts.BubbleLegend#correctSizes
- */
- correctSizes: function () {
- var legend = this.legend,
- chart = this.chart,
- bubbleSeries = chart.series[this.options.seriesIndex],
- bubbleSeriesSize = bubbleSeries.maxPxSize,
- bubbleLegendSize = this.options.maxSize;
- if (Math.abs(Math.ceil(bubbleSeriesSize) - bubbleLegendSize) > 1) {
- this.updateRanges(this.options.minSize, bubbleSeries.maxPxSize);
- legend.render();
- }
- }
- };
- // Start the bubble legend creation process.
- addEvent(H.Legend, 'afterGetAllItems', function (e) {
- var legend = this,
- bubbleLegend = legend.bubbleLegend,
- legendOptions = legend.options,
- options = legendOptions.bubbleLegend,
- bubbleSeriesIndex = legend.chart.getVisibleBubbleSeriesIndex();
- // Remove unnecessary element
- if (bubbleLegend && bubbleLegend.ranges && bubbleLegend.ranges.length) {
- // Allow change the way of calculating ranges in update
- if (options.ranges.length) {
- options.autoRanges = !!options.ranges[0].autoRanges;
- }
- // Update bubbleLegend dimensions in each redraw
- legend.destroyItem(bubbleLegend);
- }
- // Create bubble legend
- if (bubbleSeriesIndex >= 0 &&
- legendOptions.enabled &&
- options.enabled
- ) {
- options.seriesIndex = bubbleSeriesIndex;
- legend.bubbleLegend = new H.BubbleLegend(options, legend);
- legend.bubbleLegend.addToLegend(e.allItems);
- }
- });
- /**
- * Check if there is at least one visible bubble series.
- *
- * @private
- * @function Highcharts.Chart#getVisibleBubbleSeriesIndex
- *
- * @return {number}
- * First visible bubble series index
- */
- Chart.prototype.getVisibleBubbleSeriesIndex = function () {
- var series = this.series,
- i = 0;
- while (i < series.length) {
- if (
- series[i] &&
- series[i].isBubble &&
- series[i].visible &&
- series[i].zData.length
- ) {
- return i;
- }
- i++;
- }
- return -1;
- };
- /**
- * Calculate height for each row in legend.
- *
- * @private
- * @function Highcharts.Legend#getLinesHeights
- *
- * @return {Array<object>}
- * Informations about line height and items amount
- */
- Legend.prototype.getLinesHeights = function () {
- var items = this.allItems,
- lines = [],
- lastLine,
- length = items.length,
- i = 0,
- j = 0;
- for (i = 0; i < length; i++) {
- if (items[i].legendItemHeight) {
- // for bubbleLegend
- items[i].itemHeight = items[i].legendItemHeight;
- }
- if ( // Line break
- items[i] === items[length - 1] ||
- items[i + 1] &&
- items[i]._legendItemPos[1] !==
- items[i + 1]._legendItemPos[1]
- ) {
- lines.push({ height: 0 });
- lastLine = lines[lines.length - 1];
- // Find the highest item in line
- for (j; j <= i; j++) {
- if (items[j].itemHeight > lastLine.height) {
- lastLine.height = items[j].itemHeight;
- }
- }
- lastLine.step = i;
- }
- }
- return lines;
- };
- /**
- * Correct legend items translation in case of different elements heights.
- *
- * @private
- * @function Highcharts.Legend#retranslateItems
- *
- * @param {Array<object>} lines
- * Informations about line height and items amount
- */
- Legend.prototype.retranslateItems = function (lines) {
- var items = this.allItems,
- orgTranslateX,
- orgTranslateY,
- movementX,
- rtl = this.options.rtl,
- actualLine = 0;
- items.forEach(function (item, index) {
- orgTranslateX = item.legendGroup.translateX;
- orgTranslateY = item._legendItemPos[1];
- movementX = item.movementX;
- if (movementX || (rtl && item.ranges)) {
- movementX = rtl ? orgTranslateX - item.options.maxSize / 2 :
- orgTranslateX + movementX;
- item.legendGroup.attr({ translateX: movementX });
- }
- if (index > lines[actualLine].step) {
- actualLine++;
- }
- item.legendGroup.attr({
- translateY: Math.round(
- orgTranslateY + lines[actualLine].height / 2
- )
- });
- item._legendItemPos[1] = orgTranslateY + lines[actualLine].height / 2;
- });
- };
- // Hide or show bubble legend depending on the visible status of bubble series.
- addEvent(Series, 'legendItemClick', function () {
- var series = this,
- chart = series.chart,
- visible = series.visible,
- legend = series.chart.legend,
- status;
- if (legend && legend.bubbleLegend) {
- // Visible property is not set correctly yet, so temporary correct it
- series.visible = !visible;
- // Save future status for getRanges method
- series.ignoreSeries = visible;
- // Check if at lest one bubble series is visible
- status = chart.getVisibleBubbleSeriesIndex() >= 0;
- // Hide bubble legend if all bubble series are disabled
- if (legend.bubbleLegend.visible !== status) {
- // Show or hide bubble legend
- legend.update({
- bubbleLegend: { enabled: status }
- });
- legend.bubbleLegend.visible = status; // Restore default status
- }
- series.visible = visible;
- }
- });
- // If ranges are not specified, determine ranges from rendered bubble series and
- // render legend again.
- wrap(Chart.prototype, 'drawChartBox', function (proceed, options, callback) {
- var chart = this,
- legend = chart.legend,
- bubbleSeries = chart.getVisibleBubbleSeriesIndex() >= 0,
- bubbleLegendOptions,
- bubbleSizes;
- if (
- legend && legend.options.enabled && legend.bubbleLegend &&
- legend.options.bubbleLegend.autoRanges && bubbleSeries
- ) {
- bubbleLegendOptions = legend.bubbleLegend.options;
- bubbleSizes = legend.bubbleLegend.predictBubbleSizes();
- legend.bubbleLegend.updateRanges(bubbleSizes[0], bubbleSizes[1]);
- // Disable animation on init
- if (!bubbleLegendOptions.placed) {
- legend.group.placed = false;
- legend.allItems.forEach(function (item) {
- item.legendGroup.translateY = null;
- });
- }
- // Create legend with bubbleLegend
- legend.render();
- chart.getMargins();
- chart.axes.forEach(function (axis) {
- axis.render();
- if (!bubbleLegendOptions.placed) {
- axis.setScale();
- axis.updateNames();
- // Disable axis animation on init
- objectEach(axis.ticks, function (tick) {
- tick.isNew = true;
- tick.isNewLabel = true;
- });
- }
- });
- bubbleLegendOptions.placed = true;
- // After recalculate axes, calculate margins again.
- chart.getMargins();
- // Call default 'drawChartBox' method.
- proceed.call(chart, options, callback);
- // Check bubble legend sizes and correct them if necessary.
- legend.bubbleLegend.correctSizes();
- // Correct items positions with different dimensions in legend.
- legend.retranslateItems(legend.getLinesHeights());
- } else {
- proceed.call(chart, options, callback);
- if (legend && legend.options.enabled && legend.bubbleLegend) {
- // Allow color change after click in legend on static bubble legend
- legend.render();
- legend.retranslateItems(legend.getLinesHeights());
- }
- }
- });
|