@gooddata/react-components
Version:
GoodData.UI - A powerful JavaScript library for building analytical applications
166 lines (148 loc) • 6.96 kB
text/typescript
// tslint:disable-line
/* *
* (c) 2010-2019 Torstein Honsi
*
* Source: https://github.com/highcharts/highcharts/blob/v7.1.1/js/parts-more/BubbleSeries.js
* License: www.highcharts.com/license
*
* Modified by Lan Huynh to to support
* - Set default size for bubbles in bubble chart where size value is not provided
* - Fix bubbles is not rendered with min/max config
*/
import isNil = require("lodash/isNil");
import Highcharts from "../highchartsEntryPoint";
import { IHighchartsAxisExtend } from "../../../../../interfaces/HighchartsExtend";
export interface IBubbleAxis extends IHighchartsAxisExtend {
allowZoomOutside?: boolean;
dataMin?: number;
dataMax?: number;
options?: any;
userMin?: number;
userMax?: number;
}
export interface IBubbleSeries extends Highcharts.Series {
bubblePadding?: boolean;
minPxSize?: number;
maxPxSize?: number;
zData?: Array<number | null>;
radii?: Array<number | null>;
getRadii(zMin: number, zMax: number, series: Highcharts.Series): number | null;
}
export function renderBubbles(HighchartsInstance: any) {
const wrap = HighchartsInstance.wrap;
const pInt = HighchartsInstance.pInt;
const arrayMax = HighchartsInstance.arrayMax;
const arrayMin = HighchartsInstance.arrayMin;
const pick = HighchartsInstance.pick;
const isNumber = HighchartsInstance.isNumber;
if (HighchartsInstance.seriesTypes.bubble) {
// Set default size for bubbles in bubble chart where size value is not provided
wrap(HighchartsInstance.seriesTypes.bubble.prototype, "getRadius", function(
proceed: any,
zMin: number,
zMax: number,
minSize: number,
maxSize: number,
value: number,
) {
let radius = proceed.apply(this, [zMin, zMax, minSize, maxSize, value]);
if (isNaN(value) && isNil(radius)) {
// Relative size, a number between 0 and 1 (default is 0.5)
// Use Math.sqrt for buble is sized by area
radius = Math.ceil(minSize + Math.sqrt(0.5) * (maxSize - minSize)) / 2;
}
return radius;
});
// #SD-479 fix bubbles is not rendered with min/max config
wrap(HighchartsInstance.Axis.prototype, "beforePadding", function(_proceed: any) {
const axis: IBubbleAxis = this;
const axisLength: number = this.len;
const chart: Highcharts.Chart = this.chart;
const isXAxis: boolean = this.isXAxis;
const min: number = this.min;
const range = this.max - min;
const activeSeries: IBubbleSeries[] = [];
let pxMin: number = 0;
let pxMax: number = axisLength;
let transA = axisLength / range;
let zMin = Number.MAX_VALUE;
let zMax = -Number.MAX_VALUE;
function translateSizeToPixel(size: string | number): number {
const smallestSize = Math.min(chart.plotWidth, chart.plotHeight);
const isPercent = typeof size === "string" ? /%$/.test(size) : false;
const pxSize = pInt(size);
return isPercent ? (smallestSize * pxSize) / 100 : pxSize;
}
// Handle padding on the second pass, or on redraw
this.series.forEach((series: IBubbleSeries) => {
const seriesOptions = series.options;
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
const minSize = translateSizeToPixel(seriesOptions.minSize);
const maxSize = translateSizeToPixel(seriesOptions.maxSize);
series.minPxSize = minSize;
// Prioritize min size if conflict to make sure bubbles are
// always visible. #5873
series.maxPxSize = Math.max(maxSize, minSize);
// Find the min and max Z
const zData = series.zData.filter(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((series: IBubbleSeries) => {
const dataKey: string = isXAxis ? "xData" : "yData";
const data = series[dataKey];
let i = data.length;
if (isXAxis) {
series.getRadii(zMin, zMax, series);
}
if (range > 0) {
while (i--) {
if (isNumber(data[i]) && axis.dataMin <= data[i] && data[i] <= axis.dataMax) {
const 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;
// Note for Highchart upgrading later:
// - Modified the calculation of transA only
// - And transform from javascript to typescripts
const pxRange = Math.abs(Math.max(0, pxMin) - Math.min(pxMax, axisLength));
transA *= (axisLength + pxRange) / axisLength;
if (pick(axis.options.min, axis.userMin) === undefined) {
axis.min += pxMin / transA;
}
if (pick(axis.options.max, axis.userMax) === undefined) {
axis.max += pxMax / transA;
}
}
});
}
}