@acronis-platform/ui-react 5.4.5 → 5.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ui/area-chart/area-chart.js +114 -113
- package/dist/components/ui/area-chart/area-chart.js.map +1 -1
- package/dist/components/ui/bar-chart/bar-chart.js +53 -49
- package/dist/components/ui/bar-chart/bar-chart.js.map +1 -1
- package/dist/components/ui/chart/chart.js +52 -45
- package/dist/components/ui/chart/chart.js.map +1 -1
- package/dist/components/ui/composed-chart/composed-chart.js +134 -130
- package/dist/components/ui/composed-chart/composed-chart.js.map +1 -1
- package/dist/components/ui/confidence-cone/confidence-cone.js +36 -35
- package/dist/components/ui/confidence-cone/confidence-cone.js.map +1 -1
- package/dist/components/ui/funnel-chart/funnel-chart.js +38 -37
- package/dist/components/ui/funnel-chart/funnel-chart.js.map +1 -1
- package/dist/components/ui/line-chart/line-chart.js +35 -34
- package/dist/components/ui/line-chart/line-chart.js.map +1 -1
- package/dist/components/ui/pie-chart/pie-chart.js +28 -27
- package/dist/components/ui/pie-chart/pie-chart.js.map +1 -1
- package/dist/components/ui/radar-chart/radar-chart.js +68 -65
- package/dist/components/ui/radar-chart/radar-chart.js.map +1 -1
- package/dist/components/ui/radial-bar-chart/radial-bar-chart.js +64 -63
- package/dist/components/ui/radial-bar-chart/radial-bar-chart.js.map +1 -1
- package/dist/components/ui/scatter-chart/scatter-chart.js +26 -26
- package/dist/components/ui/scatter-chart/scatter-chart.js.map +1 -1
- package/dist/components/ui/treemap/treemap.js +22 -22
- package/dist/components/ui/treemap/treemap.js.map +1 -1
- package/dist/llms/area-chart.md +1 -0
- package/dist/llms/bar-chart.md +1 -0
- package/dist/llms/chart.md +10 -0
- package/dist/llms/composed-chart.md +1 -0
- package/dist/llms/confidence-cone.md +1 -0
- package/dist/llms/funnel-chart.md +1 -0
- package/dist/llms/line-chart.md +1 -0
- package/dist/llms/pie-chart.md +1 -0
- package/dist/llms/radar-chart.md +1 -0
- package/dist/llms/radial-bar-chart.md +1 -0
- package/dist/llms/scatter-chart.md +1 -0
- package/dist/llms/treemap.md +1 -0
- package/dist/llms.txt +1 -1
- package/dist/src/components/ui/area-chart/area-chart.d.ts +6 -1
- package/dist/src/components/ui/bar-chart/bar-chart.d.ts +8 -2
- package/dist/src/components/ui/category-bar/category-bar.d.ts +1 -1
- package/dist/src/components/ui/chart/__stories__/palette-control.d.ts +16 -0
- package/dist/src/components/ui/chart/chart.d.ts +12 -1
- package/dist/src/components/ui/chart/index.d.ts +1 -1
- package/dist/src/components/ui/composed-chart/composed-chart.d.ts +6 -1
- package/dist/src/components/ui/confidence-cone/confidence-cone.d.ts +6 -1
- package/dist/src/components/ui/funnel-chart/funnel-chart.d.ts +6 -1
- package/dist/src/components/ui/grid/grid.d.ts +1 -1
- package/dist/src/components/ui/line-chart/line-chart.d.ts +6 -1
- package/dist/src/components/ui/pie-chart/pie-chart.d.ts +6 -1
- package/dist/src/components/ui/progress-circle/progress-circle.d.ts +1 -1
- package/dist/src/components/ui/radar-chart/radar-chart.d.ts +9 -1
- package/dist/src/components/ui/radial-bar-chart/radial-bar-chart.d.ts +6 -1
- package/dist/src/components/ui/scatter-chart/scatter-chart.d.ts +6 -1
- package/dist/src/components/ui/spinner/spinner.d.ts +1 -1
- package/dist/src/components/ui/stack/stack.d.ts +1 -1
- package/dist/src/components/ui/treemap/treemap.d.ts +6 -1
- package/dist/ui-react.css +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radial-bar-chart.js","names":[],"sources":["../../../../src/components/ui/radial-bar-chart/radial-bar-chart.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport {\n Cell,\n Label,\n LabelList,\n PolarAngleAxis,\n PolarGrid,\n RadialBar,\n RadialBarChart as RechartsRadialBarChart,\n} from 'recharts';\n\nimport type { LegendPayload } from 'recharts/types/component/DefaultLegendContent';\n\nimport { cn } from '@/lib/utils';\nimport {\n ChartContainer,\n ChartLegendContent,\n ChartTooltip,\n ChartTooltipContent,\n resolveAnimation,\n resolveChartColors,\n resolveLabelFillClass,\n CHART_DEFAULT_PALETTE,\n CHART_LABEL_FONT_SIZE,\n type ChartConfig,\n type ChartPalette,\n type ChartAnimationProps,\n type ChartDataLabelProps,\n type ChartTooltipContentProps,\n type PolarLabelPosition,\n toCssKey,\n type ResolvedAnimation,\n type TickFormatter,\n} from '../chart';\n\n// A typed recharts composition over the shared `Chart` primitives (a polar/radial\n// type). Rows become concentric arcs sized by `dataKey`, each colored from its\n// `--color-<name>` var; `dataKeys` switches to one arc per metric instead. Like\n// ScatterChart/ComposedChart there's no CVA variant: a radial bar's\n// expressiveness is geometry (angles/radii, plain props) and the data mapping,\n// not a visual \"mode\". The angular sweep is exposed as startAngle/endAngle props\n// so a caller can build a gauge or a full ring.\n\n/** Row-per-arc datum: the `nameKey` label plus each metric's numeric value. */\ntype RadialBarChartDatum = Record<string, string | number>;\n\n/** What an arc's data label reads. */\nexport type RadialBarChartLabelFormat = 'value' | 'name-value';\n\nexport interface RadialBarChartCenterLabel {\n // Rendered as SVG <text>, which only lays out text — hence string | number,\n // not ReactNode.\n /** Headline metric rendered large in the hole (e.g. the gauge's value). */\n value?: string | number;\n /** Caption rendered under the value. */\n label?: string | number;\n}\n\n/**\n * The readout in the hole at the centre of the arcs — a headline value over a\n * caption. Rendered as a recharts `<Label>`, which is what supplies the polar\n * viewBox the text centres itself on.\n *\n * There is one readout for the whole chart, so it hangs off the first series\n * drawn — every series shares the same viewBox.\n */\nfunction RadialBarChartCenterLabelContent({\n centerLabel,\n}: {\n centerLabel: RadialBarChartCenterLabel;\n}) {\n return (\n <Label\n content={({ viewBox }) => {\n if (!viewBox || !('cx' in viewBox)) return null;\n const { cx: centerX = 0, cy: centerY = 0 } = viewBox as {\n cx?: number;\n cy?: number;\n };\n const hasValue = centerLabel.value != null;\n const hasLabel = centerLabel.label != null;\n // Straddle the centre when both lines show, so the value + label block\n // is centered as a whole (not just the value).\n const both = hasValue && hasLabel;\n return (\n <g>\n {hasValue && (\n <text\n x={centerX}\n y={both ? centerY - 10 : centerY}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n className=\"fill-foreground text-2xl font-semibold\"\n >\n {centerLabel.value}\n </text>\n )}\n {hasLabel && (\n <text\n x={centerX}\n y={both ? centerY + 13 : centerY}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n className=\"fill-muted-foreground text-sm\"\n >\n {centerLabel.label}\n </text>\n )}\n </g>\n );\n }}\n />\n );\n}\n\n/**\n * Compose one arc's data-label text. Exported for unit tests; not part of the\n * package's public API.\n *\n * `labelFormatter` only ever formats the numeric part, so a chart's labels and\n * its tooltip stay in the same units under either format. A nullish value yields\n * an empty label, which recharts renders as no `<text>` at all.\n */\nexport function radialBarChartLabelText(options: {\n format: RadialBarChartLabelFormat;\n name: string | number | undefined;\n value: string | number | undefined;\n formatter?: TickFormatter;\n}): string {\n const { format, name, value, formatter } = options;\n if (value == null) return '';\n const valueText = formatter ? formatter(value) : String(value);\n return format === 'name-value' && name != null\n ? `${name}: ${valueText}`\n : valueText;\n}\n\n/** One piece of a segmented gauge's ring: a length in degrees and what it is. */\nexport interface RadialBarChartSegment {\n /** Synthetic series key — stable for a given (index, kind). */\n key: string;\n /** Angular length of the piece, in degrees. */\n degrees: number;\n /** `value` is the metric, `track` the unreached remainder, `gap` the notch. */\n kind: 'value' | 'track' | 'gap';\n}\n\n/** Fallback gap between a segmented gauge's segments, in degrees. */\nconst SEGMENT_GAP = 3;\n\n/**\n * Cut a gauge's sweep into equal segments separated by gaps, and split the\n * segment the value ends inside into its reached and unreached parts. Exported\n * for unit tests; not part of the package's public API.\n *\n * Lengths come back in **degrees**, which is what lets the ring be built as one\n * stack of synthetic series over a `[0, sweep]` angular scale: recharts lays\n * stacked radial bars out sequentially along the angle, so a transparent series\n * between two colored ones reads as a real notch in the ring. A `gap` that\n * would leave no room for the segments themselves is clamped.\n */\nexport function radialBarChartSegments(options: {\n value: number;\n domain: [number, number];\n segments: number;\n gap: number;\n /** Absolute sweep of the chart, in degrees. */\n sweep: number;\n /** A full-circle sweep also needs a gap between the last segment and the first. */\n closed: boolean;\n}): RadialBarChartSegment[] {\n const { value, domain, segments, sweep, closed } = options;\n const gapCount = closed ? segments : segments - 1;\n // Leave at least half the sweep for the segments, however wide a gap is asked\n // for — an all-gap ring would render as nothing at all. A negative gap would\n // stretch the pieces past the sweep and overlap them, so it floors at zero.\n const gap =\n gapCount > 0\n ? Math.min(Math.max(options.gap, 0), sweep / (2 * gapCount))\n : 0;\n const segmentDegrees = (sweep - gap * gapCount) / segments;\n const [min, max] = domain;\n const span = max - min;\n // A domain with no span (`[n, n]`) or an inverted one (`[max, min]`) cannot\n // place a value on the ring at all, so there is no fraction to draw and the\n // ring stays empty. It deliberately does not fall back to comparing the value\n // against zero: that reported a *full* ring for `{ value: 5, domain: [100,\n // 100] }`, i.e. claimed completion the data never supported. For a gauge,\n // under-reading a domain that says nothing is the safe direction.\n const fraction =\n span > 0 ? Math.min(Math.max((value - min) / span, 0), 1) : 0;\n // The value maps onto the *drawn* ring, so the gaps never eat into it.\n let remainingValue = fraction * segmentDegrees * segments;\n\n const pieces: RadialBarChartSegment[] = [];\n for (let index = 0; index < segments; index++) {\n const reached = Math.min(Math.max(remainingValue, 0), segmentDegrees);\n remainingValue -= reached;\n // Both halves are always emitted, including at zero length: a stable key set\n // keeps recharts' series identities steady as the value changes.\n pieces.push({ key: `seg-${index}-value`, degrees: reached, kind: 'value' });\n pieces.push({\n key: `seg-${index}-track`,\n degrees: segmentDegrees - reached,\n kind: 'track',\n });\n if (gap > 0 && (closed || index < segments - 1)) {\n pieces.push({ key: `seg-${index}-gap`, degrees: gap, kind: 'gap' });\n }\n }\n return pieces;\n}\n\n/**\n * Fill for one piece of a segmented gauge's ring. Exported for unit tests and so\n * the forced-open tooltip story can build a ring from the real implementation\n * rather than a copy of it; not part of the package's public API.\n */\nexport function radialBarChartSegmentFill(\n kind: RadialBarChartSegment['kind'],\n colorName: string | number\n): string {\n if (kind === 'gap') return 'transparent';\n // The unreached remainder plays the part `showBackground`'s track plays on a\n // continuous arc, so it takes the same muted surface.\n return kind === 'track'\n ? 'var(--ui-border-on-status-neutral)'\n : `var(--color-${toCssKey(String(colorName))})`;\n}\n\n/**\n * What a segmented gauge's tooltip reads. Exported for unit tests; not part of\n * the package's public API.\n *\n * A segmented ring's series are pieces of geometry, so recharts would offer the\n * hovered piece's own name and length (\"seg-3-value\", 41.2°). Hovering anywhere\n * on the ring should read the metric instead, so the reading is rebuilt from the\n * data row.\n */\nexport function radialBarChartSegmentedReading(options: {\n config: ChartConfig;\n row: RadialBarChartDatum;\n nameKey: string;\n dataKey: string;\n /** The `valueDomain` maximum, when the gauge has one. */\n domainMax?: number;\n}): { colorName: string; label: React.ReactNode; valueText: string } {\n const { config, row, nameKey, dataKey, domainMax } = options;\n const colorName = String(row[nameKey]);\n const value = row[dataKey];\n const valueText =\n typeof value === 'number' ? value.toLocaleString() : String(value ?? '');\n return {\n colorName,\n label: config[colorName]?.label ?? colorName,\n // The gauge's scale is the other half of the reading, so a known maximum\n // comes along: \"29 / 38\", not a bare \"29\".\n valueText:\n domainMax == null\n ? valueText\n : `${valueText} / ${domainMax.toLocaleString()}`,\n };\n}\n\n/**\n * The tooltip a segmented gauge shows — the same swatch/name/value layout the\n * default `ChartTooltipContent` row uses, dot included, over the reading from\n * `radialBarChartSegmentedReading`. Exported for the forced-open\n * visual-regression story; not part of the package's public API.\n *\n * `payload` is trimmed to one row: a radial tooltip is axis-shared and collects a\n * row from every series — two dozen identical rows for one metric.\n * (`tooltipType=\"none\"` only tags them, and scoping the tooltip to the hovered\n * item instead would drop it in the corner — unlike Pie/Bar sectors, a RadialBar\n * sector carries no tooltip coordinate.)\n *\n * A real component rather than a closure so it can be handed to `ChartTooltip`\n * as an *element*: recharts `cloneElement`s an element but `createElement`s a\n * function, and a closure rebuilt each render is a new element *type* — which\n * remounts the tooltip on every parent render instead of updating it.\n */\nexport function RadialBarChartSegmentedTooltipContent({\n active,\n payload,\n ...reading\n}: {\n active?: boolean;\n // Loosely typed on purpose: recharts' own payload generics are wider than the\n // shared `ChartTooltipContentProps` accepts, and only the row count matters.\n payload?: readonly unknown[];\n config: ChartConfig;\n row: RadialBarChartDatum;\n nameKey: string;\n dataKey: string;\n domainMax?: number;\n}) {\n const { colorName, label, valueText } =\n radialBarChartSegmentedReading(reading);\n\n return (\n <ChartTooltipContent\n active={active}\n payload={payload?.slice(0, 1) as ChartTooltipContentProps['payload']}\n hideLabel\n formatter={() => (\n <>\n <div\n className=\"h-2.5 w-2.5 shrink-0 rounded-full\"\n style={{ backgroundColor: `var(--color-${toCssKey(colorName)})` }}\n />\n <div className=\"flex flex-1 items-center justify-between gap-2 leading-none\">\n <span className=\"text-muted-foreground\">{label}</span>\n <span className=\"font-medium tabular-nums text-foreground\">\n {valueText}\n </span>\n </div>\n </>\n )}\n />\n );\n}\n\n/**\n * The band a multi-metric tooltip's header names. Exported for unit tests; not\n * part of the package's public API.\n *\n * Each tooltip row is one metric, so the header names the band the hovered arc\n * belongs to. recharts' own label is the radius-axis index, which reads as a\n * bare number.\n */\nexport function radialBarChartBandName(\n row: RadialBarChartDatum | undefined,\n nameKey: string\n): string {\n const name = row?.[nameKey];\n return name == null ? '' : String(name);\n}\n\n/**\n * What an arc's data label calls the arc: the row's own `nameKey` value, or — in\n * multi-metric mode, where one arc *is* one metric — that metric's `config` label.\n */\nfunction radialBarChartArcName(options: {\n key: string;\n row: RadialBarChartDatum | undefined;\n isMultiMetric: boolean;\n nameKey: string;\n config: ChartConfig;\n}): string | number | undefined {\n const { key, row, isMultiMetric, nameKey, config } = options;\n if (!isMultiMetric) return row?.[nameKey];\n // ChartConfig labels are ReactNode; only a plain string/number can go into an\n // SVG <text>.\n const label = config[key]?.label;\n return typeof label === 'string' || typeof label === 'number' ? label : key;\n}\n\n/**\n * The default mapping's arcs: one `RadialBar` per series — one for the single\n * `dataKey`, or one per `dataKeys` entry in multi-metric mode — drawn\n * concentrically.\n *\n * recharts collects its graphical items from the store (each `RadialBar`\n * registers itself on render) rather than by scanning the chart's children for\n * their types, so grouping them inside a component of our own is invisible to it.\n * The `Cell`s stay direct children of their `RadialBar`, which *does* read its own\n * children by type.\n */\nfunction RadialBarChartSeries({\n seriesKeys,\n seriesData,\n isMultiMetric,\n config,\n nameKey,\n showBackground,\n cornerRadius,\n minPointSize,\n animation,\n centerLabel,\n showLabels,\n labelPosition,\n labelFormat,\n labelFormatter,\n}: {\n seriesKeys: string[];\n seriesData: RadialBarChartDatum[];\n isMultiMetric: boolean;\n config: ChartConfig;\n nameKey: string;\n showBackground: boolean;\n cornerRadius: number;\n minPointSize: number | undefined;\n animation: ResolvedAnimation;\n centerLabel: RadialBarChartCenterLabel | undefined;\n showLabels: boolean;\n labelPosition: PolarLabelPosition;\n labelFormat: RadialBarChartLabelFormat;\n labelFormatter?: TickFormatter;\n}) {\n return seriesKeys.map((key, seriesIndex) => (\n <RadialBar\n key={key}\n dataKey={key}\n fill={isMultiMetric ? `var(--color-${toCssKey(key)})` : undefined}\n background={showBackground ? { fill: 'var(--ui-border-on-status-neutral)' } : false}\n cornerRadius={cornerRadius}\n minPointSize={minPointSize}\n {...animation}\n >\n {!isMultiMetric &&\n seriesData.map((entry, index) => (\n // Keyed by index, not the name: two arcs could share a nameKey\n // value, which would collide as a React key. Same-named arcs\n // intentionally share a color/config entry via `--color-<name>`.\n <Cell key={index} fill={`var(--color-${toCssKey(String(entry[nameKey]))})`} />\n ))}\n {seriesIndex === 0 && centerLabel && (\n <RadialBarChartCenterLabelContent centerLabel={centerLabel} />\n )}\n {showLabels && (\n <LabelList\n // `valueAccessor` rather than `dataKey`: the label text can\n // carry the arc's name, which only the whole row (single\n // metric) or the metric's config entry can supply. recharts\n // ignores the accessor when a dataKey is set, so the two\n // can't be combined.\n valueAccessor={(entry) =>\n radialBarChartLabelText({\n format: labelFormat,\n name: radialBarChartArcName({\n key,\n row: entry.payload as RadialBarChartDatum | undefined,\n isMultiMetric,\n nameKey,\n config,\n }),\n value: entry.value as string | number | undefined,\n formatter: labelFormatter,\n })\n }\n position={labelPosition}\n className={resolveLabelFillClass(labelPosition)}\n fontSize={CHART_LABEL_FONT_SIZE}\n />\n )}\n </RadialBar>\n ));\n}\n\n/**\n * A segmented gauge's ring: the pieces from `radialBarChartSegments` as one stack\n * of synthetic series, so they lay out sequentially around the ring instead of\n * concentrically and the `gap` pieces read as notches.\n */\nfunction RadialBarChartSegmentedSeries({\n ringSegments,\n row,\n nameKey,\n cornerRadius,\n animation,\n centerLabel,\n}: {\n ringSegments: RadialBarChartSegment[];\n row: RadialBarChartDatum;\n nameKey: string;\n cornerRadius: number;\n animation: ResolvedAnimation;\n centerLabel: RadialBarChartCenterLabel | undefined;\n}) {\n return ringSegments.map((segment, index) => (\n <RadialBar\n key={segment.key}\n dataKey={segment.key}\n stackId=\"segments\"\n // The metric's name, so the hover has a row to key off at all\n // (`ChartTooltipContent` keys its row off `name`).\n name={String(row[nameKey])}\n fill={radialBarChartSegmentFill(segment.kind, row[nameKey])}\n cornerRadius={cornerRadius}\n {...animation}\n >\n {index === 0 && centerLabel && (\n <RadialBarChartCenterLabelContent centerLabel={centerLabel} />\n )}\n </RadialBar>\n ));\n}\n\nexport interface RadialBarChartProps\n extends\n Omit<React.ComponentProps<'div'>, 'children'>,\n ChartAnimationProps,\n ChartDataLabelProps {\n /**\n * The dataviz palette this chart's series are painted from. Series that\n * state no `color` of their own take a stop of it. See `ChartPalette`.\n */\n palette?: ChartPalette;\n /** Row-per-arc data. Each object holds the arc's `nameKey` label + its `dataKey` numeric value. */\n data: ReadonlyArray<RadialBarChartDatum>;\n /**\n * Per-arc map of `label` / `icon` / `tone`, keyed by the arc's `nameKey` value\n * — or, in multi-metric mode, by each `dataKeys` entry (imported from the\n * shared `Chart` primitives). Turned into `--color-<name>` custom properties.\n * Series take their colour from the container's `palette`; each entry maps a\n * key to a `label` and an optional `tone`.\n */\n config: ChartConfig;\n /** Numeric field that sizes each arc. Ignored when `dataKeys` is set. */\n dataKey: string;\n /**\n * Plot several metrics instead of one arc per row: one arc per key, colored and\n * named from `config` keyed by the **key** (not by `nameKey`) — the same\n * convention as the cartesian charts. Every metric shares one angular scale, so\n * pair it with `valueDomain` when the metrics are measured against a known\n * maximum. `nameKey` still names the band each metric's arc sits in.\n */\n dataKeys?: string[];\n /**\n * Label field that names each arc (drives the legend, tooltip, and\n * `--color-<name>` lookup). Values should be unique per chart (arcs sharing a\n * name share one `config`/color entry); they are sanitized automatically to a\n * valid CSS custom-property fragment (CSS-safe values are preferred to avoid\n * dev-mode warnings). In multi-metric mode it names the band rather than the\n * arc, so the legend and colors come from `dataKeys` instead.\n */\n nameKey: string;\n /** Inner radius of the innermost arc. */\n innerRadius?: number;\n /** Outer radius of the outermost arc. */\n outerRadius?: number;\n /** Angle (degrees) the arcs sweep from. */\n startAngle?: number;\n /** Angle (degrees) the arcs sweep to (default is a full clockwise circle). */\n endAngle?: number;\n /**\n * The value range the sweep represents, as `[min, max]`. Without it every arc\n * is drawn relative to the largest value in the data — so a **single** value\n * always fills the whole sweep, which is never what a gauge means. Set it to\n * the metric's own scale (e.g. `[0, 100]` for a percentage) and each arc\n * becomes that fraction of the sweep, over the `showBackground` track.\n */\n valueDomain?: [number, number];\n /** Corner radius on each arc's ends. */\n cornerRadius?: number;\n /**\n * Horizontal center of the arcs, in px or a percentage of the width.\n * Defaults to the middle (`50%`).\n */\n cx?: number | string;\n /**\n * Vertical center of the arcs, in px or a percentage of the height. Defaults to\n * the middle (`50%`) — worth moving for a half-circle gauge, whose drawn half\n * otherwise sits in the upper half of the surface.\n */\n cy?: number | string;\n /** Thickness of each arc, in px. Omit to let the radii and the gaps size it. */\n barSize?: number;\n /** Gap between the arcs of different metrics in one band, in px. */\n barGap?: number | string;\n /** Gap between bands, in px or a percentage string. */\n barCategoryGap?: number | string;\n /**\n * Smallest angle a non-zero arc may occupy, in degrees, so a tiny value stays\n * visible (and hoverable). recharts applies the floor to a `0` as well, which\n * reads as a small positive value — leave this unset when a metric can\n * legitimately be zero.\n */\n minAngle?: number;\n /** Plot-area margin, in px. Omit to use recharts' default (5 on every side). */\n margin?: { top?: number; right?: number; bottom?: number; left?: number };\n /**\n * Draw the gauge's ring as this many equal segments, notched apart — the\n * segmented-gauge look. Applies to a **single-value gauge** only and needs\n * `valueDomain` to know what a full ring means. Anything that isn't one — more\n * than one row, `dataKeys` set, fewer than two segments, or a `dataKey` value\n * that isn't a number (a stringified `\"29\"` off an API) — falls back to the\n * normal concentric arcs, legend included, rather than erroring.\n *\n * The ring is then synthetic geometry rather than data rows, so the arc labels\n * and the legend are suppressed: put the readout in `centerLabel`. The tooltip\n * stays, and reads the metric (with the `valueDomain` maximum when there is\n * one) wherever on the ring it is hovered. `showBackground` is redundant too —\n * the unreached segments *are* the track.\n */\n segments?: number;\n /** Gap between a segmented gauge's segments, in degrees. Defaults to `3`. */\n segmentGap?: number;\n /** Render a muted background track behind each arc. */\n showBackground?: boolean;\n /** Draw the concentric polar grid behind the arcs. */\n showPolarGrid?: boolean;\n /**\n * Custom content for the hole at the center — a headline `value` and/or a\n * `label` caption, stacked and centered. Needs an `innerRadius` big enough to\n * hold the text; it is not clipped to the hole.\n */\n centerLabel?: RadialBarChartCenterLabel;\n showTooltip?: boolean;\n showLegend?: boolean;\n /**\n * Format each arc's value in the list legend. In single-metric mode each arc\n * row gets its formatted value next to its label; in multi-metric mode values\n * are per-row (not per-series) so this is ignored.\n */\n legendValueFormatter?: (value: string | number) => string;\n /**\n * Replace the default tooltip. Pass a configured `ChartTooltipContent`\n * (imported from this library) — e.g. with a `formatter` / `labelFormatter` —\n * to customize formatting, per-arc rows, or extra fields without composing\n * recharts yourself. Ignored when `showTooltip` is false.\n *\n * On a segmented gauge it takes over from the built-in reading, and the payload\n * it receives is the ring's synthetic pieces (`seg-2-track`, a length in\n * degrees) rather than data rows — read the metric off `payload[0].payload`.\n */\n tooltipContent?: React.ComponentProps<typeof ChartTooltip>['content'];\n /**\n * Position of the value labels when `showLabels` is on. Defaults to\n * `insideStart` — the labels sit inside each arc, and therefore render in the\n * on-fill label token so they stay legible over the arc colour.\n */\n labelPosition?: PolarLabelPosition;\n /**\n * What each data label reads when `showLabels` is on — the value alone (the\n * default), or the arc's name and its value. The name is the row's `nameKey`\n * value, or the metric's `config` label in multi-metric mode.\n */\n labelFormat?: RadialBarChartLabelFormat;\n}\n\nconst RadialBarChart = React.forwardRef<HTMLDivElement, RadialBarChartProps>(\n (\n {\n className,\n config,\n palette,\n data,\n dataKey,\n dataKeys,\n nameKey,\n innerRadius = 20,\n outerRadius = 60,\n startAngle = 90,\n endAngle = -270,\n valueDomain,\n cornerRadius = 4,\n cx,\n cy,\n barSize,\n barGap,\n barCategoryGap,\n minAngle,\n margin,\n segments,\n segmentGap = SEGMENT_GAP,\n showBackground = true,\n showPolarGrid = false,\n centerLabel,\n showTooltip = true,\n showLegend = true,\n legendValueFormatter,\n tooltipContent,\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n showLabels = false,\n labelPosition,\n labelFormat = 'value',\n labelFormatter,\n ...props\n },\n ref\n ) => {\n const animation = resolveAnimation({\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n });\n const arcLabelPosition = labelPosition ?? 'insideStart';\n const isMultiMetric = !!dataKeys?.length;\n // A segmented ring is one metric cut into pieces, so it only means anything\n // for a single-row, single-metric chart.\n const isSegmented =\n segments != null &&\n segments > 1 &&\n !isMultiMetric &&\n data.length === 1 &&\n typeof data[0][dataKey] === 'number';\n const seriesKeys = isMultiMetric ? dataKeys! : [dataKey];\n // Stable across renders so the memoized legend content below keeps its\n // identity; `seriesKeys` itself is a fresh array every render.\n const legendKeys = React.useMemo(\n () => (dataKeys?.length ? dataKeys : [dataKey]),\n [dataKeys, dataKey]\n );\n const sweep = Math.abs(endAngle - startAngle);\n const ringSegments = isSegmented\n ? radialBarChartSegments({\n value: data[0][dataKey] as number,\n // Without a domain a lone value is its own maximum, so the ring fills.\n domain: valueDomain ?? [0, data[0][dataKey] as number],\n segments: segments!,\n gap: segmentGap,\n sweep,\n closed: sweep >= 360,\n })\n : [];\n\n // Stamp each row with its `fill` (the shadcn data-driven pattern) so a real\n // hover resolves the arc color in the tooltip — recharts' RadialBar, like\n // Funnel, carries no per-arc color on the tooltip payload item. Only in\n // single-metric mode: with one color per *metric*, a row-level fill would\n // paint every tooltip swatch the same.\n const seriesData: RadialBarChartDatum[] = isMultiMetric\n ? (data as RadialBarChartDatum[])\n : data.map((row) => ({\n ...row,\n fill: `var(--color-${toCssKey(String(row[nameKey]))})`,\n }));\n\n // Each piece of the ring becomes a synthetic series on the one row, measured\n // in degrees — see `radialBarChartSegments`. Built from the raw row, *not*\n // from `seriesData`: a row-level `fill` wins over a series' own `fill` in\n // recharts, which would paint the track and the notches the arc's color.\n const plotData: RadialBarChartDatum[] = isSegmented\n ? [\n {\n ...data[0],\n ...Object.fromEntries(ringSegments.map((s) => [s.key, s.degrees])),\n },\n ]\n : seriesData;\n\n // recharts measures the radial floor in degrees, then extends the arc by\n // `mathSign(deltaAngle || minPointSize)`, where `deltaAngle` is the *arc's*\n // own angular length (RadialBar.js). So for a non-zero arc the floor already\n // grows along the arc's direction whatever sign it carries; the sign only\n // decides which way an arc of exactly zero degrees grows. Carry the sweep's\n // sign so that one grows forwards on a counter-clockwise chart too.\n const minPointSize =\n minAngle === undefined\n ? undefined\n : endAngle < startAngle\n ? -minAngle\n : minAngle;\n\n // Resolve colors so the external list legend has the same values as the chart\n // plot without needing to be inside ChartContainer's context.\n const resolvedConfigForLegend = React.useMemo(\n () => (showLegend ? resolveChartColors(config, palette ?? CHART_DEFAULT_PALETTE) : null),\n [showLegend, config, palette]\n );\n const rightLegendPayload: LegendPayload[] = React.useMemo(() => {\n if (!resolvedConfigForLegend) return [];\n if (isMultiMetric) {\n // Multi-metric: one entry per dataKey. No per-series row value is\n // meaningful (each metric spans multiple rows), so values are omitted.\n return legendKeys.map((key) => ({\n value: key,\n dataKey: key,\n color: resolvedConfigForLegend[key]?.color ?? `var(--color-${toCssKey(key)})`,\n type: 'rect' as const,\n payload: {} as Record<string, unknown>,\n }));\n }\n // Single-metric: one entry per data row.\n return data.map((row) => ({\n value: String(row[nameKey]),\n dataKey: nameKey,\n color:\n resolvedConfigForLegend[String(row[nameKey])]?.color ??\n `var(--color-${toCssKey(String(row[nameKey]))})`,\n type: 'rect' as const,\n payload: row as Record<string, unknown>,\n }));\n }, [resolvedConfigForLegend, isMultiMetric, legendKeys, data, nameKey]);\n\n const hasLegend = showLegend && !isSegmented && !!resolvedConfigForLegend;\n\n // The built-in tooltip reading, which each mapping composes differently: a\n // segmented ring rebuilds it from the data row, multi-metric renames the\n // header after the band, and the default mapping reads the hovered arc.\n const getDefaultTooltipContent = () => {\n if (isSegmented) {\n return (\n <RadialBarChartSegmentedTooltipContent\n config={config}\n row={data[0]}\n nameKey={nameKey}\n dataKey={dataKey}\n domainMax={valueDomain?.[1]}\n />\n );\n }\n\n if (isMultiMetric) {\n return (\n <ChartTooltipContent\n labelFormatter={(_, payload) =>\n radialBarChartBandName(\n payload?.[0]?.payload as RadialBarChartDatum | undefined,\n nameKey\n )\n }\n />\n );\n }\n\n return <ChartTooltipContent nameKey={nameKey} hideLabel />;\n };\n\n // Shared recharts chart composition (used in both layout modes).\n const radialChartContent = (\n <RechartsRadialBarChart\n data={plotData}\n // Only meaningful for the single-metric mapping; in multi-metric and\n // segmented mode each <RadialBar> carries its own key and the scale\n // spans them all.\n dataKey={isMultiMetric || isSegmented ? undefined : dataKey}\n cx={cx}\n cy={cy}\n innerRadius={innerRadius}\n outerRadius={outerRadius}\n startAngle={startAngle}\n endAngle={endAngle}\n barSize={barSize}\n barGap={barGap}\n barCategoryGap={barCategoryGap}\n margin={margin ?? { top: 0, right: 0, bottom: 0, left: 0 }}\n >\n {showPolarGrid && (\n <PolarGrid gridType=\"circle\" radialLines={false} />\n )}\n {(valueDomain || isSegmented) && (\n // The angular scale itself, not chrome: it maps a value onto a\n // fraction of the sweep. Ticks/axis line stay off — the arcs and\n // the optional center readout are the readout. A segmented ring is\n // already measured in degrees, so its scale *is* the sweep.\n <PolarAngleAxis\n type=\"number\"\n domain={isSegmented ? [0, sweep] : valueDomain}\n tick={false}\n axisLine={false}\n />\n )}\n {showTooltip && (\n <ChartTooltip\n // The hover cursor marks which band of the angular axis is\n // active, which on a segmented ring is one metric either way —\n // it just draws a hairline across the gauge.\n cursor={isSegmented ? false : undefined}\n content={tooltipContent ?? getDefaultTooltipContent()}\n />\n )}\n {isSegmented ? (\n <RadialBarChartSegmentedSeries\n ringSegments={ringSegments}\n row={data[0]}\n nameKey={nameKey}\n cornerRadius={cornerRadius}\n animation={animation}\n centerLabel={centerLabel}\n />\n ) : (\n <RadialBarChartSeries\n seriesKeys={seriesKeys}\n seriesData={seriesData}\n isMultiMetric={isMultiMetric}\n config={config}\n nameKey={nameKey}\n showBackground={showBackground}\n cornerRadius={cornerRadius}\n minPointSize={minPointSize}\n animation={animation}\n centerLabel={centerLabel}\n showLabels={showLabels}\n labelPosition={arcLabelPosition}\n labelFormat={labelFormat}\n labelFormatter={labelFormatter}\n />\n )}\n </RechartsRadialBarChart>\n );\n\n const chartDivSize =\n showLabels && (arcLabelPosition === 'outside' || arcLabelPosition === 'end')\n ? 'size-[200px]'\n : 'size-[120px]';\n\n return (\n <div\n ref={ref}\n className={cn('flex flex-row items-center gap-4', !hasLegend && 'justify-center', className)}\n {...props}\n >\n <div className={cn(chartDivSize, 'shrink-0')}>\n <ChartContainer config={config} palette={palette} className=\"size-full\">\n {radialChartContent}\n </ChartContainer>\n </div>\n {showLegend && !isSegmented && resolvedConfigForLegend && (\n <ChartLegendContent\n variant=\"list\"\n payload={rightLegendPayload}\n config={resolvedConfigForLegend}\n nameKey={isMultiMetric ? undefined : nameKey}\n valueKey={isMultiMetric ? undefined : dataKey}\n valueFormatter={isMultiMetric ? undefined : legendValueFormatter}\n className=\"min-w-0 flex-1\"\n />\n )}\n </div>\n );\n }\n);\nRadialBarChart.displayName = 'RadialBarChart';\n\nexport { RadialBarChart };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"radial-bar-chart.js","names":[],"sources":["../../../../src/components/ui/radial-bar-chart/radial-bar-chart.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport {\n Cell,\n Label,\n LabelList,\n PolarAngleAxis,\n PolarGrid,\n RadialBar,\n RadialBarChart as RechartsRadialBarChart,\n} from 'recharts';\n\nimport type { LegendPayload } from 'recharts/types/component/DefaultLegendContent';\n\nimport { cn } from '@/lib/utils';\nimport {\n ChartContainer,\n ChartLegendContent,\n ChartTooltip,\n ChartTooltipContent,\n resolveAnimation,\n resolveChartColors,\n resolveLabelFillClass,\n CHART_DEFAULT_PALETTE,\n CHART_LABEL_FONT_SIZE,\n type ChartConfig,\n type ChartLegendFontSize,\n type ChartPalette,\n type ChartAnimationProps,\n type ChartDataLabelProps,\n type ChartTooltipContentProps,\n type PolarLabelPosition,\n toCssKey,\n type ResolvedAnimation,\n type TickFormatter,\n} from '../chart';\n\n// A typed recharts composition over the shared `Chart` primitives (a polar/radial\n// type). Rows become concentric arcs sized by `dataKey`, each colored from its\n// `--color-<name>` var; `dataKeys` switches to one arc per metric instead. Like\n// ScatterChart/ComposedChart there's no CVA variant: a radial bar's\n// expressiveness is geometry (angles/radii, plain props) and the data mapping,\n// not a visual \"mode\". The angular sweep is exposed as startAngle/endAngle props\n// so a caller can build a gauge or a full ring.\n\n/** Row-per-arc datum: the `nameKey` label plus each metric's numeric value. */\ntype RadialBarChartDatum = Record<string, string | number>;\n\n/** What an arc's data label reads. */\nexport type RadialBarChartLabelFormat = 'value' | 'name-value';\n\nexport interface RadialBarChartCenterLabel {\n // Rendered as SVG <text>, which only lays out text — hence string | number,\n // not ReactNode.\n /** Headline metric rendered large in the hole (e.g. the gauge's value). */\n value?: string | number;\n /** Caption rendered under the value. */\n label?: string | number;\n}\n\n/**\n * The readout in the hole at the centre of the arcs — a headline value over a\n * caption. Rendered as a recharts `<Label>`, which is what supplies the polar\n * viewBox the text centres itself on.\n *\n * There is one readout for the whole chart, so it hangs off the first series\n * drawn — every series shares the same viewBox.\n */\nfunction RadialBarChartCenterLabelContent({\n centerLabel,\n}: {\n centerLabel: RadialBarChartCenterLabel;\n}) {\n return (\n <Label\n content={({ viewBox }) => {\n if (!viewBox || !('cx' in viewBox)) return null;\n const { cx: centerX = 0, cy: centerY = 0 } = viewBox as {\n cx?: number;\n cy?: number;\n };\n const hasValue = centerLabel.value != null;\n const hasLabel = centerLabel.label != null;\n // Straddle the centre when both lines show, so the value + label block\n // is centered as a whole (not just the value).\n const both = hasValue && hasLabel;\n return (\n <g>\n {hasValue && (\n <text\n x={centerX}\n y={both ? centerY - 10 : centerY}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n className=\"fill-foreground text-2xl font-semibold\"\n >\n {centerLabel.value}\n </text>\n )}\n {hasLabel && (\n <text\n x={centerX}\n y={both ? centerY + 13 : centerY}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n className=\"fill-muted-foreground text-sm\"\n >\n {centerLabel.label}\n </text>\n )}\n </g>\n );\n }}\n />\n );\n}\n\n/**\n * Compose one arc's data-label text. Exported for unit tests; not part of the\n * package's public API.\n *\n * `labelFormatter` only ever formats the numeric part, so a chart's labels and\n * its tooltip stay in the same units under either format. A nullish value yields\n * an empty label, which recharts renders as no `<text>` at all.\n */\nexport function radialBarChartLabelText(options: {\n format: RadialBarChartLabelFormat;\n name: string | number | undefined;\n value: string | number | undefined;\n formatter?: TickFormatter;\n}): string {\n const { format, name, value, formatter } = options;\n if (value == null) return '';\n const valueText = formatter ? formatter(value) : String(value);\n return format === 'name-value' && name != null\n ? `${name}: ${valueText}`\n : valueText;\n}\n\n/** One piece of a segmented gauge's ring: a length in degrees and what it is. */\nexport interface RadialBarChartSegment {\n /** Synthetic series key — stable for a given (index, kind). */\n key: string;\n /** Angular length of the piece, in degrees. */\n degrees: number;\n /** `value` is the metric, `track` the unreached remainder, `gap` the notch. */\n kind: 'value' | 'track' | 'gap';\n}\n\n/** Fallback gap between a segmented gauge's segments, in degrees. */\nconst SEGMENT_GAP = 3;\n\n/**\n * Cut a gauge's sweep into equal segments separated by gaps, and split the\n * segment the value ends inside into its reached and unreached parts. Exported\n * for unit tests; not part of the package's public API.\n *\n * Lengths come back in **degrees**, which is what lets the ring be built as one\n * stack of synthetic series over a `[0, sweep]` angular scale: recharts lays\n * stacked radial bars out sequentially along the angle, so a transparent series\n * between two colored ones reads as a real notch in the ring. A `gap` that\n * would leave no room for the segments themselves is clamped.\n */\nexport function radialBarChartSegments(options: {\n value: number;\n domain: [number, number];\n segments: number;\n gap: number;\n /** Absolute sweep of the chart, in degrees. */\n sweep: number;\n /** A full-circle sweep also needs a gap between the last segment and the first. */\n closed: boolean;\n}): RadialBarChartSegment[] {\n const { value, domain, segments, sweep, closed } = options;\n const gapCount = closed ? segments : segments - 1;\n // Leave at least half the sweep for the segments, however wide a gap is asked\n // for — an all-gap ring would render as nothing at all. A negative gap would\n // stretch the pieces past the sweep and overlap them, so it floors at zero.\n const gap =\n gapCount > 0\n ? Math.min(Math.max(options.gap, 0), sweep / (2 * gapCount))\n : 0;\n const segmentDegrees = (sweep - gap * gapCount) / segments;\n const [min, max] = domain;\n const span = max - min;\n // A domain with no span (`[n, n]`) or an inverted one (`[max, min]`) cannot\n // place a value on the ring at all, so there is no fraction to draw and the\n // ring stays empty. It deliberately does not fall back to comparing the value\n // against zero: that reported a *full* ring for `{ value: 5, domain: [100,\n // 100] }`, i.e. claimed completion the data never supported. For a gauge,\n // under-reading a domain that says nothing is the safe direction.\n const fraction =\n span > 0 ? Math.min(Math.max((value - min) / span, 0), 1) : 0;\n // The value maps onto the *drawn* ring, so the gaps never eat into it.\n let remainingValue = fraction * segmentDegrees * segments;\n\n const pieces: RadialBarChartSegment[] = [];\n for (let index = 0; index < segments; index++) {\n const reached = Math.min(Math.max(remainingValue, 0), segmentDegrees);\n remainingValue -= reached;\n // Both halves are always emitted, including at zero length: a stable key set\n // keeps recharts' series identities steady as the value changes.\n pieces.push({ key: `seg-${index}-value`, degrees: reached, kind: 'value' });\n pieces.push({\n key: `seg-${index}-track`,\n degrees: segmentDegrees - reached,\n kind: 'track',\n });\n if (gap > 0 && (closed || index < segments - 1)) {\n pieces.push({ key: `seg-${index}-gap`, degrees: gap, kind: 'gap' });\n }\n }\n return pieces;\n}\n\n/**\n * Fill for one piece of a segmented gauge's ring. Exported for unit tests and so\n * the forced-open tooltip story can build a ring from the real implementation\n * rather than a copy of it; not part of the package's public API.\n */\nexport function radialBarChartSegmentFill(\n kind: RadialBarChartSegment['kind'],\n colorName: string | number\n): string {\n if (kind === 'gap') return 'transparent';\n // The unreached remainder plays the part `showBackground`'s track plays on a\n // continuous arc, so it takes the same muted surface.\n return kind === 'track'\n ? 'var(--ui-border-on-status-neutral)'\n : `var(--color-${toCssKey(String(colorName))})`;\n}\n\n/**\n * What a segmented gauge's tooltip reads. Exported for unit tests; not part of\n * the package's public API.\n *\n * A segmented ring's series are pieces of geometry, so recharts would offer the\n * hovered piece's own name and length (\"seg-3-value\", 41.2°). Hovering anywhere\n * on the ring should read the metric instead, so the reading is rebuilt from the\n * data row.\n */\nexport function radialBarChartSegmentedReading(options: {\n config: ChartConfig;\n row: RadialBarChartDatum;\n nameKey: string;\n dataKey: string;\n /** The `valueDomain` maximum, when the gauge has one. */\n domainMax?: number;\n}): { colorName: string; label: React.ReactNode; valueText: string } {\n const { config, row, nameKey, dataKey, domainMax } = options;\n const colorName = String(row[nameKey]);\n const value = row[dataKey];\n const valueText =\n typeof value === 'number' ? value.toLocaleString() : String(value ?? '');\n return {\n colorName,\n label: config[colorName]?.label ?? colorName,\n // The gauge's scale is the other half of the reading, so a known maximum\n // comes along: \"29 / 38\", not a bare \"29\".\n valueText:\n domainMax == null\n ? valueText\n : `${valueText} / ${domainMax.toLocaleString()}`,\n };\n}\n\n/**\n * The tooltip a segmented gauge shows — the same swatch/name/value layout the\n * default `ChartTooltipContent` row uses, dot included, over the reading from\n * `radialBarChartSegmentedReading`. Exported for the forced-open\n * visual-regression story; not part of the package's public API.\n *\n * `payload` is trimmed to one row: a radial tooltip is axis-shared and collects a\n * row from every series — two dozen identical rows for one metric.\n * (`tooltipType=\"none\"` only tags them, and scoping the tooltip to the hovered\n * item instead would drop it in the corner — unlike Pie/Bar sectors, a RadialBar\n * sector carries no tooltip coordinate.)\n *\n * A real component rather than a closure so it can be handed to `ChartTooltip`\n * as an *element*: recharts `cloneElement`s an element but `createElement`s a\n * function, and a closure rebuilt each render is a new element *type* — which\n * remounts the tooltip on every parent render instead of updating it.\n */\nexport function RadialBarChartSegmentedTooltipContent({\n active,\n payload,\n ...reading\n}: {\n active?: boolean;\n // Loosely typed on purpose: recharts' own payload generics are wider than the\n // shared `ChartTooltipContentProps` accepts, and only the row count matters.\n payload?: readonly unknown[];\n config: ChartConfig;\n row: RadialBarChartDatum;\n nameKey: string;\n dataKey: string;\n domainMax?: number;\n}) {\n const { colorName, label, valueText } =\n radialBarChartSegmentedReading(reading);\n\n return (\n <ChartTooltipContent\n active={active}\n payload={payload?.slice(0, 1) as ChartTooltipContentProps['payload']}\n hideLabel\n formatter={() => (\n <>\n <div\n className=\"h-2.5 w-2.5 shrink-0 rounded-full\"\n style={{ backgroundColor: `var(--color-${toCssKey(colorName)})` }}\n />\n <div className=\"flex flex-1 items-center justify-between gap-2 leading-none\">\n <span className=\"text-muted-foreground\">{label}</span>\n <span className=\"font-medium tabular-nums text-foreground\">\n {valueText}\n </span>\n </div>\n </>\n )}\n />\n );\n}\n\n/**\n * The band a multi-metric tooltip's header names. Exported for unit tests; not\n * part of the package's public API.\n *\n * Each tooltip row is one metric, so the header names the band the hovered arc\n * belongs to. recharts' own label is the radius-axis index, which reads as a\n * bare number.\n */\nexport function radialBarChartBandName(\n row: RadialBarChartDatum | undefined,\n nameKey: string\n): string {\n const name = row?.[nameKey];\n return name == null ? '' : String(name);\n}\n\n/**\n * What an arc's data label calls the arc: the row's own `nameKey` value, or — in\n * multi-metric mode, where one arc *is* one metric — that metric's `config` label.\n */\nfunction radialBarChartArcName(options: {\n key: string;\n row: RadialBarChartDatum | undefined;\n isMultiMetric: boolean;\n nameKey: string;\n config: ChartConfig;\n}): string | number | undefined {\n const { key, row, isMultiMetric, nameKey, config } = options;\n if (!isMultiMetric) return row?.[nameKey];\n // ChartConfig labels are ReactNode; only a plain string/number can go into an\n // SVG <text>.\n const label = config[key]?.label;\n return typeof label === 'string' || typeof label === 'number' ? label : key;\n}\n\n/**\n * The default mapping's arcs: one `RadialBar` per series — one for the single\n * `dataKey`, or one per `dataKeys` entry in multi-metric mode — drawn\n * concentrically.\n *\n * recharts collects its graphical items from the store (each `RadialBar`\n * registers itself on render) rather than by scanning the chart's children for\n * their types, so grouping them inside a component of our own is invisible to it.\n * The `Cell`s stay direct children of their `RadialBar`, which *does* read its own\n * children by type.\n */\nfunction RadialBarChartSeries({\n seriesKeys,\n seriesData,\n isMultiMetric,\n config,\n nameKey,\n showBackground,\n cornerRadius,\n minPointSize,\n animation,\n centerLabel,\n showLabels,\n labelPosition,\n labelFormat,\n labelFormatter,\n}: {\n seriesKeys: string[];\n seriesData: RadialBarChartDatum[];\n isMultiMetric: boolean;\n config: ChartConfig;\n nameKey: string;\n showBackground: boolean;\n cornerRadius: number;\n minPointSize: number | undefined;\n animation: ResolvedAnimation;\n centerLabel: RadialBarChartCenterLabel | undefined;\n showLabels: boolean;\n labelPosition: PolarLabelPosition;\n labelFormat: RadialBarChartLabelFormat;\n labelFormatter?: TickFormatter;\n}) {\n return seriesKeys.map((key, seriesIndex) => (\n <RadialBar\n key={key}\n dataKey={key}\n fill={isMultiMetric ? `var(--color-${toCssKey(key)})` : undefined}\n background={showBackground ? { fill: 'var(--ui-border-on-status-neutral)' } : false}\n cornerRadius={cornerRadius}\n minPointSize={minPointSize}\n {...animation}\n >\n {!isMultiMetric &&\n seriesData.map((entry, index) => (\n // Keyed by index, not the name: two arcs could share a nameKey\n // value, which would collide as a React key. Same-named arcs\n // intentionally share a color/config entry via `--color-<name>`.\n <Cell key={index} fill={`var(--color-${toCssKey(String(entry[nameKey]))})`} />\n ))}\n {seriesIndex === 0 && centerLabel && (\n <RadialBarChartCenterLabelContent centerLabel={centerLabel} />\n )}\n {showLabels && (\n <LabelList\n // `valueAccessor` rather than `dataKey`: the label text can\n // carry the arc's name, which only the whole row (single\n // metric) or the metric's config entry can supply. recharts\n // ignores the accessor when a dataKey is set, so the two\n // can't be combined.\n valueAccessor={(entry) =>\n radialBarChartLabelText({\n format: labelFormat,\n name: radialBarChartArcName({\n key,\n row: entry.payload as RadialBarChartDatum | undefined,\n isMultiMetric,\n nameKey,\n config,\n }),\n value: entry.value as string | number | undefined,\n formatter: labelFormatter,\n })\n }\n position={labelPosition}\n className={resolveLabelFillClass(labelPosition)}\n fontSize={CHART_LABEL_FONT_SIZE}\n />\n )}\n </RadialBar>\n ));\n}\n\n/**\n * A segmented gauge's ring: the pieces from `radialBarChartSegments` as one stack\n * of synthetic series, so they lay out sequentially around the ring instead of\n * concentrically and the `gap` pieces read as notches.\n */\nfunction RadialBarChartSegmentedSeries({\n ringSegments,\n row,\n nameKey,\n cornerRadius,\n animation,\n centerLabel,\n}: {\n ringSegments: RadialBarChartSegment[];\n row: RadialBarChartDatum;\n nameKey: string;\n cornerRadius: number;\n animation: ResolvedAnimation;\n centerLabel: RadialBarChartCenterLabel | undefined;\n}) {\n return ringSegments.map((segment, index) => (\n <RadialBar\n key={segment.key}\n dataKey={segment.key}\n stackId=\"segments\"\n // The metric's name, so the hover has a row to key off at all\n // (`ChartTooltipContent` keys its row off `name`).\n name={String(row[nameKey])}\n fill={radialBarChartSegmentFill(segment.kind, row[nameKey])}\n cornerRadius={cornerRadius}\n {...animation}\n >\n {index === 0 && centerLabel && (\n <RadialBarChartCenterLabelContent centerLabel={centerLabel} />\n )}\n </RadialBar>\n ));\n}\n\nexport interface RadialBarChartProps\n extends\n Omit<React.ComponentProps<'div'>, 'children'>,\n ChartAnimationProps,\n ChartDataLabelProps {\n /**\n * The dataviz palette this chart's series are painted from. Series that\n * state no `color` of their own take a stop of it. See `ChartPalette`.\n */\n palette?: ChartPalette;\n /** Row-per-arc data. Each object holds the arc's `nameKey` label + its `dataKey` numeric value. */\n data: ReadonlyArray<RadialBarChartDatum>;\n /**\n * Per-arc map of `label` / `icon` / `tone`, keyed by the arc's `nameKey` value\n * — or, in multi-metric mode, by each `dataKeys` entry (imported from the\n * shared `Chart` primitives). Turned into `--color-<name>` custom properties.\n * Series take their colour from the container's `palette`; each entry maps a\n * key to a `label` and an optional `tone`.\n */\n config: ChartConfig;\n /** Numeric field that sizes each arc. Ignored when `dataKeys` is set. */\n dataKey: string;\n /**\n * Plot several metrics instead of one arc per row: one arc per key, colored and\n * named from `config` keyed by the **key** (not by `nameKey`) — the same\n * convention as the cartesian charts. Every metric shares one angular scale, so\n * pair it with `valueDomain` when the metrics are measured against a known\n * maximum. `nameKey` still names the band each metric's arc sits in.\n */\n dataKeys?: string[];\n /**\n * Label field that names each arc (drives the legend, tooltip, and\n * `--color-<name>` lookup). Values should be unique per chart (arcs sharing a\n * name share one `config`/color entry); they are sanitized automatically to a\n * valid CSS custom-property fragment (CSS-safe values are preferred to avoid\n * dev-mode warnings). In multi-metric mode it names the band rather than the\n * arc, so the legend and colors come from `dataKeys` instead.\n */\n nameKey: string;\n /** Inner radius of the innermost arc. */\n innerRadius?: number;\n /** Outer radius of the outermost arc. */\n outerRadius?: number;\n /** Angle (degrees) the arcs sweep from. */\n startAngle?: number;\n /** Angle (degrees) the arcs sweep to (default is a full clockwise circle). */\n endAngle?: number;\n /**\n * The value range the sweep represents, as `[min, max]`. Without it every arc\n * is drawn relative to the largest value in the data — so a **single** value\n * always fills the whole sweep, which is never what a gauge means. Set it to\n * the metric's own scale (e.g. `[0, 100]` for a percentage) and each arc\n * becomes that fraction of the sweep, over the `showBackground` track.\n */\n valueDomain?: [number, number];\n /** Corner radius on each arc's ends. */\n cornerRadius?: number;\n /**\n * Horizontal center of the arcs, in px or a percentage of the width.\n * Defaults to the middle (`50%`).\n */\n cx?: number | string;\n /**\n * Vertical center of the arcs, in px or a percentage of the height. Defaults to\n * the middle (`50%`) — worth moving for a half-circle gauge, whose drawn half\n * otherwise sits in the upper half of the surface.\n */\n cy?: number | string;\n /** Thickness of each arc, in px. Omit to let the radii and the gaps size it. */\n barSize?: number;\n /** Gap between the arcs of different metrics in one band, in px. */\n barGap?: number | string;\n /** Gap between bands, in px or a percentage string. */\n barCategoryGap?: number | string;\n /**\n * Smallest angle a non-zero arc may occupy, in degrees, so a tiny value stays\n * visible (and hoverable). recharts applies the floor to a `0` as well, which\n * reads as a small positive value — leave this unset when a metric can\n * legitimately be zero.\n */\n minAngle?: number;\n /** Plot-area margin, in px. Omit to use recharts' default (5 on every side). */\n margin?: { top?: number; right?: number; bottom?: number; left?: number };\n /**\n * Draw the gauge's ring as this many equal segments, notched apart — the\n * segmented-gauge look. Applies to a **single-value gauge** only and needs\n * `valueDomain` to know what a full ring means. Anything that isn't one — more\n * than one row, `dataKeys` set, fewer than two segments, or a `dataKey` value\n * that isn't a number (a stringified `\"29\"` off an API) — falls back to the\n * normal concentric arcs, legend included, rather than erroring.\n *\n * The ring is then synthetic geometry rather than data rows, so the arc labels\n * and the legend are suppressed: put the readout in `centerLabel`. The tooltip\n * stays, and reads the metric (with the `valueDomain` maximum when there is\n * one) wherever on the ring it is hovered. `showBackground` is redundant too —\n * the unreached segments *are* the track.\n */\n segments?: number;\n /** Gap between a segmented gauge's segments, in degrees. Defaults to `3`. */\n segmentGap?: number;\n /** Render a muted background track behind each arc. */\n showBackground?: boolean;\n /** Draw the concentric polar grid behind the arcs. */\n showPolarGrid?: boolean;\n /**\n * Custom content for the hole at the center — a headline `value` and/or a\n * `label` caption, stacked and centered. Needs an `innerRadius` big enough to\n * hold the text; it is not clipped to the hole.\n */\n centerLabel?: RadialBarChartCenterLabel;\n showTooltip?: boolean;\n showLegend?: boolean;\n /**\n * Font size of the legend text (labels and values). Defaults to `'xs'` —\n * the size the legend has always rendered at. See `ChartLegendFontSize`.\n */\n legendFontSize?: ChartLegendFontSize;\n /**\n * Format each arc's value in the list legend. In single-metric mode each arc\n * row gets its formatted value next to its label; in multi-metric mode values\n * are per-row (not per-series) so this is ignored.\n */\n legendValueFormatter?: (value: string | number) => string;\n /**\n * Replace the default tooltip. Pass a configured `ChartTooltipContent`\n * (imported from this library) — e.g. with a `formatter` / `labelFormatter` —\n * to customize formatting, per-arc rows, or extra fields without composing\n * recharts yourself. Ignored when `showTooltip` is false.\n *\n * On a segmented gauge it takes over from the built-in reading, and the payload\n * it receives is the ring's synthetic pieces (`seg-2-track`, a length in\n * degrees) rather than data rows — read the metric off `payload[0].payload`.\n */\n tooltipContent?: React.ComponentProps<typeof ChartTooltip>['content'];\n /**\n * Position of the value labels when `showLabels` is on. Defaults to\n * `insideStart` — the labels sit inside each arc, and therefore render in the\n * on-fill label token so they stay legible over the arc colour.\n */\n labelPosition?: PolarLabelPosition;\n /**\n * What each data label reads when `showLabels` is on — the value alone (the\n * default), or the arc's name and its value. The name is the row's `nameKey`\n * value, or the metric's `config` label in multi-metric mode.\n */\n labelFormat?: RadialBarChartLabelFormat;\n}\n\nconst RadialBarChart = React.forwardRef<HTMLDivElement, RadialBarChartProps>(\n (\n {\n className,\n config,\n palette,\n data,\n dataKey,\n dataKeys,\n nameKey,\n innerRadius = 20,\n outerRadius = 60,\n startAngle = 90,\n endAngle = -270,\n valueDomain,\n cornerRadius = 4,\n cx,\n cy,\n barSize,\n barGap,\n barCategoryGap,\n minAngle,\n margin,\n segments,\n segmentGap = SEGMENT_GAP,\n showBackground = true,\n showPolarGrid = false,\n centerLabel,\n showTooltip = true,\n showLegend = true,\n legendFontSize,\n legendValueFormatter,\n tooltipContent,\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n showLabels = false,\n labelPosition,\n labelFormat = 'value',\n labelFormatter,\n ...props\n },\n ref\n ) => {\n const animation = resolveAnimation({\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n });\n const arcLabelPosition = labelPosition ?? 'insideStart';\n const isMultiMetric = !!dataKeys?.length;\n // A segmented ring is one metric cut into pieces, so it only means anything\n // for a single-row, single-metric chart.\n const isSegmented =\n segments != null &&\n segments > 1 &&\n !isMultiMetric &&\n data.length === 1 &&\n typeof data[0][dataKey] === 'number';\n const seriesKeys = isMultiMetric ? dataKeys! : [dataKey];\n // Stable across renders so the memoized legend content below keeps its\n // identity; `seriesKeys` itself is a fresh array every render.\n const legendKeys = React.useMemo(\n () => (dataKeys?.length ? dataKeys : [dataKey]),\n [dataKeys, dataKey]\n );\n const sweep = Math.abs(endAngle - startAngle);\n const ringSegments = isSegmented\n ? radialBarChartSegments({\n value: data[0][dataKey] as number,\n // Without a domain a lone value is its own maximum, so the ring fills.\n domain: valueDomain ?? [0, data[0][dataKey] as number],\n segments: segments!,\n gap: segmentGap,\n sweep,\n closed: sweep >= 360,\n })\n : [];\n\n // Stamp each row with its `fill` (the shadcn data-driven pattern) so a real\n // hover resolves the arc color in the tooltip — recharts' RadialBar, like\n // Funnel, carries no per-arc color on the tooltip payload item. Only in\n // single-metric mode: with one color per *metric*, a row-level fill would\n // paint every tooltip swatch the same.\n const seriesData: RadialBarChartDatum[] = isMultiMetric\n ? (data as RadialBarChartDatum[])\n : data.map((row) => ({\n ...row,\n fill: `var(--color-${toCssKey(String(row[nameKey]))})`,\n }));\n\n // Each piece of the ring becomes a synthetic series on the one row, measured\n // in degrees — see `radialBarChartSegments`. Built from the raw row, *not*\n // from `seriesData`: a row-level `fill` wins over a series' own `fill` in\n // recharts, which would paint the track and the notches the arc's color.\n const plotData: RadialBarChartDatum[] = isSegmented\n ? [\n {\n ...data[0],\n ...Object.fromEntries(ringSegments.map((s) => [s.key, s.degrees])),\n },\n ]\n : seriesData;\n\n // recharts measures the radial floor in degrees, then extends the arc by\n // `mathSign(deltaAngle || minPointSize)`, where `deltaAngle` is the *arc's*\n // own angular length (RadialBar.js). So for a non-zero arc the floor already\n // grows along the arc's direction whatever sign it carries; the sign only\n // decides which way an arc of exactly zero degrees grows. Carry the sweep's\n // sign so that one grows forwards on a counter-clockwise chart too.\n const minPointSize =\n minAngle === undefined\n ? undefined\n : endAngle < startAngle\n ? -minAngle\n : minAngle;\n\n // Resolve colors so the external list legend has the same values as the chart\n // plot without needing to be inside ChartContainer's context.\n const resolvedConfigForLegend = React.useMemo(\n () => (showLegend ? resolveChartColors(config, palette ?? CHART_DEFAULT_PALETTE) : null),\n [showLegend, config, palette]\n );\n const rightLegendPayload: LegendPayload[] = React.useMemo(() => {\n if (!resolvedConfigForLegend) return [];\n if (isMultiMetric) {\n // Multi-metric: one entry per dataKey. No per-series row value is\n // meaningful (each metric spans multiple rows), so values are omitted.\n return legendKeys.map((key) => ({\n value: key,\n dataKey: key,\n color: resolvedConfigForLegend[key]?.color ?? `var(--color-${toCssKey(key)})`,\n type: 'rect' as const,\n payload: {} as Record<string, unknown>,\n }));\n }\n // Single-metric: one entry per data row.\n return data.map((row) => ({\n value: String(row[nameKey]),\n dataKey: nameKey,\n color:\n resolvedConfigForLegend[String(row[nameKey])]?.color ??\n `var(--color-${toCssKey(String(row[nameKey]))})`,\n type: 'rect' as const,\n payload: row as Record<string, unknown>,\n }));\n }, [resolvedConfigForLegend, isMultiMetric, legendKeys, data, nameKey]);\n\n const hasLegend = showLegend && !isSegmented && !!resolvedConfigForLegend;\n\n // The built-in tooltip reading, which each mapping composes differently: a\n // segmented ring rebuilds it from the data row, multi-metric renames the\n // header after the band, and the default mapping reads the hovered arc.\n const getDefaultTooltipContent = () => {\n if (isSegmented) {\n return (\n <RadialBarChartSegmentedTooltipContent\n config={config}\n row={data[0]}\n nameKey={nameKey}\n dataKey={dataKey}\n domainMax={valueDomain?.[1]}\n />\n );\n }\n\n if (isMultiMetric) {\n return (\n <ChartTooltipContent\n labelFormatter={(_, payload) =>\n radialBarChartBandName(\n payload?.[0]?.payload as RadialBarChartDatum | undefined,\n nameKey\n )\n }\n />\n );\n }\n\n return <ChartTooltipContent nameKey={nameKey} hideLabel />;\n };\n\n // Shared recharts chart composition (used in both layout modes).\n const radialChartContent = (\n <RechartsRadialBarChart\n data={plotData}\n // Only meaningful for the single-metric mapping; in multi-metric and\n // segmented mode each <RadialBar> carries its own key and the scale\n // spans them all.\n dataKey={isMultiMetric || isSegmented ? undefined : dataKey}\n cx={cx}\n cy={cy}\n innerRadius={innerRadius}\n outerRadius={outerRadius}\n startAngle={startAngle}\n endAngle={endAngle}\n barSize={barSize}\n barGap={barGap}\n barCategoryGap={barCategoryGap}\n margin={margin ?? { top: 0, right: 0, bottom: 0, left: 0 }}\n >\n {showPolarGrid && (\n <PolarGrid gridType=\"circle\" radialLines={false} />\n )}\n {(valueDomain || isSegmented) && (\n // The angular scale itself, not chrome: it maps a value onto a\n // fraction of the sweep. Ticks/axis line stay off — the arcs and\n // the optional center readout are the readout. A segmented ring is\n // already measured in degrees, so its scale *is* the sweep.\n <PolarAngleAxis\n type=\"number\"\n domain={isSegmented ? [0, sweep] : valueDomain}\n tick={false}\n axisLine={false}\n />\n )}\n {showTooltip && (\n <ChartTooltip\n // The hover cursor marks which band of the angular axis is\n // active, which on a segmented ring is one metric either way —\n // it just draws a hairline across the gauge.\n cursor={isSegmented ? false : undefined}\n content={tooltipContent ?? getDefaultTooltipContent()}\n />\n )}\n {isSegmented ? (\n <RadialBarChartSegmentedSeries\n ringSegments={ringSegments}\n row={data[0]}\n nameKey={nameKey}\n cornerRadius={cornerRadius}\n animation={animation}\n centerLabel={centerLabel}\n />\n ) : (\n <RadialBarChartSeries\n seriesKeys={seriesKeys}\n seriesData={seriesData}\n isMultiMetric={isMultiMetric}\n config={config}\n nameKey={nameKey}\n showBackground={showBackground}\n cornerRadius={cornerRadius}\n minPointSize={minPointSize}\n animation={animation}\n centerLabel={centerLabel}\n showLabels={showLabels}\n labelPosition={arcLabelPosition}\n labelFormat={labelFormat}\n labelFormatter={labelFormatter}\n />\n )}\n </RechartsRadialBarChart>\n );\n\n const chartDivSize =\n showLabels && (arcLabelPosition === 'outside' || arcLabelPosition === 'end')\n ? 'size-[200px]'\n : 'size-[120px]';\n\n return (\n <div\n ref={ref}\n className={cn('flex flex-row items-center gap-4', !hasLegend && 'justify-center', className)}\n {...props}\n >\n <div className={cn(chartDivSize, 'shrink-0')}>\n <ChartContainer config={config} palette={palette} className=\"size-full\">\n {radialChartContent}\n </ChartContainer>\n </div>\n {showLegend && !isSegmented && resolvedConfigForLegend && (\n <ChartLegendContent\n variant=\"list\"\n payload={rightLegendPayload}\n config={resolvedConfigForLegend}\n nameKey={isMultiMetric ? undefined : nameKey}\n fontSize={legendFontSize}\n valueKey={isMultiMetric ? undefined : dataKey}\n valueFormatter={isMultiMetric ? undefined : legendValueFormatter}\n className=\"min-w-0 flex-1\"\n />\n )}\n </div>\n );\n }\n);\nRadialBarChart.displayName = 'RadialBarChart';\n\nexport { RadialBarChart };\n"],"mappings":"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"}
|
|
@@ -10,17 +10,17 @@ import { CartesianGrid as g, Scatter as _, ScatterChart as v, XAxis as y, YAxis
|
|
|
10
10
|
var S = p.forwardRef(({ className: S, config: C, palette: w = {
|
|
11
11
|
type: "diverging",
|
|
12
12
|
pair: "teal-violet"
|
|
13
|
-
}, series: T, xKey: E, yKey: D, zKey: O, zRange: k = [60, 400], shape: A = "circle", xAxisLabel: j, yAxisLabel: M, xUnit: N, yUnit: P, showGrid: F = !0, showTooltip: I = !0, showLegend: ne = !0,
|
|
14
|
-
let
|
|
15
|
-
animate:
|
|
16
|
-
animationDuration:
|
|
17
|
-
animationBegin:
|
|
18
|
-
animationEasing:
|
|
19
|
-
}),
|
|
13
|
+
}, series: T, xKey: E, yKey: D, zKey: O, zRange: k = [60, 400], shape: A = "circle", xAxisLabel: j, yAxisLabel: M, xUnit: N, yUnit: P, showGrid: F = !0, showTooltip: I = !0, showLegend: ne = !0, legendFontSize: L, showXAxis: R = !0, showYAxis: z = !0, xTickFormatter: B, yTickFormatter: V, xAxisAngle: H, xAxisInterval: U, yAxisTickCount: W, yAxisDomain: G, gridDashed: K = !0, gridHorizontal: q, gridVertical: J, tooltipContent: Y, animate: X, animationDuration: Z, animationBegin: re, animationEasing: ie, ...ae }, oe) => {
|
|
14
|
+
let se = o({
|
|
15
|
+
animate: X,
|
|
16
|
+
animationDuration: Z,
|
|
17
|
+
animationBegin: re,
|
|
18
|
+
animationEasing: ie
|
|
19
|
+
}), ce = u(j, "insideBottom", -8), le = d(M), ue = s(G), de = l(T.flatMap(({ data: e }) => e.map((e) => e[E])), B), Q = p.useRef(null), $ = te(Q, H, j, de.join("\0"));
|
|
20
20
|
return /* @__PURE__ */ m("div", {
|
|
21
|
-
ref:
|
|
21
|
+
ref: oe,
|
|
22
22
|
className: e(S),
|
|
23
|
-
...
|
|
23
|
+
...ae,
|
|
24
24
|
children: /* @__PURE__ */ m(t, {
|
|
25
25
|
ref: Q,
|
|
26
26
|
config: C,
|
|
@@ -35,40 +35,40 @@ var S = p.forwardRef(({ className: S, config: C, palette: w = {
|
|
|
35
35
|
}, $.margin),
|
|
36
36
|
children: [
|
|
37
37
|
F && /* @__PURE__ */ m(g, {
|
|
38
|
-
horizontal:
|
|
39
|
-
vertical:
|
|
40
|
-
strokeDasharray:
|
|
38
|
+
horizontal: q ?? !0,
|
|
39
|
+
vertical: J ?? !1,
|
|
40
|
+
strokeDasharray: K ? "3 3" : void 0
|
|
41
41
|
}),
|
|
42
42
|
/* @__PURE__ */ m(y, {
|
|
43
43
|
type: "number",
|
|
44
44
|
dataKey: E,
|
|
45
45
|
name: E,
|
|
46
46
|
unit: N,
|
|
47
|
-
hide: !
|
|
47
|
+
hide: !R,
|
|
48
48
|
tickLine: !1,
|
|
49
49
|
axisLine: !1,
|
|
50
50
|
tickMargin: 8,
|
|
51
|
-
tickFormatter:
|
|
52
|
-
angle:
|
|
53
|
-
interval:
|
|
54
|
-
textAnchor: c(
|
|
51
|
+
tickFormatter: B,
|
|
52
|
+
angle: H,
|
|
53
|
+
interval: U,
|
|
54
|
+
textAnchor: c(H),
|
|
55
55
|
height: $.height,
|
|
56
|
-
label:
|
|
56
|
+
label: ce
|
|
57
57
|
}),
|
|
58
58
|
/* @__PURE__ */ m(b, {
|
|
59
59
|
type: "number",
|
|
60
60
|
dataKey: D,
|
|
61
61
|
name: D,
|
|
62
62
|
unit: P,
|
|
63
|
-
hide: !
|
|
63
|
+
hide: !z,
|
|
64
64
|
tickLine: !1,
|
|
65
65
|
axisLine: !1,
|
|
66
66
|
tickMargin: 8,
|
|
67
|
-
tickFormatter:
|
|
68
|
-
tickCount:
|
|
69
|
-
domain:
|
|
67
|
+
tickFormatter: V,
|
|
68
|
+
tickCount: W,
|
|
69
|
+
domain: ue,
|
|
70
70
|
width: M ? 72 : void 0,
|
|
71
|
-
label:
|
|
71
|
+
label: le
|
|
72
72
|
}),
|
|
73
73
|
O && /* @__PURE__ */ m(x, {
|
|
74
74
|
type: "number",
|
|
@@ -78,15 +78,15 @@ var S = p.forwardRef(({ className: S, config: C, palette: w = {
|
|
|
78
78
|
}),
|
|
79
79
|
I && /* @__PURE__ */ m(r, {
|
|
80
80
|
cursor: { strokeDasharray: "3 3" },
|
|
81
|
-
content:
|
|
81
|
+
content: Y ?? /* @__PURE__ */ m(i, {})
|
|
82
82
|
}),
|
|
83
|
-
ne && /* @__PURE__ */ m(ee, { content: /* @__PURE__ */ m(n, {}) }),
|
|
83
|
+
ne && /* @__PURE__ */ m(ee, { content: /* @__PURE__ */ m(n, { fontSize: L }) }),
|
|
84
84
|
T.map((e) => /* @__PURE__ */ m(_, {
|
|
85
85
|
name: e.key,
|
|
86
86
|
data: e.data,
|
|
87
87
|
fill: `var(--color-${a(e.key)})`,
|
|
88
88
|
shape: A,
|
|
89
|
-
...
|
|
89
|
+
...se
|
|
90
90
|
}, e.key))
|
|
91
91
|
]
|
|
92
92
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scatter-chart.js","names":[],"sources":["../../../../src/components/ui/scatter-chart/scatter-chart.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport {\n CartesianGrid,\n Scatter,\n ScatterChart as RechartsScatterChart,\n XAxis,\n YAxis,\n ZAxis,\n} from 'recharts';\n\nimport { cn } from '@/lib/utils';\nimport {\n ChartContainer,\n ChartLegend,\n ChartLegendContent,\n ChartTooltip,\n ChartTooltipContent,\n resolveAnimation,\n resolveAxisDomain,\n resolveRotatedTickAnchor,\n resolveXAxisTitle,\n resolveYAxisTitle,\n type ChartConfig,\n type ChartPalette,\n toCssKey,\n type CartesianChartProps,\n type ChartAnimationProps,\n} from '../chart';\nimport {\n mergeXAxisLayoutMargin,\n useXAxisLayout,\n} from '../chart/use-x-axis-layout';\nimport { resolveXAxisTickLabels } from '../chart/chart-format';\n\n// A typed recharts composition over the shared `Chart` primitives. Unlike the\n// other chart types, a scatter has no visual \"mode\" to model as a CVA variant —\n// its shape is fixed (x/y points, optionally sized by z) and its expressiveness\n// comes from the data mapping. So there is no `cva` axis here (matches the\n// skill's \"usually none\"); marker `shape` and bubble sizing are plain props.\n// Each series carries its own point array (points aren't columns of a shared\n// row the way bar/line/area series are), so the API takes a `series` list rather\n// than `dataKeys` over one `data` array.\nexport type ScatterMarkerShape =\n | 'circle'\n | 'cross'\n | 'diamond'\n | 'square'\n | 'star'\n | 'triangle'\n | 'wye';\n\nexport interface ScatterSeries {\n /** Series key — must match a `config` entry; drives its `--color-<key>` fill and legend label. */\n key: string;\n /** This series' points — each row holds at least `xKey` and `yKey` (and `zKey` when used). */\n data: ReadonlyArray<Record<string, number>>;\n}\n\nexport interface ScatterChartProps\n extends Omit<React.ComponentProps<'div'>, 'children'>,\n CartesianChartProps,\n ChartAnimationProps {\n /**\n * The dataviz palette this chart's series are painted from. Series that\n * state no `color` of their own take a stop of it. See `ChartPalette`.\n */\n palette?: ChartPalette;\n /** One `<Scatter>` per entry — each with its own point array. Use a single entry for an ungrouped scatter. */\n series: ScatterSeries[];\n /**\n * Per-series map of `label` / `icon` / `tone`, keyed by `series[].key`\n * (imported from the shared `Chart` primitives). Turned into `--color-<key>`\n * custom properties. Series take their colour from the container's `palette`;\n * each entry maps a key to a `label` and an optional `tone`.\n */\n config: ChartConfig;\n /** Numeric field for the horizontal axis. */\n xKey: string;\n /** Numeric field for the vertical axis. */\n yKey: string;\n /** Unit suffix appended to X-axis tick values (recharts `unit`). */\n xUnit?: string;\n /** Optional numeric field mapped to point size (a bubble chart), via recharts `ZAxis`. */\n zKey?: string;\n /** Point-size range `[min, max]` the `zKey` maps into. Ignored when `zKey` is unset (points use recharts' default size). */\n zRange?: [number, number];\n /** Marker shape for every point. */\n shape?: ScatterMarkerShape;\n showLegend?: boolean;\n}\n\nconst ScatterChart = React.forwardRef<HTMLDivElement, ScatterChartProps>(\n (\n {\n className,\n config,\n palette = { type: 'diverging', pair: 'teal-violet' },\n series,\n xKey,\n yKey,\n zKey,\n zRange = [60, 400],\n shape = 'circle',\n xAxisLabel,\n yAxisLabel,\n xUnit,\n yUnit,\n showGrid = true,\n showTooltip = true,\n showLegend = true,\n showXAxis = true,\n showYAxis = true,\n xTickFormatter,\n yTickFormatter,\n xAxisAngle,\n xAxisInterval,\n yAxisTickCount,\n yAxisDomain,\n gridDashed = true,\n gridHorizontal,\n gridVertical,\n tooltipContent,\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n ...props\n },\n ref\n ) => {\n const animation = resolveAnimation({\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n });\n // Axis titles: the X title sits below the ticks; the Y title is rotated in\n // the left gutter. Passed to recharts' native `label` (themed via the\n // `.recharts-label` fill selector on the container).\n const xAxisTitle = resolveXAxisTitle(\n xAxisLabel,\n 'insideBottom',\n -8\n );\n const yAxisTitle = resolveYAxisTitle(yAxisLabel);\n\n const yDomain = resolveAxisDomain(yAxisDomain);\n\n const xAxisTickLabels = resolveXAxisTickLabels(\n series.flatMap(({ data }) => data.map((row) => row[xKey])),\n xTickFormatter\n );\n const chartContainerRef = React.useRef<HTMLDivElement>(null);\n const xAxisLayout = useXAxisLayout(\n chartContainerRef,\n xAxisAngle,\n xAxisLabel,\n xAxisTickLabels.join('\\u0000')\n );\n\n return (\n <div ref={ref} className={cn(className)} {...props}>\n <ChartContainer\n ref={chartContainerRef}\n config={config}\n palette={palette}\n className=\"size-full [&_.recharts-label]:fill-foreground\"\n >\n <RechartsScatterChart\n margin={mergeXAxisLayoutMargin(\n { top: 16, right: 16, bottom: 16, left: 16 },\n xAxisLayout.margin\n )}\n >\n {showGrid && (\n <CartesianGrid\n horizontal={gridHorizontal ?? true}\n vertical={gridVertical ?? false}\n strokeDasharray={gridDashed ? '3 3' : undefined}\n />\n )}\n <XAxis\n type=\"number\"\n dataKey={xKey}\n name={xKey}\n unit={xUnit}\n hide={!showXAxis}\n tickLine={false}\n axisLine={false}\n tickMargin={8}\n tickFormatter={xTickFormatter}\n angle={xAxisAngle}\n interval={xAxisInterval}\n textAnchor={resolveRotatedTickAnchor(xAxisAngle)}\n height={xAxisLayout.height}\n label={xAxisTitle}\n />\n <YAxis\n type=\"number\"\n dataKey={yKey}\n name={yKey}\n unit={yUnit}\n hide={!showYAxis}\n tickLine={false}\n axisLine={false}\n tickMargin={8}\n tickFormatter={yTickFormatter}\n tickCount={yAxisTickCount}\n domain={yDomain}\n width={yAxisLabel ? 72 : undefined}\n label={yAxisTitle}\n />\n {zKey && (\n <ZAxis type=\"number\" dataKey={zKey} range={zRange} name={zKey} />\n )}\n {showTooltip && (\n <ChartTooltip\n cursor={{ strokeDasharray: '3 3' }}\n content={tooltipContent ?? <ChartTooltipContent />}\n />\n )}\n {showLegend && <ChartLegend
|
|
1
|
+
{"version":3,"file":"scatter-chart.js","names":[],"sources":["../../../../src/components/ui/scatter-chart/scatter-chart.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport {\n CartesianGrid,\n Scatter,\n ScatterChart as RechartsScatterChart,\n XAxis,\n YAxis,\n ZAxis,\n} from 'recharts';\n\nimport { cn } from '@/lib/utils';\nimport {\n ChartContainer,\n ChartLegend,\n ChartLegendContent,\n ChartTooltip,\n ChartTooltipContent,\n resolveAnimation,\n resolveAxisDomain,\n resolveRotatedTickAnchor,\n resolveXAxisTitle,\n resolveYAxisTitle,\n type ChartConfig,\n type ChartLegendFontSize,\n type ChartPalette,\n toCssKey,\n type CartesianChartProps,\n type ChartAnimationProps,\n} from '../chart';\nimport {\n mergeXAxisLayoutMargin,\n useXAxisLayout,\n} from '../chart/use-x-axis-layout';\nimport { resolveXAxisTickLabels } from '../chart/chart-format';\n\n// A typed recharts composition over the shared `Chart` primitives. Unlike the\n// other chart types, a scatter has no visual \"mode\" to model as a CVA variant —\n// its shape is fixed (x/y points, optionally sized by z) and its expressiveness\n// comes from the data mapping. So there is no `cva` axis here (matches the\n// skill's \"usually none\"); marker `shape` and bubble sizing are plain props.\n// Each series carries its own point array (points aren't columns of a shared\n// row the way bar/line/area series are), so the API takes a `series` list rather\n// than `dataKeys` over one `data` array.\nexport type ScatterMarkerShape =\n | 'circle'\n | 'cross'\n | 'diamond'\n | 'square'\n | 'star'\n | 'triangle'\n | 'wye';\n\nexport interface ScatterSeries {\n /** Series key — must match a `config` entry; drives its `--color-<key>` fill and legend label. */\n key: string;\n /** This series' points — each row holds at least `xKey` and `yKey` (and `zKey` when used). */\n data: ReadonlyArray<Record<string, number>>;\n}\n\nexport interface ScatterChartProps\n extends Omit<React.ComponentProps<'div'>, 'children'>,\n CartesianChartProps,\n ChartAnimationProps {\n /**\n * The dataviz palette this chart's series are painted from. Series that\n * state no `color` of their own take a stop of it. See `ChartPalette`.\n */\n palette?: ChartPalette;\n /** One `<Scatter>` per entry — each with its own point array. Use a single entry for an ungrouped scatter. */\n series: ScatterSeries[];\n /**\n * Per-series map of `label` / `icon` / `tone`, keyed by `series[].key`\n * (imported from the shared `Chart` primitives). Turned into `--color-<key>`\n * custom properties. Series take their colour from the container's `palette`;\n * each entry maps a key to a `label` and an optional `tone`.\n */\n config: ChartConfig;\n /** Numeric field for the horizontal axis. */\n xKey: string;\n /** Numeric field for the vertical axis. */\n yKey: string;\n /** Unit suffix appended to X-axis tick values (recharts `unit`). */\n xUnit?: string;\n /** Optional numeric field mapped to point size (a bubble chart), via recharts `ZAxis`. */\n zKey?: string;\n /** Point-size range `[min, max]` the `zKey` maps into. Ignored when `zKey` is unset (points use recharts' default size). */\n zRange?: [number, number];\n /** Marker shape for every point. */\n shape?: ScatterMarkerShape;\n showLegend?: boolean;\n /**\n * Font size of the legend text. Defaults to `'xs'` — the size the legend has\n * always rendered at. See `ChartLegendFontSize`.\n */\n legendFontSize?: ChartLegendFontSize;\n}\n\nconst ScatterChart = React.forwardRef<HTMLDivElement, ScatterChartProps>(\n (\n {\n className,\n config,\n palette = { type: 'diverging', pair: 'teal-violet' },\n series,\n xKey,\n yKey,\n zKey,\n zRange = [60, 400],\n shape = 'circle',\n xAxisLabel,\n yAxisLabel,\n xUnit,\n yUnit,\n showGrid = true,\n showTooltip = true,\n showLegend = true,\n legendFontSize,\n showXAxis = true,\n showYAxis = true,\n xTickFormatter,\n yTickFormatter,\n xAxisAngle,\n xAxisInterval,\n yAxisTickCount,\n yAxisDomain,\n gridDashed = true,\n gridHorizontal,\n gridVertical,\n tooltipContent,\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n ...props\n },\n ref\n ) => {\n const animation = resolveAnimation({\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n });\n // Axis titles: the X title sits below the ticks; the Y title is rotated in\n // the left gutter. Passed to recharts' native `label` (themed via the\n // `.recharts-label` fill selector on the container).\n const xAxisTitle = resolveXAxisTitle(\n xAxisLabel,\n 'insideBottom',\n -8\n );\n const yAxisTitle = resolveYAxisTitle(yAxisLabel);\n\n const yDomain = resolveAxisDomain(yAxisDomain);\n\n const xAxisTickLabels = resolveXAxisTickLabels(\n series.flatMap(({ data }) => data.map((row) => row[xKey])),\n xTickFormatter\n );\n const chartContainerRef = React.useRef<HTMLDivElement>(null);\n const xAxisLayout = useXAxisLayout(\n chartContainerRef,\n xAxisAngle,\n xAxisLabel,\n xAxisTickLabels.join('\\u0000')\n );\n\n return (\n <div ref={ref} className={cn(className)} {...props}>\n <ChartContainer\n ref={chartContainerRef}\n config={config}\n palette={palette}\n className=\"size-full [&_.recharts-label]:fill-foreground\"\n >\n <RechartsScatterChart\n margin={mergeXAxisLayoutMargin(\n { top: 16, right: 16, bottom: 16, left: 16 },\n xAxisLayout.margin\n )}\n >\n {showGrid && (\n <CartesianGrid\n horizontal={gridHorizontal ?? true}\n vertical={gridVertical ?? false}\n strokeDasharray={gridDashed ? '3 3' : undefined}\n />\n )}\n <XAxis\n type=\"number\"\n dataKey={xKey}\n name={xKey}\n unit={xUnit}\n hide={!showXAxis}\n tickLine={false}\n axisLine={false}\n tickMargin={8}\n tickFormatter={xTickFormatter}\n angle={xAxisAngle}\n interval={xAxisInterval}\n textAnchor={resolveRotatedTickAnchor(xAxisAngle)}\n height={xAxisLayout.height}\n label={xAxisTitle}\n />\n <YAxis\n type=\"number\"\n dataKey={yKey}\n name={yKey}\n unit={yUnit}\n hide={!showYAxis}\n tickLine={false}\n axisLine={false}\n tickMargin={8}\n tickFormatter={yTickFormatter}\n tickCount={yAxisTickCount}\n domain={yDomain}\n width={yAxisLabel ? 72 : undefined}\n label={yAxisTitle}\n />\n {zKey && (\n <ZAxis type=\"number\" dataKey={zKey} range={zRange} name={zKey} />\n )}\n {showTooltip && (\n <ChartTooltip\n cursor={{ strokeDasharray: '3 3' }}\n content={tooltipContent ?? <ChartTooltipContent />}\n />\n )}\n {showLegend && (\n <ChartLegend\n content={<ChartLegendContent fontSize={legendFontSize} />}\n />\n )}\n {series.map((s) => (\n <Scatter\n key={s.key}\n name={s.key}\n data={s.data as Record<string, number>[]}\n fill={`var(--color-${toCssKey(s.key)})`}\n shape={shape}\n {...animation}\n />\n ))}\n </RechartsScatterChart>\n </ChartContainer>\n </div>\n );\n }\n);\nScatterChart.displayName = 'ScatterChart';\n\nexport { ScatterChart };\n"],"mappings":";;;;;;;;;AAmGA,IAAM,IAAe,EAAM,YAEvB,EACE,cACA,WACA,aAAU;CAAE,MAAM;CAAa,MAAM;AAAc,GACnD,WACA,SACA,SACA,SACA,YAAS,CAAC,IAAI,GAAG,GACjB,WAAQ,UACR,eACA,eACA,UACA,UACA,cAAW,IACX,iBAAc,IACd,iBAAa,IACb,mBACA,eAAY,IACZ,eAAY,IACZ,mBACA,mBACA,eACA,kBACA,mBACA,gBACA,gBAAa,IACb,mBACA,iBACA,mBACA,YACA,sBACA,oBACA,qBACA,GAAG,MAEL,OACG;CACH,IAAM,KAAY,EAAiB;EACjC;EACA;EACA;EACA;CACF,CAAC,GAIK,KAAa,EACjB,GACA,gBACA,EACF,GACM,KAAa,EAAkB,CAAU,GAEzC,KAAU,EAAkB,CAAW,GAEvC,KAAkB,EACtB,EAAO,SAAS,EAAE,cAAW,EAAK,KAAK,MAAQ,EAAI,EAAK,CAAC,GACzD,CACF,GACM,IAAoB,EAAM,OAAuB,IAAI,GACrD,IAAc,GAClB,GACA,GACA,GACA,GAAgB,KAAK,IAAQ,CAC/B;CAEA,OACE,kBAAC,OAAD;EAAU;EAAK,WAAW,EAAG,CAAS;EAAG,GAAI;EAC3C,UAAA,kBAAC,GAAD;GACE,KAAK;GACG;GACC;GACT,WAAU;GAEV,UAAA,kBAAC,GAAD;IACE,QAAQ,EACN;KAAE,KAAK;KAAI,OAAO;KAAI,QAAQ;KAAI,MAAM;IAAG,GAC3C,EAAY,MACd;IAJF,UAAA;KAMG,KACC,kBAAC,GAAD;MACE,YAAY,KAAkB;MAC9B,UAAU,KAAgB;MAC1B,iBAAiB,IAAa,QAAQ,KAAA;KACvC,CAAA;KAEH,kBAAC,GAAD;MACE,MAAK;MACL,SAAS;MACT,MAAM;MACN,MAAM;MACN,MAAM,CAAC;MACP,UAAU;MACV,UAAU;MACV,YAAY;MACZ,eAAe;MACf,OAAO;MACP,UAAU;MACV,YAAY,EAAyB,CAAU;MAC/C,QAAQ,EAAY;MACpB,OAAO;KACR,CAAA;KACD,kBAAC,GAAD;MACE,MAAK;MACL,SAAS;MACT,MAAM;MACN,MAAM;MACN,MAAM,CAAC;MACP,UAAU;MACV,UAAU;MACV,YAAY;MACZ,eAAe;MACf,WAAW;MACX,QAAQ;MACR,OAAO,IAAa,KAAK,KAAA;MACzB,OAAO;KACR,CAAA;KACA,KACC,kBAAC,GAAD;MAAO,MAAK;MAAS,SAAS;MAAM,OAAO;MAAQ,MAAM;KAAO,CAAA;KAEjE,KACC,kBAAC,GAAD;MACE,QAAQ,EAAE,iBAAiB,MAAM;MACjC,SAAS,KAAkB,kBAAC,GAAD,CAAsB,CAAA;KAClD,CAAA;KAEF,MACC,kBAAC,IAAD,EACE,SAAS,kBAAC,GAAD,EAAoB,UAAU,EAAiB,CAAA,EACzD,CAAA;KAEF,EAAO,KAAK,MACX,kBAAC,GAAD;MAEE,MAAM,EAAE;MACR,MAAM,EAAE;MACR,MAAM,eAAe,EAAS,EAAE,GAAG,EAAE;MAC9B;MACP,GAAI;KACL,GANM,EAAE,GAMR,CACF;IACmB;;EACR,CAAA;CACb,CAAA;AAET,CACF;AACA,EAAa,cAAc"}
|
|
@@ -52,13 +52,13 @@ function x({ x: t = 0, y: n = 0, width: r = 0, height: i = 0, name: a, primaryLa
|
|
|
52
52
|
})
|
|
53
53
|
})] });
|
|
54
54
|
}
|
|
55
|
-
var S = l.forwardRef(({ className: m, config: h, palette: g = p, data: _, dataKey: v, nameKey: b, aspectRatio: S = 4 / 3, showLabels: C = !0, labelAlign: w = "center", secondaryKeys: T, secondaryFormatter: E, secondarySeparator: D = " · ", showTooltip: O = !0, showLegend: k = !1, legendPos: A = "bottom",
|
|
56
|
-
let
|
|
57
|
-
animate:
|
|
58
|
-
animationDuration:
|
|
59
|
-
animationBegin:
|
|
60
|
-
animationEasing:
|
|
61
|
-
}),
|
|
55
|
+
var S = l.forwardRef(({ className: m, config: h, palette: g = p, data: _, dataKey: v, nameKey: b, aspectRatio: S = 4 / 3, showLabels: C = !0, labelAlign: w = "center", secondaryKeys: T, secondaryFormatter: E, secondarySeparator: D = " · ", showTooltip: O = !0, showLegend: k = !1, legendPos: A = "bottom", legendFontSize: j, tooltipContent: M, animate: N, animationDuration: P, animationBegin: F, animationEasing: I, ...L }, R) => {
|
|
56
|
+
let z = c({
|
|
57
|
+
animate: N,
|
|
58
|
+
animationDuration: P,
|
|
59
|
+
animationBegin: F,
|
|
60
|
+
animationEasing: I
|
|
61
|
+
}), B = l.useMemo(() => {
|
|
62
62
|
let e = o(h, g);
|
|
63
63
|
return _.map((t) => {
|
|
64
64
|
let n = String(t[b]), r = T?.length ? y({
|
|
@@ -83,9 +83,9 @@ var S = l.forwardRef(({ className: m, config: h, palette: g = p, data: _, dataKe
|
|
|
83
83
|
E,
|
|
84
84
|
T,
|
|
85
85
|
D
|
|
86
|
-
]),
|
|
86
|
+
]), V = l.useMemo(() => {
|
|
87
87
|
let e = /* @__PURE__ */ new Set();
|
|
88
|
-
return
|
|
88
|
+
return B.flatMap((t) => {
|
|
89
89
|
let n = String(t[b]);
|
|
90
90
|
return e.has(n) ? [] : (e.add(n), [{
|
|
91
91
|
value: n,
|
|
@@ -95,46 +95,46 @@ var S = l.forwardRef(({ className: m, config: h, palette: g = p, data: _, dataKe
|
|
|
95
95
|
payload: t
|
|
96
96
|
}]);
|
|
97
97
|
});
|
|
98
|
-
}, [b,
|
|
99
|
-
"data-chart":
|
|
100
|
-
className: "text-xs",
|
|
98
|
+
}, [b, B]), H = `chart-${l.useId().replace(/:/g, "")}`, U = k ? /* @__PURE__ */ d("div", {
|
|
99
|
+
"data-chart": H,
|
|
101
100
|
children: [/* @__PURE__ */ u(r, {
|
|
102
|
-
id:
|
|
101
|
+
id: H,
|
|
103
102
|
config: o(h, g)
|
|
104
103
|
}), /* @__PURE__ */ u(n, {
|
|
105
104
|
config: h,
|
|
106
|
-
payload:
|
|
105
|
+
payload: V,
|
|
107
106
|
verticalAlign: A,
|
|
108
|
-
nameKey: b
|
|
107
|
+
nameKey: b,
|
|
108
|
+
fontSize: j
|
|
109
109
|
})]
|
|
110
110
|
}) : null;
|
|
111
111
|
return /* @__PURE__ */ d("div", {
|
|
112
|
-
ref:
|
|
112
|
+
ref: R,
|
|
113
113
|
className: e(k && "flex flex-col", m),
|
|
114
|
-
...
|
|
114
|
+
...L,
|
|
115
115
|
children: [
|
|
116
|
-
A === "top" &&
|
|
116
|
+
A === "top" && U,
|
|
117
117
|
/* @__PURE__ */ u(t, {
|
|
118
118
|
config: h,
|
|
119
119
|
palette: g,
|
|
120
120
|
className: k ? "min-h-0 w-full flex-1" : "size-full",
|
|
121
121
|
children: /* @__PURE__ */ u(f, {
|
|
122
|
-
data:
|
|
122
|
+
data: B,
|
|
123
123
|
dataKey: v,
|
|
124
124
|
nameKey: b,
|
|
125
125
|
aspectRatio: S,
|
|
126
|
-
...
|
|
126
|
+
...z,
|
|
127
127
|
content: /* @__PURE__ */ u(x, {
|
|
128
128
|
showLabels: C,
|
|
129
129
|
labelAlign: w
|
|
130
130
|
}),
|
|
131
|
-
children: O && /* @__PURE__ */ u(i, { content:
|
|
131
|
+
children: O && /* @__PURE__ */ u(i, { content: M ?? /* @__PURE__ */ u(a, {
|
|
132
132
|
nameKey: b,
|
|
133
133
|
hideLabel: !0
|
|
134
134
|
}) })
|
|
135
135
|
})
|
|
136
136
|
}),
|
|
137
|
-
A === "bottom" &&
|
|
137
|
+
A === "bottom" && U
|
|
138
138
|
]
|
|
139
139
|
});
|
|
140
140
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"treemap.js","names":[],"sources":["../../../../src/components/ui/treemap/treemap.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { Treemap as RechartsTreemap } from 'recharts';\nimport type { LegendPayload } from 'recharts/types/component/DefaultLegendContent';\n\nimport { cn } from '@/lib/utils';\nimport {\n ChartContainer,\n ChartLegendContent,\n ChartStyle,\n resolveChartColors,\n ChartTooltip,\n ChartTooltipContent,\n resolveAnimation,\n toCssKey,\n type ChartConfig,\n type ChartPalette,\n type ChartAnimationProps,\n type TickFormatter,\n} from '../chart';\n\n// Treemap-specific default: the diverging palette shows the two-sided distribution\n// that a treemap is most commonly used for, and its adaptive text (auto-computed\n// from resolved token names below) works correctly out of the box.\nconst TREEMAP_DEFAULT_PALETTE: ChartPalette = { type: 'diverging', pair: 'blue-orange' };\n\n// A typed recharts composition over the shared `Chart` primitives. A treemap is\n// the odd one out: a single `Treemap` element with no axes/grid, themed through a\n// custom `content` cell renderer (recharts' default paints every cell the same\n// fill and has no token hooks) — and that renderer, not a `LabelList`, owns the\n// on-cell labels, so the fit/clamp logic lives here. Like Scatter/Composed/\n// RadialBar there's no CVA variant — a treemap's knobs are geometry\n// (`aspectRatio`) and the data, not a visual \"mode\". v1 is a flat treemap (a list\n// of leaves).\n\n/**\n * Where a cell's label block sits inside its rectangle. Named logically, not\n * physically: the corner variants anchor to the tile's *start* edge, so they\n * mirror under `dir=\"rtl\"`. `bottom-start` is the design's placement; `center`\n * is what the chart drew before the block had a second line.\n */\nexport type TreemapLabelAlign = 'bottom-start' | 'top-start' | 'center';\n\n// Cell shape: each tile is inset by the gap on every side, so neighbouring tiles\n// are separated by the surface showing through rather than by a stroke, and the\n// corners are rounded.\nconst CELL_GAP = 2;\nconst CELL_RADIUS = 0;\n\n// Label geometry, in px. The block's own layout is CSS (see the renderer); these\n// are the heights the tile has to clear for a label to fit *without being\n// clipped* — one line, or two.\n//\n// Line boxes, not font sizes: Tailwind's `--text-sm--line-height` is 4/3, so the\n// `text-sm` title renders a ~19px line box, and the second line inherits that same\n// ratio (an arbitrary `text-[11px]` sets font-size only) for ~14.7px. Measuring by\n// font size instead would let a tile pass the threshold and still have the block's\n// `overflow-hidden` eat the top of the first line.\nconst CELL_PADDING = 12;\nconst LINE_HEIGHT_RATIO = 4 / 3;\nconst TITLE_FONT_SIZE = 14; // text-sm\nconst SECONDARY_FONT_SIZE = 11;\nconst TITLE_LINE_HEIGHT = Math.ceil(TITLE_FONT_SIZE * LINE_HEIGHT_RATIO);\nconst SECONDARY_LINE_HEIGHT = Math.ceil(\n SECONDARY_FONT_SIZE * LINE_HEIGHT_RATIO\n);\n\n// The smallest tile that gets a label at all, and the height a second line\n// additionally needs. Below the first threshold a cell stays blank rather than\n// showing a stub — the label would be wider than the tile it names.\nconst MIN_LABEL_WIDTH = 64;\nconst MIN_LABEL_HEIGHT = CELL_PADDING * 2 + TITLE_LINE_HEIGHT;\nconst MIN_TWO_LINE_HEIGHT = MIN_LABEL_HEIGHT + SECONDARY_LINE_HEIGHT;\n\n/**\n * Compose a cell's second line out of the caller's `secondaryKeys`. Empty and\n * missing fields are skipped rather than printed as a gap, so one row without\n * the field doesn't leave a dangling separator.\n */\nexport function treemapSecondaryLabel(options: {\n row: Record<string, string | number>;\n keys: readonly string[];\n separator: string;\n formatter?: TickFormatter;\n}): string | undefined {\n const { row, keys, separator, formatter } = options;\n const parts = keys.flatMap((key, index) => {\n const value = row[key];\n if (value == null || value === '') return [];\n // Emptiness is re-checked *after* formatting, not just on the raw value: a\n // formatter that blanks a field it doesn't want to show (a zero, a sentinel)\n // would otherwise contribute an empty part and put back the dangling\n // separator this function exists to avoid.\n const part = formatter ? formatter(value, index) : String(value);\n return part === '' ? [] : [part];\n });\n return parts.length ? parts.join(separator) : undefined;\n}\n\ninterface TreemapCellProps {\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n // recharts injects the node's resolved `name` (from nameKey) + value, plus\n // every other field on the row — which is how the two stamped labels arrive.\n name?: string;\n /** The leaf's display name — its `config` label, stamped onto the row by `Treemap`. */\n primaryLabel?: string;\n /** The label's precomputed second line, stamped onto each row by `Treemap`. */\n secondaryLabel?: string;\n showLabels?: boolean;\n labelAlign?: TreemapLabelAlign;\n /**\n * How the leaf's fill relates to text contrast. Stamped by `Treemap` onto each\n * row before recharts hands it to the cell renderer. Auto-computed from the\n * resolved token name suffix:\n *\n * - `'dark'` — saturated/dark in both themes → always white text.\n * - `'pale'` — pale in both themes → always dark text (via theme-adaptive token).\n * - `'inverts'` — dark in light, pale in dark (sequential stops 7–8 whose\n * `light-dark()` values mirror across themes) → white text in light, dark text\n * in dark, achieved by a `light-dark()` color value in CSS.\n */\n fillTone?: 'dark' | 'pale' | 'inverts';\n}\n\n// How the label block sits in its tile. `text-start`/`text-center` and the\n// block-axis `justify-*` are what make the corner-anchored variants mirror under\n// `dir=\"rtl\"` — the same logical utilities every other component uses, which is\n// why the label is HTML in a `foreignObject` rather than SVG `<text>`:\n// `x`/`text-anchor` are physical, so a mirrored label would need the direction\n// read in JS and applied by hand.\n//\n// Every variant leaves the cross axis on the default `items-stretch`, including\n// `center`. Centering the line is `text-center`'s job, not `items-center`'s:\n// `items-center` would size each line to its own text instead of to the tile, and\n// a `truncate` line only ellipsizes when it is the *tile* that constrains it — a\n// shrink-to-fit line is never narrower than its text, so it would silently\n// overflow and be hard-clipped on both edges instead.\nconst LABEL_ALIGN_CLASS: Record<TreemapLabelAlign, string> = {\n 'bottom-start': 'justify-end text-start',\n 'top-start': 'justify-start text-start',\n center: 'justify-center text-center',\n};\n\n// Cell renderer: fill each rounded rect from its `--color-<name>` var, inset it so\n// the surface shows through between tiles, and lay the label block over it — the\n// leaf's name, plus the stamped second line when the tile is tall enough. Passed to\n// `Treemap.content`; recharts clones it with each node's geometry + row fields.\nexport function TreemapCell({\n x = 0,\n y = 0,\n width = 0,\n height = 0,\n name,\n primaryLabel,\n secondaryLabel,\n showLabels = true,\n labelAlign = 'center',\n fillTone = 'dark',\n}: TreemapCellProps) {\n // recharts invokes `content` for the synthetic root node too (full chart\n // dimensions, empty name). Skip any name-less node — otherwise its rect has no\n // `--color-<name>` fill and paints the SVG-default black behind everything\n // (invisible when leaves cover it, a black box on empty data).\n if (!name) return <g />;\n // The tile, not the node: the label sits inside the inset rect, so the gutter\n // never eats into its padding.\n const tileX = x + CELL_GAP;\n const tileY = y + CELL_GAP;\n const tileWidth = Math.max(0, width - CELL_GAP * 2);\n const tileHeight = Math.max(0, height - CELL_GAP * 2);\n\n // `>=` on the heights: they are exact fits (padding + line boxes), so a tile\n // that measures one of them exactly has room for that label. The width is a\n // judgement call about how narrow a label is still worth reading, not a fit, so\n // it stays a strict `>`.\n const canLabel =\n showLabels && tileWidth > MIN_LABEL_WIDTH && tileHeight >= MIN_LABEL_HEIGHT;\n const secondary =\n canLabel && secondaryLabel && tileHeight >= MIN_TWO_LINE_HEIGHT\n ? secondaryLabel\n : undefined;\n\n return (\n <g>\n <rect\n x={tileX}\n y={tileY}\n width={tileWidth}\n height={tileHeight}\n rx={CELL_RADIUS}\n style={{ fill: `var(--color-${toCssKey(name)})` }}\n />\n {canLabel && (\n // Decorative: the hover that opens the tooltip belongs to the tile, so the\n // label must not swallow it.\n <foreignObject\n x={tileX}\n y={tileY}\n width={tileWidth}\n height={tileHeight}\n className=\"pointer-events-none\"\n >\n <div\n className={cn(\n // Each line truncates with a real ellipsis — CSS measures the text,\n // so nothing has to estimate how much of it fits.\n 'flex size-full flex-col overflow-hidden p-3',\n // Adaptive text — three tones:\n // • dark: saturated in both themes → white text (on-strong-neutral).\n // • pale: pale in both themes → on-surface-primary (dark↔light).\n // • inverts: dark in light, pale in dark (sequential 7–8 whose\n // light-dark() values mirror) → on-strong-primary, which is\n // light-dark(near-white, near-black) — the exact inverse.\n fillTone === 'pale'\n ? 'text-[var(--ui-text-on-surface-primary)]'\n : fillTone === 'inverts'\n ? 'text-[var(--ui-text-on-status-strong-primary)]'\n : 'text-[var(--ui-text-on-status-strong-neutral)]',\n LABEL_ALIGN_CLASS[labelAlign]\n )}\n >\n <span className=\"truncate text-sm\">\n {primaryLabel ?? name}\n </span>\n {secondary && (\n <span className=\"truncate text-[11px]\">{secondary}</span>\n )}\n </div>\n </foreignObject>\n )}\n </g>\n );\n}\n\nexport interface TreemapProps\n extends Omit<React.ComponentProps<'div'>, 'children'>, ChartAnimationProps {\n /**\n * The dataviz palette this chart's series are painted from. Series that\n * state no `color` of their own take a stop of it. See `ChartPalette`.\n */\n palette?: ChartPalette;\n /**\n * Row-per-leaf data. Each object holds the leaf's `nameKey` label + its\n * `dataKey` numeric size.\n *\n * `fill`, `primaryLabel` and `secondaryLabel` are reserved: the chart stamps\n * them onto a copy of each row to carry the cell color and the two label lines\n * through to the renderer (recharts hands the whole row to `content`, so\n * anything the cell needs has to be on it). A row of your own carrying one of\n * those names may end up driving what the tile paints.\n */\n data: ReadonlyArray<Record<string, string | number>>;\n /**\n * Per-leaf map of `label` / `icon` / `tone`, keyed by the leaf's `nameKey`\n * value (imported from the shared `Chart` primitives). Turned into\n * `--color-<name>` custom properties. Series take their colour from the\n * container's `palette`; each entry maps a key to a `label` and an optional\n * `tone`.\n */\n config: ChartConfig;\n /** Numeric field that sizes each leaf's rectangle. */\n dataKey: string;\n /**\n * Label field that names each leaf (drives the on-cell label, legend, tooltip,\n * and `--color-<name>` lookup). Values should be unique; they are sanitized\n * automatically to a valid CSS custom-property fragment (CSS-safe keys are\n * preferred to avoid dev-mode warnings).\n */\n nameKey: string;\n /** Width-to-height ratio the tiling targets. */\n aspectRatio?: number;\n /** Render each leaf's label inside its cell (when it fits). */\n showLabels?: boolean;\n /**\n * Where a cell's label sits. Defaults to `center` — the Figma-canonical placement.\n * `bottom-start` anchors the block to the tile's bottom start corner; `top-start`\n * to the top start corner. The corner variants are named for the tile's *start* edge\n * because they mirror under `dir=\"rtl\"`.\n */\n labelAlign?: TreemapLabelAlign;\n /**\n * Fields whose values make up a second label line under the leaf's name — e.g.\n * `[dataKey]` for the size, or `['size', 'count']` for a value and a count. The\n * line is dropped on cells too short to hold it.\n */\n secondaryKeys?: readonly string[];\n /**\n * Format each secondary value. Receives the field's index in `secondaryKeys`\n * as its second argument, so one formatter can cover fields of different kinds.\n */\n secondaryFormatter?: TickFormatter;\n /** Separator drawn between multiple secondary values. */\n secondarySeparator?: string;\n showTooltip?: boolean;\n /**\n * Render the legend — one entry per leaf. Off by default: a treemap labels its\n * cells, so the legend is for the case where the tiles are too small to.\n */\n showLegend?: boolean;\n /** Which edge the legend sits on. */\n legendPos?: 'top' | 'bottom';\n /**\n * Replace the default tooltip. Pass a configured `ChartTooltipContent`\n * (imported from this library) — e.g. with a `formatter` / `labelFormatter` —\n * to customize formatting, per-cell rows, or extra fields without composing\n * recharts yourself. Ignored when `showTooltip` is false.\n */\n tooltipContent?: React.ComponentProps<typeof ChartTooltip>['content'];\n}\n\nconst Treemap = React.forwardRef<HTMLDivElement, TreemapProps>(\n (\n {\n className,\n config,\n palette = TREEMAP_DEFAULT_PALETTE,\n data,\n dataKey,\n nameKey,\n aspectRatio = 4 / 3,\n showLabels = true,\n labelAlign = 'center',\n secondaryKeys,\n secondaryFormatter,\n secondarySeparator = ' · ',\n showTooltip = true,\n showLegend = false,\n legendPos = 'bottom',\n tooltipContent,\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n ...props\n },\n ref\n ) => {\n const animation = resolveAnimation({\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n });\n // Stamp each row with its `fill` (the shadcn data-driven pattern) so a real\n // hover resolves the cell color in the tooltip — recharts' Treemap carries no\n // per-cell color on the tooltip payload item — and with its `secondaryLabel`,\n // which reaches the cell renderer the same way: recharts hands the whole row\n // to `content`, so a composed label has to be on the row before it gets there.\n const seriesData: Record<string, string | number | boolean>[] =\n React.useMemo(\n () => {\n // Auto fill-tone detection: resolve each leaf's token name and classify\n // it by suffix. Token values are light-dark() pairs, so a runtime\n // luminance check would be theme-fragile — the suffix is stable.\n const resolvedColors = resolveChartColors(config, palette);\n return data.map((row) => {\n const name = String(row[nameKey]);\n const secondaryLabel = secondaryKeys?.length\n ? treemapSecondaryLabel({\n row,\n keys: secondaryKeys,\n separator: secondarySeparator,\n formatter: secondaryFormatter,\n })\n : undefined;\n // The on-cell name is the leaf's `config` label, so a cell reads like its\n // legend entry and tooltip row do. It matters more here than elsewhere:\n // the raw `nameKey` value is sanitized before use in `--color-<name>`,\n // so a leaf whose display name has a space in it can be keyed by that name\n // while the human-readable label still comes from the `config` entry. Only\n // a string label can go in SVG text; a `ReactNode` one falls back to the key.\n const label = config[name]?.label;\n const rawToken = (resolvedColors[name]?.color ?? '').replace(\n /^var\\(|\\)$/g,\n ''\n );\n // Diverging: a1/a2/b1/b2 are pale; a3/b3 are dark.\n // Sequential: stops 1–2 are pale (in light) / dark (in dark); stops\n // 7–8 invert (dark in light, pale in dark). Stops 3–6 stay dark in\n // both themes.\n const isPale = /-(?:a[12]|b[12]|sequential-[a-z]+-[12])$/.test(rawToken);\n const inverts = /-sequential-[a-z]+-[78]$/.test(rawToken);\n const fillTone: 'dark' | 'pale' | 'inverts' = isPale\n ? 'pale'\n : inverts\n ? 'inverts'\n : 'dark';\n return {\n ...row,\n fill: `var(--color-${toCssKey(name)})`,\n fillTone,\n ...(typeof label === 'string' ? { primaryLabel: label } : {}),\n ...(secondaryLabel ? { secondaryLabel } : {}),\n };\n });\n },\n [\n config,\n data,\n nameKey,\n palette,\n secondaryFormatter,\n secondaryKeys,\n secondarySeparator,\n ]\n );\n\n // recharts 3 builds the legend payload from the graphical item, and `Treemap`\n // — unlike Bar/Line/Area/Pie/Radar/RadialBar/Scatter — never registers one,\n // so a `<Legend>` inside a `Treemap` renders empty. The payload is synthesized\n // from the leaves instead and handed to the shared `ChartLegendContent`, which\n // keeps the treemap on the same legend markers, labels and `config` lookup as\n // every other chart. `payload` carries the row so each entry resolves its own\n // `config` entry via `nameKey`.\n //\n // One entry per *distinct* leaf name: same-named leaves deliberately share a\n // `--color-<name>`/`config` entry, so a second entry would repeat the first\n // verbatim — and `ChartLegendContent` keys its entries on `value`, so it would\n // also be a duplicate React key.\n const legendPayload = React.useMemo<LegendPayload[]>(() => {\n const seen = new Set<string>();\n return seriesData.flatMap((row) => {\n const value = String(row[nameKey]);\n if (seen.has(value)) return [];\n seen.add(value);\n return [\n {\n value,\n dataKey: nameKey,\n type: 'rect' as const,\n color: String(row.fill),\n payload: row,\n },\n ];\n });\n }, [nameKey, seriesData]);\n\n // Every other chart type lets recharts shrink the plot area by the legend's\n // measured size, but recharts' `Treemap` tiles the *full* chart surface and\n // ignores the legend entirely — a `<Legend>` inside one paints over the tiles.\n // So the legend is not a `<Legend>` at all: the shared `ChartLegendContent` is\n // rendered straight into a row of its own beside the plot, on the same markers,\n // labels and `config` lookup as every other chart's legend, and normal flow\n // gives it the height it needs (one row, or several once it wraps) and takes\n // that height off the tiled surface.\n //\n // Rendering it here rather than inside the plot is also what keeps the tiling\n // correct: recharts' `Treemap` reads its container **once** (it opts out of the\n // responsive path, because its own stroke-width rounding makes that loop), and a\n // `<Legend>` can only render after the chart has a size — so the box it read\n // would always be the one from before its own legend existed, a legend-row too\n // tall, and the bottom row of tiles would be laid out under the clip. The row is\n // its final height on the first render instead, so that one read is the right one.\n //\n // The row sits outside `ChartContainer`, so it takes `config` as a prop (the\n // container's context doesn't reach it) and re-emits the `--color-<name>`\n // properties its swatches resolve against under its own `data-chart` id.\n const legendChartId = `chart-${React.useId().replace(/:/g, '')}`;\n const legendRow = showLegend ? (\n <div data-chart={legendChartId} className=\"text-xs\">\n <ChartStyle\n id={legendChartId}\n config={resolveChartColors(config, palette)}\n />\n <ChartLegendContent\n config={config}\n payload={legendPayload}\n verticalAlign={legendPos}\n nameKey={nameKey}\n />\n </div>\n ) : null;\n\n return (\n <div\n ref={ref}\n className={cn(showLegend && 'flex flex-col', className)}\n {...props}\n >\n {legendPos === 'top' && legendRow}\n <ChartContainer\n config={config}\n palette={palette}\n // `size-full` fills the caller's box; with a legend row beside it the\n // height comes from the flex line instead, so the tiles give up exactly\n // the room the legend takes.\n className={showLegend ? 'min-h-0 w-full flex-1' : 'size-full'}\n >\n <RechartsTreemap\n data={seriesData}\n dataKey={dataKey}\n nameKey={nameKey}\n aspectRatio={aspectRatio}\n {...animation}\n content={\n <TreemapCell showLabels={showLabels} labelAlign={labelAlign} />\n }\n >\n {showTooltip && (\n <ChartTooltip\n content={\n tooltipContent ?? (\n <ChartTooltipContent nameKey={nameKey} hideLabel />\n )\n }\n />\n )}\n </RechartsTreemap>\n </ChartContainer>\n {legendPos === 'bottom' && legendRow}\n </div>\n );\n }\n);\nTreemap.displayName = 'Treemap';\n\nexport { Treemap };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"treemap.js","names":[],"sources":["../../../../src/components/ui/treemap/treemap.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { Treemap as RechartsTreemap } from 'recharts';\nimport type { LegendPayload } from 'recharts/types/component/DefaultLegendContent';\n\nimport { cn } from '@/lib/utils';\nimport {\n ChartContainer,\n ChartLegendContent,\n ChartStyle,\n resolveChartColors,\n ChartTooltip,\n ChartTooltipContent,\n resolveAnimation,\n toCssKey,\n type ChartConfig,\n type ChartLegendFontSize,\n type ChartPalette,\n type ChartAnimationProps,\n type TickFormatter,\n} from '../chart';\n\n// Treemap-specific default: the diverging palette shows the two-sided distribution\n// that a treemap is most commonly used for, and its adaptive text (auto-computed\n// from resolved token names below) works correctly out of the box.\nconst TREEMAP_DEFAULT_PALETTE: ChartPalette = { type: 'diverging', pair: 'blue-orange' };\n\n// A typed recharts composition over the shared `Chart` primitives. A treemap is\n// the odd one out: a single `Treemap` element with no axes/grid, themed through a\n// custom `content` cell renderer (recharts' default paints every cell the same\n// fill and has no token hooks) — and that renderer, not a `LabelList`, owns the\n// on-cell labels, so the fit/clamp logic lives here. Like Scatter/Composed/\n// RadialBar there's no CVA variant — a treemap's knobs are geometry\n// (`aspectRatio`) and the data, not a visual \"mode\". v1 is a flat treemap (a list\n// of leaves).\n\n/**\n * Where a cell's label block sits inside its rectangle. Named logically, not\n * physically: the corner variants anchor to the tile's *start* edge, so they\n * mirror under `dir=\"rtl\"`. `bottom-start` is the design's placement; `center`\n * is what the chart drew before the block had a second line.\n */\nexport type TreemapLabelAlign = 'bottom-start' | 'top-start' | 'center';\n\n// Cell shape: each tile is inset by the gap on every side, so neighbouring tiles\n// are separated by the surface showing through rather than by a stroke, and the\n// corners are rounded.\nconst CELL_GAP = 2;\nconst CELL_RADIUS = 0;\n\n// Label geometry, in px. The block's own layout is CSS (see the renderer); these\n// are the heights the tile has to clear for a label to fit *without being\n// clipped* — one line, or two.\n//\n// Line boxes, not font sizes: Tailwind's `--text-sm--line-height` is 4/3, so the\n// `text-sm` title renders a ~19px line box, and the second line inherits that same\n// ratio (an arbitrary `text-[11px]` sets font-size only) for ~14.7px. Measuring by\n// font size instead would let a tile pass the threshold and still have the block's\n// `overflow-hidden` eat the top of the first line.\nconst CELL_PADDING = 12;\nconst LINE_HEIGHT_RATIO = 4 / 3;\nconst TITLE_FONT_SIZE = 14; // text-sm\nconst SECONDARY_FONT_SIZE = 11;\nconst TITLE_LINE_HEIGHT = Math.ceil(TITLE_FONT_SIZE * LINE_HEIGHT_RATIO);\nconst SECONDARY_LINE_HEIGHT = Math.ceil(\n SECONDARY_FONT_SIZE * LINE_HEIGHT_RATIO\n);\n\n// The smallest tile that gets a label at all, and the height a second line\n// additionally needs. Below the first threshold a cell stays blank rather than\n// showing a stub — the label would be wider than the tile it names.\nconst MIN_LABEL_WIDTH = 64;\nconst MIN_LABEL_HEIGHT = CELL_PADDING * 2 + TITLE_LINE_HEIGHT;\nconst MIN_TWO_LINE_HEIGHT = MIN_LABEL_HEIGHT + SECONDARY_LINE_HEIGHT;\n\n/**\n * Compose a cell's second line out of the caller's `secondaryKeys`. Empty and\n * missing fields are skipped rather than printed as a gap, so one row without\n * the field doesn't leave a dangling separator.\n */\nexport function treemapSecondaryLabel(options: {\n row: Record<string, string | number>;\n keys: readonly string[];\n separator: string;\n formatter?: TickFormatter;\n}): string | undefined {\n const { row, keys, separator, formatter } = options;\n const parts = keys.flatMap((key, index) => {\n const value = row[key];\n if (value == null || value === '') return [];\n // Emptiness is re-checked *after* formatting, not just on the raw value: a\n // formatter that blanks a field it doesn't want to show (a zero, a sentinel)\n // would otherwise contribute an empty part and put back the dangling\n // separator this function exists to avoid.\n const part = formatter ? formatter(value, index) : String(value);\n return part === '' ? [] : [part];\n });\n return parts.length ? parts.join(separator) : undefined;\n}\n\ninterface TreemapCellProps {\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n // recharts injects the node's resolved `name` (from nameKey) + value, plus\n // every other field on the row — which is how the two stamped labels arrive.\n name?: string;\n /** The leaf's display name — its `config` label, stamped onto the row by `Treemap`. */\n primaryLabel?: string;\n /** The label's precomputed second line, stamped onto each row by `Treemap`. */\n secondaryLabel?: string;\n showLabels?: boolean;\n labelAlign?: TreemapLabelAlign;\n /**\n * How the leaf's fill relates to text contrast. Stamped by `Treemap` onto each\n * row before recharts hands it to the cell renderer. Auto-computed from the\n * resolved token name suffix:\n *\n * - `'dark'` — saturated/dark in both themes → always white text.\n * - `'pale'` — pale in both themes → always dark text (via theme-adaptive token).\n * - `'inverts'` — dark in light, pale in dark (sequential stops 7–8 whose\n * `light-dark()` values mirror across themes) → white text in light, dark text\n * in dark, achieved by a `light-dark()` color value in CSS.\n */\n fillTone?: 'dark' | 'pale' | 'inverts';\n}\n\n// How the label block sits in its tile. `text-start`/`text-center` and the\n// block-axis `justify-*` are what make the corner-anchored variants mirror under\n// `dir=\"rtl\"` — the same logical utilities every other component uses, which is\n// why the label is HTML in a `foreignObject` rather than SVG `<text>`:\n// `x`/`text-anchor` are physical, so a mirrored label would need the direction\n// read in JS and applied by hand.\n//\n// Every variant leaves the cross axis on the default `items-stretch`, including\n// `center`. Centering the line is `text-center`'s job, not `items-center`'s:\n// `items-center` would size each line to its own text instead of to the tile, and\n// a `truncate` line only ellipsizes when it is the *tile* that constrains it — a\n// shrink-to-fit line is never narrower than its text, so it would silently\n// overflow and be hard-clipped on both edges instead.\nconst LABEL_ALIGN_CLASS: Record<TreemapLabelAlign, string> = {\n 'bottom-start': 'justify-end text-start',\n 'top-start': 'justify-start text-start',\n center: 'justify-center text-center',\n};\n\n// Cell renderer: fill each rounded rect from its `--color-<name>` var, inset it so\n// the surface shows through between tiles, and lay the label block over it — the\n// leaf's name, plus the stamped second line when the tile is tall enough. Passed to\n// `Treemap.content`; recharts clones it with each node's geometry + row fields.\nexport function TreemapCell({\n x = 0,\n y = 0,\n width = 0,\n height = 0,\n name,\n primaryLabel,\n secondaryLabel,\n showLabels = true,\n labelAlign = 'center',\n fillTone = 'dark',\n}: TreemapCellProps) {\n // recharts invokes `content` for the synthetic root node too (full chart\n // dimensions, empty name). Skip any name-less node — otherwise its rect has no\n // `--color-<name>` fill and paints the SVG-default black behind everything\n // (invisible when leaves cover it, a black box on empty data).\n if (!name) return <g />;\n // The tile, not the node: the label sits inside the inset rect, so the gutter\n // never eats into its padding.\n const tileX = x + CELL_GAP;\n const tileY = y + CELL_GAP;\n const tileWidth = Math.max(0, width - CELL_GAP * 2);\n const tileHeight = Math.max(0, height - CELL_GAP * 2);\n\n // `>=` on the heights: they are exact fits (padding + line boxes), so a tile\n // that measures one of them exactly has room for that label. The width is a\n // judgement call about how narrow a label is still worth reading, not a fit, so\n // it stays a strict `>`.\n const canLabel =\n showLabels && tileWidth > MIN_LABEL_WIDTH && tileHeight >= MIN_LABEL_HEIGHT;\n const secondary =\n canLabel && secondaryLabel && tileHeight >= MIN_TWO_LINE_HEIGHT\n ? secondaryLabel\n : undefined;\n\n return (\n <g>\n <rect\n x={tileX}\n y={tileY}\n width={tileWidth}\n height={tileHeight}\n rx={CELL_RADIUS}\n style={{ fill: `var(--color-${toCssKey(name)})` }}\n />\n {canLabel && (\n // Decorative: the hover that opens the tooltip belongs to the tile, so the\n // label must not swallow it.\n <foreignObject\n x={tileX}\n y={tileY}\n width={tileWidth}\n height={tileHeight}\n className=\"pointer-events-none\"\n >\n <div\n className={cn(\n // Each line truncates with a real ellipsis — CSS measures the text,\n // so nothing has to estimate how much of it fits.\n 'flex size-full flex-col overflow-hidden p-3',\n // Adaptive text — three tones:\n // • dark: saturated in both themes → white text (on-strong-neutral).\n // • pale: pale in both themes → on-surface-primary (dark↔light).\n // • inverts: dark in light, pale in dark (sequential 7–8 whose\n // light-dark() values mirror) → on-strong-primary, which is\n // light-dark(near-white, near-black) — the exact inverse.\n fillTone === 'pale'\n ? 'text-[var(--ui-text-on-surface-primary)]'\n : fillTone === 'inverts'\n ? 'text-[var(--ui-text-on-status-strong-primary)]'\n : 'text-[var(--ui-text-on-status-strong-neutral)]',\n LABEL_ALIGN_CLASS[labelAlign]\n )}\n >\n <span className=\"truncate text-sm\">\n {primaryLabel ?? name}\n </span>\n {secondary && (\n <span className=\"truncate text-[11px]\">{secondary}</span>\n )}\n </div>\n </foreignObject>\n )}\n </g>\n );\n}\n\nexport interface TreemapProps\n extends Omit<React.ComponentProps<'div'>, 'children'>, ChartAnimationProps {\n /**\n * The dataviz palette this chart's series are painted from. Series that\n * state no `color` of their own take a stop of it. See `ChartPalette`.\n */\n palette?: ChartPalette;\n /**\n * Row-per-leaf data. Each object holds the leaf's `nameKey` label + its\n * `dataKey` numeric size.\n *\n * `fill`, `primaryLabel` and `secondaryLabel` are reserved: the chart stamps\n * them onto a copy of each row to carry the cell color and the two label lines\n * through to the renderer (recharts hands the whole row to `content`, so\n * anything the cell needs has to be on it). A row of your own carrying one of\n * those names may end up driving what the tile paints.\n */\n data: ReadonlyArray<Record<string, string | number>>;\n /**\n * Per-leaf map of `label` / `icon` / `tone`, keyed by the leaf's `nameKey`\n * value (imported from the shared `Chart` primitives). Turned into\n * `--color-<name>` custom properties. Series take their colour from the\n * container's `palette`; each entry maps a key to a `label` and an optional\n * `tone`.\n */\n config: ChartConfig;\n /** Numeric field that sizes each leaf's rectangle. */\n dataKey: string;\n /**\n * Label field that names each leaf (drives the on-cell label, legend, tooltip,\n * and `--color-<name>` lookup). Values should be unique; they are sanitized\n * automatically to a valid CSS custom-property fragment (CSS-safe keys are\n * preferred to avoid dev-mode warnings).\n */\n nameKey: string;\n /** Width-to-height ratio the tiling targets. */\n aspectRatio?: number;\n /** Render each leaf's label inside its cell (when it fits). */\n showLabels?: boolean;\n /**\n * Where a cell's label sits. Defaults to `center` — the Figma-canonical placement.\n * `bottom-start` anchors the block to the tile's bottom start corner; `top-start`\n * to the top start corner. The corner variants are named for the tile's *start* edge\n * because they mirror under `dir=\"rtl\"`.\n */\n labelAlign?: TreemapLabelAlign;\n /**\n * Fields whose values make up a second label line under the leaf's name — e.g.\n * `[dataKey]` for the size, or `['size', 'count']` for a value and a count. The\n * line is dropped on cells too short to hold it.\n */\n secondaryKeys?: readonly string[];\n /**\n * Format each secondary value. Receives the field's index in `secondaryKeys`\n * as its second argument, so one formatter can cover fields of different kinds.\n */\n secondaryFormatter?: TickFormatter;\n /** Separator drawn between multiple secondary values. */\n secondarySeparator?: string;\n showTooltip?: boolean;\n /**\n * Render the legend — one entry per leaf. Off by default: a treemap labels its\n * cells, so the legend is for the case where the tiles are too small to.\n */\n showLegend?: boolean;\n /** Which edge the legend sits on. */\n legendPos?: 'top' | 'bottom';\n /**\n * Font size of the legend text. Defaults to `'xs'` — the size the legend has\n * always rendered at. See `ChartLegendFontSize`.\n */\n legendFontSize?: ChartLegendFontSize;\n /**\n * Replace the default tooltip. Pass a configured `ChartTooltipContent`\n * (imported from this library) — e.g. with a `formatter` / `labelFormatter` —\n * to customize formatting, per-cell rows, or extra fields without composing\n * recharts yourself. Ignored when `showTooltip` is false.\n */\n tooltipContent?: React.ComponentProps<typeof ChartTooltip>['content'];\n}\n\nconst Treemap = React.forwardRef<HTMLDivElement, TreemapProps>(\n (\n {\n className,\n config,\n palette = TREEMAP_DEFAULT_PALETTE,\n data,\n dataKey,\n nameKey,\n aspectRatio = 4 / 3,\n showLabels = true,\n labelAlign = 'center',\n secondaryKeys,\n secondaryFormatter,\n secondarySeparator = ' · ',\n showTooltip = true,\n showLegend = false,\n legendPos = 'bottom',\n legendFontSize,\n tooltipContent,\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n ...props\n },\n ref\n ) => {\n const animation = resolveAnimation({\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n });\n // Stamp each row with its `fill` (the shadcn data-driven pattern) so a real\n // hover resolves the cell color in the tooltip — recharts' Treemap carries no\n // per-cell color on the tooltip payload item — and with its `secondaryLabel`,\n // which reaches the cell renderer the same way: recharts hands the whole row\n // to `content`, so a composed label has to be on the row before it gets there.\n const seriesData: Record<string, string | number | boolean>[] =\n React.useMemo(\n () => {\n // Auto fill-tone detection: resolve each leaf's token name and classify\n // it by suffix. Token values are light-dark() pairs, so a runtime\n // luminance check would be theme-fragile — the suffix is stable.\n const resolvedColors = resolveChartColors(config, palette);\n return data.map((row) => {\n const name = String(row[nameKey]);\n const secondaryLabel = secondaryKeys?.length\n ? treemapSecondaryLabel({\n row,\n keys: secondaryKeys,\n separator: secondarySeparator,\n formatter: secondaryFormatter,\n })\n : undefined;\n // The on-cell name is the leaf's `config` label, so a cell reads like its\n // legend entry and tooltip row do. It matters more here than elsewhere:\n // the raw `nameKey` value is sanitized before use in `--color-<name>`,\n // so a leaf whose display name has a space in it can be keyed by that name\n // while the human-readable label still comes from the `config` entry. Only\n // a string label can go in SVG text; a `ReactNode` one falls back to the key.\n const label = config[name]?.label;\n const rawToken = (resolvedColors[name]?.color ?? '').replace(\n /^var\\(|\\)$/g,\n ''\n );\n // Diverging: a1/a2/b1/b2 are pale; a3/b3 are dark.\n // Sequential: stops 1–2 are pale (in light) / dark (in dark); stops\n // 7–8 invert (dark in light, pale in dark). Stops 3–6 stay dark in\n // both themes.\n const isPale = /-(?:a[12]|b[12]|sequential-[a-z]+-[12])$/.test(rawToken);\n const inverts = /-sequential-[a-z]+-[78]$/.test(rawToken);\n const fillTone: 'dark' | 'pale' | 'inverts' = isPale\n ? 'pale'\n : inverts\n ? 'inverts'\n : 'dark';\n return {\n ...row,\n fill: `var(--color-${toCssKey(name)})`,\n fillTone,\n ...(typeof label === 'string' ? { primaryLabel: label } : {}),\n ...(secondaryLabel ? { secondaryLabel } : {}),\n };\n });\n },\n [\n config,\n data,\n nameKey,\n palette,\n secondaryFormatter,\n secondaryKeys,\n secondarySeparator,\n ]\n );\n\n // recharts 3 builds the legend payload from the graphical item, and `Treemap`\n // — unlike Bar/Line/Area/Pie/Radar/RadialBar/Scatter — never registers one,\n // so a `<Legend>` inside a `Treemap` renders empty. The payload is synthesized\n // from the leaves instead and handed to the shared `ChartLegendContent`, which\n // keeps the treemap on the same legend markers, labels and `config` lookup as\n // every other chart. `payload` carries the row so each entry resolves its own\n // `config` entry via `nameKey`.\n //\n // One entry per *distinct* leaf name: same-named leaves deliberately share a\n // `--color-<name>`/`config` entry, so a second entry would repeat the first\n // verbatim — and `ChartLegendContent` keys its entries on `value`, so it would\n // also be a duplicate React key.\n const legendPayload = React.useMemo<LegendPayload[]>(() => {\n const seen = new Set<string>();\n return seriesData.flatMap((row) => {\n const value = String(row[nameKey]);\n if (seen.has(value)) return [];\n seen.add(value);\n return [\n {\n value,\n dataKey: nameKey,\n type: 'rect' as const,\n color: String(row.fill),\n payload: row,\n },\n ];\n });\n }, [nameKey, seriesData]);\n\n // Every other chart type lets recharts shrink the plot area by the legend's\n // measured size, but recharts' `Treemap` tiles the *full* chart surface and\n // ignores the legend entirely — a `<Legend>` inside one paints over the tiles.\n // So the legend is not a `<Legend>` at all: the shared `ChartLegendContent` is\n // rendered straight into a row of its own beside the plot, on the same markers,\n // labels and `config` lookup as every other chart's legend, and normal flow\n // gives it the height it needs (one row, or several once it wraps) and takes\n // that height off the tiled surface.\n //\n // Rendering it here rather than inside the plot is also what keeps the tiling\n // correct: recharts' `Treemap` reads its container **once** (it opts out of the\n // responsive path, because its own stroke-width rounding makes that loop), and a\n // `<Legend>` can only render after the chart has a size — so the box it read\n // would always be the one from before its own legend existed, a legend-row too\n // tall, and the bottom row of tiles would be laid out under the clip. The row is\n // its final height on the first render instead, so that one read is the right one.\n //\n // The row sits outside `ChartContainer`, so it takes `config` as a prop (the\n // container's context doesn't reach it) and re-emits the `--color-<name>`\n // properties its swatches resolve against under its own `data-chart` id.\n const legendChartId = `chart-${React.useId().replace(/:/g, '')}`;\n const legendRow = showLegend ? (\n <div data-chart={legendChartId}>\n <ChartStyle\n id={legendChartId}\n config={resolveChartColors(config, palette)}\n />\n <ChartLegendContent\n config={config}\n payload={legendPayload}\n verticalAlign={legendPos}\n nameKey={nameKey}\n fontSize={legendFontSize}\n />\n </div>\n ) : null;\n\n return (\n <div\n ref={ref}\n className={cn(showLegend && 'flex flex-col', className)}\n {...props}\n >\n {legendPos === 'top' && legendRow}\n <ChartContainer\n config={config}\n palette={palette}\n // `size-full` fills the caller's box; with a legend row beside it the\n // height comes from the flex line instead, so the tiles give up exactly\n // the room the legend takes.\n className={showLegend ? 'min-h-0 w-full flex-1' : 'size-full'}\n >\n <RechartsTreemap\n data={seriesData}\n dataKey={dataKey}\n nameKey={nameKey}\n aspectRatio={aspectRatio}\n {...animation}\n content={\n <TreemapCell showLabels={showLabels} labelAlign={labelAlign} />\n }\n >\n {showTooltip && (\n <ChartTooltip\n content={\n tooltipContent ?? (\n <ChartTooltipContent nameKey={nameKey} hideLabel />\n )\n }\n />\n )}\n </RechartsTreemap>\n </ChartContainer>\n {legendPos === 'bottom' && legendRow}\n </div>\n );\n }\n);\nTreemap.displayName = 'Treemap';\n\nexport { Treemap };\n"],"mappings":"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"}
|
package/dist/llms/area-chart.md
CHANGED
|
@@ -119,6 +119,7 @@ across brands until the real data-viz palette lands.
|
|
|
119
119
|
| `showGrid` | `boolean` | `true` | no | Render the CartesianGrid. |
|
|
120
120
|
| `showTooltip` | `boolean` | `true` | no | Render the hover tooltip. |
|
|
121
121
|
| `showLegend` | `boolean` | `true` | no | Render the legend. |
|
|
122
|
+
| `legendFontSize` | `'xs' | 'sm' | 'base' | 'lg' | 'xl'` | `'xs'` | no | Font size of the legend text. |
|
|
122
123
|
| `tooltipContent` | `ReactElement | ((props) => ReactNode)` | — | no | Replace the default tooltip. Pass a configured ChartTooltipContent (from this library) — e.g. with a formatter / labelFormatter — to customize the tooltip content without composing recharts. Ignored when showTooltip is false. |
|
|
123
124
|
| `showXAxis` | `boolean` | `true` | no | Render the X axis (its ticks and title). Set false to hide it. |
|
|
124
125
|
| `showYAxis` | `boolean` | `true` | no | Render the Y axis (its ticks and title). Set false to hide it. |
|
package/dist/llms/bar-chart.md
CHANGED
|
@@ -189,6 +189,7 @@ across brands until the real data-viz palette lands.
|
|
|
189
189
|
| `showGrid` | `boolean` | `true` | no | Render the CartesianGrid. |
|
|
190
190
|
| `showTooltip` | `boolean` | `true` | no | Render the hover tooltip — recharts' for `vertical`, the per-row tooltip for `horizontal`. |
|
|
191
191
|
| `showLegend` | `boolean` | `true` | no | Render the legend. |
|
|
192
|
+
| `legendFontSize` | `'xs' | 'sm' | 'base' | 'lg' | 'xl'` | `'xs'` | no | Font size of the legend text. |
|
|
192
193
|
| `tooltipContent` | `ReactElement | ((props) => ReactNode)` | — | no | Replace the default tooltip. Pass a configured ChartTooltipContent (from this library) — e.g. with a formatter / labelFormatter — to customize the tooltip content without composing recharts. Ignored when showTooltip is false. |
|
|
193
194
|
| `showXAxis` | `boolean` | `true` | no | Render the X axis (its ticks and title). Set false to hide it. |
|
|
194
195
|
| `showYAxis` | `boolean` | `true` | no | Render the Y axis (its ticks and title). Set false to hide it. |
|