UNPKG

bento-charts

Version:
102 lines 7.85 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; }; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { createContext, useCallback, useContext, useMemo } from 'react'; import { Bar, BarChart, CartesianGrid, Cell, Label, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts'; import { TOOLTIP_STYLE, COUNT_STYLE, LABEL_STYLE, MAX_TICK_LABEL_CHARS, TITLE_STYLE, TICKS_SHOW_ALL_LABELS_BELOW, UNITS_LABEL_OFFSET, TICK_MARGIN, COUNT_KEY, } from '../../constants/chartConstants'; import { useChartTranslation } from '../../ChartConfigProvider'; import NoData from '../NoData'; import { useTransformedChartData } from '../../util/chartUtils'; import ChartWrapper from './ChartWrapper'; var tickFormatter = function (tickLabel) { if (tickLabel.length <= MAX_TICK_LABEL_CHARS) { return tickLabel; } return "".concat(tickLabel.substring(0, MAX_TICK_LABEL_CHARS), "..."); }; var BAR_CHART_MARGINS = { bottom: 100, right: 0 }; var BAR_CHART_MARGIN_TOP_COUNTS = 35; var BAR_CHART_MARGIN_TOP_NO_COUNTS = 10; var MIN_BAR_WIDTH_FOR_COUNTS = 11; var BAR_LABEL_SPACING = 4; // Spacing of a BarLabel above the actual bar, in pixels. var BAR_LABEL_APPROX_NUMBER_WIDTH = 9.2; var BaseBarChart = function (_a) { var height = _a.height, width = _a.width, units = _a.units, title = _a.title, onClick = _a.onClick, onChartClick = _a.onChartClick, chartFill = _a.chartFill, otherFill = _a.otherFill, showBarCounts = _a.showBarCounts, barCountFillMode = _a.barCountFillMode, params = __rest(_a, ["height", "width", "units", "title", "onClick", "onChartClick", "chartFill", "otherFill", "showBarCounts", "barCountFillMode"]); showBarCounts = showBarCounts !== null && showBarCounts !== void 0 ? showBarCounts : true; // Show bar counts by default var t = useChartTranslation(); var margins = useMemo(function () { return (__assign(__assign({}, BAR_CHART_MARGINS), { // Top margin needs to accommodate bar count labels: top: showBarCounts ? BAR_CHART_MARGIN_TOP_COUNTS : BAR_CHART_MARGIN_TOP_NO_COUNTS })); }, [showBarCounts]); var fill = function (entry, index) { return entry.x === 'missing' ? otherFill : chartFill[index % chartFill.length]; }; var data = useTransformedChartData(params, true); var totalCount = data.reduce(function (sum, e) { return sum + e.y; }, 0); var onHover = useCallback(function (_data, _index, e) { var target = e.target; if (onClick && target) target.style.cursor = 'pointer'; }, [onClick]); if (data.length === 0) { return _jsx(NoData, { height: height }); } // string length of widest label var valuesMaxStringLength = Math.max.apply(Math, data.map(function (d) { var _a; return ((_a = d.y) !== null && _a !== void 0 ? _a : 0).toString().length; })); // Regarding XAxis.ticks below: // The weird conditional is added from https://github.com/recharts/recharts/issues/2593#issuecomment-1311678397 // Basically, if data is empty, Recharts will default to a domain of [0, "auto"] and our tickFormatter trips up // on formatting a non-string. This hack manually overrides the ticks for the axis and blanks it out. // - David L, 2023-01-03 return (_jsx(BarLabelContext.Provider, { value: { barCountFillMode: barCountFillMode !== null && barCountFillMode !== void 0 ? barCountFillMode : 'neutral' }, children: _jsxs(ChartWrapper, { responsive: typeof width !== 'number', children: [_jsx("div", { style: TITLE_STYLE, children: title }), _jsx(ResponsiveContainer, { width: width !== null && width !== void 0 ? width : '100%', height: height, children: _jsxs(BarChart, { data: data, margin: margins, onClick: onChartClick, children: [_jsx(XAxis, { dataKey: "x", height: 20, angle: -45, ticks: data.length ? undefined : [''], tickFormatter: tickFormatter, tickMargin: TICK_MARGIN, tick: { textAnchor: 'end' }, textAnchor: "end", interval: data.length < TICKS_SHOW_ALL_LABELS_BELOW ? 0 : 'preserveStartEnd', children: _jsx(Label, { value: units, offset: UNITS_LABEL_OFFSET, position: "insideBottom" }) }), _jsx(YAxis, { children: _jsx(Label, { value: t[COUNT_KEY], offset: -10, position: "left", angle: 270 }) }), _jsx(CartesianGrid, { strokeDasharray: "3 3", vertical: false }), _jsx(Tooltip, { content: _jsx(BarTooltip, { totalCount: totalCount }) }), _jsx(Bar, { dataKey: "y", isAnimationActive: false, onClick: onClick, onMouseEnter: onHover, maxBarSize: 70, label: showBarCounts ? _jsx(BarLabel, { valuesMaxStringLength: valuesMaxStringLength }) : undefined, children: data.map(function (entry, index) { return (_jsx(Cell, { fill: fill(entry, index) }, entry.x)); }) })] }) })] }) })); }; var BarTooltip = function (_a) { var _b, _c, _d; var active = _a.active, payload = _a.payload, totalCount = _a.totalCount; if (!active) { return null; } var name = (payload && ((_c = (_b = payload[0]) === null || _b === void 0 ? void 0 : _b.payload) === null || _c === void 0 ? void 0 : _c.x)) || ''; var value = (payload && ((_d = payload[0]) === null || _d === void 0 ? void 0 : _d.value)) || 0; var percentage = totalCount ? Math.round((value / totalCount) * 100) : 0; return (_jsxs("div", { style: TOOLTIP_STYLE, children: [_jsx("p", { style: LABEL_STYLE, children: name }), _jsxs("p", { style: COUNT_STYLE, children: [value, " (", percentage, "%)"] })] })); }; var BarLabelContext = createContext({ barCountFillMode: 'neutral' }); /** * Component for rendering bar counts directly above bars in the plot. */ var BarLabel = function (_a) { var x = _a.x, y = _a.y, width = _a.width, value = _a.value, fill = _a.fill, valuesMaxStringLength = _a.valuesMaxStringLength; var barCountFillMode = useContext(BarLabelContext).barCountFillMode; // Funky conversion to placate TypeScript. In reality, width should always be a number here, the Recharts types are // just incorrect or something. // noinspection SuspiciousTypeOfGuard var finalWidth = typeof width === 'string' ? MIN_BAR_WIDTH_FOR_COUNTS : (width !== null && width !== void 0 ? width : 0); // Flip the labels to vertical text when the bar width is (roughly) less than the width of widest count // noinspection SuspiciousTypeOfGuard var vertical = finalWidth < valuesMaxStringLength * BAR_LABEL_APPROX_NUMBER_WIDTH; // noinspection SuspiciousTypeOfGuard var xPos = typeof x === 'number' ? x + finalWidth / 2 : x; return (_jsx("g", { transform: "translate(".concat(xPos, ", ").concat(y, ")"), children: _jsx("text", { textAnchor: vertical ? 'start' : 'middle', dominantBaseline: vertical ? 'central' : undefined, transform: vertical ? 'rotate(-90)' : undefined, letterSpacing: vertical ? -1 : undefined, dy: vertical ? 0 : -1 * BAR_LABEL_SPACING, dx: vertical ? BAR_LABEL_SPACING : 0, fill: barCountFillMode === 'neutral' ? '#666666' : fill, children: finalWidth < MIN_BAR_WIDTH_FOR_COUNTS || value === 0 ? '' : value }) })); }; export default BaseBarChart; //# sourceMappingURL=BaseBarChart.js.map