@contractspec/lib.presentation-runtime-core 3.7.6 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -54
- package/dist/browser/index.js +473 -1
- package/dist/browser/table.js +21 -0
- package/dist/browser/visualization.echarts.js +148 -0
- package/dist/browser/visualization.js +307 -0
- package/dist/browser/visualization.model.builders.js +290 -0
- package/dist/browser/visualization.model.helpers.js +199 -0
- package/dist/browser/visualization.model.js +306 -0
- package/dist/browser/visualization.types.js +0 -0
- package/dist/browser/visualization.utils.js +65 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +473 -1
- package/dist/node/index.js +473 -1
- package/dist/node/table.js +21 -0
- package/dist/node/visualization.echarts.js +148 -0
- package/dist/node/visualization.js +307 -0
- package/dist/node/visualization.model.builders.js +290 -0
- package/dist/node/visualization.model.helpers.js +199 -0
- package/dist/node/visualization.model.js +306 -0
- package/dist/node/visualization.types.js +0 -0
- package/dist/node/visualization.utils.js +65 -0
- package/dist/table.d.ts +99 -0
- package/dist/table.js +22 -0
- package/dist/visualization.d.ts +3 -0
- package/dist/visualization.echarts.d.ts +3 -0
- package/dist/visualization.echarts.js +149 -0
- package/dist/visualization.js +308 -0
- package/dist/visualization.model.builders.d.ts +20 -0
- package/dist/visualization.model.builders.js +291 -0
- package/dist/visualization.model.d.ts +3 -0
- package/dist/visualization.model.helpers.d.ts +60 -0
- package/dist/visualization.model.helpers.js +200 -0
- package/dist/visualization.model.js +307 -0
- package/dist/visualization.model.test.d.ts +1 -0
- package/dist/visualization.types.d.ts +74 -0
- package/dist/visualization.types.js +1 -0
- package/dist/visualization.utils.d.ts +5 -0
- package/dist/visualization.utils.js +66 -0
- package/package.json +119 -3
package/README.md
CHANGED
|
@@ -1,67 +1,73 @@
|
|
|
1
1
|
# @contractspec/lib.presentation-runtime-core
|
|
2
2
|
|
|
3
|
-
Website: https://contractspec.io
|
|
3
|
+
Website: https://contractspec.io
|
|
4
4
|
|
|
5
|
+
**Core presentation runtime for contract-driven UIs.**
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
## What It Provides
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
- **Layer**: lib.
|
|
10
|
+
- **Consumers**: presentation-runtime-react, presentation-runtime-react-native.
|
|
11
|
+
- Related ContractSpec packages include `@contractspec/lib.contracts-spec`, `@contractspec/tool.bun`, `@contractspec/tool.typescript`.
|
|
12
|
+
- Related ContractSpec packages include `@contractspec/lib.contracts-spec`, `@contractspec/tool.bun`, `@contractspec/tool.typescript`.
|
|
11
13
|
|
|
12
14
|
## Installation
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
npm install @contractspec/lib.presentation-runtime-core
|
|
16
|
-
# or
|
|
17
|
-
bun add @contractspec/lib.presentation-runtime-core
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Key Concepts
|
|
21
|
-
|
|
22
|
-
- **Workflow State**: Manages the progression of steps in a `WorkflowSpec`.
|
|
23
|
-
- **Step Navigation**: Logic for next/previous/submit actions.
|
|
24
|
-
- **Validation**: Integration with Zod schemas for step validation.
|
|
16
|
+
`npm install @contractspec/lib.presentation-runtime-core`
|
|
25
17
|
|
|
26
|
-
|
|
18
|
+
or
|
|
27
19
|
|
|
28
|
-
|
|
29
|
-
- Validation helpers.
|
|
20
|
+
`bun add @contractspec/lib.presentation-runtime-core`
|
|
30
21
|
|
|
31
22
|
## Usage
|
|
32
23
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
24
|
+
Import the root entrypoint from `@contractspec/lib.presentation-runtime-core`, or choose a documented subpath when you only need one part of the package surface.
|
|
25
|
+
|
|
26
|
+
## Architecture
|
|
27
|
+
|
|
28
|
+
- `src/index.ts` is the root public barrel and package entrypoint.
|
|
29
|
+
- `src/metro.cjs` is part of the package's public or composition surface.
|
|
30
|
+
- `src/next.mjs` is part of the package's public or composition surface.
|
|
31
|
+
- `src/table.ts` is part of the package's public or composition surface.
|
|
32
|
+
- `src/visualization.echarts.ts` is part of the package's public or composition surface.
|
|
33
|
+
- `src/visualization.model.builders.ts` is part of the package's public or composition surface.
|
|
34
|
+
- `src/visualization.model.helpers.ts` is part of the package's public or composition surface.
|
|
35
|
+
|
|
36
|
+
## Public Entry Points
|
|
37
|
+
|
|
38
|
+
- Export `.` resolves through `./src/index.ts`.
|
|
39
|
+
- Export `./table` resolves through `./src/table.ts`.
|
|
40
|
+
- Export `./visualization` resolves through `./src/visualization.ts`.
|
|
41
|
+
- Export `./visualization.echarts` resolves through `./src/visualization.echarts.ts`.
|
|
42
|
+
- Export `./visualization.model` resolves through `./src/visualization.model.ts`.
|
|
43
|
+
- Export `./visualization.model.builders` resolves through `./src/visualization.model.builders.ts`.
|
|
44
|
+
- Export `./visualization.model.helpers` resolves through `./src/visualization.model.helpers.ts`.
|
|
45
|
+
- Export `./visualization.types` resolves through `./src/visualization.types.ts`.
|
|
46
|
+
- Export `./visualization.utils` resolves through `./src/visualization.utils.ts`.
|
|
47
|
+
|
|
48
|
+
## Local Commands
|
|
49
|
+
|
|
50
|
+
- `bun run dev` — contractspec-bun-build dev
|
|
51
|
+
- `bun run build` — bun run prebuild && bun run build:bundle && bun run build:types
|
|
52
|
+
- `bun run lint` — bun run lint:fix
|
|
53
|
+
- `bun run lint:check` — biome check .
|
|
54
|
+
- `bun run lint:fix` — biome check --write --unsafe --only=nursery/useSortedClasses . && biome check --write .
|
|
55
|
+
- `bun run typecheck` — tsc --noEmit -p tsconfig.json
|
|
56
|
+
- `bun run publish:pkg` — bun publish --tolerate-republish --ignore-scripts --verbose
|
|
57
|
+
- `bun run publish:pkg:canary` — bun publish:pkg --tag canary
|
|
58
|
+
- `bun run clean` — rimraf dist .turbo
|
|
59
|
+
- `bun run build:bundle` — contractspec-bun-build transpile
|
|
60
|
+
- `bun run build:types` — contractspec-bun-build types
|
|
61
|
+
- `bun run prebuild` — contractspec-bun-build prebuild
|
|
62
|
+
|
|
63
|
+
## Recent Updates
|
|
64
|
+
|
|
65
|
+
- Replace eslint+prettier by biomejs to optimize speed.
|
|
66
|
+
- Add data visualization capabilities.
|
|
67
|
+
- Add table capabilities.
|
|
68
|
+
|
|
69
|
+
## Notes
|
|
70
|
+
|
|
71
|
+
- Core runtime interface is consumed by all presentation runtimes — changes here affect both web and mobile.
|
|
72
|
+
- Must remain platform-agnostic; no React or React Native imports allowed.
|
|
73
|
+
- API surface changes require coordinated updates in both downstream runtimes.
|
package/dist/browser/index.js
CHANGED
|
@@ -1,3 +1,471 @@
|
|
|
1
|
+
// src/table.ts
|
|
2
|
+
function createEmptyTableState() {
|
|
3
|
+
return {
|
|
4
|
+
sorting: [],
|
|
5
|
+
pagination: {
|
|
6
|
+
pageIndex: 0,
|
|
7
|
+
pageSize: 25
|
|
8
|
+
},
|
|
9
|
+
columnVisibility: {},
|
|
10
|
+
columnSizing: {},
|
|
11
|
+
columnPinning: {
|
|
12
|
+
left: [],
|
|
13
|
+
right: []
|
|
14
|
+
},
|
|
15
|
+
expanded: {},
|
|
16
|
+
rowSelection: {}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/visualization.utils.ts
|
|
21
|
+
function formatVisualizationValue(value, format) {
|
|
22
|
+
if (value == null)
|
|
23
|
+
return "—";
|
|
24
|
+
if (format === "currency" && typeof value === "number") {
|
|
25
|
+
return new Intl.NumberFormat(undefined, {
|
|
26
|
+
style: "currency",
|
|
27
|
+
currency: "USD"
|
|
28
|
+
}).format(value);
|
|
29
|
+
}
|
|
30
|
+
if (format === "percentage" && typeof value === "number") {
|
|
31
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
32
|
+
}
|
|
33
|
+
if ((format === "date" || format === "dateTime") && value) {
|
|
34
|
+
const date = value instanceof Date ? value : new Date(String(value));
|
|
35
|
+
return Number.isNaN(date.getTime()) ? String(value) : new Intl.DateTimeFormat(undefined, {
|
|
36
|
+
dateStyle: "medium",
|
|
37
|
+
timeStyle: format === "dateTime" ? "short" : undefined
|
|
38
|
+
}).format(date);
|
|
39
|
+
}
|
|
40
|
+
return String(value);
|
|
41
|
+
}
|
|
42
|
+
function resolveVisualizationRows(data, resultPath) {
|
|
43
|
+
const value = resultPath ? getAtPath(data, resultPath) : data;
|
|
44
|
+
const candidate = value ?? data;
|
|
45
|
+
if (Array.isArray(candidate))
|
|
46
|
+
return candidate.map(asRow);
|
|
47
|
+
if (Array.isArray(getAtPath(candidate, "items"))) {
|
|
48
|
+
return getAtPath(candidate, "items").map(asRow);
|
|
49
|
+
}
|
|
50
|
+
if (Array.isArray(getAtPath(candidate, "rows"))) {
|
|
51
|
+
return getAtPath(candidate, "rows").map(asRow);
|
|
52
|
+
}
|
|
53
|
+
if (Array.isArray(getAtPath(candidate, "data"))) {
|
|
54
|
+
return getAtPath(candidate, "data").map(asRow);
|
|
55
|
+
}
|
|
56
|
+
return candidate && typeof candidate === "object" ? [asRow(candidate)] : [];
|
|
57
|
+
}
|
|
58
|
+
function getAtPath(source, path) {
|
|
59
|
+
if (!path || source == null)
|
|
60
|
+
return source;
|
|
61
|
+
return path.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean).reduce((current, segment) => {
|
|
62
|
+
if (current == null || typeof current !== "object")
|
|
63
|
+
return;
|
|
64
|
+
return current[segment];
|
|
65
|
+
}, source);
|
|
66
|
+
}
|
|
67
|
+
function toNumber(value) {
|
|
68
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
69
|
+
return value;
|
|
70
|
+
if (typeof value === "string" && value.trim()) {
|
|
71
|
+
const parsed = Number(value);
|
|
72
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
function asRow(value) {
|
|
77
|
+
return value && typeof value === "object" ? value : { value };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/visualization.model.helpers.ts
|
|
81
|
+
function createVisualizationMaps(spec) {
|
|
82
|
+
return {
|
|
83
|
+
dimensions: new Map((spec.visualization.dimensions ?? []).map((dimension) => [
|
|
84
|
+
dimension.key,
|
|
85
|
+
dimension
|
|
86
|
+
])),
|
|
87
|
+
measures: new Map((spec.visualization.measures ?? []).map((measure) => [
|
|
88
|
+
measure.key,
|
|
89
|
+
measure
|
|
90
|
+
]))
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function createVisualizationBaseModel(spec, rows, maps) {
|
|
94
|
+
const config = spec.visualization;
|
|
95
|
+
return {
|
|
96
|
+
kind: config.kind,
|
|
97
|
+
title: config.title ?? spec.meta.title,
|
|
98
|
+
description: config.description ?? spec.meta.description,
|
|
99
|
+
summary: `${spec.meta.title ?? spec.meta.key}: ${rows.length} row${rows.length === 1 ? "" : "s"}`,
|
|
100
|
+
warnings: rows.length === 0 ? ["No visualization rows available."] : [],
|
|
101
|
+
palette: config.palette,
|
|
102
|
+
legend: config.legend,
|
|
103
|
+
tooltip: config.tooltip,
|
|
104
|
+
drilldown: config.drilldown,
|
|
105
|
+
thresholds: config.thresholds ?? [],
|
|
106
|
+
annotations: (config.annotations ?? []).map((annotation) => resolveAnnotation(annotation, rows[0] ?? {})),
|
|
107
|
+
table: createTable(config.table?.caption, config, rows)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function createMeasureSeries(measureKey, measures, rows, dimension, config) {
|
|
111
|
+
const measure = measures.get(measureKey);
|
|
112
|
+
if (!measure)
|
|
113
|
+
return null;
|
|
114
|
+
return {
|
|
115
|
+
key: config?.key ?? measure.key,
|
|
116
|
+
label: config?.label ?? measure.label,
|
|
117
|
+
type: config?.type,
|
|
118
|
+
color: config?.color ?? measure.color,
|
|
119
|
+
stack: config?.stack,
|
|
120
|
+
smooth: config?.smooth,
|
|
121
|
+
points: rows.map((row, index) => ({
|
|
122
|
+
id: `${measure.key}-${index}`,
|
|
123
|
+
raw: row,
|
|
124
|
+
x: dimension ? readDimensionValue(row, dimension) : index,
|
|
125
|
+
y: numericValue(row, measure),
|
|
126
|
+
value: numericValue(row, measure)
|
|
127
|
+
}))
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function defaultSeries(keys) {
|
|
131
|
+
return keys.map((key) => ({ key, label: key, measure: key }));
|
|
132
|
+
}
|
|
133
|
+
function createAxis(dimension) {
|
|
134
|
+
if (!dimension)
|
|
135
|
+
return;
|
|
136
|
+
return {
|
|
137
|
+
key: dimension.key,
|
|
138
|
+
label: dimension.label,
|
|
139
|
+
type: dimension.type === "time" ? "time" : dimension.type === "number" ? "number" : "category",
|
|
140
|
+
format: dimension.format
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function createValueAxis(key, measures) {
|
|
144
|
+
const measure = key ? measures.get(key) : undefined;
|
|
145
|
+
if (!measure)
|
|
146
|
+
return;
|
|
147
|
+
return {
|
|
148
|
+
key: measure.key,
|
|
149
|
+
label: measure.label,
|
|
150
|
+
type: "number",
|
|
151
|
+
format: measure.format
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function readValue(row, measure) {
|
|
155
|
+
return measure ? getAtPath(row, measure.dataPath) : undefined;
|
|
156
|
+
}
|
|
157
|
+
function readDimensionValue(row, dimension) {
|
|
158
|
+
return dimension ? getAtPath(row, dimension.dataPath) : undefined;
|
|
159
|
+
}
|
|
160
|
+
function numericValue(row, measure) {
|
|
161
|
+
return toNumber(readValue(row, measure));
|
|
162
|
+
}
|
|
163
|
+
function numericPathValue(row, dimension) {
|
|
164
|
+
return toNumber(readDimensionValue(row, dimension));
|
|
165
|
+
}
|
|
166
|
+
function stringValue(row, dimension) {
|
|
167
|
+
const value = readDimensionValue(row, dimension);
|
|
168
|
+
return value == null ? undefined : String(value);
|
|
169
|
+
}
|
|
170
|
+
function createTable(caption, config, rows) {
|
|
171
|
+
const dimensions = config.dimensions ?? [];
|
|
172
|
+
const measures = config.measures ?? [];
|
|
173
|
+
return {
|
|
174
|
+
caption,
|
|
175
|
+
columns: [
|
|
176
|
+
...dimensions.map((dimension) => ({
|
|
177
|
+
key: dimension.key,
|
|
178
|
+
label: dimension.label
|
|
179
|
+
})),
|
|
180
|
+
...measures.map((measure) => ({
|
|
181
|
+
key: measure.key,
|
|
182
|
+
label: measure.label
|
|
183
|
+
}))
|
|
184
|
+
],
|
|
185
|
+
rows: rows.map((row) => ({
|
|
186
|
+
...Object.fromEntries(dimensions.map((dimension) => [
|
|
187
|
+
dimension.key,
|
|
188
|
+
readDimensionValue(row, dimension)
|
|
189
|
+
])),
|
|
190
|
+
...Object.fromEntries(measures.map((measure) => [measure.key, readValue(row, measure)]))
|
|
191
|
+
}))
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function resolveAnnotation(annotation, row) {
|
|
195
|
+
return {
|
|
196
|
+
key: annotation.key,
|
|
197
|
+
label: annotation.label,
|
|
198
|
+
kind: annotation.kind,
|
|
199
|
+
color: annotation.color,
|
|
200
|
+
x: annotation.xDataPath ? getAtPath(row, annotation.xDataPath) : undefined,
|
|
201
|
+
y: annotation.yDataPath ? toNumber(getAtPath(row, annotation.yDataPath)) : undefined,
|
|
202
|
+
start: annotation.startDataPath ? getAtPath(row, annotation.startDataPath) : undefined,
|
|
203
|
+
end: annotation.endDataPath ? getAtPath(row, annotation.endDataPath) : undefined
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/visualization.model.builders.ts
|
|
208
|
+
function buildMetricModel(base, config, rows, maps) {
|
|
209
|
+
const measure = maps.measures.get(config.measure);
|
|
210
|
+
const comparison = config.comparisonMeasure ? maps.measures.get(config.comparisonMeasure) : undefined;
|
|
211
|
+
const currentRow = rows.at(-1) ?? {};
|
|
212
|
+
const sparkline = config.sparkline ? createMeasureSeries(config.sparkline.measure ?? config.measure, maps.measures, rows, maps.dimensions.get(config.sparkline.dimension)) : null;
|
|
213
|
+
return {
|
|
214
|
+
...base,
|
|
215
|
+
series: sparkline ? [sparkline] : [],
|
|
216
|
+
metric: {
|
|
217
|
+
label: measure?.label ?? config.measure,
|
|
218
|
+
value: readValue(currentRow, measure),
|
|
219
|
+
comparisonValue: comparison ? readValue(currentRow, comparison) : undefined,
|
|
220
|
+
format: measure?.format
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function buildCartesianModel(base, config, rows, maps) {
|
|
225
|
+
const series = (config.series ?? defaultSeries(config.yMeasures ?? [])).map((seriesItem) => createMeasureSeries(seriesItem.measure, maps.measures, rows, maps.dimensions.get(config.xDimension), seriesItem)).filter((item) => Boolean(item));
|
|
226
|
+
return {
|
|
227
|
+
...base,
|
|
228
|
+
series,
|
|
229
|
+
xAxis: createAxis(maps.dimensions.get(config.xDimension)),
|
|
230
|
+
yAxis: createValueAxis(config.yMeasures?.[0], maps.measures)
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function buildDistributionModel(base, config, rows, maps) {
|
|
234
|
+
const nameDimension = maps.dimensions.get(config.nameDimension);
|
|
235
|
+
const valueMeasure = maps.measures.get(config.valueMeasure);
|
|
236
|
+
return {
|
|
237
|
+
...base,
|
|
238
|
+
series: [
|
|
239
|
+
{
|
|
240
|
+
key: valueMeasure?.key ?? config.valueMeasure,
|
|
241
|
+
label: valueMeasure?.label ?? config.valueMeasure,
|
|
242
|
+
type: config.kind,
|
|
243
|
+
points: rows.map((row, index) => ({
|
|
244
|
+
id: `${config.kind}-${index}`,
|
|
245
|
+
raw: row,
|
|
246
|
+
name: stringValue(row, nameDimension),
|
|
247
|
+
value: numericValue(row, valueMeasure)
|
|
248
|
+
}))
|
|
249
|
+
}
|
|
250
|
+
]
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function buildHeatmapModel(base, config, rows, maps) {
|
|
254
|
+
const xDimension = maps.dimensions.get(config.xDimension);
|
|
255
|
+
const yDimension = maps.dimensions.get(config.yDimension);
|
|
256
|
+
const valueMeasure = maps.measures.get(config.valueMeasure);
|
|
257
|
+
return {
|
|
258
|
+
...base,
|
|
259
|
+
series: [
|
|
260
|
+
{
|
|
261
|
+
key: config.valueMeasure,
|
|
262
|
+
label: valueMeasure?.label ?? config.valueMeasure,
|
|
263
|
+
type: "heatmap",
|
|
264
|
+
points: rows.map((row, index) => ({
|
|
265
|
+
id: `heatmap-${index}`,
|
|
266
|
+
raw: row,
|
|
267
|
+
name: stringValue(row, yDimension),
|
|
268
|
+
x: readDimensionValue(row, xDimension),
|
|
269
|
+
y: numericValue(row, valueMeasure),
|
|
270
|
+
value: numericValue(row, valueMeasure)
|
|
271
|
+
}))
|
|
272
|
+
}
|
|
273
|
+
],
|
|
274
|
+
xAxis: createAxis(xDimension),
|
|
275
|
+
yAxis: createAxis(yDimension)
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function buildGeoModel(base, config, rows, maps) {
|
|
279
|
+
return {
|
|
280
|
+
...base,
|
|
281
|
+
series: [
|
|
282
|
+
{
|
|
283
|
+
key: "geo",
|
|
284
|
+
label: "Geo",
|
|
285
|
+
type: config.variant ?? "scatter",
|
|
286
|
+
points: rows.map((row, index) => ({
|
|
287
|
+
id: `geo-${index}`,
|
|
288
|
+
raw: row,
|
|
289
|
+
name: config.nameDimension ? stringValue(row, maps.dimensions.get(config.nameDimension)) : undefined,
|
|
290
|
+
value: config.valueMeasure ? numericValue(row, maps.measures.get(config.valueMeasure)) : undefined,
|
|
291
|
+
latitude: numericPathValue(row, maps.dimensions.get(config.latitudeDimension ?? "")),
|
|
292
|
+
longitude: numericPathValue(row, maps.dimensions.get(config.longitudeDimension ?? ""))
|
|
293
|
+
}))
|
|
294
|
+
}
|
|
295
|
+
],
|
|
296
|
+
geo: {
|
|
297
|
+
mode: config.mode ?? "chart",
|
|
298
|
+
variant: config.variant ?? "scatter",
|
|
299
|
+
geoJson: config.geoJson
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/visualization.model.ts
|
|
305
|
+
function createVisualizationModel(spec, data) {
|
|
306
|
+
const rows = resolveVisualizationRows(data, spec.source.resultPath);
|
|
307
|
+
const maps = createVisualizationMaps(spec);
|
|
308
|
+
const base = createVisualizationBaseModel(spec, rows, maps);
|
|
309
|
+
switch (spec.visualization.kind) {
|
|
310
|
+
case "metric":
|
|
311
|
+
return buildMetricModel(base, spec.visualization, rows, maps);
|
|
312
|
+
case "cartesian":
|
|
313
|
+
return buildCartesianModel(base, spec.visualization, rows, maps);
|
|
314
|
+
case "pie":
|
|
315
|
+
case "funnel":
|
|
316
|
+
return buildDistributionModel(base, spec.visualization, rows, maps);
|
|
317
|
+
case "heatmap":
|
|
318
|
+
return buildHeatmapModel(base, spec.visualization, rows, maps);
|
|
319
|
+
case "geo":
|
|
320
|
+
return buildGeoModel(base, spec.visualization, rows, maps);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// src/visualization.echarts.ts
|
|
324
|
+
function buildVisualizationEChartsOption(model) {
|
|
325
|
+
const thresholdLines = model.thresholds.map((threshold) => ({
|
|
326
|
+
yAxis: threshold.value,
|
|
327
|
+
name: threshold.label,
|
|
328
|
+
lineStyle: {
|
|
329
|
+
color: threshold.color ?? "#ef4444",
|
|
330
|
+
type: "dashed"
|
|
331
|
+
}
|
|
332
|
+
}));
|
|
333
|
+
const annotationLines = model.annotations.filter((annotation) => annotation.kind === "line" && annotation.y != null).map((annotation) => toMarkLine(annotation));
|
|
334
|
+
switch (model.kind) {
|
|
335
|
+
case "cartesian":
|
|
336
|
+
const cartesianSeries = model.series.map((series) => ({
|
|
337
|
+
name: series.label,
|
|
338
|
+
type: resolveCartesianSeriesType(series.type),
|
|
339
|
+
smooth: series.smooth,
|
|
340
|
+
stack: series.stack,
|
|
341
|
+
areaStyle: series.type === "area" ? {} : undefined,
|
|
342
|
+
itemStyle: series.color ? { color: series.color } : undefined,
|
|
343
|
+
lineStyle: series.color ? { color: series.color } : undefined,
|
|
344
|
+
data: series.points.filter((point) => point.y != null).map((point) => [point.x, point.y]),
|
|
345
|
+
markLine: thresholdLines.length || annotationLines.length ? { data: [...thresholdLines, ...annotationLines] } : undefined
|
|
346
|
+
}));
|
|
347
|
+
return {
|
|
348
|
+
color: model.palette,
|
|
349
|
+
tooltip: model.tooltip === false ? undefined : { trigger: "axis" },
|
|
350
|
+
legend: { show: model.legend ?? model.series.length > 1 },
|
|
351
|
+
xAxis: { type: model.xAxis?.type === "time" ? "time" : "category" },
|
|
352
|
+
yAxis: { type: "value", name: model.yAxis?.label },
|
|
353
|
+
series: cartesianSeries
|
|
354
|
+
};
|
|
355
|
+
case "pie":
|
|
356
|
+
const pieSeries = [
|
|
357
|
+
{
|
|
358
|
+
type: "pie",
|
|
359
|
+
radius: ["0%", "70%"],
|
|
360
|
+
data: model.series[0]?.points.filter((point) => point.value != null).map((point) => ({
|
|
361
|
+
name: point.name ?? "",
|
|
362
|
+
value: point.value
|
|
363
|
+
})) ?? []
|
|
364
|
+
}
|
|
365
|
+
];
|
|
366
|
+
return {
|
|
367
|
+
color: model.palette,
|
|
368
|
+
tooltip: model.tooltip === false ? undefined : { trigger: "item" },
|
|
369
|
+
series: pieSeries
|
|
370
|
+
};
|
|
371
|
+
case "heatmap":
|
|
372
|
+
const heatmapSeries = [
|
|
373
|
+
{
|
|
374
|
+
type: "heatmap",
|
|
375
|
+
data: model.series[0]?.points.filter((point) => point.value != null && point.name != null).map((point) => [point.x, point.name, point.value]) ?? []
|
|
376
|
+
}
|
|
377
|
+
];
|
|
378
|
+
return {
|
|
379
|
+
color: model.palette,
|
|
380
|
+
tooltip: model.tooltip === false ? undefined : { position: "top" },
|
|
381
|
+
xAxis: {
|
|
382
|
+
type: "category",
|
|
383
|
+
data: uniqueValues(model.series[0]?.points.map((point) => point.x))
|
|
384
|
+
},
|
|
385
|
+
yAxis: {
|
|
386
|
+
type: "category",
|
|
387
|
+
data: uniqueValues(model.series[0]?.points.map((point) => point.name))
|
|
388
|
+
},
|
|
389
|
+
visualMap: {
|
|
390
|
+
min: 0,
|
|
391
|
+
max: maxValue(model.series[0]?.points.map((point) => point.value)),
|
|
392
|
+
calculable: true,
|
|
393
|
+
orient: "horizontal",
|
|
394
|
+
left: "center",
|
|
395
|
+
bottom: 0
|
|
396
|
+
},
|
|
397
|
+
series: heatmapSeries
|
|
398
|
+
};
|
|
399
|
+
case "funnel":
|
|
400
|
+
const funnelSeries = [
|
|
401
|
+
{
|
|
402
|
+
type: "funnel",
|
|
403
|
+
data: model.series[0]?.points.filter((point) => point.value != null).map((point) => ({
|
|
404
|
+
name: point.name ?? "",
|
|
405
|
+
value: point.value
|
|
406
|
+
})) ?? []
|
|
407
|
+
}
|
|
408
|
+
];
|
|
409
|
+
return {
|
|
410
|
+
color: model.palette,
|
|
411
|
+
tooltip: model.tooltip === false ? undefined : { trigger: "item" },
|
|
412
|
+
series: funnelSeries
|
|
413
|
+
};
|
|
414
|
+
case "geo":
|
|
415
|
+
if (!model.geo || model.geo.mode === "slippy-map" || !model.geo.geoJson) {
|
|
416
|
+
return {};
|
|
417
|
+
}
|
|
418
|
+
const geoSeries = [
|
|
419
|
+
{
|
|
420
|
+
type: model.geo.variant === "heatmap" ? "heatmap" : "scatter",
|
|
421
|
+
coordinateSystem: "geo",
|
|
422
|
+
data: model.series[0]?.points.filter((point) => point.longitude != null && point.latitude != null && point.value != null).map((point) => ({
|
|
423
|
+
name: point.name ?? "",
|
|
424
|
+
value: [
|
|
425
|
+
point.longitude,
|
|
426
|
+
point.latitude,
|
|
427
|
+
point.value
|
|
428
|
+
]
|
|
429
|
+
})) ?? []
|
|
430
|
+
}
|
|
431
|
+
];
|
|
432
|
+
return {
|
|
433
|
+
color: model.palette,
|
|
434
|
+
tooltip: model.tooltip === false ? undefined : { trigger: "item" },
|
|
435
|
+
geo: {
|
|
436
|
+
map: "contractspec-visualization-geo",
|
|
437
|
+
roam: true
|
|
438
|
+
},
|
|
439
|
+
series: geoSeries
|
|
440
|
+
};
|
|
441
|
+
default:
|
|
442
|
+
return {};
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
function uniqueValues(values) {
|
|
446
|
+
return Array.from(new Set((values ?? []).filter((value) => value != null).map(String)));
|
|
447
|
+
}
|
|
448
|
+
function maxValue(values) {
|
|
449
|
+
return Math.max(...(values ?? []).map((value) => value ?? 0), 1);
|
|
450
|
+
}
|
|
451
|
+
function toMarkLine(annotation) {
|
|
452
|
+
return {
|
|
453
|
+
yAxis: annotation.y,
|
|
454
|
+
name: annotation.label,
|
|
455
|
+
lineStyle: {
|
|
456
|
+
color: annotation.color ?? "#2563eb",
|
|
457
|
+
type: "solid"
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
function resolveCartesianSeriesType(type) {
|
|
462
|
+
if (type === "bar")
|
|
463
|
+
return "bar";
|
|
464
|
+
if (type === "scatter")
|
|
465
|
+
return "scatter";
|
|
466
|
+
return "line";
|
|
467
|
+
}
|
|
468
|
+
|
|
1
469
|
// src/index.ts
|
|
2
470
|
function withPresentationNextAliases(config, opts = {}) {
|
|
3
471
|
const uiWeb = opts.uiKitWeb ?? "@contractspec/lib.ui-kit-web";
|
|
@@ -51,5 +519,9 @@ function withPresentationMetroAliases(config, opts = {}) {
|
|
|
51
519
|
}
|
|
52
520
|
export {
|
|
53
521
|
withPresentationNextAliases,
|
|
54
|
-
withPresentationMetroAliases
|
|
522
|
+
withPresentationMetroAliases,
|
|
523
|
+
formatVisualizationValue,
|
|
524
|
+
createVisualizationModel,
|
|
525
|
+
createEmptyTableState,
|
|
526
|
+
buildVisualizationEChartsOption
|
|
55
527
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/table.ts
|
|
2
|
+
function createEmptyTableState() {
|
|
3
|
+
return {
|
|
4
|
+
sorting: [],
|
|
5
|
+
pagination: {
|
|
6
|
+
pageIndex: 0,
|
|
7
|
+
pageSize: 25
|
|
8
|
+
},
|
|
9
|
+
columnVisibility: {},
|
|
10
|
+
columnSizing: {},
|
|
11
|
+
columnPinning: {
|
|
12
|
+
left: [],
|
|
13
|
+
right: []
|
|
14
|
+
},
|
|
15
|
+
expanded: {},
|
|
16
|
+
rowSelection: {}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
createEmptyTableState
|
|
21
|
+
};
|