@thomasfosterau/charts 0.0.0 → 0.1.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.
@@ -0,0 +1,351 @@
1
+ import * as Arr from "effect/Array";
2
+ import * as Option from "effect/Option";
3
+ import * as Result from "effect/Result";
4
+ import * as Schema from "effect/Schema";
5
+ // src/spec/types.ts
6
+ /**
7
+ * Public, dependency-free TypeScript types for the portable chart specification.
8
+ *
9
+ * These are hand-authored mirrors of the Effect Schema definitions in
10
+ * `internal/`. They are the only types consumers see — no Effect types leak
11
+ * through the package boundary. A type-parity test asserts these stay
12
+ * structurally identical to the schema-derived types.
13
+ *
14
+ * Design invariants (see the README § Capability matrix):
15
+ * - Grammar-level only: marks and encodings, never geometry, pixels, or
16
+ * renderer names.
17
+ * - Every encoding channel carries an explicit `type`; interpreters
18
+ * reconstruct typed values from declared types, never by sniffing JSON.
19
+ * - The portable subset targets the intersection of native charting
20
+ * vocabularies (Swift Charts, Compose). Web-only richness goes in
21
+ * `extensions` and degrades gracefully.
22
+ * - Optional members are bare `prop?: T`, the one exception to the
23
+ * workspace's `T | undefined` rule: these types describe a JSON document,
24
+ * where a key is present with a value or absent and `undefined` does not
25
+ * exist. The schemas say the same (`Schema.optionalKey`), so `parseSpec`
26
+ * rejects a present-but-undefined key, and widening either side would make
27
+ * the emitted JSON Schema admit `null`.
28
+ */
29
+ /** The current portable chart spec version; what a new spec should declare. */
30
+ const SPEC_VERSION = "1.1";
31
+ // src/spec/guards.ts
32
+ /** Returns true if the spec's data is inline values. */
33
+ function isInlineData(data) {
34
+ return "values" in data;
35
+ }
36
+ /** Returns true if the spec's data is an external reference. */
37
+ function isDataRef(data) {
38
+ return "ref" in data;
39
+ }
40
+ // src/spec/internal/config.ts
41
+ /** Portable presentation hints honoured (or gracefully ignored) by every interpreter. */
42
+ const Config = Schema.Struct({
43
+ title: Schema.optionalKey(Schema.String.annotate({ description: "The chart title." })),
44
+ tooltip: Schema.optionalKey(Schema.Boolean.annotate({ description: "Whether tooltips are enabled. A trivial declarative hint — real interaction is per-platform application code, not document data." }))
45
+ }).annotate({
46
+ identifier: "Config",
47
+ description: "Portable presentation hints."
48
+ });
49
+ // src/spec/internal/data.ts
50
+ /**
51
+ * A single value inside a datum: a flat JSON primitive.
52
+ *
53
+ * `Schema.Finite` (not `Schema.Number`) so non-finite values (NaN, Infinity)
54
+ * are rejected and the emitted JSON Schema is a plain `{"type": "number"}` —
55
+ * portable data must be representable in JSON on every platform.
56
+ */
57
+ const DatumValue = Schema.Union([
58
+ Schema.String,
59
+ Schema.Finite,
60
+ Schema.Boolean,
61
+ Schema.Null
62
+ ]).annotate({
63
+ identifier: "DatumValue",
64
+ description: "A single value inside a data row: a flat JSON primitive."
65
+ });
66
+ /** A single data row: a flat record of JSON primitives. */
67
+ const Datum = Schema.Record(Schema.String, DatumValue).annotate({
68
+ identifier: "Datum",
69
+ description: "A single data row: a flat record of JSON primitives."
70
+ });
71
+ /** Inline data: small datasets embedded directly in the spec. */
72
+ const InlineData = Schema.Struct({ values: Schema.Array(Datum) }).annotate({
73
+ identifier: "InlineData",
74
+ description: "Inline data: small datasets embedded directly in the spec."
75
+ });
76
+ /** The kind of an external data reference. */
77
+ const DataRefKind = Schema.Literals([
78
+ "query",
79
+ "node",
80
+ "url"
81
+ ]).annotate({
82
+ identifier: "DataRefKind",
83
+ description: "The kind of external data reference: a live query, another content node, or a URL."
84
+ });
85
+ /** A reference to an externally stored dataset, resolved by the host/interpreter. */
86
+ const DataRef = Schema.Struct({ ref: Schema.Struct({
87
+ kind: DataRefKind,
88
+ id: Schema.String.annotate({ description: "Identifier of the referenced dataset (query id, node id, or URL)." })
89
+ }) }).annotate({
90
+ identifier: "DataRef",
91
+ description: "A reference to an externally stored dataset, resolved by the host."
92
+ });
93
+ /**
94
+ * The chart's data: exactly one of inline values or an external reference.
95
+ *
96
+ * `oneOf` mode makes the exclusivity explicit in the emitted JSON Schema,
97
+ * which keeps Swift/Kotlin codegen unambiguous.
98
+ */
99
+ const Data = Schema.Union([InlineData, DataRef], { mode: "oneOf" }).annotate({
100
+ identifier: "Data",
101
+ description: "The chart data: exactly one of inline values or an external reference."
102
+ });
103
+ // src/spec/internal/encoding.ts
104
+ /**
105
+ * The declared type of an encoded field (the Vega-Lite model).
106
+ *
107
+ * Explicit types are non-negotiable: JSON has no dates and no ordering, so
108
+ * every interpreter (TS, Swift, Kotlin) reconstructs typed plottable values
109
+ * from these declarations — never by sniffing JSON runtime values.
110
+ */
111
+ const FieldType = Schema.Literals([
112
+ "quantitative",
113
+ "ordinal",
114
+ "nominal",
115
+ "temporal"
116
+ ]).annotate({
117
+ identifier: "FieldType",
118
+ description: "The declared type of an encoded field. Interpreters reconstruct typed values from this declaration, never by sniffing JSON values."
119
+ });
120
+ /** A mapping from a data field to a visual channel, with an explicit type. */
121
+ const Channel = Schema.Struct({
122
+ field: Schema.String.annotate({ description: "The name of the field in each datum that this channel reads." }),
123
+ type: FieldType
124
+ }).annotate({
125
+ identifier: "Channel",
126
+ description: "A field-to-channel mapping with an explicit declared type."
127
+ });
128
+ /** Field-to-channel mappings. */
129
+ const Encoding = Schema.Struct({
130
+ x: Schema.optionalKey(Channel),
131
+ y: Schema.optionalKey(Channel),
132
+ x2: Schema.optionalKey(Channel.annotate({ description: "Spec 1.1: the end of a range on x. With mark \"bar\", each bar spans x to x2. Same type as x, quantitative or temporal." })),
133
+ y2: Schema.optionalKey(Channel.annotate({ description: "Spec 1.1: the end of a range on y. With mark \"bar\", each bar spans y to y2. Same type as y, quantitative or temporal." })),
134
+ id: Schema.optionalKey(Channel.annotate({ description: "Spec 1.1, mark \"tree\": the field naming each row; ids are unique." })),
135
+ parent: Schema.optionalKey(Channel.annotate({ description: "Spec 1.1, mark \"tree\": the field holding the parent row's id; null (or absent) for a root." })),
136
+ label: Schema.optionalKey(Channel.annotate({ description: "Spec 1.1, mark \"tree\": the text of each node; defaults to its id." })),
137
+ color: Schema.optionalKey(Channel),
138
+ size: Schema.optionalKey(Channel),
139
+ opacity: Schema.optionalKey(Channel)
140
+ }).annotate({
141
+ identifier: "Encoding",
142
+ description: "Field-to-channel mappings describing how data maps to visual channels."
143
+ });
144
+ // src/spec/internal/extensions.ts
145
+ /**
146
+ * Web-only, non-portable hints (e.g. preferred renderer, interaction
147
+ * descriptors). Native interpreters ignore this entirely; web interpreters
148
+ * must degrade gracefully when an extension is absent.
149
+ */
150
+ const Extensions = Schema.Record(Schema.String, Schema.Unknown).annotate({
151
+ identifier: "Extensions",
152
+ description: "Web-only, non-portable hints. Native interpreters ignore this entirely."
153
+ });
154
+ // src/spec/internal/mark.ts
155
+ /**
156
+ * The mark set: the intersection of the native charting vocabularies
157
+ * (Swift Charts BarMark/LineMark/PointMark/AreaMark and Compose
158
+ * equivalents), plus `tree` (spec 1.1), the first mark outside it: laid
159
+ * out by the core, with a normative degradation in every interpreter.
160
+ */
161
+ const Mark = Schema.Literals([
162
+ "bar",
163
+ "line",
164
+ "point",
165
+ "area",
166
+ "tree"
167
+ ]).annotate({
168
+ identifier: "Mark",
169
+ description: "The mark (geometric primitive) used to render the chart. The native-charting intersection, plus \"tree\" (spec 1.1): a hierarchy laid out by the interpreter."
170
+ });
171
+ /** Layout options of the `tree` mark (spec 1.1). Grammar-level only: no pixels. */
172
+ const TreeOptions = Schema.Struct({
173
+ orientation: Schema.optionalKey(Schema.Literals(["vertical", "horizontal"]).annotate({ description: "Vertical: roots at the top. Horizontal: roots at the start edge." })),
174
+ linkStyle: Schema.optionalKey(Schema.Literals(["orthogonal", "curved"]).annotate({ description: "How a parent joins its children." }))
175
+ }).annotate({
176
+ identifier: "TreeOptions",
177
+ description: "Spec 1.1: layout options of the \"tree\" mark. Only valid with mark \"tree\"."
178
+ });
179
+ // src/spec/internal/scales.ts
180
+ /** A domain entry: a number (quantitative), ISO string (temporal), or category value. */
181
+ const DomainValue = Schema.Union([Schema.Finite, Schema.String]).annotate({
182
+ identifier: "DomainValue",
183
+ description: "A domain entry: a number for quantitative, an ISO 8601 string for temporal, or a category value for ordinal/nominal."
184
+ });
185
+ /** Optional per-scale hints. Interpreters may infer or override these. */
186
+ const ScaleHint = Schema.Struct({
187
+ domain: Schema.optionalKey(Schema.Array(DomainValue)),
188
+ nice: Schema.optionalKey(Schema.Boolean.annotate({ description: "Round the domain to human-friendly values." })),
189
+ zero: Schema.optionalKey(Schema.Boolean.annotate({ description: "Force the domain to include zero." }))
190
+ }).annotate({
191
+ identifier: "ScaleHint",
192
+ description: "Optional scale hints. Interpreters may infer or override these."
193
+ });
194
+ /** Per-channel scale hints. */
195
+ const Scales = Schema.Struct({
196
+ x: Schema.optionalKey(ScaleHint),
197
+ y: Schema.optionalKey(ScaleHint)
198
+ }).annotate({
199
+ identifier: "Scales",
200
+ description: "Per-channel scale hints."
201
+ });
202
+ // src/spec/internal/spec.ts
203
+ /** The spec versions this interpreter accepts (`'1.1'` adds the `x2` / `y2` channels and the `tree` mark). */
204
+ const SpecVersion = Schema.Literals(["1", "1.1"]).annotate({
205
+ identifier: "SpecVersion",
206
+ description: "The portable chart spec version this document conforms to."
207
+ });
208
+ /**
209
+ * The portable chart specification (Layer 1).
210
+ *
211
+ * The durable, declarative, language-neutral chart description. Grammar-level
212
+ * only: marks and encodings, never geometry, pixels, or renderer names.
213
+ */
214
+ const ChartSpec = Schema.Struct({
215
+ specVersion: SpecVersion,
216
+ mark: Mark,
217
+ encoding: Encoding,
218
+ data: Data,
219
+ scales: Schema.optionalKey(Scales),
220
+ config: Schema.optionalKey(Config),
221
+ extensions: Schema.optionalKey(Extensions),
222
+ tree: Schema.optionalKey(TreeOptions)
223
+ }).annotate({
224
+ identifier: "ChartSpec",
225
+ title: "Portable Chart Specification",
226
+ description: "A renderer-agnostic, framework-agnostic, language-neutral chart description. Grammar-of-graphics level: mark + encodings + declared data types + data reference."
227
+ });
228
+ // src/spec/parse.ts
229
+ /**
230
+ * The Effect isolation boundary.
231
+ *
232
+ * Effect v4 Schema is a beta surface; it is used only inside this package.
233
+ * Consumers get plain functions, plain types, and plain errors — nothing
234
+ * Effect-flavoured crosses the package boundary.
235
+ */
236
+ /**
237
+ * Strict parse options, matching the emitted JSON Schema
238
+ * (`additionalProperties: false`): unknown keys are rejected, and all
239
+ * errors are collected so validation messages are complete.
240
+ */
241
+ const parseOptions = {
242
+ onExcessProperty: "error",
243
+ errors: "all"
244
+ };
245
+ const decodeChartSpec = Schema.decodeUnknownResult(ChartSpec, parseOptions);
246
+ const encode = Schema.encodeUnknownSync(ChartSpec, parseOptions);
247
+ /** A range channel and the start channel it pairs with. */
248
+ const RANGE_PAIRS = [["x2", "x"], ["y2", "y"]];
249
+ /** The errors of one range channel, `end`, against its start channel. */
250
+ function rangePairErrors(spec, end, start) {
251
+ const channel = spec.encoding[end];
252
+ if (channel === void 0) return [];
253
+ const startChannel = spec.encoding[start];
254
+ const at = `["encoding"]["${end}"]`;
255
+ return Arr.getSomes([
256
+ spec.specVersion === "1" ? Option.some(`${at}: range channels need specVersion "1.1"`) : Option.none(),
257
+ spec.mark === "bar" ? Option.none() : Option.some(`${at}: range channels are only valid with mark "bar"`),
258
+ startChannel === void 0 ? Option.some(`${at}: needs "${start}" as the start of the range`) : startChannel.type === channel.type ? Option.none() : Option.some(`${at}: type must match "${start}" (${startChannel.type})`),
259
+ channel.type === "quantitative" || channel.type === "temporal" ? Option.none() : Option.some(`${at}: must be quantitative or temporal`)
260
+ ]);
261
+ }
262
+ /**
263
+ * The cross-field rules the schema cannot express (and the JSON Schema
264
+ * therefore does not carry): a range channel (`x2` / `y2`) needs spec
265
+ * version `'1.1'`, `mark: 'bar'`, and its start channel with the same
266
+ * `quantitative` or `temporal` type.
267
+ */
268
+ function rangeErrors(spec) {
269
+ return Arr.flatMap(RANGE_PAIRS, ([end, start]) => rangePairErrors(spec, end, start));
270
+ }
271
+ /** The channels only the `tree` mark takes, and the positional ones it does not. */
272
+ const TREE_CHANNELS = [
273
+ "id",
274
+ "parent",
275
+ "label"
276
+ ];
277
+ const POSITIONAL_CHANNELS = [
278
+ "x",
279
+ "y",
280
+ "x2",
281
+ "y2"
282
+ ];
283
+ /**
284
+ * The `tree` mark's rules (spec 1.1): it needs `'1.1'` and `id` + `parent`,
285
+ * takes no positional channel (position is laid out, not encoded), and its
286
+ * channels and `tree` options are invalid on any other mark. The row-level
287
+ * rules (unique ids, known parents, no cycles) need the rows, so the core
288
+ * checks them when it lays the tree out.
289
+ */
290
+ function treeErrors(spec) {
291
+ const present = (names) => Arr.filter(names, (name) => spec.encoding[name] !== void 0);
292
+ if (spec.mark !== "tree") return Arr.appendAll(Arr.map(present(TREE_CHANNELS), (name) => `["encoding"]["${name}"]: only valid with mark "tree"`), spec.tree === void 0 ? [] : ["[\"tree\"]: only valid with mark \"tree\""]);
293
+ return Arr.getSomes([
294
+ spec.specVersion === "1" ? Option.some("[\"mark\"]: \"tree\" needs specVersion \"1.1\"") : Option.none(),
295
+ spec.encoding.id === void 0 ? Option.some("[\"encoding\"][\"id\"]: mark \"tree\" needs an id channel") : Option.none(),
296
+ spec.encoding.parent === void 0 ? Option.some("[\"encoding\"][\"parent\"]: mark \"tree\" needs a parent channel") : Option.none(),
297
+ ...Arr.map(present(POSITIONAL_CHANNELS), (name) => Option.some(`["encoding"]["${name}"]: mark "tree" lays out position; it takes no "${name}"`))
298
+ ]);
299
+ }
300
+ function decode(input) {
301
+ return Result.match(decodeChartSpec(input), {
302
+ onSuccess: (value) => Arr.match(Arr.appendAll(rangeErrors(value), treeErrors(value)), {
303
+ onEmpty: () => ({
304
+ ok: true,
305
+ value
306
+ }),
307
+ onNonEmpty: (errors) => ({
308
+ ok: false,
309
+ errors
310
+ })
311
+ }),
312
+ onFailure: (error) => ({
313
+ ok: false,
314
+ errors: [error.message]
315
+ })
316
+ });
317
+ }
318
+ /** Thrown when a value is not a valid portable chart spec. */
319
+ var SpecValidationError = class extends Error {
320
+ name = "SpecValidationError";
321
+ constructor(message) {
322
+ super(message);
323
+ }
324
+ };
325
+ /**
326
+ * Parses an unknown JSON value into a {@link ChartSpec}.
327
+ *
328
+ * @throws {SpecValidationError} when the value is not a valid spec.
329
+ */
330
+ function parseSpec(input) {
331
+ const result = decode(input);
332
+ if (!result.ok) throw new SpecValidationError(result.errors.join("\n"));
333
+ return result.value;
334
+ }
335
+ /**
336
+ * Validates an unknown JSON value against the portable chart spec,
337
+ * returning a result instead of throwing.
338
+ */
339
+ function validateSpec(input) {
340
+ return decode(input);
341
+ }
342
+ /**
343
+ * Encodes a {@link ChartSpec} back to a plain JSON-safe value, suitable for
344
+ * persisting (e.g. into ProseMirror node attrs).
345
+ */
346
+ function encodeSpec(spec) {
347
+ return encode(spec);
348
+ }
349
+ export { isDataRef as a, validateSpec as i, encodeSpec as n, isInlineData as o, parseSpec as r, SPEC_VERSION as s, SpecValidationError as t };
350
+
351
+ //# sourceMappingURL=spec-FF2Tds2a.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-FF2Tds2a.mjs","names":["ChartSpecSchema"],"sources":["../src/spec/types.ts","../src/spec/guards.ts","../src/spec/internal/config.ts","../src/spec/internal/data.ts","../src/spec/internal/encoding.ts","../src/spec/internal/extensions.ts","../src/spec/internal/mark.ts","../src/spec/internal/scales.ts","../src/spec/internal/spec.ts","../src/spec/parse.ts"],"sourcesContent":["/**\n * Public, dependency-free TypeScript types for the portable chart specification.\n *\n * These are hand-authored mirrors of the Effect Schema definitions in\n * `internal/`. They are the only types consumers see — no Effect types leak\n * through the package boundary. A type-parity test asserts these stay\n * structurally identical to the schema-derived types.\n *\n * Design invariants (see the README § Capability matrix):\n * - Grammar-level only: marks and encodings, never geometry, pixels, or\n * renderer names.\n * - Every encoding channel carries an explicit `type`; interpreters\n * reconstruct typed values from declared types, never by sniffing JSON.\n * - The portable subset targets the intersection of native charting\n * vocabularies (Swift Charts, Compose). Web-only richness goes in\n * `extensions` and degrades gracefully.\n * - Optional members are bare `prop?: T`, the one exception to the\n * workspace's `T | undefined` rule: these types describe a JSON document,\n * where a key is present with a value or absent and `undefined` does not\n * exist. The schemas say the same (`Schema.optionalKey`), so `parseSpec`\n * rejects a present-but-undefined key, and widening either side would make\n * the emitted JSON Schema admit `null`.\n */\n\n/** The current portable chart spec version; what a new spec should declare. */\nexport const SPEC_VERSION = '1.1';\n\n/**\n * The spec versions this interpreter accepts. `'1.1'` adds the `x2` / `y2`\n * range channels and the `tree` mark; every `'1'` spec is a valid `'1.1'`\n * spec. See the README § Spec versioning for the minor-version rule.\n */\nexport type SpecVersion = '1' | '1.1';\n\n/**\n * The mark (geometric primitive) used to render the chart.\n *\n * The intersection of the native charting vocabularies (Swift Charts\n * BarMark/LineMark/PointMark/AreaMark and their Compose equivalents), plus\n * `tree` (spec 1.1): a hierarchy of labelled nodes joined by links, laid\n * out by the core. It is the first mark outside the intersection, allowed\n * because the core lays it out with no renderer dependency and every\n * interpreter column has a normative degradation (README § Capability matrix).\n */\nexport type Mark = 'bar' | 'line' | 'point' | 'area' | 'tree';\n\n/** Layout options of the `tree` mark (spec 1.1). Grammar-level only: no pixels. */\nexport interface TreeOptions {\n /** `vertical`: roots at the top (default). `horizontal`: roots at the start edge. */\n readonly orientation?: 'vertical' | 'horizontal';\n /** How a parent joins its children (default `orthogonal`). */\n readonly linkStyle?: 'orthogonal' | 'curved';\n}\n\n/**\n * The declared type of an encoded field.\n *\n * - `quantitative` — continuous numeric values\n * - `temporal` — points in time, encoded as ISO 8601 strings or epoch milliseconds\n * - `ordinal` — discrete, ordered categories\n * - `nominal` — discrete, unordered categories\n */\nexport type FieldType = 'quantitative' | 'ordinal' | 'nominal' | 'temporal';\n\n/** A mapping from a data field to a visual channel, with an explicit type. */\nexport interface Channel {\n /** The name of the field in each datum that this channel reads. */\n readonly field: string;\n /** The declared type of the field. Interpreters must respect this, never sniff values. */\n readonly type: FieldType;\n}\n\n/** Field-to-channel mappings. At minimum, `x` and `y` are expected for all v1 marks. */\nexport interface Encoding {\n readonly x?: Channel;\n readonly y?: Channel;\n /**\n * The end of a range on the x axis (spec 1.1): with `mark: 'bar'`, each\n * bar spans `x` to `x2`, a range bar (a Gantt row). Same `type` as `x`,\n * `quantitative` or `temporal`.\n */\n readonly x2?: Channel;\n /** The end of a range on the y axis (spec 1.1), as `x2` is for x. */\n readonly y2?: Channel;\n /** Spec 1.1, mark `tree`: the field naming each row. Ids are unique. */\n readonly id?: Channel;\n /** Spec 1.1, mark `tree`: the field holding the parent row's id; `null` or absent for a root. */\n readonly parent?: Channel;\n /** Spec 1.1, mark `tree`: the text of each node; defaults to its id. */\n readonly label?: Channel;\n readonly color?: Channel;\n readonly size?: Channel;\n readonly opacity?: Channel;\n}\n\n/** A single value inside a datum. Inline data rows are flat records of JSON primitives. */\nexport type DatumValue = string | number | boolean | null;\n\n/** A single data row. */\nexport type Datum = { readonly [field: string]: DatumValue };\n\n/** Inline data: small datasets embedded directly in the spec. */\nexport interface InlineData {\n readonly values: ReadonlyArray<Datum>;\n}\n\n/**\n * The kind of an external data reference.\n *\n * - `query` — a live query (e.g. a Durable Object or database query) known to the host\n * - `node` — another content node in the host document store\n * - `url` — an addressable resource returning JSON rows\n */\nexport type DataRefKind = 'query' | 'node' | 'url';\n\n/** A reference to an externally stored dataset, resolved by the host/interpreter. */\nexport interface DataRef {\n readonly ref: {\n readonly kind: DataRefKind;\n readonly id: string;\n };\n}\n\n/** The chart's data: exactly one of inline values or an external reference. */\nexport type Data = InlineData | DataRef;\n\n/**\n * Optional per-scale hints. Interpreters may infer or override these;\n * they are hints, not geometry.\n */\nexport interface ScaleHint {\n /**\n * Explicit domain. Numbers for quantitative, ISO strings for temporal,\n * category values for ordinal/nominal.\n */\n readonly domain?: ReadonlyArray<number | string>;\n /** Round the domain to human-friendly values. */\n readonly nice?: boolean;\n /** Force the domain to include zero. */\n readonly zero?: boolean;\n}\n\n/** Per-channel scale hints. */\nexport interface Scales {\n readonly x?: ScaleHint;\n readonly y?: ScaleHint;\n}\n\n/** Portable presentation hints honoured (or gracefully ignored) by every interpreter. */\nexport interface Config {\n readonly title?: string;\n /** Whether tooltips are enabled. A trivial declarative hint — real interaction is per-platform. */\n readonly tooltip?: boolean;\n}\n\n/**\n * Web-only, non-portable hints (e.g. preferred renderer, interaction\n * descriptors). Native interpreters ignore this entirely.\n */\nexport type Extensions = { readonly [key: string]: unknown };\n\n/**\n * The portable chart specification (Layer 1).\n *\n * This is the durable, cross-platform contract. It is pure JSON,\n * grammar-level, and versioned. Everything else — geometry cores, framework\n * components, renderers — is a per-platform interpreter of this spec.\n */\nexport interface ChartSpec {\n readonly specVersion: SpecVersion;\n readonly mark: Mark;\n readonly encoding: Encoding;\n readonly data: Data;\n readonly scales?: Scales;\n readonly config?: Config;\n readonly extensions?: Extensions;\n /** Spec 1.1: layout options of the `tree` mark; only valid with it. */\n readonly tree?: TreeOptions;\n}\n","import type { Data, DataRef, InlineData } from './types.ts';\n\n/** Returns true if the spec's data is inline values. */\nexport function isInlineData(data: Data): data is InlineData {\n return 'values' in data;\n}\n\n/** Returns true if the spec's data is an external reference. */\nexport function isDataRef(data: Data): data is DataRef {\n return 'ref' in data;\n}\n","import * as Schema from 'effect/Schema';\n\n// The `typeof X.Type` aliases below deliberately share their names with the\n// public mirror types in `../types.ts`: `tests/spec/type-parity.test.ts`\n// asserts each pair equal. Neither barrel re-exports this module.\n// fallow-ignore-file duplicate-export\n\n/** Portable presentation hints honoured (or gracefully ignored) by every interpreter. */\nexport const Config = Schema.Struct({\n title: Schema.optionalKey(Schema.String.annotate({ description: 'The chart title.' })),\n tooltip: Schema.optionalKey(\n Schema.Boolean.annotate({\n description:\n 'Whether tooltips are enabled. A trivial declarative hint — ' +\n 'real interaction is per-platform application code, not document data.',\n }),\n ),\n}).annotate({\n identifier: 'Config',\n description: 'Portable presentation hints.',\n});\n\n/** The decoded type of {@link Config}; the public mirror is `Config` in `../types.ts`. */\nexport type Config = typeof Config.Type;\n","import * as Schema from 'effect/Schema';\n\n// The `typeof X.Type` aliases below deliberately share their names with the\n// public mirror types in `../types.ts`: `tests/spec/type-parity.test.ts`\n// asserts each pair equal. Neither barrel re-exports this module.\n// fallow-ignore-file duplicate-export\n\n/**\n * A single value inside a datum: a flat JSON primitive.\n *\n * `Schema.Finite` (not `Schema.Number`) so non-finite values (NaN, Infinity)\n * are rejected and the emitted JSON Schema is a plain `{\"type\": \"number\"}` —\n * portable data must be representable in JSON on every platform.\n */\nconst DatumValue = Schema.Union([\n Schema.String,\n Schema.Finite,\n Schema.Boolean,\n Schema.Null,\n]).annotate({\n identifier: 'DatumValue',\n description: 'A single value inside a data row: a flat JSON primitive.',\n});\n\n/** A single data row: a flat record of JSON primitives. */\nconst Datum = Schema.Record(Schema.String, DatumValue).annotate({\n identifier: 'Datum',\n description: 'A single data row: a flat record of JSON primitives.',\n});\n\n/** Inline data: small datasets embedded directly in the spec. */\nconst InlineData = Schema.Struct({\n values: Schema.Array(Datum),\n}).annotate({\n identifier: 'InlineData',\n description: 'Inline data: small datasets embedded directly in the spec.',\n});\n\n/** The kind of an external data reference. */\nconst DataRefKind = Schema.Literals(['query', 'node', 'url']).annotate({\n identifier: 'DataRefKind',\n description: 'The kind of external data reference: a live query, another content node, or a URL.',\n});\n\n/** A reference to an externally stored dataset, resolved by the host/interpreter. */\nconst DataRef = Schema.Struct({\n ref: Schema.Struct({\n kind: DataRefKind,\n id: Schema.String.annotate({\n description: 'Identifier of the referenced dataset (query id, node id, or URL).',\n }),\n }),\n}).annotate({\n identifier: 'DataRef',\n description: 'A reference to an externally stored dataset, resolved by the host.',\n});\n\n/**\n * The chart's data: exactly one of inline values or an external reference.\n *\n * `oneOf` mode makes the exclusivity explicit in the emitted JSON Schema,\n * which keeps Swift/Kotlin codegen unambiguous.\n */\nexport const Data = Schema.Union([InlineData, DataRef], { mode: 'oneOf' }).annotate({\n identifier: 'Data',\n description: 'The chart data: exactly one of inline values or an external reference.',\n});\n\n/** The decoded type of {@link DatumValue}; the public mirror is `DatumValue` in `../types.ts`. */\nexport type DatumValue = typeof DatumValue.Type;\n\n/** The decoded type of {@link Datum}; the public mirror is `Datum` in `../types.ts`. */\nexport type Datum = typeof Datum.Type;\n\n/** The decoded type of {@link InlineData}; the public mirror is `InlineData` in `../types.ts`. */\nexport type InlineData = typeof InlineData.Type;\n\n/** The decoded type of {@link DataRefKind}; the public mirror is `DataRefKind` in `../types.ts`. */\nexport type DataRefKind = typeof DataRefKind.Type;\n\n/** The decoded type of {@link DataRef}; the public mirror is `DataRef` in `../types.ts`. */\nexport type DataRef = typeof DataRef.Type;\n\n/** The decoded type of {@link Data}; the public mirror is `Data` in `../types.ts`. */\nexport type Data = typeof Data.Type;\n","import * as Schema from 'effect/Schema';\n\n// The `typeof X.Type` aliases below deliberately share their names with the\n// public mirror types in `../types.ts`: `tests/spec/type-parity.test.ts`\n// asserts each pair equal. Neither barrel re-exports this module.\n// fallow-ignore-file duplicate-export\n\n/**\n * The declared type of an encoded field (the Vega-Lite model).\n *\n * Explicit types are non-negotiable: JSON has no dates and no ordering, so\n * every interpreter (TS, Swift, Kotlin) reconstructs typed plottable values\n * from these declarations — never by sniffing JSON runtime values.\n */\nconst FieldType = Schema.Literals(['quantitative', 'ordinal', 'nominal', 'temporal']).annotate({\n identifier: 'FieldType',\n description:\n 'The declared type of an encoded field. Interpreters reconstruct typed values ' +\n 'from this declaration, never by sniffing JSON values.',\n});\n\n/** A mapping from a data field to a visual channel, with an explicit type. */\nconst Channel = Schema.Struct({\n field: Schema.String.annotate({\n description: 'The name of the field in each datum that this channel reads.',\n }),\n type: FieldType,\n}).annotate({\n identifier: 'Channel',\n description: 'A field-to-channel mapping with an explicit declared type.',\n});\n\n/** Field-to-channel mappings. */\nexport const Encoding = Schema.Struct({\n x: Schema.optionalKey(Channel),\n y: Schema.optionalKey(Channel),\n x2: Schema.optionalKey(\n Channel.annotate({\n description:\n 'Spec 1.1: the end of a range on x. With mark \"bar\", each bar spans x to x2. Same type as x, quantitative or temporal.',\n }),\n ),\n y2: Schema.optionalKey(\n Channel.annotate({\n description:\n 'Spec 1.1: the end of a range on y. With mark \"bar\", each bar spans y to y2. Same type as y, quantitative or temporal.',\n }),\n ),\n id: Schema.optionalKey(\n Channel.annotate({\n description: 'Spec 1.1, mark \"tree\": the field naming each row; ids are unique.',\n }),\n ),\n parent: Schema.optionalKey(\n Channel.annotate({\n description:\n 'Spec 1.1, mark \"tree\": the field holding the parent row\\'s id; null (or absent) for a root.',\n }),\n ),\n label: Schema.optionalKey(\n Channel.annotate({\n description: 'Spec 1.1, mark \"tree\": the text of each node; defaults to its id.',\n }),\n ),\n color: Schema.optionalKey(Channel),\n size: Schema.optionalKey(Channel),\n opacity: Schema.optionalKey(Channel),\n}).annotate({\n identifier: 'Encoding',\n description: 'Field-to-channel mappings describing how data maps to visual channels.',\n});\n\n/** The decoded type of {@link FieldType}; the public mirror is `FieldType` in `../types.ts`. */\nexport type FieldType = typeof FieldType.Type;\n\n/** The decoded type of {@link Channel}; the public mirror is `Channel` in `../types.ts`. */\nexport type Channel = typeof Channel.Type;\n\n/** The decoded type of {@link Encoding}; the public mirror is `Encoding` in `../types.ts`. */\nexport type Encoding = typeof Encoding.Type;\n","import * as Schema from 'effect/Schema';\n\n// The `typeof X.Type` aliases below deliberately share their names with the\n// public mirror types in `../types.ts`: `tests/spec/type-parity.test.ts`\n// asserts each pair equal. Neither barrel re-exports this module.\n// fallow-ignore-file duplicate-export\n\n/**\n * Web-only, non-portable hints (e.g. preferred renderer, interaction\n * descriptors). Native interpreters ignore this entirely; web interpreters\n * must degrade gracefully when an extension is absent.\n */\nexport const Extensions = Schema.Record(Schema.String, Schema.Unknown).annotate({\n identifier: 'Extensions',\n description: 'Web-only, non-portable hints. Native interpreters ignore this entirely.',\n});\n\n/** The decoded type of {@link Extensions}; the public mirror is `Extensions` in `../types.ts`. */\nexport type Extensions = typeof Extensions.Type;\n","import * as Schema from 'effect/Schema';\n\n// The `typeof X.Type` aliases below deliberately share their names with the\n// public mirror types in `../types.ts`: `tests/spec/type-parity.test.ts`\n// asserts each pair equal. Neither barrel re-exports this module.\n// fallow-ignore-file duplicate-export\n\n/**\n * The mark set: the intersection of the native charting vocabularies\n * (Swift Charts BarMark/LineMark/PointMark/AreaMark and Compose\n * equivalents), plus `tree` (spec 1.1), the first mark outside it: laid\n * out by the core, with a normative degradation in every interpreter.\n */\nexport const Mark = Schema.Literals(['bar', 'line', 'point', 'area', 'tree']).annotate({\n identifier: 'Mark',\n description:\n 'The mark (geometric primitive) used to render the chart. The native-charting intersection, ' +\n 'plus \"tree\" (spec 1.1): a hierarchy laid out by the interpreter.',\n});\n\n/** Layout options of the `tree` mark (spec 1.1). Grammar-level only: no pixels. */\nexport const TreeOptions = Schema.Struct({\n orientation: Schema.optionalKey(\n Schema.Literals(['vertical', 'horizontal']).annotate({\n description: 'Vertical: roots at the top. Horizontal: roots at the start edge.',\n }),\n ),\n linkStyle: Schema.optionalKey(\n Schema.Literals(['orthogonal', 'curved']).annotate({\n description: 'How a parent joins its children.',\n }),\n ),\n}).annotate({\n identifier: 'TreeOptions',\n description: 'Spec 1.1: layout options of the \"tree\" mark. Only valid with mark \"tree\".',\n});\n\n/** The decoded type of {@link TreeOptions}; the public mirror is `TreeOptions` in `../types.ts`. */\nexport type TreeOptions = typeof TreeOptions.Type;\n\n/** The decoded type of {@link Mark}; the public mirror is `Mark` in `../types.ts`. */\nexport type Mark = typeof Mark.Type;\n","import * as Schema from 'effect/Schema';\n\n// The `typeof X.Type` aliases below deliberately share their names with the\n// public mirror types in `../types.ts`: `tests/spec/type-parity.test.ts`\n// asserts each pair equal. Neither barrel re-exports this module.\n// fallow-ignore-file duplicate-export\n\n/** A domain entry: a number (quantitative), ISO string (temporal), or category value. */\nconst DomainValue = Schema.Union([Schema.Finite, Schema.String]).annotate({\n identifier: 'DomainValue',\n description:\n 'A domain entry: a number for quantitative, an ISO 8601 string for temporal, ' +\n 'or a category value for ordinal/nominal.',\n});\n\n/** Optional per-scale hints. Interpreters may infer or override these. */\nconst ScaleHint = Schema.Struct({\n domain: Schema.optionalKey(Schema.Array(DomainValue)),\n nice: Schema.optionalKey(\n Schema.Boolean.annotate({\n description: 'Round the domain to human-friendly values.',\n }),\n ),\n zero: Schema.optionalKey(\n Schema.Boolean.annotate({\n description: 'Force the domain to include zero.',\n }),\n ),\n}).annotate({\n identifier: 'ScaleHint',\n description: 'Optional scale hints. Interpreters may infer or override these.',\n});\n\n/** Per-channel scale hints. */\nexport const Scales = Schema.Struct({\n x: Schema.optionalKey(ScaleHint),\n y: Schema.optionalKey(ScaleHint),\n}).annotate({\n identifier: 'Scales',\n description: 'Per-channel scale hints.',\n});\n\n/** The decoded type of {@link ScaleHint}; the public mirror is `ScaleHint` in `../types.ts`. */\nexport type ScaleHint = typeof ScaleHint.Type;\n\n/** The decoded type of {@link Scales}; the public mirror is `Scales` in `../types.ts`. */\nexport type Scales = typeof Scales.Type;\n","import * as Schema from 'effect/Schema';\nimport { Config } from './config.ts';\nimport { Data } from './data.ts';\nimport { Encoding } from './encoding.ts';\nimport { Extensions } from './extensions.ts';\nimport { Mark, TreeOptions } from './mark.ts';\nimport { Scales } from './scales.ts';\n\n// The `typeof X.Type` aliases below deliberately share their names with the\n// public mirror types in `../types.ts`: `tests/spec/type-parity.test.ts`\n// asserts each pair equal. Neither barrel re-exports this module.\n// fallow-ignore-file duplicate-export\n\n/** The spec versions this interpreter accepts (`'1.1'` adds the `x2` / `y2` channels and the `tree` mark). */\nconst SpecVersion = Schema.Literals(['1', '1.1']).annotate({\n identifier: 'SpecVersion',\n description: 'The portable chart spec version this document conforms to.',\n});\n\n/**\n * The portable chart specification (Layer 1).\n *\n * The durable, declarative, language-neutral chart description. Grammar-level\n * only: marks and encodings, never geometry, pixels, or renderer names.\n */\nexport const ChartSpec = Schema.Struct({\n specVersion: SpecVersion,\n mark: Mark,\n encoding: Encoding,\n data: Data,\n scales: Schema.optionalKey(Scales),\n config: Schema.optionalKey(Config),\n extensions: Schema.optionalKey(Extensions),\n tree: Schema.optionalKey(TreeOptions),\n}).annotate({\n identifier: 'ChartSpec',\n title: 'Portable Chart Specification',\n description:\n 'A renderer-agnostic, framework-agnostic, language-neutral chart description. ' +\n 'Grammar-of-graphics level: mark + encodings + declared data types + data reference.',\n});\n\n/** The decoded type of {@link SpecVersion}; the public mirror is `SpecVersion` in `../types.ts`. */\nexport type SpecVersion = typeof SpecVersion.Type;\n\n/** The decoded type of {@link ChartSpec}; the public mirror is `ChartSpec` in `../types.ts`. */\nexport type ChartSpec = typeof ChartSpec.Type;\n","import * as Arr from 'effect/Array';\nimport * as Option from 'effect/Option';\nimport * as Result from 'effect/Result';\nimport * as Schema from 'effect/Schema';\nimport { ChartSpec as ChartSpecSchema } from './internal/spec.ts';\nimport type { ChartSpec } from './types.ts';\n\n/**\n * The Effect isolation boundary.\n *\n * Effect v4 Schema is a beta surface; it is used only inside this package.\n * Consumers get plain functions, plain types, and plain errors — nothing\n * Effect-flavoured crosses the package boundary.\n */\n\n/**\n * Strict parse options, matching the emitted JSON Schema\n * (`additionalProperties: false`): unknown keys are rejected, and all\n * errors are collected so validation messages are complete.\n */\nconst parseOptions = { onExcessProperty: 'error', errors: 'all' } as const;\n\nconst decodeChartSpec = Schema.decodeUnknownResult(ChartSpecSchema, parseOptions);\nconst encode = Schema.encodeUnknownSync(ChartSpecSchema, parseOptions);\n\n/** A range channel and the start channel it pairs with. */\nconst RANGE_PAIRS = [\n ['x2', 'x'],\n ['y2', 'y'],\n] as const;\n\n/** The errors of one range channel, `end`, against its start channel. */\nfunction rangePairErrors(\n spec: ChartSpec,\n end: 'x2' | 'y2',\n start: 'x' | 'y',\n): ReadonlyArray<string> {\n const channel = spec.encoding[end];\n if (channel === undefined) return [];\n const startChannel = spec.encoding[start];\n const at = `[\"encoding\"][\"${end}\"]`;\n return Arr.getSomes([\n spec.specVersion === '1'\n ? Option.some(`${at}: range channels need specVersion \"1.1\"`)\n : Option.none(),\n spec.mark === 'bar'\n ? Option.none()\n : Option.some(`${at}: range channels are only valid with mark \"bar\"`),\n startChannel === undefined\n ? Option.some(`${at}: needs \"${start}\" as the start of the range`)\n : startChannel.type === channel.type\n ? Option.none()\n : Option.some(`${at}: type must match \"${start}\" (${startChannel.type})`),\n channel.type === 'quantitative' || channel.type === 'temporal'\n ? Option.none()\n : Option.some(`${at}: must be quantitative or temporal`),\n ]);\n}\n\n/**\n * The cross-field rules the schema cannot express (and the JSON Schema\n * therefore does not carry): a range channel (`x2` / `y2`) needs spec\n * version `'1.1'`, `mark: 'bar'`, and its start channel with the same\n * `quantitative` or `temporal` type.\n */\nfunction rangeErrors(spec: ChartSpec): ReadonlyArray<string> {\n return Arr.flatMap(RANGE_PAIRS, ([end, start]) => rangePairErrors(spec, end, start));\n}\n\n/** The channels only the `tree` mark takes, and the positional ones it does not. */\nconst TREE_CHANNELS = ['id', 'parent', 'label'] as const;\nconst POSITIONAL_CHANNELS = ['x', 'y', 'x2', 'y2'] as const;\n\n/**\n * The `tree` mark's rules (spec 1.1): it needs `'1.1'` and `id` + `parent`,\n * takes no positional channel (position is laid out, not encoded), and its\n * channels and `tree` options are invalid on any other mark. The row-level\n * rules (unique ids, known parents, no cycles) need the rows, so the core\n * checks them when it lays the tree out.\n */\nfunction treeErrors(spec: ChartSpec): ReadonlyArray<string> {\n const present = (names: ReadonlyArray<keyof ChartSpec['encoding']>) =>\n Arr.filter(names, (name) => spec.encoding[name] !== undefined);\n if (spec.mark !== 'tree') {\n return Arr.appendAll(\n Arr.map(\n present(TREE_CHANNELS),\n (name) => `[\"encoding\"][\"${name}\"]: only valid with mark \"tree\"`,\n ),\n spec.tree === undefined ? [] : ['[\"tree\"]: only valid with mark \"tree\"'],\n );\n }\n return Arr.getSomes([\n spec.specVersion === '1'\n ? Option.some('[\"mark\"]: \"tree\" needs specVersion \"1.1\"')\n : Option.none(),\n spec.encoding.id === undefined\n ? Option.some('[\"encoding\"][\"id\"]: mark \"tree\" needs an id channel')\n : Option.none(),\n spec.encoding.parent === undefined\n ? Option.some('[\"encoding\"][\"parent\"]: mark \"tree\" needs a parent channel')\n : Option.none(),\n ...Arr.map(present(POSITIONAL_CHANNELS), (name) =>\n Option.some(`[\"encoding\"][\"${name}\"]: mark \"tree\" lays out position; it takes no \"${name}\"`),\n ),\n ]);\n}\n\nfunction decode(input: unknown): ValidationResult {\n return Result.match(decodeChartSpec(input), {\n onSuccess: (value): ValidationResult =>\n Arr.match(Arr.appendAll(rangeErrors(value), treeErrors(value)), {\n onEmpty: (): ValidationResult => ({ ok: true, value }),\n onNonEmpty: (errors): ValidationResult => ({ ok: false, errors }),\n }),\n onFailure: (error): ValidationResult => ({ ok: false, errors: [error.message] }),\n });\n}\n\n/** Thrown when a value is not a valid portable chart spec. */\nexport class SpecValidationError extends Error {\n override readonly name = 'SpecValidationError';\n\n constructor(message: string) {\n super(message);\n }\n}\n\n/** The result of {@link validateSpec}. */\nexport type ValidationResult =\n | { readonly ok: true; readonly value: ChartSpec }\n | { readonly ok: false; readonly errors: ReadonlyArray<string> };\n\n/**\n * Parses an unknown JSON value into a {@link ChartSpec}.\n *\n * @throws {SpecValidationError} when the value is not a valid spec.\n */\nexport function parseSpec(input: unknown): ChartSpec {\n const result = decode(input);\n if (!result.ok) throw new SpecValidationError(result.errors.join('\\n'));\n return result.value;\n}\n\n/**\n * Validates an unknown JSON value against the portable chart spec,\n * returning a result instead of throwing.\n */\nexport function validateSpec(input: unknown): ValidationResult {\n return decode(input);\n}\n\n/**\n * Encodes a {@link ChartSpec} back to a plain JSON-safe value, suitable for\n * persisting (e.g. into ProseMirror node attrs).\n */\nexport function encodeSpec(spec: ChartSpec): unknown {\n return encode(spec);\n}\n"],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ <!--
2
+ @component Renders a portable chart spec as an SVG named by its title, or a fallback when the spec or its data is unusable.
3
+
4
+ Pure rendering: the geometry comes from
5
+ `resolveChart` in a `$derived`, so the component renders the same under
6
+ `svelte/server` and on the client. The SVG uses a viewBox, so it scales to
7
+ its container with CSS. Colours are the core palette's and the shared
8
+ interpreter greys, not daisyUI tokens: the chart must look the same as the
9
+ spec's other renderers, which have no theme.
10
+ -->
11
+ <script lang="ts">import { outlineText, resolveChart, VISUALLY_HIDDEN_STYLE } from "@thomasfosterau/charts/core";
12
+ import ChartAxes from "./ChartAxes.svelte";
13
+ import ChartMarks from "./ChartMarks.svelte";
14
+ let { spec, data, width = 640, height = 360, margins, fallback, class: className, ...rest } = $props();
15
+ const TITLE_COLOR = "#111827";
16
+ const FONT_FAMILY = "system-ui, sans-serif";
17
+ const state = $derived(resolveChart(spec, {
18
+ width,
19
+ height
20
+ }, data, { margins }));
21
+ </script>
22
+
23
+ {#if state.kind === 'error'}
24
+ {#if fallback}
25
+ {@render fallback(state.message)}
26
+ {:else}
27
+ <div class={className} role="alert" data-chart-error="">{state.message}</div>
28
+ {/if}
29
+ {:else}
30
+ {@const { dimensions, ticks, geometry } = state.context}
31
+ {@const title = state.spec.config?.title}
32
+ <svg
33
+ xmlns="http://www.w3.org/2000/svg"
34
+ role="img"
35
+ aria-label={title}
36
+ {...rest}
37
+ viewBox="0 0 {dimensions.outer.width} {dimensions.outer.height}"
38
+ class={className}
39
+ >
40
+ {#if title}
41
+ <text
42
+ x={dimensions.outer.width / 2}
43
+ y={dimensions.margins.top / 2}
44
+ text-anchor="middle"
45
+ dominant-baseline="middle"
46
+ font-family={FONT_FAMILY}
47
+ font-size="14"
48
+ font-weight="600"
49
+ fill={TITLE_COLOR}>{title}</text
50
+ >
51
+ {/if}
52
+ <g transform="translate({dimensions.margins.left},{dimensions.margins.top})">
53
+ {#if geometry.mark !== 'tree'}<ChartAxes {ticks} plot={dimensions.plot} />{/if}
54
+ <ChartMarks {geometry} />
55
+ </g>
56
+ </svg>
57
+ {#if geometry.mark === 'tree'}
58
+ <!-- A tree's children are presentational inside role="img", so its text
59
+ alternative (the nested outline) is a sibling of the SVG. -->
60
+ <div style={VISUALLY_HIDDEN_STYLE} data-chart-outline="">{@render outlineList(geometry.outline)}</div>
61
+ {/if}
62
+ {/if}
63
+
64
+ {#snippet outlineList(nodes: ReadonlyArray<TreeOutlineNode>)}
65
+ <ul>
66
+ {#each nodes as node (node.id)}
67
+ <li>
68
+ {outlineText(node)}
69
+ {#if node.children.length > 0}{@render outlineList(node.children)}{/if}
70
+ </li>
71
+ {/each}
72
+ </ul>
73
+ {/snippet}
@@ -0,0 +1,37 @@
1
+ import type { ChartSpec } from '@thomasfosterau/charts';
2
+ import type { DataRows, Margins } from '@thomasfosterau/charts/core';
3
+ import type { Snippet } from 'svelte';
4
+ import type { SVGAttributes } from 'svelte/elements';
5
+ interface Props extends Omit<SVGAttributes<SVGSVGElement>, 'viewBox'> {
6
+ /** The portable chart spec. Validated again at render time, since specs come from documents. */
7
+ spec: ChartSpec;
8
+ /** Resolved data rows. Required when the spec's data is a reference; replaces inline values when given. */
9
+ data?: DataRows | undefined;
10
+ /**
11
+ * Outer width in pixels; sets the `viewBox`, and with it the aspect ratio.
12
+ * @defaultValue 640
13
+ */
14
+ width?: number | undefined;
15
+ /**
16
+ * Outer height in pixels.
17
+ * @defaultValue 360
18
+ */
19
+ height?: number | undefined;
20
+ /** Margin overrides, merged over the core's `DEFAULT_MARGINS`. */
21
+ margins?: Partial<Margins> | undefined;
22
+ /** Rendered instead of the chart when the spec is invalid, its data is missing or the geometry fails; receives the message. */
23
+ fallback?: Snippet<[message: string]> | undefined;
24
+ }
25
+ /**
26
+ * Renders a portable chart spec as an SVG named by its title, or a fallback when the spec or its data is unusable.
27
+ *
28
+ * Pure rendering: the geometry comes from
29
+ * `resolveChart` in a `$derived`, so the component renders the same under
30
+ * `svelte/server` and on the client. The SVG uses a viewBox, so it scales to
31
+ * its container with CSS. Colours are the core palette's and the shared
32
+ * interpreter greys, not daisyUI tokens: the chart must look the same as the
33
+ * spec's other renderers, which have no theme.
34
+ */
35
+ declare const Chart: import("svelte").Component<Props, {}, "">;
36
+ type Chart = ReturnType<typeof Chart>;
37
+ export default Chart;
@@ -0,0 +1,39 @@
1
+ <!--
2
+ @component The x and y axis lines of a chart's plot area, with tick marks and labels.
3
+ -->
4
+ <script lang="ts">let { ticks, plot } = $props();
5
+ const AXIS_COLOR = "#9ca3af";
6
+ const LABEL_COLOR = "#4b5563";
7
+ const FONT_FAMILY = "system-ui, sans-serif";
8
+ export {};
9
+ </script>
10
+
11
+ <g data-axes="">
12
+ <line x1="0" y1={plot.height} x2={plot.width} y2={plot.height} stroke={AXIS_COLOR} />
13
+ <line x1="0" y1="0" x2="0" y2={plot.height} stroke={AXIS_COLOR} />
14
+ {#each ticks.x ?? [] as tick, index (index)}
15
+ <g transform="translate({tick.position},{plot.height})">
16
+ <line y2="6" stroke={AXIS_COLOR} />
17
+ <text
18
+ y="18"
19
+ text-anchor="middle"
20
+ font-family={FONT_FAMILY}
21
+ font-size="11"
22
+ fill={LABEL_COLOR}>{tick.label}</text
23
+ >
24
+ </g>
25
+ {/each}
26
+ {#each ticks.y ?? [] as tick, index (index)}
27
+ <g transform="translate(0,{tick.position})">
28
+ <line x2="-6" stroke={AXIS_COLOR} />
29
+ <text
30
+ x="-10"
31
+ text-anchor="end"
32
+ dominant-baseline="middle"
33
+ font-family={FONT_FAMILY}
34
+ font-size="11"
35
+ fill={LABEL_COLOR}>{tick.label}</text
36
+ >
37
+ </g>
38
+ {/each}
39
+ </g>
@@ -0,0 +1,11 @@
1
+ import type { Box, ChartTicks } from '@thomasfosterau/charts/core';
2
+ interface Props {
3
+ /** The tick positions and labels from `computeContext`. */
4
+ ticks: ChartTicks;
5
+ /** The plot area, in pixels relative to its own origin. */
6
+ plot: Box;
7
+ }
8
+ /** The x and y axis lines of a chart's plot area, with tick marks and labels. */
9
+ declare const ChartAxes: import("svelte").Component<Props, {}, "">;
10
+ type ChartAxes = ReturnType<typeof ChartAxes>;
11
+ export default ChartAxes;