@gooddata/react-components
Version:
GoodData.UI - A powerful JavaScript library for building analytical applications
203 lines (172 loc) • 6.64 kB
text/typescript
// (C) 2007-2019 GoodData Corporation
import flatMap = require("lodash/flatMap");
import get = require("lodash/get");
import Highcharts from "./highchartsEntryPoint";
import {
isStacked,
IRectBySize,
isIntersecting,
pointInRange,
IAxisRange,
IAxisRangeForAxes,
} from "./helpers";
import { isAreaChart, isOneOfTypes } from "../../utils/common";
import { IDataLabelsVisible } from "../../../../interfaces/Config";
import { BLACK_LABEL, WHITE_LABEL, whiteDataLabelTypes } from "../../../../constants/label";
export function isLabelOverlappingItsShape(point: any) {
const { dataLabel, shapeArgs } = point;
if (dataLabel && shapeArgs) {
// shapeArgs for point hidden by legend is undefined
if (shapeArgs.width === undefined) {
return dataLabel.width > shapeArgs.r * 2 || dataLabel.height > shapeArgs.r * 2;
}
return dataLabel.width > shapeArgs.width || dataLabel.height > shapeArgs.height;
}
return false;
}
export const getDataLabelsGdcVisible = (chart: any): boolean | string =>
get(chart, "options.plotOptions.gdcOptions.dataLabels.visible", "auto");
const isLabelsStackedFromYAxis = (chart: any) =>
get(chart, "userOptions.yAxis.0.stackLabels.enabled", false) ||
get(chart, "userOptions.yAxis.1.stackLabels.enabled", false);
export const areLabelsStacked = (chart: any) => isLabelsStackedFromYAxis(chart) && isStacked(chart);
export const hasDataLabel = (point: any) => point.dataLabel;
export const hasShape = (point: any) => point.shapeArgs;
export const hasLabelInside = (point: any) => {
const verticalAlign = get(point, "dataLabel.alignOptions.verticalAlign", "");
return verticalAlign === "middle";
};
export const minimizeDataLabel = (point: any) => {
const { dataLabel } = point;
if (dataLabel) {
dataLabel.width = 0;
dataLabel.height = 0;
}
};
export const hideDataLabel = (point: any) => {
const { dataLabel } = point;
if (dataLabel) {
dataLabel.hide();
}
};
export const showDataLabel = (point: any) => {
const { dataLabel } = point;
if (dataLabel) {
dataLabel.show();
}
};
export const hideDataLabels = (points: any) => {
points.filter(hasDataLabel).forEach(hideDataLabel);
};
export const showDataLabels = (points: any) => {
points.filter(hasDataLabel).forEach(showDataLabel);
};
export interface IInsideResult {
vertically: boolean;
horizontally: boolean;
}
export function showDataLabelInAxisRange(point: any, value: number, axisRangeForAxes: IAxisRangeForAxes) {
const isSecondAxis = get(point, "series.yAxis.opposite", false);
const axisRange: IAxisRange = axisRangeForAxes[isSecondAxis ? "second" : "first"];
const isInsideAxisRange: boolean = pointInRange(value, axisRange);
if (!isInsideAxisRange) {
hideDataLabel(point);
}
}
export function showStackLabelInAxisRange(point: any, axisRangeForAxes: IAxisRangeForAxes) {
const isSecondAxis = get(point, "series.yAxis.opposite", false);
const axisRange: IAxisRange = axisRangeForAxes[isSecondAxis ? "second" : "first"];
const end = point.stackY || point.total;
const start = end - point.y;
const isWholeUnderMin: boolean = start <= axisRange.minAxisValue && end <= axisRange.minAxisValue;
const isWholeAboveMax: boolean = start >= axisRange.maxAxisValue && end >= axisRange.maxAxisValue;
if (isWholeUnderMin || isWholeAboveMax) {
hideDataLabel(point);
}
}
export const hideAllLabels = ({ series }: any) => hideDataLabels(flatMap(series, s => s.points));
export const showAllLabels = ({ series }: any) => showDataLabels(flatMap(series, s => s.points));
export function getDataLabelAttributes(point: any): IRectBySize {
const dataLabel = get(point, "dataLabel", null);
const parentGroup = get(point, "dataLabel.parentGroup", null);
const labelSafeOffset = -100; // labels outside axis range have typically -9999, hide them
const labelVisible = dataLabel && dataLabel.x > labelSafeOffset && dataLabel.y > labelSafeOffset;
if (dataLabel && parentGroup && labelVisible) {
return {
x: dataLabel.x + parentGroup.translateX,
y: dataLabel.y + parentGroup.translateY,
width: dataLabel.width,
height: dataLabel.height,
};
}
return {
x: 0,
y: 0,
width: 0,
height: 0,
};
}
export function intersectsParentLabel(point: any, points: any) {
const pointParent = parseInt(point.parent, 10);
// Highchart 7 doesn't render dataLabel at points which have null value
const pointLabelShape = point.dataLabel;
if (isNaN(pointParent) || !pointLabelShape) {
return false;
}
const parentPoint = points[pointParent];
const parentLabelShape = parentPoint.dataLabel;
return isIntersecting(pointLabelShape, parentLabelShape);
}
function isTruncatedByMin(shape: any, chart: any) {
return shape.y + shape.height > chart.clipBox.height;
}
function isTruncatedByMax(shape: any) {
return shape.y < 0;
}
// works for both column/bar chart thanks bar's 90deg rotation
export function getShapeVisiblePart(shape: any, chart: any, wholeSize: number) {
if (isTruncatedByMax(shape)) {
return shape.y + shape.height;
} else if (isTruncatedByMin(shape, chart)) {
return chart.clipBox.height - shape.y;
}
return wholeSize;
}
export function getLabelStyle(type: string, stacking: string) {
if (isAreaChart(type)) {
return BLACK_LABEL;
}
return stacking || isOneOfTypes(type, whiteDataLabelTypes) ? WHITE_LABEL : BLACK_LABEL;
}
/**
* A callback function to format data label and `this` is required by Highchart
* Ref: https://api.highcharts.com/highcharts/yAxis.labels.formatter
*/
export function formatAsPercent(unit: number = 100): string {
const val = parseFloat((this.value * unit).toPrecision(14));
return `${val}%`;
}
export function isInPercent(format: string = ""): boolean {
return format.includes("%");
}
export function getLabelsVisibilityConfig(visible: IDataLabelsVisible): Highcharts.DataLabelsOptionsObject {
switch (visible) {
case "auto":
return {
enabled: true,
allowOverlap: false,
};
case true:
return {
enabled: true,
allowOverlap: true,
};
case false:
return {
enabled: false,
};
default:
// keep decision on each chart for `undefined`
return {};
}
}