@chakra-ui/charts
Version:
Data visualization components for Chakra UI
278 lines (275 loc) • 9.1 kB
JavaScript
"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 };