Files
T-DAS/Epost.TestToolsWeb/Content/code/es-modules/parts-more/BubbleLegend.js

1170 lines
37 KiB
JavaScript
Raw Permalink Normal View History

2023-01-13 15:30:20 +08:00
/* *
*
* (c) 2010-2018 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 ? true : false;
}
// 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());
}
}
});