| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339 |
- /* *
- *
- * (c) 2010-2018 Grzegorz Blachlinski, Sebastian Bochan
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- 'use strict';
- import Chart from '../Core/Chart/Chart.js';
- import Color from '../Core/Color.js';
- var color = Color.parse;
- import H from '../Core/Globals.js';
- import Point from '../Core/Series/Point.js';
- import U from '../Core/Utilities.js';
- var addEvent = U.addEvent, clamp = U.clamp, defined = U.defined, extend = U.extend, extendClass = U.extendClass, fireEvent = U.fireEvent, isArray = U.isArray, isNumber = U.isNumber, merge = U.merge, pick = U.pick, seriesType = U.seriesType;
- /**
- * Formatter callback function.
- *
- * @callback Highcharts.SeriesPackedBubbleDataLabelsFormatterCallbackFunction
- *
- * @param {Highcharts.SeriesPackedBubbleDataLabelsFormatterContextObject} this
- * Data label context to format
- *
- * @return {string}
- * Formatted data label text
- */
- /**
- * Context for the formatter function.
- *
- * @interface Highcharts.SeriesPackedBubbleDataLabelsFormatterContextObject
- * @extends Highcharts.PointLabelObject
- * @since 7.0.0
- */ /**
- * The color of the node.
- * @name Highcharts.SeriesPackedBubbleDataLabelsFormatterContextObject#color
- * @type {Highcharts.ColorString}
- * @since 7.0.0
- */ /**
- * The point (node) object. The node name, if defined, is available through
- * `this.point.name`. Arrays: `this.point.linksFrom` and `this.point.linksTo`
- * contains all nodes connected to this point.
- * @name Highcharts.SeriesPackedBubbleDataLabelsFormatterContextObject#point
- * @type {Highcharts.Point}
- * @since 7.0.0
- */ /**
- * The ID of the node.
- * @name Highcharts.SeriesPackedBubbleDataLabelsFormatterContextObject#key
- * @type {string}
- * @since 7.0.0
- */
- import '../Core/Axis/Axis.js';
- import './Bubble/BubbleSeries.js';
- import '../Series/Networkgraph/DraggableNodes.js';
- import '../Series/Networkgraph/Layouts.js';
- var Series = H.Series, Reingold = H.layouts['reingold-fruchterman'], dragNodesMixin = H.dragNodesMixin;
- Chart.prototype.getSelectedParentNodes = function () {
- var chart = this, series = chart.series, selectedParentsNodes = [];
- series.forEach(function (series) {
- if (series.parentNode && series.parentNode.selected) {
- selectedParentsNodes.push(series.parentNode);
- }
- });
- return selectedParentsNodes;
- };
- H.networkgraphIntegrations.packedbubble = {
- repulsiveForceFunction: function (d, k, node, repNode) {
- return Math.min(d, (node.marker.radius + repNode.marker.radius) / 2);
- },
- barycenter: function () {
- var layout = this, gravitationalConstant = layout.options.gravitationalConstant, box = layout.box, nodes = layout.nodes, centerX, centerY;
- nodes.forEach(function (node) {
- if (layout.options.splitSeries && !node.isParentNode) {
- centerX = node.series.parentNode.plotX;
- centerY = node.series.parentNode.plotY;
- }
- else {
- centerX = box.width / 2;
- centerY = box.height / 2;
- }
- if (!node.fixedPosition) {
- node.plotX -=
- (node.plotX - centerX) *
- gravitationalConstant /
- (node.mass * Math.sqrt(nodes.length));
- node.plotY -=
- (node.plotY - centerY) *
- gravitationalConstant /
- (node.mass * Math.sqrt(nodes.length));
- }
- });
- },
- repulsive: function (node, force, distanceXY, repNode) {
- var factor = (force * this.diffTemperature / node.mass /
- node.degree), x = distanceXY.x * factor, y = distanceXY.y * factor;
- if (!node.fixedPosition) {
- node.plotX += x;
- node.plotY += y;
- }
- if (!repNode.fixedPosition) {
- repNode.plotX -= x;
- repNode.plotY -= y;
- }
- },
- integrate: H.networkgraphIntegrations.verlet.integrate,
- getK: H.noop
- };
- H.layouts.packedbubble = extendClass(Reingold, {
- beforeStep: function () {
- if (this.options.marker) {
- this.series.forEach(function (series) {
- if (series) {
- series.calculateParentRadius();
- }
- });
- }
- },
- setCircularPositions: function () {
- var layout = this, box = layout.box, nodes = layout.nodes, nodesLength = nodes.length + 1, angle = 2 * Math.PI / nodesLength, centerX, centerY, radius = layout.options.initialPositionRadius;
- nodes.forEach(function (node, index) {
- if (layout.options.splitSeries &&
- !node.isParentNode) {
- centerX = node.series.parentNode.plotX;
- centerY = node.series.parentNode.plotY;
- }
- else {
- centerX = box.width / 2;
- centerY = box.height / 2;
- }
- node.plotX = node.prevX = pick(node.plotX, centerX +
- radius * Math.cos(node.index || index * angle));
- node.plotY = node.prevY = pick(node.plotY, centerY +
- radius * Math.sin(node.index || index * angle));
- node.dispX = 0;
- node.dispY = 0;
- });
- },
- repulsiveForces: function () {
- var layout = this, force, distanceR, distanceXY, bubblePadding = layout.options.bubblePadding;
- layout.nodes.forEach(function (node) {
- node.degree = node.mass;
- node.neighbours = 0;
- layout.nodes.forEach(function (repNode) {
- force = 0;
- if (
- // Node can not repulse itself:
- node !== repNode &&
- // Only close nodes affect each other:
- // Not dragged:
- !node.fixedPosition &&
- (layout.options.seriesInteraction ||
- node.series === repNode.series)) {
- distanceXY = layout.getDistXY(node, repNode);
- distanceR = (layout.vectorLength(distanceXY) -
- (node.marker.radius +
- repNode.marker.radius +
- bubblePadding));
- // TODO padding configurable
- if (distanceR < 0) {
- node.degree += 0.01;
- node.neighbours++;
- force = layout.repulsiveForce(-distanceR / Math.sqrt(node.neighbours), layout.k, node, repNode);
- }
- layout.force('repulsive', node, force * repNode.mass, distanceXY, repNode, distanceR);
- }
- });
- });
- },
- applyLimitBox: function (node) {
- var layout = this, distanceXY, distanceR, factor = 0.01;
- // parentNodeLimit should be used together
- // with seriesInteraction: false
- if (layout.options.splitSeries &&
- !node.isParentNode &&
- layout.options.parentNodeLimit) {
- distanceXY = layout.getDistXY(node, node.series.parentNode);
- distanceR = (node.series.parentNodeRadius -
- node.marker.radius -
- layout.vectorLength(distanceXY));
- if (distanceR < 0 &&
- distanceR > -2 * node.marker.radius) {
- node.plotX -= distanceXY.x * factor;
- node.plotY -= distanceXY.y * factor;
- }
- }
- Reingold.prototype.applyLimitBox.apply(this, arguments);
- }
- });
- /**
- * @private
- * @class
- * @name Highcharts.seriesTypes.packedbubble
- *
- * @extends Highcharts.Series
- */
- seriesType('packedbubble', 'bubble',
- /**
- * A packed bubble series is a two dimensional series type, where each point
- * renders a value in X, Y position. Each point is drawn as a bubble
- * where the bubbles don't overlap with each other and the radius
- * of the bubble relates to the value.
- *
- * @sample highcharts/demo/packed-bubble/
- * Packed bubble chart
- * @sample highcharts/demo/packed-bubble-split/
- * Split packed bubble chart
- * @extends plotOptions.bubble
- * @excluding connectEnds, connectNulls, cropThreshold, dragDrop, jitter,
- * keys, pointPlacement, sizeByAbsoluteValue, step, xAxis,
- * yAxis, zMax, zMin, dataSorting, boostThreshold,
- * boostBlending
- * @product highcharts
- * @since 7.0.0
- * @requires highcharts-more
- * @optionparent plotOptions.packedbubble
- */
- {
- /**
- * Minimum bubble size. Bubbles will automatically size between the
- * `minSize` and `maxSize` to reflect the value of each bubble.
- * Can be either pixels (when no unit is given), or a percentage of
- * the smallest one of the plot width and height, divided by the square
- * root of total number of points.
- *
- * @sample highcharts/plotoptions/bubble-size/
- * Bubble size
- *
- * @type {number|string}
- *
- * @private
- */
- minSize: '10%',
- /**
- * Maximum bubble size. Bubbles will automatically size between the
- * `minSize` and `maxSize` to reflect the value of each bubble.
- * Can be either pixels (when no unit is given), or a percentage of
- * the smallest one of the plot width and height, divided by the square
- * root of total number of points.
- *
- * @sample highcharts/plotoptions/bubble-size/
- * Bubble size
- *
- * @type {number|string}
- *
- * @private
- */
- maxSize: '50%',
- sizeBy: 'area',
- zoneAxis: 'y',
- crisp: false,
- tooltip: {
- pointFormat: 'Value: {point.value}'
- },
- /**
- * Flag to determine if nodes are draggable or not. Available for
- * graph with useSimulation set to true only.
- *
- * @since 7.1.0
- *
- * @private
- */
- draggable: true,
- /**
- * An option is giving a possibility to choose between using simulation
- * for calculating bubble positions. These reflects in both animation
- * and final position of bubbles. Simulation is also adding options to
- * the series graph based on used layout. In case of big data sets, with
- * any performance issues, it is possible to disable animation and pack
- * bubble in a simple circular way.
- *
- * @sample highcharts/series-packedbubble/spiral/
- * useSimulation set to false
- *
- * @since 7.1.0
- *
- * @private
- */
- useSimulation: true,
- /**
- * Series options for parent nodes.
- *
- * @since 8.1.1
- *
- * @private
- */
- parentNode: {
- /**
- * Allow this series' parent nodes to be selected
- * by clicking on the graph.
- *
- * @since 8.1.1
- */
- allowPointSelect: false
- },
- /**
- /**
- *
- * @declare Highcharts.SeriesPackedBubbleDataLabelsOptionsObject
- *
- * @private
- */
- dataLabels: {
- /**
- * The
- * [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
- * specifying what to show for _node_ in the networkgraph. In v7.0
- * defaults to `{key}`, since v7.1 defaults to `undefined` and
- * `formatter` is used instead.
- *
- * @type {string}
- * @since 7.0.0
- * @apioption plotOptions.packedbubble.dataLabels.format
- */
- // eslint-disable-next-line valid-jsdoc
- /**
- * Callback JavaScript function to format the data label for a node.
- * Note that if a `format` is defined, the format takes precedence
- * and the formatter is ignored.
- *
- * @type {Highcharts.SeriesPackedBubbleDataLabelsFormatterCallbackFunction}
- * @since 7.0.0
- */
- formatter: function () {
- return this.point.value;
- },
- /**
- * @type {string}
- * @since 7.1.0
- * @apioption plotOptions.packedbubble.dataLabels.parentNodeFormat
- */
- // eslint-disable-next-line valid-jsdoc
- /**
- * @type {Highcharts.SeriesPackedBubbleDataLabelsFormatterCallbackFunction}
- * @since 7.1.0
- */
- parentNodeFormatter: function () {
- return this.name;
- },
- /**
- * @sample {highcharts} highcharts/series-packedbubble/packed-dashboard
- * Dashboard with dataLabels on parentNodes
- *
- * @declare Highcharts.SeriesPackedBubbleDataLabelsTextPathOptionsObject
- * @since 7.1.0
- */
- parentNodeTextPath: {
- /**
- * Presentation attributes for the text path.
- *
- * @type {Highcharts.SVGAttributes}
- * @since 7.1.0
- * @apioption plotOptions.packedbubble.dataLabels.attributes
- */
- /**
- * Enable or disable `textPath` option for link's or marker's
- * data labels.
- *
- * @since 7.1.0
- */
- enabled: true
- },
- /**
- * Options for a _node_ label text which should follow marker's
- * shape.
- *
- * **Note:** Only SVG-based renderer supports this option.
- *
- * @extends plotOptions.series.dataLabels.textPath
- * @apioption plotOptions.packedbubble.dataLabels.textPath
- */
- padding: 0,
- style: {
- transition: 'opacity 2000ms'
- }
- },
- /**
- * Options for layout algorithm when simulation is enabled. Inside there
- * are options to change the speed, padding, initial bubbles positions
- * and more.
- *
- * @extends plotOptions.networkgraph.layoutAlgorithm
- * @excluding approximation, attractiveForce, repulsiveForce, theta
- * @since 7.1.0
- *
- * @private
- */
- layoutAlgorithm: {
- /**
- * Initial layout algorithm for positioning nodes. Can be one of
- * the built-in options ("circle", "random") or a function where
- * positions should be set on each node (`this.nodes`) as
- * `node.plotX` and `node.plotY`.
- *
- * @sample highcharts/series-networkgraph/initial-positions/
- * Initial positions with callback
- *
- * @type {"circle"|"random"|Function}
- */
- initialPositions: 'circle',
- /**
- * @sample highcharts/series-packedbubble/initial-radius/
- * Initial radius set to 200
- *
- * @extends plotOptions.networkgraph.layoutAlgorithm.initialPositionRadius
- * @excluding states
- */
- initialPositionRadius: 20,
- /**
- * The distance between two bubbles, when the algorithm starts to
- * treat two bubbles as overlapping. The `bubblePadding` is also the
- * expected distance between all the bubbles on simulation end.
- */
- bubblePadding: 5,
- /**
- * Whether bubbles should interact with their parentNode to keep
- * them inside.
- */
- parentNodeLimit: false,
- /**
- * Whether series should interact with each other or not. When
- * `parentNodeLimit` is set to true, thi option should be set to
- * false to avoid sticking points in wrong series parentNode.
- */
- seriesInteraction: true,
- /**
- * In case of split series, this option allows user to drag and
- * drop points between series, for changing point related series.
- *
- * @sample highcharts/series-packedbubble/packed-dashboard/
- * Example of drag'n drop bubbles for bubble kanban
- */
- dragBetweenSeries: false,
- /**
- * Layout algorithm options for parent nodes.
- *
- * @extends plotOptions.networkgraph.layoutAlgorithm
- * @excluding approximation, attractiveForce, enableSimulation,
- * repulsiveForce, theta
- */
- parentNodeOptions: {
- maxIterations: 400,
- gravitationalConstant: 0.03,
- maxSpeed: 50,
- initialPositionRadius: 100,
- seriesInteraction: true,
- /**
- * Styling options for parentNodes markers. Similar to
- * line.marker options.
- *
- * @sample highcharts/series-packedbubble/parentnode-style/
- * Bubble size
- *
- * @extends plotOptions.series.marker
- * @excluding states
- */
- marker: {
- fillColor: null,
- fillOpacity: 1,
- lineWidth: 1,
- lineColor: null,
- symbol: 'circle'
- }
- },
- enableSimulation: true,
- /**
- * Type of the algorithm used when positioning bubbles.
- * @ignore-option
- */
- type: 'packedbubble',
- /**
- * Integration type. Integration determines how forces are applied
- * on particles. The `packedbubble` integration is based on
- * the networkgraph `verlet` integration, where the new position
- * is based on a previous position without velocity:
- * `newPosition += previousPosition - newPosition`.
- *
- * @sample highcharts/series-networkgraph/forces/
- *
- * @ignore-option
- */
- integration: 'packedbubble',
- maxIterations: 1000,
- /**
- * Whether to split series into individual groups or to mix all
- * series together.
- *
- * @since 7.1.0
- * @default false
- */
- splitSeries: false,
- /**
- * Max speed that node can get in one iteration. In terms of
- * simulation, it's a maximum translation (in pixels) that a node
- * can move (in both, x and y, dimensions). While `friction` is
- * applied on all nodes, max speed is applied only for nodes that
- * move very fast, for example small or disconnected ones.
- *
- * @see [layoutAlgorithm.integration](#series.networkgraph.layoutAlgorithm.integration)
- *
- * @see [layoutAlgorithm.friction](#series.networkgraph.layoutAlgorithm.friction)
- */
- maxSpeed: 5,
- gravitationalConstant: 0.01,
- friction: -0.981
- }
- }, {
- /**
- * An internal option used for allowing nodes dragging.
- * @private
- */
- hasDraggableNodes: true,
- /**
- * Array of internal forces. Each force should be later defined in
- * integrations.js.
- * @private
- */
- forces: ['barycenter', 'repulsive'],
- pointArrayMap: ['value'],
- trackerGroups: ['group', 'dataLabelsGroup', 'parentNodesGroup'],
- pointValKey: 'value',
- isCartesian: false,
- requireSorting: false,
- directTouch: true,
- axisTypes: [],
- noSharedTooltip: true,
- // solving #12287
- searchPoint: H.noop,
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * Create a single array of all points from all series
- * @private
- * @param {Highcharts.Series} series Array of all series objects
- * @return {Array<Highcharts.PackedBubbleData>} Returns the array of all points.
- */
- accumulateAllPoints: function (series) {
- var chart = series.chart, allDataPoints = [], i, j;
- for (i = 0; i < chart.series.length; i++) {
- series = chart.series[i];
- if (series.is('packedbubble') && // #13574
- series.visible ||
- !chart.options.chart.ignoreHiddenSeries) {
- // add data to array only if series is visible
- for (j = 0; j < series.yData.length; j++) {
- allDataPoints.push([
- null, null,
- series.yData[j],
- series.index,
- j,
- {
- id: j,
- marker: {
- radius: 0
- }
- }
- ]);
- }
- }
- }
- return allDataPoints;
- },
- init: function () {
- Series.prototype.init.apply(this, arguments);
- // When one series is modified, the others need to be recomputed
- addEvent(this, 'updatedData', function () {
- this.chart.series.forEach(function (s) {
- if (s.type === this.type) {
- s.isDirty = true;
- }
- }, this);
- });
- return this;
- },
- render: function () {
- var series = this, dataLabels = [];
- Series.prototype.render.apply(this, arguments);
- // #10823 - dataLabels should stay visible
- // when enabled allowOverlap.
- if (!series.options.dataLabels.allowOverlap) {
- series.data.forEach(function (point) {
- if (isArray(point.dataLabels)) {
- point.dataLabels.forEach(function (dataLabel) {
- dataLabels.push(dataLabel);
- });
- }
- });
- // Only hide overlapping dataLabels for layouts that
- // use simulation. Spiral packedbubble don't need
- // additional dataLabel hiding on every simulation step
- if (series.options.useSimulation) {
- series.chart.hideOverlappingLabels(dataLabels);
- }
- }
- },
- // Needed because of z-indexing issue if point is added in series.group
- setVisible: function () {
- var series = this;
- Series.prototype.setVisible.apply(series, arguments);
- if (series.parentNodeLayout && series.graph) {
- if (series.visible) {
- series.graph.show();
- if (series.parentNode.dataLabel) {
- series.parentNode.dataLabel.show();
- }
- }
- else {
- series.graph.hide();
- series.parentNodeLayout
- .removeElementFromCollection(series.parentNode, series.parentNodeLayout.nodes);
- if (series.parentNode.dataLabel) {
- series.parentNode.dataLabel.hide();
- }
- }
- }
- else if (series.layout) {
- if (series.visible) {
- series.layout.addElementsToCollection(series.points, series.layout.nodes);
- }
- else {
- series.points.forEach(function (node) {
- series.layout.removeElementFromCollection(node, series.layout.nodes);
- });
- }
- }
- },
- // Packedbubble has two separate collecions of nodes if split, render
- // dataLabels for both sets:
- drawDataLabels: function () {
- var textPath = this.options.dataLabels.textPath, points = this.points;
- // Render node labels:
- Series.prototype.drawDataLabels.apply(this, arguments);
- // Render parentNode labels:
- if (this.parentNode) {
- this.parentNode.formatPrefix = 'parentNode';
- this.points = [this.parentNode];
- this.options.dataLabels.textPath =
- this.options.dataLabels.parentNodeTextPath;
- Series.prototype.drawDataLabels.apply(this, arguments);
- // Restore nodes
- this.points = points;
- this.options.dataLabels.textPath = textPath;
- }
- },
- /**
- * The function responsible for calculating series bubble' s bBox.
- * Needed because of exporting failure when useSimulation
- * is set to false
- * @private
- */
- seriesBox: function () {
- var series = this, chart = series.chart, data = series.data, max = Math.max, min = Math.min, radius,
- // bBox = [xMin, xMax, yMin, yMax]
- bBox = [
- chart.plotLeft,
- chart.plotLeft + chart.plotWidth,
- chart.plotTop,
- chart.plotTop + chart.plotHeight
- ];
- data.forEach(function (p) {
- if (defined(p.plotX) &&
- defined(p.plotY) &&
- p.marker.radius) {
- radius = p.marker.radius;
- bBox[0] = min(bBox[0], p.plotX - radius);
- bBox[1] = max(bBox[1], p.plotX + radius);
- bBox[2] = min(bBox[2], p.plotY - radius);
- bBox[3] = max(bBox[3], p.plotY + radius);
- }
- });
- return isNumber(bBox.width / bBox.height) ?
- bBox :
- null;
- },
- /**
- * The function responsible for calculating the parent node radius
- * based on the total surface of iniside-bubbles and the group BBox
- * @private
- */
- calculateParentRadius: function () {
- var series = this, bBox, parentPadding = 20, minParentRadius = 20;
- bBox = series.seriesBox();
- series.parentNodeRadius = clamp(Math.sqrt(2 * series.parentNodeMass / Math.PI) + parentPadding, minParentRadius, bBox ?
- Math.max(Math.sqrt(Math.pow(bBox.width, 2) +
- Math.pow(bBox.height, 2)) / 2 + parentPadding, minParentRadius) :
- Math.sqrt(2 * series.parentNodeMass / Math.PI) + parentPadding);
- if (series.parentNode) {
- series.parentNode.marker.radius =
- series.parentNode.radius = series.parentNodeRadius;
- }
- },
- // Create Background/Parent Nodes for split series.
- drawGraph: function () {
- // if the series is not using layout, don't add parent nodes
- if (!this.layout || !this.layout.options.splitSeries) {
- return;
- }
- var series = this, chart = series.chart, parentAttribs = {}, nodeMarker = this.layout.options.parentNodeOptions.marker, parentOptions = {
- fill: nodeMarker.fillColor || color(series.color).brighten(0.4).get(),
- opacity: nodeMarker.fillOpacity,
- stroke: nodeMarker.lineColor || series.color,
- 'stroke-width': nodeMarker.lineWidth
- }, visibility = series.visible ? 'inherit' : 'hidden';
- // create the group for parent Nodes if doesn't exist
- if (!this.parentNodesGroup) {
- series.parentNodesGroup = series.plotGroup('parentNodesGroup', 'parentNode', visibility, 0.1, chart.seriesGroup);
- series.group.attr({
- zIndex: 2
- });
- }
- this.calculateParentRadius();
- parentAttribs = merge({
- x: series.parentNode.plotX -
- series.parentNodeRadius,
- y: series.parentNode.plotY -
- series.parentNodeRadius,
- width: series.parentNodeRadius * 2,
- height: series.parentNodeRadius * 2
- }, parentOptions);
- if (!series.parentNode.graphic) {
- series.graph = series.parentNode.graphic =
- chart.renderer.symbol(parentOptions.symbol)
- .add(series.parentNodesGroup);
- }
- series.parentNode.graphic.attr(parentAttribs);
- },
- /**
- * Creating parent nodes for split series, in which all the bubbles
- * are rendered.
- * @private
- */
- createParentNodes: function () {
- var series = this, chart = series.chart, parentNodeLayout = series.parentNodeLayout, nodeAdded, parentNode = series.parentNode, PackedBubblePoint = series.pointClass;
- series.parentNodeMass = 0;
- series.points.forEach(function (p) {
- series.parentNodeMass +=
- Math.PI * Math.pow(p.marker.radius, 2);
- });
- series.calculateParentRadius();
- parentNodeLayout.nodes.forEach(function (node) {
- if (node.seriesIndex === series.index) {
- nodeAdded = true;
- }
- });
- parentNodeLayout.setArea(0, 0, chart.plotWidth, chart.plotHeight);
- if (!nodeAdded) {
- if (!parentNode) {
- parentNode = (new PackedBubblePoint()).init(this, {
- mass: series.parentNodeRadius / 2,
- marker: {
- radius: series.parentNodeRadius
- },
- dataLabels: {
- inside: false
- },
- dataLabelOnNull: true,
- degree: series.parentNodeRadius,
- isParentNode: true,
- seriesIndex: series.index
- });
- }
- if (series.parentNode) {
- parentNode.plotX = series.parentNode.plotX;
- parentNode.plotY = series.parentNode.plotY;
- }
- series.parentNode = parentNode;
- parentNodeLayout.addElementsToCollection([series], parentNodeLayout.series);
- parentNodeLayout.addElementsToCollection([parentNode], parentNodeLayout.nodes);
- }
- },
- drawTracker: function () {
- var series = this, chart = series.chart, pointer = chart.pointer, onMouseOver = function (e) {
- var point = pointer.getPointFromEvent(e);
- // undefined on graph in scatterchart
- if (typeof point !== 'undefined') {
- pointer.isDirectTouch = true;
- point.onMouseOver(e);
- }
- }, parentNode = series.parentNode;
- var dataLabels;
- H.TrackerMixin.drawTrackerPoint.call(this);
- // Add reference to the point
- if (parentNode) {
- dataLabels = (isArray(parentNode.dataLabels) ?
- parentNode.dataLabels :
- (parentNode.dataLabel ? [parentNode.dataLabel] : []));
- if (parentNode.graphic) {
- parentNode.graphic.element.point = parentNode;
- }
- dataLabels.forEach(function (dataLabel) {
- if (dataLabel.div) {
- dataLabel.div.point = parentNode;
- }
- else {
- dataLabel.element.point = parentNode;
- }
- });
- }
- },
- /**
- * Function responsible for adding series layout, used for parent nodes.
- * @private
- */
- addSeriesLayout: function () {
- var series = this, layoutOptions = series.options.layoutAlgorithm, graphLayoutsStorage = series.chart.graphLayoutsStorage, graphLayoutsLookup = series.chart.graphLayoutsLookup, parentNodeOptions = merge(layoutOptions, layoutOptions.parentNodeOptions, {
- enableSimulation: series.layout.options.enableSimulation
- }), parentNodeLayout;
- parentNodeLayout = graphLayoutsStorage[layoutOptions.type + '-series'];
- if (!parentNodeLayout) {
- graphLayoutsStorage[layoutOptions.type + '-series'] =
- parentNodeLayout =
- new H.layouts[layoutOptions.type]();
- parentNodeLayout.init(parentNodeOptions);
- graphLayoutsLookup.splice(parentNodeLayout.index, 0, parentNodeLayout);
- }
- series.parentNodeLayout = parentNodeLayout;
- this.createParentNodes();
- },
- /**
- * Adding the basic layout to series points.
- * @private
- */
- addLayout: function () {
- var series = this, layoutOptions = series.options.layoutAlgorithm, graphLayoutsStorage = series.chart.graphLayoutsStorage, graphLayoutsLookup = series.chart.graphLayoutsLookup, chartOptions = series.chart.options.chart, layout;
- if (!graphLayoutsStorage) {
- series.chart.graphLayoutsStorage = graphLayoutsStorage = {};
- series.chart.graphLayoutsLookup = graphLayoutsLookup = [];
- }
- layout = graphLayoutsStorage[layoutOptions.type];
- if (!layout) {
- layoutOptions.enableSimulation =
- !defined(chartOptions.forExport) ?
- layoutOptions.enableSimulation :
- !chartOptions.forExport;
- graphLayoutsStorage[layoutOptions.type] = layout =
- new H.layouts[layoutOptions.type]();
- layout.init(layoutOptions);
- graphLayoutsLookup.splice(layout.index, 0, layout);
- }
- series.layout = layout;
- series.points.forEach(function (node) {
- node.mass = 2;
- node.degree = 1;
- node.collisionNmb = 1;
- });
- layout.setArea(0, 0, series.chart.plotWidth, series.chart.plotHeight);
- layout.addElementsToCollection([series], layout.series);
- layout.addElementsToCollection(series.points, layout.nodes);
- },
- /**
- * Function responsible for adding all the layouts to the chart.
- * @private
- */
- deferLayout: function () {
- // TODO split layouts to independent methods
- var series = this, layoutOptions = series.options.layoutAlgorithm;
- if (!series.visible) {
- return;
- }
- // layout is using nodes for position calculation
- series.addLayout();
- if (layoutOptions.splitSeries) {
- series.addSeriesLayout();
- }
- },
- /**
- * Extend the base translate method to handle bubble size,
- * and correct positioning them.
- * @private
- */
- translate: function () {
- var series = this, chart = series.chart, data = series.data, index = series.index, point, radius, positions, i, useSimulation = series.options.useSimulation;
- series.processedXData = series.xData;
- series.generatePoints();
- // merged data is an array with all of the data from all series
- if (!defined(chart.allDataPoints)) {
- chart.allDataPoints = series.accumulateAllPoints(series);
- // calculate radius for all added data
- series.getPointRadius();
- }
- // after getting initial radius, calculate bubble positions
- if (useSimulation) {
- positions = chart.allDataPoints;
- }
- else {
- positions = series.placeBubbles(chart.allDataPoints);
- series.options.draggable = false;
- }
- // Set the shape and arguments to be picked up in drawPoints
- for (i = 0; i < positions.length; i++) {
- if (positions[i][3] === index) {
- // update the series points with the val from positions
- // array
- point = data[positions[i][4]];
- radius = positions[i][2];
- if (!useSimulation) {
- point.plotX = (positions[i][0] - chart.plotLeft +
- chart.diffX);
- point.plotY = (positions[i][1] - chart.plotTop +
- chart.diffY);
- }
- point.marker = extend(point.marker, {
- radius: radius,
- width: 2 * radius,
- height: 2 * radius
- });
- point.radius = radius;
- }
- }
- if (useSimulation) {
- series.deferLayout();
- }
- fireEvent(series, 'afterTranslate');
- },
- /**
- * Check if two bubbles overlaps.
- * @private
- * @param {Array} first bubble
- * @param {Array} second bubble
- * @return {Boolean} overlap or not
- */
- checkOverlap: function (bubble1, bubble2) {
- var diffX = bubble1[0] - bubble2[0], // diff of X center values
- diffY = bubble1[1] - bubble2[1], // diff of Y center values
- sumRad = bubble1[2] + bubble2[2]; // sum of bubble radius
- return (Math.sqrt(diffX * diffX + diffY * diffY) -
- Math.abs(sumRad)) < -0.001;
- },
- /**
- * Function that is adding one bubble based on positions and sizes of
- * two other bubbles, lastBubble is the last added bubble, newOrigin is
- * the bubble for positioning new bubbles. nextBubble is the curently
- * added bubble for which we are calculating positions
- * @private
- * @param {Array<number>} lastBubble The closest last bubble
- * @param {Array<number>} newOrigin New bubble
- * @param {Array<number>} nextBubble The closest next bubble
- * @return {Array<number>} Bubble with correct positions
- */
- positionBubble: function (lastBubble, newOrigin, nextBubble) {
- var sqrt = Math.sqrt, asin = Math.asin, acos = Math.acos, pow = Math.pow, abs = Math.abs, distance = sqrt(// dist between lastBubble and newOrigin
- pow((lastBubble[0] - newOrigin[0]), 2) +
- pow((lastBubble[1] - newOrigin[1]), 2)), alfa = acos(
- // from cosinus theorem: alfa is an angle used for
- // calculating correct position
- (pow(distance, 2) +
- pow(nextBubble[2] + newOrigin[2], 2) -
- pow(nextBubble[2] + lastBubble[2], 2)) / (2 * (nextBubble[2] + newOrigin[2]) * distance)), beta = asin(// from sinus theorem.
- abs(lastBubble[0] - newOrigin[0]) /
- distance),
- // providing helping variables, related to angle between
- // lastBubble and newOrigin
- gamma = (lastBubble[1] - newOrigin[1]) < 0 ? 0 : Math.PI,
- // if new origin y is smaller than last bubble y value
- // (2 and 3 quarter),
- // add Math.PI to final angle
- delta = (lastBubble[0] - newOrigin[0]) *
- (lastBubble[1] - newOrigin[1]) < 0 ?
- 1 : -1, // (1st and 3rd quarter)
- finalAngle = gamma + alfa + beta * delta, cosA = Math.cos(finalAngle), sinA = Math.sin(finalAngle), posX = newOrigin[0] + (newOrigin[2] + nextBubble[2]) * sinA,
- // center of new origin + (radius1 + radius2) * sinus A
- posY = newOrigin[1] - (newOrigin[2] + nextBubble[2]) * cosA;
- return [
- posX,
- posY,
- nextBubble[2],
- nextBubble[3],
- nextBubble[4]
- ]; // the same as described before
- },
- /**
- * This is the main function responsible
- * for positioning all of the bubbles
- * allDataPoints - bubble array, in format [pixel x value,
- * pixel y value, radius,
- * related series index, related point index]
- * @private
- * @param {Array<Highcharts.PackedBubbleData>} allDataPoints All points from all series
- * @return {Array<Highcharts.PackedBubbleData>} Positions of all bubbles
- */
- placeBubbles: function (allDataPoints) {
- var series = this, checkOverlap = series.checkOverlap, positionBubble = series.positionBubble, bubblePos = [], stage = 1, j = 0, k = 0, calculatedBubble, sortedArr, arr = [], i;
- // sort all points
- sortedArr = allDataPoints.sort(function (a, b) {
- return b[2] - a[2];
- });
- if (sortedArr.length) {
- // create first bubble in the middle of the chart
- bubblePos.push([
- [
- 0,
- 0,
- sortedArr[0][2],
- sortedArr[0][3],
- sortedArr[0][4]
- ] // point index
- ]); // 0 level bubble
- if (sortedArr.length > 1) {
- bubblePos.push([
- [
- 0,
- (0 - sortedArr[1][2] -
- sortedArr[0][2]),
- // move bubble above first one
- sortedArr[1][2],
- sortedArr[1][3],
- sortedArr[1][4]
- ]
- ]); // 1 level 1st bubble
- // first two already positioned so starting from 2
- for (i = 2; i < sortedArr.length; i++) {
- sortedArr[i][2] = sortedArr[i][2] || 1;
- // in case if radius is calculated as 0.
- calculatedBubble = positionBubble(bubblePos[stage][j], bubblePos[stage - 1][k], sortedArr[i]); // calculate initial bubble position
- if (checkOverlap(calculatedBubble, bubblePos[stage][0])) {
- /* if new bubble is overlapping with first bubble
- * in current level (stage)
- */
- bubblePos.push([]);
- k = 0;
- /* reset index of bubble, used for
- * positioning the bubbles around it,
- * we are starting from first bubble in next
- * stage because we are changing level to higher
- */
- bubblePos[stage + 1].push(positionBubble(bubblePos[stage][j], bubblePos[stage][0], sortedArr[i]));
- // (last bubble, 1. from curr stage, new bubble)
- stage++; // the new level is created, above current
- j = 0; // set the index of bubble in curr level to 0
- }
- else if (stage > 1 &&
- bubblePos[stage - 1][k + 1] &&
- checkOverlap(calculatedBubble, bubblePos[stage - 1][k + 1])) {
- /* if new bubble is overlapping with one of the prev
- * stage bubbles, it means that - bubble, used for
- * positioning the bubbles around it has changed
- * so we need to recalculate it
- */
- k++;
- bubblePos[stage].push(positionBubble(bubblePos[stage][j], bubblePos[stage - 1][k], sortedArr[i]));
- // (last bubble, prev stage bubble, new bubble)
- j++;
- }
- else { // simply add calculated bubble
- j++;
- bubblePos[stage].push(calculatedBubble);
- }
- }
- }
- series.chart.stages = bubblePos;
- // it may not be necessary but adding it just in case -
- // it is containing all of the bubble levels
- series.chart.rawPositions =
- []
- .concat.apply([], bubblePos);
- // bubble positions merged into one array
- series.resizeRadius();
- arr = series.chart.rawPositions;
- }
- return arr;
- },
- /**
- * The function responsible for resizing the bubble radius.
- * In shortcut: it is taking the initially
- * calculated positions of bubbles. Then it is calculating the min max
- * of both dimensions, creating something in shape of bBox.
- * The comparison of bBox and the size of plotArea
- * (later it may be also the size set by customer) is giving the
- * value how to recalculate the radius so it will match the size
- * @private
- */
- resizeRadius: function () {
- var chart = this.chart, positions = chart.rawPositions, min = Math.min, max = Math.max, plotLeft = chart.plotLeft, plotTop = chart.plotTop, chartHeight = chart.plotHeight, chartWidth = chart.plotWidth, minX, maxX, minY, maxY, radius, bBox, spaceRatio, smallerDimension, i;
- minX = minY = Number.POSITIVE_INFINITY; // set initial values
- maxX = maxY = Number.NEGATIVE_INFINITY;
- for (i = 0; i < positions.length; i++) {
- radius = positions[i][2];
- minX = min(minX, positions[i][0] - radius);
- // (x center-radius) is the min x value used by specific bubble
- maxX = max(maxX, positions[i][0] + radius);
- minY = min(minY, positions[i][1] - radius);
- maxY = max(maxY, positions[i][1] + radius);
- }
- bBox = [maxX - minX, maxY - minY];
- spaceRatio = [
- (chartWidth - plotLeft) / bBox[0],
- (chartHeight - plotTop) / bBox[1]
- ];
- smallerDimension = min.apply([], spaceRatio);
- if (Math.abs(smallerDimension - 1) > 1e-10) {
- // if bBox is considered not the same width as possible size
- for (i = 0; i < positions.length; i++) {
- positions[i][2] *= smallerDimension;
- }
- this.placeBubbles(positions);
- }
- else {
- /** if no radius recalculation is needed, we need to position
- * the whole bubbles in center of chart plotarea
- * for this, we are adding two parameters,
- * diffY and diffX, that are related to differences
- * between the initial center and the bounding box
- */
- chart.diffY = chartHeight / 2 +
- plotTop - minY - (maxY - minY) / 2;
- chart.diffX = chartWidth / 2 +
- plotLeft - minX - (maxX - minX) / 2;
- }
- },
- /**
- * Calculate min and max bubble value for radius calculation.
- * @private
- */
- calculateZExtremes: function () {
- var chart = this.chart, zMin = this.options.zMin, zMax = this.options.zMax, valMin = Infinity, valMax = -Infinity;
- if (zMin && zMax) {
- return [zMin, zMax];
- }
- // it is needed to deal with null
- // and undefined values
- chart.series.forEach(function (s) {
- s.yData.forEach(function (p) {
- if (defined(p)) {
- if (p > valMax) {
- valMax = p;
- }
- if (p < valMin) {
- valMin = p;
- }
- }
- });
- });
- zMin = pick(zMin, valMin);
- zMax = pick(zMax, valMax);
- return [zMin, zMax];
- },
- /**
- * Calculate radius of bubbles in series.
- * @private
- */
- getPointRadius: function () {
- var series = this, chart = series.chart, plotWidth = chart.plotWidth, plotHeight = chart.plotHeight, seriesOptions = series.options, useSimulation = seriesOptions.useSimulation, smallestSize = Math.min(plotWidth, plotHeight), extremes = {}, radii = [], allDataPoints = chart.allDataPoints, minSize, maxSize, value, radius, zExtremes;
- ['minSize', 'maxSize'].forEach(function (prop) {
- var length = parseInt(seriesOptions[prop], 10), isPercent = /%$/.test(seriesOptions[prop]);
- extremes[prop] = isPercent ?
- smallestSize * length / 100 :
- length * Math.sqrt(allDataPoints.length);
- });
- chart.minRadius = minSize = extremes.minSize /
- Math.sqrt(allDataPoints.length);
- chart.maxRadius = maxSize = extremes.maxSize /
- Math.sqrt(allDataPoints.length);
- zExtremes = useSimulation ?
- series.calculateZExtremes() :
- [minSize, maxSize];
- (allDataPoints || []).forEach(function (point, i) {
- value = useSimulation ?
- clamp(point[2], zExtremes[0], zExtremes[1]) :
- point[2];
- radius = series.getRadius(zExtremes[0], zExtremes[1], minSize, maxSize, value);
- if (radius === 0) {
- radius = null;
- }
- allDataPoints[i][2] = radius;
- radii.push(radius);
- });
- series.radii = radii;
- },
- // Draggable mode:
- /**
- * Redraw halo on mousemove during the drag&drop action.
- * @private
- * @param {Highcharts.Point} point The point that should show halo.
- */
- redrawHalo: dragNodesMixin.redrawHalo,
- /**
- * Mouse down action, initializing drag&drop mode.
- * @private
- * @param {global.Event} event Browser event, before normalization.
- * @param {Highcharts.Point} point The point that event occured.
- */
- onMouseDown: dragNodesMixin.onMouseDown,
- /**
- * Mouse move action during drag&drop.
- * @private
- * @param {global.Event} event Browser event, before normalization.
- * @param {Highcharts.Point} point The point that event occured.
- */
- onMouseMove: dragNodesMixin.onMouseMove,
- /**
- * Mouse up action, finalizing drag&drop.
- * @private
- * @param {Highcharts.Point} point The point that event occured.
- */
- onMouseUp: function (point) {
- if (point.fixedPosition && !point.removed) {
- var distanceXY, distanceR, layout = this.layout, parentNodeLayout = this.parentNodeLayout;
- if (parentNodeLayout && layout.options.dragBetweenSeries) {
- parentNodeLayout.nodes.forEach(function (node) {
- if (point && point.marker &&
- node !== point.series.parentNode) {
- distanceXY = layout.getDistXY(point, node);
- distanceR = (layout.vectorLength(distanceXY) -
- node.marker.radius -
- point.marker.radius);
- if (distanceR < 0) {
- node.series.addPoint(merge(point.options, {
- plotX: point.plotX,
- plotY: point.plotY
- }), false);
- layout.removeElementFromCollection(point, layout.nodes);
- point.remove();
- }
- }
- });
- }
- dragNodesMixin.onMouseUp.apply(this, arguments);
- }
- },
- destroy: function () {
- // Remove the series from all layouts series collections #11469
- if (this.chart.graphLayoutsLookup) {
- this.chart.graphLayoutsLookup.forEach(function (layout) {
- layout.removeElementFromCollection(this, layout.series);
- }, this);
- }
- if (this.parentNode) {
- this.parentNodeLayout.removeElementFromCollection(this.parentNode, this.parentNodeLayout.nodes);
- if (this.parentNode.dataLabel) {
- this.parentNode.dataLabel =
- this.parentNode.dataLabel.destroy();
- }
- }
- H.Series.prototype.destroy.apply(this, arguments);
- },
- alignDataLabel: H.Series.prototype.alignDataLabel
- }, {
- /**
- * Destroy point.
- * Then remove point from the layout.
- * @private
- * @return {undefined}
- */
- destroy: function () {
- if (this.series.layout) {
- this.series.layout.removeElementFromCollection(this, this.series.layout.nodes);
- }
- return Point.prototype.destroy.apply(this, arguments);
- },
- firePointEvent: function (eventType, eventArgs, defaultFunction) {
- var point = this, series = this.series, seriesOptions = series.options;
- if (this.isParentNode && seriesOptions.parentNode) {
- var temp = seriesOptions.allowPointSelect;
- seriesOptions.allowPointSelect = seriesOptions.parentNode.allowPointSelect;
- Point.prototype.firePointEvent.apply(this, arguments);
- seriesOptions.allowPointSelect = temp;
- }
- else {
- Point.prototype.firePointEvent.apply(this, arguments);
- }
- },
- select: function (selected, accumulate) {
- var point = this, series = this.series, chart = series.chart;
- if (point.isParentNode) {
- chart.getSelectedPoints = chart.getSelectedParentNodes;
- Point.prototype.select.apply(this, arguments);
- chart.getSelectedPoints = H.Chart.prototype.getSelectedPoints;
- }
- else {
- Point.prototype.select.apply(this, arguments);
- }
- }
- });
- // Remove accumulated data points to redistribute all of them again
- // (i.e after hiding series by legend)
- addEvent(Chart, 'beforeRedraw', function () {
- if (this.allDataPoints) {
- delete this.allDataPoints;
- }
- });
- /* eslint-enable no-invalid-this, valid-jsdoc */
- /**
- * A `packedbubble` series. If the [type](#series.packedbubble.type) option is
- * not specified, it is inherited from [chart.type](#chart.type).
- *
- * @type {Object}
- * @extends series,plotOptions.packedbubble
- * @excluding cropThreshold, dataParser, dataSorting, dataURL, dragDrop, stack,
- * boostThreshold, boostBlending
- * @product highcharts
- * @requires highcharts-more
- * @apioption series.packedbubble
- */
- /**
- * An array of data points for the series. For the `packedbubble` series type,
- * points can be given in the following ways:
- *
- * 1. An array of `values`.
- *
- * ```js
- * data: [5, 1, 20]
- * ```
- *
- * 2. An array of objects with named values. The objects are point
- * configuration objects as seen below. If the total number of data points
- * exceeds the series' [turboThreshold](#series.packedbubble.turboThreshold),
- * this option is not available.
- *
- * ```js
- * data: [{
- * value: 1,
- * name: "Point2",
- * color: "#00FF00"
- * }, {
- * value: 5,
- * name: "Point1",
- * color: "#FF00FF"
- * }]
- * ```
- *
- * @type {Array<Object|Array>}
- * @extends series.line.data
- * @excluding marker, x, y
- * @sample {highcharts} highcharts/series/data-array-of-objects/
- * Config objects
- * @product highcharts
- * @apioption series.packedbubble.data
- */
- /**
- * @type {Highcharts.SeriesPackedBubbleDataLabelsOptionsObject|Array<Highcharts.SeriesPackedBubbleDataLabelsOptionsObject>}
- * @product highcharts
- * @apioption series.packedbubble.data.dataLabels
- */
- /**
- * @excluding enabled,enabledThreshold,height,radius,width
- * @product highcharts
- * @apioption series.packedbubble.marker
- */
- ''; // adds doclets above to transpiled file
|