@niekes/charts-3d 0.2.0 → 0.3.0

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/README.md CHANGED
@@ -35,7 +35,10 @@
35
35
  <tr style="background-color: #f6f8fa">
36
36
  <td><a target="_blank" href="https://niekes.github.io/charts-3d/docs/components/bar-chart-3d"><img src="assets/examples/bar-chart-3d.png" alt="Bar Chart 3D"></a></td>
37
37
  <td><a target="_blank" href="https://niekes.github.io/charts-3d/docs/components/scatter-chart-3d"><img src="assets/examples/scatter-chart-3d.png" alt="Scatter Chart 3D"></a></td>
38
+ </tr>
39
+ <tr style="background-color: #f6f8fa">
38
40
  <td><a target="_blank" href="https://niekes.github.io/charts-3d/docs/components/surface-chart-3d"><img src="assets/examples/surface-chart-3d.png" alt="Surface Chart 3D"></a></td>
41
+ <td><a target="_blank" href="https://niekes.github.io/charts-3d/docs/components/sankey-chart-3d"><img src="assets/examples/sankey-chart-3d.png" alt="Sankey Chart 3D"></a></td>
39
42
  </tr>
40
43
  </table>
41
44
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  "use client"
2
2
  import * as React from "react";
3
- import { AxesSpec, AxisSpec, Camera3DInstance, Point2D, TransformedPoint } from "d3-3d";
3
+ import { AxesSpec, AxisSpec, Camera3DInstance, CubeFace, Point2D, TransformedPoint } from "d3-3d";
4
4
  declare const THEMES: {
5
5
  readonly light: "";
6
6
  readonly dark: ".dark";
@@ -107,6 +107,20 @@ declare function ChartContainer3D({ id, config, defaultView, width, height, lege
107
107
  declare function Marks3D({ marks }: {
108
108
  marks: Mark3D[];
109
109
  }): React.JSX.Element[] | null;
110
+ /** A label beside a mark: `at` in the units of the view box, `anchor` the end of the text that sits there. */
111
+ export type Label3D = {
112
+ key: string;
113
+ at: Point2D;
114
+ text: React.ReactNode;
115
+ anchor: "start" | "middle" | "end";
116
+ };
117
+ /**
118
+ * The labels of the marks of a chart, ringed by the background so that they
119
+ * read on the marks. Like the labels of the box, they are SVG with every renderer.
120
+ */
121
+ declare function Labels3D({ labels }: {
122
+ labels: Label3D[];
123
+ }): React.JSX.Element;
110
124
  declare function ChartLegend3D({ config }: {
111
125
  config: ChartConfig;
112
126
  }): React.JSX.Element;
@@ -134,6 +148,8 @@ declare function worldBox(spec: AxesSpec, depth?: number): World;
134
148
  declare function shade(face: TransformedPoint<unknown>[], color: string): string;
135
149
  /** The mean of the projected points, where a tooltip points at. */
136
150
  declare function center(points: TransformedPoint<unknown>[]): Point2D;
151
+ /** The marks of a cube: its faces that look at the camera, shaded in `color`, each with `tooltip` on hover. */
152
+ declare function cubeMarks(id: string, faces: CubeFace[], color: string, tooltip: Tooltip3D): Mark3D[];
137
153
  export type BarChart3DProps<T extends Record<string, unknown> = Record<string, unknown>> = {
138
154
  /** One entry per row of bars, with a value per series. */
139
155
  data: T[];
@@ -220,5 +236,32 @@ export type SurfaceChart3DProps = {
220
236
  * of the chart config, by default its first two.
221
237
  */
222
238
  export declare function SurfaceChart3D({ data, domain, colors, format, titles }: SurfaceChart3DProps): React.JSX.Element;
223
- export { Axes3D, ChartContainer3D, ChartLegend3D, Marks3D, axis, center, linear, shade, worldBox };
239
+ export type SankeyChart3DProps = {
240
+ /** One entry per flow. `source` and `target` are keys of the chart config, which names and colors the nodes. */
241
+ data: {
242
+ source: string;
243
+ target: string;
244
+ value: number;
245
+ }[];
246
+ /**
247
+ * The width of a node, as a share of the distance between two columns.
248
+ * @defaultValue 0.2
249
+ */
250
+ nodeWidth?: number;
251
+ /**
252
+ * The gap between two nodes of a column, as a share of the height.
253
+ * @defaultValue 0.05
254
+ */
255
+ nodePadding?: number;
256
+ /** Formats the values in the tooltip. */
257
+ format?: (value: number) => React.ReactNode;
258
+ };
259
+ /**
260
+ * Flows from left to right: every node a box in its column, as high as the
261
+ * larger of what flows in and what flows out, and every flow a band from node
262
+ * to node, as thick as its value, that turns from the color of its source to
263
+ * the color of its target.
264
+ */
265
+ export declare function SankeyChart3D({ data, nodeWidth, nodePadding, format }: SankeyChart3DProps): React.JSX.Element;
266
+ export { Axes3D, ChartContainer3D, ChartLegend3D, Labels3D, Marks3D, axis, center, cubeMarks, linear, shade, worldBox };
224
267
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -106,6 +106,7 @@ const SHORTCUTS = [
106
106
  }
107
107
  ];
108
108
  const KEYS = Object.assign({}, ...SHORTCUTS.map((s) => s.keys));
109
+ const KEY_SHORTCUTS = Object.keys(KEYS).map((k) => k === "+" ? "Plus" : k).join(" ");
109
110
  function ChartContainer3D({ id, config, defaultView = {
110
111
  yaw: Math.PI / 6,
111
112
  tilt: Math.PI / 6
@@ -138,30 +139,36 @@ function ChartContainer3D({ id, config, defaultView = {
138
139
  id: chartId,
139
140
  config
140
141
  }),
141
- /* @__PURE__ */ jsx("svg", {
142
- viewBox: `0 0 ${width} ${height}`,
143
- role: "img",
144
- "aria-label": ariaLabel,
145
- tabIndex: 0,
146
- className: "block h-auto w-full cursor-grab touch-none rounded-lg outline-hidden select-none focus-visible:ring-[3px] focus-visible:ring-ring/50 active:cursor-grabbing",
147
- "data-orbiting": orbiting,
148
- onPointerLeave: leave,
149
- ...handlers,
150
- children: /* @__PURE__ */ jsx(Chart3DContext.Provider, {
151
- value: context,
152
- children
153
- })
154
- }),
155
- /* @__PURE__ */ jsx(ChartCanvas3D, {
156
- renderer,
157
- canvas,
158
- width,
159
- height
160
- }),
161
- /* @__PURE__ */ jsx(ChartTooltip3D, {
162
- tooltip,
163
- width,
164
- height
142
+ /* @__PURE__ */ jsxs("div", {
143
+ className: "relative rounded-lg has-focus-visible:ring-[3px] has-focus-visible:ring-ring/50",
144
+ children: [
145
+ /* @__PURE__ */ jsx("svg", {
146
+ viewBox: `0 0 ${width} ${height}`,
147
+ role: "img",
148
+ "aria-label": ariaLabel,
149
+ "aria-keyshortcuts": shortcuts ? `${KEY_SHORTCUTS} ?` : KEY_SHORTCUTS,
150
+ tabIndex: 0,
151
+ className: "block h-auto w-full cursor-grab touch-none outline-hidden select-none mask-y-from-96% active:cursor-grabbing",
152
+ "data-orbiting": orbiting,
153
+ onPointerLeave: leave,
154
+ ...handlers,
155
+ children: /* @__PURE__ */ jsx(Chart3DContext.Provider, {
156
+ value: context,
157
+ children
158
+ })
159
+ }),
160
+ /* @__PURE__ */ jsx(ChartCanvas3D, {
161
+ renderer,
162
+ canvas,
163
+ width,
164
+ height
165
+ }),
166
+ /* @__PURE__ */ jsx(ChartTooltip3D, {
167
+ tooltip,
168
+ width,
169
+ height
170
+ })
171
+ ]
165
172
  }),
166
173
  shortcuts && /* @__PURE__ */ jsx(ChartShortcuts3D, {}),
167
174
  legend && /* @__PURE__ */ jsx(ChartLegend3D, { config })
@@ -392,7 +399,6 @@ function useCanvas(scene, width) {
392
399
  return {
393
400
  canvas: React.useCallback((node) => {
394
401
  element.current = node;
395
- if (!node) return;
396
402
  repaint();
397
403
  const resize = new ResizeObserver(repaint);
398
404
  resize.observe(node);
@@ -415,7 +421,6 @@ function paint(canvas, marks, width, color) {
415
421
  canvas.width = Math.round(canvas.clientWidth * devicePixelRatio);
416
422
  canvas.height = Math.round(canvas.clientHeight * devicePixelRatio);
417
423
  const ctx = canvas.getContext("2d");
418
- if (!ctx) return;
419
424
  const scale = canvas.width / width;
420
425
  ctx.setTransform(scale, 0, 0, scale, 0, 0);
421
426
  ctx.lineJoin = "round";
@@ -439,7 +444,7 @@ function ChartCanvas3D({ renderer, canvas, width, height }) {
439
444
  if (renderer !== "canvas") return null;
440
445
  return /* @__PURE__ */ jsx("canvas", {
441
446
  ref: canvas,
442
- className: "pointer-events-none absolute top-0 left-0 w-full",
447
+ className: "pointer-events-none absolute top-0 left-0 w-full mask-y-from-96%",
443
448
  style: { aspectRatio: `${width} / ${height}` }
444
449
  });
445
450
  }
@@ -469,6 +474,26 @@ function Marks3D({ marks }) {
469
474
  strokeWidth: stroke ? 1 : void 0
470
475
  }, key));
471
476
  }
477
+ /**
478
+ * The labels of the marks of a chart, ringed by the background so that they
479
+ * read on the marks. Like the labels of the box, they are SVG with every renderer.
480
+ */
481
+ function Labels3D({ labels }) {
482
+ return /* @__PURE__ */ jsx("g", {
483
+ "data-slot": "chart-3d-labels",
484
+ children: labels.map(({ key, at, text, anchor }) => /* @__PURE__ */ jsx("text", {
485
+ x: at.x,
486
+ y: at.y,
487
+ textAnchor: anchor,
488
+ dominantBaseline: "central",
489
+ paintOrder: "stroke",
490
+ strokeWidth: 3,
491
+ strokeLinejoin: "round",
492
+ className: "fill-foreground stroke-background text-[10px] font-medium",
493
+ children: text
494
+ }, key))
495
+ });
496
+ }
472
497
  function ChartStyle({ id, config }) {
473
498
  const colorConfig = Object.entries(config).filter(([, c]) => c.theme || c.color);
474
499
  if (!colorConfig.length) return null;
@@ -540,8 +565,10 @@ function ChartShortcuts3D() {
540
565
  }), open && /* @__PURE__ */ jsx("div", {
541
566
  className: "grid grid-cols-[auto_1fr] items-center gap-x-3 gap-y-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-2 shadow-xl",
542
567
  children: SHORTCUTS.map(({ hint, label }, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [/* @__PURE__ */ jsx("span", {
568
+ "data-slot": "kbd-group",
543
569
  className: "flex gap-1",
544
570
  children: hint.map((key) => /* @__PURE__ */ jsx("kbd", {
571
+ "data-slot": "kbd",
545
572
  className: "pointer-events-none inline-flex h-5 min-w-5 items-center justify-center rounded-sm bg-muted px-1 font-sans text-xs font-medium text-muted-foreground select-none",
546
573
  children: key
547
574
  }, key))
@@ -577,39 +604,92 @@ function Axes3D({ spec, world, format = {}, titles = {} }) {
577
604
  d: box.draw(wall),
578
605
  className: "fill-muted/40 stroke-border",
579
606
  strokeWidth: .75
580
- }, `${wall.axis}${wall.side}`)), box.axes(spec).map((edge) => /* @__PURE__ */ jsx(AxisEdge3D, {
581
- edge,
582
- format: format[edge.axis] ?? String,
583
- title: titles[edge.axis]
584
- }, edge.axis))]
607
+ }, `${wall.axis}${wall.side}`)), labelAxes(box.axes(spec), format, titles).map((axis) => /* @__PURE__ */ jsx(AxisEdge3D, {
608
+ ticks: axis.ticks,
609
+ title: axis.title
610
+ }, axis.key))]
585
611
  });
586
612
  }
587
613
  /**
588
- * The tick labels of one axis on the outline of the box, pushed away from the
589
- * box along the normal of the edge, and the title of the axis further out.
614
+ * The labels of one axis on the outline of the box: a label per tick, pushed
615
+ * away from the box along the normal of the edge, and the title of the axis
616
+ * further out. `key` names the edge, one of the four the axis moves between
617
+ * as the camera turns.
590
618
  */
591
- function AxisEdge3D({ edge, format, title }) {
592
- if (!edge.length) return null;
619
+ function axisLabels(edge, format, title) {
593
620
  const { normal: n } = edge;
594
621
  const start = edge[0].projected;
595
622
  const end = edge[edge.length - 1].projected;
596
623
  const anchor = n.x > .3 ? "start" : n.x < -.3 ? "end" : "middle";
624
+ const label = (at, out, text, size) => ({
625
+ x: at.x + n.x * out,
626
+ y: at.y + n.y * out,
627
+ anchor,
628
+ text,
629
+ size
630
+ });
631
+ const middle = {
632
+ x: (start.x + end.x) / 2,
633
+ y: (start.y + end.y) / 2
634
+ };
635
+ return {
636
+ key: `${edge.axis}:${edge[0].x},${edge[0].y},${edge[0].z}`,
637
+ ticks: edge.map((tick) => label(tick.projected, 10, format(tick[edge.axis]), 10)),
638
+ title: title ? label(middle, 44, title, 12) : void 0
639
+ };
640
+ }
641
+ /**
642
+ * The labels of the axes, with every tick label hidden that would cover a
643
+ * title or a tick label before it, so an axis seen end on keeps its first
644
+ * label and a short one every other.
645
+ */
646
+ function labelAxes(edges, format, titles) {
647
+ const axes = edges.filter((edge) => edge.length).map((edge) => axisLabels(edge, format[edge.axis] ?? String, titles[edge.axis]));
648
+ const placed = axes.flatMap((axis) => axis.title ? [bounds(axis.title)] : []);
649
+ for (const label of axes.flatMap((axis) => axis.ticks)) {
650
+ const b = bounds(label);
651
+ label.hidden = placed.some((p) => b.left < p.right && p.left < b.right && b.top < p.bottom && p.top < b.bottom);
652
+ if (!label.hidden) placed.push(b);
653
+ }
654
+ return axes;
655
+ }
656
+ /** The box a label covers, guessed from its text, as the browser lays it out only after the render. */
657
+ function bounds({ x, y, anchor, text, size }) {
658
+ const width = (typeof text === "string" || typeof text === "number" ? String(text).length : 4) * size * .6;
659
+ const left = x - width * {
660
+ start: 0,
661
+ middle: .5,
662
+ end: 1
663
+ }[anchor];
664
+ return {
665
+ left,
666
+ right: left + width,
667
+ top: y - size / 2,
668
+ bottom: y + size / 2
669
+ };
670
+ }
671
+ /**
672
+ * The labels of one axis, dimmed while the camera turns. When the axis jumps
673
+ * to another edge of the box, its key changes, so they mount anew and fade in
674
+ * there.
675
+ */
676
+ function AxisEdge3D({ ticks, title }) {
597
677
  return /* @__PURE__ */ jsxs("g", {
598
- className: "transition-opacity delay-300 duration-300 in-data-[orbiting=true]:opacity-0 in-data-[orbiting=true]:delay-0 in-data-[orbiting=true]:duration-150",
599
- children: [edge.map((tick, i) => /* @__PURE__ */ jsx("text", {
600
- x: tick.projected.x + n.x * 10,
601
- y: tick.projected.y + n.y * 10,
678
+ className: "transition-opacity duration-300 starting:opacity-0 in-data-[orbiting=true]:opacity-50",
679
+ children: [ticks.map(({ x, y, anchor, text, hidden }, i) => /* @__PURE__ */ jsx("text", {
680
+ x,
681
+ y,
602
682
  textAnchor: anchor,
603
683
  dominantBaseline: "central",
604
- className: "fill-muted-foreground text-[10px]",
605
- children: format(tick[edge.axis])
684
+ className: cn("fill-muted-foreground text-[10px] transition-opacity", hidden && "opacity-0"),
685
+ children: text
606
686
  }, i)), title && /* @__PURE__ */ jsx("text", {
607
- x: (start.x + end.x) / 2 + n.x * 44,
608
- y: (start.y + end.y) / 2 + n.y * 44,
609
- textAnchor: anchor,
687
+ x: title.x,
688
+ y: title.y,
689
+ textAnchor: title.anchor,
610
690
  dominantBaseline: "central",
611
691
  className: "fill-foreground font-medium",
612
- children: title
692
+ children: title.text
613
693
  })]
614
694
  });
615
695
  }
@@ -663,6 +743,15 @@ function center(points) {
663
743
  y: sum.y / points.length
664
744
  };
665
745
  }
746
+ /** The marks of a cube: its faces that look at the camera, shaded in `color`, each with `tooltip` on hover. */
747
+ function cubeMarks(id, faces, color, tooltip) {
748
+ return faces.filter((face) => face.ccw).map((face) => ({
749
+ key: `${id}-${face.face}`,
750
+ points: face.map((p) => p.projected),
751
+ fill: shade(face, color),
752
+ tooltip
753
+ }));
754
+ }
666
755
  // registry/new-york/ui/bar-chart-3d.tsx
667
756
  /**
668
757
  * Bars on a floor: one row per entry of `data` along x, one per series
@@ -672,22 +761,24 @@ function center(points) {
672
761
  function BarChart3D({ data, index, keys, barSize = .6, format = {}, titles }) {
673
762
  const { config, camera } = useChart3D();
674
763
  const series = keys ?? Object.keys(config);
675
- const values = data.flatMap((d) => series.map((key) => Number(d[key]) || 0));
676
- const { spec, world, size } = barBox(data.length, series.length, values, barSize);
677
- const bars = cubes3D().camera(camera).data(data.flatMap((d, i) => series.map((key, j) => {
678
- const value = Number(d[key]) || 0;
679
- return Object.assign(barCorners(world, i, j, value, size), {
680
- i,
681
- key,
682
- value
683
- });
764
+ const cells = data.flatMap((d, i) => series.map((key, j) => ({
765
+ i,
766
+ j,
767
+ key,
768
+ value: Number(d[key]) || 0
769
+ })));
770
+ const { spec, world, size } = barBox(data.length, series.length, cells.map((cell) => cell.value), barSize);
771
+ const bars = cubes3D().camera(camera).data(cells.map(({ i, j, key, value }) => Object.assign(barCorners(world, i, j, value, size), {
772
+ i,
773
+ key,
774
+ value
684
775
  }))).map((bar) => Object.assign(bar, { bottom: bar.faces.find((f) => f.face === "bottom") })).sort((a, b) => sort(a.bottom, b.bottom));
685
776
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Axes3D, {
686
777
  spec,
687
778
  world,
688
779
  titles,
689
780
  format: {
690
- x: (i) => (format.x ?? String)(String(data[i]?.[index] ?? "")),
781
+ x: (i) => (format.x ?? String)(String(data[i][index] ?? "")),
691
782
  y: format.y,
692
783
  z: (j) => config[series[j]]?.label ?? series[j]
693
784
  }
@@ -745,7 +836,7 @@ function barCorners(world, x, z, value, size) {
745
836
  }
746
837
  /** The marks of one bar: the faces that look at the camera, shaded, each with its entry and value on hover. */
747
838
  function barMarks({ id, faces, color, title, label, value }) {
748
- const tooltip = {
839
+ return cubeMarks(id, faces, color, {
749
840
  at: center(faces.find((face) => face.face === "top")),
750
841
  title,
751
842
  items: [{
@@ -753,13 +844,7 @@ function barMarks({ id, faces, color, title, label, value }) {
753
844
  value,
754
845
  color
755
846
  }]
756
- };
757
- return faces.filter((face) => face.ccw).map((face) => ({
758
- key: `${id}-${face.face}`,
759
- points: face.map((p) => p.projected),
760
- fill: shade(face, color),
761
- tooltip
762
- }));
847
+ });
763
848
  }
764
849
  // registry/new-york/ui/scatter-chart-3d.tsx
765
850
  /**
@@ -841,7 +926,7 @@ function SurfaceChart3D({ data, domain = {}, colors, format, titles }) {
841
926
  const world = worldBox(spec);
842
927
  const toX = linear([0, columns - 1], [x0, x1]);
843
928
  const toZ = linear([0, rows - 1], [z0, z1]);
844
- const grid = gridPlanes3D().camera(camera).rows(columns).x((p) => world.x(p.x)).y((p) => world.y(p.y)).z((p) => world.z(p.z));
929
+ const grid = gridPlanes3D().camera(camera).rows(Math.max(1, columns)).x((p) => world.x(p.x)).y((p) => world.y(p.y)).z((p) => world.z(p.z));
845
930
  const points = data.flatMap((row, r) => row.map((y, c) => ({
846
931
  x: toX(c),
847
932
  y,
@@ -870,6 +955,297 @@ function SurfaceChart3D({ data, domain = {}, colors, format, titles }) {
870
955
  titles
871
956
  }), /* @__PURE__ */ jsx(Marks3D, { marks })] });
872
957
  }
873
- export { Axes3D, BarChart3D, ChartContainer3D, ChartLegend3D, Marks3D, ScatterChart3D, SurfaceChart3D, axis, center, linear, shade, useChart3D, worldBox };
958
+ // registry/new-york/ui/sankey-chart-3d.tsx
959
+ const DEPTH = .4;
960
+ const RIBBON = .7;
961
+ const SEGMENTS = 16;
962
+ /**
963
+ * Flows from left to right: every node a box in its column, as high as the
964
+ * larger of what flows in and what flows out, and every flow a band from node
965
+ * to node, as thick as its value, that turns from the color of its source to
966
+ * the color of its target.
967
+ */
968
+ function SankeyChart3D({ data, nodeWidth = .2, nodePadding = .05, format = (value) => value }) {
969
+ const { config, camera } = useChart3D();
970
+ const { nodes, columns, ky } = sankeyLayout(data, nodePadding);
971
+ const pieces = depthSort(cubes3D().camera(camera).data(sankeyPieces(nodes, columns, ky, nodeWidth)), camera);
972
+ const labels = pieces.flatMap(({ key, faces, node }) => node ? [nodeLabel(key, faces, node.column < (columns - 1) / 2 ? "left" : "right", style(config, node).label)] : []);
973
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Marks3D, { marks: pieces.flatMap((piece) => pieceMarks(piece, config, format)) }), /* @__PURE__ */ jsx(Labels3D, { labels })] });
974
+ }
975
+ /** The label and the color of a node, from the chart config. */
976
+ function style(config, node) {
977
+ return {
978
+ label: config[node.id]?.label ?? node.id,
979
+ color: config[node.id] ? `var(--color-${node.id})` : "var(--muted-foreground)"
980
+ };
981
+ }
982
+ /**
983
+ * The marks of a piece, each with its tooltip: the faces of a node, or the
984
+ * faces of a slice of a flow, in the color it has turned to there.
985
+ */
986
+ function pieceMarks({ key, faces, node, flow, t }, config, format) {
987
+ const at = center(faces.find((face) => face.face === "top"));
988
+ if (node) {
989
+ const { label, color } = style(config, node);
990
+ return cubeMarks(key, faces, color, {
991
+ at,
992
+ items: [{
993
+ label,
994
+ value: format(node.value),
995
+ color
996
+ }]
997
+ });
998
+ }
999
+ const source = style(config, flow.source);
1000
+ const target = style(config, flow.target);
1001
+ const item = {
1002
+ label: /* @__PURE__ */ jsxs(Fragment, { children: [
1003
+ source.label,
1004
+ " → ",
1005
+ target.label
1006
+ ] }),
1007
+ value: format(flow.value),
1008
+ color: source.color
1009
+ };
1010
+ return cubeMarks(key, faces.filter((face) => face.face !== "left" && face.face !== "right"), `color-mix(in oklch, color-mix(in oklch, ${target.color} ${t}%, ${source.color}) 75%, var(--background))`, {
1011
+ at,
1012
+ items: [item]
1013
+ });
1014
+ }
1015
+ /** The nodes of `data`, waypoints included, stacked in their columns, and `ky`, the world units per value. */
1016
+ function sankeyLayout(data, nodePadding) {
1017
+ const { nodes, flows, columns } = sankeyColumns(data);
1018
+ const all = sankeyHops(nodes, flows);
1019
+ const ky = stackColumns(all, columns, nodePadding);
1020
+ stackHops(all, ky);
1021
+ return {
1022
+ nodes: all,
1023
+ columns,
1024
+ ky
1025
+ };
1026
+ }
1027
+ function newNode(id, column, flow) {
1028
+ return {
1029
+ id,
1030
+ column,
1031
+ value: 0,
1032
+ y0: 0,
1033
+ y1: 0,
1034
+ incoming: [],
1035
+ outgoing: [],
1036
+ flow
1037
+ };
1038
+ }
1039
+ function middle(node) {
1040
+ return (node.y0 + node.y1) / 2;
1041
+ }
1042
+ /**
1043
+ * The nodes of `data` in the order they first appear, and the flows between
1044
+ * them, without the ones of no value. The column of a node is the longest
1045
+ * path to it from a source; the sinks move to the last column.
1046
+ */
1047
+ function sankeyColumns(data) {
1048
+ const nodes = /* @__PURE__ */ new Map();
1049
+ const node = (id) => {
1050
+ if (!nodes.has(id)) nodes.set(id, newNode(id, 0));
1051
+ return nodes.get(id);
1052
+ };
1053
+ const flows = data.filter((d) => d.value > 0).map((d, i) => ({
1054
+ key: String(i),
1055
+ source: node(d.source),
1056
+ target: node(d.target),
1057
+ value: d.value
1058
+ }));
1059
+ for (let pass = 0; flows.some((f) => f.target.column <= f.source.column); pass += 1) {
1060
+ if (pass === nodes.size) throw new Error("SankeyChart3D: the flows form a cycle");
1061
+ for (const f of flows) f.target.column = Math.max(f.target.column, f.source.column + 1);
1062
+ }
1063
+ const last = Math.max(0, ...flows.map((f) => f.target.column));
1064
+ const sources = new Set(flows.map((f) => f.source));
1065
+ for (const n of nodes.values()) if (!sources.has(n)) n.column = last;
1066
+ return {
1067
+ nodes: [...nodes.values()],
1068
+ flows,
1069
+ columns: last + 1
1070
+ };
1071
+ }
1072
+ /**
1073
+ * Splits every flow into hops from one column to the next. A flow that skips a
1074
+ * column passes it at a waypoint, a node of its own, so it has a slot in every
1075
+ * column it crosses and never runs through another node.
1076
+ */
1077
+ function sankeyHops(nodes, flows) {
1078
+ const all = [...nodes];
1079
+ for (const flow of flows) {
1080
+ let source = flow.source;
1081
+ for (let column = source.column + 1; column <= flow.target.column; column += 1) {
1082
+ const target = column < flow.target.column ? newNode(`${flow.key}:${column}`, column, flow) : flow.target;
1083
+ if (target.flow) all.push(target);
1084
+ const hop = {
1085
+ flow,
1086
+ source,
1087
+ target,
1088
+ y0: 0,
1089
+ y1: 0
1090
+ };
1091
+ source.outgoing.push(hop);
1092
+ target.incoming.push(hop);
1093
+ source = target;
1094
+ }
1095
+ }
1096
+ return all;
1097
+ }
1098
+ /**
1099
+ * Sizes the nodes to the largest scale at which every column fits, and stacks
1100
+ * each column from the top, centered. A column after the first orders its nodes
1101
+ * by the mean height of their sources, which uncrosses most flows. Returns the
1102
+ * scale, world units per value.
1103
+ */
1104
+ function stackColumns(nodes, columns, nodePadding) {
1105
+ const total = (hops) => hops.reduce((sum, hop) => sum + hop.flow.value, 0);
1106
+ for (const node of nodes) node.value = Math.max(total(node.incoming), total(node.outgoing));
1107
+ const stacks = Array.from({ length: columns }, (_, c) => nodes.filter((n) => n.column === c));
1108
+ const sum = (stack) => stack.reduce((s, n) => s + n.value, 0);
1109
+ const pad = Math.min(2 * nodePadding, 2 / Math.max(1, ...stacks.map((s) => s.length)));
1110
+ const ky = Math.min(...stacks.map((s) => (2 - pad * (s.length - 1)) / sum(s)));
1111
+ const mean = (node) => node.incoming.reduce((s, hop) => s + hop.flow.value * middle(hop.source), 0) / total(node.incoming);
1112
+ for (const [c, stack] of stacks.entries()) {
1113
+ if (c > 0) stack.sort((a, b) => mean(a) - mean(b));
1114
+ let y = -(sum(stack) * ky + pad * (stack.length - 1)) / 2;
1115
+ for (const node of stack) {
1116
+ node.y0 = y;
1117
+ node.y1 = y + node.value * ky;
1118
+ y = node.y1 + pad;
1119
+ }
1120
+ }
1121
+ return ky;
1122
+ }
1123
+ /**
1124
+ * Stacks the hops at every node from its top: the ones that leave it in the
1125
+ * order of their targets, the ones that arrive in the order of their sources,
1126
+ * so that the hops of a node do not cross.
1127
+ */
1128
+ function stackHops(nodes, ky) {
1129
+ for (const node of nodes) {
1130
+ let y = node.y0;
1131
+ for (const hop of node.outgoing.sort((a, b) => middle(a.target) - middle(b.target))) {
1132
+ hop.y0 = y + hop.flow.value * ky / 2;
1133
+ y += hop.flow.value * ky;
1134
+ }
1135
+ y = node.y0;
1136
+ for (const hop of node.incoming.sort((a, b) => middle(a.source) - middle(b.source))) {
1137
+ hop.y1 = y + hop.flow.value * ky / 2;
1138
+ y += hop.flow.value * ky;
1139
+ }
1140
+ }
1141
+ }
1142
+ /**
1143
+ * The pieces of the chart: a box per node, a flat slice of its flow per
1144
+ * waypoint and `SEGMENTS` slices per hop. The columns stand from x = -1 to 1,
1145
+ * a node the share `nodeWidth` of the distance between two of them wide.
1146
+ */
1147
+ function sankeyPieces(nodes, columns, ky, nodeWidth) {
1148
+ const step = 2 / Math.max(1, columns - 1);
1149
+ const x = (column) => -1 + column * step;
1150
+ const half = nodeWidth * step / 2;
1151
+ const share = (flow, at) => {
1152
+ const from = x(flow.source.column) + half;
1153
+ return Math.round((at - from) / (x(flow.target.column) - half - from) * 100);
1154
+ };
1155
+ return nodes.flatMap((node) => {
1156
+ const band = x(node.column);
1157
+ const own = node.flow ? {
1158
+ key: `way:${node.id}`,
1159
+ band,
1160
+ flow: node.flow,
1161
+ t: share(node.flow, band)
1162
+ } : {
1163
+ key: `node:${node.id}`,
1164
+ band,
1165
+ node,
1166
+ t: 0
1167
+ };
1168
+ const depth = node.flow ? RIBBON * DEPTH : DEPTH;
1169
+ const box = slab(band - half, middle(node), band + half, middle(node), node.y1 - node.y0, depth);
1170
+ return [Object.assign(box, own), ...node.outgoing.flatMap((hop) => hopPieces(hop, band + half, x(node.column + 1) - half, ky, share))];
1171
+ });
1172
+ }
1173
+ /**
1174
+ * The slices of a hop from `x0` to `x1`, along an S-curve that leaves and
1175
+ * arrives level, so that the hops of a flow join without a kink. Every hop
1176
+ * between the same two columns has the same slices.
1177
+ */
1178
+ function hopPieces(hop, x0, x1, ky, share) {
1179
+ const x = (k) => x0 + (x1 - x0) * k / SEGMENTS;
1180
+ const y = (k) => {
1181
+ const t = k / SEGMENTS;
1182
+ return hop.y0 + (hop.y1 - hop.y0) * t * t * (3 - 2 * t);
1183
+ };
1184
+ return Array.from({ length: SEGMENTS }, (_, k) => {
1185
+ const band = (x(k) + x(k + 1)) / 2;
1186
+ return Object.assign(slab(x(k), y(k), x(k + 1), y(k + 1), hop.flow.value * ky, RIBBON * DEPTH), {
1187
+ key: `hop:${hop.flow.key}:${hop.source.column}:${k}`,
1188
+ band,
1189
+ flow: hop.flow,
1190
+ t: share(hop.flow, band)
1191
+ });
1192
+ });
1193
+ }
1194
+ /** The corners of a slab from `x0` to `x1`, its middle at `y0` and `y1`, `height` high and `depth` deep, in the order of box(). */
1195
+ function slab(x0, y0, x1, y1, height, depth) {
1196
+ return [depth / 2, -depth / 2].flatMap((z) => [
1197
+ {
1198
+ x: x0,
1199
+ y: y0 - height / 2,
1200
+ z
1201
+ },
1202
+ {
1203
+ x: x0,
1204
+ y: y0 + height / 2,
1205
+ z
1206
+ },
1207
+ {
1208
+ x: x1,
1209
+ y: y1 + height / 2,
1210
+ z
1211
+ },
1212
+ {
1213
+ x: x1,
1214
+ y: y1 - height / 2,
1215
+ z
1216
+ }
1217
+ ]);
1218
+ }
1219
+ /**
1220
+ * The pieces in the order to paint them. The slabs of the columns and of the
1221
+ * slices lie between planes x = const, so they paint from the slab farthest
1222
+ * from the eye to the nearest, which is exact; within a slab, where the pieces
1223
+ * lie one above the other, by depth.
1224
+ */
1225
+ function depthSort(pieces, camera) {
1226
+ const { x: vx } = camera.viewDirection();
1227
+ const eye = vx * camera.perspective();
1228
+ const far = (x) => Number.isFinite(eye) ? Math.abs(x - eye) : -x * Math.sign(vx);
1229
+ return pieces.sort((a, b) => far(b.band) - far(a.band) || sort(a, b));
1230
+ }
1231
+ /**
1232
+ * The label of a node beside its `outer` face, pushed away from the other side,
1233
+ * so that it stays outside whichever way the camera turns.
1234
+ */
1235
+ function nodeLabel(key, faces, outer, text) {
1236
+ const side = (name) => center(faces.find((face) => face.face === name));
1237
+ const at = side(outer);
1238
+ const away = Math.sign(at.x - side(outer === "left" ? "right" : "left").x) || (outer === "left" ? -1 : 1);
1239
+ return {
1240
+ key,
1241
+ at: {
1242
+ x: at.x + 8 * away,
1243
+ y: at.y
1244
+ },
1245
+ text,
1246
+ anchor: away < 0 ? "end" : "start"
1247
+ };
1248
+ }
1249
+ export { Axes3D, BarChart3D, ChartContainer3D, ChartLegend3D, Labels3D, Marks3D, SankeyChart3D, ScatterChart3D, SurfaceChart3D, axis, center, cubeMarks, linear, shade, useChart3D, worldBox };
874
1250
 
875
1251
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../lib/utils.ts","../registry/new-york/ui/chart-3d.tsx","../registry/new-york/ui/bar-chart-3d.tsx","../registry/new-york/ui/scatter-chart-3d.tsx","../registry/new-york/ui/surface-chart-3d.tsx"],"sourcesContent":["import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n","\"use client\"\n\nimport * as React from \"react\"\nimport { nice, ticks } from \"d3-array\"\nimport {\n axes3D,\n camera3D,\n normal,\n type AxesSpec,\n type AxisEdge,\n type AxisSpec,\n type Camera3DInstance,\n type Point2D,\n type TransformedPoint,\n} from \"d3-3d\"\nimport { KeyboardIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n// The same shape as the ChartConfig of shadcn's chart, so one config drives\n// a 2D and a 3D chart side by side.\nconst THEMES = { light: \"\", dark: \".dark\" } as const\n\nexport type ChartConfig = {\n [k in string]: {\n label?: React.ReactNode\n icon?: React.ComponentType\n } & (\n | { color?: string; theme?: never }\n | { color?: never; theme: Record<keyof typeof THEMES, string> }\n )\n}\n\n/** How the camera looks at the chart box, angles in radians. */\nexport type View3D = {\n yaw: number\n /** The angle above the floor the camera looks down from. */\n tilt: number\n zoom?: number\n /** How far the chart is moved, in the units of the view box. */\n pan?: Point2D\n /** The distance of the eye in box units; Infinity, the default, is orthographic. */\n perspective?: number\n}\n\n/** How the marks of a chart are drawn. The box, its labels, the tooltip and the legend are always SVG and HTML. */\nexport type Renderer3D = \"svg\" | \"canvas\"\n\n/** Maps data values to the world box, which spans -1 to 1 on every axis, y up. */\nexport type World = Record<keyof AxesSpec, (value: number) => number>\n\nexport type Tooltip3D = {\n /** The anchor in the units of the view box. */\n at: Point2D\n title?: React.ReactNode\n items: { label: React.ReactNode; value: React.ReactNode; color?: string }[]\n}\n\n/** One shape of a chart, projected, in paint order: a polygon, or a circle of radius `r` around its one point. */\nexport type Mark3D = {\n /** Stable across renders, so React moves the shape instead of redrawing it. */\n key: string\n /** The corners of a polygon, or the center of a circle, in the units of the view box. */\n points: Point2D[]\n r?: number\n /** A CSS color, such as the result of `shade` or `var(--color-x)`. */\n fill: string\n /** The ring around a circle, a CSS color. */\n stroke?: string\n /** Shown while the pointer is over the mark. */\n tooltip?: Tooltip3D\n}\n\ntype ChartContext3D = {\n config: ChartConfig\n camera: Camera3DInstance\n renderer: Renderer3D\n /** The marks of the chart, which the hover and the canvas read. */\n scene: React.RefObject<Mark3D[]>\n repaint: () => void\n}\n\nconst Chart3DContext = React.createContext<ChartContext3D | null>(null)\n\nexport function useChart3D() {\n const context = React.useContext(Chart3DContext)\n if (!context) {\n throw new Error(\"useChart3D must be used within a <ChartContainer3D />\")\n }\n return context\n}\n\nconst DRAG = Math.PI / 230\nconst KEY_STEP = Math.PI / 36\nconst PAN_STEP = 20\nconst ZOOM_STEP = 1.2\nconst CLICK_DISTANCE = 4\n\n/** A step of the camera: turned by `yaw` and `tilt`, moved by `pan`, zoomed by the factor `zoom`, or reset. */\ntype Move = { yaw?: number; tilt?: number; pan?: Point2D; zoom?: number; reset?: boolean }\n\n// The shortcuts of a chart: the keys of a row move the camera, and the\n// shortcut panel lists every row, so the two never disagree.\nconst SHORTCUTS: { hint: string[]; label: string; keys?: Record<string, Move> }[] = [\n { hint: [\"Drag\"], label: \"Rotate\" },\n {\n hint: [\"←\", \"→\", \"↑\", \"↓\"],\n label: \"Rotate\",\n keys: {\n ArrowLeft: { yaw: -KEY_STEP },\n ArrowRight: { yaw: KEY_STEP },\n ArrowUp: { tilt: -KEY_STEP },\n ArrowDown: { tilt: KEY_STEP },\n },\n },\n { hint: [\"Space\", \"Drag\"], label: \"Pan\" },\n {\n hint: [\"Shift\", \"←\", \"→\", \"↑\", \"↓\"],\n label: \"Pan\",\n keys: {\n \"Shift+ArrowLeft\": { pan: { x: -PAN_STEP, y: 0 } },\n \"Shift+ArrowRight\": { pan: { x: PAN_STEP, y: 0 } },\n \"Shift+ArrowUp\": { pan: { x: 0, y: -PAN_STEP } },\n \"Shift+ArrowDown\": { pan: { x: 0, y: PAN_STEP } },\n },\n },\n {\n hint: [\"+\", \"−\"],\n label: \"Zoom\",\n keys: { \"+\": { zoom: ZOOM_STEP }, \"=\": { zoom: ZOOM_STEP }, \"-\": { zoom: 1 / ZOOM_STEP } },\n },\n { hint: [\"Ctrl\", \"Scroll\"], label: \"Zoom\" },\n { hint: [\"0\"], label: \"Reset\", keys: { \"0\": { reset: true } } },\n { hint: [\"Double-click\"], label: \"Reset\" },\n { hint: [\"?\"], label: \"Shortcuts\" },\n]\nconst KEYS: Record<string, Move | undefined> = Object.assign({}, ...SHORTCUTS.map((s) => s.keys))\n\nexport type ChartContainer3DProps = {\n /** The series of the chart: a label and a color per key, as in the chart of shadcn/ui. */\n config: ChartConfig\n /**\n * Where the camera starts, and where a reset returns it.\n * @defaultValue { yaw: π/6, tilt: π/6 }\n */\n defaultView?: View3D\n /**\n * The width of the view box the chart is drawn in.\n * @defaultValue 640\n */\n width?: number\n /**\n * The height of the view box the chart is drawn in.\n * @defaultValue 400\n */\n height?: number\n /**\n * Shows the label and the color of every config key below the chart.\n * @defaultValue false\n */\n legend?: boolean\n /**\n * How the marks of the chart are drawn: \"canvas\" suits many marks. The\n * chart draws as SVG until the browser shows it has a canvas, and where it\n * has none.\n * @defaultValue \"svg\"\n */\n renderer?: Renderer3D\n /**\n * Shows a button that lists the keyboard shortcuts of the chart; `?` lists them too.\n * @defaultValue true\n */\n shortcuts?: boolean\n}\n\nfunction ChartContainer3D({\n id,\n config,\n defaultView = { yaw: Math.PI / 6, tilt: Math.PI / 6 },\n width = 640,\n height = 400,\n legend = false,\n renderer: requested = \"svg\",\n shortcuts = true,\n className,\n children,\n \"aria-label\": ariaLabel = \"3D chart. Drag to rotate it, press ? for all shortcuts.\",\n ...props\n}: React.ComponentProps<\"div\"> & ChartContainer3DProps) {\n const uniqueId = React.useId()\n const chartId = `chart-${id || uniqueId.replace(/:/g, \"\")}`\n const [tooltip, setTooltip] = React.useState<Tooltip3D | null>(null)\n const { scene, renderer, canvas, repaint, hover, leave } = useScene(\n requested,\n width,\n height,\n setTooltip\n )\n const { view, orbiting, handlers } = useOrbit(defaultView, width, leave, hover)\n const camera = useCamera(view, width, height)\n const context = React.useMemo(\n () => ({ config, camera, renderer, scene, repaint }),\n [config, camera, renderer, repaint]\n )\n\n return (\n <div\n data-slot=\"chart-3d\"\n data-chart={chartId}\n className={cn(\"relative w-full text-xs\", className)}\n {...props}\n >\n <ChartStyle id={chartId} config={config} />\n <svg\n viewBox={`0 0 ${width} ${height}`}\n role=\"img\"\n aria-label={ariaLabel}\n tabIndex={0}\n className=\"block h-auto w-full cursor-grab touch-none rounded-lg outline-hidden select-none focus-visible:ring-[3px] focus-visible:ring-ring/50 active:cursor-grabbing\"\n data-orbiting={orbiting}\n onPointerLeave={leave}\n {...handlers}\n >\n <Chart3DContext.Provider value={context}>{children}</Chart3DContext.Provider>\n </svg>\n <ChartCanvas3D renderer={renderer} canvas={canvas} width={width} height={height} />\n <ChartTooltip3D tooltip={tooltip} width={width} height={height} />\n {shortcuts && <ChartShortcuts3D />}\n {legend && <ChartLegend3D config={config} />}\n </div>\n )\n}\n\n/**\n * The marks of the chart and what reads them: the renderer that draws them,\n * the canvas it paints them on and the hover that shows their tooltips.\n */\nfunction useScene(\n requested: Renderer3D,\n width: number,\n height: number,\n setTooltip: (tooltip: Tooltip3D | null) => void\n) {\n const scene = React.useRef<Mark3D[]>([])\n const renderer = useRenderer(requested)\n const { canvas, repaint } = useCanvas(scene, width)\n const { hover, leave } = useHover(scene, width, height, setTooltip)\n return { scene, renderer, canvas, repaint, hover, leave }\n}\n\n/** The camera of a view. The shapes of the chart hang on it, so a new view re-projects them all. */\nfunction useCamera(view: View3D, width: number, height: number) {\n return React.useMemo(\n () =>\n camera3D()\n .origin({ x: width / 2 + (view.pan?.x ?? 0), y: height / 2 + (view.pan?.y ?? 0) })\n .scale(Math.min(width, height) * 0.3 * (view.zoom ?? 1))\n .perspective(view.perspective ?? Infinity)\n .rotateY(view.yaw)\n .rotateX(-view.tilt),\n [view, width, height]\n )\n}\n\n/** `view` moved by one step. The tilt stops at the zenith and the nadir, the zoom at 1/4 and 8. */\nfunction moved(view: View3D, move: Move, initial: View3D): View3D {\n if (move.reset) return initial\n const step = { yaw: 0, tilt: 0, zoom: 1, pan: { x: 0, y: 0 }, ...move }\n const pan = view.pan ?? { x: 0, y: 0 }\n const clamp = (lo: number, hi: number, value: number) => Math.max(lo, Math.min(hi, value))\n return {\n ...view,\n yaw: view.yaw + step.yaw,\n tilt: clamp(-Math.PI / 2, Math.PI / 2, view.tilt + step.tilt),\n zoom: clamp(0.25, 8, (view.zoom ?? 1) * step.zoom),\n pan: { x: pan.x + step.pan.x, y: pan.y + step.pan.y },\n }\n}\n\n/**\n * The view of the camera, and the handlers that move it: a drag turns it,\n * or pans it while Space is held, the keys of `SHORTCUTS` step it, Ctrl or ⌘\n * with the wheel zooms it and a double click resets it. `orbiting` is true\n * while the camera turns. `onDragStart` runs when a press becomes a drag,\n * `onHover` on a move without one.\n */\nfunction useOrbit(\n defaultView: View3D,\n width: number,\n onDragStart: () => void,\n onHover: (e: React.PointerEvent<SVGSVGElement>) => void\n) {\n const [view, setView] = React.useState(defaultView)\n const [orbiting, setOrbiting] = React.useState(false)\n const drag = React.useRef<Point2D & { moved: boolean }>(null)\n const space = React.useRef(false)\n const ref = useWheelZoom(setView)\n const move = (step: Move) => setView((v) => moved(v, step, defaultView))\n const release = () => {\n drag.current = null\n setOrbiting(false)\n }\n\n const handlers: React.SVGProps<SVGSVGElement> = {\n ref,\n onPointerDown: (e) => {\n drag.current = { x: e.clientX, y: e.clientY, moved: false }\n },\n onPointerMove: (e) => {\n const d = drag.current\n if (!d) return onHover(e)\n const dx = e.clientX - d.x\n const dy = e.clientY - d.y\n // A press that wanders a few pixels is still a click on a bar.\n if (!d.moved && Math.hypot(dx, dy) < CLICK_DISTANCE) return\n if (!d.moved) {\n e.currentTarget.setPointerCapture(e.pointerId)\n onDragStart()\n }\n drag.current = { x: e.clientX, y: e.clientY, moved: true }\n const k = width / e.currentTarget.getBoundingClientRect().width\n setOrbiting(!space.current)\n move(space.current ? { pan: { x: dx * k, y: dy * k } } : { yaw: dx * DRAG, tilt: dy * DRAG })\n },\n onPointerUp: release,\n onPointerCancel: release,\n onDoubleClick: () => move({ reset: true }),\n onKeyDown: (e) => {\n // Held on the chart, Space pans a drag instead of scrolling the page.\n if (e.key === \" \") space.current = true\n const step = (e.shiftKey && KEYS[`Shift+${e.key}`]) || KEYS[e.key]\n if (!step && e.key !== \" \") return\n e.preventDefault()\n if (!step) return\n setOrbiting(Boolean(step.yaw || step.tilt))\n move(step)\n },\n onKeyUp: (e) => {\n if (e.key === \" \") space.current = false\n setOrbiting(false)\n },\n }\n\n return { view, orbiting, handlers }\n}\n\n/**\n * A ref for the svg that zooms the view with Ctrl or ⌘ and the wheel, which\n * is also how a trackpad pinches. The wheel alone still scrolls the page.\n */\nfunction useWheelZoom(setView: React.Dispatch<React.SetStateAction<View3D>>) {\n // React listens to the wheel passively, and only a listener that is not\n // passive can keep the browser from zooming the page.\n return React.useCallback(\n (svg: SVGSVGElement | null) => {\n const onWheel = (e: WheelEvent) => {\n if (!e.ctrlKey && !e.metaKey) return\n e.preventDefault()\n setView((v) => moved(v, { zoom: 2 ** (-e.deltaY / 200) }, v))\n }\n svg?.addEventListener(\"wheel\", onWheel, { passive: false })\n return () => svg?.removeEventListener(\"wheel\", onWheel)\n },\n [setView]\n )\n}\n\n/**\n * The tooltip of the mark under the pointer, the one painted last. `hover`\n * sets it only when the pointer moves onto another mark, so a move within\n * one does not render the chart again.\n */\nfunction useHover(\n scene: React.RefObject<Mark3D[]>,\n width: number,\n height: number,\n setTooltip: (tooltip: Tooltip3D | null) => void\n) {\n const hovered = React.useRef<Mark3D>(undefined)\n const show = (mark?: Mark3D) => {\n if (mark?.key === hovered.current?.key) return\n hovered.current = mark\n setTooltip(mark?.tooltip ?? null)\n }\n\n return {\n hover: (e: React.PointerEvent<SVGSVGElement>) => {\n const rect = e.currentTarget.getBoundingClientRect()\n const at = {\n x: ((e.clientX - rect.left) / rect.width) * width,\n y: ((e.clientY - rect.top) / rect.height) * height,\n }\n show(hit(scene.current, at))\n },\n leave: () => show(undefined),\n }\n}\n\n/** The topmost mark under `at`: the last one painted that holds it. */\nfunction hit(marks: Mark3D[], at: Point2D) {\n for (let i = marks.length - 1; i >= 0; i -= 1) {\n const { points, r } = marks[i]\n const holds =\n r === undefined ? inside(points, at) : Math.hypot(points[0].x - at.x, points[0].y - at.y) <= r\n if (holds) return marks[i]\n }\n}\n\n/** Whether `at` lies inside `polygon`: a ray from it crosses an odd number of its edges. */\nfunction inside(polygon: Point2D[], at: Point2D) {\n let odd = false\n for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i, i += 1) {\n const a = polygon[i]\n const b = polygon[j]\n if (a.y > at.y !== b.y > at.y && at.x < ((b.x - a.x) * (at.y - a.y)) / (b.y - a.y) + a.x) {\n odd = !odd\n }\n }\n return odd\n}\n\n/**\n * The renderer the chart draws with: SVG on the server and in the first\n * render on the client, then the requested one if the browser has it.\n */\nfunction useRenderer(requested: Renderer3D) {\n const [renderer, setRenderer] = React.useState<Renderer3D>(\"svg\")\n React.useEffect(() => {\n // A canvas of its own, as one that gave out a context cannot give another.\n const canvas = requested === \"canvas\" && document.createElement(\"canvas\").getContext(\"2d\")\n setRenderer(canvas ? \"canvas\" : \"svg\")\n }, [requested])\n return renderer\n}\n\n/**\n * A ref for the canvas of the marks, and the repaint that paints them: on new\n * marks, when the canvas changes its size and when the theme changes. A color\n * resolves on the canvas itself, inside the chart, so the `--color-*` apply.\n */\nfunction useCanvas(scene: React.RefObject<Mark3D[]>, width: number) {\n const element = React.useRef<HTMLCanvasElement>(null)\n const colors = React.useRef(new Map<string, string>())\n\n const repaint = React.useCallback(() => {\n const canvas = element.current\n if (!canvas) return\n paint(canvas, scene.current, width, (css) => {\n let color = colors.current.get(css)\n if (color === undefined) {\n canvas.style.color = css\n color = getComputedStyle(canvas).color\n colors.current.set(css, color)\n }\n return color\n })\n }, [scene, width])\n\n const canvas = React.useCallback(\n (node: HTMLCanvasElement | null) => {\n element.current = node\n if (!node) return\n repaint()\n const resize = new ResizeObserver(repaint)\n resize.observe(node)\n // ponytail: watches the theme on <html> only, as the class `.dark` of THEMES does.\n const theme = new MutationObserver(() => {\n colors.current.clear()\n repaint()\n })\n theme.observe(document.documentElement, { attributeFilter: [\"class\", \"style\"] })\n return () => {\n element.current = null\n resize.disconnect()\n theme.disconnect()\n }\n },\n [repaint]\n )\n\n return { canvas, repaint }\n}\n\n/** Paints `marks` on `canvas`, from the view box of `width` to the pixels of the canvas. */\nfunction paint(\n canvas: HTMLCanvasElement,\n marks: Mark3D[],\n width: number,\n color: (css: string) => string\n) {\n canvas.width = Math.round(canvas.clientWidth * devicePixelRatio)\n canvas.height = Math.round(canvas.clientHeight * devicePixelRatio)\n const ctx = canvas.getContext(\"2d\")\n if (!ctx) return\n const scale = canvas.width / width\n ctx.setTransform(scale, 0, 0, scale, 0, 0)\n ctx.lineJoin = \"round\"\n\n for (const { points, r, fill, stroke } of marks) {\n ctx.beginPath()\n if (r === undefined) {\n for (const p of points) ctx.lineTo(p.x, p.y)\n ctx.closePath()\n } else {\n ctx.arc(points[0].x, points[0].y, r, 0, 2 * Math.PI)\n }\n ctx.fillStyle = color(fill)\n ctx.fill()\n // A polygon is outlined in its own color, which closes the seams to its neighbours.\n const outline = r === undefined ? fill : stroke\n if (!outline) continue\n ctx.strokeStyle = color(outline)\n ctx.lineWidth = r === undefined ? 0.5 : 1\n ctx.stroke()\n }\n}\n\n/** The canvas over the chart box that the marks are painted on, if the chart draws on one. */\nfunction ChartCanvas3D({\n renderer,\n canvas,\n width,\n height,\n}: {\n renderer: Renderer3D\n canvas: React.Ref<HTMLCanvasElement>\n width: number\n height: number\n}) {\n if (renderer !== \"canvas\") return null\n\n return (\n <canvas\n ref={canvas}\n className=\"pointer-events-none absolute top-0 left-0 w-full\"\n style={{ aspectRatio: `${width} / ${height}` }}\n />\n )\n}\n\n/**\n * The marks of a chart, drawn by the renderer of the container: as SVG\n * elements, or on the canvas over the chart box. The hover reads them too.\n */\nfunction Marks3D({ marks }: { marks: Mark3D[] }) {\n const { renderer, scene, repaint } = useChart3D()\n React.useLayoutEffect(() => {\n scene.current = marks\n repaint()\n })\n\n if (renderer === \"canvas\") return null\n return marks.map(({ key, points, r, fill, stroke }) =>\n r === undefined ? (\n <path\n key={key}\n d={`M${points.map((p) => `${+p.x.toFixed(2)},${+p.y.toFixed(2)}`).join(\"L\")}Z`}\n fill={fill}\n stroke={fill}\n strokeWidth={0.5}\n strokeLinejoin=\"round\"\n />\n ) : (\n <circle\n key={key}\n cx={points[0].x}\n cy={points[0].y}\n r={r}\n fill={fill}\n stroke={stroke}\n strokeWidth={stroke ? 1 : undefined}\n />\n )\n )\n}\n\nfunction ChartStyle({ id, config }: { id: string; config: ChartConfig }) {\n const colorConfig = Object.entries(config).filter(([, c]) => c.theme || c.color)\n\n if (!colorConfig.length) {\n return null\n }\n\n return (\n <style\n dangerouslySetInnerHTML={{\n __html: Object.entries(THEMES)\n .map(\n ([theme, prefix]) => `\n${prefix} [data-chart=${id}] {\n${colorConfig\n .map(([key, item]) => {\n const color = item.theme?.[theme as keyof typeof item.theme] || item.color\n return color ? ` --color-${key}: ${color};` : null\n })\n .join(\"\\n\")}\n}\n`\n )\n .join(\"\\n\"),\n }}\n />\n )\n}\n\nfunction ChartTooltip3D({\n tooltip,\n width,\n height,\n}: {\n tooltip: Tooltip3D | null\n width: number\n height: number\n}) {\n if (!tooltip) return null\n\n return (\n <div\n className=\"pointer-events-none absolute z-10 grid min-w-32 -translate-x-1/2 -translate-y-[calc(100%+0.5rem)] gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl\"\n style={{ left: `${(tooltip.at.x / width) * 100}%`, top: `${(tooltip.at.y / height) * 100}%` }}\n >\n {tooltip.title && <div className=\"font-medium\">{tooltip.title}</div>}\n {tooltip.items.map((item, i) => (\n <div key={i} className=\"flex items-center gap-2\">\n {item.color && (\n <span className=\"size-2.5 shrink-0 rounded-[2px]\" style={{ background: item.color }} />\n )}\n <span className=\"text-muted-foreground\">{item.label}</span>\n <span className=\"ml-auto font-mono font-medium text-foreground tabular-nums\">\n {typeof item.value === \"number\" ? item.value.toLocaleString() : item.value}\n </span>\n </div>\n ))}\n </div>\n )\n}\n\n/**\n * A button in the corner of the chart that lists `SHORTCUTS`. `?` on the\n * chart opens and closes the list, Escape closes it.\n */\nfunction ChartShortcuts3D() {\n const [open, setOpen] = React.useState(false)\n const ref = React.useRef<HTMLDivElement>(null)\n\n React.useEffect(() => {\n const chart = ref.current?.parentElement\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"?\") setOpen((o) => !o)\n if (e.key === \"Escape\") setOpen(false)\n }\n chart?.addEventListener(\"keydown\", onKeyDown)\n return () => chart?.removeEventListener(\"keydown\", onKeyDown)\n }, [])\n\n return (\n <div ref={ref} className=\"absolute top-2 right-2 z-10 flex flex-col items-end gap-1\">\n <button\n type=\"button\"\n aria-label=\"Keyboard shortcuts\"\n aria-expanded={open}\n onClick={() => setOpen((o) => !o)}\n className=\"inline-flex size-7 items-center justify-center rounded-md text-muted-foreground outline-none hover:bg-accent hover:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-4\"\n >\n <KeyboardIcon />\n </button>\n {open && (\n <div className=\"grid grid-cols-[auto_1fr] items-center gap-x-3 gap-y-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-2 shadow-xl\">\n {SHORTCUTS.map(({ hint, label }, i) => (\n <React.Fragment key={i}>\n <span className=\"flex gap-1\">\n {hint.map((key) => (\n <kbd\n key={key}\n className=\"pointer-events-none inline-flex h-5 min-w-5 items-center justify-center rounded-sm bg-muted px-1 font-sans text-xs font-medium text-muted-foreground select-none\"\n >\n {key}\n </kbd>\n ))}\n </span>\n <span className=\"text-muted-foreground\">{label}</span>\n </React.Fragment>\n ))}\n </div>\n )}\n </div>\n )\n}\n\nfunction ChartLegend3D({ config }: { config: ChartConfig }) {\n return (\n <div className=\"flex items-center justify-center gap-4 pt-3\">\n {Object.entries(config).map(([key, item]) => (\n <div key={key} className=\"flex items-center gap-1.5\">\n {item.icon ? (\n <item.icon />\n ) : (\n <span className=\"size-2 rounded-[2px]\" style={{ background: `var(--color-${key})` }} />\n )}\n {item.label ?? key}\n </div>\n ))}\n </div>\n )\n}\n\n/**\n * The walls of the chart box that face away from the camera, with their\n * gridlines, and an axis with ticks on the outline of the box.\n */\nfunction Axes3D({\n spec,\n world,\n format = {},\n titles = {},\n}: {\n spec: AxesSpec\n world: World\n format?: Partial<Record<keyof AxesSpec, (value: number) => React.ReactNode>>\n titles?: Partial<Record<keyof AxesSpec, React.ReactNode>>\n}) {\n const { camera } = useChart3D()\n const box = axes3D()\n .camera(camera)\n .x((p) => world.x(p.x))\n .y((p) => world.y(p.y))\n .z((p) => world.z(p.z))\n\n return (\n <g data-slot=\"chart-3d-axes\">\n {box\n .data(spec)\n .filter((wall) => !wall.ccw)\n .map((wall) => (\n <path\n key={`${wall.axis}${wall.side}`}\n d={box.draw(wall)}\n className=\"fill-muted/40 stroke-border\"\n strokeWidth={0.75}\n />\n ))}\n {box.axes(spec).map((edge) => (\n <AxisEdge3D\n key={edge.axis}\n edge={edge}\n format={format[edge.axis] ?? String}\n title={titles[edge.axis]}\n />\n ))}\n </g>\n )\n}\n\n/**\n * The tick labels of one axis on the outline of the box, pushed away from the\n * box along the normal of the edge, and the title of the axis further out.\n */\nfunction AxisEdge3D({\n edge,\n format,\n title,\n}: {\n edge: AxisEdge\n format: (value: number) => React.ReactNode\n title?: React.ReactNode\n}) {\n // An axis without ticks, such as the rows of an empty chart, has nothing to label.\n if (!edge.length) return null\n const { normal: n } = edge\n const start = edge[0].projected\n const end = edge[edge.length - 1].projected\n const anchor = n.x > 0.3 ? \"start\" : n.x < -0.3 ? \"end\" : \"middle\"\n\n // The labels jump to another edge as the camera turns, so they fade out\n // while it turns and fade back in a moment after it rests.\n return (\n <g className=\"transition-opacity delay-300 duration-300 in-data-[orbiting=true]:opacity-0 in-data-[orbiting=true]:delay-0 in-data-[orbiting=true]:duration-150\">\n {edge.map((tick, i) => (\n <text\n key={i}\n x={tick.projected.x + n.x * 10}\n y={tick.projected.y + n.y * 10}\n textAnchor={anchor}\n dominantBaseline=\"central\"\n className=\"fill-muted-foreground text-[10px]\"\n >\n {format(tick[edge.axis])}\n </text>\n ))}\n {title && (\n <text\n x={(start.x + end.x) / 2 + n.x * 44}\n y={(start.y + end.y) / 2 + n.y * 44}\n textAnchor={anchor}\n dominantBaseline=\"central\"\n className=\"fill-foreground font-medium\"\n >\n {title}\n </text>\n )}\n </g>\n )\n}\n\n/** A nice axis from `lo` to `hi` with about `count` ticks. */\nfunction axis(lo: number, hi: number, count = 5): AxisSpec {\n const [start, stop] = nice(lo, lo === hi ? hi + 1 : hi, count)\n return { domain: [start, stop], ticks: ticks(start, stop, count) }\n}\n\n/** The linear map from `domain` to `range`. */\nfunction linear([d0, d1]: number[], [r0, r1]: number[]) {\n return (value: number) => r0 + ((value - d0) / (d1 - d0 || 1)) * (r1 - r0)\n}\n\n/** The world box of `spec`: x from -1 to 1, y up from 1 to -1, z from -`depth` to `depth`. */\nfunction worldBox(spec: AxesSpec, depth = 1): World {\n return {\n x: linear(spec.x.domain, [-1, 1]),\n y: linear(spec.y.domain, [1, -1]),\n z: linear(spec.z.domain, [-depth, depth]),\n }\n}\n\n// From the upper left, towards the viewer, in view space where y points down.\nconst LIGHT = { x: -0.4, y: -0.8, z: 0.44 }\n\n/**\n * `color` lit by the light from the upper left: the face that looks at it\n * keeps the color, one that turns away gets darker. A CSS color-mix, so the\n * shade follows the theme variables.\n */\nfunction shade(face: TransformedPoint<unknown>[], color: string) {\n const n = normal(face.map((p) => p.rotated))\n // The normal of the side the camera sees, whichever way the face winds.\n const side = n.z < 0 ? -1 : 1\n const light = Math.max(0, side * (n.x * LIGHT.x + n.y * LIGHT.y + n.z * LIGHT.z))\n return `color-mix(in oklch, ${color} ${Math.round(65 + 35 * light)}%, black)`\n}\n\n/** The mean of the projected points, where a tooltip points at. */\nfunction center(points: TransformedPoint<unknown>[]): Point2D {\n const sum = points.reduce((s, p) => ({ x: s.x + p.projected.x, y: s.y + p.projected.y }), {\n x: 0,\n y: 0,\n })\n return { x: sum.x / points.length, y: sum.y / points.length }\n}\n\nexport { ChartContainer3D, ChartLegend3D, Axes3D, Marks3D, axis, linear, worldBox, shade, center }\n","\"use client\"\n\nimport * as React from \"react\"\nimport { box, cubes3D, sort, type AxesSpec, type CubeFace } from \"d3-3d\"\n\nimport {\n Axes3D,\n Marks3D,\n axis,\n center,\n shade,\n useChart3D,\n worldBox,\n type Mark3D,\n type World,\n} from \"@/registry/new-york/ui/chart-3d\"\n\nexport type BarChart3DProps<T extends Record<string, unknown> = Record<string, unknown>> = {\n /** One entry per row of bars, with a value per series. */\n data: T[]\n /** The field that names each entry, such as \"month\". */\n index: keyof T & string\n /**\n * The fields that hold the series, one row of bars each.\n * @defaultValue the keys of the chart config\n */\n keys?: string[]\n /**\n * The share of its cell a bar covers.\n * @defaultValue 0.6\n */\n barSize?: number\n /** Formats the labels of the entries along x and the values along y. */\n format?: { x?: (label: string) => React.ReactNode; y?: (value: number) => React.ReactNode }\n /** The titles of the axes. */\n titles?: Partial<Record<keyof AxesSpec, React.ReactNode>>\n}\n\n/**\n * Bars on a floor: one row per entry of `data` along x, one per series\n * along z, the value as the height. The series are the keys of the chart\n * config unless `keys` names them.\n */\nexport function BarChart3D<T extends Record<string, unknown>>({\n data,\n index,\n keys,\n barSize = 0.6,\n format = {},\n titles,\n}: BarChart3DProps<T>) {\n const { config, camera } = useChart3D()\n const series = keys ?? Object.keys(config)\n const values = data.flatMap((d) => series.map((key) => Number(d[key]) || 0))\n\n const { spec, world, size } = barBox(data.length, series.length, values, barSize)\n const bars = cubes3D()\n .camera(camera)\n .data(\n data.flatMap((d, i) =>\n series.map((key, j) => {\n const value = Number(d[key]) || 0\n return Object.assign(barCorners(world, i, j, value, size), { i, key, value })\n })\n )\n )\n .map((bar) => Object.assign(bar, { bottom: bar.faces.find((f) => f.face === \"bottom\")! }))\n // The bars stand on one floor, so they paint back to front by their footprint.\n .sort((a, b) => sort(a.bottom, b.bottom))\n\n return (\n <>\n <Axes3D\n spec={spec}\n world={world}\n titles={titles}\n format={{\n x: (i) => (format.x ?? String)(String(data[i]?.[index] ?? \"\")),\n y: format.y,\n z: (j) => config[series[j]]?.label ?? series[j],\n }}\n />\n <Marks3D\n marks={bars.flatMap((bar) =>\n barMarks({\n id: `${bar.i}-${bar.key}`,\n faces: bar.faces,\n color: `var(--color-${bar.key})`,\n title: String(data[bar.i][index]),\n label: config[bar.key]?.label ?? bar.key,\n value: bar.value,\n })\n )}\n />\n </>\n )\n}\n\n/**\n * The box of a bar chart: a row per entry along x, a row per series along z,\n * and the size of a bar, the share `barSize` of its cell.\n */\nfunction barBox(entries: number, series: number, values: number[], barSize: number) {\n const index = (length: number) => Array.from({ length }, (_, i) => i)\n const spec: AxesSpec = {\n x: { domain: [-0.5, entries - 0.5], ticks: index(entries) },\n y: axis(Math.min(0, ...values), Math.max(0, ...values)),\n z: { domain: [-0.5, series - 0.5], ticks: index(series) },\n }\n // A shallow box for few series, so the bars stay square.\n const depth = Math.min(1, Math.max(0.25, series / entries))\n const size = { width: (2 / entries) * barSize, depth: ((2 * depth) / series) * barSize }\n return { spec, world: worldBox(spec, depth), size }\n}\n\n/**\n * The corners of one bar, from the floor to its value, at row `x` and series\n * `z`. box() rises towards -y, so a negative bar starts at its value.\n */\nfunction barCorners(\n world: World,\n x: number,\n z: number,\n value: number,\n size: { width: number; depth: number }\n) {\n const floor = world.y(0)\n const top = world.y(value)\n return box({\n x: world.x(x),\n y: Math.max(floor, top),\n z: world.z(z),\n ...size,\n height: Math.abs(floor - top),\n })\n}\n\n/** The marks of one bar: the faces that look at the camera, shaded, each with its entry and value on hover. */\nfunction barMarks({\n id,\n faces,\n color,\n title,\n label,\n value,\n}: {\n id: string\n faces: CubeFace[]\n color: string\n title: string\n label: React.ReactNode\n value: number\n}): Mark3D[] {\n const tooltip = {\n at: center(faces.find((face) => face.face === \"top\")!),\n title,\n items: [{ label, value, color }],\n }\n\n // The faces point outwards: a convex bar shows the ones facing the camera.\n return faces\n .filter((face) => face.ccw)\n .map((face) => ({\n key: `${id}-${face.face}`,\n points: face.map((p) => p.projected),\n fill: shade(face, color),\n tooltip,\n }))\n}\n","\"use client\"\n\nimport * as React from \"react\"\nimport { extent } from \"d3-array\"\nimport { points3D, sort, type AxesSpec } from \"d3-3d\"\n\nimport { Axes3D, Marks3D, axis, useChart3D, worldBox } from \"@/registry/new-york/ui/chart-3d\"\n\nexport type ScatterChart3DProps<T extends Record<string, unknown> = Record<string, unknown>> = {\n /** One entry per point. */\n data: T[]\n /** The field of the position along x. */\n x: keyof T & string\n /** The field of the position along y, the height. */\n y: keyof T & string\n /** The field of the position along z. */\n z: keyof T & string\n /**\n * The field whose value is the config key of the color of a point.\n * @defaultValue the first key of the chart config for every point\n */\n series?: keyof T & string\n /**\n * The radius of a point in the units of the view box.\n * @defaultValue 4\n */\n radius?: number\n /** Formats the tick labels of each axis. */\n format?: Partial<Record<keyof AxesSpec, (value: number) => React.ReactNode>>\n /** The titles of the axes. */\n titles?: Partial<Record<keyof AxesSpec, React.ReactNode>>\n}\n\n/**\n * Points in the chart box, at the fields `x`, `y` and `z` of each entry.\n * `series` names the field whose value is the config key of the color.\n */\nexport function ScatterChart3D<T extends Record<string, unknown>>({\n data,\n x,\n y,\n z,\n series,\n radius = 4,\n format,\n titles,\n}: ScatterChart3DProps<T>) {\n const { config, camera } = useChart3D()\n const fields = { x, y, z }\n const read = (d: T, axis: keyof AxesSpec) => Number(d[fields[axis]])\n\n const range = (a: keyof AxesSpec) => {\n const [lo = 0, hi = 0] = extent(data, (d) => read(d, a))\n return axis(lo, hi)\n }\n const spec: AxesSpec = { x: range(\"x\"), y: range(\"y\"), z: range(\"z\") }\n const world = worldBox(spec)\n\n const points = points3D<{ d: T; i: number }>()\n .camera(camera)\n .x(({ d }) => world.x(read(d, \"x\")))\n .y(({ d }) => world.y(read(d, \"y\")))\n .z(({ d }) => world.z(read(d, \"z\")))\n const fallback = Object.keys(config)[0]\n\n const marks = points\n .data(data.map((d, i) => ({ d, i })))\n .sort(sort)\n .map((p) => {\n const key = series ? String(p.d[series]) : fallback\n return {\n key: String(p.i),\n points: [p.projected],\n r: radius,\n fill: `var(--color-${key})`,\n // Ringed by the background, so that points in front stand out.\n stroke: \"var(--background)\",\n tooltip: {\n at: p.projected,\n title: series ? (config[key]?.label ?? key) : undefined,\n items: ([\"x\", \"y\", \"z\"] as const).map((a) => ({\n label: titles?.[a] ?? fields[a],\n value: read(p.d, a),\n })),\n },\n }\n })\n\n return (\n <>\n <Axes3D spec={spec} world={world} format={format} titles={titles} />\n <Marks3D marks={marks} />\n </>\n )\n}\n","\"use client\"\n\nimport * as React from \"react\"\nimport { extent } from \"d3-array\"\nimport { gridPlanes3D, sort, type AxesSpec, type Point3D } from \"d3-3d\"\n\nimport {\n Axes3D,\n Marks3D,\n axis,\n center,\n linear,\n shade,\n useChart3D,\n worldBox,\n} from \"@/registry/new-york/ui/chart-3d\"\n\nexport type SurfaceChart3DProps = {\n /** The heights on the grid, `data[row][column]`. */\n data: number[][]\n /**\n * The data values the first and the last column (x) and row (z) stand for.\n * @defaultValue the indices of the columns and rows\n */\n domain?: { x?: [number, number]; z?: [number, number] }\n /**\n * The config keys of the colors at the lowest and at the highest height.\n * @defaultValue the first two keys of the chart config\n */\n colors?: [string, string]\n /** Formats the tick labels of each axis. */\n format?: Partial<Record<keyof AxesSpec, (value: number) => React.ReactNode>>\n /** The titles of the axes. */\n titles?: Partial<Record<keyof AxesSpec, React.ReactNode>>\n}\n\n/**\n * A surface over a grid: `data[row][column]` is the height, the columns run\n * along x and the rows along z. The fill ramps from the first color of\n * `colors` at the lowest height to the second at the highest; both are keys\n * of the chart config, by default its first two.\n */\nexport function SurfaceChart3D({ data, domain = {}, colors, format, titles }: SurfaceChart3DProps) {\n const { config, camera } = useChart3D()\n const [low, high = low] = colors ?? Object.keys(config)\n const rows = data.length\n const columns = data[0]?.length ?? 0\n const [x0, x1] = domain.x ?? [0, columns - 1]\n const [z0, z1] = domain.z ?? [0, rows - 1]\n const [lo = 0, hi = 0] = extent(data.flat())\n\n const spec: AxesSpec = { x: axis(x0, x1), y: axis(lo, hi), z: axis(z0, z1) }\n const world = worldBox(spec)\n const toX = linear([0, columns - 1], [x0, x1])\n const toZ = linear([0, rows - 1], [z0, z1])\n\n const grid = gridPlanes3D<Point3D>()\n .camera(camera)\n .rows(columns)\n .x((p) => world.x(p.x))\n .y((p) => world.y(p.y))\n .z((p) => world.z(p.z))\n const points = data.flatMap((row, r) => row.map((y, c) => ({ x: toX(c), y, z: toZ(r) })))\n\n const marks = grid\n .data(points)\n .sort(sort)\n .map((quad) => {\n const height = quad.reduce((sum, p) => sum + p.y, 0) / quad.length\n const t = Math.round(((height - lo) / (hi - lo || 1)) * 100)\n return {\n key: quad.plane,\n points: quad.map((p) => p.projected),\n fill: shade(quad, `color-mix(in oklch, var(--color-${high}) ${t}%, var(--color-${low}))`),\n tooltip: { at: center(quad), items: [{ label: titles?.y ?? \"value\", value: height }] },\n }\n })\n\n return (\n <>\n <Axes3D spec={spec} world={world} format={format} titles={titles} />\n <Marks3D marks={marks} />\n </>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../lib/utils.ts","../registry/new-york/ui/chart-3d.tsx","../registry/new-york/ui/bar-chart-3d.tsx","../registry/new-york/ui/scatter-chart-3d.tsx","../registry/new-york/ui/surface-chart-3d.tsx","../registry/new-york/ui/sankey-chart-3d.tsx"],"sourcesContent":["import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n","\"use client\"\n\nimport * as React from \"react\"\nimport { nice, ticks } from \"d3-array\"\nimport {\n axes3D,\n camera3D,\n normal,\n type AxesSpec,\n type AxisEdge,\n type AxisSpec,\n type Camera3DInstance,\n type CubeFace,\n type Point2D,\n type TransformedPoint,\n} from \"d3-3d\"\nimport { KeyboardIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n// The same shape as the ChartConfig of shadcn's chart, so one config drives\n// a 2D and a 3D chart side by side.\nconst THEMES = { light: \"\", dark: \".dark\" } as const\n\nexport type ChartConfig = {\n [k in string]: {\n label?: React.ReactNode\n icon?: React.ComponentType\n } & (\n | { color?: string; theme?: never }\n | { color?: never; theme: Record<keyof typeof THEMES, string> }\n )\n}\n\n/** How the camera looks at the chart box, angles in radians. */\nexport type View3D = {\n yaw: number\n /** The angle above the floor the camera looks down from. */\n tilt: number\n zoom?: number\n /** How far the chart is moved, in the units of the view box. */\n pan?: Point2D\n /** The distance of the eye in box units; Infinity, the default, is orthographic. */\n perspective?: number\n}\n\n/** How the marks of a chart are drawn. The box, its labels, the tooltip and the legend are always SVG and HTML. */\nexport type Renderer3D = \"svg\" | \"canvas\"\n\n/** Maps data values to the world box, which spans -1 to 1 on every axis, y up. */\nexport type World = Record<keyof AxesSpec, (value: number) => number>\n\nexport type Tooltip3D = {\n /** The anchor in the units of the view box. */\n at: Point2D\n title?: React.ReactNode\n items: { label: React.ReactNode; value: React.ReactNode; color?: string }[]\n}\n\n/** One shape of a chart, projected, in paint order: a polygon, or a circle of radius `r` around its one point. */\nexport type Mark3D = {\n /** Stable across renders, so React moves the shape instead of redrawing it. */\n key: string\n /** The corners of a polygon, or the center of a circle, in the units of the view box. */\n points: Point2D[]\n r?: number\n /** A CSS color, such as the result of `shade` or `var(--color-x)`. */\n fill: string\n /** The ring around a circle, a CSS color. */\n stroke?: string\n /** Shown while the pointer is over the mark. */\n tooltip?: Tooltip3D\n}\n\ntype ChartContext3D = {\n config: ChartConfig\n camera: Camera3DInstance\n renderer: Renderer3D\n /** The marks of the chart, which the hover and the canvas read. */\n scene: React.RefObject<Mark3D[]>\n repaint: () => void\n}\n\nconst Chart3DContext = React.createContext<ChartContext3D | null>(null)\n\nexport function useChart3D() {\n const context = React.useContext(Chart3DContext)\n if (!context) {\n throw new Error(\"useChart3D must be used within a <ChartContainer3D />\")\n }\n return context\n}\n\nconst DRAG = Math.PI / 230\nconst KEY_STEP = Math.PI / 36\nconst PAN_STEP = 20\nconst ZOOM_STEP = 1.2\nconst CLICK_DISTANCE = 4\n\n/** A step of the camera: turned by `yaw` and `tilt`, moved by `pan`, zoomed by the factor `zoom`, or reset. */\ntype Move = { yaw?: number; tilt?: number; pan?: Point2D; zoom?: number; reset?: boolean }\n\n// The shortcuts of a chart: the keys of a row move the camera, and the\n// shortcut panel lists every row, so the two never disagree.\nconst SHORTCUTS: { hint: string[]; label: string; keys?: Record<string, Move> }[] = [\n { hint: [\"Drag\"], label: \"Rotate\" },\n {\n hint: [\"←\", \"→\", \"↑\", \"↓\"],\n label: \"Rotate\",\n keys: {\n ArrowLeft: { yaw: -KEY_STEP },\n ArrowRight: { yaw: KEY_STEP },\n ArrowUp: { tilt: -KEY_STEP },\n ArrowDown: { tilt: KEY_STEP },\n },\n },\n { hint: [\"Space\", \"Drag\"], label: \"Pan\" },\n {\n hint: [\"Shift\", \"←\", \"→\", \"↑\", \"↓\"],\n label: \"Pan\",\n keys: {\n \"Shift+ArrowLeft\": { pan: { x: -PAN_STEP, y: 0 } },\n \"Shift+ArrowRight\": { pan: { x: PAN_STEP, y: 0 } },\n \"Shift+ArrowUp\": { pan: { x: 0, y: -PAN_STEP } },\n \"Shift+ArrowDown\": { pan: { x: 0, y: PAN_STEP } },\n },\n },\n {\n hint: [\"+\", \"−\"],\n label: \"Zoom\",\n keys: { \"+\": { zoom: ZOOM_STEP }, \"=\": { zoom: ZOOM_STEP }, \"-\": { zoom: 1 / ZOOM_STEP } },\n },\n { hint: [\"Ctrl\", \"Scroll\"], label: \"Zoom\" },\n { hint: [\"0\"], label: \"Reset\", keys: { \"0\": { reset: true } } },\n { hint: [\"Double-click\"], label: \"Reset\" },\n { hint: [\"?\"], label: \"Shortcuts\" },\n]\nconst KEYS: Record<string, Move | undefined> = Object.assign({}, ...SHORTCUTS.map((s) => s.keys))\n// The keys as `aria-keyshortcuts` writes them: `+` delimits, so the plus key is `Plus`.\nconst KEY_SHORTCUTS = Object.keys(KEYS)\n .map((k) => (k === \"+\" ? \"Plus\" : k))\n .join(\" \")\n\nexport type ChartContainer3DProps = {\n /** The series of the chart: a label and a color per key, as in the chart of shadcn/ui. */\n config: ChartConfig\n /**\n * Where the camera starts, and where a reset returns it.\n * @defaultValue { yaw: π/6, tilt: π/6 }\n */\n defaultView?: View3D\n /**\n * The width of the view box the chart is drawn in.\n * @defaultValue 640\n */\n width?: number\n /**\n * The height of the view box the chart is drawn in.\n * @defaultValue 400\n */\n height?: number\n /**\n * Shows the label and the color of every config key below the chart.\n * @defaultValue false\n */\n legend?: boolean\n /**\n * How the marks of the chart are drawn: \"canvas\" suits many marks. The\n * chart draws as SVG until the browser shows it has a canvas, and where it\n * has none.\n * @defaultValue \"svg\"\n */\n renderer?: Renderer3D\n /**\n * Shows a button that lists the keyboard shortcuts of the chart; `?` lists them too.\n * @defaultValue true\n */\n shortcuts?: boolean\n}\n\nfunction ChartContainer3D({\n id,\n config,\n defaultView = { yaw: Math.PI / 6, tilt: Math.PI / 6 },\n width = 640,\n height = 400,\n legend = false,\n renderer: requested = \"svg\",\n shortcuts = true,\n className,\n children,\n \"aria-label\": ariaLabel = \"3D chart. Drag to rotate it, press ? for all shortcuts.\",\n ...props\n}: React.ComponentProps<\"div\"> & ChartContainer3DProps) {\n const uniqueId = React.useId()\n const chartId = `chart-${id || uniqueId.replace(/:/g, \"\")}`\n const [tooltip, setTooltip] = React.useState<Tooltip3D | null>(null)\n const { scene, renderer, canvas, repaint, hover, leave } = useScene(\n requested,\n width,\n height,\n setTooltip\n )\n const { view, orbiting, handlers } = useOrbit(defaultView, width, leave, hover)\n const camera = useCamera(view, width, height)\n const context = React.useMemo(\n () => ({ config, camera, renderer, scene, repaint }),\n [config, camera, renderer, repaint]\n )\n\n return (\n <div\n data-slot=\"chart-3d\"\n data-chart={chartId}\n className={cn(\"relative w-full text-xs\", className)}\n {...props}\n >\n <ChartStyle id={chartId} config={config} />\n <div className=\"relative rounded-lg has-focus-visible:ring-[3px] has-focus-visible:ring-ring/50\">\n <svg\n viewBox={`0 0 ${width} ${height}`}\n role=\"img\"\n aria-label={ariaLabel}\n aria-keyshortcuts={shortcuts ? `${KEY_SHORTCUTS} ?` : KEY_SHORTCUTS}\n tabIndex={0}\n className=\"block h-auto w-full cursor-grab touch-none outline-hidden select-none mask-y-from-96% active:cursor-grabbing\"\n data-orbiting={orbiting}\n onPointerLeave={leave}\n {...handlers}\n >\n <Chart3DContext.Provider value={context}>{children}</Chart3DContext.Provider>\n </svg>\n <ChartCanvas3D renderer={renderer} canvas={canvas} width={width} height={height} />\n <ChartTooltip3D tooltip={tooltip} width={width} height={height} />\n </div>\n {shortcuts && <ChartShortcuts3D />}\n {legend && <ChartLegend3D config={config} />}\n </div>\n )\n}\n\n/**\n * The marks of the chart and what reads them: the renderer that draws them,\n * the canvas it paints them on and the hover that shows their tooltips.\n */\nfunction useScene(\n requested: Renderer3D,\n width: number,\n height: number,\n setTooltip: (tooltip: Tooltip3D | null) => void\n) {\n const scene = React.useRef<Mark3D[]>([])\n const renderer = useRenderer(requested)\n const { canvas, repaint } = useCanvas(scene, width)\n const { hover, leave } = useHover(scene, width, height, setTooltip)\n return { scene, renderer, canvas, repaint, hover, leave }\n}\n\n/** The camera of a view. The shapes of the chart hang on it, so a new view re-projects them all. */\nfunction useCamera(view: View3D, width: number, height: number) {\n return React.useMemo(\n () =>\n camera3D()\n .origin({ x: width / 2 + (view.pan?.x ?? 0), y: height / 2 + (view.pan?.y ?? 0) })\n .scale(Math.min(width, height) * 0.3 * (view.zoom ?? 1))\n .perspective(view.perspective ?? Infinity)\n .rotateY(view.yaw)\n .rotateX(-view.tilt),\n [view, width, height]\n )\n}\n\n/** `view` moved by one step. The tilt stops at the zenith and the nadir, the zoom at 1/4 and 8. */\nfunction moved(view: View3D, move: Move, initial: View3D): View3D {\n if (move.reset) return initial\n const step = { yaw: 0, tilt: 0, zoom: 1, pan: { x: 0, y: 0 }, ...move }\n const pan = view.pan ?? { x: 0, y: 0 }\n const clamp = (lo: number, hi: number, value: number) => Math.max(lo, Math.min(hi, value))\n return {\n ...view,\n yaw: view.yaw + step.yaw,\n tilt: clamp(-Math.PI / 2, Math.PI / 2, view.tilt + step.tilt),\n zoom: clamp(0.25, 8, (view.zoom ?? 1) * step.zoom),\n pan: { x: pan.x + step.pan.x, y: pan.y + step.pan.y },\n }\n}\n\n/**\n * The view of the camera, and the handlers that move it: a drag turns it,\n * or pans it while Space is held, the keys of `SHORTCUTS` step it, Ctrl or ⌘\n * with the wheel zooms it and a double click resets it. `orbiting` is true\n * while the camera turns. `onDragStart` runs when a press becomes a drag,\n * `onHover` on a move without one.\n */\nfunction useOrbit(\n defaultView: View3D,\n width: number,\n onDragStart: () => void,\n onHover: (e: React.PointerEvent<SVGSVGElement>) => void\n) {\n const [view, setView] = React.useState(defaultView)\n const [orbiting, setOrbiting] = React.useState(false)\n const drag = React.useRef<Point2D & { moved: boolean }>(null)\n const space = React.useRef(false)\n const ref = useWheelZoom(setView)\n const move = (step: Move) => setView((v) => moved(v, step, defaultView))\n const release = () => {\n drag.current = null\n setOrbiting(false)\n }\n\n const handlers: React.SVGProps<SVGSVGElement> = {\n ref,\n onPointerDown: (e) => {\n drag.current = { x: e.clientX, y: e.clientY, moved: false }\n },\n onPointerMove: (e) => {\n const d = drag.current\n if (!d) return onHover(e)\n const dx = e.clientX - d.x\n const dy = e.clientY - d.y\n // A press that wanders a few pixels is still a click on a bar.\n if (!d.moved && Math.hypot(dx, dy) < CLICK_DISTANCE) return\n if (!d.moved) {\n e.currentTarget.setPointerCapture(e.pointerId)\n onDragStart()\n }\n drag.current = { x: e.clientX, y: e.clientY, moved: true }\n const k = width / e.currentTarget.getBoundingClientRect().width\n setOrbiting(!space.current)\n move(space.current ? { pan: { x: dx * k, y: dy * k } } : { yaw: dx * DRAG, tilt: dy * DRAG })\n },\n onPointerUp: release,\n onPointerCancel: release,\n onDoubleClick: () => move({ reset: true }),\n onKeyDown: (e) => {\n // Held on the chart, Space pans a drag instead of scrolling the page.\n if (e.key === \" \") space.current = true\n const step = (e.shiftKey && KEYS[`Shift+${e.key}`]) || KEYS[e.key]\n if (!step && e.key !== \" \") return\n e.preventDefault()\n if (!step) return\n setOrbiting(Boolean(step.yaw || step.tilt))\n move(step)\n },\n onKeyUp: (e) => {\n if (e.key === \" \") space.current = false\n setOrbiting(false)\n },\n }\n\n return { view, orbiting, handlers }\n}\n\n/**\n * A ref for the svg that zooms the view with Ctrl or ⌘ and the wheel, which\n * is also how a trackpad pinches. The wheel alone still scrolls the page.\n */\nfunction useWheelZoom(setView: React.Dispatch<React.SetStateAction<View3D>>) {\n // React listens to the wheel passively, and only a listener that is not\n // passive can keep the browser from zooming the page.\n return React.useCallback(\n (svg: SVGSVGElement | null) => {\n const onWheel = (e: WheelEvent) => {\n if (!e.ctrlKey && !e.metaKey) return\n e.preventDefault()\n setView((v) => moved(v, { zoom: 2 ** (-e.deltaY / 200) }, v))\n }\n svg?.addEventListener(\"wheel\", onWheel, { passive: false })\n return () => svg?.removeEventListener(\"wheel\", onWheel)\n },\n [setView]\n )\n}\n\n/**\n * The tooltip of the mark under the pointer, the one painted last. `hover`\n * sets it only when the pointer moves onto another mark, so a move within\n * one does not render the chart again.\n */\nfunction useHover(\n scene: React.RefObject<Mark3D[]>,\n width: number,\n height: number,\n setTooltip: (tooltip: Tooltip3D | null) => void\n) {\n const hovered = React.useRef<Mark3D>(undefined)\n const show = (mark?: Mark3D) => {\n if (mark?.key === hovered.current?.key) return\n hovered.current = mark\n setTooltip(mark?.tooltip ?? null)\n }\n\n return {\n hover: (e: React.PointerEvent<SVGSVGElement>) => {\n const rect = e.currentTarget.getBoundingClientRect()\n const at = {\n x: ((e.clientX - rect.left) / rect.width) * width,\n y: ((e.clientY - rect.top) / rect.height) * height,\n }\n show(hit(scene.current, at))\n },\n leave: () => show(undefined),\n }\n}\n\n/** The topmost mark under `at`: the last one painted that holds it. */\nfunction hit(marks: Mark3D[], at: Point2D) {\n for (let i = marks.length - 1; i >= 0; i -= 1) {\n const { points, r } = marks[i]\n const holds =\n r === undefined ? inside(points, at) : Math.hypot(points[0].x - at.x, points[0].y - at.y) <= r\n if (holds) return marks[i]\n }\n}\n\n/** Whether `at` lies inside `polygon`: a ray from it crosses an odd number of its edges. */\nfunction inside(polygon: Point2D[], at: Point2D) {\n let odd = false\n for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i, i += 1) {\n const a = polygon[i]\n const b = polygon[j]\n if (a.y > at.y !== b.y > at.y && at.x < ((b.x - a.x) * (at.y - a.y)) / (b.y - a.y) + a.x) {\n odd = !odd\n }\n }\n return odd\n}\n\n/**\n * The renderer the chart draws with: SVG on the server and in the first\n * render on the client, then the requested one if the browser has it.\n */\nfunction useRenderer(requested: Renderer3D) {\n const [renderer, setRenderer] = React.useState<Renderer3D>(\"svg\")\n React.useEffect(() => {\n // A canvas of its own, as one that gave out a context cannot give another.\n const canvas = requested === \"canvas\" && document.createElement(\"canvas\").getContext(\"2d\")\n setRenderer(canvas ? \"canvas\" : \"svg\")\n }, [requested])\n return renderer\n}\n\n/**\n * A ref for the canvas of the marks, and the repaint that paints them: on new\n * marks, when the canvas changes its size and when the theme changes. A color\n * resolves on the canvas itself, inside the chart, so the `--color-*` apply.\n */\nfunction useCanvas(scene: React.RefObject<Mark3D[]>, width: number) {\n const element = React.useRef<HTMLCanvasElement>(null)\n const colors = React.useRef(new Map<string, string>())\n\n const repaint = React.useCallback(() => {\n const canvas = element.current\n if (!canvas) return\n paint(canvas, scene.current, width, (css) => {\n let color = colors.current.get(css)\n if (color === undefined) {\n canvas.style.color = css\n color = getComputedStyle(canvas).color\n colors.current.set(css, color)\n }\n return color\n })\n }, [scene, width])\n\n const canvas = React.useCallback(\n // A ref that returns its cleanup is never called with null.\n (node: HTMLCanvasElement) => {\n element.current = node\n repaint()\n const resize = new ResizeObserver(repaint)\n resize.observe(node)\n // ponytail: watches the theme on <html> only, as the class `.dark` of THEMES does.\n const theme = new MutationObserver(() => {\n colors.current.clear()\n repaint()\n })\n theme.observe(document.documentElement, { attributeFilter: [\"class\", \"style\"] })\n return () => {\n element.current = null\n resize.disconnect()\n theme.disconnect()\n }\n },\n [repaint]\n )\n\n return { canvas, repaint }\n}\n\n/** Paints `marks` on `canvas`, from the view box of `width` to the pixels of the canvas. */\nfunction paint(\n canvas: HTMLCanvasElement,\n marks: Mark3D[],\n width: number,\n color: (css: string) => string\n) {\n canvas.width = Math.round(canvas.clientWidth * devicePixelRatio)\n canvas.height = Math.round(canvas.clientHeight * devicePixelRatio)\n // useRenderer draws on a canvas only where the browser gives one a 2D context.\n const ctx = canvas.getContext(\"2d\")!\n const scale = canvas.width / width\n ctx.setTransform(scale, 0, 0, scale, 0, 0)\n ctx.lineJoin = \"round\"\n\n for (const { points, r, fill, stroke } of marks) {\n ctx.beginPath()\n if (r === undefined) {\n for (const p of points) ctx.lineTo(p.x, p.y)\n ctx.closePath()\n } else {\n ctx.arc(points[0].x, points[0].y, r, 0, 2 * Math.PI)\n }\n ctx.fillStyle = color(fill)\n ctx.fill()\n // A polygon is outlined in its own color, which closes the seams to its neighbours.\n const outline = r === undefined ? fill : stroke\n if (!outline) continue\n ctx.strokeStyle = color(outline)\n ctx.lineWidth = r === undefined ? 0.5 : 1\n ctx.stroke()\n }\n}\n\n/** The canvas over the chart box that the marks are painted on, if the chart draws on one. */\nfunction ChartCanvas3D({\n renderer,\n canvas,\n width,\n height,\n}: {\n renderer: Renderer3D\n canvas: React.Ref<HTMLCanvasElement>\n width: number\n height: number\n}) {\n if (renderer !== \"canvas\") return null\n\n return (\n <canvas\n ref={canvas}\n className=\"pointer-events-none absolute top-0 left-0 w-full mask-y-from-96%\"\n style={{ aspectRatio: `${width} / ${height}` }}\n />\n )\n}\n\n/**\n * The marks of a chart, drawn by the renderer of the container: as SVG\n * elements, or on the canvas over the chart box. The hover reads them too.\n */\nfunction Marks3D({ marks }: { marks: Mark3D[] }) {\n const { renderer, scene, repaint } = useChart3D()\n React.useLayoutEffect(() => {\n scene.current = marks\n repaint()\n })\n\n if (renderer === \"canvas\") return null\n return marks.map(({ key, points, r, fill, stroke }) =>\n r === undefined ? (\n <path\n key={key}\n d={`M${points.map((p) => `${+p.x.toFixed(2)},${+p.y.toFixed(2)}`).join(\"L\")}Z`}\n fill={fill}\n stroke={fill}\n strokeWidth={0.5}\n strokeLinejoin=\"round\"\n />\n ) : (\n <circle\n key={key}\n cx={points[0].x}\n cy={points[0].y}\n r={r}\n fill={fill}\n stroke={stroke}\n strokeWidth={stroke ? 1 : undefined}\n />\n )\n )\n}\n\n/** A label beside a mark: `at` in the units of the view box, `anchor` the end of the text that sits there. */\nexport type Label3D = {\n key: string\n at: Point2D\n text: React.ReactNode\n anchor: \"start\" | \"middle\" | \"end\"\n}\n\n/**\n * The labels of the marks of a chart, ringed by the background so that they\n * read on the marks. Like the labels of the box, they are SVG with every renderer.\n */\nfunction Labels3D({ labels }: { labels: Label3D[] }) {\n // ponytail: under the canvas of the canvas renderer, as the labels of the box; an SVG layer over the canvas would lift them.\n return (\n <g data-slot=\"chart-3d-labels\">\n {labels.map(({ key, at, text, anchor }) => (\n <text\n key={key}\n x={at.x}\n y={at.y}\n textAnchor={anchor}\n dominantBaseline=\"central\"\n paintOrder=\"stroke\"\n strokeWidth={3}\n strokeLinejoin=\"round\"\n className=\"fill-foreground stroke-background text-[10px] font-medium\"\n >\n {text}\n </text>\n ))}\n </g>\n )\n}\n\nfunction ChartStyle({ id, config }: { id: string; config: ChartConfig }) {\n const colorConfig = Object.entries(config).filter(([, c]) => c.theme || c.color)\n\n if (!colorConfig.length) {\n return null\n }\n\n return (\n <style\n dangerouslySetInnerHTML={{\n __html: Object.entries(THEMES)\n .map(\n ([theme, prefix]) => `\n${prefix} [data-chart=${id}] {\n${colorConfig\n .map(([key, item]) => {\n const color = item.theme?.[theme as keyof typeof item.theme] || item.color\n return color ? ` --color-${key}: ${color};` : null\n })\n .join(\"\\n\")}\n}\n`\n )\n .join(\"\\n\"),\n }}\n />\n )\n}\n\nfunction ChartTooltip3D({\n tooltip,\n width,\n height,\n}: {\n tooltip: Tooltip3D | null\n width: number\n height: number\n}) {\n if (!tooltip) return null\n\n return (\n <div\n className=\"pointer-events-none absolute z-10 grid min-w-32 -translate-x-1/2 -translate-y-[calc(100%+0.5rem)] gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl\"\n style={{ left: `${(tooltip.at.x / width) * 100}%`, top: `${(tooltip.at.y / height) * 100}%` }}\n >\n {tooltip.title && <div className=\"font-medium\">{tooltip.title}</div>}\n {tooltip.items.map((item, i) => (\n <div key={i} className=\"flex items-center gap-2\">\n {item.color && (\n <span className=\"size-2.5 shrink-0 rounded-[2px]\" style={{ background: item.color }} />\n )}\n <span className=\"text-muted-foreground\">{item.label}</span>\n <span className=\"ml-auto font-mono font-medium text-foreground tabular-nums\">\n {typeof item.value === \"number\" ? item.value.toLocaleString() : item.value}\n </span>\n </div>\n ))}\n </div>\n )\n}\n\n/**\n * A button in the corner of the chart that lists `SHORTCUTS`. `?` on the\n * chart opens and closes the list, Escape closes it.\n */\nfunction ChartShortcuts3D() {\n const [open, setOpen] = React.useState(false)\n const ref = React.useRef<HTMLDivElement>(null)\n\n React.useEffect(() => {\n const chart = ref.current?.parentElement\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"?\") setOpen((o) => !o)\n if (e.key === \"Escape\") setOpen(false)\n }\n chart?.addEventListener(\"keydown\", onKeyDown)\n return () => chart?.removeEventListener(\"keydown\", onKeyDown)\n }, [])\n\n return (\n <div ref={ref} className=\"absolute top-2 right-2 z-10 flex flex-col items-end gap-1\">\n <button\n type=\"button\"\n aria-label=\"Keyboard shortcuts\"\n aria-expanded={open}\n onClick={() => setOpen((o) => !o)}\n className=\"inline-flex size-7 items-center justify-center rounded-md text-muted-foreground outline-none hover:bg-accent hover:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-4\"\n >\n <KeyboardIcon />\n </button>\n {open && (\n <div className=\"grid grid-cols-[auto_1fr] items-center gap-x-3 gap-y-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-2 shadow-xl\">\n {SHORTCUTS.map(({ hint, label }, i) => (\n <React.Fragment key={i}>\n <span data-slot=\"kbd-group\" className=\"flex gap-1\">\n {hint.map((key) => (\n <kbd\n key={key}\n data-slot=\"kbd\"\n className=\"pointer-events-none inline-flex h-5 min-w-5 items-center justify-center rounded-sm bg-muted px-1 font-sans text-xs font-medium text-muted-foreground select-none\"\n >\n {key}\n </kbd>\n ))}\n </span>\n <span className=\"text-muted-foreground\">{label}</span>\n </React.Fragment>\n ))}\n </div>\n )}\n </div>\n )\n}\n\nfunction ChartLegend3D({ config }: { config: ChartConfig }) {\n return (\n <div className=\"flex items-center justify-center gap-4 pt-3\">\n {Object.entries(config).map(([key, item]) => (\n <div key={key} className=\"flex items-center gap-1.5\">\n {item.icon ? (\n <item.icon />\n ) : (\n <span className=\"size-2 rounded-[2px]\" style={{ background: `var(--color-${key})` }} />\n )}\n {item.label ?? key}\n </div>\n ))}\n </div>\n )\n}\n\n/**\n * The walls of the chart box that face away from the camera, with their\n * gridlines, and an axis with ticks on the outline of the box.\n */\nfunction Axes3D({\n spec,\n world,\n format = {},\n titles = {},\n}: {\n spec: AxesSpec\n world: World\n format?: Partial<Record<keyof AxesSpec, (value: number) => React.ReactNode>>\n titles?: Partial<Record<keyof AxesSpec, React.ReactNode>>\n}) {\n const { camera } = useChart3D()\n const box = axes3D()\n .camera(camera)\n .x((p) => world.x(p.x))\n .y((p) => world.y(p.y))\n .z((p) => world.z(p.z))\n\n return (\n <g data-slot=\"chart-3d-axes\">\n {box\n .data(spec)\n .filter((wall) => !wall.ccw)\n .map((wall) => (\n <path\n key={`${wall.axis}${wall.side}`}\n d={box.draw(wall)}\n className=\"fill-muted/40 stroke-border\"\n strokeWidth={0.75}\n />\n ))}\n {labelAxes(box.axes(spec), format, titles).map((axis) => (\n <AxisEdge3D key={axis.key} ticks={axis.ticks} title={axis.title} />\n ))}\n </g>\n )\n}\n\n/** A label of the chart box in the units of the view box: a tick label or the title of an axis. */\ntype AxisLabel = {\n x: number\n y: number\n anchor: \"start\" | \"middle\" | \"end\"\n text: React.ReactNode\n /** The font size, which the overlap check reads. */\n size: number\n hidden?: boolean\n}\n\n/**\n * The labels of one axis on the outline of the box: a label per tick, pushed\n * away from the box along the normal of the edge, and the title of the axis\n * further out. `key` names the edge, one of the four the axis moves between\n * as the camera turns.\n */\nfunction axisLabels(\n edge: AxisEdge,\n format: (value: number) => React.ReactNode,\n title?: React.ReactNode\n) {\n const { normal: n } = edge\n const start = edge[0].projected\n const end = edge[edge.length - 1].projected\n const anchor = n.x > 0.3 ? \"start\" : n.x < -0.3 ? \"end\" : \"middle\"\n const label = (at: Point2D, out: number, text: React.ReactNode, size: number): AxisLabel => ({\n x: at.x + n.x * out,\n y: at.y + n.y * out,\n anchor,\n text,\n size,\n })\n const middle = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 }\n\n return {\n key: `${edge.axis}:${edge[0].x},${edge[0].y},${edge[0].z}`,\n ticks: edge.map((tick) => label(tick.projected, 10, format(tick[edge.axis]), 10)),\n title: title ? label(middle, 44, title, 12) : undefined,\n }\n}\n\n/**\n * The labels of the axes, with every tick label hidden that would cover a\n * title or a tick label before it, so an axis seen end on keeps its first\n * label and a short one every other.\n */\nfunction labelAxes(\n edges: AxisEdge[],\n format: Partial<Record<keyof AxesSpec, (value: number) => React.ReactNode>>,\n titles: Partial<Record<keyof AxesSpec, React.ReactNode>>\n) {\n // An axis without ticks, such as the rows of an empty chart, has nothing to label.\n const axes = edges\n .filter((edge) => edge.length)\n .map((edge) => axisLabels(edge, format[edge.axis] ?? String, titles[edge.axis]))\n const placed = axes.flatMap((axis) => (axis.title ? [bounds(axis.title)] : []))\n for (const label of axes.flatMap((axis) => axis.ticks)) {\n const b = bounds(label)\n label.hidden = placed.some(\n (p) => b.left < p.right && p.left < b.right && b.top < p.bottom && p.top < b.bottom\n )\n if (!label.hidden) placed.push(b)\n }\n return axes\n}\n\n/** The box a label covers, guessed from its text, as the browser lays it out only after the render. */\nfunction bounds({ x, y, anchor, text, size }: AxisLabel) {\n // ponytail: 0.6em a character; measure with getBBox if wide fonts or rich labels still overlap.\n const chars = typeof text === \"string\" || typeof text === \"number\" ? String(text).length : 4\n const width = chars * size * 0.6\n const left = x - width * { start: 0, middle: 0.5, end: 1 }[anchor]\n return { left, right: left + width, top: y - size / 2, bottom: y + size / 2 }\n}\n\n/**\n * The labels of one axis, dimmed while the camera turns. When the axis jumps\n * to another edge of the box, its key changes, so they mount anew and fade in\n * there.\n */\nfunction AxisEdge3D({ ticks, title }: { ticks: AxisLabel[]; title?: AxisLabel }) {\n return (\n <g className=\"transition-opacity duration-300 starting:opacity-0 in-data-[orbiting=true]:opacity-50\">\n {ticks.map(({ x, y, anchor, text, hidden }, i) => (\n <text\n key={i}\n x={x}\n y={y}\n textAnchor={anchor}\n dominantBaseline=\"central\"\n className={cn(\n \"fill-muted-foreground text-[10px] transition-opacity\",\n hidden && \"opacity-0\"\n )}\n >\n {text}\n </text>\n ))}\n {title && (\n <text\n x={title.x}\n y={title.y}\n textAnchor={title.anchor}\n dominantBaseline=\"central\"\n className=\"fill-foreground font-medium\"\n >\n {title.text}\n </text>\n )}\n </g>\n )\n}\n\n/** A nice axis from `lo` to `hi` with about `count` ticks. */\nfunction axis(lo: number, hi: number, count = 5): AxisSpec {\n const [start, stop] = nice(lo, lo === hi ? hi + 1 : hi, count)\n return { domain: [start, stop], ticks: ticks(start, stop, count) }\n}\n\n/** The linear map from `domain` to `range`. */\nfunction linear([d0, d1]: number[], [r0, r1]: number[]) {\n return (value: number) => r0 + ((value - d0) / (d1 - d0 || 1)) * (r1 - r0)\n}\n\n/** The world box of `spec`: x from -1 to 1, y up from 1 to -1, z from -`depth` to `depth`. */\nfunction worldBox(spec: AxesSpec, depth = 1): World {\n return {\n x: linear(spec.x.domain, [-1, 1]),\n y: linear(spec.y.domain, [1, -1]),\n z: linear(spec.z.domain, [-depth, depth]),\n }\n}\n\n// From the upper left, towards the viewer, in view space where y points down.\nconst LIGHT = { x: -0.4, y: -0.8, z: 0.44 }\n\n/**\n * `color` lit by the light from the upper left: the face that looks at it\n * keeps the color, one that turns away gets darker. A CSS color-mix, so the\n * shade follows the theme variables.\n */\nfunction shade(face: TransformedPoint<unknown>[], color: string) {\n const n = normal(face.map((p) => p.rotated))\n // The normal of the side the camera sees, whichever way the face winds.\n const side = n.z < 0 ? -1 : 1\n const light = Math.max(0, side * (n.x * LIGHT.x + n.y * LIGHT.y + n.z * LIGHT.z))\n return `color-mix(in oklch, ${color} ${Math.round(65 + 35 * light)}%, black)`\n}\n\n/** The mean of the projected points, where a tooltip points at. */\nfunction center(points: TransformedPoint<unknown>[]): Point2D {\n const sum = points.reduce((s, p) => ({ x: s.x + p.projected.x, y: s.y + p.projected.y }), {\n x: 0,\n y: 0,\n })\n return { x: sum.x / points.length, y: sum.y / points.length }\n}\n\n/** The marks of a cube: its faces that look at the camera, shaded in `color`, each with `tooltip` on hover. */\nfunction cubeMarks(id: string, faces: CubeFace[], color: string, tooltip: Tooltip3D): Mark3D[] {\n // The faces point outwards: a convex shape shows the ones facing the camera.\n return faces\n .filter((face) => face.ccw)\n .map((face) => ({\n key: `${id}-${face.face}`,\n points: face.map((p) => p.projected),\n fill: shade(face, color),\n tooltip,\n }))\n}\n\nexport {\n ChartContainer3D,\n ChartLegend3D,\n Axes3D,\n Marks3D,\n Labels3D,\n axis,\n linear,\n worldBox,\n shade,\n center,\n cubeMarks,\n}\n","\"use client\"\n\nimport * as React from \"react\"\nimport { box, cubes3D, sort, type AxesSpec, type CubeFace } from \"d3-3d\"\n\nimport {\n Axes3D,\n Marks3D,\n axis,\n center,\n cubeMarks,\n useChart3D,\n worldBox,\n type World,\n} from \"@/registry/new-york/ui/chart-3d\"\n\nexport type BarChart3DProps<T extends Record<string, unknown> = Record<string, unknown>> = {\n /** One entry per row of bars, with a value per series. */\n data: T[]\n /** The field that names each entry, such as \"month\". */\n index: keyof T & string\n /**\n * The fields that hold the series, one row of bars each.\n * @defaultValue the keys of the chart config\n */\n keys?: string[]\n /**\n * The share of its cell a bar covers.\n * @defaultValue 0.6\n */\n barSize?: number\n /** Formats the labels of the entries along x and the values along y. */\n format?: { x?: (label: string) => React.ReactNode; y?: (value: number) => React.ReactNode }\n /** The titles of the axes. */\n titles?: Partial<Record<keyof AxesSpec, React.ReactNode>>\n}\n\n/**\n * Bars on a floor: one row per entry of `data` along x, one per series\n * along z, the value as the height. The series are the keys of the chart\n * config unless `keys` names them.\n */\nexport function BarChart3D<T extends Record<string, unknown>>({\n data,\n index,\n keys,\n barSize = 0.6,\n format = {},\n titles,\n}: BarChart3DProps<T>) {\n const { config, camera } = useChart3D()\n const series = keys ?? Object.keys(config)\n // A value that is not a number stands as no bar, one of height 0.\n const cells = data.flatMap((d, i) =>\n series.map((key, j) => ({ i, j, key, value: Number(d[key]) || 0 }))\n )\n\n const { spec, world, size } = barBox(\n data.length,\n series.length,\n cells.map((cell) => cell.value),\n barSize\n )\n const bars = cubes3D()\n .camera(camera)\n .data(\n cells.map(({ i, j, key, value }) =>\n Object.assign(barCorners(world, i, j, value, size), { i, key, value })\n )\n )\n .map((bar) => Object.assign(bar, { bottom: bar.faces.find((f) => f.face === \"bottom\")! }))\n // The bars stand on one floor, so they paint back to front by their footprint.\n .sort((a, b) => sort(a.bottom, b.bottom))\n\n return (\n <>\n <Axes3D\n spec={spec}\n world={world}\n titles={titles}\n format={{\n // The ticks along x are the indices of the entries.\n x: (i) => (format.x ?? String)(String(data[i][index] ?? \"\")),\n y: format.y,\n z: (j) => config[series[j]]?.label ?? series[j],\n }}\n />\n <Marks3D\n marks={bars.flatMap((bar) =>\n barMarks({\n id: `${bar.i}-${bar.key}`,\n faces: bar.faces,\n color: `var(--color-${bar.key})`,\n title: String(data[bar.i][index]),\n label: config[bar.key]?.label ?? bar.key,\n value: bar.value,\n })\n )}\n />\n </>\n )\n}\n\n/**\n * The box of a bar chart: a row per entry along x, a row per series along z,\n * and the size of a bar, the share `barSize` of its cell.\n */\nfunction barBox(entries: number, series: number, values: number[], barSize: number) {\n const index = (length: number) => Array.from({ length }, (_, i) => i)\n const spec: AxesSpec = {\n x: { domain: [-0.5, entries - 0.5], ticks: index(entries) },\n y: axis(Math.min(0, ...values), Math.max(0, ...values)),\n z: { domain: [-0.5, series - 0.5], ticks: index(series) },\n }\n // A shallow box for few series, so the bars stay square.\n const depth = Math.min(1, Math.max(0.25, series / entries))\n const size = { width: (2 / entries) * barSize, depth: ((2 * depth) / series) * barSize }\n return { spec, world: worldBox(spec, depth), size }\n}\n\n/**\n * The corners of one bar, from the floor to its value, at row `x` and series\n * `z`. box() rises towards -y, so a negative bar starts at its value.\n */\nfunction barCorners(\n world: World,\n x: number,\n z: number,\n value: number,\n size: { width: number; depth: number }\n) {\n const floor = world.y(0)\n const top = world.y(value)\n return box({\n x: world.x(x),\n y: Math.max(floor, top),\n z: world.z(z),\n ...size,\n height: Math.abs(floor - top),\n })\n}\n\n/** The marks of one bar: the faces that look at the camera, shaded, each with its entry and value on hover. */\nfunction barMarks({\n id,\n faces,\n color,\n title,\n label,\n value,\n}: {\n id: string\n faces: CubeFace[]\n color: string\n title: string\n label: React.ReactNode\n value: number\n}) {\n const tooltip = {\n at: center(faces.find((face) => face.face === \"top\")!),\n title,\n items: [{ label, value, color }],\n }\n return cubeMarks(id, faces, color, tooltip)\n}\n","\"use client\"\n\nimport * as React from \"react\"\nimport { extent } from \"d3-array\"\nimport { points3D, sort, type AxesSpec } from \"d3-3d\"\n\nimport { Axes3D, Marks3D, axis, useChart3D, worldBox } from \"@/registry/new-york/ui/chart-3d\"\n\nexport type ScatterChart3DProps<T extends Record<string, unknown> = Record<string, unknown>> = {\n /** One entry per point. */\n data: T[]\n /** The field of the position along x. */\n x: keyof T & string\n /** The field of the position along y, the height. */\n y: keyof T & string\n /** The field of the position along z. */\n z: keyof T & string\n /**\n * The field whose value is the config key of the color of a point.\n * @defaultValue the first key of the chart config for every point\n */\n series?: keyof T & string\n /**\n * The radius of a point in the units of the view box.\n * @defaultValue 4\n */\n radius?: number\n /** Formats the tick labels of each axis. */\n format?: Partial<Record<keyof AxesSpec, (value: number) => React.ReactNode>>\n /** The titles of the axes. */\n titles?: Partial<Record<keyof AxesSpec, React.ReactNode>>\n}\n\n/**\n * Points in the chart box, at the fields `x`, `y` and `z` of each entry.\n * `series` names the field whose value is the config key of the color.\n */\nexport function ScatterChart3D<T extends Record<string, unknown>>({\n data,\n x,\n y,\n z,\n series,\n radius = 4,\n format,\n titles,\n}: ScatterChart3DProps<T>) {\n const { config, camera } = useChart3D()\n const fields = { x, y, z }\n const read = (d: T, axis: keyof AxesSpec) => Number(d[fields[axis]])\n\n const range = (a: keyof AxesSpec) => {\n const [lo = 0, hi = 0] = extent(data, (d) => read(d, a))\n return axis(lo, hi)\n }\n const spec: AxesSpec = { x: range(\"x\"), y: range(\"y\"), z: range(\"z\") }\n const world = worldBox(spec)\n\n const points = points3D<{ d: T; i: number }>()\n .camera(camera)\n .x(({ d }) => world.x(read(d, \"x\")))\n .y(({ d }) => world.y(read(d, \"y\")))\n .z(({ d }) => world.z(read(d, \"z\")))\n const fallback = Object.keys(config)[0]\n\n const marks = points\n .data(data.map((d, i) => ({ d, i })))\n .sort(sort)\n .map((p) => {\n const key = series ? String(p.d[series]) : fallback\n return {\n key: String(p.i),\n points: [p.projected],\n r: radius,\n fill: `var(--color-${key})`,\n // Ringed by the background, so that points in front stand out.\n stroke: \"var(--background)\",\n tooltip: {\n at: p.projected,\n title: series ? (config[key]?.label ?? key) : undefined,\n items: ([\"x\", \"y\", \"z\"] as const).map((a) => ({\n label: titles?.[a] ?? fields[a],\n value: read(p.d, a),\n })),\n },\n }\n })\n\n return (\n <>\n <Axes3D spec={spec} world={world} format={format} titles={titles} />\n <Marks3D marks={marks} />\n </>\n )\n}\n","\"use client\"\n\nimport * as React from \"react\"\nimport { extent } from \"d3-array\"\nimport { gridPlanes3D, sort, type AxesSpec, type Point3D } from \"d3-3d\"\n\nimport {\n Axes3D,\n Marks3D,\n axis,\n center,\n linear,\n shade,\n useChart3D,\n worldBox,\n} from \"@/registry/new-york/ui/chart-3d\"\n\nexport type SurfaceChart3DProps = {\n /** The heights on the grid, `data[row][column]`. */\n data: number[][]\n /**\n * The data values the first and the last column (x) and row (z) stand for.\n * @defaultValue the indices of the columns and rows\n */\n domain?: { x?: [number, number]; z?: [number, number] }\n /**\n * The config keys of the colors at the lowest and at the highest height.\n * @defaultValue the first two keys of the chart config\n */\n colors?: [string, string]\n /** Formats the tick labels of each axis. */\n format?: Partial<Record<keyof AxesSpec, (value: number) => React.ReactNode>>\n /** The titles of the axes. */\n titles?: Partial<Record<keyof AxesSpec, React.ReactNode>>\n}\n\n/**\n * A surface over a grid: `data[row][column]` is the height, the columns run\n * along x and the rows along z. The fill ramps from the first color of\n * `colors` at the lowest height to the second at the highest; both are keys\n * of the chart config, by default its first two.\n */\nexport function SurfaceChart3D({ data, domain = {}, colors, format, titles }: SurfaceChart3DProps) {\n const { config, camera } = useChart3D()\n const [low, high = low] = colors ?? Object.keys(config)\n const rows = data.length\n const columns = data[0]?.length ?? 0\n const [x0, x1] = domain.x ?? [0, columns - 1]\n const [z0, z1] = domain.z ?? [0, rows - 1]\n const [lo = 0, hi = 0] = extent(data.flat())\n\n const spec: AxesSpec = { x: axis(x0, x1), y: axis(lo, hi), z: axis(z0, z1) }\n const world = worldBox(spec)\n const toX = linear([0, columns - 1], [x0, x1])\n const toZ = linear([0, rows - 1], [z0, z1])\n\n const grid = gridPlanes3D<Point3D>()\n .camera(camera)\n // d3-3d reads rows(0) as the getter; a grid without columns has no points anyway.\n .rows(Math.max(1, columns))\n .x((p) => world.x(p.x))\n .y((p) => world.y(p.y))\n .z((p) => world.z(p.z))\n const points = data.flatMap((row, r) => row.map((y, c) => ({ x: toX(c), y, z: toZ(r) })))\n\n const marks = grid\n .data(points)\n .sort(sort)\n .map((quad) => {\n const height = quad.reduce((sum, p) => sum + p.y, 0) / quad.length\n const t = Math.round(((height - lo) / (hi - lo || 1)) * 100)\n return {\n key: quad.plane,\n points: quad.map((p) => p.projected),\n fill: shade(quad, `color-mix(in oklch, var(--color-${high}) ${t}%, var(--color-${low}))`),\n tooltip: { at: center(quad), items: [{ label: titles?.y ?? \"value\", value: height }] },\n }\n })\n\n return (\n <>\n <Axes3D spec={spec} world={world} format={format} titles={titles} />\n <Marks3D marks={marks} />\n </>\n )\n}\n","\"use client\"\n\nimport * as React from \"react\"\nimport { cubes3D, sort, type Camera3DInstance, type Cube, type CubeFace, type Point3D } from \"d3-3d\"\n\nimport {\n Labels3D,\n Marks3D,\n center,\n cubeMarks,\n useChart3D,\n type ChartConfig,\n type Label3D,\n} from \"@/registry/new-york/ui/chart-3d\"\n\nexport type SankeyChart3DProps = {\n /** One entry per flow. `source` and `target` are keys of the chart config, which names and colors the nodes. */\n data: { source: string; target: string; value: number }[]\n /**\n * The width of a node, as a share of the distance between two columns.\n * @defaultValue 0.2\n */\n nodeWidth?: number\n /**\n * The gap between two nodes of a column, as a share of the height.\n * @defaultValue 0.05\n */\n nodePadding?: number\n /** Formats the values in the tooltip. */\n format?: (value: number) => React.ReactNode\n}\n\n// The depth of a node in world units, and the share of it a flow takes.\nconst DEPTH = 0.4\nconst RIBBON = 0.7\n// The slices of a flow from one column to the next: the more, the smoother it bends.\nconst SEGMENTS = 16\n\n/**\n * A node of the layout, from its top `y0` to its bottom `y1` in the world. A\n * node with a `flow` is a waypoint, where a flow that skips a column passes it.\n */\ntype SankeyNode = {\n id: string\n column: number\n value: number\n y0: number\n y1: number\n incoming: Hop[]\n outgoing: Hop[]\n flow?: Flow\n}\n\n/** An entry of `data` between its two nodes. */\ntype Flow = { key: string; source: SankeyNode; target: SankeyNode; value: number }\n\n/** The part of a flow from one column to the next, from the middle of its slot at the source, `y0`, to the one at the target, `y1`. */\ntype Hop = { flow: Flow; source: SankeyNode; target: SankeyNode; y0: number; y1: number }\n\n/**\n * A part of the chart in the world: a node, or a slice of a flow that has come\n * the share `t` of its way there. `band` is the middle of the slab between two\n * planes x = const that the part lies in.\n */\ntype Piece = Point3D[] & { key: string; band: number; node?: SankeyNode; flow?: Flow; t: number }\n\n/**\n * Flows from left to right: every node a box in its column, as high as the\n * larger of what flows in and what flows out, and every flow a band from node\n * to node, as thick as its value, that turns from the color of its source to\n * the color of its target.\n */\nexport function SankeyChart3D({\n data,\n nodeWidth = 0.2,\n nodePadding = 0.05,\n format = (value) => value,\n}: SankeyChart3DProps) {\n const { config, camera } = useChart3D()\n const { nodes, columns, ky } = sankeyLayout(data, nodePadding)\n const pieces = depthSort(\n cubes3D()\n .camera(camera)\n .data(sankeyPieces(nodes, columns, ky, nodeWidth)),\n camera\n )\n // The labels sit outside: left of the nodes in the left half, right of the others.\n const labels = pieces.flatMap(({ key, faces, node }) =>\n node\n ? [\n nodeLabel(\n key,\n faces,\n node.column < (columns - 1) / 2 ? \"left\" : \"right\",\n style(config, node).label\n ),\n ]\n : []\n )\n\n return (\n <>\n <Marks3D marks={pieces.flatMap((piece) => pieceMarks(piece, config, format))} />\n <Labels3D labels={labels} />\n </>\n )\n}\n\n/** The label and the color of a node, from the chart config. */\nfunction style(config: ChartConfig, node: SankeyNode) {\n return {\n label: config[node.id]?.label ?? node.id,\n color: config[node.id] ? `var(--color-${node.id})` : \"var(--muted-foreground)\",\n }\n}\n\n/**\n * The marks of a piece, each with its tooltip: the faces of a node, or the\n * faces of a slice of a flow, in the color it has turned to there.\n */\nfunction pieceMarks(\n { key, faces, node, flow, t }: Cube<Point3D, Piece>,\n config: ChartConfig,\n format: (value: number) => React.ReactNode\n) {\n const at = center(faces.find((face) => face.face === \"top\")!)\n if (node) {\n const { label, color } = style(config, node)\n return cubeMarks(key, faces, color, {\n at,\n items: [{ label, value: format(node.value), color }],\n })\n }\n const source = style(config, flow!.source)\n const target = style(config, flow!.target)\n const item = {\n label: (\n <>\n {source.label} → {target.label}\n </>\n ),\n value: format(flow!.value),\n color: source.color,\n }\n // The ends of a slice are joints, inside the flow or against a node.\n const sides = faces.filter((face) => face.face !== \"left\" && face.face !== \"right\")\n // A flow is softer than the nodes, a quarter towards the background, so that they stand out of it.\n const gradient = `color-mix(in oklch, color-mix(in oklch, ${target.color} ${t}%, ${source.color}) 75%, var(--background))`\n return cubeMarks(key, sides, gradient, { at, items: [item] })\n}\n\n/** The nodes of `data`, waypoints included, stacked in their columns, and `ky`, the world units per value. */\nfunction sankeyLayout(data: SankeyChart3DProps[\"data\"], nodePadding: number) {\n const { nodes, flows, columns } = sankeyColumns(data)\n const all = sankeyHops(nodes, flows)\n const ky = stackColumns(all, columns, nodePadding)\n stackHops(all, ky)\n return { nodes: all, columns, ky }\n}\n\nfunction newNode(id: string, column: number, flow?: Flow): SankeyNode {\n return { id, column, value: 0, y0: 0, y1: 0, incoming: [], outgoing: [], flow }\n}\n\nfunction middle(node: SankeyNode) {\n return (node.y0 + node.y1) / 2\n}\n\n/**\n * The nodes of `data` in the order they first appear, and the flows between\n * them, without the ones of no value. The column of a node is the longest\n * path to it from a source; the sinks move to the last column.\n */\nfunction sankeyColumns(data: SankeyChart3DProps[\"data\"]) {\n const nodes = new Map<string, SankeyNode>()\n const node = (id: string) => {\n if (!nodes.has(id)) nodes.set(id, newNode(id, 0))\n return nodes.get(id)!\n }\n const flows = data\n .filter((d) => d.value > 0)\n .map((d, i) => ({\n key: String(i),\n source: node(d.source),\n target: node(d.target),\n value: d.value,\n }))\n\n // Bellman-Ford for the longest paths: a pass more than there are nodes means a cycle.\n for (let pass = 0; flows.some((f) => f.target.column <= f.source.column); pass += 1) {\n if (pass === nodes.size) throw new Error(\"SankeyChart3D: the flows form a cycle\")\n for (const f of flows) f.target.column = Math.max(f.target.column, f.source.column + 1)\n }\n const last = Math.max(0, ...flows.map((f) => f.target.column))\n const sources = new Set(flows.map((f) => f.source))\n for (const n of nodes.values()) if (!sources.has(n)) n.column = last\n return { nodes: [...nodes.values()], flows, columns: last + 1 }\n}\n\n/**\n * Splits every flow into hops from one column to the next. A flow that skips a\n * column passes it at a waypoint, a node of its own, so it has a slot in every\n * column it crosses and never runs through another node.\n */\nfunction sankeyHops(nodes: SankeyNode[], flows: Flow[]) {\n const all = [...nodes]\n for (const flow of flows) {\n let source = flow.source\n for (let column = source.column + 1; column <= flow.target.column; column += 1) {\n const target =\n column < flow.target.column ? newNode(`${flow.key}:${column}`, column, flow) : flow.target\n if (target.flow) all.push(target)\n const hop = { flow, source, target, y0: 0, y1: 0 }\n source.outgoing.push(hop)\n target.incoming.push(hop)\n source = target\n }\n }\n return all\n}\n\n/**\n * Sizes the nodes to the largest scale at which every column fits, and stacks\n * each column from the top, centered. A column after the first orders its nodes\n * by the mean height of their sources, which uncrosses most flows. Returns the\n * scale, world units per value.\n */\nfunction stackColumns(nodes: SankeyNode[], columns: number, nodePadding: number) {\n const total = (hops: Hop[]) => hops.reduce((sum, hop) => sum + hop.flow.value, 0)\n for (const node of nodes) node.value = Math.max(total(node.incoming), total(node.outgoing))\n const stacks = Array.from({ length: columns }, (_, c) => nodes.filter((n) => n.column === c))\n const sum = (stack: SankeyNode[]) => stack.reduce((s, n) => s + n.value, 0)\n // A crowded column narrows the gaps, so that its nodes keep a height.\n const pad = Math.min(2 * nodePadding, 2 / Math.max(1, ...stacks.map((s) => s.length)))\n const ky = Math.min(...stacks.map((s) => (2 - pad * (s.length - 1)) / sum(s)))\n const mean = (node: SankeyNode) =>\n node.incoming.reduce((s, hop) => s + hop.flow.value * middle(hop.source), 0) /\n total(node.incoming)\n\n for (const [c, stack] of stacks.entries()) {\n if (c > 0) stack.sort((a, b) => mean(a) - mean(b))\n let y = -(sum(stack) * ky + pad * (stack.length - 1)) / 2\n for (const node of stack) {\n node.y0 = y\n node.y1 = y + node.value * ky\n y = node.y1 + pad\n }\n }\n return ky\n}\n\n/**\n * Stacks the hops at every node from its top: the ones that leave it in the\n * order of their targets, the ones that arrive in the order of their sources,\n * so that the hops of a node do not cross.\n */\nfunction stackHops(nodes: SankeyNode[], ky: number) {\n for (const node of nodes) {\n let y = node.y0\n for (const hop of node.outgoing.sort((a, b) => middle(a.target) - middle(b.target))) {\n hop.y0 = y + (hop.flow.value * ky) / 2\n y += hop.flow.value * ky\n }\n y = node.y0\n for (const hop of node.incoming.sort((a, b) => middle(a.source) - middle(b.source))) {\n hop.y1 = y + (hop.flow.value * ky) / 2\n y += hop.flow.value * ky\n }\n }\n}\n\n/**\n * The pieces of the chart: a box per node, a flat slice of its flow per\n * waypoint and `SEGMENTS` slices per hop. The columns stand from x = -1 to 1,\n * a node the share `nodeWidth` of the distance between two of them wide.\n */\nfunction sankeyPieces(nodes: SankeyNode[], columns: number, ky: number, nodeWidth: number) {\n const step = 2 / Math.max(1, columns - 1)\n const x = (column: number) => -1 + column * step\n const half = (nodeWidth * step) / 2\n // How far a flow has come at `at`, from the side of its source to the side of its target, in percent.\n const share = (flow: Flow, at: number) => {\n const from = x(flow.source.column) + half\n return Math.round(((at - from) / (x(flow.target.column) - half - from)) * 100)\n }\n\n return nodes.flatMap((node): Piece[] => {\n const band = x(node.column)\n const own = node.flow\n ? { key: `way:${node.id}`, band, flow: node.flow, t: share(node.flow, band) }\n : { key: `node:${node.id}`, band, node, t: 0 }\n const depth = node.flow ? RIBBON * DEPTH : DEPTH\n const box = slab(band - half, middle(node), band + half, middle(node), node.y1 - node.y0, depth)\n return [\n Object.assign(box, own),\n ...node.outgoing.flatMap((hop) =>\n hopPieces(hop, band + half, x(node.column + 1) - half, ky, share)\n ),\n ]\n })\n}\n\n/**\n * The slices of a hop from `x0` to `x1`, along an S-curve that leaves and\n * arrives level, so that the hops of a flow join without a kink. Every hop\n * between the same two columns has the same slices.\n */\nfunction hopPieces(\n hop: Hop,\n x0: number,\n x1: number,\n ky: number,\n share: (flow: Flow, at: number) => number\n): Piece[] {\n const x = (k: number) => x0 + ((x1 - x0) * k) / SEGMENTS\n const y = (k: number) => {\n const t = k / SEGMENTS\n return hop.y0 + (hop.y1 - hop.y0) * t * t * (3 - 2 * t)\n }\n return Array.from({ length: SEGMENTS }, (_, k) => {\n const band = (x(k) + x(k + 1)) / 2\n return Object.assign(\n slab(x(k), y(k), x(k + 1), y(k + 1), hop.flow.value * ky, RIBBON * DEPTH),\n {\n key: `hop:${hop.flow.key}:${hop.source.column}:${k}`,\n band,\n flow: hop.flow,\n t: share(hop.flow, band),\n }\n )\n })\n}\n\n/** The corners of a slab from `x0` to `x1`, its middle at `y0` and `y1`, `height` high and `depth` deep, in the order of box(). */\nfunction slab(x0: number, y0: number, x1: number, y1: number, height: number, depth: number) {\n return [depth / 2, -depth / 2].flatMap((z) => [\n { x: x0, y: y0 - height / 2, z },\n { x: x0, y: y0 + height / 2, z },\n { x: x1, y: y1 + height / 2, z },\n { x: x1, y: y1 - height / 2, z },\n ])\n}\n\n/**\n * The pieces in the order to paint them. The slabs of the columns and of the\n * slices lie between planes x = const, so they paint from the slab farthest\n * from the eye to the nearest, which is exact; within a slab, where the pieces\n * lie one above the other, by depth.\n */\nfunction depthSort<P extends { band: number; centroid: Point3D }>(\n pieces: P[],\n camera: Camera3DInstance\n) {\n const { x: vx } = camera.viewDirection()\n // The x of the eye: ±Infinity for an orthographic camera, NaN for one that looks along the planes.\n const eye = vx * camera.perspective()\n const far = (x: number) => (Number.isFinite(eye) ? Math.abs(x - eye) : -x * Math.sign(vx))\n // ponytail: two hops that cross between the same columns pass through each other, the upper\n // in front; a lane in z per source would part them if the order of stackColumns leaves many.\n return pieces.sort((a, b) => far(b.band) - far(a.band) || sort(a, b))\n}\n\n/**\n * The label of a node beside its `outer` face, pushed away from the other side,\n * so that it stays outside whichever way the camera turns.\n */\nfunction nodeLabel(\n key: string,\n faces: CubeFace[],\n outer: \"left\" | \"right\",\n text: React.ReactNode\n): Label3D {\n const side = (name: \"left\" | \"right\") => center(faces.find((face) => face.face === name)!)\n const at = side(outer)\n const away =\n Math.sign(at.x - side(outer === \"left\" ? \"right\" : \"left\").x) || (outer === \"left\" ? -1 : 1)\n return { key, at: { x: at.x + 8 * away, y: at.y }, text, anchor: away < 0 ? \"end\" : \"start\" }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@niekes/charts-3d",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "3D charts for shadcn/ui, built on d3-3d. A shadcn registry and an npm package from the same source.",
5
5
  "keywords": [
6
6
  "3D",
@@ -16,13 +16,13 @@
16
16
  ],
17
17
  "homepage": "https://niekes.github.io/charts-3d/",
18
18
  "bugs": {
19
- "url": "https://github.com/niekes/charts-3d/issues"
19
+ "url": "https://github.com/Niekes/charts-3d/issues"
20
20
  },
21
21
  "license": "BSD-3-Clause",
22
22
  "author": "Niekes (https://niekes.com)",
23
23
  "repository": {
24
24
  "type": "git",
25
- "url": "git+https://github.com/niekes/charts-3d.git"
25
+ "url": "git+https://github.com/Niekes/charts-3d.git"
26
26
  },
27
27
  "funding": {
28
28
  "type": "github",