@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
|
@@ -74,7 +74,7 @@ catalog `key` for you.
|
|
|
74
74
|
| `sankey` | `sankeyDiagram(data, opts)` | `{nodes?: Array<{id,label?,color?}>, links: Array<{source,target,value}>}` | proportional-width flow links; `nodeWidth`, `linkOpacity` |
|
|
75
75
|
| `chord` | `chordDiagram(data, opts)` | same as `sankey` | nodes on a circle with curved chords of proportional thickness |
|
|
76
76
|
| `candlestick` | `candlestick(data, opts)` | `Array<{label, open, high, low, close}>` | `upColor`, `downColor`, `markers` (tooltip annotations) |
|
|
77
|
-
| `stackedBar` | `stackedBarChart(data, opts)` | `Array<{label, values:number[]}>` | `colors`
|
|
77
|
+
| `stackedBar` | `stackedBarChart(data, opts)` | multi-series `Array<{name, values:Array<{label,value}>}>` or `Array<{label, values:number[]}>` | `colors`, `legend`, `seriesLabels`; draws Y-axis ticks and legend |
|
|
78
78
|
| `pipe` | `pipeChart(data, opts)` | `Array<{label, value, metric?}>` or multi-series | `metric` tapers line width; `legend` |
|
|
79
79
|
| `radar` | `radarChart(data, opts)` | `Array<{axis, value}>` or multi-series | `colors`, `legend` |
|
|
80
80
|
| `funnel` | `funnelChart(data, opts)` | `Array<{label, value}>` (largest first) | `colors` |
|
|
@@ -1057,7 +1057,10 @@ types — see `ARCHITECTURE.md`):
|
|
|
1057
1057
|
|
|
1058
1058
|
**In the workbench:** `DataModelPart` (type `data-model`, `RenderEngineWorkspace.DataModelPart`) shows one
|
|
1059
1059
|
model: File ▸ Open…, Save, Save As… (Ctrl+O, Ctrl+S; `part.file`, the `ModelFile` editors use, • while changed,
|
|
1060
|
-
asking before the page is left with changes unsaved),
|
|
1060
|
+
asking before the page is left with changes unsaved), File ▸ **Import from pasted text (.datamodel.json)…** (a model from
|
|
1061
|
+
pasted text rather than a file: a file's contents, or an assistant's answer or a page with the JSON in a ``` block
|
|
1062
|
+
or between other words, found by `findPayload(format, text)`; `askPasteImport(format, { onImport })`; every
|
|
1063
|
+
`ModelHosted` part offers it for its text formats, `part.pasteItems()`; it opens as Open… does), and hosted by a `ModelsPart` (`editorType: 'data-model'`)
|
|
1061
1064
|
New ▸ (New model, From base: …), Duplicate model, Close model and This browser ▸ (with `keep`: kept in this browser
|
|
1062
1065
|
as it changes, reopened next time, backed up to a folder chosen once, as the modeller's models are); Edit ▸ Undo
|
|
1063
1066
|
and Redo; Tools ▸ Compare with and Merge from (the other open models and the bases; a merge is one undo step); Model ▸ New / Edit / Delete perspective and context (those dialogs), Perspective and Context selects (when
|
|
@@ -1081,6 +1084,13 @@ this to `line`/`area`/`stackedBar`/etc. is the same three-line change: call
|
|
|
1081
1084
|
`this._drawYAxisTicks(x, y, width, height, rawMax)` before drawing marks, and scale against its
|
|
1082
1085
|
return value instead of the raw max — not done yet for the rest, tracked as a follow-up.
|
|
1083
1086
|
|
|
1087
|
+
**Negative values.** `bar` and `groupedBar` (and so `pivotChart`, which charts through the grouped
|
|
1088
|
+
bar) take in the smallest value as well as the largest: when one is negative the axis goes below zero
|
|
1089
|
+
on one common step (`Axis.niceRange(min, max)`, drawn by `Axis.drawYRange`), a zero line is drawn
|
|
1090
|
+
across the plot where it crosses, and a negative bar hangs down from it, its value written below it.
|
|
1091
|
+
Money signed both ways (income positive, spending negative) charts as it is, and a total over both is
|
|
1092
|
+
the net. Without a negative value nothing changes: the axis starts at 0 at the bottom.
|
|
1093
|
+
|
|
1084
1094
|
### Choosing a chart for result rows — `ChartChooser`
|
|
1085
1095
|
|
|
1086
1096
|
`new ChartChooser().choose(rows, columns?, { complete? })` says which chart types fit a set of result rows (a query
|
|
@@ -1138,6 +1148,7 @@ const view = new RenderEngine(container, { rendererType: 'svg' }).resultView(row
|
|
|
1138
1148
|
pageSize: 50,
|
|
1139
1149
|
menus: { row: ({ row }) => [{ label: 'Open order', action: () => open(row.id) }] },
|
|
1140
1150
|
highlight: (row) => row.id === selectedId, // rows drawn highlighted: the host's selection
|
|
1151
|
+
format: { Revenue: new Intl.NumberFormat('en-AU', { style: 'currency', currency: 'AUD' }) },
|
|
1141
1152
|
});
|
|
1142
1153
|
```
|
|
1143
1154
|
|
|
@@ -1159,6 +1170,12 @@ or `chartHeight`, 360); pass `fitHeight: false` to size it yourself. A host adds
|
|
|
1159
1170
|
any menu with `menus: { cell, row, column, table }`, each `(context) → PopupMenu items`, shown after
|
|
1160
1171
|
a separator.
|
|
1161
1172
|
|
|
1173
|
+
**Number format.** `format` says how numbers show: a function (number → text) or an
|
|
1174
|
+
`Intl.NumberFormat` for every number, or an object naming either per column, where a column it does
|
|
1175
|
+
not name stays plain (116028; a fraction to at most 4 decimals). Only the cells' text changes: sorting
|
|
1176
|
+
and filtering use the values, and every copy (the table, a column, a row, a value) keeps the raw
|
|
1177
|
+
numbers, so a pasted copy still adds up. `pivotView` takes the same `format`, per measure.
|
|
1178
|
+
|
|
1162
1179
|
Interaction needs the SVG renderer (its shapes take clicks); on canvas the same table is drawn
|
|
1163
1180
|
without it. `TableView` (`src/charting/table/TableView.js`) holds all of the state and builds every
|
|
1164
1181
|
menu as data, so it can be driven and tested without a DOM; `TableViewChart` draws it.
|
|
@@ -1211,6 +1228,10 @@ grid, as `resultView`'s does. `PivotView` (`src/charting/pivot/`) holds all the
|
|
|
1211
1228
|
menus as data, so it can be tested without a DOM; `PivotGridChart` draws it, with the interactive
|
|
1212
1229
|
table's click handling (SVG for clicks; canvas draws the grid without them).
|
|
1213
1230
|
|
|
1231
|
+
`format` (as `resultView`'s, per measure: `{ Revenue: new Intl.NumberFormat(…) }`, or one for every
|
|
1232
|
+
measure) writes the cells, the totals and the traffic lights' cut-offs ("Revenue ≥ $3,000.00"); the
|
|
1233
|
+
grid copy keeps the raw numbers.
|
|
1234
|
+
|
|
1214
1235
|
#### Saved views
|
|
1215
1236
|
|
|
1216
1237
|
A saved view is plain data: the layout (`view.layout()`) and how the grid shows it
|
|
@@ -2098,8 +2119,32 @@ colour by, then a colour for each of its values (a soft palette by default, `Dia
|
|
|
2098
2119
|
colour" leaves a value's shapes as they are), and whether to add a legend. Each shape takes its value's colour as
|
|
2099
2120
|
its own fill, so the colours are kept like any other (an Archi file included), and one undo puts them back. The
|
|
2100
2121
|
template editor colours by the kind of shape; the ArchiMate canvas by **Layer**, **Type**, **Specialization** or any
|
|
2101
|
-
**property** its elements have (notes and pictures have no value and stay as they are)
|
|
2102
|
-
|
|
2122
|
+
**property** its elements have (notes and pictures have no value and stay as they are), and two more:
|
|
2123
|
+
|
|
2124
|
+
- **Change (create, update, delete)**, offered when the model has a deliverable or work package: each element related
|
|
2125
|
+
to one (either way round) takes the change the relationship's name says, or, when the relationship has no name, the
|
|
2126
|
+
deliverable's or work package's name. Synonyms count, in any form: delete, remove, retire, decommission, drop,
|
|
2127
|
+
sunset, phase out, shut down, …; create, add, new, introduce, build, implement, set up, launch, …; update, change,
|
|
2128
|
+
modify, upgrade, enhance, migrate, replace, … (`CHANGE_WORDS`, whole words only). Where an element is given more
|
|
2129
|
+
than one, the one with most consequence wins: delete, then create, then update. The colours offered are
|
|
2130
|
+
**Create** yellow #FDD835, **Update** blue #1E88E5 and **Delete** magenta #D81B60 (`CHANGE_COLOURS`), each still
|
|
2131
|
+
changeable (`changeAction(text)`, `elementChange(model, elementId)`). The lines are coloured too: each relationship
|
|
2132
|
+
to a deliverable or work package is drawn in the colour of the change it says itself (`relationshipChange(model,
|
|
2133
|
+
rel)`), so an element changed two ways shows both on its lines; other lines keep their colour. Lines only drawn
|
|
2134
|
+
(derived ones) are left as they are (`editor.colourEdgeValue(edge, key)`, which a subclass overrides to colour
|
|
2135
|
+
lines by a key).
|
|
2136
|
+
- **A formula over properties…**, offered when elements have properties: a number from each element's numeric
|
|
2137
|
+
properties, in the formula language `colorCol` uses (`kernelExpr.js`: arithmetic, comparisons, `a ? b : c`,
|
|
2138
|
+
maths functions), each property by its name with anything but letters, digits and _ made _ ("Risk score" is
|
|
2139
|
+
`Risk_score`), e.g. `Cost * Risk_score` or `Cost > 10000 ? 1 : 0`. A formula naming no property is refused
|
|
2140
|
+
with what the names are. An element without a number for a property the formula uses is left as it is. Up to
|
|
2141
|
+
eight different results are each a value; more are put in five equal bands, smallest first (a band with nothing in
|
|
2142
|
+
it left out), and the colours offered run light to dark (`expressionValues`, `resultBands`,
|
|
2143
|
+
`sequentialColours`; the key is `expression:<formula>`).
|
|
2144
|
+
|
|
2145
|
+
A legend is a box under the
|
|
2146
|
+
drawing, titled at its top-left, holding a box per value in its colour (in rows of up to four,
|
|
2147
|
+
`DiagramEditor.legendLayout`), all grouped so they move as one (notes; on a canvas, stickies). **Add legend of the kinds
|
|
2103
2148
|
shown** adds one for the kinds of shape (ArchiMate: the element types) in the colour of each's first shape. A
|
|
2104
2149
|
locked shape keeps its colour. `colourKeys()`, `colourValue(template, key)`, `colourValues(key)`,
|
|
2105
2150
|
`colourByCommand(key, colours, { legend })`, `kindLegendEntries()` and `legendCommand(entries, title)`, which a
|
|
@@ -2224,7 +2269,23 @@ label shown, in the same box (or none), with the same lines drawn (each relation
|
|
|
2224
2269
|
thing at the other end), as one undo step with its lines (`view.repeatedBoxes()`, `editor.removeRepeatedBoxes()`).
|
|
2225
2270
|
The first box stays; a box holding others is never taken off. The **File** menu (`EditorPart`'s, and the ArchiMate editor's) exports the current view
|
|
2226
2271
|
as an image: PNG, SVG or a copy to the clipboard (`editor.exportImage('png' | 'svg' | 'copy')`, without the grid
|
|
2227
|
-
or selection; the ArchiMate editor's is off with `features.exportImage: false`)
|
|
2272
|
+
or selection; the ArchiMate editor's is off with `features.exportImage: false`), and so does a right-click on empty
|
|
2273
|
+
canvas (**Export image**, `editor.canvasContextMenu(at)`). By default a picture takes in **the whole drawing**,
|
|
2274
|
+
wherever the view is scrolled: every shape and line, line labels and arrowheads included, with 20px clear all round
|
|
2275
|
+
so nothing touches the edge (`editor.drawingBounds({ margin })`), at **100%**. **Export image…** asks the format,
|
|
2276
|
+
the area (the whole drawing, or only what is in sight) and the scale in percent, and the quick items use its last
|
|
2277
|
+
choice (`editor.exportOptions`, `setExportOptions({ area, scale })`; `exportImage(format, { area, scale })`).
|
|
2278
|
+
**⚙ Settings ▸ Export area** shows where the whole drawing's picture ends as a faint dashed box, never itself
|
|
2279
|
+
exported (`editor.showExportArea`). An SVG cropped to a viewBox keeps a white background over all of it.
|
|
2280
|
+
|
|
2281
|
+
**🗑 Delete from model…**, on an element's menu in the tree and on its box's menu in the diagram (the elements
|
|
2282
|
+
selected, when it is one of them), takes it out of the model rather than off one view, as Archi's "Delete from
|
|
2283
|
+
Model". First a dialog says where it is used: each view it is on (with how many boxes), and each relationship that
|
|
2284
|
+
goes too (those ending on it, and those ending on such a relationship); **Delete from model** goes ahead. Every
|
|
2285
|
+
box and line of them goes from every view, with any view's own connection to one of its boxes, as one undo step
|
|
2286
|
+
that puts it all back, ids and all (`model.usage(elementIds)`, `model.deleteFromModelCommand(elementIds)`,
|
|
2287
|
+
`editor.askDeleteFromModel(ids)` / `deleteFromModel(ids)`, `deleteFromModelMessage(model, ids)`;
|
|
2288
|
+
`elementMenuItems` offers it when the host gives `deleteFromModel`). **Generate View For This**, on
|
|
2228
2289
|
an element's menu in the diagram or the tree, makes a view of the element with everything related to it
|
|
2229
2290
|
around it and every relationship between them (`model.generateView(elementIds, { name, related })`, Archi's
|
|
2230
2291
|
"Generate View For").
|
|
@@ -2587,7 +2648,7 @@ single list of cascading properties:
|
|
|
2587
2648
|
| Key | Values (default first) |
|
|
2588
2649
|
|---|---|
|
|
2589
2650
|
| `routing` | `straight`, `orthogonal`, `curved`, `polyline` (bends around other shapes) |
|
|
2590
|
-
| `attach` | `floating` (outline, aimed at the other end), `sides` (middle of the facing side) |
|
|
2651
|
+
| `attach` | `shortest` (the default, as Archi draws: where two shapes face each other across a gap with their widths or heights overlapping, a straight line straight across the middle of the overlap, outline to outline; otherwise as `floating`), `floating` (outline, aimed at the other end), `sides` (middle of the facing side). `shortest` leaves a line with bends, a pinned end, routing other than straight, or a rotated shape as `floating` draws it |
|
|
2591
2652
|
| `lineStyle` | `solid`, `dashed`, `dotted` (same dash patterns as `GraphDiagram`, via `LINE_STYLE_DASH`) |
|
|
2592
2653
|
| `strokeColor`, `strokeWidth` | `#666666`, `2` |
|
|
2593
2654
|
| `startMarker`, `endMarker` | `none`, or an `EdgeMarker` key |
|
|
@@ -3221,6 +3282,35 @@ const mine = ICONS.with({ // a host's own icons over
|
|
|
3221
3282
|
the workspace bundle), until a `foreignObject`, whose children are HTML again. A spec's `ns` sets the namespace
|
|
3222
3283
|
explicitly.
|
|
3223
3284
|
|
|
3285
|
+
## Ask Bar & Next-Word Typeahead (`AskBar` / `AskView`)
|
|
3286
|
+
|
|
3287
|
+
`AskBar` (`src/charting/ask/AskBar.js`) and its DOM-free state model `AskView` (`src/charting/ask/AskView.js`) provide the interactive question bar and scrollable next-word prediction dropdown (as used in `semanticengine/examples/browser/index.html` and the **Cubing** tab in `test/charts.html`).
|
|
3288
|
+
|
|
3289
|
+
```javascript
|
|
3290
|
+
import { RenderEngine, AskBar, AskView } from '@peter.prib/renderengine';
|
|
3291
|
+
|
|
3292
|
+
const bar = RenderEngine.askBar(document.getElementById('ask-bar'), {
|
|
3293
|
+
value: 'top 3 customers by revenue',
|
|
3294
|
+
// Either pass a custom suggestion callback (e.g. semanticengine's engine.suggest):
|
|
3295
|
+
suggest: (text) => semanticEngine.suggest(text, { sampler: (dim) => semanticEngine.members(dim) }),
|
|
3296
|
+
// Or pass declarative cube/table metadata for built-in next-word prediction:
|
|
3297
|
+
// measures: ['Revenue', 'Cost'],
|
|
3298
|
+
// dimensions: ['Region', 'Quarter'],
|
|
3299
|
+
// members: (dim) => [...],
|
|
3300
|
+
sampleQuestions: ['revenue by region', 'top 3 customers by revenue'],
|
|
3301
|
+
visibleCount: 5, // items visible at once in the scrollable list
|
|
3302
|
+
predictChoices: [0, 3, 5, 8, 10], // 0 = Off; user can switch in the bar's selector
|
|
3303
|
+
onAsk: (question) => runQuery(question),
|
|
3304
|
+
});
|
|
3305
|
+
```
|
|
3306
|
+
|
|
3307
|
+
- **Configurable scrollable next-word prediction:** `visibleCount` (default `5`) sets how many predicted items show at once before scrolling (`max-height` on `.re-ask-suggest-box` with `overflow-y: auto`), and `predictChoices` (default `[0, 3, 5, 8, 10]`) populates the **Predict next word** selector (`Off`, `3 visible (scrollable)`, `5 visible (scrollable)`, …). Set `showPredictControl: false` to hide the selector.
|
|
3308
|
+
- **Semantic badges:** each suggestion item (`{ display, completion, sense?, reference?, relation?, kind? }`) renders its `display` token, optional `sense → reference` subtitle, lexical `relation` badge (`synonym`, `hypernym`, `hyponym`, `antonym`), and `kind` badge (`metric`, `dimension`, `keyword`, `value`).
|
|
3309
|
+
- **Keyboard navigation:** nothing in the list is selected until you move into it, so `Enter` asks the question as typed. `↑` / `↓` move through the scrollable list, starting at the last or first item and scrolling the active item into view. `Enter` completes the selected item; `Tab` completes the selected item, or the first. Either one immediately predicts the following word. `Escape` closes the popup.
|
|
3310
|
+
- **Async sources and answers:** `suggest` can return a promise, for example from an engine that is still loading or a vocabulary on a server. The list keeps showing what it had until the promise settles. An answer for text the user has since changed is dropped, and a rejected promise shows no suggestions. `onAsk` can also return a promise of `{ rows, columns, complete }`, which `resultEngine` shows once it settles.
|
|
3311
|
+
- **For a host:** `bar.setSuggest(fn)` swaps where suggestions come from, for example when the user picks another model or ontology. `bar.setValue(text)` sets the question without suggesting. `bar.focus()` puts the cursor in the question, and `bar.destroy()` removes the bar and cancels any pending close.
|
|
3312
|
+
- **Accessible:** the input is a WAI-ARIA combobox over a `listbox` of `option`s. `aria-expanded` follows the popup and `aria-activedescendant` follows the active item. Each bar's list has its own id, so several bars can share a page.
|
|
3313
|
+
|
|
3224
3314
|
## Where to go next
|
|
3225
3315
|
|
|
3226
3316
|
- **`ARCHITECTURE.md`** — module layout, the chart-type catalog mechanics, how to add a new chart type.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peter.prib/renderengine",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "ArchiMate modeller, diagram editor and chart library for the browser, with no runtime dependencies.",
|
|
3
|
+
"version": "0.14.0",
|
|
4
|
+
"description": "ArchiMate modeller, data modeller, diagram editor, OLAP cube & pivot chart studio, and 50+ type chart library for the browser, with no runtime dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"module": "src/index.js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"archimate": "node scripts/open.mjs examples/archimate.html",
|
|
29
29
|
"diagram": "node scripts/open.mjs examples/diagram-editor.html",
|
|
30
30
|
"datamodel": "node scripts/open.mjs examples/data-model.html",
|
|
31
|
-
"test": "node --test --test-timeout=10000 ./test/*.js ./test/charting/*.js ./test/charting/charts/*.js ./test/charting/table/*.js ./test/charting/pivot/*.js ./test/charting/diagrams/*.js ./test/charting/shapes/*.js",
|
|
31
|
+
"test": "node --test --test-timeout=10000 ./test/*.js ./test/charting/*.js ./test/charting/charts/*.js ./test/charting/table/*.js ./test/charting/pivot/*.js ./test/charting/ask/*.js ./test/charting/diagrams/*.js ./test/charting/shapes/*.js",
|
|
32
32
|
"build": "rollup -c",
|
|
33
33
|
"prepublishOnly": "npm test && npm run build",
|
|
34
34
|
"screenshots": "node scripts/screenshots/screenshots.mjs"
|
|
@@ -38,6 +38,11 @@
|
|
|
38
38
|
"archi",
|
|
39
39
|
"diagram",
|
|
40
40
|
"diagram-editor",
|
|
41
|
+
"data-modelling",
|
|
42
|
+
"bian",
|
|
43
|
+
"olap",
|
|
44
|
+
"pivot",
|
|
45
|
+
"cube",
|
|
41
46
|
"modelling",
|
|
42
47
|
"charts",
|
|
43
48
|
"svg",
|
package/src/chartCatalog.js
CHANGED
|
@@ -318,12 +318,15 @@ export const CHART_CATALOG = [
|
|
|
318
318
|
},
|
|
319
319
|
{
|
|
320
320
|
key: 'stackedBar', label: 'Stacked Bar', method: 'stackedBarChart', category: '2d',
|
|
321
|
-
coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend:
|
|
321
|
+
coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: true, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.NONE,
|
|
322
|
+
seriesCapable: true,
|
|
322
323
|
axes: [
|
|
323
324
|
{ 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:
|
|
325
|
+
{ role: 'values', kind: AXIS_KIND.NUMERIC_LIST, channel: ENCODING_CHANNEL.POSITION_Y_STACKED, required: false },
|
|
326
|
+
{ role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y_STACKED, required: false },
|
|
327
|
+
{ role: 'series', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.COLOR, required: false },
|
|
325
328
|
],
|
|
326
|
-
dataShape: "Array<{label, values:number[]}>
|
|
329
|
+
dataShape: "Array<{label, values:number[]}> or multi-series Array<{name, values:Array<{label,value}>}> — segments stacked from zero, with optional series legend",
|
|
327
330
|
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
331
|
},
|
|
329
332
|
{
|
|
@@ -564,6 +567,50 @@ export const CHART_CATALOG = [
|
|
|
564
567
|
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
568
|
streamShape: 'flat',
|
|
566
569
|
},
|
|
570
|
+
{
|
|
571
|
+
key: 'pareto', label: 'Pareto', method: 'paretoChart', category: '2d',
|
|
572
|
+
coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: false, colorMode: COLOR_MODE.SEMANTIC, camera: CAMERA_MODE.NONE,
|
|
573
|
+
axes: [
|
|
574
|
+
{ role: 'category', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_X, required: true },
|
|
575
|
+
{ role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
|
|
576
|
+
],
|
|
577
|
+
dataShape: 'Array<{label, value}> — sorted descending with a right-hand 0-100% cumulative percentage line and 80% cutoff reference line',
|
|
578
|
+
streamShape: 'flat',
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
key: 'lollipop', label: 'Lollipop', method: 'lollipopChart', category: '2d',
|
|
582
|
+
coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: true, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.NONE,
|
|
583
|
+
seriesCapable: true,
|
|
584
|
+
axes: [
|
|
585
|
+
{ role: 'category', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_X, required: true },
|
|
586
|
+
{ role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y, required: true },
|
|
587
|
+
{ role: 'series', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.COLOR, required: false },
|
|
588
|
+
],
|
|
589
|
+
dataShape: 'Array<{label, value}> or multi-series Array<{name, values:Array<{label,value}>}> — stem-and-dot alternative to bars',
|
|
590
|
+
streamShape: 'flat',
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
key: 'marimekko', label: 'Marimekko', method: 'marimekkoChart', category: '2d',
|
|
594
|
+
coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: true, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.NONE,
|
|
595
|
+
seriesCapable: true,
|
|
596
|
+
axes: [
|
|
597
|
+
{ role: 'category', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.SHAPE_WIDTH, required: true },
|
|
598
|
+
{ role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.POSITION_Y_HEIGHT, required: true },
|
|
599
|
+
{ role: 'series', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.COLOR, required: true },
|
|
600
|
+
],
|
|
601
|
+
dataShape: 'Multi-series Array<{name, values:Array<{label,value}>}> or Array<{label, values:number[]}> — variable-width 100%-stacked mosaic columns',
|
|
602
|
+
streamShape: 'series',
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
key: 'icicle', label: 'Icicle', method: 'icicleChart', category: '2d',
|
|
606
|
+
coordSystem: COORD_SYSTEM.CARTESIAN, title: true, legend: false, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.NONE,
|
|
607
|
+
axes: [
|
|
608
|
+
{ role: 'value', kind: AXIS_KIND.NUMERIC, channel: ENCODING_CHANNEL.ANGLE_OR_AREA, required: true },
|
|
609
|
+
{ role: 'group', kind: AXIS_KIND.CATEGORICAL, channel: ENCODING_CHANNEL.POSITION_ORDER, required: false },
|
|
610
|
+
],
|
|
611
|
+
dataShape: 'Array<{label, value?, children?}> — rectangular hierarchical partition chart (top-down bands by depth, width proportional to value)',
|
|
612
|
+
streamShape: 'flat',
|
|
613
|
+
},
|
|
567
614
|
{
|
|
568
615
|
key: 'bar3DOrbit', label: 'Bar (3D Orbit)', method: 'barChart3DOrbit', category: '3d',
|
|
569
616
|
coordSystem: COORD_SYSTEM.ORBIT_3D, title: true, legend: false, colorMode: COLOR_MODE.PALETTE, camera: CAMERA_MODE.ORBIT,
|
package/src/charting/Axis.js
CHANGED
|
@@ -27,6 +27,30 @@ export class Axis {
|
|
|
27
27
|
return ticks;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Ticks over a range that may go below zero: one common "nice" step, from at or below `minVal`
|
|
32
|
+
* to at or above `maxVal`, always taking in 0 so bars have a baseline. Without a negative it is
|
|
33
|
+
* `niceTicks(maxVal)`.
|
|
34
|
+
* @param {number} minVal
|
|
35
|
+
* @param {number} maxVal
|
|
36
|
+
* @param {number} [count=4] desired number of intervals
|
|
37
|
+
* @returns {number[]}
|
|
38
|
+
*/
|
|
39
|
+
static niceRange(minVal, maxVal, count = 4) {
|
|
40
|
+
const lo = Math.min(0, minVal || 0);
|
|
41
|
+
const hi = Math.max(0, maxVal || 0);
|
|
42
|
+
if (lo === 0) return Axis.niceTicks(hi, count);
|
|
43
|
+
const raw = (hi - lo) / count;
|
|
44
|
+
const mag = Math.pow(10, Math.floor(Math.log10(raw)));
|
|
45
|
+
const frac = raw / mag;
|
|
46
|
+
const step = frac < 1.5 ? mag : frac < 3.5 ? 2 * mag : frac < 7.5 ? 5 * mag : 10 * mag;
|
|
47
|
+
const first = Math.floor(lo / step + 1e-9);
|
|
48
|
+
const last = Math.ceil(hi / step - 1e-9);
|
|
49
|
+
const ticks = [];
|
|
50
|
+
for (let i = first; i <= last; i++) ticks.push(parseFloat((i * step).toFixed(10)));
|
|
51
|
+
return ticks;
|
|
52
|
+
}
|
|
53
|
+
|
|
30
54
|
/**
|
|
31
55
|
* How often a category label fits along an axis `width` wide: 1 (every
|
|
32
56
|
* label), 2 (every other), … — so a long series (a minute of samples, a
|
|
@@ -71,18 +95,36 @@ export class Axis {
|
|
|
71
95
|
* @returns {number} the nice domain max the ticks actually span
|
|
72
96
|
*/
|
|
73
97
|
drawYAxis(renderer, box, rawMax) {
|
|
74
|
-
|
|
75
|
-
|
|
98
|
+
return this.drawYRange(renderer, box, 0, rawMax).max;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Draw a y-axis over a range that may go below zero (`niceRange`): ticks, labels, gridlines,
|
|
103
|
+
* and, when the range does go below zero, a zero line across the plot where it crosses.
|
|
104
|
+
*
|
|
105
|
+
* @param {*} renderer a renderer implementing .path() and .text()
|
|
106
|
+
* @param {{x:number, y:number, width:number, height:number}} box plot-area box
|
|
107
|
+
* @param {number} rawMin the raw data minimum (0 or more: the axis starts at 0)
|
|
108
|
+
* @param {number} rawMax the raw data maximum
|
|
109
|
+
* @returns {{min:number, max:number}} the nice domain the ticks actually span
|
|
110
|
+
*/
|
|
111
|
+
drawYRange(renderer, box, rawMin, rawMax) {
|
|
112
|
+
const ticks = Axis.niceRange(rawMin, rawMax);
|
|
113
|
+
const min = ticks[0];
|
|
114
|
+
const max = ticks.length > 1 ? ticks[ticks.length - 1] : 1;
|
|
115
|
+
const yOf = (v) => box.y + box.height - ((v - min) / (max - min)) * box.height;
|
|
76
116
|
for (const t of ticks) {
|
|
77
|
-
const ty =
|
|
117
|
+
const ty = yOf(t);
|
|
78
118
|
renderer.path(`M${box.x - 5},${ty} L${box.x},${ty}`, 'none', { color: '#999', width: 1 });
|
|
79
|
-
if (t
|
|
119
|
+
if (t !== 0) {
|
|
80
120
|
renderer.path(`M${box.x},${ty} L${box.x + box.width},${ty}`, 'none', { color: '#eee', width: 1 });
|
|
121
|
+
} else if (min < 0) {
|
|
122
|
+
renderer.path(`M${box.x},${ty} L${box.x + box.width},${ty}`, 'none', { color: '#666', width: 1, zeroLine: true });
|
|
81
123
|
}
|
|
82
124
|
renderer.text(Axis.formatValue(t), box.x - 10, ty, {
|
|
83
125
|
textAnchor: 'end', verticalAlign: 'middle', fontSize: '10px', fill: '#666',
|
|
84
126
|
});
|
|
85
127
|
}
|
|
86
|
-
return
|
|
128
|
+
return { min, max };
|
|
87
129
|
}
|
|
88
130
|
}
|
package/src/charting/Chart3D.js
CHANGED
|
@@ -72,8 +72,9 @@ export class Chart3D extends Chart {
|
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
74
|
* Draw one 3D box through the orbit camera. Returns average corner depth.
|
|
75
|
+
* When `datum` is supplied (`{ label, series? }`), each face is recorded via `_datum()`.
|
|
75
76
|
*/
|
|
76
|
-
drawBox(renderer, x0, x1, y0, y1, z0, z1, color, toScreen) {
|
|
77
|
+
drawBox(renderer, x0, x1, y0, y1, z0, z1, color, toScreen, datum = null) {
|
|
77
78
|
const camera = this.camera;
|
|
78
79
|
const faceXSign = Math.sin(camera.theta) >= 0 ? 1 : -1;
|
|
79
80
|
const faceZSign = Math.cos(camera.theta) >= 0 ? 1 : -1;
|
|
@@ -87,7 +88,8 @@ export class Chart3D extends Chart {
|
|
|
87
88
|
|
|
88
89
|
const drawFace = (pts, shade) => {
|
|
89
90
|
const d = `M${pts[0].x},${pts[0].y} L${pts[1].x},${pts[1].y} L${pts[2].x},${pts[2].y} L${pts[3].x},${pts[3].y} Z`;
|
|
90
|
-
renderer.path(d, ColorPalette.lighten(color, shade), { color: '#333', width: 1 });
|
|
91
|
+
const el = renderer.path(d, ColorPalette.lighten(color, shade), { color: '#333', width: 1 });
|
|
92
|
+
if (datum) this._datum(el, datum);
|
|
91
93
|
};
|
|
92
94
|
drawFace(top, 0.15);
|
|
93
95
|
drawFace(xSide, faceXSign > 0 ? -0.05 : -0.2);
|