@voila.dev/chart 5.0.110 → 5.0.111

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
@@ -41,7 +41,7 @@ hit-testing, keyboard focus, the tooltip and the data table read it.
41
41
 
42
42
  ## Marks
43
43
 
44
- Cartesian: `lineY` `lineX` `areaY` `areaX` `barY` `barX` `dot` `cell` `ruleY` `ruleX` `text`
44
+ Cartesian: `lineY` `lineX` `areaY` `areaX` (a band with `from`/`to`) `barY` `barX` `dot` `cell` `ruleY` `ruleX` `tickY` `tickX` `text`
45
45
 
46
46
  Round and frame: `arc` `donut` `radialBar` `radar` `funnel`
47
47
 
@@ -0,0 +1,15 @@
1
+ import { type ChartMarkOptions } from "./shared.js";
2
+ import type { ChartAccessor, ChartMark, ChartValue } from "../types.js";
3
+ export interface TickOptions<TDatum> extends ChartMarkOptions<TDatum> {
4
+ readonly x?: ChartAccessor<TDatum, ChartValue>;
5
+ readonly y?: ChartAccessor<TDatum, ChartValue>;
6
+ readonly stroke?: string;
7
+ readonly strokeWidth?: number;
8
+ /** The tick's length in pixels where the cross axis is continuous; on a band it spans the band. */
9
+ readonly length?: number;
10
+ }
11
+ /** A short vertical stroke at each x: a strip plot, a rug, one value per category band. */
12
+ export declare function tickX<TDatum>(data: ReadonlyArray<TDatum>, options?: TickOptions<TDatum>): ChartMark;
13
+ /** A short horizontal stroke at each y: a target per bar, a median per category. */
14
+ export declare function tickY<TDatum>(data: ReadonlyArray<TDatum>, options?: TickOptions<TDatum>): ChartMark;
15
+ //# sourceMappingURL=tick.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tick.d.ts","sourceRoot":"","sources":["../../../src/core/marks/tick.ts"],"names":[],"mappings":"AACA,OAAO,EACN,KAAK,gBAAgB,EAOrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EACX,aAAa,EACb,SAAS,EAGT,UAAU,EAEV,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,WAAW,CAAC,MAAM,CAAE,SAAQ,gBAAgB,CAAC,MAAM,CAAC;IACpE,QAAQ,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC/C,QAAQ,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC/C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,mGAAmG;IACnG,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB;AAoJD,2FAA2F;AAC3F,wBAAgB,KAAK,CAAC,MAAM,EAC3B,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAC3B,OAAO,GAAE,WAAW,CAAC,MAAM,CAAM,GAC/B,SAAS,CAEX;AAED,oFAAoF;AACpF,wBAAgB,KAAK,CAAC,MAAM,EAC3B,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAC3B,OAAO,GAAE,WAAW,CAAC,MAAM,CAAM,GAC/B,SAAS,CAEX"}
@@ -0,0 +1,136 @@
1
+ import { channelLabel, isPlaceable, readChannel } from "../channel.js";
2
+ import {
3
+ colorChannel,
4
+ markId,
5
+ paint,
6
+ seriesResolver,
7
+ tips,
8
+ valueOn
9
+ } from "./shared.js";
10
+ const DEFAULT_LENGTH = 12;
11
+ const DEFAULT_STROKE_WIDTH = 2;
12
+ function span(scale, value, length, plot) {
13
+ if (scale === void 0 || value === void 0) {
14
+ return [plot.start, plot.start + plot.size];
15
+ }
16
+ if (scale.kind === "band") {
17
+ const start = scale.map(value);
18
+ return [start, start + scale.bandwidth];
19
+ }
20
+ const center = scale.center(value);
21
+ return [center - length / 2, center + length / 2];
22
+ }
23
+ function tickMark(kind, data, options) {
24
+ const vertical = kind === "tickX";
25
+ const xs = readChannel(data, options.x);
26
+ const ys = readChannel(data, options.y);
27
+ const colors = colorChannel(data, options);
28
+ const valueKey = vertical ? options.x : options.y;
29
+ const crossKey = vertical ? options.y : options.x;
30
+ const values = vertical ? xs : ys;
31
+ const crosses = vertical ? ys : xs;
32
+ return {
33
+ id: options.id,
34
+ coordinate: "cartesian",
35
+ focusOrder: "point",
36
+ channels: {
37
+ [vertical ? "x" : "y"]: {
38
+ values: values.filter(isPlaceable),
39
+ label: channelLabel(valueKey) ?? options.label
40
+ },
41
+ ...crossKey === void 0 ? {} : {
42
+ [vertical ? "y" : "x"]: {
43
+ values: crosses.filter(isPlaceable),
44
+ label: channelLabel(crossKey),
45
+ discrete: "band"
46
+ }
47
+ },
48
+ color: colors.channel
49
+ },
50
+ render(context) {
51
+ const id = markId(options.id, kind, context);
52
+ const valueScale = vertical ? context.scales.x : context.scales.y;
53
+ const crossScale = vertical ? context.scales.y : context.scales.x;
54
+ if (valueScale === void 0) {
55
+ return { nodes: [] };
56
+ }
57
+ const { plot } = context;
58
+ const seriesOf = seriesResolver(
59
+ colors.values,
60
+ { ...options, fixedColor: options.stroke },
61
+ id,
62
+ context
63
+ );
64
+ const length = options.length ?? DEFAULT_LENGTH;
65
+ const strokeWidth = options.strokeWidth ?? DEFAULT_STROKE_WIDTH;
66
+ const nodes = [];
67
+ const points = [];
68
+ for (const [index, datum] of data.entries()) {
69
+ const value = valueOn(valueScale, values[index]);
70
+ if (value === void 0) {
71
+ continue;
72
+ }
73
+ const cross = crossKey === void 0 || crossScale === void 0 ? void 0 : valueOn(crossScale, crosses[index]);
74
+ if (crossKey !== void 0 && cross === void 0) {
75
+ continue;
76
+ }
77
+ const at = valueScale.center(value);
78
+ const [from, to] = span(
79
+ crossKey === void 0 ? void 0 : crossScale,
80
+ cross,
81
+ length,
82
+ vertical ? { start: plot.y, size: plot.height } : { start: plot.x, size: plot.width }
83
+ );
84
+ const series = seriesOf(index);
85
+ nodes.push({
86
+ kind: "line",
87
+ key: `${id}:${index}`,
88
+ series: series.key,
89
+ role: "mark",
90
+ ...vertical ? { x1: at, y1: from, x2: at, y2: to } : { x1: from, y1: at, x2: to, y2: at },
91
+ paint: paint({
92
+ stroke: series.color,
93
+ strokeWidth,
94
+ opacity: options.opacity
95
+ })
96
+ });
97
+ if (tips(options.tip, datum, index)) {
98
+ const middle = (from + to) / 2;
99
+ const xValue = vertical ? value : cross;
100
+ const yValue = vertical ? cross : value;
101
+ points.push({
102
+ key: `${id}:${index}`,
103
+ markId: id,
104
+ index,
105
+ datum,
106
+ x: vertical ? at : middle,
107
+ y: vertical ? middle : at,
108
+ xValue,
109
+ yValue,
110
+ series: series.key,
111
+ seriesLabel: series.label,
112
+ color: series.color,
113
+ title: cross === void 0 ? series.label ?? "" : vertical ? context.formatY(cross) : context.formatX(cross),
114
+ value: vertical ? context.formatX(value) : context.formatY(value),
115
+ hit: {
116
+ kind: "rect",
117
+ rect: vertical ? { x: at - 4, y: from, width: 8, height: to - from } : { x: from, y: at - 4, width: to - from, height: 8 }
118
+ }
119
+ });
120
+ }
121
+ }
122
+ return { nodes, points };
123
+ }
124
+ };
125
+ }
126
+ function tickX(data, options = {}) {
127
+ return tickMark("tickX", data, options);
128
+ }
129
+ function tickY(data, options = {}) {
130
+ return tickMark("tickY", data, options);
131
+ }
132
+ export {
133
+ tickX,
134
+ tickY
135
+ };
136
+ //# sourceMappingURL=tick.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/core/marks/tick.ts"],
4
+ "sourcesContent": ["import { channelLabel, isPlaceable, readChannel } from \"#/core/channel.ts\";\nimport {\n\ttype ChartMarkOptions,\n\tcolorChannel,\n\tmarkId,\n\tpaint,\n\tseriesResolver,\n\ttips,\n\tvalueOn,\n} from \"#/core/marks/shared.ts\";\nimport type {\n\tChartAccessor,\n\tChartMark,\n\tChartPoint,\n\tChartPositionScale,\n\tChartValue,\n\tSceneNode,\n} from \"#/core/types.ts\";\n\nexport interface TickOptions<TDatum> extends ChartMarkOptions<TDatum> {\n\treadonly x?: ChartAccessor<TDatum, ChartValue>;\n\treadonly y?: ChartAccessor<TDatum, ChartValue>;\n\treadonly stroke?: string;\n\treadonly strokeWidth?: number;\n\t/** The tick's length in pixels where the cross axis is continuous; on a band it spans the band. */\n\treadonly length?: number;\n}\n\nconst DEFAULT_LENGTH = 12;\nconst DEFAULT_STROKE_WIDTH = 2;\n\n/** Where the tick spans on the cross axis: the band, or `length` around the value. */\nfunction span(\n\tscale: ChartPositionScale | undefined,\n\tvalue: ChartValue | undefined,\n\tlength: number,\n\tplot: { readonly start: number; readonly size: number },\n): readonly [number, number] {\n\tif (scale === undefined || value === undefined) {\n\t\treturn [plot.start, plot.start + plot.size];\n\t}\n\tif (scale.kind === \"band\") {\n\t\tconst start = scale.map(value);\n\t\treturn [start, start + scale.bandwidth];\n\t}\n\tconst center = scale.center(value);\n\treturn [center - length / 2, center + length / 2];\n}\n\nfunction tickMark<TDatum>(\n\tkind: \"tickX\" | \"tickY\",\n\tdata: ReadonlyArray<TDatum>,\n\toptions: TickOptions<TDatum>,\n): ChartMark {\n\tconst vertical = kind === \"tickX\";\n\tconst xs = readChannel(data, options.x);\n\tconst ys = readChannel(data, options.y);\n\tconst colors = colorChannel(data, options);\n\tconst valueKey = vertical ? options.x : options.y;\n\tconst crossKey = vertical ? options.y : options.x;\n\tconst values = vertical ? xs : ys;\n\tconst crosses = vertical ? ys : xs;\n\n\treturn {\n\t\tid: options.id,\n\t\tcoordinate: \"cartesian\",\n\t\tfocusOrder: \"point\",\n\t\tchannels: {\n\t\t\t[vertical ? \"x\" : \"y\"]: {\n\t\t\t\tvalues: values.filter(isPlaceable),\n\t\t\t\tlabel: channelLabel(valueKey) ?? options.label,\n\t\t\t},\n\t\t\t...(crossKey === undefined\n\t\t\t\t? {}\n\t\t\t\t: {\n\t\t\t\t\t\t[vertical ? \"y\" : \"x\"]: {\n\t\t\t\t\t\t\tvalues: crosses.filter(isPlaceable),\n\t\t\t\t\t\t\tlabel: channelLabel(crossKey),\n\t\t\t\t\t\t\tdiscrete: \"band\" as const,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\tcolor: colors.channel,\n\t\t},\n\t\trender(context) {\n\t\t\tconst id = markId(options.id, kind, context);\n\t\t\tconst valueScale = vertical ? context.scales.x : context.scales.y;\n\t\t\tconst crossScale = vertical ? context.scales.y : context.scales.x;\n\t\t\tif (valueScale === undefined) {\n\t\t\t\treturn { nodes: [] };\n\t\t\t}\n\t\t\tconst { plot } = context;\n\t\t\tconst seriesOf = seriesResolver(\n\t\t\t\tcolors.values,\n\t\t\t\t{ ...options, fixedColor: options.stroke },\n\t\t\t\tid,\n\t\t\t\tcontext,\n\t\t\t);\n\t\t\tconst length = options.length ?? DEFAULT_LENGTH;\n\t\t\tconst strokeWidth = options.strokeWidth ?? DEFAULT_STROKE_WIDTH;\n\t\t\tconst nodes: SceneNode[] = [];\n\t\t\tconst points: ChartPoint[] = [];\n\t\t\tfor (const [index, datum] of data.entries()) {\n\t\t\t\tconst value = valueOn(valueScale, values[index]);\n\t\t\t\tif (value === undefined) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst cross =\n\t\t\t\t\tcrossKey === undefined || crossScale === undefined\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: valueOn(crossScale, crosses[index]);\n\t\t\t\tif (crossKey !== undefined && cross === undefined) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst at = valueScale.center(value);\n\t\t\t\tconst [from, to] = span(\n\t\t\t\t\tcrossKey === undefined ? undefined : crossScale,\n\t\t\t\t\tcross,\n\t\t\t\t\tlength,\n\t\t\t\t\tvertical\n\t\t\t\t\t\t? { start: plot.y, size: plot.height }\n\t\t\t\t\t\t: { start: plot.x, size: plot.width },\n\t\t\t\t);\n\t\t\t\tconst series = seriesOf(index);\n\t\t\t\tnodes.push({\n\t\t\t\t\tkind: \"line\",\n\t\t\t\t\tkey: `${id}:${index}`,\n\t\t\t\t\tseries: series.key,\n\t\t\t\t\trole: \"mark\",\n\t\t\t\t\t...(vertical\n\t\t\t\t\t\t? { x1: at, y1: from, x2: at, y2: to }\n\t\t\t\t\t\t: { x1: from, y1: at, x2: to, y2: at }),\n\t\t\t\t\tpaint: paint({\n\t\t\t\t\t\tstroke: series.color,\n\t\t\t\t\t\tstrokeWidth,\n\t\t\t\t\t\topacity: options.opacity,\n\t\t\t\t\t}),\n\t\t\t\t});\n\t\t\t\tif (tips(options.tip, datum, index)) {\n\t\t\t\t\tconst middle = (from + to) / 2;\n\t\t\t\t\tconst xValue = vertical ? value : cross;\n\t\t\t\t\tconst yValue = vertical ? cross : value;\n\t\t\t\t\tpoints.push({\n\t\t\t\t\t\tkey: `${id}:${index}`,\n\t\t\t\t\t\tmarkId: id,\n\t\t\t\t\t\tindex,\n\t\t\t\t\t\tdatum,\n\t\t\t\t\t\tx: vertical ? at : middle,\n\t\t\t\t\t\ty: vertical ? middle : at,\n\t\t\t\t\t\txValue,\n\t\t\t\t\t\tyValue,\n\t\t\t\t\t\tseries: series.key,\n\t\t\t\t\t\tseriesLabel: series.label,\n\t\t\t\t\t\tcolor: series.color,\n\t\t\t\t\t\ttitle:\n\t\t\t\t\t\t\tcross === undefined\n\t\t\t\t\t\t\t\t? (series.label ?? \"\")\n\t\t\t\t\t\t\t\t: vertical\n\t\t\t\t\t\t\t\t\t? context.formatY(cross)\n\t\t\t\t\t\t\t\t\t: context.formatX(cross),\n\t\t\t\t\t\tvalue: vertical ? context.formatX(value) : context.formatY(value),\n\t\t\t\t\t\thit: {\n\t\t\t\t\t\t\tkind: \"rect\",\n\t\t\t\t\t\t\trect: vertical\n\t\t\t\t\t\t\t\t? { x: at - 4, y: from, width: 8, height: to - from }\n\t\t\t\t\t\t\t\t: { x: from, y: at - 4, width: to - from, height: 8 },\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { nodes, points };\n\t\t},\n\t};\n}\n\n/** A short vertical stroke at each x: a strip plot, a rug, one value per category band. */\nexport function tickX<TDatum>(\n\tdata: ReadonlyArray<TDatum>,\n\toptions: TickOptions<TDatum> = {},\n): ChartMark {\n\treturn tickMark(\"tickX\", data, options);\n}\n\n/** A short horizontal stroke at each y: a target per bar, a median per category. */\nexport function tickY<TDatum>(\n\tdata: ReadonlyArray<TDatum>,\n\toptions: TickOptions<TDatum> = {},\n): ChartMark {\n\treturn tickMark(\"tickY\", data, options);\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"hierarchy.d.ts","sourceRoot":"","sources":["../../src/hierarchy/hierarchy.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACX,SAAS,EAIT,MAAM,iBAAiB,CAAC;AAEzB;;;;;GAKG;AAEH,MAAM,WAAW,gBAAgB,CAAC,KAAK;IACtC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,aAAa,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC;IACtE,gDAAgD;IAChD,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,MAAM,CAAC;IACzC,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,MAAM,CAAC;IACvC,kDAAkD;IAClD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CACvB;AAiGD,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,KAAK,EAChC,IAAI,EAAE,KAAK,EACX,OAAO,EAAE,gBAAgB,CAAC,KAAK,CAAC,GAC9B,SAAS,CA+EX;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,KAAK,EACjC,IAAI,EAAE,KAAK,EACX,OAAO,EAAE,gBAAgB,CAAC,KAAK,CAAC,GAC9B,SAAS,CA8DX;AAKD,iFAAiF;AACjF,wBAAgB,QAAQ,CAAC,KAAK,EAC7B,IAAI,EAAE,KAAK,EACX,OAAO,EAAE,gBAAgB,CAAC,KAAK,CAAC,GAC9B,SAAS,CAqFX"}
1
+ {"version":3,"file":"hierarchy.d.ts","sourceRoot":"","sources":["../../src/hierarchy/hierarchy.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACX,SAAS,EAIT,MAAM,iBAAiB,CAAC;AAEzB;;;;;GAKG;AAEH,MAAM,WAAW,gBAAgB,CAAC,KAAK;IACtC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,aAAa,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC;IACtE,gDAAgD;IAChD,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,MAAM,CAAC;IACzC,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,MAAM,CAAC;IACvC,kDAAkD;IAClD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CACvB;AAiGD,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,KAAK,EAChC,IAAI,EAAE,KAAK,EACX,OAAO,EAAE,gBAAgB,CAAC,KAAK,CAAC,GAC9B,SAAS,CA+EX;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,KAAK,EACjC,IAAI,EAAE,KAAK,EACX,OAAO,EAAE,gBAAgB,CAAC,KAAK,CAAC,GAC9B,SAAS,CA8DX;AAKD,iFAAiF;AACjF,wBAAgB,QAAQ,CAAC,KAAK,EAC7B,IAAI,EAAE,KAAK,EACX,OAAO,EAAE,gBAAgB,CAAC,KAAK,CAAC,GAC9B,SAAS,CAwFX"}
@@ -206,15 +206,16 @@ function treeMark(root, options) {
206
206
  0,
207
207
  ...rootNode.leaves().map((leaf) => measureText(options.name(leaf.data), theme.fontSize))
208
208
  );
209
+ const rootLabel = measureText(options.name(rootNode.data), theme.fontSize) + LABEL_GAP;
209
210
  const width = Math.max(
210
211
  0,
211
- plot.width - leafLabel - LABEL_GAP - NODE_RADIUS * 2
212
+ plot.width - rootLabel - leafLabel - LABEL_GAP - NODE_RADIUS * 2
212
213
  );
213
214
  const laid = tree().size([plot.height, width])(
214
215
  rootNode
215
216
  );
216
217
  const position = (node) => ({
217
- x: plot.x + NODE_RADIUS + (node.y ?? 0),
218
+ x: plot.x + rootLabel + NODE_RADIUS + (node.y ?? 0),
218
219
  y: plot.y + (node.x ?? 0)
219
220
  });
220
221
  const nodes = [];
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/hierarchy/hierarchy.ts"],
4
- "sourcesContent": ["import {\n\ttype HierarchyNode,\n\ttype HierarchyRectangularNode,\n\thierarchy,\n\tpartition,\n\ttree,\n\ttreemap,\n} from \"d3-hierarchy\";\nimport { truncate } from \"#/core/guides/axes.ts\";\nimport { markId } from \"#/core/marks/shared.ts\";\nimport { arcPath, polarToCartesian } from \"#/core/paths.ts\";\nimport type {\n\tChartMark,\n\tChartMarkContext,\n\tChartPoint,\n\tSceneNode,\n} from \"#/core/types.ts\";\n\n/**\n * Nested data: a treemap, a sunburst, a tree. Each takes one root and reads\n * its children, value and name through accessors, so any nested shape works.\n * Colours follow the root's direct children (the branches the legend lists);\n * deeper levels keep their branch's colour.\n */\n\nexport interface HierarchyOptions<TNode> {\n\treadonly id?: string;\n\treadonly children?: (node: TNode) => ReadonlyArray<TNode> | undefined;\n\t/** A leaf's size. Branches sum their leaves. */\n\treadonly value?: (node: TNode) => number;\n\treadonly name: (node: TNode) => string;\n\t/** What the values are, for the table heading. */\n\treadonly label?: string;\n\treadonly tip?: boolean;\n}\n\nfunction rooted<TNode>(\n\troot: TNode,\n\toptions: HierarchyOptions<TNode>,\n): HierarchyNode<TNode> {\n\tconst node = hierarchy(\n\t\troot,\n\t\t(datum) =>\n\t\t\t(options.children?.(datum) as TNode[] | undefined) ??\n\t\t\t(datum as { children?: TNode[] }).children,\n\t);\n\treturn node\n\t\t.sum((datum) => (options.value ? Math.max(0, options.value(datum)) : 0))\n\t\t.sort((left, right) => (right.value ?? 0) - (left.value ?? 0));\n}\n\nfunction branchOf<TNode>(node: HierarchyNode<TNode>): HierarchyNode<TNode> {\n\tlet current = node;\n\twhile (current.parent?.parent) {\n\t\tcurrent = current.parent;\n\t}\n\treturn current;\n}\n\nfunction pathName<TNode>(\n\tnode: HierarchyNode<TNode>,\n\toptions: HierarchyOptions<TNode>,\n): string {\n\treturn node\n\t\t.ancestors()\n\t\t.reverse()\n\t\t.slice(1)\n\t\t.map((ancestor) => options.name(ancestor.data))\n\t\t.join(\" \u203A \");\n}\n\nfunction branchNames<TNode>(\n\troot: TNode,\n\toptions: HierarchyOptions<TNode>,\n): string[] {\n\treturn (rooted(root, options).children ?? []).map((child) =>\n\t\toptions.name(child.data),\n\t);\n}\n\nfunction nodeKey<TNode>(\n\tnode: HierarchyNode<TNode>,\n\toptions: HierarchyOptions<TNode>,\n): string {\n\treturn node\n\t\t.ancestors()\n\t\t.map((ancestor) => options.name(ancestor.data))\n\t\t.reverse()\n\t\t.join(\"/\");\n}\n\nfunction colorFor<TNode>(\n\tnode: HierarchyNode<TNode>,\n\toptions: HierarchyOptions<TNode>,\n\tcontext: ChartMarkContext,\n): string {\n\treturn node.depth === 0\n\t\t? context.theme.muted\n\t\t: context.colorOf(options.name(branchOf(node).data));\n}\n\nfunction pointFor<TNode>(\n\tid: string,\n\tnode: HierarchyNode<TNode>,\n\toptions: HierarchyOptions<TNode>,\n\tcontext: ChartMarkContext,\n\tindex: number,\n\tat: { x: number; y: number },\n\thit?: ChartPoint[\"hit\"],\n): ChartPoint {\n\treturn {\n\t\tkey: `${id}:${nodeKey(node, options)}`,\n\t\tmarkId: id,\n\t\tindex,\n\t\tdatum: node.data,\n\t\tx: at.x,\n\t\ty: at.y,\n\t\txValue: options.name(node.data),\n\t\tyValue: node.value ?? 0,\n\t\tseries: options.name(branchOf(node).data),\n\t\tseriesLabel: options.label,\n\t\tcolor: colorFor(node, options, context),\n\t\ttitle: pathName(node, options) || options.name(node.data),\n\t\tvalue: context.formatY(node.value ?? 0),\n\t\thit,\n\t};\n}\n\nconst LABEL_PADDING = 4;\nconst MIN_LABEL_HEIGHT = 16;\n\n/** Rectangles nested inside rectangles, each leaf as large as its value. */\nexport function treemapMark<TNode>(\n\troot: TNode,\n\toptions: HierarchyOptions<TNode>,\n): ChartMark {\n\treturn {\n\t\tid: options.id,\n\t\tcoordinate: \"frame\",\n\t\tfocusOrder: \"point\",\n\t\ttitles: { value: options.label },\n\t\tchannels: { color: { values: branchNames(root, options) } },\n\t\trender(context) {\n\t\t\tconst id = markId(options.id, \"treemap\", context);\n\t\t\tconst { plot, theme } = context;\n\t\t\tconst laid = treemap<TNode>()\n\t\t\t\t.size([plot.width, plot.height])\n\t\t\t\t.paddingInner(2)\n\t\t\t\t.round(true)(rooted(root, options));\n\t\t\tconst nodes: SceneNode[] = [];\n\t\t\tconst points: ChartPoint[] = [];\n\t\t\tfor (const [index, leaf] of laid.leaves().entries()) {\n\t\t\t\tconst rect = {\n\t\t\t\t\tx: plot.x + leaf.x0,\n\t\t\t\t\ty: plot.y + leaf.y0,\n\t\t\t\t\twidth: Math.max(0, leaf.x1 - leaf.x0),\n\t\t\t\t\theight: Math.max(0, leaf.y1 - leaf.y0),\n\t\t\t\t};\n\t\t\t\tconst key = nodeKey(leaf, options);\n\t\t\t\tconst series = options.name(branchOf(leaf).data);\n\t\t\t\tnodes.push({\n\t\t\t\t\tkind: \"rect\",\n\t\t\t\t\tkey: `${id}:${key}`,\n\t\t\t\t\tseries,\n\t\t\t\t\trole: \"mark\",\n\t\t\t\t\t...rect,\n\t\t\t\t\tcorners: [3, 3, 3, 3],\n\t\t\t\t\tpaint: {\n\t\t\t\t\t\tfill: colorFor(leaf, options, context),\n\t\t\t\t\t\tfillOpacity: 0.55 + 0.45 / leaf.depth,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tif (\n\t\t\t\t\trect.height >= MIN_LABEL_HEIGHT &&\n\t\t\t\t\trect.width > theme.fontSize * 2\n\t\t\t\t) {\n\t\t\t\t\tnodes.push({\n\t\t\t\t\t\tkind: \"text\",\n\t\t\t\t\t\tkey: `${id}:label:${key}`,\n\t\t\t\t\t\tseries,\n\t\t\t\t\t\trole: \"label\",\n\t\t\t\t\t\tx: rect.x + LABEL_PADDING,\n\t\t\t\t\t\ty: rect.y + LABEL_PADDING,\n\t\t\t\t\t\ttext: truncate(\n\t\t\t\t\t\t\toptions.name(leaf.data),\n\t\t\t\t\t\t\trect.width - LABEL_PADDING * 2,\n\t\t\t\t\t\t\ttheme.fontSize,\n\t\t\t\t\t\t\tcontext.measureText,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tpaint: {\n\t\t\t\t\t\t\tfill: theme.background,\n\t\t\t\t\t\t\tfontSize: theme.fontSize,\n\t\t\t\t\t\t\tfontWeight: 600,\n\t\t\t\t\t\t\tbaseline: \"top\",\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (options.tip !== false) {\n\t\t\t\t\tpoints.push(\n\t\t\t\t\t\tpointFor(\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\tleaf,\n\t\t\t\t\t\t\toptions,\n\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\tindex,\n\t\t\t\t\t\t\t{ x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 },\n\t\t\t\t\t\t\t{ kind: \"rect\", rect },\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { nodes, points };\n\t\t},\n\t};\n}\n\n/** Rings of arcs: the root's children on the first ring, theirs on the next. */\nexport function sunburstMark<TNode>(\n\troot: TNode,\n\toptions: HierarchyOptions<TNode>,\n): ChartMark {\n\treturn {\n\t\tid: options.id,\n\t\tcoordinate: \"frame\",\n\t\tfocusOrder: \"point\",\n\t\ttitles: { value: options.label },\n\t\tchannels: { color: { values: branchNames(root, options) } },\n\t\trender(context) {\n\t\t\tconst id = markId(options.id, \"sunburst\", context);\n\t\t\tconst { plot } = context;\n\t\t\tconst radius = Math.max(0, Math.min(plot.width, plot.height) / 2 - 4);\n\t\t\tconst cx = plot.x + plot.width / 2;\n\t\t\tconst cy = plot.y + plot.height / 2;\n\t\t\tconst laid = partition<TNode>().size([360, radius])(\n\t\t\t\trooted(root, options),\n\t\t\t);\n\t\t\t// The root's ring would be a disc in the middle: leave it empty.\n\t\t\tconst ringWidth = radius / (laid.height + 1);\n\t\t\tconst nodes: SceneNode[] = [];\n\t\t\tconst points: ChartPoint[] = [];\n\t\t\tconst descendants = laid\n\t\t\t\t.descendants()\n\t\t\t\t.filter((node) => node.depth > 0 && node.x1 - node.x0 > 0.2);\n\t\t\tfor (const [index, node] of descendants.entries()) {\n\t\t\t\tconst shape = {\n\t\t\t\t\tcx,\n\t\t\t\t\tcy,\n\t\t\t\t\tinnerRadius: node.depth * ringWidth,\n\t\t\t\t\touterRadius: (node.depth + 1) * ringWidth - 1,\n\t\t\t\t\tstartAngle: node.x0 + 0.25,\n\t\t\t\t\tendAngle: node.x1 - 0.25,\n\t\t\t\t};\n\t\t\t\tconst series = options.name(branchOf(node).data);\n\t\t\t\tnodes.push({\n\t\t\t\t\tkind: \"path\",\n\t\t\t\t\tkey: `${id}:${nodeKey(node, options)}`,\n\t\t\t\t\tseries,\n\t\t\t\t\trole: \"mark\",\n\t\t\t\t\td: arcPath(shape),\n\t\t\t\t\tpaint: {\n\t\t\t\t\t\tfill: colorFor(node, options, context),\n\t\t\t\t\t\tfillOpacity: 1 - (node.depth - 1) * 0.18,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tif (options.tip !== false) {\n\t\t\t\t\tconst anchor = polarToCartesian(\n\t\t\t\t\t\tcx,\n\t\t\t\t\t\tcy,\n\t\t\t\t\t\t(shape.innerRadius + shape.outerRadius) / 2,\n\t\t\t\t\t\t(shape.startAngle + shape.endAngle) / 2,\n\t\t\t\t\t);\n\t\t\t\t\tpoints.push(\n\t\t\t\t\t\tpointFor(id, node, options, context, index, anchor, {\n\t\t\t\t\t\t\tkind: \"arc\",\n\t\t\t\t\t\t\t...shape,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { nodes, points };\n\t\t},\n\t};\n}\n\nconst NODE_RADIUS = 4;\nconst LABEL_GAP = 6;\n\n/** A node-link tree, root on the left, leaves on the right, every node named. */\nexport function treeMark<TNode>(\n\troot: TNode,\n\toptions: HierarchyOptions<TNode>,\n): ChartMark {\n\treturn {\n\t\tid: options.id,\n\t\tcoordinate: \"frame\",\n\t\tfocusOrder: \"point\",\n\t\ttitles: { value: options.label },\n\t\tchannels: {},\n\t\trender(context) {\n\t\t\tconst id = markId(options.id, \"tree\", context);\n\t\t\tconst { plot, theme, measureText } = context;\n\t\t\tconst rootNode = rooted(root, options);\n\t\t\tconst leafLabel = Math.max(\n\t\t\t\t0,\n\t\t\t\t...rootNode\n\t\t\t\t\t.leaves()\n\t\t\t\t\t.map((leaf) => measureText(options.name(leaf.data), theme.fontSize)),\n\t\t\t);\n\t\t\tconst width = Math.max(\n\t\t\t\t0,\n\t\t\t\tplot.width - leafLabel - LABEL_GAP - NODE_RADIUS * 2,\n\t\t\t);\n\t\t\tconst laid = tree<TNode>().size([plot.height, width])(\n\t\t\t\trootNode,\n\t\t\t) as HierarchyRectangularNode<TNode> & HierarchyNode<TNode>;\n\t\t\tconst position = (\n\t\t\t\tnode: HierarchyNode<TNode> & { x?: number; y?: number },\n\t\t\t) => ({\n\t\t\t\tx: plot.x + NODE_RADIUS + (node.y ?? 0),\n\t\t\t\ty: plot.y + (node.x ?? 0),\n\t\t\t});\n\t\t\tconst nodes: SceneNode[] = [];\n\t\t\tconst points: ChartPoint[] = [];\n\t\t\tfor (const link of laid.links()) {\n\t\t\t\tconst from = position(link.source);\n\t\t\t\tconst to = position(link.target);\n\t\t\t\tconst middle = (from.x + to.x) / 2;\n\t\t\t\tnodes.push({\n\t\t\t\t\tkind: \"path\",\n\t\t\t\t\tkey: `${id}:link:${nodeKey(link.target, options)}`,\n\t\t\t\t\trole: \"grid\",\n\t\t\t\t\td: `M${from.x},${from.y}C${middle},${from.y} ${middle},${to.y} ${to.x},${to.y}`,\n\t\t\t\t\tpaint: { fill: \"none\", stroke: theme.grid, strokeWidth: 1.5 },\n\t\t\t\t});\n\t\t\t}\n\t\t\tfor (const [index, node] of laid.descendants().entries()) {\n\t\t\t\tconst at = position(node);\n\t\t\t\tconst leaf = node.children === undefined;\n\t\t\t\tnodes.push(\n\t\t\t\t\t{\n\t\t\t\t\t\tkind: \"circle\",\n\t\t\t\t\t\tkey: `${id}:${nodeKey(node, options)}`,\n\t\t\t\t\t\trole: \"mark\",\n\t\t\t\t\t\tcx: at.x,\n\t\t\t\t\t\tcy: at.y,\n\t\t\t\t\t\tr: NODE_RADIUS,\n\t\t\t\t\t\tpaint: {\n\t\t\t\t\t\t\tfill: leaf ? theme.background : colorFor(node, options, context),\n\t\t\t\t\t\t\tstroke: colorFor(node, options, context),\n\t\t\t\t\t\t\tstrokeWidth: 1.5,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tkind: \"text\",\n\t\t\t\t\t\tkey: `${id}:label:${nodeKey(node, options)}`,\n\t\t\t\t\t\trole: \"label\",\n\t\t\t\t\t\tx:\n\t\t\t\t\t\t\tat.x +\n\t\t\t\t\t\t\t(leaf ? LABEL_GAP + NODE_RADIUS : -(LABEL_GAP + NODE_RADIUS)),\n\t\t\t\t\t\ty: at.y,\n\t\t\t\t\t\ttext: options.name(node.data),\n\t\t\t\t\t\tpaint: {\n\t\t\t\t\t\t\tfill: theme.foreground,\n\t\t\t\t\t\t\tfontSize: theme.fontSize,\n\t\t\t\t\t\t\ttextAnchor: leaf ? \"start\" : \"end\",\n\t\t\t\t\t\t\tbaseline: \"middle\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tif (options.tip !== false) {\n\t\t\t\t\tpoints.push(pointFor(id, node, options, context, index, at));\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { nodes, points };\n\t\t},\n\t};\n}\n"],
5
- "mappings": "AAAA;AAAA,EAGC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,SAAS,SAAS,wBAAwB;AA0B1C,SAAS,OACR,MACA,SACuB;AACvB,QAAM,OAAO;AAAA,IACZ;AAAA,IACA,CAAC,UACC,QAAQ,WAAW,KAAK,KACxB,MAAiC;AAAA,EACpC;AACA,SAAO,KACL,IAAI,CAAC,UAAW,QAAQ,QAAQ,KAAK,IAAI,GAAG,QAAQ,MAAM,KAAK,CAAC,IAAI,CAAE,EACtE,KAAK,CAAC,MAAM,WAAW,MAAM,SAAS,MAAM,KAAK,SAAS,EAAE;AAC/D;AAEA,SAAS,SAAgB,MAAkD;AAC1E,MAAI,UAAU;AACd,SAAO,QAAQ,QAAQ,QAAQ;AAC9B,cAAU,QAAQ;AAAA,EACnB;AACA,SAAO;AACR;AAEA,SAAS,SACR,MACA,SACS;AACT,SAAO,KACL,UAAU,EACV,QAAQ,EACR,MAAM,CAAC,EACP,IAAI,CAAC,aAAa,QAAQ,KAAK,SAAS,IAAI,CAAC,EAC7C,KAAK,UAAK;AACb;AAEA,SAAS,YACR,MACA,SACW;AACX,UAAQ,OAAO,MAAM,OAAO,EAAE,YAAY,CAAC,GAAG;AAAA,IAAI,CAAC,UAClD,QAAQ,KAAK,MAAM,IAAI;AAAA,EACxB;AACD;AAEA,SAAS,QACR,MACA,SACS;AACT,SAAO,KACL,UAAU,EACV,IAAI,CAAC,aAAa,QAAQ,KAAK,SAAS,IAAI,CAAC,EAC7C,QAAQ,EACR,KAAK,GAAG;AACX;AAEA,SAAS,SACR,MACA,SACA,SACS;AACT,SAAO,KAAK,UAAU,IACnB,QAAQ,MAAM,QACd,QAAQ,QAAQ,QAAQ,KAAK,SAAS,IAAI,EAAE,IAAI,CAAC;AACrD;AAEA,SAAS,SACR,IACA,MACA,SACA,SACA,OACA,IACA,KACa;AACb,SAAO;AAAA,IACN,KAAK,GAAG,EAAE,IAAI,QAAQ,MAAM,OAAO,CAAC;AAAA,IACpC,QAAQ;AAAA,IACR;AAAA,IACA,OAAO,KAAK;AAAA,IACZ,GAAG,GAAG;AAAA,IACN,GAAG,GAAG;AAAA,IACN,QAAQ,QAAQ,KAAK,KAAK,IAAI;AAAA,IAC9B,QAAQ,KAAK,SAAS;AAAA,IACtB,QAAQ,QAAQ,KAAK,SAAS,IAAI,EAAE,IAAI;AAAA,IACxC,aAAa,QAAQ;AAAA,IACrB,OAAO,SAAS,MAAM,SAAS,OAAO;AAAA,IACtC,OAAO,SAAS,MAAM,OAAO,KAAK,QAAQ,KAAK,KAAK,IAAI;AAAA,IACxD,OAAO,QAAQ,QAAQ,KAAK,SAAS,CAAC;AAAA,IACtC;AAAA,EACD;AACD;AAEA,MAAM,gBAAgB;AACtB,MAAM,mBAAmB;AAGlB,SAAS,YACf,MACA,SACY;AACZ,SAAO;AAAA,IACN,IAAI,QAAQ;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,QAAQ,EAAE,OAAO,QAAQ,MAAM;AAAA,IAC/B,UAAU,EAAE,OAAO,EAAE,QAAQ,YAAY,MAAM,OAAO,EAAE,EAAE;AAAA,IAC1D,OAAO,SAAS;AACf,YAAM,KAAK,OAAO,QAAQ,IAAI,WAAW,OAAO;AAChD,YAAM,EAAE,MAAM,MAAM,IAAI;AACxB,YAAM,OAAO,QAAe,EAC1B,KAAK,CAAC,KAAK,OAAO,KAAK,MAAM,CAAC,EAC9B,aAAa,CAAC,EACd,MAAM,IAAI,EAAE,OAAO,MAAM,OAAO,CAAC;AACnC,YAAM,QAAqB,CAAC;AAC5B,YAAM,SAAuB,CAAC;AAC9B,iBAAW,CAAC,OAAO,IAAI,KAAK,KAAK,OAAO,EAAE,QAAQ,GAAG;AACpD,cAAM,OAAO;AAAA,UACZ,GAAG,KAAK,IAAI,KAAK;AAAA,UACjB,GAAG,KAAK,IAAI,KAAK;AAAA,UACjB,OAAO,KAAK,IAAI,GAAG,KAAK,KAAK,KAAK,EAAE;AAAA,UACpC,QAAQ,KAAK,IAAI,GAAG,KAAK,KAAK,KAAK,EAAE;AAAA,QACtC;AACA,cAAM,MAAM,QAAQ,MAAM,OAAO;AACjC,cAAM,SAAS,QAAQ,KAAK,SAAS,IAAI,EAAE,IAAI;AAC/C,cAAM,KAAK;AAAA,UACV,MAAM;AAAA,UACN,KAAK,GAAG,EAAE,IAAI,GAAG;AAAA,UACjB;AAAA,UACA,MAAM;AAAA,UACN,GAAG;AAAA,UACH,SAAS,CAAC,GAAG,GAAG,GAAG,CAAC;AAAA,UACpB,OAAO;AAAA,YACN,MAAM,SAAS,MAAM,SAAS,OAAO;AAAA,YACrC,aAAa,OAAO,OAAO,KAAK;AAAA,UACjC;AAAA,QACD,CAAC;AACD,YACC,KAAK,UAAU,oBACf,KAAK,QAAQ,MAAM,WAAW,GAC7B;AACD,gBAAM,KAAK;AAAA,YACV,MAAM;AAAA,YACN,KAAK,GAAG,EAAE,UAAU,GAAG;AAAA,YACvB;AAAA,YACA,MAAM;AAAA,YACN,GAAG,KAAK,IAAI;AAAA,YACZ,GAAG,KAAK,IAAI;AAAA,YACZ,MAAM;AAAA,cACL,QAAQ,KAAK,KAAK,IAAI;AAAA,cACtB,KAAK,QAAQ,gBAAgB;AAAA,cAC7B,MAAM;AAAA,cACN,QAAQ;AAAA,YACT;AAAA,YACA,OAAO;AAAA,cACN,MAAM,MAAM;AAAA,cACZ,UAAU,MAAM;AAAA,cAChB,YAAY;AAAA,cACZ,UAAU;AAAA,YACX;AAAA,UACD,CAAC;AAAA,QACF;AACA,YAAI,QAAQ,QAAQ,OAAO;AAC1B,iBAAO;AAAA,YACN;AAAA,cACC;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA,EAAE,GAAG,KAAK,IAAI,KAAK,QAAQ,GAAG,GAAG,KAAK,IAAI,KAAK,SAAS,EAAE;AAAA,cAC1D,EAAE,MAAM,QAAQ,KAAK;AAAA,YACtB;AAAA,UACD;AAAA,QACD;AAAA,MACD;AACA,aAAO,EAAE,OAAO,OAAO;AAAA,IACxB;AAAA,EACD;AACD;AAGO,SAAS,aACf,MACA,SACY;AACZ,SAAO;AAAA,IACN,IAAI,QAAQ;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,QAAQ,EAAE,OAAO,QAAQ,MAAM;AAAA,IAC/B,UAAU,EAAE,OAAO,EAAE,QAAQ,YAAY,MAAM,OAAO,EAAE,EAAE;AAAA,IAC1D,OAAO,SAAS;AACf,YAAM,KAAK,OAAO,QAAQ,IAAI,YAAY,OAAO;AACjD,YAAM,EAAE,KAAK,IAAI;AACjB,YAAM,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,OAAO,KAAK,MAAM,IAAI,IAAI,CAAC;AACpE,YAAM,KAAK,KAAK,IAAI,KAAK,QAAQ;AACjC,YAAM,KAAK,KAAK,IAAI,KAAK,SAAS;AAClC,YAAM,OAAO,UAAiB,EAAE,KAAK,CAAC,KAAK,MAAM,CAAC;AAAA,QACjD,OAAO,MAAM,OAAO;AAAA,MACrB;AAEA,YAAM,YAAY,UAAU,KAAK,SAAS;AAC1C,YAAM,QAAqB,CAAC;AAC5B,YAAM,SAAuB,CAAC;AAC9B,YAAM,cAAc,KAClB,YAAY,EACZ,OAAO,CAAC,SAAS,KAAK,QAAQ,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAC5D,iBAAW,CAAC,OAAO,IAAI,KAAK,YAAY,QAAQ,GAAG;AAClD,cAAM,QAAQ;AAAA,UACb;AAAA,UACA;AAAA,UACA,aAAa,KAAK,QAAQ;AAAA,UAC1B,cAAc,KAAK,QAAQ,KAAK,YAAY;AAAA,UAC5C,YAAY,KAAK,KAAK;AAAA,UACtB,UAAU,KAAK,KAAK;AAAA,QACrB;AACA,cAAM,SAAS,QAAQ,KAAK,SAAS,IAAI,EAAE,IAAI;AAC/C,cAAM,KAAK;AAAA,UACV,MAAM;AAAA,UACN,KAAK,GAAG,EAAE,IAAI,QAAQ,MAAM,OAAO,CAAC;AAAA,UACpC;AAAA,UACA,MAAM;AAAA,UACN,GAAG,QAAQ,KAAK;AAAA,UAChB,OAAO;AAAA,YACN,MAAM,SAAS,MAAM,SAAS,OAAO;AAAA,YACrC,aAAa,KAAK,KAAK,QAAQ,KAAK;AAAA,UACrC;AAAA,QACD,CAAC;AACD,YAAI,QAAQ,QAAQ,OAAO;AAC1B,gBAAM,SAAS;AAAA,YACd;AAAA,YACA;AAAA,aACC,MAAM,cAAc,MAAM,eAAe;AAAA,aACzC,MAAM,aAAa,MAAM,YAAY;AAAA,UACvC;AACA,iBAAO;AAAA,YACN,SAAS,IAAI,MAAM,SAAS,SAAS,OAAO,QAAQ;AAAA,cACnD,MAAM;AAAA,cACN,GAAG;AAAA,YACJ,CAAC;AAAA,UACF;AAAA,QACD;AAAA,MACD;AACA,aAAO,EAAE,OAAO,OAAO;AAAA,IACxB;AAAA,EACD;AACD;AAEA,MAAM,cAAc;AACpB,MAAM,YAAY;AAGX,SAAS,SACf,MACA,SACY;AACZ,SAAO;AAAA,IACN,IAAI,QAAQ;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,QAAQ,EAAE,OAAO,QAAQ,MAAM;AAAA,IAC/B,UAAU,CAAC;AAAA,IACX,OAAO,SAAS;AACf,YAAM,KAAK,OAAO,QAAQ,IAAI,QAAQ,OAAO;AAC7C,YAAM,EAAE,MAAM,OAAO,YAAY,IAAI;AACrC,YAAM,WAAW,OAAO,MAAM,OAAO;AACrC,YAAM,YAAY,KAAK;AAAA,QACtB;AAAA,QACA,GAAG,SACD,OAAO,EACP,IAAI,CAAC,SAAS,YAAY,QAAQ,KAAK,KAAK,IAAI,GAAG,MAAM,QAAQ,CAAC;AAAA,MACrE;AACA,YAAM,QAAQ,KAAK;AAAA,QAClB;AAAA,QACA,KAAK,QAAQ,YAAY,YAAY,cAAc;AAAA,MACpD;AACA,YAAM,OAAO,KAAY,EAAE,KAAK,CAAC,KAAK,QAAQ,KAAK,CAAC;AAAA,QACnD;AAAA,MACD;AACA,YAAM,WAAW,CAChB,UACK;AAAA,QACL,GAAG,KAAK,IAAI,eAAe,KAAK,KAAK;AAAA,QACrC,GAAG,KAAK,KAAK,KAAK,KAAK;AAAA,MACxB;AACA,YAAM,QAAqB,CAAC;AAC5B,YAAM,SAAuB,CAAC;AAC9B,iBAAW,QAAQ,KAAK,MAAM,GAAG;AAChC,cAAM,OAAO,SAAS,KAAK,MAAM;AACjC,cAAM,KAAK,SAAS,KAAK,MAAM;AAC/B,cAAM,UAAU,KAAK,IAAI,GAAG,KAAK;AACjC,cAAM,KAAK;AAAA,UACV,MAAM;AAAA,UACN,KAAK,GAAG,EAAE,SAAS,QAAQ,KAAK,QAAQ,OAAO,CAAC;AAAA,UAChD,MAAM;AAAA,UACN,GAAG,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,MAAM,IAAI,KAAK,CAAC,IAAI,MAAM,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC;AAAA,UAC7E,OAAO,EAAE,MAAM,QAAQ,QAAQ,MAAM,MAAM,aAAa,IAAI;AAAA,QAC7D,CAAC;AAAA,MACF;AACA,iBAAW,CAAC,OAAO,IAAI,KAAK,KAAK,YAAY,EAAE,QAAQ,GAAG;AACzD,cAAM,KAAK,SAAS,IAAI;AACxB,cAAM,OAAO,KAAK,aAAa;AAC/B,cAAM;AAAA,UACL;AAAA,YACC,MAAM;AAAA,YACN,KAAK,GAAG,EAAE,IAAI,QAAQ,MAAM,OAAO,CAAC;AAAA,YACpC,MAAM;AAAA,YACN,IAAI,GAAG;AAAA,YACP,IAAI,GAAG;AAAA,YACP,GAAG;AAAA,YACH,OAAO;AAAA,cACN,MAAM,OAAO,MAAM,aAAa,SAAS,MAAM,SAAS,OAAO;AAAA,cAC/D,QAAQ,SAAS,MAAM,SAAS,OAAO;AAAA,cACvC,aAAa;AAAA,YACd;AAAA,UACD;AAAA,UACA;AAAA,YACC,MAAM;AAAA,YACN,KAAK,GAAG,EAAE,UAAU,QAAQ,MAAM,OAAO,CAAC;AAAA,YAC1C,MAAM;AAAA,YACN,GACC,GAAG,KACF,OAAO,YAAY,cAAc,EAAE,YAAY;AAAA,YACjD,GAAG,GAAG;AAAA,YACN,MAAM,QAAQ,KAAK,KAAK,IAAI;AAAA,YAC5B,OAAO;AAAA,cACN,MAAM,MAAM;AAAA,cACZ,UAAU,MAAM;AAAA,cAChB,YAAY,OAAO,UAAU;AAAA,cAC7B,UAAU;AAAA,YACX;AAAA,UACD;AAAA,QACD;AACA,YAAI,QAAQ,QAAQ,OAAO;AAC1B,iBAAO,KAAK,SAAS,IAAI,MAAM,SAAS,SAAS,OAAO,EAAE,CAAC;AAAA,QAC5D;AAAA,MACD;AACA,aAAO,EAAE,OAAO,OAAO;AAAA,IACxB;AAAA,EACD;AACD;",
4
+ "sourcesContent": ["import {\n\ttype HierarchyNode,\n\ttype HierarchyRectangularNode,\n\thierarchy,\n\tpartition,\n\ttree,\n\ttreemap,\n} from \"d3-hierarchy\";\nimport { truncate } from \"#/core/guides/axes.ts\";\nimport { markId } from \"#/core/marks/shared.ts\";\nimport { arcPath, polarToCartesian } from \"#/core/paths.ts\";\nimport type {\n\tChartMark,\n\tChartMarkContext,\n\tChartPoint,\n\tSceneNode,\n} from \"#/core/types.ts\";\n\n/**\n * Nested data: a treemap, a sunburst, a tree. Each takes one root and reads\n * its children, value and name through accessors, so any nested shape works.\n * Colours follow the root's direct children (the branches the legend lists);\n * deeper levels keep their branch's colour.\n */\n\nexport interface HierarchyOptions<TNode> {\n\treadonly id?: string;\n\treadonly children?: (node: TNode) => ReadonlyArray<TNode> | undefined;\n\t/** A leaf's size. Branches sum their leaves. */\n\treadonly value?: (node: TNode) => number;\n\treadonly name: (node: TNode) => string;\n\t/** What the values are, for the table heading. */\n\treadonly label?: string;\n\treadonly tip?: boolean;\n}\n\nfunction rooted<TNode>(\n\troot: TNode,\n\toptions: HierarchyOptions<TNode>,\n): HierarchyNode<TNode> {\n\tconst node = hierarchy(\n\t\troot,\n\t\t(datum) =>\n\t\t\t(options.children?.(datum) as TNode[] | undefined) ??\n\t\t\t(datum as { children?: TNode[] }).children,\n\t);\n\treturn node\n\t\t.sum((datum) => (options.value ? Math.max(0, options.value(datum)) : 0))\n\t\t.sort((left, right) => (right.value ?? 0) - (left.value ?? 0));\n}\n\nfunction branchOf<TNode>(node: HierarchyNode<TNode>): HierarchyNode<TNode> {\n\tlet current = node;\n\twhile (current.parent?.parent) {\n\t\tcurrent = current.parent;\n\t}\n\treturn current;\n}\n\nfunction pathName<TNode>(\n\tnode: HierarchyNode<TNode>,\n\toptions: HierarchyOptions<TNode>,\n): string {\n\treturn node\n\t\t.ancestors()\n\t\t.reverse()\n\t\t.slice(1)\n\t\t.map((ancestor) => options.name(ancestor.data))\n\t\t.join(\" \u203A \");\n}\n\nfunction branchNames<TNode>(\n\troot: TNode,\n\toptions: HierarchyOptions<TNode>,\n): string[] {\n\treturn (rooted(root, options).children ?? []).map((child) =>\n\t\toptions.name(child.data),\n\t);\n}\n\nfunction nodeKey<TNode>(\n\tnode: HierarchyNode<TNode>,\n\toptions: HierarchyOptions<TNode>,\n): string {\n\treturn node\n\t\t.ancestors()\n\t\t.map((ancestor) => options.name(ancestor.data))\n\t\t.reverse()\n\t\t.join(\"/\");\n}\n\nfunction colorFor<TNode>(\n\tnode: HierarchyNode<TNode>,\n\toptions: HierarchyOptions<TNode>,\n\tcontext: ChartMarkContext,\n): string {\n\treturn node.depth === 0\n\t\t? context.theme.muted\n\t\t: context.colorOf(options.name(branchOf(node).data));\n}\n\nfunction pointFor<TNode>(\n\tid: string,\n\tnode: HierarchyNode<TNode>,\n\toptions: HierarchyOptions<TNode>,\n\tcontext: ChartMarkContext,\n\tindex: number,\n\tat: { x: number; y: number },\n\thit?: ChartPoint[\"hit\"],\n): ChartPoint {\n\treturn {\n\t\tkey: `${id}:${nodeKey(node, options)}`,\n\t\tmarkId: id,\n\t\tindex,\n\t\tdatum: node.data,\n\t\tx: at.x,\n\t\ty: at.y,\n\t\txValue: options.name(node.data),\n\t\tyValue: node.value ?? 0,\n\t\tseries: options.name(branchOf(node).data),\n\t\tseriesLabel: options.label,\n\t\tcolor: colorFor(node, options, context),\n\t\ttitle: pathName(node, options) || options.name(node.data),\n\t\tvalue: context.formatY(node.value ?? 0),\n\t\thit,\n\t};\n}\n\nconst LABEL_PADDING = 4;\nconst MIN_LABEL_HEIGHT = 16;\n\n/** Rectangles nested inside rectangles, each leaf as large as its value. */\nexport function treemapMark<TNode>(\n\troot: TNode,\n\toptions: HierarchyOptions<TNode>,\n): ChartMark {\n\treturn {\n\t\tid: options.id,\n\t\tcoordinate: \"frame\",\n\t\tfocusOrder: \"point\",\n\t\ttitles: { value: options.label },\n\t\tchannels: { color: { values: branchNames(root, options) } },\n\t\trender(context) {\n\t\t\tconst id = markId(options.id, \"treemap\", context);\n\t\t\tconst { plot, theme } = context;\n\t\t\tconst laid = treemap<TNode>()\n\t\t\t\t.size([plot.width, plot.height])\n\t\t\t\t.paddingInner(2)\n\t\t\t\t.round(true)(rooted(root, options));\n\t\t\tconst nodes: SceneNode[] = [];\n\t\t\tconst points: ChartPoint[] = [];\n\t\t\tfor (const [index, leaf] of laid.leaves().entries()) {\n\t\t\t\tconst rect = {\n\t\t\t\t\tx: plot.x + leaf.x0,\n\t\t\t\t\ty: plot.y + leaf.y0,\n\t\t\t\t\twidth: Math.max(0, leaf.x1 - leaf.x0),\n\t\t\t\t\theight: Math.max(0, leaf.y1 - leaf.y0),\n\t\t\t\t};\n\t\t\t\tconst key = nodeKey(leaf, options);\n\t\t\t\tconst series = options.name(branchOf(leaf).data);\n\t\t\t\tnodes.push({\n\t\t\t\t\tkind: \"rect\",\n\t\t\t\t\tkey: `${id}:${key}`,\n\t\t\t\t\tseries,\n\t\t\t\t\trole: \"mark\",\n\t\t\t\t\t...rect,\n\t\t\t\t\tcorners: [3, 3, 3, 3],\n\t\t\t\t\tpaint: {\n\t\t\t\t\t\tfill: colorFor(leaf, options, context),\n\t\t\t\t\t\tfillOpacity: 0.55 + 0.45 / leaf.depth,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tif (\n\t\t\t\t\trect.height >= MIN_LABEL_HEIGHT &&\n\t\t\t\t\trect.width > theme.fontSize * 2\n\t\t\t\t) {\n\t\t\t\t\tnodes.push({\n\t\t\t\t\t\tkind: \"text\",\n\t\t\t\t\t\tkey: `${id}:label:${key}`,\n\t\t\t\t\t\tseries,\n\t\t\t\t\t\trole: \"label\",\n\t\t\t\t\t\tx: rect.x + LABEL_PADDING,\n\t\t\t\t\t\ty: rect.y + LABEL_PADDING,\n\t\t\t\t\t\ttext: truncate(\n\t\t\t\t\t\t\toptions.name(leaf.data),\n\t\t\t\t\t\t\trect.width - LABEL_PADDING * 2,\n\t\t\t\t\t\t\ttheme.fontSize,\n\t\t\t\t\t\t\tcontext.measureText,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tpaint: {\n\t\t\t\t\t\t\tfill: theme.background,\n\t\t\t\t\t\t\tfontSize: theme.fontSize,\n\t\t\t\t\t\t\tfontWeight: 600,\n\t\t\t\t\t\t\tbaseline: \"top\",\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (options.tip !== false) {\n\t\t\t\t\tpoints.push(\n\t\t\t\t\t\tpointFor(\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\tleaf,\n\t\t\t\t\t\t\toptions,\n\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\tindex,\n\t\t\t\t\t\t\t{ x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 },\n\t\t\t\t\t\t\t{ kind: \"rect\", rect },\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { nodes, points };\n\t\t},\n\t};\n}\n\n/** Rings of arcs: the root's children on the first ring, theirs on the next. */\nexport function sunburstMark<TNode>(\n\troot: TNode,\n\toptions: HierarchyOptions<TNode>,\n): ChartMark {\n\treturn {\n\t\tid: options.id,\n\t\tcoordinate: \"frame\",\n\t\tfocusOrder: \"point\",\n\t\ttitles: { value: options.label },\n\t\tchannels: { color: { values: branchNames(root, options) } },\n\t\trender(context) {\n\t\t\tconst id = markId(options.id, \"sunburst\", context);\n\t\t\tconst { plot } = context;\n\t\t\tconst radius = Math.max(0, Math.min(plot.width, plot.height) / 2 - 4);\n\t\t\tconst cx = plot.x + plot.width / 2;\n\t\t\tconst cy = plot.y + plot.height / 2;\n\t\t\tconst laid = partition<TNode>().size([360, radius])(\n\t\t\t\trooted(root, options),\n\t\t\t);\n\t\t\t// The root's ring would be a disc in the middle: leave it empty.\n\t\t\tconst ringWidth = radius / (laid.height + 1);\n\t\t\tconst nodes: SceneNode[] = [];\n\t\t\tconst points: ChartPoint[] = [];\n\t\t\tconst descendants = laid\n\t\t\t\t.descendants()\n\t\t\t\t.filter((node) => node.depth > 0 && node.x1 - node.x0 > 0.2);\n\t\t\tfor (const [index, node] of descendants.entries()) {\n\t\t\t\tconst shape = {\n\t\t\t\t\tcx,\n\t\t\t\t\tcy,\n\t\t\t\t\tinnerRadius: node.depth * ringWidth,\n\t\t\t\t\touterRadius: (node.depth + 1) * ringWidth - 1,\n\t\t\t\t\tstartAngle: node.x0 + 0.25,\n\t\t\t\t\tendAngle: node.x1 - 0.25,\n\t\t\t\t};\n\t\t\t\tconst series = options.name(branchOf(node).data);\n\t\t\t\tnodes.push({\n\t\t\t\t\tkind: \"path\",\n\t\t\t\t\tkey: `${id}:${nodeKey(node, options)}`,\n\t\t\t\t\tseries,\n\t\t\t\t\trole: \"mark\",\n\t\t\t\t\td: arcPath(shape),\n\t\t\t\t\tpaint: {\n\t\t\t\t\t\tfill: colorFor(node, options, context),\n\t\t\t\t\t\tfillOpacity: 1 - (node.depth - 1) * 0.18,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tif (options.tip !== false) {\n\t\t\t\t\tconst anchor = polarToCartesian(\n\t\t\t\t\t\tcx,\n\t\t\t\t\t\tcy,\n\t\t\t\t\t\t(shape.innerRadius + shape.outerRadius) / 2,\n\t\t\t\t\t\t(shape.startAngle + shape.endAngle) / 2,\n\t\t\t\t\t);\n\t\t\t\t\tpoints.push(\n\t\t\t\t\t\tpointFor(id, node, options, context, index, anchor, {\n\t\t\t\t\t\t\tkind: \"arc\",\n\t\t\t\t\t\t\t...shape,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { nodes, points };\n\t\t},\n\t};\n}\n\nconst NODE_RADIUS = 4;\nconst LABEL_GAP = 6;\n\n/** A node-link tree, root on the left, leaves on the right, every node named. */\nexport function treeMark<TNode>(\n\troot: TNode,\n\toptions: HierarchyOptions<TNode>,\n): ChartMark {\n\treturn {\n\t\tid: options.id,\n\t\tcoordinate: \"frame\",\n\t\tfocusOrder: \"point\",\n\t\ttitles: { value: options.label },\n\t\tchannels: {},\n\t\trender(context) {\n\t\t\tconst id = markId(options.id, \"tree\", context);\n\t\t\tconst { plot, theme, measureText } = context;\n\t\t\tconst rootNode = rooted(root, options);\n\t\t\tconst leafLabel = Math.max(\n\t\t\t\t0,\n\t\t\t\t...rootNode\n\t\t\t\t\t.leaves()\n\t\t\t\t\t.map((leaf) => measureText(options.name(leaf.data), theme.fontSize)),\n\t\t\t);\n\t\t\t// The root's label sits left of it, the leaves' right of them: both inside the plot.\n\t\t\tconst rootLabel =\n\t\t\t\tmeasureText(options.name(rootNode.data), theme.fontSize) + LABEL_GAP;\n\t\t\tconst width = Math.max(\n\t\t\t\t0,\n\t\t\t\tplot.width - rootLabel - leafLabel - LABEL_GAP - NODE_RADIUS * 2,\n\t\t\t);\n\t\t\tconst laid = tree<TNode>().size([plot.height, width])(\n\t\t\t\trootNode,\n\t\t\t) as HierarchyRectangularNode<TNode> & HierarchyNode<TNode>;\n\t\t\tconst position = (\n\t\t\t\tnode: HierarchyNode<TNode> & { x?: number; y?: number },\n\t\t\t) => ({\n\t\t\t\tx: plot.x + rootLabel + NODE_RADIUS + (node.y ?? 0),\n\t\t\t\ty: plot.y + (node.x ?? 0),\n\t\t\t});\n\t\t\tconst nodes: SceneNode[] = [];\n\t\t\tconst points: ChartPoint[] = [];\n\t\t\tfor (const link of laid.links()) {\n\t\t\t\tconst from = position(link.source);\n\t\t\t\tconst to = position(link.target);\n\t\t\t\tconst middle = (from.x + to.x) / 2;\n\t\t\t\tnodes.push({\n\t\t\t\t\tkind: \"path\",\n\t\t\t\t\tkey: `${id}:link:${nodeKey(link.target, options)}`,\n\t\t\t\t\trole: \"grid\",\n\t\t\t\t\td: `M${from.x},${from.y}C${middle},${from.y} ${middle},${to.y} ${to.x},${to.y}`,\n\t\t\t\t\tpaint: { fill: \"none\", stroke: theme.grid, strokeWidth: 1.5 },\n\t\t\t\t});\n\t\t\t}\n\t\t\tfor (const [index, node] of laid.descendants().entries()) {\n\t\t\t\tconst at = position(node);\n\t\t\t\tconst leaf = node.children === undefined;\n\t\t\t\tnodes.push(\n\t\t\t\t\t{\n\t\t\t\t\t\tkind: \"circle\",\n\t\t\t\t\t\tkey: `${id}:${nodeKey(node, options)}`,\n\t\t\t\t\t\trole: \"mark\",\n\t\t\t\t\t\tcx: at.x,\n\t\t\t\t\t\tcy: at.y,\n\t\t\t\t\t\tr: NODE_RADIUS,\n\t\t\t\t\t\tpaint: {\n\t\t\t\t\t\t\tfill: leaf ? theme.background : colorFor(node, options, context),\n\t\t\t\t\t\t\tstroke: colorFor(node, options, context),\n\t\t\t\t\t\t\tstrokeWidth: 1.5,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tkind: \"text\",\n\t\t\t\t\t\tkey: `${id}:label:${nodeKey(node, options)}`,\n\t\t\t\t\t\trole: \"label\",\n\t\t\t\t\t\tx:\n\t\t\t\t\t\t\tat.x +\n\t\t\t\t\t\t\t(leaf ? LABEL_GAP + NODE_RADIUS : -(LABEL_GAP + NODE_RADIUS)),\n\t\t\t\t\t\ty: at.y,\n\t\t\t\t\t\ttext: options.name(node.data),\n\t\t\t\t\t\tpaint: {\n\t\t\t\t\t\t\tfill: theme.foreground,\n\t\t\t\t\t\t\tfontSize: theme.fontSize,\n\t\t\t\t\t\t\ttextAnchor: leaf ? \"start\" : \"end\",\n\t\t\t\t\t\t\tbaseline: \"middle\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tif (options.tip !== false) {\n\t\t\t\t\tpoints.push(pointFor(id, node, options, context, index, at));\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { nodes, points };\n\t\t},\n\t};\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/index.d.ts CHANGED
@@ -21,6 +21,7 @@ export { type RegressionOptions, regressionY, } from "./core/marks/stats/regress
21
21
  export { boxSummary, kernelDensity, linearFit, quantile, } from "./core/marks/stats/statistics.js";
22
22
  export { type WaffleOptions, waffleY } from "./core/marks/stats/waffle.js";
23
23
  export { type TextOptions, text } from "./core/marks/text.js";
24
+ export { type TickOptions, tickX, tickY } from "./core/marks/tick.js";
24
25
  export { easeOutCubic, tweenScene } from "./core/motion/tween.js";
25
26
  export { type ChartFocusStop, findNearest, focusStops, } from "./core/nearest.js";
26
27
  export { estimateTextWidth } from "./core/text.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EACN,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,SAAS,EACd,WAAW,GACX,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAE,KAAK,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAE,KAAK,WAAW,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,KAAK,UAAU,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EACN,KAAK,UAAU,EACf,GAAG,EACH,KAAK,EACL,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,EACL,SAAS,GACT,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACtE,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EACN,KAAK,iBAAiB,EACtB,WAAW,GACX,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACN,KAAK,UAAU,EACf,IAAI,EACJ,KAAK,gBAAgB,EACrB,SAAS,EACT,OAAO,GACP,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EACN,KAAK,gBAAgB,EACrB,SAAS,GACT,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACN,KAAK,iBAAiB,EACtB,WAAW,GACX,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACN,UAAU,EACV,aAAa,EACb,SAAS,EACT,QAAQ,GACR,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,KAAK,aAAa,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,EAAE,KAAK,WAAW,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EACN,KAAK,cAAc,EACnB,WAAW,EACX,UAAU,GACV,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,mBAAmB,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EACN,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,SAAS,EACd,WAAW,GACX,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAE,KAAK,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAE,KAAK,WAAW,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,KAAK,UAAU,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EACN,KAAK,UAAU,EACf,GAAG,EACH,KAAK,EACL,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,EACL,SAAS,GACT,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACtE,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EACN,KAAK,iBAAiB,EACtB,WAAW,GACX,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACN,KAAK,UAAU,EACf,IAAI,EACJ,KAAK,gBAAgB,EACrB,SAAS,EACT,OAAO,GACP,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EACN,KAAK,gBAAgB,EACrB,SAAS,GACT,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACN,KAAK,iBAAiB,EACtB,WAAW,GACX,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACN,UAAU,EACV,aAAa,EACb,SAAS,EACT,QAAQ,GACR,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,KAAK,aAAa,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,EAAE,KAAK,WAAW,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EACN,KAAK,cAAc,EACnB,WAAW,EACX,UAAU,GACV,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,mBAAmB,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -42,6 +42,7 @@ import {
42
42
  } from "./core/marks/stats/statistics.js";
43
43
  import { waffleY } from "./core/marks/stats/waffle.js";
44
44
  import { text } from "./core/marks/text.js";
45
+ import { tickX, tickY } from "./core/marks/tick.js";
45
46
  import { easeOutCubic, tweenScene } from "./core/motion/tween.js";
46
47
  import {
47
48
  findNearest,
@@ -89,6 +90,8 @@ export {
89
90
  ruleX,
90
91
  ruleY,
91
92
  text,
93
+ tickX,
94
+ tickY,
92
95
  tweenScene,
93
96
  violinY,
94
97
  waffleY
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["export { type CompileOptions, compileChart } from \"#/core/compile.ts\";\nexport {\n\ttype ChartAxisOptions,\n\ttype ChartBuildContext,\n\ttype ChartColorOptions,\n\ttype ChartDefinition,\n\ttype ChartFacet,\n\ttype ChartFacetOptions,\n\ttype ChartPositionScaleOptions,\n\ttype ChartSpec,\n\tdefineChart,\n} from \"#/core/define-chart.ts\";\nexport { facet } from \"#/core/facet.ts\";\nexport { formatDate, formatNumber, formatValue } from \"#/core/format.ts\";\nexport { type AreaOptions, areaX, areaY } from \"#/core/marks/area.ts\";\nexport { type BarOptions, barX, barY } from \"#/core/marks/bar.ts\";\nexport { type CellOptions, cell } from \"#/core/marks/cell.ts\";\nexport { type DotOptions, dot } from \"#/core/marks/dot.ts\";\nexport { type FunnelOptions, funnel } from \"#/core/marks/funnel.ts\";\nexport { type LineOptions, lineX, lineY } from \"#/core/marks/line.ts\";\nexport {\n\ttype ArcOptions,\n\tarc,\n\tdonut,\n\ttype RadarOptions,\n\ttype RadialBarOptions,\n\tradar,\n\tradialBar,\n} from \"#/core/marks/polar.ts\";\nexport { type RectOptions, rectY } from \"#/core/marks/rect.ts\";\nexport { type RuleOptions, ruleX, ruleY } from \"#/core/marks/rule.ts\";\nexport type { ChartMarkOptions } from \"#/core/marks/shared.ts\";\nexport {\n\ttype DifferenceOptions,\n\tdifferenceY,\n} from \"#/core/marks/stats/difference.ts\";\nexport {\n\ttype BoxOptions,\n\tboxY,\n\ttype RidgelineOptions,\n\tridgeline,\n\tviolinY,\n} from \"#/core/marks/stats/distribution.ts\";\nexport { type DodgeOptions, dodgeY } from \"#/core/marks/stats/dodge.ts\";\nexport { type HexbinOptions, hexbin } from \"#/core/marks/stats/hexbin.ts\";\nexport {\n\ttype HistogramOptions,\n\thistogram,\n} from \"#/core/marks/stats/histogram.ts\";\nexport {\n\ttype RegressionOptions,\n\tregressionY,\n} from \"#/core/marks/stats/regression.ts\";\nexport {\n\tboxSummary,\n\tkernelDensity,\n\tlinearFit,\n\tquantile,\n} from \"#/core/marks/stats/statistics.ts\";\nexport { type WaffleOptions, waffleY } from \"#/core/marks/stats/waffle.ts\";\nexport { type TextOptions, text } from \"#/core/marks/text.ts\";\nexport { easeOutCubic, tweenScene } from \"#/core/motion/tween.ts\";\nexport {\n\ttype ChartFocusStop,\n\tfindNearest,\n\tfocusStops,\n} from \"#/core/nearest.ts\";\nexport { estimateTextWidth } from \"#/core/text.ts\";\nexport { DEFAULT_THEME } from \"#/core/theme.ts\";\nexport type * from \"#/core/types.ts\";\n"],
5
- "mappings": "AAAA,SAA8B,oBAAoB;AAClD;AAAA,EASC;AAAA,OACM;AACP,SAAS,aAAa;AACtB,SAAS,YAAY,cAAc,mBAAmB;AACtD,SAA2B,OAAO,aAAa;AAC/C,SAA0B,MAAM,YAAY;AAC5C,SAA2B,YAAY;AACvC,SAA0B,WAAW;AACrC,SAA6B,cAAc;AAC3C,SAA2B,OAAO,aAAa;AAC/C;AAAA,EAEC;AAAA,EACA;AAAA,EAGA;AAAA,EACA;AAAA,OACM;AACP,SAA2B,aAAa;AACxC,SAA2B,OAAO,aAAa;AAE/C;AAAA,EAEC;AAAA,OACM;AACP;AAAA,EAEC;AAAA,EAEA;AAAA,EACA;AAAA,OACM;AACP,SAA4B,cAAc;AAC1C,SAA6B,cAAc;AAC3C;AAAA,EAEC;AAAA,OACM;AACP;AAAA,EAEC;AAAA,OACM;AACP;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAA6B,eAAe;AAC5C,SAA2B,YAAY;AACvC,SAAS,cAAc,kBAAkB;AACzC;AAAA,EAEC;AAAA,EACA;AAAA,OACM;AACP,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;",
4
+ "sourcesContent": ["export { type CompileOptions, compileChart } from \"#/core/compile.ts\";\nexport {\n\ttype ChartAxisOptions,\n\ttype ChartBuildContext,\n\ttype ChartColorOptions,\n\ttype ChartDefinition,\n\ttype ChartFacet,\n\ttype ChartFacetOptions,\n\ttype ChartPositionScaleOptions,\n\ttype ChartSpec,\n\tdefineChart,\n} from \"#/core/define-chart.ts\";\nexport { facet } from \"#/core/facet.ts\";\nexport { formatDate, formatNumber, formatValue } from \"#/core/format.ts\";\nexport { type AreaOptions, areaX, areaY } from \"#/core/marks/area.ts\";\nexport { type BarOptions, barX, barY } from \"#/core/marks/bar.ts\";\nexport { type CellOptions, cell } from \"#/core/marks/cell.ts\";\nexport { type DotOptions, dot } from \"#/core/marks/dot.ts\";\nexport { type FunnelOptions, funnel } from \"#/core/marks/funnel.ts\";\nexport { type LineOptions, lineX, lineY } from \"#/core/marks/line.ts\";\nexport {\n\ttype ArcOptions,\n\tarc,\n\tdonut,\n\ttype RadarOptions,\n\ttype RadialBarOptions,\n\tradar,\n\tradialBar,\n} from \"#/core/marks/polar.ts\";\nexport { type RectOptions, rectY } from \"#/core/marks/rect.ts\";\nexport { type RuleOptions, ruleX, ruleY } from \"#/core/marks/rule.ts\";\nexport type { ChartMarkOptions } from \"#/core/marks/shared.ts\";\nexport {\n\ttype DifferenceOptions,\n\tdifferenceY,\n} from \"#/core/marks/stats/difference.ts\";\nexport {\n\ttype BoxOptions,\n\tboxY,\n\ttype RidgelineOptions,\n\tridgeline,\n\tviolinY,\n} from \"#/core/marks/stats/distribution.ts\";\nexport { type DodgeOptions, dodgeY } from \"#/core/marks/stats/dodge.ts\";\nexport { type HexbinOptions, hexbin } from \"#/core/marks/stats/hexbin.ts\";\nexport {\n\ttype HistogramOptions,\n\thistogram,\n} from \"#/core/marks/stats/histogram.ts\";\nexport {\n\ttype RegressionOptions,\n\tregressionY,\n} from \"#/core/marks/stats/regression.ts\";\nexport {\n\tboxSummary,\n\tkernelDensity,\n\tlinearFit,\n\tquantile,\n} from \"#/core/marks/stats/statistics.ts\";\nexport { type WaffleOptions, waffleY } from \"#/core/marks/stats/waffle.ts\";\nexport { type TextOptions, text } from \"#/core/marks/text.ts\";\nexport { type TickOptions, tickX, tickY } from \"#/core/marks/tick.ts\";\nexport { easeOutCubic, tweenScene } from \"#/core/motion/tween.ts\";\nexport {\n\ttype ChartFocusStop,\n\tfindNearest,\n\tfocusStops,\n} from \"#/core/nearest.ts\";\nexport { estimateTextWidth } from \"#/core/text.ts\";\nexport { DEFAULT_THEME } from \"#/core/theme.ts\";\nexport type * from \"#/core/types.ts\";\n"],
5
+ "mappings": "AAAA,SAA8B,oBAAoB;AAClD;AAAA,EASC;AAAA,OACM;AACP,SAAS,aAAa;AACtB,SAAS,YAAY,cAAc,mBAAmB;AACtD,SAA2B,OAAO,aAAa;AAC/C,SAA0B,MAAM,YAAY;AAC5C,SAA2B,YAAY;AACvC,SAA0B,WAAW;AACrC,SAA6B,cAAc;AAC3C,SAA2B,OAAO,aAAa;AAC/C;AAAA,EAEC;AAAA,EACA;AAAA,EAGA;AAAA,EACA;AAAA,OACM;AACP,SAA2B,aAAa;AACxC,SAA2B,OAAO,aAAa;AAE/C;AAAA,EAEC;AAAA,OACM;AACP;AAAA,EAEC;AAAA,EAEA;AAAA,EACA;AAAA,OACM;AACP,SAA4B,cAAc;AAC1C,SAA6B,cAAc;AAC3C;AAAA,EAEC;AAAA,OACM;AACP;AAAA,EAEC;AAAA,OACM;AACP;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAA6B,eAAe;AAC5C,SAA2B,YAAY;AACvC,SAA2B,OAAO,aAAa;AAC/C,SAAS,cAAc,kBAAkB;AACzC;AAAA,EAEC;AAAA,EACA;AAAA,OACM;AACP,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { roundedBarPath } from "../core/paths.js";
3
3
  const BASELINE = {
4
- top: "hanging",
4
+ top: "text-before-edge",
5
5
  middle: "central",
6
6
  bottom: "text-after-edge",
7
7
  alphabetic: "alphabetic"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/react/svg-scene.tsx"],
4
- "sourcesContent": ["import type * as React from \"react\";\nimport { roundedBarPath } from \"#/core/paths.ts\";\nimport type {\n\tChartPaint,\n\tChartScene,\n\tChartTextPaint,\n\tSceneNode,\n} from \"#/core/types.ts\";\n\nconst BASELINE: Record<\n\tNonNullable<ChartTextPaint[\"baseline\"]>,\n\tReact.SVGAttributes<SVGTextElement>[\"dominantBaseline\"]\n> = {\n\ttop: \"hanging\",\n\tmiddle: \"central\",\n\tbottom: \"text-after-edge\",\n\talphabetic: \"alphabetic\",\n};\n\nconst HATCH_STEP = 6;\n\n/** A stable id per colour (djb2), the same on the server and in the browser. */\nfunction hatchId(prefix: string, color: string): string {\n\tlet hash = 5381;\n\tfor (const character of color) {\n\t\thash = ((hash << 5) + hash + character.charCodeAt(0)) | 0;\n\t}\n\treturn `${prefix}-hatch-${(hash >>> 0).toString(36)}`;\n}\n\nfunction hatchColors(\n\tnodes: ReadonlyArray<SceneNode>,\n\tinto = new Set<string>(),\n): Set<string> {\n\tfor (const node of nodes) {\n\t\tif (node.kind === \"group\") {\n\t\t\thatchColors(node.children, into);\n\t\t} else if (node.paint.hatch && node.paint.fill) {\n\t\t\tinto.add(node.paint.fill);\n\t\t}\n\t}\n\treturn into;\n}\n\nfunction paintProps(\n\tpaint: ChartPaint,\n\tprefix: string,\n): React.SVGAttributes<SVGElement> {\n\treturn {\n\t\tfill:\n\t\t\tpaint.hatch && paint.fill\n\t\t\t\t? `url(#${hatchId(prefix, paint.fill)})`\n\t\t\t\t: (paint.fill ?? \"none\"),\n\t\tstroke: paint.stroke,\n\t\tstrokeWidth: paint.strokeWidth,\n\t\tstrokeDasharray: paint.strokeDasharray,\n\t\tstrokeLinecap: paint.strokeLinecap,\n\t\tstrokeLinejoin: paint.strokeLinejoin,\n\t\topacity: paint.opacity,\n\t\tfillOpacity: paint.fillOpacity,\n\t\tstrokeOpacity: paint.strokeOpacity,\n\t};\n}\n\nfunction SceneElement({\n\tnode,\n\tclipPrefix,\n}: {\n\tnode: SceneNode;\n\tclipPrefix: string;\n}) {\n\tconst data = { \"data-role\": node.role, \"data-series\": node.series };\n\tswitch (node.kind) {\n\t\tcase \"group\": {\n\t\t\tconst clipId = node.clip ? `${clipPrefix}-${node.key}` : undefined;\n\t\t\treturn (\n\t\t\t\t<g\n\t\t\t\t\t{...data}\n\t\t\t\t\ttransform={\n\t\t\t\t\t\tnode.translate\n\t\t\t\t\t\t\t? `translate(${node.translate.x} ${node.translate.y})`\n\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t}\n\t\t\t\t\tclipPath={clipId ? `url(#${clipId})` : undefined}\n\t\t\t\t>\n\t\t\t\t\t{node.clip && clipId ? (\n\t\t\t\t\t\t<clipPath id={clipId}>\n\t\t\t\t\t\t\t<rect {...node.clip} />\n\t\t\t\t\t\t</clipPath>\n\t\t\t\t\t) : null}\n\t\t\t\t\t{node.children.map((child) => (\n\t\t\t\t\t\t<SceneElement\n\t\t\t\t\t\t\tkey={child.key}\n\t\t\t\t\t\t\tnode={child}\n\t\t\t\t\t\t\tclipPrefix={clipPrefix}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t</g>\n\t\t\t);\n\t\t}\n\t\tcase \"rect\":\n\t\t\treturn node.corners ? (\n\t\t\t\t<path\n\t\t\t\t\t{...data}\n\t\t\t\t\td={roundedBarPath({ ...node, radius: node.corners })}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t/>\n\t\t\t) : (\n\t\t\t\t<rect\n\t\t\t\t\t{...data}\n\t\t\t\t\tx={node.x}\n\t\t\t\t\ty={node.y}\n\t\t\t\t\twidth={node.width}\n\t\t\t\t\theight={node.height}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t/>\n\t\t\t);\n\t\tcase \"path\":\n\t\t\treturn (\n\t\t\t\t<path {...data} d={node.d} {...paintProps(node.paint, clipPrefix)} />\n\t\t\t);\n\t\tcase \"circle\":\n\t\t\treturn (\n\t\t\t\t<circle\n\t\t\t\t\t{...data}\n\t\t\t\t\tcx={node.cx}\n\t\t\t\t\tcy={node.cy}\n\t\t\t\t\tr={node.r}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t/>\n\t\t\t);\n\t\tcase \"line\":\n\t\t\treturn (\n\t\t\t\t<line\n\t\t\t\t\t{...data}\n\t\t\t\t\tx1={node.x1}\n\t\t\t\t\ty1={node.y1}\n\t\t\t\t\tx2={node.x2}\n\t\t\t\t\ty2={node.y2}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t/>\n\t\t\t);\n\t\tcase \"text\":\n\t\t\treturn (\n\t\t\t\t<text\n\t\t\t\t\t{...data}\n\t\t\t\t\tx={node.x}\n\t\t\t\t\ty={node.y}\n\t\t\t\t\ttransform={\n\t\t\t\t\t\tnode.rotate\n\t\t\t\t\t\t\t? `rotate(${node.rotate} ${node.x} ${node.y})`\n\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t}\n\t\t\t\t\tfontSize={node.paint.fontSize}\n\t\t\t\t\tfontWeight={node.paint.fontWeight}\n\t\t\t\t\ttextAnchor={node.paint.textAnchor}\n\t\t\t\t\tdominantBaseline={\n\t\t\t\t\t\tnode.paint.baseline ? BASELINE[node.paint.baseline] : undefined\n\t\t\t\t\t}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t>\n\t\t\t\t\t{node.text}\n\t\t\t\t</text>\n\t\t\t);\n\t}\n}\n\n/** The scene's nodes as SVG elements, keyed so React reconciles a data update in place. */\nexport function SvgSceneNodes({\n\tscene,\n\tclipPrefix,\n}: {\n\tscene: ChartScene;\n\tclipPrefix: string;\n}) {\n\tconst hatches = [...hatchColors(scene.nodes)];\n\treturn (\n\t\t<>\n\t\t\t{hatches.length > 0 ? (\n\t\t\t\t<defs>\n\t\t\t\t\t{hatches.map((color) => (\n\t\t\t\t\t\t<pattern\n\t\t\t\t\t\t\tkey={color}\n\t\t\t\t\t\t\tid={hatchId(clipPrefix, color)}\n\t\t\t\t\t\t\twidth={HATCH_STEP}\n\t\t\t\t\t\t\theight={HATCH_STEP}\n\t\t\t\t\t\t\tpatternUnits=\"userSpaceOnUse\"\n\t\t\t\t\t\t\tpatternTransform=\"rotate(45)\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<line\n\t\t\t\t\t\t\t\tx1={0}\n\t\t\t\t\t\t\t\ty1={0}\n\t\t\t\t\t\t\t\tx2={0}\n\t\t\t\t\t\t\t\ty2={HATCH_STEP}\n\t\t\t\t\t\t\t\tstroke={color}\n\t\t\t\t\t\t\t\tstrokeWidth={1.5}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</pattern>\n\t\t\t\t\t))}\n\t\t\t\t</defs>\n\t\t\t) : null}\n\t\t\t{scene.nodes.map((node) => (\n\t\t\t\t<SceneElement key={node.key} node={node} clipPrefix={clipPrefix} />\n\t\t\t))}\n\t\t</>\n\t);\n}\n"],
4
+ "sourcesContent": ["import type * as React from \"react\";\nimport { roundedBarPath } from \"#/core/paths.ts\";\nimport type {\n\tChartPaint,\n\tChartScene,\n\tChartTextPaint,\n\tSceneNode,\n} from \"#/core/types.ts\";\n\nconst BASELINE: Record<\n\tNonNullable<ChartTextPaint[\"baseline\"]>,\n\tReact.SVGAttributes<SVGTextElement>[\"dominantBaseline\"]\n> = {\n\ttop: \"text-before-edge\",\n\tmiddle: \"central\",\n\tbottom: \"text-after-edge\",\n\talphabetic: \"alphabetic\",\n};\n\nconst HATCH_STEP = 6;\n\n/** A stable id per colour (djb2), the same on the server and in the browser. */\nfunction hatchId(prefix: string, color: string): string {\n\tlet hash = 5381;\n\tfor (const character of color) {\n\t\thash = ((hash << 5) + hash + character.charCodeAt(0)) | 0;\n\t}\n\treturn `${prefix}-hatch-${(hash >>> 0).toString(36)}`;\n}\n\nfunction hatchColors(\n\tnodes: ReadonlyArray<SceneNode>,\n\tinto = new Set<string>(),\n): Set<string> {\n\tfor (const node of nodes) {\n\t\tif (node.kind === \"group\") {\n\t\t\thatchColors(node.children, into);\n\t\t} else if (node.paint.hatch && node.paint.fill) {\n\t\t\tinto.add(node.paint.fill);\n\t\t}\n\t}\n\treturn into;\n}\n\nfunction paintProps(\n\tpaint: ChartPaint,\n\tprefix: string,\n): React.SVGAttributes<SVGElement> {\n\treturn {\n\t\tfill:\n\t\t\tpaint.hatch && paint.fill\n\t\t\t\t? `url(#${hatchId(prefix, paint.fill)})`\n\t\t\t\t: (paint.fill ?? \"none\"),\n\t\tstroke: paint.stroke,\n\t\tstrokeWidth: paint.strokeWidth,\n\t\tstrokeDasharray: paint.strokeDasharray,\n\t\tstrokeLinecap: paint.strokeLinecap,\n\t\tstrokeLinejoin: paint.strokeLinejoin,\n\t\topacity: paint.opacity,\n\t\tfillOpacity: paint.fillOpacity,\n\t\tstrokeOpacity: paint.strokeOpacity,\n\t};\n}\n\nfunction SceneElement({\n\tnode,\n\tclipPrefix,\n}: {\n\tnode: SceneNode;\n\tclipPrefix: string;\n}) {\n\tconst data = { \"data-role\": node.role, \"data-series\": node.series };\n\tswitch (node.kind) {\n\t\tcase \"group\": {\n\t\t\tconst clipId = node.clip ? `${clipPrefix}-${node.key}` : undefined;\n\t\t\treturn (\n\t\t\t\t<g\n\t\t\t\t\t{...data}\n\t\t\t\t\ttransform={\n\t\t\t\t\t\tnode.translate\n\t\t\t\t\t\t\t? `translate(${node.translate.x} ${node.translate.y})`\n\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t}\n\t\t\t\t\tclipPath={clipId ? `url(#${clipId})` : undefined}\n\t\t\t\t>\n\t\t\t\t\t{node.clip && clipId ? (\n\t\t\t\t\t\t<clipPath id={clipId}>\n\t\t\t\t\t\t\t<rect {...node.clip} />\n\t\t\t\t\t\t</clipPath>\n\t\t\t\t\t) : null}\n\t\t\t\t\t{node.children.map((child) => (\n\t\t\t\t\t\t<SceneElement\n\t\t\t\t\t\t\tkey={child.key}\n\t\t\t\t\t\t\tnode={child}\n\t\t\t\t\t\t\tclipPrefix={clipPrefix}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t</g>\n\t\t\t);\n\t\t}\n\t\tcase \"rect\":\n\t\t\treturn node.corners ? (\n\t\t\t\t<path\n\t\t\t\t\t{...data}\n\t\t\t\t\td={roundedBarPath({ ...node, radius: node.corners })}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t/>\n\t\t\t) : (\n\t\t\t\t<rect\n\t\t\t\t\t{...data}\n\t\t\t\t\tx={node.x}\n\t\t\t\t\ty={node.y}\n\t\t\t\t\twidth={node.width}\n\t\t\t\t\theight={node.height}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t/>\n\t\t\t);\n\t\tcase \"path\":\n\t\t\treturn (\n\t\t\t\t<path {...data} d={node.d} {...paintProps(node.paint, clipPrefix)} />\n\t\t\t);\n\t\tcase \"circle\":\n\t\t\treturn (\n\t\t\t\t<circle\n\t\t\t\t\t{...data}\n\t\t\t\t\tcx={node.cx}\n\t\t\t\t\tcy={node.cy}\n\t\t\t\t\tr={node.r}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t/>\n\t\t\t);\n\t\tcase \"line\":\n\t\t\treturn (\n\t\t\t\t<line\n\t\t\t\t\t{...data}\n\t\t\t\t\tx1={node.x1}\n\t\t\t\t\ty1={node.y1}\n\t\t\t\t\tx2={node.x2}\n\t\t\t\t\ty2={node.y2}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t/>\n\t\t\t);\n\t\tcase \"text\":\n\t\t\treturn (\n\t\t\t\t<text\n\t\t\t\t\t{...data}\n\t\t\t\t\tx={node.x}\n\t\t\t\t\ty={node.y}\n\t\t\t\t\ttransform={\n\t\t\t\t\t\tnode.rotate\n\t\t\t\t\t\t\t? `rotate(${node.rotate} ${node.x} ${node.y})`\n\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t}\n\t\t\t\t\tfontSize={node.paint.fontSize}\n\t\t\t\t\tfontWeight={node.paint.fontWeight}\n\t\t\t\t\ttextAnchor={node.paint.textAnchor}\n\t\t\t\t\tdominantBaseline={\n\t\t\t\t\t\tnode.paint.baseline ? BASELINE[node.paint.baseline] : undefined\n\t\t\t\t\t}\n\t\t\t\t\t{...paintProps(node.paint, clipPrefix)}\n\t\t\t\t>\n\t\t\t\t\t{node.text}\n\t\t\t\t</text>\n\t\t\t);\n\t}\n}\n\n/** The scene's nodes as SVG elements, keyed so React reconciles a data update in place. */\nexport function SvgSceneNodes({\n\tscene,\n\tclipPrefix,\n}: {\n\tscene: ChartScene;\n\tclipPrefix: string;\n}) {\n\tconst hatches = [...hatchColors(scene.nodes)];\n\treturn (\n\t\t<>\n\t\t\t{hatches.length > 0 ? (\n\t\t\t\t<defs>\n\t\t\t\t\t{hatches.map((color) => (\n\t\t\t\t\t\t<pattern\n\t\t\t\t\t\t\tkey={color}\n\t\t\t\t\t\t\tid={hatchId(clipPrefix, color)}\n\t\t\t\t\t\t\twidth={HATCH_STEP}\n\t\t\t\t\t\t\theight={HATCH_STEP}\n\t\t\t\t\t\t\tpatternUnits=\"userSpaceOnUse\"\n\t\t\t\t\t\t\tpatternTransform=\"rotate(45)\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<line\n\t\t\t\t\t\t\t\tx1={0}\n\t\t\t\t\t\t\t\ty1={0}\n\t\t\t\t\t\t\t\tx2={0}\n\t\t\t\t\t\t\t\ty2={HATCH_STEP}\n\t\t\t\t\t\t\t\tstroke={color}\n\t\t\t\t\t\t\t\tstrokeWidth={1.5}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</pattern>\n\t\t\t\t\t))}\n\t\t\t\t</defs>\n\t\t\t) : null}\n\t\t\t{scene.nodes.map((node) => (\n\t\t\t\t<SceneElement key={node.key} node={node} clipPrefix={clipPrefix} />\n\t\t\t))}\n\t\t</>\n\t);\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voila.dev/chart",
3
- "version": "5.0.110",
3
+ "version": "5.0.111",
4
4
  "type": "module",
5
5
  "description": "Charts as data: a definition of marks, channels and scales, drawn to SVG or Canvas, keyboard and screen-reader accessible. The core has no dependencies; the heavy layouts (geo, hierarchy, sankey…) sit behind their own subpaths.",
6
6
  "license": "MIT",
@@ -0,0 +1,189 @@
1
+ import { channelLabel, isPlaceable, readChannel } from "#/core/channel.ts";
2
+ import {
3
+ type ChartMarkOptions,
4
+ colorChannel,
5
+ markId,
6
+ paint,
7
+ seriesResolver,
8
+ tips,
9
+ valueOn,
10
+ } from "#/core/marks/shared.ts";
11
+ import type {
12
+ ChartAccessor,
13
+ ChartMark,
14
+ ChartPoint,
15
+ ChartPositionScale,
16
+ ChartValue,
17
+ SceneNode,
18
+ } from "#/core/types.ts";
19
+
20
+ export interface TickOptions<TDatum> extends ChartMarkOptions<TDatum> {
21
+ readonly x?: ChartAccessor<TDatum, ChartValue>;
22
+ readonly y?: ChartAccessor<TDatum, ChartValue>;
23
+ readonly stroke?: string;
24
+ readonly strokeWidth?: number;
25
+ /** The tick's length in pixels where the cross axis is continuous; on a band it spans the band. */
26
+ readonly length?: number;
27
+ }
28
+
29
+ const DEFAULT_LENGTH = 12;
30
+ const DEFAULT_STROKE_WIDTH = 2;
31
+
32
+ /** Where the tick spans on the cross axis: the band, or `length` around the value. */
33
+ function span(
34
+ scale: ChartPositionScale | undefined,
35
+ value: ChartValue | undefined,
36
+ length: number,
37
+ plot: { readonly start: number; readonly size: number },
38
+ ): readonly [number, number] {
39
+ if (scale === undefined || value === undefined) {
40
+ return [plot.start, plot.start + plot.size];
41
+ }
42
+ if (scale.kind === "band") {
43
+ const start = scale.map(value);
44
+ return [start, start + scale.bandwidth];
45
+ }
46
+ const center = scale.center(value);
47
+ return [center - length / 2, center + length / 2];
48
+ }
49
+
50
+ function tickMark<TDatum>(
51
+ kind: "tickX" | "tickY",
52
+ data: ReadonlyArray<TDatum>,
53
+ options: TickOptions<TDatum>,
54
+ ): ChartMark {
55
+ const vertical = kind === "tickX";
56
+ const xs = readChannel(data, options.x);
57
+ const ys = readChannel(data, options.y);
58
+ const colors = colorChannel(data, options);
59
+ const valueKey = vertical ? options.x : options.y;
60
+ const crossKey = vertical ? options.y : options.x;
61
+ const values = vertical ? xs : ys;
62
+ const crosses = vertical ? ys : xs;
63
+
64
+ return {
65
+ id: options.id,
66
+ coordinate: "cartesian",
67
+ focusOrder: "point",
68
+ channels: {
69
+ [vertical ? "x" : "y"]: {
70
+ values: values.filter(isPlaceable),
71
+ label: channelLabel(valueKey) ?? options.label,
72
+ },
73
+ ...(crossKey === undefined
74
+ ? {}
75
+ : {
76
+ [vertical ? "y" : "x"]: {
77
+ values: crosses.filter(isPlaceable),
78
+ label: channelLabel(crossKey),
79
+ discrete: "band" as const,
80
+ },
81
+ }),
82
+ color: colors.channel,
83
+ },
84
+ render(context) {
85
+ const id = markId(options.id, kind, context);
86
+ const valueScale = vertical ? context.scales.x : context.scales.y;
87
+ const crossScale = vertical ? context.scales.y : context.scales.x;
88
+ if (valueScale === undefined) {
89
+ return { nodes: [] };
90
+ }
91
+ const { plot } = context;
92
+ const seriesOf = seriesResolver(
93
+ colors.values,
94
+ { ...options, fixedColor: options.stroke },
95
+ id,
96
+ context,
97
+ );
98
+ const length = options.length ?? DEFAULT_LENGTH;
99
+ const strokeWidth = options.strokeWidth ?? DEFAULT_STROKE_WIDTH;
100
+ const nodes: SceneNode[] = [];
101
+ const points: ChartPoint[] = [];
102
+ for (const [index, datum] of data.entries()) {
103
+ const value = valueOn(valueScale, values[index]);
104
+ if (value === undefined) {
105
+ continue;
106
+ }
107
+ const cross =
108
+ crossKey === undefined || crossScale === undefined
109
+ ? undefined
110
+ : valueOn(crossScale, crosses[index]);
111
+ if (crossKey !== undefined && cross === undefined) {
112
+ continue;
113
+ }
114
+ const at = valueScale.center(value);
115
+ const [from, to] = span(
116
+ crossKey === undefined ? undefined : crossScale,
117
+ cross,
118
+ length,
119
+ vertical
120
+ ? { start: plot.y, size: plot.height }
121
+ : { start: plot.x, size: plot.width },
122
+ );
123
+ const series = seriesOf(index);
124
+ nodes.push({
125
+ kind: "line",
126
+ key: `${id}:${index}`,
127
+ series: series.key,
128
+ role: "mark",
129
+ ...(vertical
130
+ ? { x1: at, y1: from, x2: at, y2: to }
131
+ : { x1: from, y1: at, x2: to, y2: at }),
132
+ paint: paint({
133
+ stroke: series.color,
134
+ strokeWidth,
135
+ opacity: options.opacity,
136
+ }),
137
+ });
138
+ if (tips(options.tip, datum, index)) {
139
+ const middle = (from + to) / 2;
140
+ const xValue = vertical ? value : cross;
141
+ const yValue = vertical ? cross : value;
142
+ points.push({
143
+ key: `${id}:${index}`,
144
+ markId: id,
145
+ index,
146
+ datum,
147
+ x: vertical ? at : middle,
148
+ y: vertical ? middle : at,
149
+ xValue,
150
+ yValue,
151
+ series: series.key,
152
+ seriesLabel: series.label,
153
+ color: series.color,
154
+ title:
155
+ cross === undefined
156
+ ? (series.label ?? "")
157
+ : vertical
158
+ ? context.formatY(cross)
159
+ : context.formatX(cross),
160
+ value: vertical ? context.formatX(value) : context.formatY(value),
161
+ hit: {
162
+ kind: "rect",
163
+ rect: vertical
164
+ ? { x: at - 4, y: from, width: 8, height: to - from }
165
+ : { x: from, y: at - 4, width: to - from, height: 8 },
166
+ },
167
+ });
168
+ }
169
+ }
170
+ return { nodes, points };
171
+ },
172
+ };
173
+ }
174
+
175
+ /** A short vertical stroke at each x: a strip plot, a rug, one value per category band. */
176
+ export function tickX<TDatum>(
177
+ data: ReadonlyArray<TDatum>,
178
+ options: TickOptions<TDatum> = {},
179
+ ): ChartMark {
180
+ return tickMark("tickX", data, options);
181
+ }
182
+
183
+ /** A short horizontal stroke at each y: a target per bar, a median per category. */
184
+ export function tickY<TDatum>(
185
+ data: ReadonlyArray<TDatum>,
186
+ options: TickOptions<TDatum> = {},
187
+ ): ChartMark {
188
+ return tickMark("tickY", data, options);
189
+ }
@@ -306,9 +306,12 @@ export function treeMark<TNode>(
306
306
  .leaves()
307
307
  .map((leaf) => measureText(options.name(leaf.data), theme.fontSize)),
308
308
  );
309
+ // The root's label sits left of it, the leaves' right of them: both inside the plot.
310
+ const rootLabel =
311
+ measureText(options.name(rootNode.data), theme.fontSize) + LABEL_GAP;
309
312
  const width = Math.max(
310
313
  0,
311
- plot.width - leafLabel - LABEL_GAP - NODE_RADIUS * 2,
314
+ plot.width - rootLabel - leafLabel - LABEL_GAP - NODE_RADIUS * 2,
312
315
  );
313
316
  const laid = tree<TNode>().size([plot.height, width])(
314
317
  rootNode,
@@ -316,7 +319,7 @@ export function treeMark<TNode>(
316
319
  const position = (
317
320
  node: HierarchyNode<TNode> & { x?: number; y?: number },
318
321
  ) => ({
319
- x: plot.x + NODE_RADIUS + (node.y ?? 0),
322
+ x: plot.x + rootLabel + NODE_RADIUS + (node.y ?? 0),
320
323
  y: plot.y + (node.x ?? 0),
321
324
  });
322
325
  const nodes: SceneNode[] = [];
package/src/index.ts CHANGED
@@ -59,6 +59,7 @@ export {
59
59
  } from "#/core/marks/stats/statistics.ts";
60
60
  export { type WaffleOptions, waffleY } from "#/core/marks/stats/waffle.ts";
61
61
  export { type TextOptions, text } from "#/core/marks/text.ts";
62
+ export { type TickOptions, tickX, tickY } from "#/core/marks/tick.ts";
62
63
  export { easeOutCubic, tweenScene } from "#/core/motion/tween.ts";
63
64
  export {
64
65
  type ChartFocusStop,
@@ -11,7 +11,7 @@ const BASELINE: Record<
11
11
  NonNullable<ChartTextPaint["baseline"]>,
12
12
  React.SVGAttributes<SVGTextElement>["dominantBaseline"]
13
13
  > = {
14
- top: "hanging",
14
+ top: "text-before-edge",
15
15
  middle: "central",
16
16
  bottom: "text-after-edge",
17
17
  alphabetic: "alphabetic",
@@ -0,0 +1,11 @@
1
+ import "vitest/browser";
2
+
3
+ declare module "vitest/browser" {
4
+ interface BrowserCommands {
5
+ emulateMedia(media: {
6
+ readonly colorScheme?: "light" | "dark" | null;
7
+ readonly forcedColors?: "active" | "none" | null;
8
+ readonly reducedMotion?: "reduce" | "no-preference" | null;
9
+ }): Promise<void>;
10
+ }
11
+ }