@taylorwong/ichartjs 2.0.13 → 2.0.14
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/CHANGELOG.md +7 -0
- package/README.md +2 -2
- package/docs/agent/charting-scenario.md +1 -1
- package/docs/agent/coding-agent-integration.md +1 -1
- package/docs/agent/development/2.0-release-readiness.md +4 -0
- package/docs/agent/development/iteration-13.md +278 -0
- package/docs/agent/development/roadmap.md +2 -1
- package/docs/agent/editing-contract.md +3 -0
- package/docs/agent/frontend-integration.md +1 -1
- package/docs/agent/runtime-contract.md +4 -0
- package/docs/agent/usage-scenarios.md +1 -1
- package/docs/agent/zh-CN/charting-scenario.md +1 -1
- package/docs/agent/zh-CN/coding-agent-integration.md +1 -1
- package/docs/agent/zh-CN/editing-contract.md +2 -0
- package/docs/agent/zh-CN/frontend-integration.md +1 -1
- package/docs/agent/zh-CN/runtime-contract.md +3 -1
- package/docs/agent/zh-CN/usage-scenarios.md +1 -1
- package/docs/manifests/capabilities.json +1310 -32
- package/docs/manifests/commands.json +7 -7
- package/docs/manifests/schemas.json +182 -10
- package/package.json +21 -5
- package/skills/ichartjs/SKILL.md +1 -1
- package/src/capabilities.mjs +10 -36
- package/src/chart-lifecycle.mjs +31 -0
- package/src/charts.mjs +19 -10
- package/src/command.mjs +3 -2
- package/src/contract-registry.mjs +59 -0
- package/src/edit-controller.mjs +1 -1
- package/src/errors.mjs +31 -0
- package/src/index.mjs +142 -10
- package/src/schema.mjs +1 -0
- package/src/spec.mjs +45 -3
- package/types/consumer-fixture.ts +35 -0
- package/types/index.d.ts +33 -3
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"version": "1.0",
|
|
3
3
|
"source": "src/command.mjs#operationTypes",
|
|
4
4
|
"commands": {
|
|
5
|
-
"updateField": { "models": ["project-task", "timeline-event", "milestone", "burndown-sample", "flow-node", "flow-edge", "swimlane", "architecture-node", "architecture-edge", "mindmap-node"], "previewRequired": true, "undoable": true },
|
|
6
|
-
"updateRecord": { "models": ["project-task", "timeline-event", "milestone", "burndown-sample", "flow-node", "flow-edge", "swimlane", "architecture-node", "architecture-edge", "mindmap-node"], "previewRequired": true, "undoable": true },
|
|
5
|
+
"updateField": { "models": ["project-task", "timeline-event", "milestone", "burndown-sample", "flow-node", "flow-edge", "swimlane", "architecture-node", "architecture-edge", "mindmap-node", "mindmap-edge"], "previewRequired": true, "undoable": true },
|
|
6
|
+
"updateRecord": { "models": ["project-task", "timeline-event", "milestone", "burndown-sample", "flow-node", "flow-edge", "swimlane", "architecture-node", "architecture-edge", "mindmap-node", "mindmap-edge"], "previewRequired": true, "undoable": true },
|
|
7
7
|
"updateTask": { "models": ["project-task"], "previewRequired": true, "undoable": true },
|
|
8
8
|
"shiftTask": { "models": ["project-task"], "required": ["taskId", "days"], "previewRequired": true, "undoable": true },
|
|
9
9
|
"updateProgress": { "models": ["project-task"], "required": ["taskId", "progress"], "previewRequired": true, "undoable": true },
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"assignNodesToGroup": { "models": ["flow-node"], "required": ["nodeIds", "groupId"], "previewRequired": true, "confirmationRequired": true, "undoable": true },
|
|
22
22
|
"duplicateGroup": { "models": ["flow-node", "flow-edge"], "required": ["groupId"], "previewRequired": true, "confirmationRequired": true, "undoable": true },
|
|
23
23
|
"deleteGroup": { "models": ["flow-node", "flow-edge"], "required": ["groupId", "policy"], "previewRequired": true, "confirmationRequired": true, "undoable": true },
|
|
24
|
-
"updateEdge": { "models": ["flow-edge", "architecture-edge"], "required": ["edgeId", "changes"], "previewRequired": true, "undoable": true },
|
|
25
|
-
"removeEdge": { "models": ["flow-edge", "architecture-edge"], "required": ["edgeId"], "previewRequired": true, "confirmationRequired": true, "undoable": true },
|
|
26
|
-
"addEdge": { "models": ["flow-edge", "architecture-edge"], "required": ["from", "to"], "previewRequired": true, "confirmationRequired": true, "undoable": true },
|
|
24
|
+
"updateEdge": { "models": ["flow-edge", "architecture-edge", "mindmap-edge"], "required": ["edgeId", "changes"], "previewRequired": true, "undoable": true },
|
|
25
|
+
"removeEdge": { "models": ["flow-edge", "architecture-edge", "mindmap-edge"], "required": ["edgeId"], "previewRequired": true, "confirmationRequired": true, "undoable": true },
|
|
26
|
+
"addEdge": { "models": ["flow-edge", "architecture-edge", "mindmap-edge"], "required": ["from", "to"], "previewRequired": true, "confirmationRequired": true, "undoable": true },
|
|
27
27
|
"toggleGroupCollapse": { "models": ["flow-node"], "required": ["groupId"], "previewRequired": true, "confirmationRequired": true, "undoable": true },
|
|
28
|
-
"duplicateSelection": { "models": ["flow-node", "flow-edge"], "required": ["nodeIds"], "previewRequired": true, "confirmationRequired": true, "undoable": true },
|
|
29
|
-
"pasteSelection": { "models": ["flow-node", "flow-edge"], "required": ["nodes"], "previewRequired": true, "confirmationRequired": true, "undoable": true }
|
|
28
|
+
"duplicateSelection": { "models": ["flow-node", "flow-edge", "mindmap-edge"], "required": ["nodeIds"], "previewRequired": true, "confirmationRequired": true, "undoable": true },
|
|
29
|
+
"pasteSelection": { "models": ["flow-node", "flow-edge", "mindmap-edge"], "required": ["nodes"], "previewRequired": true, "confirmationRequired": true, "undoable": true }
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -2,15 +2,187 @@
|
|
|
2
2
|
"version": "1.0",
|
|
3
3
|
"source": "src/schema.mjs#getBusinessSchema",
|
|
4
4
|
"models": {
|
|
5
|
-
"project-task": {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
"project-task": {
|
|
6
|
+
"key": "id",
|
|
7
|
+
"required": [
|
|
8
|
+
"id",
|
|
9
|
+
"name",
|
|
10
|
+
"start",
|
|
11
|
+
"end"
|
|
12
|
+
],
|
|
13
|
+
"editable": [
|
|
14
|
+
"name",
|
|
15
|
+
"start",
|
|
16
|
+
"end",
|
|
17
|
+
"baselineStart",
|
|
18
|
+
"baselineEnd",
|
|
19
|
+
"actualStart",
|
|
20
|
+
"actualEnd",
|
|
21
|
+
"baselineDate",
|
|
22
|
+
"actualDate",
|
|
23
|
+
"progress",
|
|
24
|
+
"status",
|
|
25
|
+
"owner",
|
|
26
|
+
"resource",
|
|
27
|
+
"capacity",
|
|
28
|
+
"load",
|
|
29
|
+
"points",
|
|
30
|
+
"sprint",
|
|
31
|
+
"priority",
|
|
32
|
+
"labels",
|
|
33
|
+
"dependencies"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"timeline-event": {
|
|
37
|
+
"key": "id",
|
|
38
|
+
"required": [
|
|
39
|
+
"id",
|
|
40
|
+
"title",
|
|
41
|
+
"date"
|
|
42
|
+
],
|
|
43
|
+
"editable": [
|
|
44
|
+
"title",
|
|
45
|
+
"date",
|
|
46
|
+
"description",
|
|
47
|
+
"status"
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
"milestone": {
|
|
51
|
+
"key": "id",
|
|
52
|
+
"required": [
|
|
53
|
+
"id",
|
|
54
|
+
"title",
|
|
55
|
+
"date"
|
|
56
|
+
],
|
|
57
|
+
"editable": [
|
|
58
|
+
"title",
|
|
59
|
+
"date",
|
|
60
|
+
"status"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
"burndown-sample": {
|
|
64
|
+
"key": "id",
|
|
65
|
+
"required": [
|
|
66
|
+
"id",
|
|
67
|
+
"date",
|
|
68
|
+
"remaining"
|
|
69
|
+
],
|
|
70
|
+
"editable": [
|
|
71
|
+
"date",
|
|
72
|
+
"remaining",
|
|
73
|
+
"scopeChange"
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
"flow-node": {
|
|
77
|
+
"key": "id",
|
|
78
|
+
"required": [
|
|
79
|
+
"id",
|
|
80
|
+
"label"
|
|
81
|
+
],
|
|
82
|
+
"editable": [
|
|
83
|
+
"label",
|
|
84
|
+
"position",
|
|
85
|
+
"size",
|
|
86
|
+
"laneId",
|
|
87
|
+
"description",
|
|
88
|
+
"status"
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
"flow-edge": {
|
|
92
|
+
"key": "id",
|
|
93
|
+
"required": [
|
|
94
|
+
"id",
|
|
95
|
+
"from",
|
|
96
|
+
"to"
|
|
97
|
+
],
|
|
98
|
+
"editable": [
|
|
99
|
+
"from",
|
|
100
|
+
"to",
|
|
101
|
+
"fromPort",
|
|
102
|
+
"toPort",
|
|
103
|
+
"label",
|
|
104
|
+
"status",
|
|
105
|
+
"routing",
|
|
106
|
+
"curveTension",
|
|
107
|
+
"waypoints"
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
"swimlane": {
|
|
111
|
+
"key": "id",
|
|
112
|
+
"required": [
|
|
113
|
+
"id",
|
|
114
|
+
"label"
|
|
115
|
+
],
|
|
116
|
+
"editable": [
|
|
117
|
+
"label"
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
"architecture-node": {
|
|
121
|
+
"key": "id",
|
|
122
|
+
"required": [
|
|
123
|
+
"id",
|
|
124
|
+
"label"
|
|
125
|
+
],
|
|
126
|
+
"editable": [
|
|
127
|
+
"label",
|
|
128
|
+
"layerId",
|
|
129
|
+
"boundaryId",
|
|
130
|
+
"position",
|
|
131
|
+
"size",
|
|
132
|
+
"description",
|
|
133
|
+
"status"
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
"architecture-edge": {
|
|
137
|
+
"key": "id",
|
|
138
|
+
"required": [
|
|
139
|
+
"id",
|
|
140
|
+
"from",
|
|
141
|
+
"to"
|
|
142
|
+
],
|
|
143
|
+
"editable": [
|
|
144
|
+
"from",
|
|
145
|
+
"to",
|
|
146
|
+
"label",
|
|
147
|
+
"relation",
|
|
148
|
+
"routing",
|
|
149
|
+
"curveTension",
|
|
150
|
+
"waypoints",
|
|
151
|
+
"status"
|
|
152
|
+
]
|
|
153
|
+
},
|
|
154
|
+
"mindmap-edge": {
|
|
155
|
+
"key": "id",
|
|
156
|
+
"required": [
|
|
157
|
+
"id",
|
|
158
|
+
"from",
|
|
159
|
+
"to"
|
|
160
|
+
],
|
|
161
|
+
"editable": [
|
|
162
|
+
"from",
|
|
163
|
+
"to",
|
|
164
|
+
"label",
|
|
165
|
+
"routing",
|
|
166
|
+
"curveTension",
|
|
167
|
+
"waypoints",
|
|
168
|
+
"status"
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
"mindmap-node": {
|
|
172
|
+
"key": "id",
|
|
173
|
+
"required": [
|
|
174
|
+
"id",
|
|
175
|
+
"label"
|
|
176
|
+
],
|
|
177
|
+
"editable": [
|
|
178
|
+
"label",
|
|
179
|
+
"parentId",
|
|
180
|
+
"position",
|
|
181
|
+
"size",
|
|
182
|
+
"branch",
|
|
183
|
+
"description",
|
|
184
|
+
"status"
|
|
185
|
+
]
|
|
186
|
+
}
|
|
15
187
|
}
|
|
16
188
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taylorwong/ichartjs",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.14",
|
|
4
4
|
"description": "Agent-first, renderer-independent charting and project visualization runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.mjs",
|
|
@@ -37,14 +37,20 @@
|
|
|
37
37
|
"test": "node --test tests/*.test.mjs",
|
|
38
38
|
"test:browser": "node --test tests/browser/*.test.mjs",
|
|
39
39
|
"check": "for file in src/*.mjs; do node --check \"$file\" || exit 1; done",
|
|
40
|
+
"contracts:generate": "node scripts/generate-contract-manifests.mjs",
|
|
41
|
+
"contracts:check": "node scripts/check-contracts.mjs",
|
|
42
|
+
"types:check": "node scripts/check-types.mjs",
|
|
43
|
+
"deps:check": "node scripts/check-cycles.mjs",
|
|
44
|
+
"docs:examples": "node examples/agent-workflow.mjs",
|
|
40
45
|
"docs:check": "node scripts/check-agent-docs.mjs",
|
|
41
46
|
"example:agent": "node examples/agent-workflow.mjs",
|
|
42
47
|
"playground": "node scripts/serve-playground.mjs",
|
|
43
|
-
"agent:check": "npm run docs:check && npm run check && npm test",
|
|
48
|
+
"agent:check": "npm run contracts:check && npm run types:check && npm run deps:check && npm run docs:check && npm run docs:examples && npm run check && npm test",
|
|
44
49
|
"rc:check": "npm test && npm run check && git diff --check"
|
|
45
50
|
},
|
|
46
51
|
"devDependencies": {
|
|
47
|
-
"playwright-core": "^1.63.0"
|
|
52
|
+
"playwright-core": "^1.63.0",
|
|
53
|
+
"typescript": "^5.9.3"
|
|
48
54
|
},
|
|
49
55
|
"repository": {
|
|
50
56
|
"type": "git",
|
|
@@ -52,8 +58,18 @@
|
|
|
52
58
|
},
|
|
53
59
|
"homepage": "https://github.com/wanghetommy/ichartjs#readme",
|
|
54
60
|
"bugs": "https://github.com/wanghetommy/ichartjs/issues",
|
|
55
|
-
"keywords": [
|
|
56
|
-
|
|
61
|
+
"keywords": [
|
|
62
|
+
"chart",
|
|
63
|
+
"visualization",
|
|
64
|
+
"agent",
|
|
65
|
+
"svg",
|
|
66
|
+
"canvas",
|
|
67
|
+
"gantt",
|
|
68
|
+
"diagram"
|
|
69
|
+
],
|
|
70
|
+
"engines": {
|
|
71
|
+
"node": ">=18"
|
|
72
|
+
},
|
|
57
73
|
"author": "taylorwong <wanghetommy@gmail.com>",
|
|
58
74
|
"license": "Apache-2.0"
|
|
59
75
|
}
|
package/skills/ichartjs/SKILL.md
CHANGED
|
@@ -21,7 +21,7 @@ Recommended installation:
|
|
|
21
21
|
npx skills add wanghetommy/ichartjs --skill ichartjs
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
Use `--agent codex --global --yes` for global non-interactive Codex installation. Use the tagged directory `https://github.com/wanghetommy/ichartjs/tree/v2.0.
|
|
24
|
+
Use `--agent codex --global --yes` for global non-interactive Codex installation. Use the tagged directory `https://github.com/wanghetommy/ichartjs/tree/v2.0.14/skills/ichartjs` when reproducibility matters. WorkBuddy can import the same directory through its Skill interface; do not assume a `--agent workbuddy` adapter unless the installed CLI declares it.
|
|
25
25
|
|
|
26
26
|
The Skill is a workflow adapter, not the chart runtime. If the current JavaScript or TypeScript project does not already depend on iChart.js, install the matching runtime from GitHub:
|
|
27
27
|
|
package/src/capabilities.mjs
CHANGED
|
@@ -4,44 +4,13 @@
|
|
|
4
4
|
import { inspectData } from './data.mjs';
|
|
5
5
|
import { planStyle, styleCapabilities } from './theme.mjs';
|
|
6
6
|
import { defaultPreferences, preferenceDensities, preferenceMotions, preferenceTriStates } from './preferences.mjs';
|
|
7
|
+
import { businessModels, chartProfiles, chartTypes, commandTypes, contractVersion, diagramEdgeModels, diagramOperations, exportTypes, interactionDefaults, renderers } from './contract-registry.mjs';
|
|
7
8
|
|
|
8
|
-
export
|
|
9
|
+
export { chartProfiles, chartTypes };
|
|
9
10
|
|
|
10
11
|
const cartesian = ['line', 'area', 'bar', 'column', 'scatter'];
|
|
11
12
|
const project = ['gantt', 'timeline', 'milestone', 'burndown'];
|
|
12
13
|
const diagrams = ['flow', 'swimlane', 'architecture', 'mindmap'];
|
|
13
|
-
const status = (supported, notApplicable = []) => Object.fromEntries(supported.map(name => [name, 'supported']).concat(notApplicable.map(name => [name, 'not-applicable'])));
|
|
14
|
-
const commonPresentation = ['title', 'subtitle', 'theme', 'responsive', 'empty-state', 'invalid-data-state', 'export', 'branding'];
|
|
15
|
-
|
|
16
|
-
const definitions = {
|
|
17
|
-
line: { family: 'cartesian', intents: ['trend', 'time-series'], required: ['dimension', 'measure'], optional: ['series'], interactions: ['hover', 'tooltip', 'selection', 'crosshair', 'zoom', 'pan', 'keyboard'], features: status([...commonPresentation, 'axes', 'grid', 'legend', 'labels', 'formatting', 'multi-series', 'dual-axis']) },
|
|
18
|
-
area: { family: 'cartesian', intents: ['trend', 'composition'], required: ['dimension', 'measure'], optional: ['series'], interactions: ['hover', 'tooltip', 'selection', 'crosshair', 'zoom', 'pan', 'keyboard'], features: status([...commonPresentation, 'axes', 'grid', 'legend', 'labels', 'formatting', 'multi-series', 'stacked', 'percent-stacked']) },
|
|
19
|
-
bar: { family: 'cartesian', intents: ['comparison', 'ranking'], required: ['dimension', 'measure'], optional: ['series'], interactions: ['hover', 'tooltip', 'selection', 'zoom', 'pan', 'keyboard'], features: status([...commonPresentation, 'axes', 'grid', 'legend', 'labels', 'formatting', 'multi-series', 'stacked', 'percent-stacked']) },
|
|
20
|
-
column: { family: 'cartesian', intents: ['comparison', 'distribution', 'composition'], required: ['dimension', 'measure'], optional: ['series'], interactions: ['hover', 'tooltip', 'selection', 'zoom', 'pan', 'keyboard'], features: status([...commonPresentation, 'axes', 'grid', 'legend', 'labels', 'formatting', 'multi-series', 'stacked', 'percent-stacked', 'mixed-line-column', 'dual-axis', 'bin']) },
|
|
21
|
-
pie: { family: 'part-to-whole', intents: ['part-to-whole'], required: ['dimension', 'measure'], optional: [], interactions: ['hover', 'tooltip', 'selection', 'keyboard'], features: status([...commonPresentation, 'legend', 'labels', 'formatting', 'donut', 'zero-total'], ['axes', 'grid', 'zoom', 'pan']) },
|
|
22
|
-
scatter: { family: 'cartesian', intents: ['relationship', 'correlation'], required: ['measure', 'measure'], optional: ['dimension'], interactions: ['hover', 'tooltip', 'selection', 'brush', 'zoom', 'pan', 'keyboard'], features: status([...commonPresentation, 'axes', 'grid', 'labels', 'formatting'], ['stacked']) },
|
|
23
|
-
funnel: { family: 'stage', intents: ['funnel', 'conversion'], required: ['dimension', 'measure'], optional: [], interactions: ['hover', 'tooltip', 'selection', 'keyboard'], features: status([...commonPresentation, 'labels', 'formatting'], ['axes', 'grid', 'zoom', 'pan']) },
|
|
24
|
-
gauge: { family: 'indicator', intents: ['progress', 'single-value'], required: ['measure'], optional: [], interactions: ['hover', 'tooltip', 'selection', 'keyboard'], features: status([...commonPresentation, 'labels', 'formatting', 'domain'], ['axes', 'grid', 'legend', 'zoom', 'pan']) },
|
|
25
|
-
heatmap: { family: 'matrix', intents: ['matrix', 'correlation-grid'], required: ['dimension', 'dimension', 'measure'], optional: [], interactions: ['hover', 'tooltip', 'selection', 'keyboard'], features: status([...commonPresentation, 'axes', 'labels', 'formatting', 'color-scale', 'missing-values'], ['zoom', 'pan']) },
|
|
26
|
-
radar: { family: 'radial', intents: ['multidimensional', 'profile'], required: ['three-or-more-measures'], optional: ['series'], interactions: ['hover', 'tooltip', 'selection', 'keyboard'], features: status([...commonPresentation, 'legend', 'labels', 'formatting', 'multi-series', 'indicator-domains', 'mixed-unit-warning'], ['axes', 'grid', 'zoom', 'pan']) },
|
|
27
|
-
gantt: { family: 'project', intents: ['schedule', 'variance'], required: ['id', 'start', 'end'], optional: ['dependencies', 'progress', 'baseline', 'actual'], interactions: ['hover', 'tooltip', 'selection', 'zoom', 'pan', 'keyboard', 'linked-filter', 'linked-selection'], features: status([...commonPresentation, 'labels', 'formatting', 'critical-path', 'slack', 'baseline-actual-variance', 'working-calendar', 'dependencies']) },
|
|
28
|
-
timeline: { family: 'project', intents: ['timeline', 'milestone'], required: ['date', 'label'], optional: ['status'], interactions: ['hover', 'tooltip', 'selection', 'zoom', 'pan', 'keyboard'], features: status([...commonPresentation, 'labels', 'formatting']) },
|
|
29
|
-
milestone: { family: 'project', intents: ['milestone'], required: ['date', 'label'], optional: ['baseline', 'actual'], interactions: ['hover', 'tooltip', 'selection', 'zoom', 'pan', 'keyboard'], features: status([...commonPresentation, 'labels', 'formatting', 'baseline-actual-variance']) },
|
|
30
|
-
burndown: { family: 'project', intents: ['progress', 'release'], required: ['date', 'remaining'], optional: ['ideal', 'scopeChange'], interactions: ['hover', 'tooltip', 'selection', 'zoom', 'pan', 'keyboard'], features: status([...commonPresentation, 'axes', 'labels', 'formatting', 'scope-change', 'release-forecast']) },
|
|
31
|
-
flow: { family: 'diagram', intents: ['workflow'], required: ['nodes'], optional: ['edges', 'groups', 'ports'], interactions: ['hover', 'tooltip', 'selection', 'edge-selection', 'drag', 'edgeDrag', 'zoom', 'pan', 'keyboard', 'keyboard-edit', 'keyboard-port-connect', 'portConnect', 'copy-paste'], features: status([...commonPresentation, 'labels', 'routing', 'manual-routing', 'layout', 'groups', 'ports', 'history'], ['axes', 'grid', 'legend']) },
|
|
32
|
-
swimlane: { family: 'diagram', intents: ['responsibility'], required: ['nodes', 'lanes'], optional: ['edges', 'groups', 'ports'], interactions: ['hover', 'tooltip', 'selection', 'edge-selection', 'drag', 'edgeDrag', 'zoom', 'pan', 'keyboard', 'keyboard-edit', 'keyboard-port-connect', 'portConnect', 'copy-paste'], features: status([...commonPresentation, 'labels', 'routing', 'manual-routing', 'layout', 'groups', 'ports', 'lanes', 'history'], ['axes', 'grid', 'legend']) },
|
|
33
|
-
architecture: { family: 'diagram', intents: ['architecture', 'business-architecture', 'data-architecture', 'technical-architecture'], required: ['nodes'], optional: ['edges', 'layers', 'boundaries', 'groups', 'ports'], interactions: ['hover', 'tooltip', 'selection', 'edge-selection', 'drag', 'edgeDrag', 'zoom', 'pan', 'keyboard', 'keyboard-edit', 'keyboard-port-connect', 'portConnect', 'copy-paste'], features: status([...commonPresentation, 'labels', 'routing', 'manual-routing', 'layout', 'layers', 'boundaries', 'groups', 'ports', 'history'], ['axes', 'grid', 'legend']) },
|
|
34
|
-
mindmap: { family: 'diagram', intents: ['mindmap', 'hierarchy', 'brainstorm'], required: ['nodes'], optional: ['parentId', 'edges', 'groups'], interactions: ['hover', 'tooltip', 'selection', 'edge-selection', 'drag', 'edgeDrag', 'zoom', 'pan', 'keyboard', 'keyboard-edit', 'keyboard-port-connect', 'portConnect', 'copy-paste'], features: status([...commonPresentation, 'labels', 'routing', 'cubic-bezier', 'curve-tension', 'obstacle-fallback', 'manual-routing', 'layout', 'tree-layout', 'radial-layout', 'history'], ['axes', 'grid', 'legend']) }
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export const chartProfiles = Object.fromEntries(chartTypes.map(type => [type, {
|
|
38
|
-
type,
|
|
39
|
-
...definitions[type],
|
|
40
|
-
dataShapes: definitions[type].family === 'diagram' ? ['diagram-spec'] : definitions[type].family === 'project' ? ['records'] : ['records', 'data-table'],
|
|
41
|
-
renderers: ['canvas', 'svg'],
|
|
42
|
-
exports: ['png', 'svg', 'json'],
|
|
43
|
-
limits: type === 'pie' ? { recommendedCategories: 8 } : type === 'radar' ? { minimumIndicators: 3, recommendedIndicators: 8 } : type === 'heatmap' ? { recommendedCells: 2500 } : {}
|
|
44
|
-
}]));
|
|
45
14
|
|
|
46
15
|
const intentMap = {
|
|
47
16
|
schedule: 'gantt', variance: 'gantt', timeline: 'timeline', milestone: 'milestone', progress: 'burndown', release: 'burndown', capacity: 'column', risk: 'scatter', aging: 'bar', matrix: 'heatmap', 'correlation-grid': 'heatmap', multidimensional: 'radar', profile: 'radar', workflow: 'flow', responsibility: 'swimlane', architecture: 'architecture', 'business-architecture': 'architecture', 'data-architecture': 'architecture', 'technical-architecture': 'architecture', mindmap: 'mindmap', hierarchy: 'mindmap', brainstorm: 'mindmap', trend: 'line', 'time-series': 'line', 'part-to-whole': 'pie', distribution: 'column', relationship: 'scatter', correlation: 'scatter', funnel: 'funnel', conversion: 'funnel', 'single-value': 'gauge', ranking: 'bar', comparison: 'bar', composition: 'column'
|
|
@@ -225,7 +194,7 @@ export function getCapabilities() {
|
|
|
225
194
|
};
|
|
226
195
|
return {
|
|
227
196
|
version: '2.0',
|
|
228
|
-
contractVersion
|
|
197
|
+
contractVersion,
|
|
229
198
|
chartTypes,
|
|
230
199
|
charts: JSON.parse(JSON.stringify(chartProfiles)),
|
|
231
200
|
intents,
|
|
@@ -240,9 +209,10 @@ export function getCapabilities() {
|
|
|
240
209
|
},
|
|
241
210
|
diagrams,
|
|
242
211
|
excluded: ['map', '3d'],
|
|
243
|
-
renderers: [
|
|
212
|
+
renderers: [...renderers],
|
|
213
|
+
interactionDefaults: { ...interactionDefaults },
|
|
244
214
|
interactions: [...new Set(Object.values(chartProfiles).flatMap(profile => profile.interactions))],
|
|
245
|
-
exports: [
|
|
215
|
+
exports: [...exportTypes],
|
|
246
216
|
headless: {
|
|
247
217
|
preview: true,
|
|
248
218
|
json: true,
|
|
@@ -263,6 +233,10 @@ export function getCapabilities() {
|
|
|
263
233
|
precedence: preferenceCapabilities.precedence,
|
|
264
234
|
schema: preferenceCapabilities
|
|
265
235
|
},
|
|
236
|
+
commands: [...commandTypes],
|
|
237
|
+
diagramOperations: [...diagramOperations],
|
|
238
|
+
businessModels: [...businessModels],
|
|
239
|
+
diagramEdgeModels: [...diagramEdgeModels],
|
|
266
240
|
plugins: true,
|
|
267
241
|
branding: { defaultEnabled: true, signature: 'Powered by iChart.js', options: [{ name: 'enabled', type: 'boolean', default: true }] },
|
|
268
242
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Renderer-independent chart lifecycle cleanup. */
|
|
2
|
+
export function destroyChart(chart) {
|
|
3
|
+
if (chart._destroyed) return;
|
|
4
|
+
chart._destroyed = true;
|
|
5
|
+
chart._preferencesUnsubscribe?.();
|
|
6
|
+
chart._resizeObserver?.disconnect();
|
|
7
|
+
chart._colorSchemeQuery?.removeEventListener?.('change', chart._colorSchemeHandler);
|
|
8
|
+
if (chart._eventsBound) {
|
|
9
|
+
const { target, handler, start, move, end, leave, touchStart, touchMove, touchEnd, wheel } = chart._eventsBound;
|
|
10
|
+
target.removeEventListener('mousemove', handler);
|
|
11
|
+
target.removeEventListener('click', handler);
|
|
12
|
+
target.removeEventListener('pointerdown', start);
|
|
13
|
+
target.removeEventListener('pointermove', move);
|
|
14
|
+
target.removeEventListener('pointerup', end);
|
|
15
|
+
target.removeEventListener('pointercancel', end);
|
|
16
|
+
target.removeEventListener('pointerleave', leave);
|
|
17
|
+
target.removeEventListener('touchstart', touchStart);
|
|
18
|
+
target.removeEventListener('touchmove', touchMove);
|
|
19
|
+
target.removeEventListener('touchend', touchEnd);
|
|
20
|
+
target.removeEventListener('wheel', wheel);
|
|
21
|
+
}
|
|
22
|
+
const target = chart.renderer.svg || chart.renderer.canvas;
|
|
23
|
+
if (chart._keyboardHandler) target?.removeEventListener('keydown', chart._keyboardHandler);
|
|
24
|
+
chart._tooltip?.remove();
|
|
25
|
+
chart.plugins.destroy();
|
|
26
|
+
chart.renderer.destroy();
|
|
27
|
+
chart.listeners.clear();
|
|
28
|
+
chart._eventsBound = null;
|
|
29
|
+
chart._selected.clear();
|
|
30
|
+
chart._clipboard = { nodes: [], edges: [] };
|
|
31
|
+
}
|
package/src/charts.mjs
CHANGED
|
@@ -142,27 +142,35 @@ function layout(spec, data, legendEntries = []) {
|
|
|
142
142
|
const leftLabelWidth = maxWidth(leftTexts), rightLabelWidth = yEncodings.length > 1 && !stackMode ? maxWidth(rightTicks.map(value => formatValue(value, spec.yAxis?.right?.format, spec.locale))) : 0;
|
|
143
143
|
const centeredPlot = ['pie', 'funnel', 'gauge', 'radar'].includes(spec.type);
|
|
144
144
|
const sideLabelWidth = Math.max(spec.type === 'bar' ? leftLabelWidth : 0, spec.type === 'heatmap' ? maxWidth(matrixTexts) : 0);
|
|
145
|
-
const leftTitleBand = spec.yAxis?.title
|
|
145
|
+
const leftTitleBand = spec.yAxis?.title ? axisSize + 16 : 0;
|
|
146
146
|
const rightTitleBand = spec.yAxis?.right?.title && yEncodings.length > 1 && !stackMode ? axisSize + 16 : 0;
|
|
147
147
|
const cartesianLeft = 8 + leftTitleBand + leftLabelWidth + 10;
|
|
148
148
|
const plotLeft = centeredPlot
|
|
149
149
|
? Math.max(p.left, p.right)
|
|
150
|
-
:
|
|
151
|
-
? Math.
|
|
152
|
-
:
|
|
150
|
+
: spec.type === 'bar'
|
|
151
|
+
? Math.max(p.left, spec.yAxis?.title ? cartesianLeft : sideLabelWidth + 24)
|
|
152
|
+
: spec.type === 'heatmap'
|
|
153
|
+
? Math.min(width * 0.35, Math.max(p.left, sideLabelWidth + 24))
|
|
154
|
+
: Math.max(p.left, cartesianLeft);
|
|
153
155
|
const plotRight = centeredPlot ? plotLeft : Math.max(p.right, rightLabelWidth ? rightLabelWidth + rightTitleBand + 18 : 0);
|
|
154
156
|
const labelTopReserve = spec.type === 'column' && spec.labels?.enabled ? Math.ceil(fontSize(spec, 'label', 12) * 1.5 + 4) : 0;
|
|
155
|
-
const
|
|
156
|
-
const plotTop = Math.max(p.top, chrome.bottom + (chrome.bottom ? 12 : 0)) + labelTopReserve + barTitleReserve;
|
|
157
|
+
const plotTop = Math.max(p.top, chrome.bottom + (chrome.bottom ? 12 : 0)) + labelTopReserve;
|
|
157
158
|
const plotWidth = Math.max(1, width - plotLeft - plotRight);
|
|
158
159
|
const axisLabelTexts = temporal ? timeTicks(xMin, xMax, 5).map(value => formatTime(value, xMax - xMin, spec.xAxis?.format, spec.locale)) : quantitativeX ? timeTicks(xMin, xMax, 5).map(value => formatValue(value, spec.xAxis?.format, spec.locale)) : categories;
|
|
159
160
|
const xLabels = axisLabelLayout(spec, spec.type === 'bar' ? [] : axisLabelTexts, plotWidth);
|
|
160
161
|
const bottomReserve = Math.max(p.bottom, Math.ceil(16 + xLabels.projectedHeight + (spec.xAxis?.title ? 20 : 0)));
|
|
161
162
|
const plot = { x: plotLeft, y: plotTop, width: plotWidth, height: Math.max(1, height - plotTop - bottomReserve) };
|
|
163
|
+
const axisTitles = {
|
|
164
|
+
y: spec.yAxis?.title ? truncateText(spec.yAxis.title, plot.height, axisSize) : null,
|
|
165
|
+
right: spec.yAxis?.right?.title && yEncodings.length > 1 && !stackMode ? truncateText(spec.yAxis.right.title, plot.height, axisSize) : null
|
|
166
|
+
};
|
|
167
|
+
const axisTitleWarnings = [];
|
|
168
|
+
if (spec.yAxis?.title && axisTitles.y !== String(spec.yAxis.title)) axisTitleWarnings.push({ path: 'yAxis.title', title: String(spec.yAxis.title), availableWidth: plot.height });
|
|
169
|
+
if (spec.yAxis?.right?.title && axisTitles.right !== String(spec.yAxis.right.title) && yEncodings.length > 1 && !stackMode) axisTitleWarnings.push({ path: 'yAxis.right.title', title: String(spec.yAxis.right.title), availableWidth: plot.height });
|
|
162
170
|
const rightTransform = value => rightType === 'log' ? Math.log10(Math.max(0.000001, Number(value))) : Number(value);
|
|
163
171
|
const yRight = value => plot.y + plot.height - ((rightTransform(value) - rightTransform(rightMin)) / (rightTransform(rightMax) - rightTransform(rightMin) || 1)) * plot.height;
|
|
164
172
|
const axisInfo = (rawDomain, domain, ticks, axis) => ({ rawDomain, domain, ticks, step: ticks.length > 1 ? (ticks[1] - ticks[0]) : null, policy: validDomain(axis?.domain) ? 'explicit' : axis?.nice === false ? 'raw' : 'nice' });
|
|
165
|
-
return { plot, chrome, xLabels, axisLayout: { size: axisSize, leftLabelWidth, rightLabelWidth }, compact: width < 360 || height < 240, recommendedSize: { minWidth: 280, minHeight: 220 }, xField, xEncoding, temporal, quantitativeX, xMin, xMax, yField, yEncodings, categories, rawMin, rawMax, min, max, rawRightMin, rawRightMax, rightMin, rightMax, yTicks, rightTicks, axes: { y: axisInfo([rawMin, rawMax], [min, max], yTicks, spec.yAxis || {}), right: axisInfo([rawRightMin, rawRightMax], [rightMin, rightMax], rightTicks, spec.yAxis?.right || {}) }, x, y, yRight, axisType, rightType };
|
|
173
|
+
return { plot, chrome, xLabels, axisTitles, axisTitleWarnings, axisLayout: { size: axisSize, leftLabelWidth, rightLabelWidth }, compact: width < 360 || height < 240, recommendedSize: { minWidth: 280, minHeight: 220 }, xField, xEncoding, temporal, quantitativeX, xMin, xMax, yField, yEncodings, categories, rawMin, rawMax, min, max, rawRightMin, rawRightMax, rightMin, rightMax, yTicks, rightTicks, axes: { y: axisInfo([rawMin, rawMax], [min, max], yTicks, spec.yAxis || {}), right: axisInfo([rawRightMin, rawRightMax], [rightMin, rightMax], rightTicks, spec.yAxis?.right || {}) }, x, y, yRight, axisType, rightType };
|
|
166
174
|
}
|
|
167
175
|
|
|
168
176
|
function addText(scene, id, text, x, y, style = {}, dataRef = null) { scene.add(new SceneNode({ id, type: 'text', geometry: { text: String(text), x, y }, style, dataRef })); }
|
|
@@ -178,7 +186,7 @@ function addAxes(scene, spec, state) {
|
|
|
178
186
|
ticks.forEach(value => addText(scene, `label-x-value-${value}`, formatValue(value, spec.xAxis?.format || spec.yAxis?.format, spec.locale), numericX(value), plot.y + plot.height + 22, { fill: spec.theme.muted, font: font(spec, 'axis'), textAnchor: 'middle' }));
|
|
179
187
|
categories.forEach((category, index) => addText(scene, `label-y-category-${index}`, category, plot.x - 10, plot.y + (index + .5) * plot.height / Math.max(1, categories.length) + 4, { fill: spec.theme.muted, font: font(spec, 'axis'), textAnchor: 'end' }));
|
|
180
188
|
if (spec.xAxis?.title) addText(scene, 'axis-x-title', spec.xAxis.title, plot.x + plot.width / 2, plot.y + plot.height + 42, { fill: spec.theme.text, font: font(spec, 'axis'), textAnchor: 'middle' });
|
|
181
|
-
if (
|
|
189
|
+
if (state.axisTitles.y) addText(scene, 'axis-y-title', state.axisTitles.y, 8 + axisSize / 2, plot.y + plot.height / 2, { fill: spec.theme.text, font: font(spec, 'axis'), textAnchor: 'middle', textBaseline: 'middle', baseline: 'middle', rotation: -90 });
|
|
182
190
|
return;
|
|
183
191
|
}
|
|
184
192
|
const labels = state.temporal || state.quantitativeX ? timeTicks(state.xMin, state.xMax, 5) : categories;
|
|
@@ -191,8 +199,8 @@ function addAxes(scene, spec, state) {
|
|
|
191
199
|
yTicks.forEach(value => addText(scene, `label-y-${value}`, formatValue(value, spec.yAxis?.format, spec.locale), plot.x - 10, y(value) + 4, { fill: spec.theme.muted, font: font(spec, 'axis'), textAnchor: 'end' }));
|
|
192
200
|
if (state.yEncodings.length > 1 && !spec.stack) state.rightTicks.forEach(value => addText(scene, `label-y-right-${value}`, formatValue(value, spec.yAxis?.right?.format, spec.locale), plot.x + plot.width + 10, yRight(value) + 4, { fill: spec.theme.muted, font: font(spec, 'axis') }));
|
|
193
201
|
if (spec.xAxis?.title) addText(scene, 'axis-x-title', spec.xAxis.title, plot.x + plot.width / 2, plot.y + plot.height + (state.xLabels.rotation ? state.xLabels.projectedHeight + 20 : 42), { fill: spec.theme.text, font: font(spec, 'axis'), textAnchor: 'middle' });
|
|
194
|
-
if (
|
|
195
|
-
if (state.
|
|
202
|
+
if (state.axisTitles.y) addText(scene, 'axis-y-title', state.axisTitles.y, 8 + axisSize / 2, plot.y + plot.height / 2, { fill: spec.theme.text, font: font(spec, 'axis'), textAnchor: 'middle', textBaseline: 'middle', baseline: 'middle', rotation: -90 });
|
|
203
|
+
if (state.axisTitles.right) addText(scene, 'axis-y-right-title', state.axisTitles.right, spec.width - 8 - axisSize / 2, plot.y + plot.height / 2, { fill: spec.theme.text, font: font(spec, 'axis'), textAnchor: 'middle', textBaseline: 'middle', baseline: 'middle', rotation: 90 });
|
|
196
204
|
}
|
|
197
205
|
|
|
198
206
|
function addTitles(scene, spec, positions) {
|
|
@@ -375,6 +383,7 @@ export function buildScene(spec) {
|
|
|
375
383
|
const entries = legendEntries(spec, data, series, colors);
|
|
376
384
|
const state = layout(spec, data, entries);
|
|
377
385
|
if (state.chrome.legend.truncatedCount) data.warnings.push({ code: 'LEGEND_OVERFLOW', path: 'legend', count: state.chrome.legend.truncatedCount, message: `${state.chrome.legend.truncatedCount} legend label${state.chrome.legend.truncatedCount === 1 ? '' : 's'} exceeded the available width and was truncated.`, suggestion: 'Use shorter category names or a wider chart.' });
|
|
386
|
+
state.axisTitleWarnings.forEach(({ path, title, availableWidth }) => data.warnings.push({ code: 'TITLE_TRUNCATED', path, availableWidth, message: `Axis title "${title}" exceeded the available vertical plot space and was truncated.`, suggestion: 'Use a taller chart or a shorter axis title.' }));
|
|
378
387
|
scene._labelArea = state.plot;
|
|
379
388
|
addTitles(scene, spec, state.chrome.title);
|
|
380
389
|
if (!['pie', 'funnel', 'gauge', 'heatmap', 'radar'].includes(spec.type)) addAxes(scene, spec, state);
|
package/src/command.mjs
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* Commands are JSON-safe, stable-ID based, and do not mutate input data.
|
|
4
4
|
*/
|
|
5
5
|
import { copyJSON, issue } from './schema.mjs';
|
|
6
|
+
import { commandTypes } from './contract-registry.mjs';
|
|
6
7
|
|
|
7
8
|
export const commandVersion = '1.0';
|
|
8
|
-
export const operationTypes = [
|
|
9
|
+
export const operationTypes = [...commandTypes];
|
|
9
10
|
|
|
10
11
|
export function normalizeCommand(input) {
|
|
11
12
|
const command = copyJSON(input);
|
|
@@ -63,7 +64,7 @@ export function commandCapabilities(schema) {
|
|
|
63
64
|
const fields = schema?.fields || {};
|
|
64
65
|
const can = name => fields[name]?.editable !== false && fields[name]?.agentEditable !== false;
|
|
65
66
|
const flowNode = ['flow-node', 'architecture-node', 'mindmap-node'].includes(schema?.name);
|
|
66
|
-
const flowEdge = ['flow-edge', 'architecture-edge'].includes(schema?.name);
|
|
67
|
+
const flowEdge = ['flow-edge', 'architecture-edge', 'mindmap-edge'].includes(schema?.name);
|
|
67
68
|
return {
|
|
68
69
|
updateField: Object.keys(fields).filter(can),
|
|
69
70
|
updateRecord: true,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-safe public contract metadata shared by runtime discovery and release checks.
|
|
3
|
+
* Keep this module dependency-free so browser consumers can import the registry safely.
|
|
4
|
+
*/
|
|
5
|
+
export const contractVersion = '1.1';
|
|
6
|
+
export const chartTypes = ['line', 'area', 'bar', 'column', 'pie', 'scatter', 'funnel', 'gauge', 'heatmap', 'radar', 'gantt', 'timeline', 'milestone', 'burndown', 'flow', 'swimlane', 'architecture', 'mindmap'];
|
|
7
|
+
export const renderers = ['canvas', 'svg'];
|
|
8
|
+
export const exportTypes = ['png', 'jpeg', 'svg', 'json'];
|
|
9
|
+
export const interactionDefaults = { zoom: false, pan: false, brush: false, drag: false, edgeDrag: false, portConnect: false, editing: false };
|
|
10
|
+
export const commandTypes = ['updateField', 'updateRecord', 'updateTask', 'shiftTask', 'updateProgress', 'addDependency', 'removeDependency', 'updateMilestone', 'moveNode', 'moveNodes', 'moveNodeToLane', 'resizeNode', 'alignNodes', 'snapNodes', 'moveGroup', 'resizeGroup', 'assignNodesToGroup', 'duplicateGroup', 'deleteGroup', 'updateEdge', 'removeEdge', 'addEdge', 'toggleGroupCollapse', 'duplicateSelection', 'pasteSelection'];
|
|
11
|
+
export const diagramOperations = ['moveNode', 'moveNodes', 'resizeNode', 'alignNodes', 'snapNodes', 'moveNodeToLane', 'moveGroup', 'resizeGroup', 'assignNodesToGroup', 'duplicateGroup', 'deleteGroup', 'updateEdge', 'removeEdge', 'addEdge', 'toggleGroupCollapse', 'duplicateSelection', 'pasteSelection'];
|
|
12
|
+
export const businessModels = ['project-task', 'timeline-event', 'milestone', 'burndown-sample', 'flow-node', 'flow-edge', 'swimlane', 'architecture-node', 'architecture-edge', 'mindmap-edge', 'mindmap-node'];
|
|
13
|
+
export const diagramEdgeModels = ['flow-edge', 'architecture-edge', 'mindmap-edge'];
|
|
14
|
+
|
|
15
|
+
const status = (supported, notApplicable = []) => Object.fromEntries(supported.map(name => [name, 'supported']).concat(notApplicable.map(name => [name, 'not-applicable'])));
|
|
16
|
+
const commonPresentation = ['title', 'subtitle', 'theme', 'responsive', 'empty-state', 'invalid-data-state', 'export', 'branding'];
|
|
17
|
+
|
|
18
|
+
export const chartDefinitions = {
|
|
19
|
+
line: { family: 'cartesian', intents: ['trend', 'time-series'], required: ['dimension', 'measure'], optional: ['series'], interactions: ['hover', 'tooltip', 'selection', 'crosshair', 'zoom', 'pan', 'keyboard'], features: status([...commonPresentation, 'axes', 'grid', 'legend', 'labels', 'formatting', 'multi-series', 'dual-axis']) },
|
|
20
|
+
area: { family: 'cartesian', intents: ['trend', 'composition'], required: ['dimension', 'measure'], optional: ['series'], interactions: ['hover', 'tooltip', 'selection', 'crosshair', 'zoom', 'pan', 'keyboard'], features: status([...commonPresentation, 'axes', 'grid', 'legend', 'labels', 'formatting', 'multi-series', 'stacked', 'percent-stacked']) },
|
|
21
|
+
bar: { family: 'cartesian', intents: ['comparison', 'ranking'], required: ['dimension', 'measure'], optional: ['series'], interactions: ['hover', 'tooltip', 'selection', 'zoom', 'pan', 'keyboard'], features: status([...commonPresentation, 'axes', 'grid', 'legend', 'labels', 'formatting', 'multi-series', 'stacked', 'percent-stacked']) },
|
|
22
|
+
column: { family: 'cartesian', intents: ['comparison', 'distribution', 'composition'], required: ['dimension', 'measure'], optional: ['series'], interactions: ['hover', 'tooltip', 'selection', 'zoom', 'pan', 'keyboard'], features: status([...commonPresentation, 'axes', 'grid', 'legend', 'labels', 'formatting', 'multi-series', 'stacked', 'percent-stacked', 'mixed-line-column', 'dual-axis', 'bin']) },
|
|
23
|
+
pie: { family: 'part-to-whole', intents: ['part-to-whole'], required: ['dimension', 'measure'], optional: [], interactions: ['hover', 'tooltip', 'selection', 'keyboard'], features: status([...commonPresentation, 'legend', 'labels', 'formatting', 'donut', 'zero-total'], ['axes', 'grid', 'zoom', 'pan']) },
|
|
24
|
+
scatter: { family: 'cartesian', intents: ['relationship', 'correlation'], required: ['measure', 'measure'], optional: ['dimension'], interactions: ['hover', 'tooltip', 'selection', 'brush', 'zoom', 'pan', 'keyboard'], features: status([...commonPresentation, 'axes', 'grid', 'labels', 'formatting'], ['stacked']) },
|
|
25
|
+
funnel: { family: 'stage', intents: ['funnel', 'conversion'], required: ['dimension', 'measure'], optional: [], interactions: ['hover', 'tooltip', 'selection', 'keyboard'], features: status([...commonPresentation, 'labels', 'formatting'], ['axes', 'grid', 'zoom', 'pan']) },
|
|
26
|
+
gauge: { family: 'indicator', intents: ['progress', 'single-value'], required: ['measure'], optional: [], interactions: ['hover', 'tooltip', 'selection', 'keyboard'], features: status([...commonPresentation, 'labels', 'formatting', 'domain'], ['axes', 'grid', 'legend', 'zoom', 'pan']) },
|
|
27
|
+
heatmap: { family: 'matrix', intents: ['matrix', 'correlation-grid'], required: ['dimension', 'dimension', 'measure'], optional: [], interactions: ['hover', 'tooltip', 'selection', 'keyboard'], features: status([...commonPresentation, 'axes', 'labels', 'formatting', 'color-scale', 'missing-values'], ['zoom', 'pan']) },
|
|
28
|
+
radar: { family: 'radial', intents: ['multidimensional', 'profile'], required: ['three-or-more-measures'], optional: ['series'], interactions: ['hover', 'tooltip', 'selection', 'keyboard'], features: status([...commonPresentation, 'legend', 'labels', 'formatting', 'multi-series', 'indicator-domains', 'mixed-unit-warning'], ['axes', 'grid', 'zoom', 'pan']) },
|
|
29
|
+
gantt: { family: 'project', intents: ['schedule', 'variance'], required: ['id', 'start', 'end'], optional: ['dependencies', 'progress', 'baseline', 'actual'], interactions: ['hover', 'tooltip', 'selection', 'zoom', 'pan', 'keyboard', 'linked-filter', 'linked-selection'], features: status([...commonPresentation, 'labels', 'formatting', 'critical-path', 'slack', 'baseline-actual-variance', 'working-calendar', 'dependencies']) },
|
|
30
|
+
timeline: { family: 'project', intents: ['timeline', 'milestone'], required: ['date', 'label'], optional: ['status'], interactions: ['hover', 'tooltip', 'selection', 'zoom', 'pan', 'keyboard'], features: status([...commonPresentation, 'labels', 'formatting']) },
|
|
31
|
+
milestone: { family: 'project', intents: ['milestone'], required: ['date', 'label'], optional: ['baseline', 'actual'], interactions: ['hover', 'tooltip', 'selection', 'zoom', 'pan', 'keyboard'], features: status([...commonPresentation, 'labels', 'formatting', 'baseline-actual-variance']) },
|
|
32
|
+
burndown: { family: 'project', intents: ['progress', 'release'], required: ['date', 'remaining'], optional: ['ideal', 'scopeChange'], interactions: ['hover', 'tooltip', 'selection', 'zoom', 'pan', 'keyboard'], features: status([...commonPresentation, 'axes', 'labels', 'formatting', 'scope-change', 'release-forecast']) },
|
|
33
|
+
flow: { family: 'diagram', intents: ['workflow'], required: ['nodes'], optional: ['edges', 'groups', 'ports'], interactions: ['hover', 'tooltip', 'selection', 'edge-selection', 'drag', 'edgeDrag', 'zoom', 'pan', 'keyboard', 'keyboard-edit', 'keyboard-port-connect', 'portConnect', 'copy-paste'], features: status([...commonPresentation, 'labels', 'routing', 'manual-routing', 'layout', 'groups', 'ports', 'history'], ['axes', 'grid', 'legend']) },
|
|
34
|
+
swimlane: { family: 'diagram', intents: ['responsibility'], required: ['nodes', 'lanes'], optional: ['edges', 'groups', 'ports'], interactions: ['hover', 'tooltip', 'selection', 'edge-selection', 'drag', 'edgeDrag', 'zoom', 'pan', 'keyboard', 'keyboard-edit', 'keyboard-port-connect', 'portConnect', 'copy-paste'], features: status([...commonPresentation, 'labels', 'routing', 'manual-routing', 'layout', 'groups', 'ports', 'lanes', 'history'], ['axes', 'grid', 'legend']) },
|
|
35
|
+
architecture: { family: 'diagram', intents: ['architecture', 'business-architecture', 'data-architecture', 'technical-architecture'], required: ['nodes'], optional: ['edges', 'layers', 'boundaries', 'groups', 'ports'], interactions: ['hover', 'tooltip', 'selection', 'edge-selection', 'drag', 'edgeDrag', 'zoom', 'pan', 'keyboard', 'keyboard-edit', 'keyboard-port-connect', 'portConnect', 'copy-paste'], features: status([...commonPresentation, 'labels', 'routing', 'manual-routing', 'layout', 'layers', 'boundaries', 'groups', 'ports', 'history'], ['axes', 'grid', 'legend']) },
|
|
36
|
+
mindmap: { family: 'diagram', intents: ['mindmap', 'hierarchy', 'brainstorm'], required: ['nodes'], optional: ['parentId', 'edges', 'groups'], interactions: ['hover', 'tooltip', 'selection', 'edge-selection', 'drag', 'edgeDrag', 'zoom', 'pan', 'keyboard', 'keyboard-edit', 'keyboard-port-connect', 'portConnect', 'copy-paste'], features: status([...commonPresentation, 'labels', 'routing', 'cubic-bezier', 'curve-tension', 'obstacle-fallback', 'manual-routing', 'layout', 'tree-layout', 'radial-layout', 'history'], ['axes', 'grid', 'legend']) }
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const chartProfiles = Object.fromEntries(chartTypes.map(type => [type, {
|
|
40
|
+
type,
|
|
41
|
+
...chartDefinitions[type],
|
|
42
|
+
dataShapes: chartDefinitions[type].family === 'diagram' ? ['diagram-spec'] : chartDefinitions[type].family === 'project' ? ['records'] : ['records', 'data-table'],
|
|
43
|
+
renderers: [...renderers],
|
|
44
|
+
exports: [...exportTypes],
|
|
45
|
+
limits: type === 'pie' ? { recommendedCategories: 8 } : type === 'radar' ? { minimumIndicators: 3, recommendedIndicators: 8 } : type === 'heatmap' ? { recommendedCells: 2500 } : {}
|
|
46
|
+
}]));
|
|
47
|
+
|
|
48
|
+
export const contractRegistry = Object.freeze({
|
|
49
|
+
contractVersion,
|
|
50
|
+
chartTypes: [...chartTypes],
|
|
51
|
+
chartProfiles: JSON.parse(JSON.stringify(chartProfiles)),
|
|
52
|
+
renderers: [...renderers],
|
|
53
|
+
exportTypes: [...exportTypes],
|
|
54
|
+
interactionDefaults: { ...interactionDefaults },
|
|
55
|
+
commandTypes: [...commandTypes],
|
|
56
|
+
diagramOperations: [...diagramOperations],
|
|
57
|
+
businessModels: [...businessModels],
|
|
58
|
+
diagramEdgeModels: [...diagramEdgeModels]
|
|
59
|
+
});
|
package/src/edit-controller.mjs
CHANGED
|
@@ -60,7 +60,7 @@ export class EditController {
|
|
|
60
60
|
edgeSchema: spec.data.edgeSchema,
|
|
61
61
|
type: spec.type,
|
|
62
62
|
nodeModel: spec.type === 'architecture' ? 'architecture-node' : spec.type === 'mindmap' ? 'mindmap-node' : 'flow-node',
|
|
63
|
-
edgeModel: spec.type === 'architecture' ? 'architecture-edge' : 'flow-edge',
|
|
63
|
+
edgeModel: spec.type === 'architecture' ? 'architecture-edge' : spec.type === 'mindmap' ? 'mindmap-edge' : 'flow-edge',
|
|
64
64
|
validationOptions: { ...spec.validationOptions, references: { ...spec.validationOptions?.references, ...(diagram ? { 'flow-node': nodes.map(row => row.id), swimlane: lanes.map(row => row.id) } : {}) } },
|
|
65
65
|
requireConfirmation: spec.editing?.requireConfirmation,
|
|
66
66
|
grid: spec.diagram?.grid || 8,
|