123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661 |
- /* *
- * (c) 2010-2019 Torstein Honsi
- *
- * License: www.highcharts.com/license
- */
- 'use strict';
- import H from '../parts/Globals.js';
- import '../parts/Utilities.js';
- import '../parts/Axis.js';
- import '../parts/Color.js';
- import '../parts/Point.js';
- import '../parts/Series.js';
- import '../parts/ScatterSeries.js';
- import './BubbleLegend.js';
- var arrayMax = H.arrayMax,
- arrayMin = H.arrayMin,
- Axis = H.Axis,
- color = H.color,
- isNumber = H.isNumber,
- noop = H.noop,
- pick = H.pick,
- pInt = H.pInt,
- Point = H.Point,
- Series = H.Series,
- seriesType = H.seriesType,
- seriesTypes = H.seriesTypes;
- /**
- * A bubble series is a three dimensional series type where each point renders
- * an X, Y and Z value. Each points is drawn as a bubble where the position
- * along the X and Y axes mark the X and Y values, and the size of the bubble
- * relates to the Z value. Requires `highcharts-more.js`.
- *
- * @sample {highcharts} highcharts/demo/bubble/
- * Bubble chart
- *
- * @extends plotOptions.scatter
- * @product highcharts highstock
- * @optionparent plotOptions.bubble
- */
- seriesType('bubble', 'scatter', {
- dataLabels: {
- formatter: function () { // #2945
- return this.point.z;
- },
- inside: true,
- verticalAlign: 'middle'
- },
- /**
- * If there are more points in the series than the `animationLimit`, the
- * animation won't run. Animation affects overall performance and doesn't
- * work well with heavy data series.
- *
- * @since 6.1.0
- */
- animationLimit: 250,
- /**
- * Whether to display negative sized bubbles. The threshold is given
- * by the [zThreshold](#plotOptions.bubble.zThreshold) option, and negative
- * bubbles can be visualized by setting
- * [negativeColor](#plotOptions.bubble.negativeColor).
- *
- * @sample {highcharts} highcharts/plotoptions/bubble-negative/
- * Negative bubbles
- *
- * @type {boolean}
- * @default true
- * @since 3.0
- * @apioption plotOptions.bubble.displayNegative
- */
- /**
- * @extends plotOptions.series.marker
- * @excluding enabled, enabledThreshold, height, radius, width
- */
- marker: {
- lineColor: null, // inherit from series.color
- lineWidth: 1,
- /**
- * The fill opacity of the bubble markers.
- */
- fillOpacity: 0.5,
- /**
- * In bubble charts, the radius is overridden and determined based on
- * the point's data value.
- *
- * @ignore
- */
- radius: null,
- states: {
- hover: {
- radiusPlus: 0
- }
- },
- /**
- * A predefined shape or symbol for the marker. Possible values are
- * "circle", "square", "diamond", "triangle" and "triangle-down".
- *
- * Additionally, the URL to a graphic can be given on the form
- * `url(graphic.png)`. Note that for the image to be applied to exported
- * charts, its URL needs to be accessible by the export server.
- *
- * Custom callbacks for symbol path generation can also be added to
- * `Highcharts.SVGRenderer.prototype.symbols`. The callback is then
- * used by its method name, as shown in the demo.
- *
- * @sample {highcharts} highcharts/plotoptions/bubble-symbol/
- * Bubble chart with various symbols
- * @sample {highcharts} highcharts/plotoptions/series-marker-symbol/
- * General chart with predefined, graphic and custom markers
- *
- * @since 5.0.11
- * @validvalue ["circle", "square", "diamond", "triangle",
- * "triangle-down"]
- */
- symbol: 'circle'
- },
- /**
- * Minimum bubble size. Bubbles will automatically size between the
- * `minSize` and `maxSize` to reflect the `z` 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.
- *
- * @sample {highcharts} highcharts/plotoptions/bubble-size/
- * Bubble size
- *
- * @type {number|string}
- * @since 3.0
- * @product highcharts highstock
- */
- minSize: 8,
- /**
- * Maximum bubble size. Bubbles will automatically size between the
- * `minSize` and `maxSize` to reflect the `z` 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.
- *
- * @sample {highcharts} highcharts/plotoptions/bubble-size/
- * Bubble size
- *
- * @type {number|string}
- * @since 3.0
- * @product highcharts highstock
- */
- maxSize: '20%',
- /**
- * When a point's Z value is below the
- * [zThreshold](#plotOptions.bubble.zThreshold) setting, this color is used.
- *
- * @sample {highcharts} highcharts/plotoptions/bubble-negative/
- * Negative bubbles
- *
- * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
- * @since 3.0
- * @product highcharts
- * @apioption plotOptions.bubble.negativeColor
- */
- /**
- * Whether the bubble's 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} highcharts/plotoptions/bubble-sizeby/
- * Comparison of area and size
- *
- * @type {string}
- * @default area
- * @since 3.0.7
- * @validvalue ["area", "width"]
- * @apioption plotOptions.bubble.sizeBy
- */
- /**
- * 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`.
- *
- * @sample {highcharts} highcharts/plotoptions/bubble-sizebyabsolutevalue/
- * Size by absolute value, various thresholds
- *
- * @type {boolean}
- * @default false
- * @since 4.1.9
- * @product highcharts
- * @apioption plotOptions.bubble.sizeByAbsoluteValue
- */
- /**
- * When this is true, the series will not cause the Y axis to cross
- * the zero plane (or [threshold](#plotOptions.series.threshold) option)
- * unless the data actually crosses the plane.
- *
- * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
- * 3 will make the Y axis show negative values according to the `minPadding`
- * option. If `softThreshold` is `true`, the Y axis starts at 0.
- *
- * @since 4.1.9
- * @product highcharts
- */
- softThreshold: false,
- states: {
- hover: {
- halo: {
- size: 5
- }
- }
- },
- tooltip: {
- pointFormat: '({point.x}, {point.y}), Size: {point.z}'
- },
- turboThreshold: 0,
- /**
- * The minimum for the Z value range. Defaults to the highest Z value
- * in the data.
- *
- * @see [zMin](#plotOptions.bubble.zMin)
- *
- * @sample {highcharts} highcharts/plotoptions/bubble-zmin-zmax/
- * Z has a possible range of 0-100
- *
- * @type {number}
- * @since 4.0.3
- * @product highcharts
- * @apioption plotOptions.bubble.zMax
- */
- /**
- * The minimum for the Z value range. Defaults to the lowest Z value
- * in the data.
- *
- * @see [zMax](#plotOptions.bubble.zMax)
- *
- * @sample {highcharts} highcharts/plotoptions/bubble-zmin-zmax/
- * Z has a possible range of 0-100
- *
- * @type {number}
- * @since 4.0.3
- * @product highcharts
- * @apioption plotOptions.bubble.zMin
- */
- /**
- * When [displayNegative](#plotOptions.bubble.displayNegative) is `false`,
- * bubbles with lower Z values are skipped. When `displayNegative`
- * is `true` and a [negativeColor](#plotOptions.bubble.negativeColor)
- * is given, points with lower Z is colored.
- *
- * @sample {highcharts} highcharts/plotoptions/bubble-negative/
- * Negative bubbles
- *
- * @since 3.0
- * @product highcharts
- */
- zThreshold: 0,
- zoneAxis: 'z'
- // Prototype members
- }, {
- pointArrayMap: ['y', 'z'],
- parallelArrays: ['x', 'y', 'z'],
- trackerGroups: ['group', 'dataLabelsGroup'],
- specialGroup: 'group', // To allow clipping (#6296)
- bubblePadding: true,
- zoneAxis: 'z',
- directTouch: true,
- isBubble: true,
- pointAttribs: function (point, state) {
- var markerOptions = this.options.marker,
- fillOpacity = markerOptions.fillOpacity,
- attr = Series.prototype.pointAttribs.call(this, point, state);
- if (fillOpacity !== 1) {
- attr.fill = color(attr.fill).setOpacity(fillOpacity).get('rgba');
- }
- return attr;
- },
- // Get the radius for each point based on the minSize, maxSize and each
- // point's Z value. This must be done prior to Series.translate because
- // the axis needs to add padding in accordance with the point sizes.
- getRadii: function (zMin, zMax, series) {
- var len,
- i,
- zData = this.zData,
- minSize = series.minPxSize,
- maxSize = series.maxPxSize,
- radii = [],
- value;
- // Set the shape type and arguments to be picked up in drawPoints
- for (i = 0, len = zData.length; i < len; i++) {
- value = zData[i];
- // Separate method to get individual radius for bubbleLegend
- radii.push(this.getRadius(zMin, zMax, minSize, maxSize, value));
- }
- this.radii = radii;
- },
- // Get the individual radius for one point.
- getRadius: function (zMin, zMax, minSize, maxSize, value) {
- var options = this.options,
- sizeByArea = options.sizeBy !== 'width',
- zThreshold = options.zThreshold,
- pos,
- zRange = zMax - zMin,
- radius;
- // When sizing by threshold, the absolute value of z determines
- // the size of the bubble.
- if (options.sizeByAbsoluteValue && value !== null) {
- value = Math.abs(value - zThreshold);
- zMax = zRange = Math.max(
- zMax - zThreshold,
- Math.abs(zMin - zThreshold)
- );
- zMin = 0;
- }
- if (!isNumber(value)) {
- radius = null;
- // Issue #4419 - if value is less than zMin, push a radius that's
- // always smaller than the minimum size
- } else if (value < zMin) {
- radius = minSize / 2 - 1;
- } else {
- // Relative size, a number between 0 and 1
- pos = zRange > 0 ? (value - zMin) / zRange : 0.5;
- if (sizeByArea && pos >= 0) {
- pos = Math.sqrt(pos);
- }
- radius = Math.ceil(minSize + pos * (maxSize - minSize)) / 2;
- }
- return radius;
- },
- // Perform animation on the bubbles
- animate: function (init) {
- if (
- !init &&
- this.points.length < this.options.animationLimit // #8099
- ) {
- this.points.forEach(function (point) {
- var graphic = point.graphic,
- animationTarget;
- if (graphic && graphic.width) { // URL symbols don't have width
- animationTarget = {
- x: graphic.x,
- y: graphic.y,
- width: graphic.width,
- height: graphic.height
- };
- // Start values
- graphic.attr({
- x: point.plotX,
- y: point.plotY,
- width: 1,
- height: 1
- });
- // Run animation
- graphic.animate(animationTarget, this.options.animation);
- }
- }, this);
- // delete this function to allow it only once
- this.animate = null;
- }
- },
- // Extend the base translate method to handle bubble size
- translate: function () {
- var i,
- data = this.data,
- point,
- radius,
- radii = this.radii;
- // Run the parent method
- seriesTypes.scatter.prototype.translate.call(this);
- // Set the shape type and arguments to be picked up in drawPoints
- i = data.length;
- while (i--) {
- point = data[i];
- radius = radii ? radii[i] : 0; // #1737
- if (isNumber(radius) && radius >= this.minPxSize / 2) {
- // Shape arguments
- point.marker = H.extend(point.marker, {
- radius: radius,
- width: 2 * radius,
- height: 2 * radius
- });
- // Alignment box for the data label
- point.dlBox = {
- x: point.plotX - radius,
- y: point.plotY - radius,
- width: 2 * radius,
- height: 2 * radius
- };
- } else { // below zThreshold
- // #1691
- point.shapeArgs = point.plotY = point.dlBox = undefined;
- }
- }
- },
- alignDataLabel: seriesTypes.column.prototype.alignDataLabel,
- buildKDTree: noop,
- applyZones: noop
- // Point class
- }, {
- haloPath: function (size) {
- return Point.prototype.haloPath.call(
- this,
- // #6067
- size === 0 ? 0 : (this.marker ? this.marker.radius || 0 : 0) + size
- );
- },
- ttBelow: false
- });
- // Add logic to pad each axis with the amount of pixels necessary to avoid the
- // bubbles to overflow.
- Axis.prototype.beforePadding = function () {
- var axis = this,
- axisLength = this.len,
- chart = this.chart,
- pxMin = 0,
- pxMax = axisLength,
- isXAxis = this.isXAxis,
- dataKey = isXAxis ? 'xData' : 'yData',
- min = this.min,
- extremes = {},
- smallestSize = Math.min(chart.plotWidth, chart.plotHeight),
- zMin = Number.MAX_VALUE,
- zMax = -Number.MAX_VALUE,
- range = this.max - min,
- transA = axisLength / range,
- activeSeries = [];
- // Handle padding on the second pass, or on redraw
- this.series.forEach(function (series) {
- var seriesOptions = series.options,
- zData;
- if (
- series.bubblePadding &&
- (series.visible || !chart.options.chart.ignoreHiddenSeries)
- ) {
- // Correction for #1673
- axis.allowZoomOutside = true;
- // Cache it
- activeSeries.push(series);
- if (isXAxis) { // because X axis is evaluated first
- // For each series, translate the size extremes to pixel values
- ['minSize', 'maxSize'].forEach(function (prop) {
- var length = seriesOptions[prop],
- isPercent = /%$/.test(length);
- length = pInt(length);
- extremes[prop] = isPercent ?
- smallestSize * length / 100 :
- length;
- });
- series.minPxSize = extremes.minSize;
- // Prioritize min size if conflict to make sure bubbles are
- // always visible. #5873
- series.maxPxSize = Math.max(extremes.maxSize, extremes.minSize);
- // Find the min and max Z
- zData = series.zData.filter(H.isNumber);
- if (zData.length) { // #1735
- zMin = pick(seriesOptions.zMin, Math.min(
- zMin,
- Math.max(
- arrayMin(zData),
- seriesOptions.displayNegative === false ?
- seriesOptions.zThreshold :
- -Number.MAX_VALUE
- )
- ));
- zMax = pick(
- seriesOptions.zMax,
- Math.max(zMax, arrayMax(zData))
- );
- }
- }
- }
- });
- activeSeries.forEach(function (series) {
- var data = series[dataKey],
- i = data.length,
- radius;
- if (isXAxis) {
- series.getRadii(zMin, zMax, series);
- }
- if (range > 0) {
- while (i--) {
- if (
- isNumber(data[i]) &&
- axis.dataMin <= data[i] &&
- data[i] <= axis.dataMax
- ) {
- radius = series.radii[i];
- pxMin = Math.min(
- ((data[i] - min) * transA) - radius,
- pxMin
- );
- pxMax = Math.max(
- ((data[i] - min) * transA) + radius,
- pxMax
- );
- }
- }
- }
- });
- // Apply the padding to the min and max properties
- if (activeSeries.length && range > 0 && !this.isLog) {
- pxMax -= axisLength;
- transA *= (
- axisLength +
- Math.max(0, pxMin) - // #8901
- Math.min(pxMax, axisLength)
- ) / axisLength;
- [['min', 'userMin', pxMin], ['max', 'userMax', pxMax]].forEach(
- function (keys) {
- if (pick(axis.options[keys[0]], axis[keys[1]]) === undefined) {
- axis[keys[0]] += keys[2] / transA;
- }
- }
- );
- }
- };
- /**
- * A `bubble` series. If the [type](#series.bubble.type) option is
- * not specified, it is inherited from [chart.type](#chart.type).
- *
- * @extends series,plotOptions.bubble
- * @excluding dataParser, dataURL, stack
- * @product highcharts highstock
- * @apioption series.bubble
- */
- /**
- * An array of data points for the series. For the `bubble` series type,
- * points can be given in the following ways:
- *
- * 1. An array of arrays with 3 or 2 values. In this case, the values correspond
- * to `x,y,z`. If the first value is a string, it is applied as the name of
- * the point, and the `x` value is inferred. The `x` value can also be
- * omitted, in which case the inner arrays should be of length 2\. Then the
- * `x` value is automatically calculated, either starting at 0 and
- * incremented by 1, or from `pointStart` and `pointInterval` given in the
- * series options.
- * ```js
- * data: [
- * [0, 1, 2],
- * [1, 5, 5],
- * [2, 0, 2]
- * ]
- * ```
- *
- * 2. An array of objects with named values. The following snippet shows only a
- * few settings, see the complete options set below. If the total number of
- * data points exceeds the series'
- * [turboThreshold](#series.bubble.turboThreshold), this option is not
- * available.
- * ```js
- * data: [{
- * x: 1,
- * y: 1,
- * z: 1,
- * name: "Point2",
- * color: "#00FF00"
- * }, {
- * x: 1,
- * y: 5,
- * z: 4,
- * name: "Point1",
- * color: "#FF00FF"
- * }]
- * ```
- *
- * @sample {highcharts} highcharts/series/data-array-of-arrays/
- * Arrays of numeric x and y
- * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
- * Arrays of datetime x and y
- * @sample {highcharts} highcharts/series/data-array-of-name-value/
- * Arrays of point.name and y
- * @sample {highcharts} highcharts/series/data-array-of-objects/
- * Config objects
- *
- * @type {Array<Array<(number|string),number>|Array<(number|string),number,number>|*>}
- * @extends series.line.data
- * @excluding marker
- * @product highcharts
- * @apioption series.bubble.data
- */
- /**
- * The size value for each bubble. The bubbles' diameters are computed
- * based on the `z`, and controlled by series options like `minSize`,
- * `maxSize`, `sizeBy`, `zMin` and `zMax`.
- *
- * @type {number}
- * @product highcharts
- * @apioption series.bubble.data.z
- */
- /**
- * @excluding enabled, enabledThreshold, height, radius, width
- * @apioption series.bubble.marker
- */
|