Chart
chartWraps responsive Recharts area, bar, line, pie, radar, and radial charts with theme tokens, axis sizing, and series legends.
Usage
Area Chart
data + series + xKey three-piece set; multi-sequence overlay, color progression chart token adaptive light and dark.
<AreaChart
data={data}
series={[{ key: "revenue", label: "Revenue (thousand yuan)" }, { key: "orders", label: "Order" }]}
xKey="month"
/>Polyline / Column
The same data can be exchanged for LineChart / BarChart rendering.
<>
<LineChart data={data} series={series} xKey="month" />
<BarChart data={data} series={series} xKey="month" />
</>Legend
Multi-sequence diagrams do not provide legends, so readers have no way of knowing which line is which sequence. legend opens a row of color points (take color from Dot, which is homologous to the sequence color); "top" / "bottom" selects the position. height is still the total height of the component - the canvas becomes shorter accordingly and does not increase the total height.
<AreaChart data={data} series={series} xKey="month" legend />Stacking
stacked enables multi-sequence stacking (Area/Bar takes effect).
<BarChart data={data} series={series} xKey="month" stacked />Horizontal columnar (CJK category adaptive axis width)
horizontal Move the category to the Y axis. The axis width is adapted according to the longest label by default (CJK full-angle estimated width, 48–160px), and Chinese characters are no longer truncated; the transmission yAxisWidth must be accurately controlled.
<BarChart
horizontal
data={[
{ stage: "Audio Decoding", p50: 105 },
{ stage: "ASR identification", p50: 620 },
{ stage: "The first sentence of LLM", p50: 890 },
{ stage: "TTS first tone", p50: 760 },
]}
xKey="stage"
series={[{ key: "p50", label: "P50 Time-consuming (ms)" }]}
/>Pie Chart/Donut
Flat {name,value} data; donut center hollowed.
<>
<PieChart data={[{ name: "Search", value: 420 }, { name: "Direct", value: 280 }]} />
<PieChart donut data={[{ name: "Search", value: 420 }, { name: "Direct", value: 280 }]} />
</>Dual Y axes
ComposedChart gives bars and lines their own value axis, so two series whose units differ by orders of magnitude can share one chart (matching echarts' dual axes).
<ComposedChart
data={data}
xKey="month"
series={[
{ key: "revenue", label: "Revenue (k)", type: "bar" },
{ key: "orders", label: "Orders", type: "line", axis: "right" },
]}
leftAxisLabel="Revenue (k)"
rightAxisLabel="Orders"
legend
/>Reference lines
referenceLines draws target lines, average lines, and the 80% line of a Pareto chart (matching echarts markLine).
<BarChart
data={data}
series={[{ key: "revenue", label: "Revenue (k)" }]}
xKey="month"
referenceLines={[{ y: 100, label: "Target" }]}
/>Radar chart
Comparison of multi-dimensional capabilities, xKey is the dimension field, multi-sequence superposition.
<RadarChart
data={radarData}
series={[{ key: "Current" }, { key: "Benchmark" }]}
xKey="dim"
/>Radar with per-axis full scale
axisMax gives every angle axis its own full scale, so shapes stay comparable across three orders of magnitude; the tooltip still shows the original values.
<RadarChart
data={dims}
series={[{ key: "hubin", label: "Hubin" }, { key: "xinjiekou", label: "Xinjiekou" }]}
xKey="dim"
axisMax={{ Revenue: 500000, Orders: 800, "Avg order": 600, Members: 4000, "Return rate": 100 }}
/>When to use
Use Chart for complete dashboard trends, distributions, and comparisons with one or more series. Use Stat or Statistic for one KPI and Sparkline for a lightweight inline trend.
Import
import { AreaChart, BarChart, ComposedChart, LineChart, PieChart, RadarChart, RadialChart, chartColor } from "@hulianui/ui"Props
AreaChart / BarChart / LineChart / ComposedChart / RadarChart (Cartesian)
| Name | Type | Default | Description |
|---|---|---|---|
| data* | TDatum[] | - | Row data. |
| series* | ChartSeries[] | - | { key, label?, color? }; colors default by index to chart tokens. |
| xKey* | string | - | Horizontal-axis field. |
| height | number | 280 | Explicit SSR-safe height. |
| stacked | boolean | false | Stacks AreaChart or BarChart series. |
| legend | boolean | "top" | "bottom" | false (true for RadarChart) | Series legend with a color dot and label. true is equivalent to "bottom"; false turns it off. Dot colors come from the same source as the series through Dot's color prop. The default splits by chart family: the Cartesian three default to off, while RadarChart defaults to on because it has always shipped a legend. |
| legendScroll | boolean | false | Keeps the legend on a single horizontally scrollable row (matching echarts' legend.type: "scroll"). The default wraps and centers, which stacks into several rows and squeezes the canvas once there are many series. Enable it beyond ~8 series. |
| horizontal | boolean | false | BarChart-only horizontal orientation. |
| yAxisWidth | number | Adaptive | BarChart-only category-axis width; horizontal mode estimates 48-160 px through categoryAxisWidth. |
| radiusAxis | boolean | true | RadarChart-only radius-axis tick numbers (0 15 30 …). Pass false to keep only the grid rings and angle labels, which is what echarts' radar renders by default (its axisLabel.show defaults to false). If your radar has many series or densely filled data, turning it off is recommended (see the pitfalls below). |
| onPointClick | (info: { datum, index, seriesKey? }) => void | - | Data-point click, matching echarts' chart.on('click') for drill-down. Hit detection shares its rule with the tooltip: if the tooltip is showing, a click always fires, so there is no need to hit a 2px line exactly; clicks on empty canvas or on an axis do not fire. seriesKey is not guaranteed: with a shared tooltip recharts does not consider any single series to be hit. RadarChart does not have this prop. |
| referenceLines | ChartReferenceLine[] | - | Value-axis reference lines, matching echarts' markLine: the 80/95 lines of a Pareto chart, an average line, a target line. See the ChartReferenceLine table below. RadarChart does not have this prop. |
| yAxisDomain | [number | "auto", number | "auto"] | auto | Value-axis range [min, max]; "auto" keeps that end data-driven (matches echarts' yAxis.min/max). The typical use is locking a percentage axis to [0, 100]: with auto, "82%" draws near the top and reads as almost-full, and out-of-range referenceLines are silently discarded. If data exceeds the locked range the axis expands to fit (paths are never clipped). On a horizontal bar chart the value axis is the X axis, still governed by this prop. ComposedChart uses `leftAxisDomain`/`rightAxisDomain` instead; RadarChart does not have this prop. |
| series[].type | "bar" | "line" | "area" | "bar" | ComposedChart only: how this series is drawn. |
| series[].axis | "left" | "right" | "left" | ComposedChart only: which value axis this series reads. |
| leftAxisLabel / rightAxisLabel | string | - | ComposedChart only: axis titles. With two different units in play, unlabeled axes leave readers unable to tell which line reads which axis. |
| leftAxisDomain / rightAxisDomain | [number | "auto", number | "auto"] | auto | ComposedChart only: per-axis domain, same semantics as yAxisDomain. The right axis' biggest user is a percentage axis (Pareto cumulative share, return rate, attainment); lock rightAxisDomain={[0, 100]} and referenceLines={[{ y: 95, axis: "right" }]} is no longer discarded for being out of range. |
| axisMax | Record<string, number> | - | RadarChart only: full scale per angle axis, keyed by the angle-axis value. See "Radars with mismatched units". |
| className | string | - | Custom class, commonly used for width. |
PieChart / RadialChart (flat data)
| Name | Type | Default | Description |
|---|---|---|---|
| data* | ChartDatum[] | - | Flat { name, value, color? } data. |
| donut | boolean | false | Creates a center hole in PieChart. |
| height | number | 280 | Chart height. |
| legend | boolean | "top" | "bottom" | true | Legend built from data[].name, same semantics as above but on by default because these two have always shipped a legend. Pass false to turn it off, which is required before drawing your own; otherwise two legends render side by side. |
| legendScroll | boolean | false | Same as above: keeps the legend on a single horizontally scrollable row. |
| onPointClick | (info: { datum, index }) => void | - | Fires when a slice is clicked (for drill-down). Hit detection is per sector; clicks on empty space do not fire. |
| className | string | - | Custom class name. |
ChartReferenceLine
| Name | Type | Default | Description |
|---|---|---|---|
| y* | number | - | Position on the value axis. |
| label | string | - | Text drawn on the line ("80%", "Target"). |
| axis | "left" | "right" | "left" | Which value axis to attach to; only meaningful on ComposedChart. |
| color | string | --color-muted-foreground | Line color. The default deliberately avoids chart-N: a reference line is not data, and borrowing a series hue makes it read as "series N". |
| dash | string | "4 4" | Dash pattern; pass an empty string for a solid line. |
Pitfalls
- Width comes from the parent or className, but height must be nonzero; the default is 280.
- ResponsiveContainer may measure zero inside shrinking flex layouts. Give the container explicit width; see [[recharts-responsive-container-needs-explicit-width-in-shrink-flex]].
- Headless screenshots can starve the entrance-animation clip path and show only axes. Enable reduced motion before capture; see [[recharts-headless-screenshot-blank-clippath-animation-starved]].
- Adaptive horizontal category width caps at 160 px. Shorten very long labels or pass
yAxisWidthexplicitly. heightremains the component's total height whenlegendis enabled. The legend consumes a row and reduces the canvas height rather than increasing the total height. Once there are enough series to wrap, the legend stacks into several rows and keeps eating the canvas. Do not try to absorb that by raising `height` (a 28-series legend is five rows; restoring a readable radar would mean doubling the total height). EnablelegendScrollto keep the legend on one scrollable row.- For the polar three (Pie/Radar/Radial),
legenddefaults to `true`, the opposite of the Cartesian three, because they have always shipped a legend and flipping the default would break existing layouts. Setlegend={false}before drawing your own, or two legends render side by side. - When drawing a custom legend, use
<Dot color={...} />, not<Dot style={{ color }} />. Dot uses a background color, so the latter silently leaves it gray; see Dot pitfalls. - RadarChart's radius-axis ticks (
radiusAxis, on by default) are drawn inside the plot area, not outside it: the tick anchors spread along a horizontal radius running from the center of the radar to its edge, and recharts rotates each number 90° so it reads vertically. With many series or densely filled data, the first few ticks land entirely inside the data polygons, covering the shape and hard to read at the same time. You cannot tell you picked wrong by looking at the layout: the chart renders fine, it is just muddled. For multi-series radars preferradiusAxis={false}; a radar is read as a shape comparison, and exact values are available from the tooltip.
Related
Stat · Statistic · Meter · Timeline · NumberTicker · WorldMap
Playground
<AreaChart data={series} series={[{ key: "revenue" }, { key: "orders" }]} xKey="month" />