@unovis/mcp 1.7.0-mcp.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +193 -0
- package/README.md +154 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +109 -0
- package/dist/cli.js.map +1 -0
- package/dist/codegen/index.d.ts +14 -0
- package/dist/codegen/index.js +346 -0
- package/dist/codegen/index.js.map +1 -0
- package/dist/env/bbox.d.ts +13 -0
- package/dist/env/bbox.js +335 -0
- package/dist/env/bbox.js.map +1 -0
- package/dist/env/canvas.d.ts +9 -0
- package/dist/env/canvas.js +55 -0
- package/dist/env/canvas.js.map +1 -0
- package/dist/env/computed-style.d.ts +38 -0
- package/dist/env/computed-style.js +186 -0
- package/dist/env/computed-style.js.map +1 -0
- package/dist/env/fonts.d.ts +3 -0
- package/dist/env/fonts.js +103 -0
- package/dist/env/fonts.js.map +1 -0
- package/dist/env/index.d.ts +13 -0
- package/dist/env/index.js +121 -0
- package/dist/env/index.js.map +1 -0
- package/dist/env/raf-queue.d.ts +23 -0
- package/dist/env/raf-queue.js +51 -0
- package/dist/env/raf-queue.js.map +1 -0
- package/dist/env/size.d.ts +9 -0
- package/dist/env/size.js +30 -0
- package/dist/env/size.js.map +1 -0
- package/dist/html/document.d.ts +16 -0
- package/dist/html/document.js +95 -0
- package/dist/html/document.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/recipes/area.d.ts +66 -0
- package/dist/recipes/area.js +76 -0
- package/dist/recipes/area.js.map +1 -0
- package/dist/recipes/bar.d.ts +67 -0
- package/dist/recipes/bar.js +69 -0
- package/dist/recipes/bar.js.map +1 -0
- package/dist/recipes/boxplot.d.ts +64 -0
- package/dist/recipes/boxplot.js +89 -0
- package/dist/recipes/boxplot.js.map +1 -0
- package/dist/recipes/chord.d.ts +41 -0
- package/dist/recipes/chord.js +72 -0
- package/dist/recipes/chord.js.map +1 -0
- package/dist/recipes/choropleth-regions.d.ts +49 -0
- package/dist/recipes/choropleth-regions.js +521 -0
- package/dist/recipes/choropleth-regions.js.map +1 -0
- package/dist/recipes/choropleth.d.ts +27 -0
- package/dist/recipes/choropleth.js +86 -0
- package/dist/recipes/choropleth.js.map +1 -0
- package/dist/recipes/donut.d.ts +25 -0
- package/dist/recipes/donut.js +51 -0
- package/dist/recipes/donut.js.map +1 -0
- package/dist/recipes/graph.d.ts +55 -0
- package/dist/recipes/graph.js +132 -0
- package/dist/recipes/graph.js.map +1 -0
- package/dist/recipes/heatmap.d.ts +21 -0
- package/dist/recipes/heatmap.js +79 -0
- package/dist/recipes/heatmap.js.map +1 -0
- package/dist/recipes/index.d.ts +5 -0
- package/dist/recipes/index.js +35 -0
- package/dist/recipes/index.js.map +1 -0
- package/dist/recipes/line.d.ts +69 -0
- package/dist/recipes/line.js +64 -0
- package/dist/recipes/line.js.map +1 -0
- package/dist/recipes/nested-donut.d.ts +22 -0
- package/dist/recipes/nested-donut.js +47 -0
- package/dist/recipes/nested-donut.js.map +1 -0
- package/dist/recipes/radial-bar.d.ts +25 -0
- package/dist/recipes/radial-bar.js +53 -0
- package/dist/recipes/radial-bar.js.map +1 -0
- package/dist/recipes/sankey.d.ts +41 -0
- package/dist/recipes/sankey.js +61 -0
- package/dist/recipes/sankey.js.map +1 -0
- package/dist/recipes/scatter.d.ts +68 -0
- package/dist/recipes/scatter.js +75 -0
- package/dist/recipes/scatter.js.map +1 -0
- package/dist/recipes/shared.d.ts +144 -0
- package/dist/recipes/shared.js +212 -0
- package/dist/recipes/shared.js.map +1 -0
- package/dist/recipes/timeline.d.ts +31 -0
- package/dist/recipes/timeline.js +123 -0
- package/dist/recipes/timeline.js.map +1 -0
- package/dist/recipes/treemap.d.ts +19 -0
- package/dist/recipes/treemap.js +43 -0
- package/dist/recipes/treemap.js.map +1 -0
- package/dist/recipes/types.d.ts +16 -0
- package/dist/recipes/types.js +2 -0
- package/dist/recipes/types.js.map +1 -0
- package/dist/render/headless.d.ts +62 -0
- package/dist/render/headless.js +158 -0
- package/dist/render/headless.js.map +1 -0
- package/dist/render/materialize.d.ts +35 -0
- package/dist/render/materialize.js +160 -0
- package/dist/render/materialize.js.map +1 -0
- package/dist/render/mutex.d.ts +9 -0
- package/dist/render/mutex.js +14 -0
- package/dist/render/mutex.js.map +1 -0
- package/dist/render/rasterize.d.ts +10 -0
- package/dist/render/rasterize.js +29 -0
- package/dist/render/rasterize.js.map +1 -0
- package/dist/render/renderer.d.ts +21 -0
- package/dist/render/renderer.js +66 -0
- package/dist/render/renderer.js.map +1 -0
- package/dist/render/spec.d.ts +95 -0
- package/dist/render/spec.js +15 -0
- package/dist/render/spec.js.map +1 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +44 -0
- package/dist/server.js.map +1 -0
- package/dist/svg/collect-css.d.ts +14 -0
- package/dist/svg/collect-css.js +53 -0
- package/dist/svg/collect-css.js.map +1 -0
- package/dist/svg/css-vars.d.ts +17 -0
- package/dist/svg/css-vars.js +64 -0
- package/dist/svg/css-vars.js.map +1 -0
- package/dist/svg/header.d.ts +8 -0
- package/dist/svg/header.js +81 -0
- package/dist/svg/header.js.map +1 -0
- package/dist/svg/ids.d.ts +12 -0
- package/dist/svg/ids.js +94 -0
- package/dist/svg/ids.js.map +1 -0
- package/dist/svg/postprocess.d.ts +41 -0
- package/dist/svg/postprocess.js +135 -0
- package/dist/svg/postprocess.js.map +1 -0
- package/dist/tools/register.d.ts +12 -0
- package/dist/tools/register.js +127 -0
- package/dist/tools/register.js.map +1 -0
- package/dist/widget/bundle.js +1777 -0
- package/dist/widget/bundle.meta.json +3 -0
- package/dist/widget/entry.d.ts +34 -0
- package/dist/widget/entry.js +118 -0
- package/dist/widget/entry.js.map +1 -0
- package/dist/widget/interactions.d.ts +14 -0
- package/dist/widget/interactions.js +151 -0
- package/dist/widget/interactions.js.map +1 -0
- package/dist/widget/unovis-slim.d.ts +33 -0
- package/dist/widget/unovis-slim.js +34 -0
- package/dist/widget/unovis-slim.js.map +1 -0
- package/docs/README.md +56 -0
- package/docs/architecture.md +158 -0
- package/docs/chart-spec.md +148 -0
- package/docs/getting-started.md +175 -0
- package/docs/interactive.md +125 -0
- package/docs/output-types.md +170 -0
- package/docs/programmatic.md +234 -0
- package/docs/tools.md +273 -0
- package/docs/troubleshooting.md +140 -0
- package/fonts/README.md +19 -0
- package/package.json +73 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { commonInput, dataRecords, fieldName, assertFieldsExist, baseSpec, field } from './shared.js';
|
|
3
|
+
export const nestedDonutInputShape = {
|
|
4
|
+
data: dataRecords,
|
|
5
|
+
layers: z.array(fieldName).min(1).max(4)
|
|
6
|
+
.describe('Fields defining the hierarchy rings, innermost ring first, e.g. ["region", "country"]'),
|
|
7
|
+
value: fieldName.optional()
|
|
8
|
+
.describe('Field with the numeric segment weight. Records are counted when omitted'),
|
|
9
|
+
centralLabel: z.string().optional().describe('Text in the middle of the donut'),
|
|
10
|
+
centralSubLabel: z.string().optional().describe('Smaller text under the central label'),
|
|
11
|
+
layerPadding: z.number().min(0).max(20).default(0).describe('Gap between rings in pixels'),
|
|
12
|
+
cornerRadius: z.number().min(0).max(20).default(0).describe('Segment corner radius in pixels'),
|
|
13
|
+
showSegmentLabels: z.boolean().default(true).describe('Show the category name on each segment (labels that do not fit are hidden)'),
|
|
14
|
+
...commonInput,
|
|
15
|
+
};
|
|
16
|
+
export const nestedDonutRecipe = {
|
|
17
|
+
name: 'generate_nested_donut_chart',
|
|
18
|
+
title: 'Nested donut / sunburst chart',
|
|
19
|
+
description: 'Generate a nested donut (sunburst) chart showing hierarchical part-of-whole data as concentric rings, ' +
|
|
20
|
+
'innermost ring first. Segments are sized by the value field, or by record count when it is omitted. ' +
|
|
21
|
+
'Example: data=[{"region":"EMEA","country":"Germany","sales":420},{"region":"EMEA","country":"France","sales":310}], layers=["region","country"], value="sales".',
|
|
22
|
+
inputShape: nestedDonutInputShape,
|
|
23
|
+
toSpec: (input) => {
|
|
24
|
+
const data = input.data;
|
|
25
|
+
assertFieldsExist(data, [...input.layers, input.value]);
|
|
26
|
+
return {
|
|
27
|
+
container: 'single',
|
|
28
|
+
...baseSpec(input),
|
|
29
|
+
components: [{
|
|
30
|
+
type: 'NestedDonut',
|
|
31
|
+
config: {
|
|
32
|
+
layers: input.layers.map(layer => field(layer)),
|
|
33
|
+
// 'outwards' places layers[0] on the innermost ring, leaves outward
|
|
34
|
+
direction: 'outwards',
|
|
35
|
+
...(input.value ? { value: field(input.value, 'number') } : {}),
|
|
36
|
+
centralLabel: input.centralLabel,
|
|
37
|
+
centralSubLabel: input.centralSubLabel,
|
|
38
|
+
layerPadding: input.layerPadding,
|
|
39
|
+
cornerRadius: input.cornerRadius,
|
|
40
|
+
showSegmentLabels: input.showSegmentLabels,
|
|
41
|
+
},
|
|
42
|
+
}],
|
|
43
|
+
data,
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=nested-donut.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nested-donut.js","sourceRoot":"","sources":["../../src/recipes/nested-donut.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,iBAAiB,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAGrG,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;SACrC,QAAQ,CAAC,uFAAuF,CAAC;IACpG,KAAK,EAAE,SAAS,CAAC,QAAQ,EAAE;SACxB,QAAQ,CAAC,yEAAyE,CAAC;IACtF,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;IAC/E,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;IACvF,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,6BAA6B,CAAC;IAC1F,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,iCAAiC,CAAC;IAC9F,iBAAiB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,4EAA4E,CAAC;IACnI,GAAG,WAAW;CACf,CAAA;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAyC;IACrE,IAAI,EAAE,6BAA6B;IACnC,KAAK,EAAE,+BAA+B;IACtC,WAAW,EAAE,wGAAwG;QACnH,sGAAsG;QACtG,iKAAiK;IACnK,UAAU,EAAE,qBAAqB;IACjC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;QAChB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAoB,CAAA;QACvC,iBAAiB,CAAC,IAAI,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAA;QAEvD,OAAO;YACL,SAAS,EAAE,QAAQ;YACnB,GAAG,QAAQ,CAAC,KAAK,CAAC;YAClB,UAAU,EAAE,CAAC;oBACX,IAAI,EAAE,aAAa;oBACnB,MAAM,EAAE;wBACN,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;wBAC/C,oEAAoE;wBACpE,SAAS,EAAE,UAAU;wBACrB,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;wBAC/D,YAAY,EAAE,KAAK,CAAC,YAAY;wBAChC,eAAe,EAAE,KAAK,CAAC,eAAe;wBACtC,YAAY,EAAE,KAAK,CAAC,YAAY;wBAChC,YAAY,EAAE,KAAK,CAAC,YAAY;wBAChC,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;qBAC3C;iBACF,CAAC;YACF,IAAI;SACL,CAAA;IACH,CAAC;CACF,CAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Recipe } from './types.js';
|
|
3
|
+
export declare const radialBarInputShape: {
|
|
4
|
+
width: z.ZodDefault<z.ZodNumber>;
|
|
5
|
+
height: z.ZodDefault<z.ZodNumber>;
|
|
6
|
+
theme: z.ZodDefault<z.ZodEnum<["light", "dark"]>>;
|
|
7
|
+
title: z.ZodOptional<z.ZodString>;
|
|
8
|
+
colors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
9
|
+
framework: z.ZodDefault<z.ZodEnum<["ts", "react", "svelte", "vue", "angular", "solid"]>>;
|
|
10
|
+
outputType: z.ZodDefault<z.ZodEnum<["svg", "png", "html", "interactive", "config", "code"]>>;
|
|
11
|
+
outputPath: z.ZodOptional<z.ZodString>;
|
|
12
|
+
scale: z.ZodDefault<z.ZodNumber>;
|
|
13
|
+
data: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>, "many">;
|
|
14
|
+
value: z.ZodString;
|
|
15
|
+
label: z.ZodOptional<z.ZodString>;
|
|
16
|
+
maxValue: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
arcWidth: z.ZodDefault<z.ZodNumber>;
|
|
18
|
+
arcPadding: z.ZodDefault<z.ZodNumber>;
|
|
19
|
+
cornerRadius: z.ZodOptional<z.ZodNumber>;
|
|
20
|
+
centralLabel: z.ZodOptional<z.ZodString>;
|
|
21
|
+
centralSubLabel: z.ZodOptional<z.ZodString>;
|
|
22
|
+
showBackground: z.ZodDefault<z.ZodBoolean>;
|
|
23
|
+
legend: z.ZodDefault<z.ZodBoolean>;
|
|
24
|
+
};
|
|
25
|
+
export declare const radialBarRecipe: Recipe<typeof radialBarInputShape>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { commonInput, dataRecords, fieldName, assertFieldsExist, baseSpec, field } from './shared.js';
|
|
3
|
+
export const radialBarInputShape = {
|
|
4
|
+
data: dataRecords,
|
|
5
|
+
value: fieldName.describe('Field with the numeric value of each ring'),
|
|
6
|
+
label: fieldName.optional().describe('Field with the ring name (used for the legend)'),
|
|
7
|
+
maxValue: z.number().positive().optional()
|
|
8
|
+
.describe('Value at which a ring forms a full circle. Defaults to the largest value in the data'),
|
|
9
|
+
arcWidth: z.number().min(2).max(100).default(16).describe('Ring thickness in pixels'),
|
|
10
|
+
arcPadding: z.number().min(0).max(40).default(4).describe('Gap between rings in pixels'),
|
|
11
|
+
cornerRadius: z.number().min(0).max(50).optional()
|
|
12
|
+
.describe('Rounded bar ends, in pixels. Defaults to half the ring thickness'),
|
|
13
|
+
centralLabel: z.string().optional().describe('Text in the middle of the rings'),
|
|
14
|
+
centralSubLabel: z.string().optional().describe('Smaller text under the central label'),
|
|
15
|
+
showBackground: z.boolean().default(true).describe('Show a faded full-circle track behind each ring'),
|
|
16
|
+
legend: z.boolean().default(true).describe('Show a legend (requires the label field)'),
|
|
17
|
+
...commonInput,
|
|
18
|
+
};
|
|
19
|
+
export const radialBarRecipe = {
|
|
20
|
+
name: 'generate_radial_bar_chart',
|
|
21
|
+
title: 'Radial bar chart',
|
|
22
|
+
description: 'Generate a radial bar chart (activity rings): one concentric ring per record, ' +
|
|
23
|
+
'filled proportionally to its value. Good for progress toward goals or comparing a few values. ' +
|
|
24
|
+
'Example: data=[{"metric":"Move","pct":84},{"metric":"Exercise","pct":62}], value="pct", label="metric", maxValue=100.',
|
|
25
|
+
inputShape: radialBarInputShape,
|
|
26
|
+
toSpec: (input) => {
|
|
27
|
+
const data = input.data;
|
|
28
|
+
assertFieldsExist(data, [input.value, input.label]);
|
|
29
|
+
const legend = input.legend && input.label
|
|
30
|
+
? data.map((d, i) => ({ name: String(d[input.label]), paletteIndex: i, color: input.colors?.[i % (input.colors.length || 1)] }))
|
|
31
|
+
: undefined;
|
|
32
|
+
return {
|
|
33
|
+
container: 'single',
|
|
34
|
+
...baseSpec(input),
|
|
35
|
+
components: [{
|
|
36
|
+
type: 'RadialBar',
|
|
37
|
+
config: {
|
|
38
|
+
value: field(input.value, 'number'),
|
|
39
|
+
...(input.maxValue !== undefined ? { maxValue: input.maxValue } : {}),
|
|
40
|
+
trackWidth: input.arcWidth,
|
|
41
|
+
trackPadding: input.arcPadding,
|
|
42
|
+
cornerRadius: input.cornerRadius ?? Math.floor(input.arcWidth / 2),
|
|
43
|
+
centralLabel: input.centralLabel,
|
|
44
|
+
centralSubLabel: input.centralSubLabel,
|
|
45
|
+
showBackground: input.showBackground,
|
|
46
|
+
},
|
|
47
|
+
}],
|
|
48
|
+
legend,
|
|
49
|
+
data,
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=radial-bar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"radial-bar.js","sourceRoot":"","sources":["../../src/recipes/radial-bar.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Recipe } from './types.js';
|
|
3
|
+
export declare const sankeyInputShape: {
|
|
4
|
+
width: z.ZodDefault<z.ZodNumber>;
|
|
5
|
+
height: z.ZodDefault<z.ZodNumber>;
|
|
6
|
+
theme: z.ZodDefault<z.ZodEnum<["light", "dark"]>>;
|
|
7
|
+
title: z.ZodOptional<z.ZodString>;
|
|
8
|
+
colors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
9
|
+
framework: z.ZodDefault<z.ZodEnum<["ts", "react", "svelte", "vue", "angular", "solid"]>>;
|
|
10
|
+
outputType: z.ZodDefault<z.ZodEnum<["svg", "png", "html", "interactive", "config", "code"]>>;
|
|
11
|
+
outputPath: z.ZodOptional<z.ZodString>;
|
|
12
|
+
scale: z.ZodDefault<z.ZodNumber>;
|
|
13
|
+
links: z.ZodArray<z.ZodObject<{
|
|
14
|
+
source: z.ZodString;
|
|
15
|
+
target: z.ZodString;
|
|
16
|
+
value: z.ZodNumber;
|
|
17
|
+
}, "strip", z.ZodTypeAny, {
|
|
18
|
+
value: number;
|
|
19
|
+
source: string;
|
|
20
|
+
target: string;
|
|
21
|
+
}, {
|
|
22
|
+
value: number;
|
|
23
|
+
source: string;
|
|
24
|
+
target: string;
|
|
25
|
+
}>, "many">;
|
|
26
|
+
nodes: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
27
|
+
id: z.ZodString;
|
|
28
|
+
label: z.ZodOptional<z.ZodString>;
|
|
29
|
+
}, "strip", z.ZodTypeAny, {
|
|
30
|
+
id: string;
|
|
31
|
+
label?: string | undefined;
|
|
32
|
+
}, {
|
|
33
|
+
id: string;
|
|
34
|
+
label?: string | undefined;
|
|
35
|
+
}>, "many">>;
|
|
36
|
+
nodeWidth: z.ZodDefault<z.ZodNumber>;
|
|
37
|
+
nodePadding: z.ZodDefault<z.ZodNumber>;
|
|
38
|
+
showValues: z.ZodDefault<z.ZodBoolean>;
|
|
39
|
+
valueSuffix: z.ZodDefault<z.ZodString>;
|
|
40
|
+
};
|
|
41
|
+
export declare const sankeyRecipe: Recipe<typeof sankeyInputShape>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { commonInput, baseSpec, ChartInputError } from './shared.js';
|
|
3
|
+
const sankeyNode = z.object({
|
|
4
|
+
id: z.string().min(1),
|
|
5
|
+
label: z.string().optional().describe('Display name (defaults to id)'),
|
|
6
|
+
}).describe('A node; optional if all nodes can be derived from links');
|
|
7
|
+
const sankeyLink = z.object({
|
|
8
|
+
source: z.string().min(1).describe('Source node id'),
|
|
9
|
+
target: z.string().min(1).describe('Target node id'),
|
|
10
|
+
value: z.number().positive().describe('Flow volume'),
|
|
11
|
+
});
|
|
12
|
+
export const sankeyInputShape = {
|
|
13
|
+
links: z.array(sankeyLink).min(1).describe('Flows between nodes, e.g. [{"source":"A","target":"B","value":10}]'),
|
|
14
|
+
nodes: z.array(sankeyNode).optional().describe('Node definitions; derived from links when omitted'),
|
|
15
|
+
nodeWidth: z.number().min(2).max(100).default(25).describe('Node bar width in pixels'),
|
|
16
|
+
nodePadding: z.number().min(0).max(100).default(8).describe('Vertical padding between nodes'),
|
|
17
|
+
showValues: z.boolean().default(true).describe('Show node totals as sub-labels'),
|
|
18
|
+
valueSuffix: z.string().default('').describe('Unit appended to node values, e.g. " GB"'),
|
|
19
|
+
...commonInput,
|
|
20
|
+
};
|
|
21
|
+
export const sankeyRecipe = {
|
|
22
|
+
name: 'generate_sankey_diagram',
|
|
23
|
+
title: 'Sankey diagram',
|
|
24
|
+
description: 'Generate a Sankey diagram visualizing flows between stages/nodes (e.g. traffic, budgets, energy). ' +
|
|
25
|
+
'Provide links as {source, target, value}; nodes are derived automatically. The graph must be acyclic. ' +
|
|
26
|
+
'Example: links=[{"source":"Salary","target":"Budget","value":5000},{"source":"Budget","target":"Rent","value":2000}].',
|
|
27
|
+
inputShape: sankeyInputShape,
|
|
28
|
+
toSpec: (input) => {
|
|
29
|
+
const declared = new Map((input.nodes ?? []).map(n => [n.id, n]));
|
|
30
|
+
const ids = new Set();
|
|
31
|
+
for (const link of input.links) {
|
|
32
|
+
ids.add(link.source);
|
|
33
|
+
ids.add(link.target);
|
|
34
|
+
if (link.source === link.target)
|
|
35
|
+
throw new ChartInputError(`Self-referencing link: "${link.source}" → "${link.target}"`);
|
|
36
|
+
}
|
|
37
|
+
for (const node of declared.keys())
|
|
38
|
+
ids.add(node);
|
|
39
|
+
const nodes = Array.from(ids).map(id => ({
|
|
40
|
+
id,
|
|
41
|
+
label: declared.get(id)?.label ?? id,
|
|
42
|
+
}));
|
|
43
|
+
return {
|
|
44
|
+
container: 'single',
|
|
45
|
+
...baseSpec(input),
|
|
46
|
+
components: [{
|
|
47
|
+
type: 'Sankey',
|
|
48
|
+
config: {
|
|
49
|
+
label: { $field: 'label' },
|
|
50
|
+
nodeWidth: input.nodeWidth,
|
|
51
|
+
nodePadding: input.nodePadding,
|
|
52
|
+
labelFit: 'trim',
|
|
53
|
+
labelMaxWidth: Math.max(80, input.width * 0.16),
|
|
54
|
+
...(input.showValues ? { subLabel: { $format: { field: 'value', suffix: input.valueSuffix } } } : {}),
|
|
55
|
+
},
|
|
56
|
+
}],
|
|
57
|
+
data: { nodes, links: input.links },
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=sankey.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sankey.js","sourceRoot":"","sources":["../../src/recipes/sankey.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Recipe } from './types.js';
|
|
3
|
+
export declare const scatterInputShape: {
|
|
4
|
+
width: z.ZodDefault<z.ZodNumber>;
|
|
5
|
+
height: z.ZodDefault<z.ZodNumber>;
|
|
6
|
+
theme: z.ZodDefault<z.ZodEnum<["light", "dark"]>>;
|
|
7
|
+
title: z.ZodOptional<z.ZodString>;
|
|
8
|
+
colors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
9
|
+
framework: z.ZodDefault<z.ZodEnum<["ts", "react", "svelte", "vue", "angular", "solid"]>>;
|
|
10
|
+
outputType: z.ZodDefault<z.ZodEnum<["svg", "png", "html", "interactive", "config", "code"]>>;
|
|
11
|
+
outputPath: z.ZodOptional<z.ZodString>;
|
|
12
|
+
scale: z.ZodDefault<z.ZodNumber>;
|
|
13
|
+
xAxisLabel: z.ZodOptional<z.ZodString>;
|
|
14
|
+
yAxisLabel: z.ZodOptional<z.ZodString>;
|
|
15
|
+
showGridLines: z.ZodDefault<z.ZodBoolean>;
|
|
16
|
+
legend: z.ZodDefault<z.ZodBoolean>;
|
|
17
|
+
referenceLines: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
18
|
+
axis: z.ZodDefault<z.ZodEnum<["x", "y"]>>;
|
|
19
|
+
value: z.ZodNumber;
|
|
20
|
+
label: z.ZodOptional<z.ZodString>;
|
|
21
|
+
color: z.ZodOptional<z.ZodString>;
|
|
22
|
+
lineWidth: z.ZodDefault<z.ZodNumber>;
|
|
23
|
+
style: z.ZodDefault<z.ZodEnum<["dashed", "solid", "dotted"]>>;
|
|
24
|
+
}, "strip", z.ZodTypeAny, {
|
|
25
|
+
value: number;
|
|
26
|
+
style: "solid" | "dashed" | "dotted";
|
|
27
|
+
axis: "x" | "y";
|
|
28
|
+
lineWidth: number;
|
|
29
|
+
label?: string | undefined;
|
|
30
|
+
color?: string | undefined;
|
|
31
|
+
}, {
|
|
32
|
+
value: number;
|
|
33
|
+
label?: string | undefined;
|
|
34
|
+
color?: string | undefined;
|
|
35
|
+
style?: "solid" | "dashed" | "dotted" | undefined;
|
|
36
|
+
axis?: "x" | "y" | undefined;
|
|
37
|
+
lineWidth?: number | undefined;
|
|
38
|
+
}>, "many">>;
|
|
39
|
+
referenceBands: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
40
|
+
axis: z.ZodDefault<z.ZodEnum<["x", "y"]>>;
|
|
41
|
+
from: z.ZodNumber;
|
|
42
|
+
to: z.ZodNumber;
|
|
43
|
+
label: z.ZodOptional<z.ZodString>;
|
|
44
|
+
color: z.ZodOptional<z.ZodString>;
|
|
45
|
+
}, "strip", z.ZodTypeAny, {
|
|
46
|
+
axis: "x" | "y";
|
|
47
|
+
from: number;
|
|
48
|
+
to: number;
|
|
49
|
+
label?: string | undefined;
|
|
50
|
+
color?: string | undefined;
|
|
51
|
+
}, {
|
|
52
|
+
from: number;
|
|
53
|
+
to: number;
|
|
54
|
+
label?: string | undefined;
|
|
55
|
+
color?: string | undefined;
|
|
56
|
+
axis?: "x" | "y" | undefined;
|
|
57
|
+
}>, "many">>;
|
|
58
|
+
data: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>, "many">;
|
|
59
|
+
x: z.ZodString;
|
|
60
|
+
y: z.ZodString;
|
|
61
|
+
size: z.ZodOptional<z.ZodString>;
|
|
62
|
+
sizeRange: z.ZodDefault<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
|
|
63
|
+
pointSize: z.ZodDefault<z.ZodNumber>;
|
|
64
|
+
colorBy: z.ZodOptional<z.ZodString>;
|
|
65
|
+
label: z.ZodOptional<z.ZodString>;
|
|
66
|
+
shape: z.ZodDefault<z.ZodEnum<["circle", "cross", "diamond", "square", "star", "triangle", "wye"]>>;
|
|
67
|
+
};
|
|
68
|
+
export declare const scatterRecipe: Recipe<typeof scatterInputShape>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { commonInput, xyInput, dataRecords, fieldName, assertFieldsExist, baseSpec, distinctValues, field, seriesLegend, xyAxes, xyDecorations, decorationComponents, } from './shared.js';
|
|
3
|
+
/** Category color: custom palette entries cycle; otherwise theme palette variables
|
|
4
|
+
* (resolved to literal colors per theme during SVG post-processing) */
|
|
5
|
+
const categoryColor = (i, colors) => colors?.length ? colors[i % colors.length] : `var(--vis-color${i % 6})`;
|
|
6
|
+
/** Neutral grey for points whose category is missing (works on both themes) */
|
|
7
|
+
const UNKNOWN_CATEGORY_COLOR = '#94A0AB';
|
|
8
|
+
export const scatterInputShape = {
|
|
9
|
+
data: dataRecords,
|
|
10
|
+
x: fieldName.describe('Field for X values (numeric)'),
|
|
11
|
+
y: fieldName.describe('Field for Y values (numeric)'),
|
|
12
|
+
size: fieldName.optional()
|
|
13
|
+
.describe('Numeric field mapped to point size (bubble chart). Values are scaled into sizeRange'),
|
|
14
|
+
sizeRange: z.tuple([z.number().min(1).max(200), z.number().min(1).max(200)]).default([8, 40])
|
|
15
|
+
.describe('Point diameter range [min, max] in pixels, used when size is set'),
|
|
16
|
+
pointSize: z.number().min(1).max(100).default(10)
|
|
17
|
+
.describe('Fixed point diameter in pixels, used when size is not set'),
|
|
18
|
+
colorBy: fieldName.optional()
|
|
19
|
+
.describe('Categorical field: points are colored by its value and a legend is shown'),
|
|
20
|
+
label: fieldName.optional()
|
|
21
|
+
.describe('Field with point labels. Overlapping labels are hidden automatically'),
|
|
22
|
+
shape: z.enum(['circle', 'cross', 'diamond', 'square', 'star', 'triangle', 'wye']).default('circle')
|
|
23
|
+
.describe('Point shape'),
|
|
24
|
+
...xyDecorations,
|
|
25
|
+
...xyInput,
|
|
26
|
+
...commonInput,
|
|
27
|
+
};
|
|
28
|
+
export const scatterRecipe = {
|
|
29
|
+
name: 'generate_scatter_plot',
|
|
30
|
+
title: 'Scatter plot',
|
|
31
|
+
description: 'Generate a scatter plot (or bubble chart) showing the relationship between two numeric fields. ' +
|
|
32
|
+
'Optional: size field for bubbles, colorBy category field for colored groups with a legend, label field for point labels. ' +
|
|
33
|
+
'Example: data=[{"gdp":43000,"lifeExp":81.2,"pop":38.2,"region":"Europe","country":"..."},...], ' +
|
|
34
|
+
'x="gdp", y="lifeExp", size="pop", colorBy="region", label="country".',
|
|
35
|
+
inputShape: scatterInputShape,
|
|
36
|
+
toSpec: (input) => {
|
|
37
|
+
const data = input.data;
|
|
38
|
+
assertFieldsExist(data, [input.x, input.y, input.size, input.colorBy, input.label]);
|
|
39
|
+
// Categorical coloring: distinct categories map onto the palette in
|
|
40
|
+
// first-seen order; the legend mirrors the same order/colors.
|
|
41
|
+
let color;
|
|
42
|
+
let legend;
|
|
43
|
+
if (input.colorBy) {
|
|
44
|
+
const categories = distinctValues(data, input.colorBy).map(String);
|
|
45
|
+
const mapping = {};
|
|
46
|
+
categories.forEach((category, i) => { mapping[category] = categoryColor(i, input.colors); });
|
|
47
|
+
color = { $mapField: { field: input.colorBy, mapping, fallback: UNKNOWN_CATEGORY_COLOR } };
|
|
48
|
+
legend = seriesLegend(categories, input.legend, input.colors);
|
|
49
|
+
}
|
|
50
|
+
const axes = xyAxes(input);
|
|
51
|
+
const deco = decorationComponents(input);
|
|
52
|
+
return {
|
|
53
|
+
container: 'xy',
|
|
54
|
+
...baseSpec(input),
|
|
55
|
+
components: [...deco.bands, {
|
|
56
|
+
type: 'Scatter',
|
|
57
|
+
config: {
|
|
58
|
+
x: field(input.x, 'number'),
|
|
59
|
+
y: field(input.y, 'number'),
|
|
60
|
+
shape: input.shape,
|
|
61
|
+
...(input.size
|
|
62
|
+
? { size: field(input.size, 'number'), sizeRange: input.sizeRange }
|
|
63
|
+
: { size: input.pointSize }),
|
|
64
|
+
...(color ? { color } : {}),
|
|
65
|
+
...(input.label ? { label: { $field: input.label } } : {}),
|
|
66
|
+
},
|
|
67
|
+
}, ...deco.lines],
|
|
68
|
+
xAxis: { ...axes.xAxis, tickFormat: { $numTickFormat: true } },
|
|
69
|
+
yAxis: { ...axes.yAxis, tickFormat: { $numTickFormat: true } },
|
|
70
|
+
legend,
|
|
71
|
+
data,
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=scatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scatter.js","sourceRoot":"","sources":["../../src/recipes/scatter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/** Shared schema fragments and helpers for chart recipes.
|
|
2
|
+
*
|
|
3
|
+
* Conventions:
|
|
4
|
+
* - data is always an array of flat records
|
|
5
|
+
* - accessors are field names (never code)
|
|
6
|
+
* - every option has a description and a sane default
|
|
7
|
+
* - enums over free-form strings, bounded numbers
|
|
8
|
+
*/
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import { ChartInputError } from '../render/materialize.js';
|
|
11
|
+
import type { AccessorRef, AxisSpec, ChartSpec, ComponentSpec, LegendItemSpec } from '../render/spec.js';
|
|
12
|
+
export { ChartInputError };
|
|
13
|
+
export declare const dataValue: z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>;
|
|
14
|
+
export declare const dataRecords: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>, "many">;
|
|
15
|
+
export declare const fieldName: z.ZodString;
|
|
16
|
+
export declare const commonInput: {
|
|
17
|
+
width: z.ZodDefault<z.ZodNumber>;
|
|
18
|
+
height: z.ZodDefault<z.ZodNumber>;
|
|
19
|
+
theme: z.ZodDefault<z.ZodEnum<["light", "dark"]>>;
|
|
20
|
+
title: z.ZodOptional<z.ZodString>;
|
|
21
|
+
colors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
22
|
+
framework: z.ZodDefault<z.ZodEnum<["ts", "react", "svelte", "vue", "angular", "solid"]>>;
|
|
23
|
+
outputType: z.ZodDefault<z.ZodEnum<["svg", "png", "html", "interactive", "config", "code"]>>;
|
|
24
|
+
outputPath: z.ZodOptional<z.ZodString>;
|
|
25
|
+
scale: z.ZodDefault<z.ZodNumber>;
|
|
26
|
+
};
|
|
27
|
+
export declare const xyInput: {
|
|
28
|
+
xAxisLabel: z.ZodOptional<z.ZodString>;
|
|
29
|
+
yAxisLabel: z.ZodOptional<z.ZodString>;
|
|
30
|
+
showGridLines: z.ZodDefault<z.ZodBoolean>;
|
|
31
|
+
legend: z.ZodDefault<z.ZodBoolean>;
|
|
32
|
+
};
|
|
33
|
+
/** Reference-line / shaded-band decorations available on XY charts */
|
|
34
|
+
export declare const xyDecorations: {
|
|
35
|
+
referenceLines: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
36
|
+
axis: z.ZodDefault<z.ZodEnum<["x", "y"]>>;
|
|
37
|
+
value: z.ZodNumber;
|
|
38
|
+
label: z.ZodOptional<z.ZodString>;
|
|
39
|
+
color: z.ZodOptional<z.ZodString>;
|
|
40
|
+
lineWidth: z.ZodDefault<z.ZodNumber>;
|
|
41
|
+
style: z.ZodDefault<z.ZodEnum<["dashed", "solid", "dotted"]>>;
|
|
42
|
+
}, "strip", z.ZodTypeAny, {
|
|
43
|
+
value: number;
|
|
44
|
+
style: "solid" | "dashed" | "dotted";
|
|
45
|
+
axis: "x" | "y";
|
|
46
|
+
lineWidth: number;
|
|
47
|
+
label?: string | undefined;
|
|
48
|
+
color?: string | undefined;
|
|
49
|
+
}, {
|
|
50
|
+
value: number;
|
|
51
|
+
label?: string | undefined;
|
|
52
|
+
color?: string | undefined;
|
|
53
|
+
style?: "solid" | "dashed" | "dotted" | undefined;
|
|
54
|
+
axis?: "x" | "y" | undefined;
|
|
55
|
+
lineWidth?: number | undefined;
|
|
56
|
+
}>, "many">>;
|
|
57
|
+
referenceBands: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
58
|
+
axis: z.ZodDefault<z.ZodEnum<["x", "y"]>>;
|
|
59
|
+
from: z.ZodNumber;
|
|
60
|
+
to: z.ZodNumber;
|
|
61
|
+
label: z.ZodOptional<z.ZodString>;
|
|
62
|
+
color: z.ZodOptional<z.ZodString>;
|
|
63
|
+
}, "strip", z.ZodTypeAny, {
|
|
64
|
+
axis: "x" | "y";
|
|
65
|
+
from: number;
|
|
66
|
+
to: number;
|
|
67
|
+
label?: string | undefined;
|
|
68
|
+
color?: string | undefined;
|
|
69
|
+
}, {
|
|
70
|
+
from: number;
|
|
71
|
+
to: number;
|
|
72
|
+
label?: string | undefined;
|
|
73
|
+
color?: string | undefined;
|
|
74
|
+
axis?: "x" | "y" | undefined;
|
|
75
|
+
}>, "many">>;
|
|
76
|
+
};
|
|
77
|
+
export interface DecorationInput {
|
|
78
|
+
referenceLines?: {
|
|
79
|
+
axis: 'x' | 'y';
|
|
80
|
+
value: number;
|
|
81
|
+
label?: string;
|
|
82
|
+
color?: string;
|
|
83
|
+
lineWidth: number;
|
|
84
|
+
style: 'dashed' | 'solid' | 'dotted';
|
|
85
|
+
}[];
|
|
86
|
+
referenceBands?: {
|
|
87
|
+
axis: 'x' | 'y';
|
|
88
|
+
from: number;
|
|
89
|
+
to: number;
|
|
90
|
+
label?: string;
|
|
91
|
+
color?: string;
|
|
92
|
+
}[];
|
|
93
|
+
}
|
|
94
|
+
/** Decoration components: bands render behind the data, lines on top */
|
|
95
|
+
export declare function decorationComponents(input: DecorationInput): {
|
|
96
|
+
bands: ComponentSpec[];
|
|
97
|
+
lines: ComponentSpec[];
|
|
98
|
+
};
|
|
99
|
+
export type DataRecord = Record<string, string | number | boolean | null>;
|
|
100
|
+
export declare const field: (name: string, as?: "number" | "string" | "date") => AccessorRef;
|
|
101
|
+
export declare const toArray: <T>(value: T | T[]) => T[];
|
|
102
|
+
/** Throw an actionable error when referenced fields are missing from the data */
|
|
103
|
+
export declare function assertFieldsExist(data: DataRecord[], fields: (string | undefined)[]): void;
|
|
104
|
+
/** True when every value of a field (in the first few records) is numeric */
|
|
105
|
+
export declare function isNumericField(data: DataRecord[], fieldName: string): boolean;
|
|
106
|
+
/** Distinct values of a field, in first-seen order */
|
|
107
|
+
export declare function distinctValues(data: DataRecord[], fieldName: string): (string | number)[];
|
|
108
|
+
export interface CategoricalX {
|
|
109
|
+
/** x accessor: the field itself (numeric) or a category index */
|
|
110
|
+
x: AccessorRef;
|
|
111
|
+
/** Category names when x is ordinal (drives tickFormat/numTicks) */
|
|
112
|
+
categories?: string[];
|
|
113
|
+
}
|
|
114
|
+
/** Numeric fields map directly; string categories map to their index with
|
|
115
|
+
* labeled ticks (Unovis XY scales are numeric). */
|
|
116
|
+
export declare function categoricalX(data: DataRecord[], fieldName: string, isTime?: boolean): CategoricalX;
|
|
117
|
+
/** Axis spec for a categorical dimension: one labeled tick per category.
|
|
118
|
+
* Explicit integer tickValues — approximate d3 tick counts can emit
|
|
119
|
+
* fractional ticks that would round into duplicate category labels. */
|
|
120
|
+
export declare function categoryAxis(base: AxisSpec, categories: string[]): AxisSpec;
|
|
121
|
+
/** Calendar-aligned tick values for date axes. Linear-scale ticks land at
|
|
122
|
+
* arbitrary times of day, which the date formatter would render as
|
|
123
|
+
* time-of-day labels ("08:53 AM" on a 3-month chart) — align ticks to
|
|
124
|
+
* month/day/hour/minute boundaries instead. */
|
|
125
|
+
export declare function dateTickValues(min: number, max: number, target?: number): number[] | undefined;
|
|
126
|
+
/** Tick values for a time axis derived from the data extent of a date field */
|
|
127
|
+
export declare function timeTickValuesFromData(data: DataRecord[], fieldName: string): number[] | undefined;
|
|
128
|
+
export declare function seriesLegend(labels: string[], enabled: boolean, colors?: string[]): LegendItemSpec[] | undefined;
|
|
129
|
+
/** Common ChartSpec fields from common tool inputs */
|
|
130
|
+
export declare function baseSpec(input: {
|
|
131
|
+
width: number;
|
|
132
|
+
height: number;
|
|
133
|
+
theme: 'light' | 'dark';
|
|
134
|
+
title?: string;
|
|
135
|
+
colors?: string[];
|
|
136
|
+
}): Pick<ChartSpec, 'width' | 'height' | 'theme' | 'title' | 'colors'>;
|
|
137
|
+
export declare function xyAxes(input: {
|
|
138
|
+
xAxisLabel?: string;
|
|
139
|
+
yAxisLabel?: string;
|
|
140
|
+
showGridLines: boolean;
|
|
141
|
+
}): {
|
|
142
|
+
xAxis: AxisSpec;
|
|
143
|
+
yAxis: AxisSpec;
|
|
144
|
+
};
|