@peter.prib/renderengine 0.12.0 → 0.14.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 +454 -296
- package/dist/renderengine-workspace.umd.js +4122 -1167
- package/dist/renderengine.umd.js +4103 -1164
- package/documentation/REFERENCE.md +96 -6
- package/package.json +8 -3
- package/src/chartCatalog.js +50 -3
- package/src/charting/Axis.js +47 -5
- package/src/charting/Chart3D.js +4 -2
- package/src/charting/DiagramEditor.js +239 -34
- package/src/charting/Hierarchy.js +28 -0
- package/src/charting/ModelFileFormat.js +47 -0
- package/src/charting/archimate/ArchimateCanvasEditor.js +1108 -1012
- package/src/charting/archimate/deleteFromModel.js +57 -0
- package/src/charting/archimate/elementMenuItems.js +3 -1
- package/src/charting/archimate/heatMaps.js +169 -0
- package/src/charting/archimate/index.js +1 -0
- package/src/charting/ask/AskBar.js +388 -0
- package/src/charting/ask/AskView.js +400 -0
- package/src/charting/charts/AreaChart.js +20 -3
- package/src/charting/charts/BarChart.js +5 -5
- package/src/charting/charts/BarChart3D.js +2 -2
- package/src/charting/charts/BarChart3DOrbit.js +3 -1
- package/src/charting/charts/BarChartBase.js +21 -3
- package/src/charting/charts/BoxPlotChart.js +2 -2
- package/src/charting/charts/BubbleChart.js +4 -1
- package/src/charting/charts/ChordDiagram.js +4 -4
- package/src/charting/charts/DensityChart.js +1 -1
- package/src/charting/charts/FunnelChart.js +1 -1
- package/src/charting/charts/GroupedBarChart.js +7 -9
- package/src/charting/charts/HeatmapChart.js +4 -1
- package/src/charting/charts/HistogramChart.js +2 -2
- package/src/charting/charts/IcicleChart.js +47 -0
- package/src/charting/charts/LollipopChart.js +104 -0
- package/src/charting/charts/MarimekkoChart.js +75 -0
- package/src/charting/charts/OutlineChart.js +10 -8
- package/src/charting/charts/ParallelCoordinatesChart.js +2 -2
- package/src/charting/charts/ParetoChart.js +92 -0
- package/src/charting/charts/PipeChart.js +5 -2
- package/src/charting/charts/PopulationPyramidChart.js +4 -4
- package/src/charting/charts/RadarChart.js +5 -4
- package/src/charting/charts/RadialBarChart.js +2 -2
- package/src/charting/charts/SankeyDiagram.js +4 -4
- package/src/charting/charts/ScatterChart.js +6 -3
- package/src/charting/charts/StackedBarChart.js +82 -11
- package/src/charting/charts/StackedBarChart3DOrbit.js +5 -2
- package/src/charting/charts/StreamGraphChart.js +8 -0
- package/src/charting/charts/SunburstChart.js +2 -2
- package/src/charting/charts/TableChart.js +18 -2
- package/src/charting/charts/TreemapChart.js +4 -4
- package/src/charting/charts/ViolinChart.js +2 -2
- package/src/charting/charts/WaterfallChart.js +2 -2
- package/src/charting/charts/WordCloudChart.js +2 -2
- package/src/charting/diagrams/ArchimateModel.js +33 -0
- package/src/charting/diagrams/DeleteFromModelCommand.js +81 -0
- package/src/charting/diagrams/EdgeGeometry.js +16 -0
- package/src/charting/edgeStyle/EdgeStyleSchema.js +6 -2
- package/src/charting/pivot/PivotChart.js +205 -13
- package/src/charting/pivot/PivotGridChart.js +1 -1
- package/src/charting/pivot/PivotView.js +555 -23
- package/src/charting/table/TableView.js +10 -1
- package/src/charting/table/TableViewChart.js +1 -1
- package/src/charting/templates/Template.js +10 -0
- package/src/exportImage.js +45 -18
- package/src/index.js +115 -6
- package/src/workspace/parts/ArchimateCanvasPart.js +3 -0
- package/src/workspace/parts/DiagramEditorPart.js +2 -0
- package/src/workspace/parts/ModelHosted.js +15 -4
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ChartChooser } from '../ChartChooser.js';
|
|
2
|
+
import { TableChart } from '../charts/TableChart.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* TableView — the state of an interactive table over result rows, kept apart from drawing so all of
|
|
@@ -52,8 +53,10 @@ export class TableView {
|
|
|
52
53
|
* @param {function} [options.copy] writes text to the clipboard (navigator.clipboard by default)
|
|
53
54
|
* @param {ChartChooser} [options.chooser]
|
|
54
55
|
* @param {function} [options.highlight] row → true for a row drawn highlighted (a host's selection)
|
|
56
|
+
* @param {function|Intl.NumberFormat|object} [options.format] how numbers show, for every column or per
|
|
57
|
+
* column (`TableChart.formatFor`). A copy keeps the raw numbers.
|
|
55
58
|
*/
|
|
56
|
-
constructor(rows, { columns = null, complete = true, pageSize = 50, onChange = null, menus = {}, copy = null, chooser = null, highlight = null } = {}) {
|
|
59
|
+
constructor(rows, { columns = null, complete = true, pageSize = 50, onChange = null, menus = {}, copy = null, chooser = null, highlight = null, format = null } = {}) {
|
|
57
60
|
this.rows = rows;
|
|
58
61
|
this.columns = ChartChooser.describe(rows, columns);
|
|
59
62
|
this.complete = complete;
|
|
@@ -63,6 +66,7 @@ export class TableView {
|
|
|
63
66
|
this.copy = copy ?? TableView.clipboard;
|
|
64
67
|
this.chooser = chooser ?? new ChartChooser();
|
|
65
68
|
this.highlight = highlight;
|
|
69
|
+
this.format = format;
|
|
66
70
|
/** The sort keys, most significant first: `{ column, direction: 'asc' | 'desc' }`. */
|
|
67
71
|
this.sorts = [];
|
|
68
72
|
this.filters = []; // { column, op, value }
|
|
@@ -70,6 +74,11 @@ export class TableView {
|
|
|
70
74
|
this.page = 0;
|
|
71
75
|
}
|
|
72
76
|
|
|
77
|
+
/** A column's value as the table shows it, in the column's `format` when there is one. */
|
|
78
|
+
valueText(column, value, nullText = '—') {
|
|
79
|
+
return TableChart.cellText(value, nullText, TableChart.formatFor(this.format, column));
|
|
80
|
+
}
|
|
81
|
+
|
|
73
82
|
static clipboard(text) {
|
|
74
83
|
return globalThis.navigator?.clipboard?.writeText(text);
|
|
75
84
|
}
|
|
@@ -82,7 +82,7 @@ export class TableViewChart extends TableChart {
|
|
|
82
82
|
columns.forEach((c, i) => {
|
|
83
83
|
const left = x0 + i * colW;
|
|
84
84
|
const hit = this.renderer.rect(left, top, colW, ROW_H, 'transparent', null);
|
|
85
|
-
const text = this._cell(
|
|
85
|
+
const text = this._cell(view.valueText(c.key, row[c.key], nullText), c, left, top, colW, { fill: row[c.key] == null ? '#999' : '#222' });
|
|
86
86
|
this._on(interactive, [hit, text], (e) => this.showMenu(e.clientX, e.clientY, view.cellMenu(row, c.key)), null);
|
|
87
87
|
});
|
|
88
88
|
top += ROW_H;
|
|
@@ -102,6 +102,16 @@ export class Template {
|
|
|
102
102
|
return { x: this.x + ux * t, y: this.y + uy * t };
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Where a line from (px, py), a point inside the shape, heading in
|
|
107
|
+
* direction (dx, dy) leaves its outline (the shape unrotated).
|
|
108
|
+
* @returns {{x: number, y: number}}
|
|
109
|
+
*/
|
|
110
|
+
exitPoint(px, py, dx, dy) {
|
|
111
|
+
const t = rayExitDistance(this.outline(), px, py, dx, dy);
|
|
112
|
+
return { x: px + dx * t, y: py + dy * t };
|
|
113
|
+
}
|
|
114
|
+
|
|
105
115
|
/** Centre of the name text; the z-index line sits just below it. */
|
|
106
116
|
get labelAnchor() { return { x: this.x, y: this.y - 8 }; }
|
|
107
117
|
|
package/src/exportImage.js
CHANGED
|
@@ -31,10 +31,10 @@ export const MAX_IMAGE_PIXELS = 32_000_000;
|
|
|
31
31
|
/**
|
|
32
32
|
* Work out the bitmap size to rasterize at.
|
|
33
33
|
*
|
|
34
|
-
* `scale` is what the caller asked for (2 = retina-sharp
|
|
35
|
-
* the diagram's own dimensions, when the result
|
|
36
|
-
* scale of a huge diagram still shows all of
|
|
37
|
-
* the diagram loses content or legibility.
|
|
34
|
+
* `scale` is what the caller asked for (1 = 100%, 2 = retina-sharp, 0.5 = half
|
|
35
|
+
* size). It is reduced, never the diagram's own dimensions, when the result
|
|
36
|
+
* would be too large: halving the scale of a huge diagram still shows all of
|
|
37
|
+
* it, whereas cropping or shrinking the diagram loses content or legibility.
|
|
38
38
|
*
|
|
39
39
|
* @returns {{width:number, height:number, scale:number, clamped:boolean}}
|
|
40
40
|
*/
|
|
@@ -49,10 +49,6 @@ export function imageSize({ width, height, scale = 2, maxPixels = MAX_IMAGE_PIXE
|
|
|
49
49
|
s = Math.sqrt(maxPixels / (w * h));
|
|
50
50
|
clamped = true;
|
|
51
51
|
}
|
|
52
|
-
// A scale below 1:1 would be a downscale the caller never asked for, but a
|
|
53
|
-
// diagram that is itself over the budget leaves no choice.
|
|
54
|
-
if (s < 1 && w * h <= maxPixels) s = 1;
|
|
55
|
-
|
|
56
52
|
return { width: Math.max(1, Math.round(w * s)), height: Math.max(1, Math.round(h * s)), scale: s, clamped };
|
|
57
53
|
}
|
|
58
54
|
|
|
@@ -67,8 +63,10 @@ export function imageSize({ width, height, scale = 2, maxPixels = MAX_IMAGE_PIXE
|
|
|
67
63
|
* @param {number} [options.width] pixels; omitted leaves what is there
|
|
68
64
|
* @param {number} [options.height]
|
|
69
65
|
* @param {string|null} [options.background='#ffffff'] null for transparent
|
|
66
|
+
* @param {string} [options.viewBox] the drawing's own units, when it has none
|
|
67
|
+
* (so a picture sized other than as drawn scales rather than crops)
|
|
70
68
|
*/
|
|
71
|
-
export function prepareSvgMarkup(markup, { width, height, background = '#ffffff' } = {}) {
|
|
69
|
+
export function prepareSvgMarkup(markup, { width, height, background = '#ffffff', viewBox } = {}) {
|
|
72
70
|
let out = String(markup ?? '').trim();
|
|
73
71
|
const open = out.match(/^<svg\b[^>]*>/i);
|
|
74
72
|
if (!open) return out; // not an <svg> — hand it back untouched
|
|
@@ -87,13 +85,23 @@ export function prepareSvgMarkup(markup, { width, height, background = '#ffffff'
|
|
|
87
85
|
};
|
|
88
86
|
if (Number.isFinite(width) && width > 0) setAttr('width', Math.round(width));
|
|
89
87
|
if (Number.isFinite(height) && height > 0) setAttr('height', Math.round(height));
|
|
88
|
+
if (viewBox && !/\sviewBox\s*=/i.test(tag)) setAttr('viewBox', viewBox);
|
|
90
89
|
|
|
91
90
|
out = tag + out.slice(open[0].length);
|
|
92
91
|
|
|
93
92
|
if (background) {
|
|
94
|
-
// First child, so it sits behind everything.
|
|
95
|
-
//
|
|
96
|
-
|
|
93
|
+
// First child, so it sits behind everything. With a viewBox it reaches
|
|
94
|
+
// a whole viewBox past each side (the picture clips it): a percentage
|
|
95
|
+
// counts from (0,0), so a viewBox starting elsewhere (a cropped
|
|
96
|
+
// drawing) would leave a strip bare, and the viewBox alone would leave
|
|
97
|
+
// the bands a picture of another shape letterboxes it with.
|
|
98
|
+
// Without one, percentages, so it covers whatever size it is shown at.
|
|
99
|
+
const box = tag.match(/\sviewBox\s*=\s*["']\s*([-\d.e+]+)[\s,]+([-\d.e+]+)[\s,]+([-\d.e+]+)[\s,]+([-\d.e+]+)\s*["']/i);
|
|
100
|
+
const [x, y, w, h] = box ? box.slice(1).map(Number) : [];
|
|
101
|
+
const rect = box
|
|
102
|
+
? `<rect x="${x - w}" y="${y - h}" width="${3 * w}" height="${3 * h}" fill="${background}"/>`
|
|
103
|
+
: `<rect x="0" y="0" width="100%" height="100%" fill="${background}"/>`;
|
|
104
|
+
out = out.replace(tag, `${tag}${rect}`);
|
|
97
105
|
}
|
|
98
106
|
return out;
|
|
99
107
|
}
|
|
@@ -126,12 +134,12 @@ export function findDrawable(host) {
|
|
|
126
134
|
* Size is taken from the element's laid-out box rather than its attributes,
|
|
127
135
|
* because that is what the user is actually looking at.
|
|
128
136
|
*/
|
|
129
|
-
export function serializeSvg(svgEl, { background = '#ffffff', width, height } = {}) {
|
|
137
|
+
export function serializeSvg(svgEl, { background = '#ffffff', width, height, scale = 1 } = {}) {
|
|
130
138
|
const box = svgEl.getBoundingClientRect?.() ?? {};
|
|
131
139
|
const w = width ?? svgEl.width?.baseVal?.value ?? box.width;
|
|
132
140
|
const h = height ?? svgEl.height?.baseVal?.value ?? box.height;
|
|
133
141
|
const markup = new XMLSerializer().serializeToString(svgEl);
|
|
134
|
-
return prepareSvgMarkup(markup, { width: w, height: h, background });
|
|
142
|
+
return prepareSvgMarkup(markup, { width: w * scale, height: h * scale, background, viewBox: `0 0 ${w} ${h}` });
|
|
135
143
|
}
|
|
136
144
|
|
|
137
145
|
/**
|
|
@@ -150,9 +158,16 @@ export async function toPngBlob(host, { scale = 2, background = '#ffffff', maxPi
|
|
|
150
158
|
}
|
|
151
159
|
|
|
152
160
|
const box = drawable.getBoundingClientRect();
|
|
153
|
-
const size = imageSize({ width: box.width, height: box.height, scale, maxPixels });
|
|
154
161
|
const markup = serializeSvg(drawable, { background, width: box.width, height: box.height });
|
|
162
|
+
return svgMarkupToPngBlob(markup, { width: box.width, height: box.height, scale, background, maxPixels });
|
|
163
|
+
}
|
|
155
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Rasterize standalone SVG markup (prepareSvgMarkup's) drawn `width` × `height`
|
|
167
|
+
* to a PNG Blob at `scale`. DOM only.
|
|
168
|
+
*/
|
|
169
|
+
export async function svgMarkupToPngBlob(markup, { width, height, scale = 1, background = '#ffffff', maxPixels = MAX_IMAGE_PIXELS } = {}) {
|
|
170
|
+
const size = imageSize({ width, height, scale, maxPixels });
|
|
156
171
|
// A data: URL rather than a blob: URL — a blob: URL taints nothing either,
|
|
157
172
|
// but Safari has historically refused to draw SVG from one.
|
|
158
173
|
const url = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(markup)}`;
|
|
@@ -200,7 +215,15 @@ export function downloadBlob(blob, filename) {
|
|
|
200
215
|
* @returns {Promise<{via:'clipboard'|'download', filename?:string}>}
|
|
201
216
|
*/
|
|
202
217
|
export async function copyImage(host, { scale = 2, background = '#ffffff', name = 'diagram' } = {}) {
|
|
203
|
-
|
|
218
|
+
return copyPngBlob(await toPngBlob(host, { scale, background }), { name });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Put a PNG Blob on the clipboard, or download it where the browser cannot
|
|
223
|
+
* (as copyImage). DOM only.
|
|
224
|
+
* @returns {Promise<{via:'clipboard'|'download', filename?:string}>}
|
|
225
|
+
*/
|
|
226
|
+
export async function copyPngBlob(blob, { name = 'diagram' } = {}) {
|
|
204
227
|
const canWrite = typeof ClipboardItem !== 'undefined'
|
|
205
228
|
&& typeof navigator !== 'undefined'
|
|
206
229
|
&& navigator.clipboard?.write;
|
|
@@ -220,10 +243,14 @@ export async function copyImage(host, { scale = 2, background = '#ffffff', name
|
|
|
220
243
|
}
|
|
221
244
|
|
|
222
245
|
/** Save a diagram as an SVG file. DOM only. */
|
|
223
|
-
export function downloadSvg(host, { background = '#ffffff', name = 'diagram' } = {}) {
|
|
246
|
+
export function downloadSvg(host, { background = '#ffffff', name = 'diagram', scale = 1 } = {}) {
|
|
224
247
|
const drawable = findDrawable(host);
|
|
225
248
|
if (!drawable || drawable.tagName.toLowerCase() !== 'svg') throw new Error('No <svg> to export');
|
|
226
|
-
|
|
249
|
+
downloadSvgMarkup(serializeSvg(drawable, { background, scale }), { name });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Save standalone SVG markup as a file. DOM only. */
|
|
253
|
+
export function downloadSvgMarkup(markup, { name = 'diagram' } = {}) {
|
|
227
254
|
downloadBlob(new Blob([markup], { type: 'image/svg+xml;charset=utf-8' }), imageFileName(name, 'svg'));
|
|
228
255
|
}
|
|
229
256
|
|
package/src/index.js
CHANGED
|
@@ -33,6 +33,10 @@ import { LagPlotChart } from './charting/charts/LagPlotChart.js';
|
|
|
33
33
|
import { TreemapChart } from './charting/charts/TreemapChart.js';
|
|
34
34
|
import { SunburstChart } from './charting/charts/SunburstChart.js';
|
|
35
35
|
import { OutlineChart } from './charting/charts/OutlineChart.js';
|
|
36
|
+
import { IcicleChart } from './charting/charts/IcicleChart.js';
|
|
37
|
+
import { ParetoChart } from './charting/charts/ParetoChart.js';
|
|
38
|
+
import { LollipopChart } from './charting/charts/LollipopChart.js';
|
|
39
|
+
import { MarimekkoChart } from './charting/charts/MarimekkoChart.js';
|
|
36
40
|
import { TableChart } from './charting/charts/TableChart.js';
|
|
37
41
|
import { ChartChooser } from './charting/ChartChooser.js';
|
|
38
42
|
import { TableView } from './charting/table/TableView.js';
|
|
@@ -40,6 +44,8 @@ import { TableViewChart } from './charting/table/TableViewChart.js';
|
|
|
40
44
|
import { PivotView } from './charting/pivot/PivotView.js';
|
|
41
45
|
import { PivotGridChart } from './charting/pivot/PivotGridChart.js';
|
|
42
46
|
import { PivotChart } from './charting/pivot/PivotChart.js';
|
|
47
|
+
import { AskView } from './charting/ask/AskView.js';
|
|
48
|
+
import { AskBar } from './charting/ask/AskBar.js';
|
|
43
49
|
import { RadarChart } from './charting/charts/RadarChart.js';
|
|
44
50
|
import { FunnelChart } from './charting/charts/FunnelChart.js';
|
|
45
51
|
import { WaterfallChart } from './charting/charts/WaterfallChart.js';
|
|
@@ -188,13 +194,18 @@ import {
|
|
|
188
194
|
parseXML, parseArchiModel, toRenderEngineModel, toArchimateModel, toArchiXML, fromArchimateView, toArchiExport,
|
|
189
195
|
} from './archimateArchiFormat.js';
|
|
190
196
|
import { parseExchangeModel, toExchangeXML, EXCHANGE_NAMESPACE, EXCHANGE_TYPE_PROPERTY, EXCHANGE_VIEWPOINTS } from './archimateExchangeFormat.js';
|
|
191
|
-
import { ModelFileFormat, modelFileMenuItems, importFile } from './charting/ModelFileFormat.js';
|
|
197
|
+
import { ModelFileFormat, modelFileMenuItems, importFile, findPayload, askPasteImport } from './charting/ModelFileFormat.js';
|
|
192
198
|
import { ArchiFileFormat, ExchangeFileFormat, ArchimateCsvFileFormat, ARCHIMATE_FILE_FORMATS } from './charting/archimate/index.js';
|
|
193
199
|
import { ShowElementCommand } from './charting/diagrams/ShowElementCommand.js';
|
|
194
200
|
import { EmphasisRule, EMPHASIS_OPACITY, EMPHASIS_LEVELS, strongerEmphasis } from './charting/emphasis/EmphasisRule.js';
|
|
195
201
|
import { HighlightRule } from './charting/emphasis/HighlightRule.js';
|
|
196
202
|
import { CatalogDialog } from './charting/CatalogDialog.js';
|
|
197
|
-
import { CustomViewpoints, CUSTOM_VIEWPOINTS_STORE, specializationCatalogDialog, elementTypeChoices } from './charting/archimate/index.js';
|
|
203
|
+
import { CustomViewpoints, CUSTOM_VIEWPOINTS_STORE, specializationCatalogDialog, elementTypeChoices, askDeleteFromModel, deleteFromModelMessage } from './charting/archimate/index.js';
|
|
204
|
+
import { DeleteFromModelCommand } from './charting/diagrams/DeleteFromModelCommand.js';
|
|
205
|
+
import {
|
|
206
|
+
CHANGE_COLOURS, CHANGE_MAKERS, CHANGE_WORDS, changeAction, relationshipChange, elementChange,
|
|
207
|
+
propertyParams, expressionValues, resultBands, sequentialColours,
|
|
208
|
+
} from './charting/archimate/heatMaps.js';
|
|
198
209
|
import { ViewpointRule, ElementFilterRule } from './charting/archimate/index.js';
|
|
199
210
|
import { MODEL_ELEMENT_MIME, TREE_LAYERS, treeLayerOf } from './charting/modelTree/ModelTreePanel.js';
|
|
200
211
|
import { parseArchiFile, toArchiFile, ARCHI_MODEL_ENTRY } from './archimateArchive.js';
|
|
@@ -207,6 +218,7 @@ import {
|
|
|
207
218
|
// reference sits inside a function body, so loading it under node does nothing.
|
|
208
219
|
import {
|
|
209
220
|
copyImage, toPngBlob, downloadPng, downloadSvg, downloadBlob,
|
|
221
|
+
svgMarkupToPngBlob, copyPngBlob, downloadSvgMarkup,
|
|
210
222
|
serializeSvg, prepareSvgMarkup, imageSize, imageFileName, findDrawable, MAX_IMAGE_PIXELS,
|
|
211
223
|
} from './exportImage.js';
|
|
212
224
|
// Same reasoning as exportImage: every DOM reference sits inside a function
|
|
@@ -222,7 +234,8 @@ import {
|
|
|
222
234
|
// separate browser-only entry: import 'renderengine/src/workspace.js' directly.
|
|
223
235
|
|
|
224
236
|
export {
|
|
225
|
-
Mat4, OrbitCamera, CHART_CATALOG, getChartCatalogEntry, chartRegistry, ChartChooser, TableChart, TableView, TableViewChart, PivotView, PivotGridChart, PivotChart,
|
|
237
|
+
Mat4, OrbitCamera, CHART_CATALOG, getChartCatalogEntry, chartRegistry, ChartChooser, TableChart, TableView, TableViewChart, PivotView, PivotGridChart, PivotChart, AskView, AskBar,
|
|
238
|
+
ParetoChart, LollipopChart, MarimekkoChart, IcicleChart,
|
|
226
239
|
DIAGRAM_CATALOG, getDiagramCatalogEntry, diagramRegistry,
|
|
227
240
|
TREE_MODE_CATALOG, treeModeRegistry, getTreeModeCatalogEntry,
|
|
228
241
|
AXIS_KIND, ENCODING_CHANNEL, COORD_SYSTEM, COLOR_MODE, CAMERA_MODE,
|
|
@@ -249,12 +262,16 @@ export {
|
|
|
249
262
|
importElementsCSV, importRelationshipsCSV, importModelCSV,
|
|
250
263
|
parseArchiModel, toArchimateModel, fromArchimateView, toArchiExport,
|
|
251
264
|
parseExchangeModel, toExchangeXML, EXCHANGE_NAMESPACE, EXCHANGE_TYPE_PROPERTY, EXCHANGE_VIEWPOINTS,
|
|
252
|
-
ModelFileFormat, modelFileMenuItems, importFile, ArchiFileFormat, ExchangeFileFormat, ArchimateCsvFileFormat, ARCHIMATE_FILE_FORMATS,
|
|
265
|
+
ModelFileFormat, modelFileMenuItems, importFile, findPayload, askPasteImport, ArchiFileFormat, ExchangeFileFormat, ArchimateCsvFileFormat, ARCHIMATE_FILE_FORMATS,
|
|
253
266
|
ShowElementCommand, MODEL_ELEMENT_MIME, TREE_LAYERS, treeLayerOf,
|
|
254
267
|
EmphasisRule, EMPHASIS_OPACITY, EMPHASIS_LEVELS, strongerEmphasis, HighlightRule, ViewpointRule, ElementFilterRule,
|
|
255
268
|
CatalogDialog, CustomViewpoints, CUSTOM_VIEWPOINTS_STORE, specializationCatalogDialog, elementTypeChoices,
|
|
269
|
+
askDeleteFromModel, deleteFromModelMessage, DeleteFromModelCommand,
|
|
270
|
+
CHANGE_COLOURS, CHANGE_MAKERS, CHANGE_WORDS, changeAction, relationshipChange, elementChange,
|
|
271
|
+
propertyParams, expressionValues, resultBands, sequentialColours,
|
|
256
272
|
parseArchiFile, toArchiFile, ARCHI_MODEL_ENTRY, ZipArchive,
|
|
257
273
|
copyImage, toPngBlob, downloadPng, downloadSvg, downloadBlob,
|
|
274
|
+
svgMarkupToPngBlob, copyPngBlob, downloadSvgMarkup,
|
|
258
275
|
serializeSvg, prepareSvgMarkup, imageSize, imageFileName, findDrawable, MAX_IMAGE_PIXELS,
|
|
259
276
|
showPropertiesDialog, showDiagramPropertiesDialog, showGlobalSettingsDialog,
|
|
260
277
|
propertyRowsFor, coerceFieldValue, derivedRefreshPlan, LAYER_CHOICES, NODE_SECTIONS,
|
|
@@ -1460,6 +1477,8 @@ export class RenderEngine {
|
|
|
1460
1477
|
* @param {string} [options.title]
|
|
1461
1478
|
* @param {number} [options.chartHeight] a chart's height (360)
|
|
1462
1479
|
* @param {boolean} [options.fitHeight] size the container to what is drawn (true)
|
|
1480
|
+
* @param {function|Intl.NumberFormat|object} [options.format] how numbers show: for every column, or
|
|
1481
|
+
* per column ({ Amount: Intl.NumberFormat }); copies keep the raw numbers
|
|
1463
1482
|
* @returns {TableView|null} the table's state, for the host to read or change
|
|
1464
1483
|
*/
|
|
1465
1484
|
resultView(rows, options = {}) {
|
|
@@ -1488,6 +1507,39 @@ export class RenderEngine {
|
|
|
1488
1507
|
const back = this.renderer.text('◂ Table', 8, 14, { fill: '#2b5fd9', fontSize: '12px', verticalAlign: 'middle' });
|
|
1489
1508
|
back?.addEventListener?.('click', () => this._drawResultTable(view, options));
|
|
1490
1509
|
if (back?.style) back.style.cursor = 'pointer';
|
|
1510
|
+
const choices = view.chartChoices();
|
|
1511
|
+
if (choices.length > 1) {
|
|
1512
|
+
const pick = this.renderer.text(`${choice.label} ▾`, 68, 14, { fill: '#2b5fd9', fontSize: '12px', verticalAlign: 'middle', tooltip: choice.reason });
|
|
1513
|
+
const show = options.showMenu ?? ((x, y, items) => PopupMenu.show(x, y, items));
|
|
1514
|
+
pick?.addEventListener?.('click', (e) => show(e.clientX, e.clientY, choices.map(c => ({
|
|
1515
|
+
label: `${c.label}${c.key === choice.key ? ' ✓' : ''}`,
|
|
1516
|
+
action: () => this._drawResultChart(view, c, options),
|
|
1517
|
+
}))));
|
|
1518
|
+
if (pick?.style) pick.style.cursor = 'pointer';
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
/**
|
|
1523
|
+
* Mounts an interactive `AskBar` (question input, configurable next-word prediction in a
|
|
1524
|
+
* scrollable list, semantic sense/reference/relation badges, and sample query pills) into
|
|
1525
|
+
* `container` (or links `resultEngine: this` when called as `engine.askBar(container, options)`).
|
|
1526
|
+
* @param {HTMLElement|string|object} containerOrOptions host container element/id, or options when mounting into `this.container`
|
|
1527
|
+
* @param {object} [options] `AskBar` / `AskView` options (`suggest`, `measures`, `dimensions`, `members`, `sampleQuestions`, `visibleCount`, `predictChoices`, `onAsk`, etc.)
|
|
1528
|
+
* @returns {AskBar}
|
|
1529
|
+
*/
|
|
1530
|
+
askBar(containerOrOptions, options = {}) {
|
|
1531
|
+
const isPlainOptions = containerOrOptions
|
|
1532
|
+
&& typeof containerOrOptions === 'object'
|
|
1533
|
+
&& !containerOrOptions.nodeType
|
|
1534
|
+
&& typeof containerOrOptions.appendChild !== 'function';
|
|
1535
|
+
const container = isPlainOptions ? this.container : containerOrOptions;
|
|
1536
|
+
const opts = isPlainOptions ? containerOrOptions : options;
|
|
1537
|
+
return new AskBar(container, { resultEngine: this, ...opts });
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
/** Static helper: `RenderEngine.askBar(container, options)` constructs and mounts an `AskBar`. */
|
|
1541
|
+
static askBar(container, options = {}) {
|
|
1542
|
+
return new AskBar(container, options);
|
|
1491
1543
|
}
|
|
1492
1544
|
|
|
1493
1545
|
/**
|
|
@@ -1499,7 +1551,8 @@ export class RenderEngine {
|
|
|
1499
1551
|
* the axes. The container's height follows the grid, as resultView's does.
|
|
1500
1552
|
* @param {Array<object>} rows
|
|
1501
1553
|
* @param {object} [options] rowAxis, columnAxis, measures, columns (hints, with the rollup column's),
|
|
1502
|
-
* dimensions, onDrill, menus, title, nullText, fitHeight
|
|
1554
|
+
* dimensions, onDrill, menus, title, nullText, fitHeight, format (how numbers show, per measure or for
|
|
1555
|
+
* all: cells, totals and the traffic lights' cut-offs; the copy keeps the raw numbers)
|
|
1503
1556
|
* @returns {PivotView|null} the grid's state, for the host to read or change
|
|
1504
1557
|
*/
|
|
1505
1558
|
pivotView(rows, options = {}) {
|
|
@@ -1537,7 +1590,8 @@ export class RenderEngine {
|
|
|
1537
1590
|
pivotChart(view, options = {}) {
|
|
1538
1591
|
if (!this.renderer) return null;
|
|
1539
1592
|
const chart = new PivotChart(view, options);
|
|
1540
|
-
chart.
|
|
1593
|
+
chart.redraw = () => this._drawPivotChart(chart, options);
|
|
1594
|
+
chart.detach = view.listen(chart.redraw);
|
|
1541
1595
|
this._drawPivotChart(chart, options);
|
|
1542
1596
|
return chart;
|
|
1543
1597
|
}
|
|
@@ -1548,15 +1602,19 @@ export class RenderEngine {
|
|
|
1548
1602
|
if (choice === null) {
|
|
1549
1603
|
this.renderer.clear();
|
|
1550
1604
|
this.renderer.text('No chart fits what the grid shows', 8, 14, { fill: '#666', fontSize: '12px', verticalAlign: 'middle' });
|
|
1605
|
+
options.onDraw?.(null, chart);
|
|
1551
1606
|
return;
|
|
1552
1607
|
}
|
|
1553
1608
|
// No title unless one is given: the grid beside it says what is charted, and the link below
|
|
1554
1609
|
// names the chart (with the chooser's reason as its tooltip).
|
|
1555
1610
|
this.renderChartType(choice.key, choice.data, {
|
|
1611
|
+
...choice.options,
|
|
1556
1612
|
title: options.title,
|
|
1557
1613
|
legend: true, // a series per column member: which colour is which must be said
|
|
1558
1614
|
onDatumClick: (datum) => chart.click(datum),
|
|
1615
|
+
onNodeClick: (node) => chart.click({ label: node.label }),
|
|
1559
1616
|
});
|
|
1617
|
+
options.onDraw?.(choice, chart);
|
|
1560
1618
|
// Which chart, and the menu to change it, drawn over the chart as resultView's "◂ Table" is.
|
|
1561
1619
|
if (!this.renderer.supportsSceneGraph) return;
|
|
1562
1620
|
const pick = this.renderer.text(`${choice.label} ▾`, 8, 14, { fill: '#2b5fd9', fontSize: '12px', verticalAlign: 'middle', tooltip: choice.reason });
|
|
@@ -1574,6 +1632,57 @@ export class RenderEngine {
|
|
|
1574
1632
|
this.fitToContainer();
|
|
1575
1633
|
}
|
|
1576
1634
|
|
|
1635
|
+
/**
|
|
1636
|
+
* Renders an icicle chart — rectangular hierarchical partition chart (top-down bands by depth,
|
|
1637
|
+
* width proportional to value within each parent's span).
|
|
1638
|
+
* @param {Array<{label: string, value?: number, children?: Array}>} data
|
|
1639
|
+
* @param {object} [options]
|
|
1640
|
+
*/
|
|
1641
|
+
icicleChart(data, options = {}) {
|
|
1642
|
+
if (!this.renderer) return;
|
|
1643
|
+
this._chart3D = false;
|
|
1644
|
+
this._chartState = { type: 'icicle', data, options };
|
|
1645
|
+
this._renderChart(new IcicleChart(this.renderer, data, options));
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
/**
|
|
1649
|
+
* Renders a Pareto chart — categories sorted descending with a cumulative percentage line (0–100%)
|
|
1650
|
+
* on a right-hand axis and an 80% cutoff reference line.
|
|
1651
|
+
* @param {Array<{label: string, value: number}>} data
|
|
1652
|
+
* @param {object} [options]
|
|
1653
|
+
*/
|
|
1654
|
+
paretoChart(data, options = {}) {
|
|
1655
|
+
if (!this.renderer) return;
|
|
1656
|
+
this._chart3D = false;
|
|
1657
|
+
this._chartState = { type: 'pareto', data, options };
|
|
1658
|
+
this._renderChart(new ParetoChart(this.renderer, data, options));
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
/**
|
|
1662
|
+
* Renders a lollipop chart — stem-and-dot alternative to bars for 1D or multi-series comparisons.
|
|
1663
|
+
* @param {Array<{label: string, value: number}>|Array<{name: string, values: Array<{label: string, value: number}>}>} data
|
|
1664
|
+
* @param {object} [options]
|
|
1665
|
+
*/
|
|
1666
|
+
lollipopChart(data, options = {}) {
|
|
1667
|
+
if (!this.renderer) return;
|
|
1668
|
+
this._chart3D = false;
|
|
1669
|
+
this._chartState = { type: 'lollipop', data, options };
|
|
1670
|
+
this._renderChart(new LollipopChart(this.renderer, data, options));
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
/**
|
|
1674
|
+
* Renders a Marimekko (mosaic) chart — variable-width 100%-stacked columns where column width is
|
|
1675
|
+
* proportional to each category's total and segment height is each series' share within that category.
|
|
1676
|
+
* @param {Array<{name: string, values: Array<{label: string, value: number}>}>|Array<{label: string, values: number[]}>} data
|
|
1677
|
+
* @param {object} [options]
|
|
1678
|
+
*/
|
|
1679
|
+
marimekkoChart(data, options = {}) {
|
|
1680
|
+
if (!this.renderer) return;
|
|
1681
|
+
this._chart3D = false;
|
|
1682
|
+
this._chartState = { type: 'marimekko', data, options };
|
|
1683
|
+
this._renderChart(new MarimekkoChart(this.renderer, data, options));
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1577
1686
|
/**
|
|
1578
1687
|
* Renders a waterfall chart — running-total bars, each floating from the
|
|
1579
1688
|
* previous cumulative value to the new one (a "delta" bar), except
|
|
@@ -253,6 +253,8 @@ export class ArchimateCanvasPart extends EditorPart {
|
|
|
253
253
|
if (previous) {
|
|
254
254
|
this.editor.showGrid = previous.showGrid;
|
|
255
255
|
this.editor.gridSize = previous.gridSize;
|
|
256
|
+
this.editor.showExportArea = previous.showExportArea;
|
|
257
|
+
this.editor.exportOptions = previous.exportOptions;
|
|
256
258
|
}
|
|
257
259
|
|
|
258
260
|
// A canvas's palette is Archi's canvas palette; another view's, ArchiMate's element types.
|
|
@@ -351,6 +353,7 @@ export class ArchimateCanvasPart extends EditorPart {
|
|
|
351
353
|
this.editor?.selectTemplate(instanceId);
|
|
352
354
|
},
|
|
353
355
|
addToView: (el) => this.editor?.showElement(el.id, this.editor.visibleCentre()),
|
|
356
|
+
deleteFromModel: (el) => this.editor?.askDeleteFromModel([el.id]),
|
|
354
357
|
});
|
|
355
358
|
}
|
|
356
359
|
|
|
@@ -89,6 +89,8 @@ export class DiagramEditorPart extends EditorPart {
|
|
|
89
89
|
if (previous) {
|
|
90
90
|
this.editor.showGrid = previous.showGrid;
|
|
91
91
|
this.editor.gridSize = previous.gridSize;
|
|
92
|
+
this.editor.showExportArea = previous.showExportArea;
|
|
93
|
+
this.editor.exportOptions = previous.exportOptions;
|
|
92
94
|
}
|
|
93
95
|
this.editor.initPaletteForDiagramType(palette, model.templateType);
|
|
94
96
|
this.menuBar?.dispose();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ModelFile } from './ModelFile.js';
|
|
2
2
|
import { EditorPart } from './EditorPart.js';
|
|
3
|
-
import { modelFileMenuItems } from '../../charting/ModelFileFormat.js';
|
|
3
|
+
import { modelFileMenuItems, askPasteImport } from '../../charting/ModelFileFormat.js';
|
|
4
4
|
import { pickFile } from '../../charting/toolbar/pickFile.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -121,9 +121,20 @@ export const ModelHosted = (Base) => class extends Base {
|
|
|
121
121
|
|
|
122
122
|
/** Import ▸: each format that only reads (another tool's export), opened as a new model. */
|
|
123
123
|
importItems() {
|
|
124
|
-
return
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
return [
|
|
125
|
+
...modelFileMenuItems(this.fileFormats().filter(f => f.exports === false), {
|
|
126
|
+
model: () => this.model, only: 'import', onImport: (model) => this.onImported(model), document: () => this.document,
|
|
127
|
+
}),
|
|
128
|
+
...this.pasteItems(),
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Paste …: a model in each of its text formats from pasted text (an assistant's answer, say), as Open… would bring it. */
|
|
133
|
+
pasteItems() {
|
|
134
|
+
return this.fileFormats().filter(f => f.imports && !f.readsBytes).map(format => ({
|
|
135
|
+
label: `From pasted text (${format.label})…`,
|
|
136
|
+
action: () => askPasteImport(format, { onImport: (model) => this.onImported(model), document: this.document ?? globalThis.document }),
|
|
137
|
+
}));
|
|
127
138
|
}
|
|
128
139
|
|
|
129
140
|
/**
|