bento-charts
Version:
Charts library for Bento-platform
170 lines • 10.4 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useCallback, useMemo, useState } from 'react';
import { PieChart, Pie, Cell, Curve, Tooltip, Sector, ResponsiveContainer, } from 'recharts';
import { TOOLTIP_STYLE, TOOLTIP_OTHER_PROPS, LABEL_STYLE, COUNT_STYLE, CHART_MISSING_FILL, RADIAN, LABEL_THRESHOLD, COUNT_TEXT_STYLE, TEXT_STYLE, OTHER_KEY, } from '../../constants/chartConstants';
import { useChartTheme, useChartTranslation, useChartThreshold, useChartMaxLabelChars, } from '../../ChartConfigProvider';
import { polarToCartesian, useTransformedChartData } from '../../util/chartUtils';
import NoData from '../NoData';
import ChartWrapper from './ChartWrapper';
var labelShortName = function (name, maxChars) {
if (name.length <= maxChars) {
return name;
}
// removing 3 character cause ... s add three characters
return "".concat(name.substring(0, maxChars - 3), "\u2026");
};
var _entryFill = function (entry, index, theme) {
return entry.name.toLowerCase() === 'missing' ? CHART_MISSING_FILL : theme[index % theme.length];
};
// Prevents the last segment from having the same fill as the first segment (unless "missing") to ensure visual distinction.
var getPieSegmentFill = function (entry, index, data, theme) {
var fill = _entryFill(entry, index, theme);
if (index === data.length - 1 && entry.name.toLowerCase() !== 'missing') {
var firstEntry = data[0];
var firstFill = _entryFill(firstEntry, 0, theme);
if (fill === firstFill) {
fill = theme[(index + 1) % theme.length];
}
}
return fill;
};
var BentoPie = function (_a) {
var height = _a.height, width = _a.width, onClick = _a.onClick, _b = _a.sort, sort = _b === void 0 ? true : _b, _c = _a.colorTheme, colorTheme = _c === void 0 ? 'default' : _c, chartThreshold = _a.chartThreshold, maxLabelChars = _a.maxLabelChars, params = __rest(_a, ["height", "width", "onClick", "sort", "colorTheme", "chartThreshold", "maxLabelChars"]);
var t = useChartTranslation();
var theme = useChartTheme().pie[colorTheme].fill;
var defaultChartThreshold = useChartThreshold();
var defaultMaxLabelChars = useChartMaxLabelChars();
var resolvedChartThreshold = chartThreshold !== null && chartThreshold !== void 0 ? chartThreshold : defaultChartThreshold;
var resolvedMaxLabelChars = maxLabelChars !== null && maxLabelChars !== void 0 ? maxLabelChars : defaultMaxLabelChars;
var _d = useState(undefined), activeIndex = _d[0], setActiveIndex = _d[1];
// ##################### Data processing #####################
var transformedData = useTransformedChartData(params, true, sort);
var _e = useMemo(function () {
var data = __spreadArray([], transformedData, true);
// combining sections with less than chartThreshold
var sum = data.reduce(function (acc, e) { return acc + e.y; }, 0);
var length = data.length;
var threshold = resolvedChartThreshold * sum;
var dataAboveThreshold = data.filter(function (e) { return e.y > threshold; });
// length - 1 intentional: if there is just one category below threshold, the "Other" category is not necessary.
data = dataAboveThreshold.length === length - 1 ? data : dataAboveThreshold;
if (data.length !== length) {
data.push({
x: t[OTHER_KEY],
y: sum - data.reduce(function (acc, e) { return acc + e.y; }, 0),
id: OTHER_KEY,
});
}
return {
data: data.map(function (e) { return (__assign({ name: e.x, value: e.y }, e)); }),
sum: sum,
};
}, [t, transformedData, resolvedChartThreshold]), data = _e.data, sum = _e.sum;
// ##################### Rendering #####################
var onEnter = useCallback(function (_data, index) {
setActiveIndex(index);
}, []);
var onHover = useCallback(function (data, _index, e) {
var target = e.target;
if (onClick && target && data.name !== t[OTHER_KEY])
target.style.cursor = 'pointer';
}, [t, onClick]);
var onLeave = useCallback(function () {
setActiveIndex(undefined);
}, []);
if (data.length === 0) {
return _jsx(NoData, { height: height });
}
return (_jsx(ChartWrapper, { responsive: typeof width !== 'number', children: _jsx(ResponsiveContainer, { width: width !== null && width !== void 0 ? width : '100%', height: height, children: _jsxs(PieChart, { children: [_jsx(Pie, { data: data, dataKey: "value", cx: "50%", cy: "50%", innerRadius: "25%", outerRadius: "55%", label: renderLabel(resolvedMaxLabelChars), labelLine: false, isAnimationActive: false, onMouseEnter: onEnter, onMouseLeave: onLeave, onMouseOver: onHover, activeIndex: activeIndex, activeShape: RenderActiveLabel, onClick: onClick, children: data.map(function (entry, index) { return (_jsx(Cell, { fill: getPieSegmentFill(entry, index, data, theme) }, index)); }) }), _jsx(Tooltip, __assign({}, TOOLTIP_OTHER_PROPS, { content: _jsx(CustomTooltip, { totalCount: sum }), isAnimationActive: false }))] }) }) }));
};
var toNumber = function (val, defaultValue) {
if (val && typeof val === 'string') {
return Number(val);
}
else if (val && typeof val === 'number') {
return val;
}
return defaultValue || 0;
};
var renderLabel = function (resolvedMaxLabelChars) {
var BentoPieLabel = function (params) {
var fill = params.fill, payload = params.payload, index = params.index, activeIndex = params.activeIndex;
var percent = params.percent || 0;
var midAngle = params.midAngle || 0;
// skip rendering this static label if the sector is selected.
// this will let the 'renderActiveState' draw without overlapping.
// also, skip rendering if segment is too small a percentage (avoids label clutter)
if (index === activeIndex || percent < LABEL_THRESHOLD) {
return;
}
var outerRadius = toNumber(params.outerRadius);
var cx = toNumber(params.cx);
var cy = toNumber(params.cy);
var name = payload.name === 'null' ? '(Empty)' : payload.name;
var sin = Math.sin(-RADIAN * midAngle);
var cos = Math.cos(-RADIAN * midAngle);
var sx = cx + (outerRadius + 10) * cos;
var sy = cy + (outerRadius + 10) * sin;
var mx = cx + (outerRadius + 20) * cos;
var my = cy + (outerRadius + 20) * sin;
var ex = mx + (cos >= 0 ? 1 : -1) * 22;
var ey = my;
var textAnchor = cos >= 0 ? 'start' : 'end';
var currentTextStyle = __assign(__assign({}, TEXT_STYLE), { fontWeight: payload.selected ? 'bold' : 'normal', fontStyle: payload.name === 'null' ? 'italic' : 'normal' });
var offsetRadius = 20;
var startPoint = polarToCartesian(cx, cy, outerRadius, midAngle);
var endPoint = polarToCartesian(cx, cy, outerRadius + offsetRadius, midAngle);
var lineProps = __assign(__assign({}, params), { fill: 'none', stroke: fill, points: [startPoint, endPoint] });
return (_jsxs("g", { children: [_jsx(Curve, __assign({}, lineProps, { type: "linear", className: "recharts-pie-label-line" })), _jsx("path", { d: "M".concat(sx, ",").concat(sy, "L").concat(mx, ",").concat(my, "L").concat(ex, ",").concat(ey), stroke: fill, fill: "none" }), _jsx("circle", { cx: ex, cy: ey, r: 2, fill: fill, stroke: "none" }), _jsx("text", { x: ex + (cos >= 0 ? 1 : -1) * 12, y: ey + 3, textAnchor: textAnchor, style: currentTextStyle, children: labelShortName(name, resolvedMaxLabelChars) }), _jsx("text", { x: ex + (cos >= 0 ? 1 : -1) * 12, y: ey, dy: 14, textAnchor: textAnchor, style: COUNT_TEXT_STYLE, children: "(".concat(payload.value, ")") })] }));
};
BentoPieLabel.displayName = BentoPieLabel;
return BentoPieLabel;
};
var RenderActiveLabel = function (params) {
var cx = params.cx, cy = params.cy, innerRadius = params.innerRadius, outerRadius = params.outerRadius, startAngle = params.startAngle, endAngle = params.endAngle, fill = params.fill;
// render arc around active segment
return (_jsxs("g", { children: [_jsx(Sector, { cx: cx, cy: cy, startAngle: startAngle, endAngle: endAngle, innerRadius: innerRadius, outerRadius: outerRadius, fill: fill }), _jsx(Sector, { cx: cx, cy: cy, startAngle: startAngle, endAngle: endAngle, innerRadius: outerRadius + 6, outerRadius: outerRadius + 10, fill: fill })] }));
};
var CustomTooltip = function (_a) {
var active = _a.active, payload = _a.payload, totalCount = _a.totalCount;
if (!active) {
return null;
}
var name = payload ? payload[0].name : '';
var value = payload ? payload[0].value : 0;
var percentage = totalCount ? Math.round((value / totalCount) * 100) : 0;
return name !== 'other' ? (_jsxs("div", { style: TOOLTIP_STYLE, children: [_jsx("p", { style: LABEL_STYLE, children: name }), _jsxs("p", { style: COUNT_STYLE, children: [' ', value, " (", percentage, "%)"] })] })) : (_jsx("div", { children: "No data" }));
};
export default BentoPie;
//# sourceMappingURL=BentoPie.js.map