UNPKG

@chakra-ui/charts

Version:

Data visualization components for Chakra UI

278 lines (275 loc) 9.1 kB
"use strict"; "use client"; import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; import { defineStyle, Stack, Text, Flex, HStack, ColorSwatch, Span, Box, Separator } from '@chakra-ui/react'; import { createContext, useContext, useMemo } from 'react'; import { ResponsiveContainer } from 'recharts'; import { getProp } from '../use-chart.js'; const ChartContext = createContext({}); const useChartContext = () => useContext(ChartContext); const baseCss = defineStyle({ width: "100%", [`& :where(${[ ".recharts-cartesian-axis-tick-value", ".recharts-polar-angle-axis-tick-value", ".recharts-polar-radius-axis-tick-value", ".recharts-pie-label-text" ].join(", ")})`]: { fill: "fg.muted" }, "& .recharts-cartesian-axis .recharts-label": { fill: "fg", fontWeight: "medium" }, "& *": { outline: "none" }, "& svg": { overflow: "visible" } }); function ChartRoot(props) { const { children, css, chart, ...rest } = props; return /* @__PURE__ */ jsx(ChartContext.Provider, { value: chart, children: /* @__PURE__ */ jsx( Box, { aspectRatio: "landscape", textStyle: "xs", css: [baseCss, css], ...rest, children: /* @__PURE__ */ jsx(ResponsiveContainer, { children }) } ) }); } function ChartGradient(props) { const chart = useChartContext(); const { id, fillOpacity, stops } = props; return /* @__PURE__ */ jsx("linearGradient", { id, x1: "0", y1: "0", x2: "0", y2: "1", children: stops.map((stop, index) => /* @__PURE__ */ jsx( "stop", { offset: stop.offset, stopColor: chart.color(stop.color), stopOpacity: stop.opacity ?? fillOpacity }, index )) }); } const hAlignMap = { left: "flex-start", center: "center", right: "flex-end" }; function ChartLegend(props) { const { payload, verticalAlign = "bottom", align = "center", title, orientation, nameKey, spacing = "3", interaction = "hover" } = props; const chart = useChartContext(); const filteredPayload = payload?.filter( (item) => item.color !== "none" || item.type !== "none" ); if (!filteredPayload?.length) return null; const spacingValue = typeof spacing === "number" ? `${spacing}px` : chart.spacing(spacing); return /* @__PURE__ */ jsxs( Stack, { gap: "1.5", align: hAlignMap[align], pt: verticalAlign === "bottom" ? "3" : void 0, pb: verticalAlign === "top" ? "3" : void 0, children: [ title && /* @__PURE__ */ jsx(Text, { fontWeight: "medium", children: title }), /* @__PURE__ */ jsx( Flex, { "data-orientation": orientation, gap: spacingValue, direction: { _horizontal: "row", _vertical: "column" }, align: { _horizontal: "center", _vertical: "flex-start" }, flexWrap: "wrap", children: filteredPayload.map((item, index) => { const config = chart.getSeries(item); const seriesName = config?.name?.toString(); const name = getProp(item.payload, nameKey); return /* @__PURE__ */ jsxs( HStack, { gap: "1.5", _icon: { boxSize: "3" }, style: { opacity: chart.getSeriesOpacity(seriesName, 0.6) }, onClick: () => { if (interaction === "click" && seriesName) { chart.setHighlightedSeries( (prev) => prev === seriesName ? null : seriesName ); } }, onMouseEnter: () => { if (interaction === "hover" && seriesName) { chart.setHighlightedSeries(seriesName); } }, onMouseLeave: () => { if (interaction === "hover" && seriesName) { chart.setHighlightedSeries(null); } }, children: [ config?.icon || /* @__PURE__ */ jsx(ColorSwatch, { boxSize: "2", value: chart.color(config?.color) }), /* @__PURE__ */ jsx(Span, { color: "fg.muted", children: name || config?.label }) ] }, index ); }) } ) ] } ); } function ChartTooltip(props) { const { payload: payloadProp, label, labelFormatter, hideLabel, hideIndicator, hideSeriesLabel, showTotal, fitContent, nameKey, formatter, render } = props; const chart = useChartContext(); const payload = payloadProp?.filter( (item) => item.color !== "none" || item.type !== "none" ); const total = useMemo(() => chart.getPayloadTotal(payload), [payload, chart]); const tooltipLabel = useMemo(() => { const item = payload?.[0]; const itemLabel = `${getProp(item?.payload, nameKey) || label || item?.dataKey || "value"}`; return labelFormatter?.(itemLabel, payload ?? []) ?? itemLabel; }, [payload, labelFormatter, label, nameKey]); if (!payload?.length) return null; return /* @__PURE__ */ jsxs( Stack, { minW: fitContent ? void 0 : "8rem", gap: "1", rounded: "l2", bg: "bg.panel", px: "2.5", py: "1", textStyle: "xs", shadow: "md", children: [ !hideLabel && /* @__PURE__ */ jsx(Text, { fontWeight: "medium", children: tooltipLabel }), /* @__PURE__ */ jsx(Box, { children: payload.map((item, index) => { const config = chart.getSeries(item); if (render) return render(item.payload); const formatted = formatter ? formatter(item.value, config?.label || item.name) : item.value?.toLocaleString(); const [formattedValue, formattedName] = Array.isArray(formatted) ? formatted : [formatted, config?.label || item.name]; return /* @__PURE__ */ jsxs( Flex, { gap: "1.5", wrap: "wrap", align: "center", _icon: { boxSize: "2.5" }, children: [ config?.icon, config?.color && !config?.icon && !hideIndicator && /* @__PURE__ */ jsx( ColorSwatch, { rounded: "full", boxSize: "2", value: chart.color(config.color) } ), /* @__PURE__ */ jsxs(HStack, { justify: "space-between", flex: "1", children: [ !hideSeriesLabel && /* @__PURE__ */ jsx(Span, { color: "fg.muted", children: formattedName }), item.value && /* @__PURE__ */ jsx( Text, { fontFamily: "mono", fontWeight: "medium", fontVariantNumeric: "tabular-nums", children: formattedValue } ) ] }) ] }, index ); }) }), showTotal && total != null && /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx(Separator, { mt: "1" }), /* @__PURE__ */ jsxs(HStack, { gap: "1", justify: "space-between", pb: "1", children: [ /* @__PURE__ */ jsx(Span, { color: "fg.muted", children: "Total" }), /* @__PURE__ */ jsx( Text, { fontFamily: "mono", fontWeight: "medium", fontVariantNumeric: "tabular-nums", children: (() => { if (!formatter) return total.toLocaleString(); const formatted = formatter(total, ""); return Array.isArray(formatted) ? formatted[0] : formatted; })() } ) ] }) ] }) ] } ); } const isPolarViewBox = (viewBox) => "cx" in viewBox && "cy" in viewBox; function ChartRadialText(props) { const { viewBox, title, description, gap = 24, fontSize = "2rem" } = props; const chart = useChartContext(); if (!viewBox || !isPolarViewBox(viewBox)) return null; return /* @__PURE__ */ jsxs( "text", { x: viewBox.cx, y: viewBox.cy, textAnchor: "middle", dominantBaseline: "middle", fill: chart.color("fg"), children: [ /* @__PURE__ */ jsx( "tspan", { x: viewBox.cx, y: viewBox.cy, style: { fontSize, fontWeight: 600 }, children: title } ), /* @__PURE__ */ jsx( "tspan", { x: viewBox.cx, y: (viewBox.cy || 0) + gap, style: { fill: chart.color("fg.muted") }, children: description } ) ] } ); } export { ChartGradient, ChartLegend, ChartRadialText, ChartRoot, ChartTooltip };