@peter.prib/renderengine 0.15.2 → 0.15.3
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/dist/renderengine-workspace.umd.js +5963 -3766
- package/dist/renderengine.umd.js +5863 -3772
- package/documentation/REFERENCE.md +37 -3
- package/package.json +1 -1
- package/src/archimateArchiFormat.js +114 -10
- package/src/archimateArchive.js +27 -4
- package/src/archimateExchangeFormat.js +12 -4
- package/src/archimateSpecializations.js +11 -1
- package/src/chartCatalog.js +6 -4
- package/src/charting/ArchimateDiagramEditor.js +5 -108
- package/src/charting/DiagramTypeRegistry.js +20 -1
- package/src/charting/DiagramViewPropertiesDialog.js +54 -10
- package/src/charting/Legend.js +1 -2
- package/src/charting/Statistics.js +14 -0
- package/src/charting/charts/BarChart.js +59 -8
- package/src/charting/charts/HexbinChart.js +15 -0
- package/src/charting/charts/LollipopChart.js +42 -7
- package/src/charting/decision/DecisionWorkbench.js +71 -4
- package/src/charting/diagramModel/DiagramView.js +7 -2
- package/src/charting/diagrams/GraphDiagram3D.js +15 -0
- package/src/charting/graph/GraphSource.js +72 -0
- package/src/charting/modelTree/NavigatorModel.js +22 -8
- package/src/charting/report/TemplatedReport.js +27 -377
- package/src/charting/report/ViewsReport.js +118 -0
- package/src/charting/report/reportWriters.js +496 -0
- package/src/charting/templates/C4BoundaryTemplate.js +34 -0
- package/src/charting/templates/C4ComponentTemplate.js +30 -0
- package/src/charting/templates/C4ContainerTemplate.js +49 -0
- package/src/charting/templates/C4PersonTemplate.js +35 -0
- package/src/charting/templates/C4SystemTemplate.js +32 -0
- package/src/charting/templates/C4Template.js +95 -0
- package/src/charting/templates/index.js +6 -0
- package/src/charting/templates/templateRegistry.js +7 -0
- package/src/compute/ComputeBackend.js +132 -0
- package/src/compute/CpuBackend.js +209 -0
- package/src/compute/WebGpuBackend.js +630 -0
- package/src/compute/index.js +5 -0
- package/src/compute/selectBackend.js +85 -0
- package/src/index.js +3403 -3371
- package/src/kernelExpr.js +80 -5
- package/src/workspace/parts/DiagramEditorPart.js +3 -1
- package/src/workspace/parts/ViewsPart.js +106 -0
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { treeLayerOf } from './ModelTreePanel.js';
|
|
2
|
+
import { DocumentGraph } from '../graph/GraphSource.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
* NavigatorModel — pure, DOM-free graph traversal and navigation engine for an ArchiMate
|
|
5
|
+
* NavigatorModel — pure, DOM-free graph traversal and navigation engine for any graph: an ArchiMate
|
|
6
|
+
* model, or any GraphSource (`DocumentGraph` for a `{ nodes, edges }` graph document — see
|
|
7
|
+
* `NavigatorModel.forGraph`). It reads the graph only through `getElement`, `relationshipsFor`,
|
|
8
|
+
* `viewsContaining` and `layerOf` (see `GraphSource` for why).
|
|
5
9
|
*
|
|
6
10
|
* Allows exploring relationships step-by-step from any focused root element to arbitrary depth,
|
|
7
11
|
* with cycle detection, navigation history (back/forward), direction filters, path-finding
|
|
@@ -9,12 +13,17 @@ import { treeLayerOf } from './ModelTreePanel.js';
|
|
|
9
13
|
*/
|
|
10
14
|
export class NavigatorModel {
|
|
11
15
|
/**
|
|
12
|
-
* @param {ArchimateModel} model
|
|
16
|
+
* @param {ArchimateModel|import('../graph/GraphSource.js').GraphSource} model
|
|
13
17
|
* @param {Object} [options]
|
|
14
18
|
* @param {string} [options.rootId] - Initial focused element id
|
|
15
19
|
* @param {'both'|'outgoing'|'incoming'} [options.direction='both']
|
|
16
20
|
* @param {Iterable<string>} [options.allowedKinds] - Optional relationship kinds to include
|
|
17
21
|
*/
|
|
22
|
+
/** A navigator over a graph document (`{ nodes, edges }`): the same walker, through a DocumentGraph. */
|
|
23
|
+
static forGraph(doc, options) {
|
|
24
|
+
return new NavigatorModel(new DocumentGraph(doc), options);
|
|
25
|
+
}
|
|
26
|
+
|
|
18
27
|
constructor(model, { rootId = null, direction = 'both', allowedKinds = null } = {}) {
|
|
19
28
|
this.model = model;
|
|
20
29
|
this.direction = direction;
|
|
@@ -39,6 +48,11 @@ export class NavigatorModel {
|
|
|
39
48
|
return () => this._listeners.delete(listener);
|
|
40
49
|
}
|
|
41
50
|
|
|
51
|
+
/** An element's layer: the graph's own answer, or ArchiMate's (an ArchimateModel has none of its own). */
|
|
52
|
+
layerOf(element) {
|
|
53
|
+
return typeof this.model.layerOf === 'function' ? this.model.layerOf(element) : treeLayerOf(element);
|
|
54
|
+
}
|
|
55
|
+
|
|
42
56
|
_notify() {
|
|
43
57
|
for (const listener of this._listeners) {
|
|
44
58
|
try {
|
|
@@ -209,7 +223,7 @@ export class NavigatorModel {
|
|
|
209
223
|
direction: dir,
|
|
210
224
|
relationship: rel,
|
|
211
225
|
element: otherElement,
|
|
212
|
-
layer:
|
|
226
|
+
layer: this.layerOf(otherElement),
|
|
213
227
|
cycle,
|
|
214
228
|
expanded,
|
|
215
229
|
children,
|
|
@@ -227,8 +241,8 @@ export class NavigatorModel {
|
|
|
227
241
|
return {
|
|
228
242
|
pathKey,
|
|
229
243
|
element,
|
|
230
|
-
layer:
|
|
231
|
-
views: this.model.viewsContaining(element.id),
|
|
244
|
+
layer: this.layerOf(element),
|
|
245
|
+
views: typeof this.model.viewsContaining === 'function' ? this.model.viewsContaining(element.id) : [],
|
|
232
246
|
incoming: incomingBranches,
|
|
233
247
|
outgoing: outgoingBranches,
|
|
234
248
|
};
|
|
@@ -355,7 +369,7 @@ export class NavigatorModel {
|
|
|
355
369
|
const depths = new Map([[elementId, 0]]);
|
|
356
370
|
const elementsMap = new Map();
|
|
357
371
|
const relsMap = new Map();
|
|
358
|
-
const layers = new Set([
|
|
372
|
+
const layers = new Set([this.layerOf(root)]);
|
|
359
373
|
|
|
360
374
|
const queue = [{ id: elementId, depth: 0 }];
|
|
361
375
|
|
|
@@ -376,7 +390,7 @@ export class NavigatorModel {
|
|
|
376
390
|
if (!depths.has(target.id)) {
|
|
377
391
|
depths.set(target.id, nextDepth);
|
|
378
392
|
elementsMap.set(target.id, target);
|
|
379
|
-
layers.add(
|
|
393
|
+
layers.add(this.layerOf(target));
|
|
380
394
|
queue.push({ id: target.id, depth: nextDepth });
|
|
381
395
|
}
|
|
382
396
|
}
|
|
@@ -392,7 +406,7 @@ export class NavigatorModel {
|
|
|
392
406
|
if (!depths.has(source.id)) {
|
|
393
407
|
depths.set(source.id, nextDepth);
|
|
394
408
|
elementsMap.set(source.id, source);
|
|
395
|
-
layers.add(
|
|
409
|
+
layers.add(this.layerOf(source));
|
|
396
410
|
queue.push({ id: source.id, depth: nextDepth });
|
|
397
411
|
}
|
|
398
412
|
}
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TemplatedReport — template-driven report generator (
|
|
2
|
+
* TemplatedReport — template-driven report generator (HTML, Word, Excel, CSV, Markdown).
|
|
3
3
|
*
|
|
4
4
|
* Implements Archi's Jasper-style reporting with customizable templates,
|
|
5
5
|
* placeholders for model metadata, views, view diagrams (SVG/PNG), elements,
|
|
6
6
|
* relationships, and properties.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import {
|
|
10
|
+
escapeHtml,
|
|
11
|
+
escapeXml,
|
|
12
|
+
escapeCsv,
|
|
13
|
+
formatHtml,
|
|
14
|
+
formatWord,
|
|
15
|
+
formatExcel,
|
|
16
|
+
formatCsv,
|
|
17
|
+
formatMarkdown,
|
|
18
|
+
} from './reportWriters.js';
|
|
19
|
+
|
|
20
|
+
export { escapeHtml, escapeXml, escapeCsv };
|
|
21
|
+
|
|
9
22
|
/** Built-in template templates */
|
|
10
23
|
export const REPORT_TEMPLATES = Object.freeze({
|
|
11
24
|
EXECUTIVE: 'executive',
|
|
@@ -13,27 +26,6 @@ export const REPORT_TEMPLATES = Object.freeze({
|
|
|
13
26
|
TABULAR: 'tabular',
|
|
14
27
|
});
|
|
15
28
|
|
|
16
|
-
function escapeHtml(str) {
|
|
17
|
-
return String(str ?? '')
|
|
18
|
-
.replace(/&/g, '&')
|
|
19
|
-
.replace(/</g, '<')
|
|
20
|
-
.replace(/>/g, '>')
|
|
21
|
-
.replace(/"/g, '"')
|
|
22
|
-
.replace(/'/g, ''');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function escapeXml(str) {
|
|
26
|
-
return escapeHtml(str);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function escapeCsv(val) {
|
|
30
|
-
const s = String(val ?? '');
|
|
31
|
-
if (/[",\n\r]/.test(s)) {
|
|
32
|
-
return `"${s.replace(/"/g, '""')}"`;
|
|
33
|
-
}
|
|
34
|
-
return s;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
29
|
export class TemplatedReport {
|
|
38
30
|
/**
|
|
39
31
|
* @param {Object|ViewSetModel|ArchimateModel} model
|
|
@@ -50,14 +42,14 @@ export class TemplatedReport {
|
|
|
50
42
|
}
|
|
51
43
|
|
|
52
44
|
get title() {
|
|
53
|
-
return this._title || this.model
|
|
45
|
+
return this._title || this.model?.name || this.model?.title || 'Model Report';
|
|
54
46
|
}
|
|
55
47
|
|
|
56
48
|
/**
|
|
57
49
|
* Normalized model data extracted for reporting.
|
|
58
50
|
*/
|
|
59
51
|
extractData() {
|
|
60
|
-
const m = this.model;
|
|
52
|
+
const m = this.model || {};
|
|
61
53
|
const title = this.title;
|
|
62
54
|
const doc = m.documentation || '';
|
|
63
55
|
|
|
@@ -149,6 +141,9 @@ export class TemplatedReport {
|
|
|
149
141
|
version: m.version || '1.0',
|
|
150
142
|
properties,
|
|
151
143
|
},
|
|
144
|
+
title,
|
|
145
|
+
documentation: doc,
|
|
146
|
+
version: m.version || '1.0',
|
|
152
147
|
views,
|
|
153
148
|
elements: normElements,
|
|
154
149
|
relationships: normRelationships,
|
|
@@ -163,279 +158,24 @@ export class TemplatedReport {
|
|
|
163
158
|
* @param {boolean} [options.printable=true]
|
|
164
159
|
* @returns {string}
|
|
165
160
|
*/
|
|
166
|
-
toHtml(
|
|
167
|
-
|
|
168
|
-
const { model, views, elements, relationships, properties, date } = data;
|
|
169
|
-
|
|
170
|
-
const printCss = printable ? `
|
|
171
|
-
@media print {
|
|
172
|
-
body { background: #fff; margin: 0; padding: 0; font-size: 11pt; color: #000; }
|
|
173
|
-
header, footer { position: fixed; width: 100%; left: 0; font-size: 9pt; color: #777; }
|
|
174
|
-
header { top: 0; border-bottom: 1px solid #ccc; padding-bottom: 4px; }
|
|
175
|
-
footer { bottom: 0; border-top: 1px solid #ccc; padding-top: 4px; display: flex; justify-content: space-between; }
|
|
176
|
-
.page-break { page-break-after: always; }
|
|
177
|
-
.no-break { page-break-inside: avoid; }
|
|
178
|
-
.picture { max-width: 100% !important; page-break-inside: avoid; }
|
|
179
|
-
table { page-break-inside: auto; }
|
|
180
|
-
tr { page-break-inside: avoid; page-break-after: auto; }
|
|
181
|
-
}
|
|
182
|
-
@page {
|
|
183
|
-
size: A4;
|
|
184
|
-
margin: 20mm;
|
|
185
|
-
}` : '';
|
|
186
|
-
|
|
187
|
-
return `<!DOCTYPE html>
|
|
188
|
-
<html lang="en">
|
|
189
|
-
<head>
|
|
190
|
-
<meta charset="utf-8">
|
|
191
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
192
|
-
<title>${escapeHtml(model.name)}</title>
|
|
193
|
-
<style>
|
|
194
|
-
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; line-height: 1.5; color: #222; margin: 24px auto; max-width: 960px; padding: 0 16px; }
|
|
195
|
-
h1 { font-size: 26px; border-bottom: 2px solid #2563eb; padding-bottom: 6px; margin-top: 24px; color: #1e3a8a; }
|
|
196
|
-
h2 { font-size: 20px; border-bottom: 1px solid #cbd5e1; padding-bottom: 4px; margin-top: 24px; color: #1e40af; }
|
|
197
|
-
h3 { font-size: 16px; margin-top: 18px; color: #334155; }
|
|
198
|
-
.meta { font-size: 13px; color: #64748b; margin-bottom: 20px; }
|
|
199
|
-
.doc { white-space: pre-wrap; background: #f8fafc; border-left: 4px solid #3b82f6; padding: 10px 14px; margin: 12px 0; font-size: 14px; }
|
|
200
|
-
table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; }
|
|
201
|
-
th, td { border: 1px solid #e2e8f0; padding: 6px 10px; text-align: left; vertical-align: top; }
|
|
202
|
-
th { background: #f1f5f9; font-weight: 600; color: #334155; }
|
|
203
|
-
tr:nth-child(even) { background: #f8fafc; }
|
|
204
|
-
.picture { margin: 16px 0; border: 1px solid #e2e8f0; border-radius: 4px; padding: 8px; background: #fff; overflow-x: auto; text-align: center; }
|
|
205
|
-
.picture svg { max-width: 100%; height: auto; }
|
|
206
|
-
.badge { display: inline-block; padding: 2px 6px; font-size: 11px; font-weight: 500; border-radius: 3px; background: #e0f2fe; color: #0369a1; }
|
|
207
|
-
${printCss}
|
|
208
|
-
</style>
|
|
209
|
-
</head>
|
|
210
|
-
<body>
|
|
211
|
-
<h1>${escapeHtml(model.name)}</h1>
|
|
212
|
-
<div class="meta">Generated: ${escapeHtml(date)} | Version: ${escapeHtml(model.version)}</div>
|
|
213
|
-
${model.documentation ? `<div class="doc">${escapeHtml(model.documentation)}</div>` : ''}
|
|
214
|
-
|
|
215
|
-
${properties.length ? `
|
|
216
|
-
<h2>Model Properties</h2>
|
|
217
|
-
<table>
|
|
218
|
-
<thead><tr><th>Property</th><th>Value</th></tr></thead>
|
|
219
|
-
<tbody>
|
|
220
|
-
${properties.map(p => `<tr><td>${escapeHtml(p.name)}</td><td>${escapeHtml(p.value)}</td></tr>`).join('')}
|
|
221
|
-
</tbody>
|
|
222
|
-
</table>` : ''}
|
|
223
|
-
|
|
224
|
-
<h2>Views (${views.length})</h2>
|
|
225
|
-
${views.map(v => `
|
|
226
|
-
<div class="no-break">
|
|
227
|
-
<h3>${escapeHtml(v.name)}</h3>
|
|
228
|
-
${v.documentation ? `<div class="doc">${escapeHtml(v.documentation)}</div>` : ''}
|
|
229
|
-
${v.picture ? `<div class="picture">${v.picture}</div>` : ''}
|
|
230
|
-
${v.elements.length ? `
|
|
231
|
-
<table>
|
|
232
|
-
<thead><tr><th>Element</th><th>Type</th></tr></thead>
|
|
233
|
-
<tbody>
|
|
234
|
-
${v.elements.map(e => `<tr><td>${escapeHtml(e.name)}</td><td><span class="badge">${escapeHtml(e.type)}</span></td></tr>`).join('')}
|
|
235
|
-
</tbody>
|
|
236
|
-
</table>` : ''}
|
|
237
|
-
</div>
|
|
238
|
-
<div class="page-break"></div>
|
|
239
|
-
`).join('')}
|
|
240
|
-
|
|
241
|
-
${elements.length ? `
|
|
242
|
-
<h2>Elements (${elements.length})</h2>
|
|
243
|
-
<table>
|
|
244
|
-
<thead><tr><th>Name</th><th>Type</th><th>Layer</th><th>Documentation</th></tr></thead>
|
|
245
|
-
<tbody>
|
|
246
|
-
${elements.map(e => `<tr><td><strong>${escapeHtml(e.name)}</strong></td><td><span class="badge">${escapeHtml(e.type)}</span></td><td>${escapeHtml(e.layer)}</td><td>${escapeHtml(e.documentation)}</td></tr>`).join('')}
|
|
247
|
-
</tbody>
|
|
248
|
-
</table>` : ''}
|
|
249
|
-
|
|
250
|
-
${relationships.length ? `
|
|
251
|
-
<h2>Relationships (${relationships.length})</h2>
|
|
252
|
-
<table>
|
|
253
|
-
<thead><tr><th>Type</th><th>Source</th><th>Target</th><th>Name</th><th>Documentation</th></tr></thead>
|
|
254
|
-
<tbody>
|
|
255
|
-
${relationships.map(r => `<tr><td><span class="badge">${escapeHtml(r.type)}</span></td><td>${escapeHtml(r.source)}</td><td>${escapeHtml(r.target)}</td><td>${escapeHtml(r.name)}</td><td>${escapeHtml(r.documentation)}</td></tr>`).join('')}
|
|
256
|
-
</tbody>
|
|
257
|
-
</table>` : ''}
|
|
258
|
-
|
|
259
|
-
</body>
|
|
260
|
-
</html>`;
|
|
161
|
+
toHtml(options = {}) {
|
|
162
|
+
return formatHtml(this.extractData(), options);
|
|
261
163
|
}
|
|
262
164
|
|
|
263
165
|
/**
|
|
264
|
-
* Render the report as Microsoft Word-compatible
|
|
265
|
-
* Word opens this with native tables, headings, and formatting intact.
|
|
166
|
+
* Render the report as Microsoft Word-compatible document (.doc).
|
|
266
167
|
* @returns {string}
|
|
267
168
|
*/
|
|
268
169
|
toWord() {
|
|
269
|
-
|
|
270
|
-
const { model, views, elements, relationships, properties, date } = data;
|
|
271
|
-
|
|
272
|
-
return `<html xmlns:o="urn:schemas-microsoft-com:office:office"
|
|
273
|
-
xmlns:w="urn:schemas-microsoft-com:office:word"
|
|
274
|
-
xmlns="http://www.w3.org/TR/REC-html40">
|
|
275
|
-
<head>
|
|
276
|
-
<meta charset="utf-8">
|
|
277
|
-
<title>${escapeXml(model.name)}</title>
|
|
278
|
-
<!--[if gte mso 9]>
|
|
279
|
-
<xml>
|
|
280
|
-
<w:WordDocument>
|
|
281
|
-
<w:View>Print</w:View>
|
|
282
|
-
<w:Zoom>100</w:Zoom>
|
|
283
|
-
<w:DoNotOptimizeForBrowser/>
|
|
284
|
-
</w:WordDocument>
|
|
285
|
-
</xml>
|
|
286
|
-
<![endif]-->
|
|
287
|
-
<style>
|
|
288
|
-
body { font-family: Calibri, Arial, sans-serif; font-size: 11pt; line-height: 1.4; color: #333333; }
|
|
289
|
-
h1 { font-size: 22pt; color: #1f4e79; border-bottom: 2pt solid #1f4e79; margin-bottom: 12pt; }
|
|
290
|
-
h2 { font-size: 16pt; color: #2e75b6; margin-top: 18pt; margin-bottom: 8pt; }
|
|
291
|
-
h3 { font-size: 13pt; color: #595959; margin-top: 12pt; }
|
|
292
|
-
p.doc { font-style: italic; background-color: #f2f2f2; padding: 6pt; border-left: 3pt solid #2e75b6; }
|
|
293
|
-
table { width: 100%; border-collapse: collapse; margin-top: 8pt; margin-bottom: 12pt; }
|
|
294
|
-
th, td { border: 0.5pt solid #d9d9d9; padding: 4pt 6pt; font-size: 10pt; }
|
|
295
|
-
th { background-color: #f2f2f2; font-weight: bold; color: #1f4e79; }
|
|
296
|
-
.page-break { page-break-before: always; }
|
|
297
|
-
</style>
|
|
298
|
-
</head>
|
|
299
|
-
<body>
|
|
300
|
-
<h1>${escapeXml(model.name)}</h1>
|
|
301
|
-
<p><strong>Generated:</strong> ${escapeXml(date)} | <strong>Version:</strong> ${escapeXml(model.version)}</p>
|
|
302
|
-
${model.documentation ? `<p class="doc">${escapeXml(model.documentation)}</p>` : ''}
|
|
303
|
-
|
|
304
|
-
${properties.length ? `
|
|
305
|
-
<h2>Properties</h2>
|
|
306
|
-
<table>
|
|
307
|
-
<tr><th>Property</th><th>Value</th></tr>
|
|
308
|
-
${properties.map(p => `<tr><td>${escapeXml(p.name)}</td><td>${escapeXml(p.value)}</td></tr>`).join('')}
|
|
309
|
-
</table>` : ''}
|
|
310
|
-
|
|
311
|
-
<h2>Views</h2>
|
|
312
|
-
${views.map(v => `
|
|
313
|
-
<div class="page-break"></div>
|
|
314
|
-
<h3>${escapeXml(v.name)}</h3>
|
|
315
|
-
${v.documentation ? `<p class="doc">${escapeXml(v.documentation)}</p>` : ''}
|
|
316
|
-
${v.picture ? `<div>${v.picture}</div>` : ''}
|
|
317
|
-
${v.elements.length ? `
|
|
318
|
-
<table>
|
|
319
|
-
<tr><th>Element</th><th>Type</th></tr>
|
|
320
|
-
${v.elements.map(e => `<tr><td>${escapeXml(e.name)}</td><td>${escapeXml(e.type)}</td></tr>`).join('')}
|
|
321
|
-
</table>` : ''}
|
|
322
|
-
`).join('')}
|
|
323
|
-
|
|
324
|
-
${elements.length ? `
|
|
325
|
-
<div class="page-break"></div>
|
|
326
|
-
<h2>Elements (${elements.length})</h2>
|
|
327
|
-
<table>
|
|
328
|
-
<tr><th>Name</th><th>Type</th><th>Layer</th><th>Documentation</th></tr>
|
|
329
|
-
${elements.map(e => `<tr><td><strong>${escapeXml(e.name)}</strong></td><td>${escapeXml(e.type)}</td><td>${escapeXml(e.layer)}</td><td>${escapeXml(e.documentation)}</td></tr>`).join('')}
|
|
330
|
-
</table>` : ''}
|
|
331
|
-
|
|
332
|
-
${relationships.length ? `
|
|
333
|
-
<div class="page-break"></div>
|
|
334
|
-
<h2>Relationships (${relationships.length})</h2>
|
|
335
|
-
<table>
|
|
336
|
-
<tr><th>Type</th><th>Source</th><th>Target</th><th>Name</th></tr>
|
|
337
|
-
${relationships.map(r => `<tr><td>${escapeXml(r.type)}</td><td>${escapeXml(r.source)}</td><td>${escapeXml(r.target)}</td><td>${escapeXml(r.name)}</td></tr>`).join('')}
|
|
338
|
-
</table>` : ''}
|
|
339
|
-
|
|
340
|
-
</body>
|
|
341
|
-
</html>`;
|
|
170
|
+
return formatWord(this.extractData());
|
|
342
171
|
}
|
|
343
172
|
|
|
344
173
|
/**
|
|
345
|
-
* Render the report as Microsoft Excel XML Spreadsheet 2003 format (.xml
|
|
346
|
-
* Opens in Excel with native worksheets: Model, Views, Elements, Relationships, Properties.
|
|
174
|
+
* Render the report as Microsoft Excel XML Spreadsheet 2003 format (.xml).
|
|
347
175
|
* @returns {string}
|
|
348
176
|
*/
|
|
349
177
|
toExcel() {
|
|
350
|
-
|
|
351
|
-
const { model, views, elements, relationships, properties, date } = data;
|
|
352
|
-
|
|
353
|
-
const makeRow = (cells, isHeader = false) => `
|
|
354
|
-
<Row>
|
|
355
|
-
${cells.map(c => ` <Cell${isHeader ? ' ss:StyleID="Header"' : ''}><Data ss:Type="String">${escapeXml(c)}</Data></Cell>`).join('\n')}
|
|
356
|
-
</Row>`;
|
|
357
|
-
|
|
358
|
-
return `<?xml version="1.0"?>
|
|
359
|
-
<?mso-application progid="Excel.Sheet"?>
|
|
360
|
-
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
|
|
361
|
-
xmlns:o="urn:schemas-microsoft-com:office:office"
|
|
362
|
-
xmlns:x="urn:schemas-microsoft-com:office:excel"
|
|
363
|
-
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
|
|
364
|
-
<Styles>
|
|
365
|
-
<Style ss:ID="Default" ss:Name="Normal">
|
|
366
|
-
<Alignment ss:Vertical="Center"/>
|
|
367
|
-
<Font ss:FontName="Calibri" ss:Size="11" ss:Color="#000000"/>
|
|
368
|
-
</Style>
|
|
369
|
-
<Style ss:ID="Header">
|
|
370
|
-
<Font ss:FontName="Calibri" ss:Size="11" ss:Color="#FFFFFF" ss:Bold="1"/>
|
|
371
|
-
<Interior ss:Color="#2563EB" ss:Pattern="Solid"/>
|
|
372
|
-
</Style>
|
|
373
|
-
<Style ss:ID="Title">
|
|
374
|
-
<Font ss:FontName="Calibri" ss:Size="16" ss:Color="#1E3A8A" ss:Bold="1"/>
|
|
375
|
-
</Style>
|
|
376
|
-
</Styles>
|
|
377
|
-
|
|
378
|
-
<Worksheet ss:Name="Model Summary">
|
|
379
|
-
<Table>
|
|
380
|
-
<Column ss:Width="160"/>
|
|
381
|
-
<Column ss:Width="400"/>
|
|
382
|
-
<Row>
|
|
383
|
-
<Cell ss:StyleID="Title"><Data ss:Type="String">${escapeXml(model.name)}</Data></Cell>
|
|
384
|
-
</Row>
|
|
385
|
-
${makeRow(['Item', 'Details'], true)}
|
|
386
|
-
${makeRow(['Title', model.name])}
|
|
387
|
-
${makeRow(['Version', model.version])}
|
|
388
|
-
${makeRow(['Generated', date])}
|
|
389
|
-
${makeRow(['Documentation', model.documentation])}
|
|
390
|
-
${makeRow(['Total Views', String(views.length)])}
|
|
391
|
-
${makeRow(['Total Elements', String(elements.length)])}
|
|
392
|
-
${makeRow(['Total Relationships', String(relationships.length)])}
|
|
393
|
-
</Table>
|
|
394
|
-
</Worksheet>
|
|
395
|
-
|
|
396
|
-
<Worksheet ss:Name="Views">
|
|
397
|
-
<Table>
|
|
398
|
-
<Column ss:Width="180"/>
|
|
399
|
-
<Column ss:Width="80"/>
|
|
400
|
-
<Column ss:Width="300"/>
|
|
401
|
-
${makeRow(['View Name', 'Elements Count', 'Documentation'], true)}
|
|
402
|
-
${views.map(v => makeRow([v.name, String(v.elements.length), v.documentation])).join('')}
|
|
403
|
-
</Table>
|
|
404
|
-
</Worksheet>
|
|
405
|
-
|
|
406
|
-
<Worksheet ss:Name="Elements">
|
|
407
|
-
<Table>
|
|
408
|
-
<Column ss:Width="140"/>
|
|
409
|
-
<Column ss:Width="160"/>
|
|
410
|
-
<Column ss:Width="100"/>
|
|
411
|
-
<Column ss:Width="280"/>
|
|
412
|
-
${makeRow(['Name', 'Type', 'Layer', 'Documentation'], true)}
|
|
413
|
-
${elements.map(e => makeRow([e.name, e.type, e.layer, e.documentation])).join('')}
|
|
414
|
-
</Table>
|
|
415
|
-
</Worksheet>
|
|
416
|
-
|
|
417
|
-
<Worksheet ss:Name="Relationships">
|
|
418
|
-
<Table>
|
|
419
|
-
<Column ss:Width="140"/>
|
|
420
|
-
<Column ss:Width="160"/>
|
|
421
|
-
<Column ss:Width="160"/>
|
|
422
|
-
<Column ss:Width="100"/>
|
|
423
|
-
<Column ss:Width="200"/>
|
|
424
|
-
${makeRow(['Type', 'Source', 'Target', 'Name', 'Documentation'], true)}
|
|
425
|
-
${relationships.map(r => makeRow([r.type, r.source, r.target, r.name, r.documentation])).join('')}
|
|
426
|
-
</Table>
|
|
427
|
-
</Worksheet>
|
|
428
|
-
|
|
429
|
-
${properties.length ? `
|
|
430
|
-
<Worksheet ss:Name="Properties">
|
|
431
|
-
<Table>
|
|
432
|
-
<Column ss:Width="160"/>
|
|
433
|
-
<Column ss:Width="300"/>
|
|
434
|
-
${makeRow(['Name', 'Value'], true)}
|
|
435
|
-
${properties.map(p => makeRow([p.name, p.value])).join('')}
|
|
436
|
-
</Table>
|
|
437
|
-
</Worksheet>` : ''}
|
|
438
|
-
</Workbook>`;
|
|
178
|
+
return formatExcel(this.extractData());
|
|
439
179
|
}
|
|
440
180
|
|
|
441
181
|
/**
|
|
@@ -444,30 +184,7 @@ ${cells.map(c => ` <Cell${isHeader ? ' ss:StyleID="Header"' : ''}><Data ss:T
|
|
|
444
184
|
* @returns {string}
|
|
445
185
|
*/
|
|
446
186
|
toCsv(table = 'elements') {
|
|
447
|
-
|
|
448
|
-
switch (table) {
|
|
449
|
-
case 'views': {
|
|
450
|
-
const header = ['ID', 'Name', 'Elements', 'Documentation'];
|
|
451
|
-
const rows = data.views.map(v => [v.id, v.name, v.elements.length, v.documentation]);
|
|
452
|
-
return [header, ...rows].map(r => r.map(escapeCsv).join(',')).join('\r\n');
|
|
453
|
-
}
|
|
454
|
-
case 'relationships': {
|
|
455
|
-
const header = ['ID', 'Type', 'Source', 'Target', 'Name', 'Documentation'];
|
|
456
|
-
const rows = data.relationships.map(r => [r.id, r.type, r.source, r.target, r.name, r.documentation]);
|
|
457
|
-
return [header, ...rows].map(r => r.map(escapeCsv).join(',')).join('\r\n');
|
|
458
|
-
}
|
|
459
|
-
case 'properties': {
|
|
460
|
-
const header = ['Name', 'Value'];
|
|
461
|
-
const rows = data.properties.map(p => [p.name, p.value]);
|
|
462
|
-
return [header, ...rows].map(r => r.map(escapeCsv).join(',')).join('\r\n');
|
|
463
|
-
}
|
|
464
|
-
case 'elements':
|
|
465
|
-
default: {
|
|
466
|
-
const header = ['ID', 'Name', 'Type', 'Layer', 'Documentation'];
|
|
467
|
-
const rows = data.elements.map(e => [e.id, e.name, e.type, e.layer, e.documentation]);
|
|
468
|
-
return [header, ...rows].map(r => r.map(escapeCsv).join(',')).join('\r\n');
|
|
469
|
-
}
|
|
470
|
-
}
|
|
187
|
+
return formatCsv(this.extractData(), table);
|
|
471
188
|
}
|
|
472
189
|
|
|
473
190
|
/**
|
|
@@ -475,73 +192,6 @@ ${cells.map(c => ` <Cell${isHeader ? ' ss:StyleID="Header"' : ''}><Data ss:T
|
|
|
475
192
|
* @returns {string}
|
|
476
193
|
*/
|
|
477
194
|
toMarkdown() {
|
|
478
|
-
|
|
479
|
-
const { model, views, elements, relationships, properties, date } = data;
|
|
480
|
-
const lines = [];
|
|
481
|
-
|
|
482
|
-
lines.push(`# ${model.name}`);
|
|
483
|
-
lines.push(`*Generated: ${date} | Version: ${model.version}*`);
|
|
484
|
-
lines.push('');
|
|
485
|
-
|
|
486
|
-
if (model.documentation) {
|
|
487
|
-
lines.push(model.documentation);
|
|
488
|
-
lines.push('');
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
if (properties.length) {
|
|
492
|
-
lines.push('## Properties');
|
|
493
|
-
lines.push('');
|
|
494
|
-
lines.push('| Property | Value |');
|
|
495
|
-
lines.push('| --- | --- |');
|
|
496
|
-
for (const p of properties) {
|
|
497
|
-
lines.push(`| ${p.name} | ${p.value} |`);
|
|
498
|
-
}
|
|
499
|
-
lines.push('');
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
if (views.length) {
|
|
503
|
-
lines.push(`## Views (${views.length})`);
|
|
504
|
-
lines.push('');
|
|
505
|
-
for (const v of views) {
|
|
506
|
-
lines.push(`### ${v.name}`);
|
|
507
|
-
if (v.documentation) {
|
|
508
|
-
lines.push('');
|
|
509
|
-
lines.push(v.documentation);
|
|
510
|
-
}
|
|
511
|
-
if (v.elements.length) {
|
|
512
|
-
lines.push('');
|
|
513
|
-
lines.push('| Element | Type |');
|
|
514
|
-
lines.push('| --- | --- |');
|
|
515
|
-
for (const e of v.elements) {
|
|
516
|
-
lines.push(`| ${e.name} | ${e.type} |`);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
lines.push('');
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
if (elements.length) {
|
|
524
|
-
lines.push(`## Elements (${elements.length})`);
|
|
525
|
-
lines.push('');
|
|
526
|
-
lines.push('| Name | Type | Layer | Documentation |');
|
|
527
|
-
lines.push('| --- | --- | --- | --- |');
|
|
528
|
-
for (const e of elements) {
|
|
529
|
-
lines.push(`| **${e.name}** | ${e.type} | ${e.layer} | ${e.documentation.replace(/\n/g, ' ')} |`);
|
|
530
|
-
}
|
|
531
|
-
lines.push('');
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
if (relationships.length) {
|
|
535
|
-
lines.push(`## Relationships (${relationships.length})`);
|
|
536
|
-
lines.push('');
|
|
537
|
-
lines.push('| Type | Source | Target | Name |');
|
|
538
|
-
lines.push('| --- | --- | --- | --- |');
|
|
539
|
-
for (const r of relationships) {
|
|
540
|
-
lines.push(`| ${r.type} | ${r.source} | ${r.target} | ${r.name} |`);
|
|
541
|
-
}
|
|
542
|
-
lines.push('');
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
return lines.join('\n');
|
|
195
|
+
return formatMarkdown(this.extractData());
|
|
546
196
|
}
|
|
547
197
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ViewsReport — a templated report for any set of chart views (ViewsPart, DecisionWorkbench).
|
|
3
|
+
*
|
|
4
|
+
* Normalizes views containing charts (SVGs, titles, data) and notes/documentation into
|
|
5
|
+
* the report structure used by formatHtml, formatWord, formatExcel, formatCsv, and formatMarkdown.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { TemplatedReport, REPORT_TEMPLATES } from './TemplatedReport.js';
|
|
9
|
+
|
|
10
|
+
export class ViewsReport extends TemplatedReport {
|
|
11
|
+
/**
|
|
12
|
+
* @param {Array<Object>|Object} views - array of view objects or document holding a views list
|
|
13
|
+
* @param {Object} [options]
|
|
14
|
+
* @param {string} [options.title] - Report title (default 'Views Report')
|
|
15
|
+
* @param {string} [options.documentation] - Overall documentation/summary
|
|
16
|
+
* @param {string} [options.version='1.0'] - Version
|
|
17
|
+
* @param {Array<Object>} [options.properties=[]] - Overall properties
|
|
18
|
+
* @param {Object<string, string>} [options.pictures={}] - SVG markup mapped by viewId, viewId:index, or chart title
|
|
19
|
+
* @param {string} [options.template] - executive | detailed | tabular
|
|
20
|
+
*/
|
|
21
|
+
constructor(views = [], options = {}) {
|
|
22
|
+
let viewList = [];
|
|
23
|
+
let docTitle = null;
|
|
24
|
+
let docDocumentation = null;
|
|
25
|
+
let docProperties = [];
|
|
26
|
+
|
|
27
|
+
if (Array.isArray(views)) {
|
|
28
|
+
viewList = views;
|
|
29
|
+
} else if (views && typeof views === 'object') {
|
|
30
|
+
viewList = Array.isArray(views.views) ? views.views : (typeof views.views === 'function' ? views.views() : []);
|
|
31
|
+
docTitle = views.title || views.name || null;
|
|
32
|
+
docDocumentation = views.documentation || views.notes || null;
|
|
33
|
+
docProperties = Array.isArray(views.properties) ? views.properties : [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
super(null, {
|
|
37
|
+
title: options.title || docTitle || 'Views Report',
|
|
38
|
+
pictures: options.pictures || {},
|
|
39
|
+
template: options.template || REPORT_TEMPLATES.DETAILED,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
this.rawViews = viewList;
|
|
43
|
+
this.options = options;
|
|
44
|
+
this.documentation = options.documentation ?? (typeof docDocumentation === 'string' ? docDocumentation : '') ?? '';
|
|
45
|
+
this.version = options.version ?? '1.0';
|
|
46
|
+
this.properties = options.properties ?? docProperties ?? [];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get title() {
|
|
50
|
+
return this._title || 'Views Report';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Extract normalized report data from views.
|
|
55
|
+
*/
|
|
56
|
+
extractData() {
|
|
57
|
+
const title = this.title;
|
|
58
|
+
const views = this.rawViews.map((v, vIndex) => {
|
|
59
|
+
const vId = v.id || `view-${vIndex + 1}`;
|
|
60
|
+
const vName = v.title || v.name || vId;
|
|
61
|
+
const notes = Array.isArray(v.notes) ? v.notes : (v.notes ? [String(v.notes)] : []);
|
|
62
|
+
const doc = v.documentation || (notes.length ? notes.join('\n\n') : '');
|
|
63
|
+
|
|
64
|
+
const charts = (v.charts || []).map((c, cIndex) => {
|
|
65
|
+
const picKey = `${vId}:${cIndex}`;
|
|
66
|
+
const pic = this.pictures[picKey]
|
|
67
|
+
|| this.pictures[c.title]
|
|
68
|
+
|| this.pictures[c.id]
|
|
69
|
+
|| (cIndex === 0 ? this.pictures[vId] : null)
|
|
70
|
+
|| c.picture
|
|
71
|
+
|| c.svg
|
|
72
|
+
|| '';
|
|
73
|
+
return {
|
|
74
|
+
id: c.id || `${vId}-chart-${cIndex + 1}`,
|
|
75
|
+
title: c.title || `Chart ${cIndex + 1}`,
|
|
76
|
+
chartType: c.chartType || '',
|
|
77
|
+
picture: pic,
|
|
78
|
+
data: c.data,
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Single picture if charts pictures exist or view picture
|
|
83
|
+
const chartPictures = charts.map(c => c.picture).filter(Boolean);
|
|
84
|
+
const primaryPic = chartPictures.length
|
|
85
|
+
? chartPictures.join('\n')
|
|
86
|
+
: (this.pictures[vId] || v.picture || (typeof v.toSvg === 'function' ? v.toSvg() : ''));
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
id: vId,
|
|
90
|
+
name: vName,
|
|
91
|
+
documentation: doc,
|
|
92
|
+
notes,
|
|
93
|
+
picture: primaryPic,
|
|
94
|
+
charts,
|
|
95
|
+
elements: Array.isArray(v.elements) ? v.elements : [],
|
|
96
|
+
properties: Array.isArray(v.properties) ? v.properties : [],
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
model: {
|
|
102
|
+
id: 'views-report',
|
|
103
|
+
name: title,
|
|
104
|
+
documentation: this.documentation,
|
|
105
|
+
version: this.version,
|
|
106
|
+
properties: this.properties,
|
|
107
|
+
},
|
|
108
|
+
title,
|
|
109
|
+
documentation: this.documentation,
|
|
110
|
+
version: this.version,
|
|
111
|
+
views,
|
|
112
|
+
elements: [],
|
|
113
|
+
relationships: [],
|
|
114
|
+
properties: this.properties,
|
|
115
|
+
date: new Date().toISOString().split('T')[0],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|