UNPKG

bento-charts

Version:
170 lines 10.4 kB
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