@peter.prib/renderengine 0.13.0 → 0.15.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.
Files changed (124) hide show
  1. package/README.md +480 -302
  2. package/bin/renderengine.mjs +157 -0
  3. package/dist/renderengine-workspace.umd.js +49217 -37484
  4. package/dist/renderengine.umd.js +24899 -13093
  5. package/documentation/REFERENCE.md +68 -1
  6. package/package.json +13 -3
  7. package/src/CanvasRenderer.js +225 -200
  8. package/src/SVGRenderer.js +476 -454
  9. package/src/archimate.js +22 -2
  10. package/src/archimateArchiFormat.js +1735 -1701
  11. package/src/archimateArchive.js +36 -7
  12. package/src/archimateCSV.js +93 -37
  13. package/src/archimateDrawio.js +2 -0
  14. package/src/archimateExchangeFormat.js +3 -1
  15. package/src/chartCatalog.js +269 -3
  16. package/src/charting/Annotations.js +169 -0
  17. package/src/charting/ArchimateDiagramEditor.js +54 -10
  18. package/src/charting/Axis.js +217 -5
  19. package/src/charting/Chart.js +99 -9
  20. package/src/charting/Chart3D.js +7 -2
  21. package/src/charting/ChartBox.js +8 -1
  22. package/src/charting/ColorPalette.js +42 -4
  23. package/src/charting/DiagramEditor.js +174 -39
  24. package/src/charting/DiagramEditorCommands.js +28 -0
  25. package/src/charting/Edge.js +7 -0
  26. package/src/charting/Hierarchy.js +142 -0
  27. package/src/charting/InteractionManager.js +4 -0
  28. package/src/charting/ModelFileFormat.js +5 -1
  29. package/src/charting/SettingsManager.js +35 -0
  30. package/src/charting/ShapePaths.js +82 -10
  31. package/src/charting/archimate/ArchimateCsvFileFormat.js +51 -12
  32. package/src/charting/archimate/ArchimateElementTemplate.js +235 -231
  33. package/src/charting/ask/AskBar.js +388 -0
  34. package/src/charting/ask/AskView.js +400 -0
  35. package/src/charting/charts/AlluvialDiagram.js +311 -0
  36. package/src/charting/charts/AreaChart.js +69 -15
  37. package/src/charting/charts/BarChart.js +38 -3
  38. package/src/charting/charts/BarChart3D.js +6 -4
  39. package/src/charting/charts/BarChart3DOrbit.js +3 -1
  40. package/src/charting/charts/BarChartBase.js +26 -6
  41. package/src/charting/charts/BeeswarmChart.js +164 -0
  42. package/src/charting/charts/BoxPlotChart.js +25 -3
  43. package/src/charting/charts/BubbleChart.js +4 -1
  44. package/src/charting/charts/CalendarHeatmapChart.js +2 -1
  45. package/src/charting/charts/CandlestickChart.js +172 -4
  46. package/src/charting/charts/ChordDiagram.js +107 -4
  47. package/src/charting/charts/CirclePackChart.js +81 -0
  48. package/src/charting/charts/ComboChart.js +233 -0
  49. package/src/charting/charts/ContourChart.js +310 -0
  50. package/src/charting/charts/DensityChart.js +1 -1
  51. package/src/charting/charts/DonutChart3D.js +13 -0
  52. package/src/charting/charts/DumbbellChart.js +217 -0
  53. package/src/charting/charts/EcdfChart.js +150 -0
  54. package/src/charting/charts/FunnelChart.js +5 -2
  55. package/src/charting/charts/GeoMapChart.js +132 -11
  56. package/src/charting/charts/GroupedBarChart.js +44 -3
  57. package/src/charting/charts/GroupedDistributionChart.js +26 -1
  58. package/src/charting/charts/HeatmapChart.js +6 -3
  59. package/src/charting/charts/HexbinChart.js +137 -0
  60. package/src/charting/charts/HierarchyChart.js +77 -3
  61. package/src/charting/charts/HistogramChart.js +2 -2
  62. package/src/charting/charts/HorizonChart.js +105 -0
  63. package/src/charting/charts/IcicleChart.js +47 -0
  64. package/src/charting/charts/KagiChart.js +167 -0
  65. package/src/charting/charts/LineChart.js +74 -18
  66. package/src/charting/charts/LollipopChart.js +104 -0
  67. package/src/charting/charts/MarimekkoChart.js +75 -0
  68. package/src/charting/charts/OhlcChart.js +79 -0
  69. package/src/charting/charts/OutlineChart.js +10 -8
  70. package/src/charting/charts/ParallelCoordinatesChart.js +11 -3
  71. package/src/charting/charts/ParetoChart.js +92 -0
  72. package/src/charting/charts/PieChart.js +195 -17
  73. package/src/charting/charts/PieChart3D.js +137 -0
  74. package/src/charting/charts/PipeChart.js +5 -2
  75. package/src/charting/charts/PointAndFigureChart.js +198 -0
  76. package/src/charting/charts/PolarAreaChart.js +189 -0
  77. package/src/charting/charts/PopulationPyramidChart.js +4 -4
  78. package/src/charting/charts/RadarChart.js +12 -5
  79. package/src/charting/charts/RadialBarChart.js +6 -3
  80. package/src/charting/charts/RenkoChart.js +178 -0
  81. package/src/charting/charts/RidgelineChart.js +113 -0
  82. package/src/charting/charts/SankeyDiagram.js +129 -6
  83. package/src/charting/charts/ScatterChart.js +19 -3
  84. package/src/charting/charts/ScatterMatrixChart.js +119 -0
  85. package/src/charting/charts/SlopegraphChart.js +205 -0
  86. package/src/charting/charts/SparklineTableChart.js +166 -0
  87. package/src/charting/charts/StackedBarChart.js +125 -11
  88. package/src/charting/charts/StackedBarChart3DOrbit.js +14 -6
  89. package/src/charting/charts/StreamGraphChart.js +18 -3
  90. package/src/charting/charts/SunburstChart.js +28 -8
  91. package/src/charting/charts/SurfaceChart3D.js +157 -0
  92. package/src/charting/charts/TreemapChart.js +31 -11
  93. package/src/charting/charts/ViolinChart.js +20 -2
  94. package/src/charting/charts/WaterfallChart.js +2 -2
  95. package/src/charting/charts/WordCloudChart.js +2 -2
  96. package/src/charting/datamodel/ArchimateDataLink.js +70 -0
  97. package/src/charting/datamodel/DataLineage.js +150 -0
  98. package/src/charting/datamodel/DataModel.js +83 -1
  99. package/src/charting/datamodel/ModelMapping.js +87 -0
  100. package/src/charting/datamodel/SqlDdl.js +110 -85
  101. package/src/charting/datamodel/index.js +3 -0
  102. package/src/charting/decision/DecisionWorkbench.js +295 -0
  103. package/src/charting/diagrams/ArchimateDiagram.js +25 -9
  104. package/src/charting/diagrams/ArchimateModel.js +1382 -1238
  105. package/src/charting/diagrams/GraphDiagram.js +1424 -1350
  106. package/src/charting/diagrams/SketchView.js +121 -0
  107. package/src/charting/diagrams/sketchBoxKinds.js +30 -0
  108. package/src/charting/modelTree/AnalysisPanel.js +101 -23
  109. package/src/charting/modelTree/NavigatorModel.js +410 -0
  110. package/src/charting/modelTree/NavigatorPanel.js +453 -0
  111. package/src/charting/pivot/PivotChart.js +310 -13
  112. package/src/charting/pivot/PivotGridChart.js +322 -34
  113. package/src/charting/pivot/PivotView.js +626 -21
  114. package/src/charting/report/TemplatedReport.js +547 -0
  115. package/src/charting/templates/BoxTemplate.js +28 -27
  116. package/src/charting/templates/PackageTemplate.js +26 -25
  117. package/src/charting/templates/SwimlaneTemplate.js +19 -18
  118. package/src/charting/templates/Template.js +219 -212
  119. package/src/cli.js +146 -0
  120. package/src/index.js +3370 -2899
  121. package/src/workspace/Workspace.js +126 -7
  122. package/src/workspace/archimateServices.js +4 -0
  123. package/src/workspace/parts/ArchimateCanvasPart.js +1 -1
  124. package/src/workspace/styles.js +36 -1
@@ -53,6 +53,15 @@ export function parseArchiFile(input) {
53
53
  visual.imagePath = url;
54
54
  }
55
55
  });
56
+ for (const el of model.archi?.elements || []) {
57
+ if (el.attributes?.imagePath) {
58
+ const url = resolve(el.attributes.imagePath);
59
+ if (url) {
60
+ el.attributes.archiImagePath = el.attributes.imagePath;
61
+ el.attributes.imagePath = url;
62
+ }
63
+ }
64
+ }
56
65
  return model;
57
66
  }
58
67
 
@@ -66,21 +75,41 @@ export function parseArchiFile(input) {
66
75
  export function toArchiFile(model, views) {
67
76
  const copies = _copyTrees(views);
68
77
  const pictures = new Map(); // data: URL -> archive path
69
- _eachVisual(copies, (visual) => {
70
- const url = visual.imagePath;
78
+ const registerPicture = (target, pathKey = 'imagePath', archiKey = 'archiImagePath') => {
79
+ const url = target[pathKey];
71
80
  if (!url?.startsWith('data:')) return;
72
81
  if (!pictures.has(url)) {
73
82
  // Its own archive path when it came from one (and no other picture took it), else a new one.
74
83
  const taken = new Set(pictures.values());
75
- const own = visual.archiImagePath && !taken.has(visual.archiImagePath) ? visual.archiImagePath : null;
84
+ const own = target[archiKey] && !taken.has(target[archiKey]) ? target[archiKey] : null;
76
85
  const { mime } = _parseDataUrl(url);
77
86
  let path = own;
78
- for (let n = pictures.size + 1; !path || (path !== own && taken.has(path)); n++) path = `${IMAGE_FOLDER}image-${n}.${EXTENSION_BY_MIME[mime] ?? 'bin'}`;
87
+ for (let n = pictures.size + 1; !path || (path !== own && taken.has(path)); n++) {
88
+ path = `${IMAGE_FOLDER}image-${n}.${EXTENSION_BY_MIME[mime] ?? 'bin'}`;
89
+ }
79
90
  pictures.set(url, path);
80
91
  }
81
- visual.imagePath = pictures.get(url);
82
- });
83
- const xml = toArchiXML(model, copies);
92
+ target[pathKey] = pictures.get(url);
93
+ };
94
+
95
+ _eachVisual(copies, (visual) => registerPicture(visual));
96
+
97
+ let modelForXml = model;
98
+ if (model?.archi?.elements?.some(e => e.attributes?.imagePath?.startsWith('data:'))) {
99
+ modelForXml = {
100
+ ...model,
101
+ archi: {
102
+ ...model.archi,
103
+ elements: model.archi.elements.map(e => {
104
+ if (!e.attributes?.imagePath?.startsWith('data:')) return e;
105
+ const clonedAttr = { ...e.attributes };
106
+ registerPicture(clonedAttr);
107
+ return { ...e, attributes: clonedAttr };
108
+ }),
109
+ },
110
+ };
111
+ }
112
+ const xml = toArchiXML(modelForXml, copies);
84
113
  if (!pictures.size) return { data: xml, archive: false, mimeType: 'application/xml' };
85
114
 
86
115
  const zip = new ZipArchive().add(ARCHI_MODEL_ENTRY, xml);
@@ -1,15 +1,13 @@
1
1
  import { parseCsvTable } from './csvTable.js';
2
+
2
3
  /**
3
- * archimateCSV — minimal CSV import/export for renderengine element/relationship
4
- * models. No external dependencies: a small RFC-4180-ish parser just big enough
5
- * for the two shapes we need.
6
- *
7
- * Two files are used:
8
- * - elements.csv: id, name, type, layer, documentation, properties
9
- * - relationships.csv: id, source, target, kind, label, documentation, properties
4
+ * archimateCSV — CSV import/export for renderengine element/relationship
5
+ * models and Archi interchange.
10
6
  *
11
- * The `properties` column is a JSON string of [{name, type, value}]. Empty cells
12
- * round-trip cleanly.
7
+ * Supports three files / tables:
8
+ * - elements.csv: id, name, type, layer, documentation, properties (or Archi ID, Type, Name, Documentation)
9
+ * - relations.csv / relationships.csv: id, source, target, kind, label, documentation, properties (or Archi ID, Type, Name, Documentation, Source, Target)
10
+ * - properties.csv: ID, Key, Value (or id, key, value)
13
11
  */
14
12
 
15
13
  function escapeCsvCell(value) {
@@ -66,8 +64,8 @@ export function exportRelationshipsCSV(relationships = []) {
66
64
  escapeCsvCell(rel.id),
67
65
  escapeCsvCell(rel.source),
68
66
  escapeCsvCell(rel.target),
69
- escapeCsvCell(rel.kind),
70
- escapeCsvCell(rel.label),
67
+ escapeCsvCell(rel.kind || rel.type),
68
+ escapeCsvCell(rel.label || rel.name),
71
69
  escapeCsvCell(rel.documentation),
72
70
  escapeCsvCell(stringifyProperties(rel.properties)),
73
71
  ].join(','));
@@ -75,18 +73,55 @@ export function exportRelationshipsCSV(relationships = []) {
75
73
  return lines.join('\n');
76
74
  }
77
75
 
76
+ /** Export properties of elements and relationships to properties.csv format (ID, Key, Value). */
77
+ export function exportPropertiesCSV(elements = [], relationships = []) {
78
+ const header = ['ID', 'Key', 'Value'];
79
+ const lines = [header.join(',')];
80
+ const exportItemProps = (item) => {
81
+ if (!item || !Array.isArray(item.properties)) return;
82
+ for (const p of item.properties) {
83
+ const k = p.key ?? p.name;
84
+ const v = p.value;
85
+ if (k != null) {
86
+ lines.push([escapeCsvCell(item.id), escapeCsvCell(k), escapeCsvCell(v)].join(','));
87
+ }
88
+ }
89
+ };
90
+ for (const el of elements) exportItemProps(el);
91
+ for (const rel of relationships) exportItemProps(rel);
92
+ return lines.join('\n');
93
+ }
94
+
95
+ /** Parse a properties CSV string into a Map of itemId -> Array<{name, value}>. */
96
+ export function importPropertiesCSV(csv) {
97
+ const rows = parseCsv(csv);
98
+ const byId = new Map();
99
+ for (const row of rows) {
100
+ const id = row.ID ?? row.id;
101
+ const key = row.Key ?? row.key ?? row.Name ?? row.name;
102
+ const val = row.Value ?? row.value ?? '';
103
+ if (id && key) {
104
+ if (!byId.has(id)) byId.set(id, []);
105
+ byId.get(id).push({ name: key, value: val });
106
+ }
107
+ }
108
+ return byId;
109
+ }
110
+
78
111
  /** Parse an elements CSV string into element objects. */
79
112
  export function importElementsCSV(csv) {
80
113
  const rows = parseCsv(csv);
81
114
  return rows.map(row => {
82
- const el = {
83
- id: row.id,
84
- name: row.name,
85
- type: row.type,
86
- };
87
- if (row.layer) el.layer = row.layer;
88
- if (row.documentation) el.documentation = row.documentation;
89
- const props = parseProperties(row.properties);
115
+ const id = row.id ?? row.ID;
116
+ const name = row.name ?? row.Name;
117
+ const type = row.type ?? row.Type;
118
+ const layer = row.layer ?? row.Layer;
119
+ const doc = row.documentation ?? row.Documentation;
120
+
121
+ const el = { id, name, type };
122
+ if (layer) el.layer = layer;
123
+ if (doc) el.documentation = doc;
124
+ const props = parseProperties(row.properties ?? row.Properties);
90
125
  if (props) el.properties = props;
91
126
  return el;
92
127
  });
@@ -96,32 +131,53 @@ export function importElementsCSV(csv) {
96
131
  export function importRelationshipsCSV(csv) {
97
132
  const rows = parseCsv(csv);
98
133
  return rows.map(row => {
99
- const rel = {
100
- id: row.id,
101
- source: row.source,
102
- target: row.target,
103
- kind: row.kind,
104
- };
105
- if (row.label) rel.label = row.label;
106
- if (row.documentation) rel.documentation = row.documentation;
107
- const props = parseProperties(row.properties);
134
+ const id = row.id ?? row.ID;
135
+ const source = row.source ?? row.Source;
136
+ const target = row.target ?? row.Target;
137
+ const kind = row.kind ?? row.Kind ?? row.type ?? row.Type;
138
+ const label = row.label ?? row.Label ?? row.name ?? row.Name;
139
+ const doc = row.documentation ?? row.Documentation;
140
+
141
+ const rel = { id, source, target, kind };
142
+ if (label) rel.label = label;
143
+ if (doc) rel.documentation = doc;
144
+ const props = parseProperties(row.properties ?? row.Properties);
108
145
  if (props) rel.properties = props;
109
146
  return rel;
110
147
  });
111
148
  }
112
149
 
113
- /** Convenience: export both in one call. Returns { elementsCSV, relationshipsCSV }. */
150
+ /** Convenience: export all three tables in one call. Returns { elementsCSV, relationshipsCSV, propertiesCSV }. */
114
151
  export function exportModelCSV(model = {}) {
152
+ const elements = model.elements || [];
153
+ const relationships = model.relationships || [];
115
154
  return {
116
- elementsCSV: exportElementsCSV(model.elements || []),
117
- relationshipsCSV: exportRelationshipsCSV(model.relationships || []),
155
+ elementsCSV: exportElementsCSV(elements),
156
+ relationshipsCSV: exportRelationshipsCSV(relationships),
157
+ propertiesCSV: exportPropertiesCSV(elements, relationships),
118
158
  };
119
159
  }
120
160
 
121
- /** Convenience: import both in one call. Returns { elements, relationships }. */
122
- export function importModelCSV(elementsCSV = '', relationshipsCSV = '') {
123
- return {
124
- elements: importElementsCSV(elementsCSV),
125
- relationships: importRelationshipsCSV(relationshipsCSV),
126
- };
161
+ /** Convenience: import all three tables in one call. Attaches parsed properties to items. */
162
+ export function importModelCSV(elementsCSV = '', relationshipsCSV = '', propertiesCSV = '') {
163
+ const elements = importElementsCSV(elementsCSV);
164
+ const relationships = importRelationshipsCSV(relationshipsCSV);
165
+
166
+ if (propertiesCSV && propertiesCSV.trim()) {
167
+ const propsById = importPropertiesCSV(propertiesCSV);
168
+ for (const el of elements) {
169
+ const props = propsById.get(el.id);
170
+ if (props?.length) {
171
+ el.properties = [...(el.properties || []), ...props];
172
+ }
173
+ }
174
+ for (const rel of relationships) {
175
+ const props = propsById.get(rel.id);
176
+ if (props?.length) {
177
+ rel.properties = [...(rel.properties || []), ...props];
178
+ }
179
+ }
180
+ }
181
+
182
+ return { elements, relationships };
127
183
  }
@@ -53,6 +53,8 @@ export const DRAWIO_ELEMENT_STYLES = Object.freeze({
53
53
  location: app('location', 'square'),
54
54
  grouping: box({ shape: 'folder', spacingTop: 10, tabWidth: 100, tabHeight: 25, tabPosition: 'left', dashed: 1, outlineConnect: undefined }),
55
55
  junction: box({ verticalLabelPosition: 'bottom', verticalAlign: 'top', outlineConnect: undefined }, 'ellipse;'),
56
+ 'and-junction': box({ verticalLabelPosition: 'bottom', verticalAlign: 'top', outlineConnect: undefined }, 'ellipse;fillColor=#000000;'),
57
+ 'or-junction': box({ verticalLabelPosition: 'bottom', verticalAlign: 'top', outlineConnect: undefined }, 'ellipse;fillColor=#ffffff;'),
56
58
  });
57
59
 
58
60
  /** The other boxes a view draws: notes, groups (folders), pictures, canvas boxes; and a type with no shape of its own. */
@@ -353,7 +353,9 @@ export function toExchangeXML(model, views = []) {
353
353
  ] : [];
354
354
  const textLines = (tag, text, indent) => text ? [`${indent}<${tag}>${escapeXml(text)}</${tag}>`] : [];
355
355
  const conceptPairs = (concept, extra = []) => [
356
- ...Object.entries(typedArrayToProperties(concept.properties)).map(([key, value]) => ({ key, value })),
356
+ ...(Array.isArray(concept.properties)
357
+ ? concept.properties.map(p => ({ key: p.name ?? p.key ?? '', value: p.value == null ? '' : String(p.value) }))
358
+ : Object.entries(typedArrayToProperties(concept.properties)).map(([key, value]) => ({ key, value }))),
357
359
  ...extra,
358
360
  ];
359
361
 
@@ -242,6 +242,16 @@ export const CHART_CATALOG = [
242
242
  dataShape: 'Array<{label, value}> — angular categories with radial bar length',
243
243
  streamShape: 'flat',
244
244
  },
245
+ {
246
+ key: 'polarArea', label: 'Polar Area (Nightingale Rose)', method: 'polarAreaChart', category: '2d',
247
+ coordSystem: COORD_SYSTEM.RADIAL, title: true, legend: true, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.ISOMETRIC,
248
+ axes: [
249
+ { role: 'category', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.RADIAL_AXIS, required: true },
250
+ { role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.RADIAL_DISTANCE, required: true },
251
+ ],
252
+ dataShape: 'Array<{label, value, exploded?}> — equal-angle wedges whose radius (or area) encodes value; supports 3D extrusion ({extrude3D:true}) and hover pull-out',
253
+ streamShape: 'flat',
254
+ },
245
255
  {
246
256
  key: 'populationPyramid', label: 'Population Pyramid', method: 'populationPyramid', category: '2d',
247
257
  coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: false, colorMode: COLOR_MODE.SEMANTIC, camera: CAMERA_MODE.NONE,
@@ -318,12 +328,15 @@ export const CHART_CATALOG = [
318
328
  },
319
329
  {
320
330
  key: 'stackedBar', label: 'Stacked Bar', method: 'stackedBarChart', category: '2d',
321
- coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: false, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.NONE,
331
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: true, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.NONE,
332
+ seriesCapable: true,
322
333
  axes: [
323
334
  { role: 'category', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_X, required: true },
324
- { role: 'values', kind: AXIS_KIND.NUMERIC_LIST, channel: ENCODING_CHANNEL.POSITION_Y_STACKED, required: true },
335
+ { role: 'values', kind: AXIS_KIND.NUMERIC_LIST, channel: ENCODING_CHANNEL.POSITION_Y_STACKED, required: false },
336
+ { role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y_STACKED, required: false },
337
+ { role: 'series', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.COLOR, required: false },
325
338
  ],
326
- dataShape: "Array<{label, values:number[]}> e.g. [{label:'Jan', values:[10,20]}] — no built-in legend option despite being multi-series (a real gap; areaChart's stacked mode has one, this doesn't)",
339
+ dataShape: "Array<{label, values:number[]}> or multi-series Array<{name, values:Array<{label,value}>}> — segments stacked from zero, with optional series legend",
327
340
  streamShape: 'flat', // a new stacked bar is a whole new {label,values} record appended to the outer array, not growth of an existing record's values
328
341
  },
329
342
  {
@@ -551,6 +564,26 @@ export const CHART_CATALOG = [
551
564
  dataShape: 'Array<{label, value, depth?}> — a single fixed-angle isometric shadow per bar, not a real camera (see bar3DOrbit for that)',
552
565
  streamShape: 'flat',
553
566
  },
567
+ {
568
+ key: 'pie3D', label: 'Pie (3D)', method: 'pieChart3D', category: '3d',
569
+ coordSystem: COORD_SYSTEM.RADIAL, title: true, legend: true, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.ISOMETRIC,
570
+ axes: [
571
+ { role: 'category', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.ANGLE_OR_AREA, required: true },
572
+ { role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.ANGLE_OR_AREA, required: true },
573
+ ],
574
+ dataShape: 'Array<{label, value, exploded?}> — extruded 3D tilted pie with cylinder skirt, radial cut walls, exploded slices, and hover slice pull-out',
575
+ streamShape: 'flat',
576
+ },
577
+ {
578
+ key: 'donut3D', label: 'Donut (3D)', method: 'donutChart3D', category: '3d',
579
+ coordSystem: COORD_SYSTEM.RADIAL, title: true, legend: true, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.ISOMETRIC,
580
+ axes: [
581
+ { role: 'category', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.ANGLE_OR_AREA, required: true },
582
+ { role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.ANGLE_OR_AREA, required: true },
583
+ ],
584
+ dataShape: 'Array<{label, value, exploded?}> — extruded 3D tilted doughnut (innerRadiusRatio=0.5) with inner/outer cylinder skirts, exploded slices, and hover slice pull-out',
585
+ streamShape: 'flat',
586
+ },
554
587
  {
555
588
  key: 'bubble3D', label: 'Bubble (3D)', method: 'bubbleChart3D', category: '3d',
556
589
  coordSystem: COORD_SYSTEM.ORBIT_3D, title: true, legend: false, colorMode: COLOR_MODE.GROUPED_FIELD, camera: CAMERA_MODE.ORBIT,
@@ -564,6 +597,50 @@ export const CHART_CATALOG = [
564
597
  dataShape: 'Array<{label, x, y, z, size?, category?}> — real orbit camera (drag-rotate/wheel-zoom); a future sphere/ellipsoid variant is this same shape with a second shape-size-channeled role, not a new chart type',
565
598
  streamShape: 'flat',
566
599
  },
600
+ {
601
+ key: 'pareto', label: 'Pareto', method: 'paretoChart', category: '2d',
602
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: false, colorMode: COLOR_MODE.SEMANTIC, camera: CAMERA_MODE.NONE,
603
+ axes: [
604
+ { role: 'category', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_X, required: true },
605
+ { role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
606
+ ],
607
+ dataShape: 'Array<{label, value}> — sorted descending with a right-hand 0-100% cumulative percentage line and 80% cutoff reference line',
608
+ streamShape: 'flat',
609
+ },
610
+ {
611
+ key: 'lollipop', label: 'Lollipop', method: 'lollipopChart', category: '2d',
612
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: true, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.NONE,
613
+ seriesCapable: true,
614
+ axes: [
615
+ { role: 'category', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_X, required: true },
616
+ { role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
617
+ { role: 'series', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.COLOR, required: false },
618
+ ],
619
+ dataShape: 'Array<{label, value}> or multi-series Array<{name, values:Array<{label,value}>}> — stem-and-dot alternative to bars',
620
+ streamShape: 'flat',
621
+ },
622
+ {
623
+ key: 'marimekko', label: 'Marimekko', method: 'marimekkoChart', category: '2d',
624
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: true, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.NONE,
625
+ seriesCapable: true,
626
+ axes: [
627
+ { role: 'category', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.SHAPE_WIDTH, required: true },
628
+ { role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y_HEIGHT, required: true },
629
+ { role: 'series', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.COLOR, required: true },
630
+ ],
631
+ dataShape: 'Multi-series Array<{name, values:Array<{label,value}>}> or Array<{label, values:number[]}> — variable-width 100%-stacked mosaic columns',
632
+ streamShape: 'series',
633
+ },
634
+ {
635
+ key: 'icicle', label: 'Icicle', method: 'icicleChart', category: '2d',
636
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: false, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.NONE,
637
+ axes: [
638
+ { role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.ANGLE_OR_AREA, required: true },
639
+ { role: 'group', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_ORDER, required: false },
640
+ ],
641
+ dataShape: 'Array<{label, value?, children?}> — rectangular hierarchical partition chart (top-down bands by depth, width proportional to value)',
642
+ streamShape: 'flat',
643
+ },
567
644
  {
568
645
  key: 'bar3DOrbit', label: 'Bar (3D Orbit)', method: 'barChart3DOrbit', category: '3d',
569
646
  coordSystem: COORD_SYSTEM.ORBIT_3D, title: true, legend: false, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.ORBIT,
@@ -586,6 +663,195 @@ export const CHART_CATALOG = [
586
663
  dataShape: 'Array<{x, z, values:number[]}> — same grid as bar3DOrbit, each cell a stack of segments; colors indexed by segment position, not z-group',
587
664
  streamShape: 'none', // fixed x*z grid
588
665
  },
666
+ {
667
+ key: 'surface3D', label: 'Surface Mesh (3D Orbit)', method: 'surfaceChart3D', category: '3d',
668
+ coordSystem: COORD_SYSTEM.ORBIT_3D, title: true, legend: false, colorMode: COLOR_MODE.SCALE, camera: CAMERA_MODE.ORBIT,
669
+ axes: [
670
+ { role: 'x', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_X, required: true },
671
+ { role: 'z', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_Z, required: true },
672
+ { role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y_HEIGHT, required: true },
673
+ ],
674
+ dataShape: 'Array<{x, z, value}> or {xLabels, zLabels, values:number[][]} — continuous 3D surface mesh with gradient shading, wireframe, and real orbit camera',
675
+ streamShape: 'none',
676
+ },
677
+ {
678
+ key: 'dumbbell', label: 'Dumbbell / Gap Chart', method: 'dumbbellChart', category: '2d',
679
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: true, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.NONE,
680
+ axes: [
681
+ { role: 'category', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_X, required: true },
682
+ { role: 'start', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
683
+ { role: 'end', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
684
+ ],
685
+ dataShape: 'Array<{label, start, end}> or multi-series Array<{name, values}> — connected pair of dots showing before/after or target/actual change across categories',
686
+ streamShape: 'flat',
687
+ },
688
+ {
689
+ key: 'slopegraph', label: 'Slopegraph', method: 'slopegraph', category: '2d',
690
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: false, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.NONE,
691
+ axes: [
692
+ { role: 'category', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_ORDER, required: true },
693
+ { role: 'values', kind: AXIS_KIND.NUMERIC_LIST, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
694
+ ],
695
+ dataShape: 'Array<{label, values: number[]}>, Array<{label, start, end}>, or multi-series — connects categories across stages with direct labels and hover highlighting',
696
+ streamShape: 'flat',
697
+ },
698
+ {
699
+ key: 'circlePack', label: 'Circle Packing', method: 'circlePackChart', category: '2d',
700
+ coordSystem: COORD_SYSTEM.RADIAL, title: true, legend: false, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.NONE,
701
+ axes: [
702
+ { role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.ANGLE_OR_AREA, required: true },
703
+ { role: 'group', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_ORDER, required: false },
704
+ ],
705
+ dataShape: 'Array<{label, value?, children?}> — nested tangent circles sized by value with translucent branch containment and hover elevation',
706
+ streamShape: 'flat',
707
+ },
708
+ {
709
+ key: 'ridgeline', label: 'Ridgeline / Joy Plot', method: 'ridgelineChart', category: 'statistical',
710
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: false, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.NONE,
711
+ axes: [
712
+ { role: 'group', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_ORDER, required: true },
713
+ { role: 'sample', kind: AXIS_KIND.RAW_SAMPLE, channel: ENCODING_CHANNEL.DERIVED, required: true },
714
+ ],
715
+ dataShape: 'Array<{label, values: number[]}> — vertically staggered, partially overlapping Gaussian-KDE density curves (Joy plot) with median lines',
716
+ streamShape: 'grouped-sample',
717
+ },
718
+ {
719
+ key: 'beeswarm', label: 'Beeswarm / Raincloud Plot', method: 'beeswarmChart', category: 'statistical',
720
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: false, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.NONE,
721
+ axes: [
722
+ { role: 'group', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_X, required: true },
723
+ { role: 'sample', kind: AXIS_KIND.RAW_SAMPLE, channel: ENCODING_CHANNEL.DERIVED, required: true },
724
+ ],
725
+ dataShape: 'Array<{label, values: number[]}> — individual sample points circle-packed along each group slot; Raincloud mode pairs half-KDE cloud, mini-box, and packed dots',
726
+ streamShape: 'grouped-sample',
727
+ },
728
+ {
729
+ key: 'combo', label: 'Combo (Bars & Lines)', method: 'comboChart', category: '2d',
730
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: true, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.NONE,
731
+ axes: [
732
+ { role: 'category', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_X, required: true },
733
+ { role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
734
+ { role: 'series', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.COLOR, required: true },
735
+ ],
736
+ dataShape: 'Array<{name, type?:"bar"|"line"|"area", yAxis?:"left"|"right", values}> — pairs bar series with lines/areas and optional secondary right Y-axis',
737
+ streamShape: 'series',
738
+ },
739
+ {
740
+ key: 'hexbin', label: 'Hexbin (2D Density)', method: 'hexbinChart', category: 'statistical',
741
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: false, colorMode: COLOR_MODE.SCALE, camera: CAMERA_MODE.NONE,
742
+ axes: [
743
+ { role: 'x', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_X, required: true },
744
+ { role: 'y', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
745
+ ],
746
+ dataShape: 'Array<{x, y, value?}> or Array<[x, y]> — 2D hexagonal binning and density plot for dense point clouds, eliminating overplotting',
747
+ streamShape: 'flat',
748
+ },
749
+ {
750
+ key: 'ecdf', label: 'Empirical CDF (Step Plot)', method: 'ecdfChart', category: 'statistical',
751
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: true, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.NONE,
752
+ axes: [
753
+ { role: 'sample', kind: AXIS_KIND.RAW_SAMPLE, channel: ENCODING_CHANNEL.DERIVED, required: true },
754
+ ],
755
+ dataShape: 'number[] or Array<{label, values: number[]}> — cumulative distribution step function (0..1) with median and Kolmogorov-Smirnov distance comparison',
756
+ streamShape: 'grouped-sample',
757
+ },
758
+ {
759
+ key: 'splom', label: 'Scatter Plot Matrix (SPLOM)', method: 'scatterMatrix', category: 'statistical',
760
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: true, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.NONE,
761
+ axes: [
762
+ { role: 'dimensions', kind: AXIS_KIND.NUMERIC_LIST, channel: ENCODING_CHANNEL.POSITION_ORDER, required: true },
763
+ ],
764
+ dataShape: 'Array<object> with options.dimensions — N x N grid of pairwise scatter plots with univariate KDE curves on the diagonal',
765
+ streamShape: 'flat',
766
+ },
767
+ {
768
+ key: 'sparklineTable', label: 'Sparkline Table', method: 'sparklineTable', category: '2d',
769
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: false, colorMode: COLOR_MODE.NONE, camera: CAMERA_MODE.NONE,
770
+ axes: [
771
+ { role: 'row', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_ORDER, required: true },
772
+ { role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.TEXT_VALUE, required: true },
773
+ ],
774
+ dataShape: 'Array<{label, value, target?, sparkline?: number[], delta?: number, status?}> — multi-metric table embedding inline sparklines and bullet KPI bars',
775
+ streamShape: 'flat',
776
+ },
777
+ {
778
+ key: 'horizon', label: 'Horizon Chart', method: 'horizonChart', category: '2d',
779
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: false, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.NONE,
780
+ axes: [
781
+ { role: 'series', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_ORDER, required: true },
782
+ { role: 'values', kind: AXIS_KIND.NUMERIC_LIST, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
783
+ ],
784
+ dataShape: 'Array<{label, values: number[]}> — compact layered area strips folding positive and negative magnitude bands into narrow viewports',
785
+ streamShape: 'flat',
786
+ },
787
+ {
788
+ key: 'ohlc', label: 'OHLC Bar Chart', method: 'ohlcChart', category: '2d',
789
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: false, colorMode: COLOR_MODE.SEMANTIC, camera: CAMERA_MODE.NONE,
790
+ axes: [
791
+ { role: 'category', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_X, required: true },
792
+ { role: 'open', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
793
+ { role: 'high', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
794
+ { role: 'low', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
795
+ { role: 'close', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
796
+ ],
797
+ dataShape: 'Array<{label, open, high, low, close}> — financial price bar chart with high-low spine, left open tick, and right close tick',
798
+ streamShape: 'flat',
799
+ },
800
+ {
801
+ key: 'alluvial', label: 'Alluvial Diagram', method: 'alluvialDiagram', category: '2d',
802
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: false, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.NONE,
803
+ axes: [
804
+ { role: 'dimensions', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_ORDER, required: true },
805
+ { role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.SHAPE_WIDTH, required: true },
806
+ ],
807
+ dataShape: '{dimensions: string[], rows: Array<object>} or Array<{steps: string[], value: number}> — multi-stage categorical flow diagram across 3+ ordered dimensions',
808
+ streamShape: 'none',
809
+ },
810
+ {
811
+ key: 'renko', label: 'Renko / Heikin-Ashi', method: 'renkoChart', category: '2d',
812
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: false, colorMode: COLOR_MODE.SEMANTIC, camera: CAMERA_MODE.NONE,
813
+ axes: [
814
+ { role: 'category', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_X, required: true },
815
+ { role: 'open', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
816
+ { role: 'high', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
817
+ { role: 'low', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
818
+ { role: 'close', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
819
+ ],
820
+ dataShape: 'Array<{label, open, high, low, close}> — price trend brick chart filtering out time noise (renko), or modified smoothed momentum candles (mode: "heikinAshi")',
821
+ streamShape: 'flat',
822
+ },
823
+ {
824
+ key: 'contour', label: 'Contour Plot', method: 'contourChart', category: '2d',
825
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: true, colorMode: COLOR_MODE.SCALE, camera: CAMERA_MODE.NONE,
826
+ axes: [
827
+ { role: 'x', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_X, required: true },
828
+ { role: 'y', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
829
+ { role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.COLOR_SCALE, required: true },
830
+ ],
831
+ dataShape: 'number[][] (2D scalar grid) or Array<{x, y, value}> — marching-squares isolines and filled isoband contours with numeric level labels',
832
+ streamShape: 'none',
833
+ },
834
+ {
835
+ key: 'kagi', label: 'Kagi Chart', method: 'kagiChart', category: '2d',
836
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: false, colorMode: COLOR_MODE.SEMANTIC, camera: CAMERA_MODE.NONE,
837
+ axes: [
838
+ { role: 'category', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_X, required: true },
839
+ { role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
840
+ ],
841
+ dataShape: 'Array<{label, value}> or Array<number> — time-independent price action tracking reversals with Yang and Yin inflection lines',
842
+ streamShape: 'flat',
843
+ },
844
+ {
845
+ key: 'pointAndFigure', label: 'Point and Figure', method: 'pointAndFigure', category: '2d',
846
+ coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: false, colorMode: COLOR_MODE.SEMANTIC, camera: CAMERA_MODE.NONE,
847
+ axes: [
848
+ { role: 'category', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_X, required: true },
849
+ { role: 'high', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
850
+ { role: 'low', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
851
+ ],
852
+ dataShape: 'Array<{label, high, low}> or Array<number> — classical supply/demand chart with columns of rising X and falling O boxes and 45-degree support trendlines',
853
+ streamShape: 'flat',
854
+ },
589
855
  ];
590
856
 
591
857
  import { ChartRegistry } from './charting/ChartRegistry.js';