@elaraai/e3-ui 1.0.98 → 1.0.99
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 +0 -2
- package/dist/src/allocation/adapters.d.ts +1 -1
- package/dist/src/allocation/adapters.js +1 -1
- package/dist/src/allocation/index.d.ts +46 -90
- package/dist/src/allocation/index.d.ts.map +1 -1
- package/dist/src/allocation/index.js +5 -5
- package/dist/src/allocation/index.js.map +1 -1
- package/dist/src/bind/data-indexed.d.ts +273 -51
- package/dist/src/bind/data-indexed.d.ts.map +1 -1
- package/dist/src/bind/data-indexed.js +233 -56
- package/dist/src/bind/data-indexed.js.map +1 -1
- package/dist/src/bind/data.d.ts +48 -9
- package/dist/src/bind/data.d.ts.map +1 -1
- package/dist/src/bind/data.js +48 -3
- package/dist/src/bind/data.js.map +1 -1
- package/dist/src/bind/record.d.ts +61 -24
- package/dist/src/bind/record.d.ts.map +1 -1
- package/dist/src/bind/record.js +17 -57
- package/dist/src/bind/record.js.map +1 -1
- package/dist/src/calendar/index.d.ts +55 -95
- package/dist/src/calendar/index.d.ts.map +1 -1
- package/dist/src/calendar/index.js +10 -6
- package/dist/src/calendar/index.js.map +1 -1
- package/dist/src/flowchart/index.d.ts +35 -31
- package/dist/src/flowchart/index.d.ts.map +1 -1
- package/dist/src/flowchart/index.js +35 -31
- package/dist/src/flowchart/index.js.map +1 -1
- package/dist/src/flowchart/library.d.ts +108 -84
- package/dist/src/flowchart/library.d.ts.map +1 -1
- package/dist/src/flowchart/library.js +108 -84
- package/dist/src/flowchart/library.js.map +1 -1
- package/dist/src/flowchart/payload.d.ts +39 -107
- package/dist/src/flowchart/payload.d.ts.map +1 -1
- package/dist/src/flowchart/payload.js +3 -3
- package/dist/src/flowchart/payload.js.map +1 -1
- package/dist/src/flowchart/values.d.ts +35 -31
- package/dist/src/flowchart/values.d.ts.map +1 -1
- package/dist/src/flowchart/values.js +35 -31
- package/dist/src/flowchart/values.js.map +1 -1
- package/dist/src/flowchart/window.d.ts +0 -12
- package/dist/src/flowchart/window.d.ts.map +1 -1
- package/dist/src/flowchart/window.js +3 -2
- package/dist/src/flowchart/window.js.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/internal.d.ts +7 -3
- package/dist/src/internal.d.ts.map +1 -1
- package/dist/src/internal.js +12 -3
- package/dist/src/internal.js.map +1 -1
- package/dist/src/plan/assemble.d.ts +8 -0
- package/dist/src/plan/assemble.d.ts.map +1 -1
- package/dist/src/plan/assemble.js +1 -1
- package/dist/src/plan/assemble.js.map +1 -1
- package/dist/src/plan/builders.d.ts +1518 -2
- package/dist/src/plan/builders.d.ts.map +1 -1
- package/dist/src/plan/builders.js +248 -114
- package/dist/src/plan/builders.js.map +1 -1
- package/dist/src/plan/event-rows.d.ts +9 -14
- package/dist/src/plan/event-rows.d.ts.map +1 -1
- package/dist/src/plan/event-rows.js +6 -46
- package/dist/src/plan/event-rows.js.map +1 -1
- package/dist/src/plan/factories.d.ts +72 -4
- package/dist/src/plan/factories.d.ts.map +1 -1
- package/dist/src/plan/factories.js +147 -1
- package/dist/src/plan/factories.js.map +1 -1
- package/dist/src/plan/index.d.ts +147 -125
- package/dist/src/plan/index.d.ts.map +1 -1
- package/dist/src/plan/index.js +135 -122
- package/dist/src/plan/index.js.map +1 -1
- package/dist/src/plan/ir.d.ts +1105 -44
- package/dist/src/plan/ir.d.ts.map +1 -1
- package/dist/src/plan/ir.js +5 -0
- package/dist/src/plan/ir.js.map +1 -1
- package/dist/src/plan/library.d.ts +65 -42
- package/dist/src/plan/library.d.ts.map +1 -1
- package/dist/src/plan/library.js +65 -42
- package/dist/src/plan/library.js.map +1 -1
- package/dist/src/plan/nested-checks.d.ts +146 -0
- package/dist/src/plan/nested-checks.d.ts.map +1 -0
- package/dist/src/plan/nested-checks.js +468 -0
- package/dist/src/plan/nested-checks.js.map +1 -0
- package/dist/src/plan/nested-patch.d.ts +35 -0
- package/dist/src/plan/nested-patch.d.ts.map +1 -0
- package/dist/src/plan/nested-patch.js +143 -0
- package/dist/src/plan/nested-patch.js.map +1 -0
- package/dist/src/plan/nested-read.d.ts +3250 -0
- package/dist/src/plan/nested-read.d.ts.map +1 -0
- package/dist/src/plan/nested-read.js +163 -0
- package/dist/src/plan/nested-read.js.map +1 -0
- package/dist/src/plan/nested.d.ts +280 -0
- package/dist/src/plan/nested.d.ts.map +1 -0
- package/dist/src/plan/nested.js +6 -0
- package/dist/src/plan/nested.js.map +1 -0
- package/dist/src/plan/ordered-source.d.ts +1030 -1
- package/dist/src/plan/ordered-source.d.ts.map +1 -1
- package/dist/src/plan/ordered-source.js +48 -6
- package/dist/src/plan/ordered-source.js.map +1 -1
- package/dist/src/plan/over.d.ts +111 -75
- package/dist/src/plan/over.d.ts.map +1 -1
- package/dist/src/plan/over.js +111 -75
- package/dist/src/plan/over.js.map +1 -1
- package/dist/src/plan/pick.d.ts +15 -3
- package/dist/src/plan/pick.d.ts.map +1 -1
- package/dist/src/plan/pick.js +75 -36
- package/dist/src/plan/pick.js.map +1 -1
- package/dist/src/plan/plan.d.ts +2719 -558
- package/dist/src/plan/plan.d.ts.map +1 -1
- package/dist/src/plan/plan.js +14 -2
- package/dist/src/plan/plan.js.map +1 -1
- package/dist/src/plan/reads.d.ts +421 -0
- package/dist/src/plan/reads.d.ts.map +1 -0
- package/dist/src/plan/reads.js +143 -0
- package/dist/src/plan/reads.js.map +1 -0
- package/dist/src/plan/refs.d.ts +111 -75
- package/dist/src/plan/refs.d.ts.map +1 -1
- package/dist/src/plan/refs.js +111 -75
- package/dist/src/plan/refs.js.map +1 -1
- package/dist/src/plan/root.d.ts +10 -5
- package/dist/src/plan/root.d.ts.map +1 -1
- package/dist/src/plan/root.js +76 -13
- package/dist/src/plan/root.js.map +1 -1
- package/dist/src/plan/series.d.ts +5140 -4331
- package/dist/src/plan/series.d.ts.map +1 -1
- package/dist/src/plan/series.js +1026 -264
- package/dist/src/plan/series.js.map +1 -1
- package/dist/src/plan/shade.d.ts +981 -0
- package/dist/src/plan/shade.d.ts.map +1 -0
- package/dist/src/plan/shade.js +60 -0
- package/dist/src/plan/shade.js.map +1 -0
- package/dist/src/plan/types.d.ts +178 -18
- package/dist/src/plan/types.d.ts.map +1 -1
- package/dist/src/plan/types.js +69 -19
- package/dist/src/plan/types.js.map +1 -1
- package/dist/src/query/builder.d.ts +3 -3
- package/dist/src/query/builder.d.ts.map +1 -1
- package/dist/src/query/builder.js +1 -1
- package/dist/src/query/library.d.ts +3 -3
- package/dist/src/query/library.d.ts.map +1 -1
- package/dist/src/query/library.js +1 -1
- package/dist/src/query/queries.d.ts +2 -2
- package/dist/src/query/queries.js +1 -1
- package/dist/src/query/saved.d.ts +8 -4
- package/dist/src/query/saved.d.ts.map +1 -1
- package/dist/src/query/saved.js +8 -4
- package/dist/src/query/saved.js.map +1 -1
- package/dist/src/query/types.d.ts +8 -3
- package/dist/src/query/types.d.ts.map +1 -1
- package/dist/src/query/types.js +8 -3
- package/dist/src/query/types.js.map +1 -1
- package/dist/src/roster/index.d.ts +47 -91
- package/dist/src/roster/index.d.ts.map +1 -1
- package/dist/src/roster/index.js +6 -6
- package/dist/src/roster/index.js.map +1 -1
- package/dist/src/roster/targets.d.ts +8 -4
- package/dist/src/roster/targets.d.ts.map +1 -1
- package/dist/src/roster/targets.js +9 -5
- package/dist/src/roster/targets.js.map +1 -1
- package/dist/src/schedule/cell-checks.js +3 -3
- package/dist/src/schedule/cell-checks.js.map +1 -1
- package/dist/src/schedule/days.d.ts +1 -1
- package/dist/src/schedule/days.js +1 -1
- package/dist/src/schedule/events.d.ts +10 -6
- package/dist/src/schedule/events.d.ts.map +1 -1
- package/dist/src/schedule/events.js +252 -228
- package/dist/src/schedule/events.js.map +1 -1
- package/dist/src/schedule/patch.d.ts +1 -1
- package/dist/src/schedule/patch.js +1 -1
- package/dist/src/schedule/periods.d.ts +8 -4
- package/dist/src/schedule/periods.d.ts.map +1 -1
- package/dist/src/schedule/periods.js +1 -1
- package/dist/src/schedule/periods.js.map +1 -1
- package/dist/src/schedule/record-window.d.ts +38 -58
- package/dist/src/schedule/record-window.d.ts.map +1 -1
- package/dist/src/schedule/record-window.js +15 -16
- package/dist/src/schedule/record-window.js.map +1 -1
- package/dist/src/schedule/resources.d.ts +1 -1
- package/dist/src/schedule/session.d.ts +1 -1
- package/dist/src/schedule/session.js +1 -1
- package/dist/src/schedule/types.d.ts +29 -1
- package/dist/src/schedule/types.d.ts.map +1 -1
- package/dist/src/schedule/types.js +16 -1
- package/dist/src/schedule/types.js.map +1 -1
- package/dist/src/schematic/index.d.ts +19 -9
- package/dist/src/schematic/index.d.ts.map +1 -1
- package/dist/src/schematic/index.js +19 -9
- package/dist/src/schematic/index.js.map +1 -1
- package/dist/src/schematic/kinds.d.ts +4 -4
- package/dist/src/schematic/kinds.js +9 -9
- package/dist/src/schematic/kinds.js.map +1 -1
- package/dist/src/schematic/payload.d.ts +38 -88
- package/dist/src/schematic/payload.d.ts.map +1 -1
- package/dist/src/schematic/payload.js +4 -4
- package/dist/src/schematic/payload.js.map +1 -1
- package/dist/src/schematic/window.d.ts +8 -7
- package/dist/src/schematic/window.d.ts.map +1 -1
- package/dist/src/schematic/window.js +22 -19
- package/dist/src/schematic/window.js.map +1 -1
- package/dist/src/sheet/bridge.d.ts +19 -0
- package/dist/src/sheet/bridge.d.ts.map +1 -1
- package/dist/src/sheet/bridge.js +28 -21
- package/dist/src/sheet/bridge.js.map +1 -1
- package/dist/src/sheet/index.d.ts +66 -46
- package/dist/src/sheet/index.d.ts.map +1 -1
- package/dist/src/sheet/index.js +66 -46
- package/dist/src/sheet/index.js.map +1 -1
- package/dist/src/sheet/library.d.ts +13 -8
- package/dist/src/sheet/library.d.ts.map +1 -1
- package/dist/src/sheet/library.js +13 -8
- package/dist/src/sheet/library.js.map +1 -1
- package/dist/src/sheet/record.d.ts +1 -1
- package/dist/src/sheet/record.js +2 -2
- package/dist/src/sheet/record.js.map +1 -1
- package/dist/src/sheet/registers.d.ts +152 -128
- package/dist/src/sheet/registers.d.ts.map +1 -1
- package/dist/src/sheet/registers.js +152 -128
- package/dist/src/sheet/registers.js.map +1 -1
- package/dist/src/sheet/root.d.ts +4 -1
- package/dist/src/sheet/root.d.ts.map +1 -1
- package/dist/src/sheet/root.js +15 -3
- package/dist/src/sheet/root.js.map +1 -1
- package/dist/src/sheet/sheet.d.ts +39 -113
- package/dist/src/sheet/sheet.d.ts.map +1 -1
- package/dist/src/sheet/sheet.js +7 -5
- package/dist/src/sheet/sheet.js.map +1 -1
- package/dist/src/sheet/sub-rows.d.ts +106 -76
- package/dist/src/sheet/sub-rows.d.ts.map +1 -1
- package/dist/src/sheet/sub-rows.js +106 -76
- package/dist/src/sheet/sub-rows.js.map +1 -1
- package/dist/src/sheet/types.d.ts +19 -62
- package/dist/src/sheet/types.d.ts.map +1 -1
- package/dist/src/studio/builder.d.ts +3 -153
- package/dist/src/studio/builder.d.ts.map +1 -1
- package/dist/src/studio/builder.js +1 -1
- package/dist/src/studio/canvas.d.ts +5 -4
- package/dist/src/studio/canvas.d.ts.map +1 -1
- package/dist/src/studio/canvas.js +8 -7
- package/dist/src/studio/canvas.js.map +1 -1
- package/dist/src/studio/component.d.ts +30 -20
- package/dist/src/studio/component.d.ts.map +1 -1
- package/dist/src/studio/component.js +26 -61
- package/dist/src/studio/component.js.map +1 -1
- package/dist/src/studio/index.d.ts +2 -2
- package/dist/src/studio/index.d.ts.map +1 -1
- package/dist/src/studio/index.js +1 -1
- package/dist/src/studio/index.js.map +1 -1
- package/dist/src/studio/inspector.d.ts +10 -39
- package/dist/src/studio/inspector.d.ts.map +1 -1
- package/dist/src/studio/inspector.js +11 -31
- package/dist/src/studio/inspector.js.map +1 -1
- package/dist/src/studio/library.d.ts +3 -169
- package/dist/src/studio/library.d.ts.map +1 -1
- package/dist/src/studio/library.js +1 -1
- package/dist/src/studio/page.d.ts +0 -8
- package/dist/src/studio/page.d.ts.map +1 -1
- package/dist/src/studio/pages.d.ts +17 -591
- package/dist/src/studio/pages.d.ts.map +1 -1
- package/dist/src/studio/pages.js +17 -44
- package/dist/src/studio/pages.js.map +1 -1
- package/dist/src/studio/palette.d.ts +2 -7
- package/dist/src/studio/palette.d.ts.map +1 -1
- package/dist/src/studio/palette.js +4 -5
- package/dist/src/studio/palette.js.map +1 -1
- package/dist/src/studio/publish.d.ts +8 -18
- package/dist/src/studio/publish.d.ts.map +1 -1
- package/dist/src/studio/publish.js +10 -36
- package/dist/src/studio/publish.js.map +1 -1
- package/dist/src/utils/derive.d.ts +12 -12
- package/dist/src/utils/derive.d.ts.map +1 -1
- package/dist/src/utils/derive.js +5 -2
- package/dist/src/utils/derive.js.map +1 -1
- package/dist/src/utils/ordered-source.d.ts.map +1 -1
- package/dist/src/utils/ordered-source.js +14 -5
- package/dist/src/utils/ordered-source.js.map +1 -1
- package/dist/test/allocation/allocation.examples.d.ts +1900 -1598
- package/dist/test/allocation/allocation.examples.d.ts.map +1 -1
- package/dist/test/allocation/allocation.examples.js +179 -89
- package/dist/test/allocation/allocation.examples.js.map +1 -1
- package/dist/test/allocation/optimise.examples.d.ts.map +1 -1
- package/dist/test/allocation/optimise.examples.js +7 -7
- package/dist/test/allocation/optimise.examples.js.map +1 -1
- package/dist/test/bind/data/data.examples.d.ts +1057 -903
- package/dist/test/bind/data/data.examples.d.ts.map +1 -1
- package/dist/test/bind/data/data.examples.js +260 -204
- package/dist/test/bind/data/data.examples.js.map +1 -1
- package/dist/test/bind/record/record.examples.d.ts +457 -426
- package/dist/test/bind/record/record.examples.d.ts.map +1 -1
- package/dist/test/bind/record/record.examples.js +41 -21
- package/dist/test/bind/record/record.examples.js.map +1 -1
- package/dist/test/calendar/calendar-records.examples.d.ts +162 -170
- package/dist/test/calendar/calendar-records.examples.d.ts.map +1 -1
- package/dist/test/calendar/calendar-records.examples.js +20 -12
- package/dist/test/calendar/calendar-records.examples.js.map +1 -1
- package/dist/test/calendar/calendar-windows.examples.d.ts.map +1 -1
- package/dist/test/calendar/calendar-windows.examples.js +12 -12
- package/dist/test/calendar/calendar-windows.examples.js.map +1 -1
- package/dist/test/calendar/calendar.examples.d.ts +1172 -901
- package/dist/test/calendar/calendar.examples.d.ts.map +1 -1
- package/dist/test/calendar/calendar.examples.js +211 -124
- package/dist/test/calendar/calendar.examples.js.map +1 -1
- package/dist/test/calendar/optimise.examples.js +2 -2
- package/dist/test/calendar/optimise.examples.js.map +1 -1
- package/dist/test/examples-package.d.ts +6 -9
- package/dist/test/examples-package.d.ts.map +1 -1
- package/dist/test/examples-package.js +11 -32
- package/dist/test/examples-package.js.map +1 -1
- package/dist/test/flowchart/flowchart.examples.d.ts +3841 -2820
- package/dist/test/flowchart/flowchart.examples.d.ts.map +1 -1
- package/dist/test/flowchart/flowchart.examples.js +211 -121
- package/dist/test/flowchart/flowchart.examples.js.map +1 -1
- package/dist/test/plan/optimise.examples.js +2 -2
- package/dist/test/plan/optimise.examples.js.map +1 -1
- package/dist/test/plan/plan-events.examples.d.ts +4055 -3263
- package/dist/test/plan/plan-events.examples.d.ts.map +1 -1
- package/dist/test/plan/plan-events.examples.js +492 -319
- package/dist/test/plan/plan-events.examples.js.map +1 -1
- package/dist/test/plan/plan-nested.examples.d.ts +1829 -0
- package/dist/test/plan/plan-nested.examples.d.ts.map +1 -0
- package/dist/test/plan/plan-nested.examples.js +321 -0
- package/dist/test/plan/plan-nested.examples.js.map +1 -0
- package/dist/test/plan/plan-records.examples.d.ts +328 -362
- package/dist/test/plan/plan-records.examples.d.ts.map +1 -1
- package/dist/test/plan/plan-records.examples.js +81 -57
- package/dist/test/plan/plan-records.examples.js.map +1 -1
- package/dist/test/plan/plan-windows.examples.d.ts +1756 -1398
- package/dist/test/plan/plan-windows.examples.d.ts.map +1 -1
- package/dist/test/plan/plan-windows.examples.js +161 -128
- package/dist/test/plan/plan-windows.examples.js.map +1 -1
- package/dist/test/plan/plan.examples.d.ts +2238 -5712
- package/dist/test/plan/plan.examples.d.ts.map +1 -1
- package/dist/test/plan/plan.examples.js +839 -968
- package/dist/test/plan/plan.examples.js.map +1 -1
- package/dist/test/query/query.examples.d.ts +5221 -3005
- package/dist/test/query/query.examples.d.ts.map +1 -1
- package/dist/test/query/query.examples.js +169 -118
- package/dist/test/query/query.examples.js.map +1 -1
- package/dist/test/roster/optimise.examples.d.ts.map +1 -1
- package/dist/test/roster/optimise.examples.js +7 -7
- package/dist/test/roster/optimise.examples.js.map +1 -1
- package/dist/test/roster/roster-records.examples.d.ts +364 -392
- package/dist/test/roster/roster-records.examples.d.ts.map +1 -1
- package/dist/test/roster/roster-records.examples.js +40 -26
- package/dist/test/roster/roster-records.examples.js.map +1 -1
- package/dist/test/roster/roster.examples.d.ts +4605 -4308
- package/dist/test/roster/roster.examples.d.ts.map +1 -1
- package/dist/test/roster/roster.examples.js +212 -113
- package/dist/test/roster/roster.examples.js.map +1 -1
- package/dist/test/schedule/schedule.examples.d.ts +2646 -2471
- package/dist/test/schedule/schedule.examples.d.ts.map +1 -1
- package/dist/test/schedule/schedule.examples.js +144 -88
- package/dist/test/schedule/schedule.examples.js.map +1 -1
- package/dist/test/schematic/schematic-records.examples.d.ts +286 -276
- package/dist/test/schematic/schematic-records.examples.d.ts.map +1 -1
- package/dist/test/schematic/schematic-records.examples.js +52 -38
- package/dist/test/schematic/schematic-records.examples.js.map +1 -1
- package/dist/test/schematic/schematic.examples.d.ts +1842 -1491
- package/dist/test/schematic/schematic.examples.d.ts.map +1 -1
- package/dist/test/schematic/schematic.examples.js +240 -134
- package/dist/test/schematic/schematic.examples.js.map +1 -1
- package/dist/test/sheet/optimise.examples.d.ts +102 -95
- package/dist/test/sheet/optimise.examples.d.ts.map +1 -1
- package/dist/test/sheet/optimise.examples.js +7 -3
- package/dist/test/sheet/optimise.examples.js.map +1 -1
- package/dist/test/sheet/sheet-records.examples.d.ts +175 -214
- package/dist/test/sheet/sheet-records.examples.d.ts.map +1 -1
- package/dist/test/sheet/sheet-records.examples.js +29 -17
- package/dist/test/sheet/sheet-records.examples.js.map +1 -1
- package/dist/test/sheet/sheet.examples.d.ts +3642 -3353
- package/dist/test/sheet/sheet.examples.d.ts.map +1 -1
- package/dist/test/sheet/sheet.examples.js +389 -282
- package/dist/test/sheet/sheet.examples.js.map +1 -1
- package/dist/test/studio/component.examples.js +2 -2
- package/dist/test/studio/component.examples.js.map +1 -1
- package/dist/test/studio/page.examples.js +6 -6
- package/dist/test/studio/page.examples.js.map +1 -1
- package/dist/test/studio/studio.examples.d.ts +2329 -873
- package/dist/test/studio/studio.examples.d.ts.map +1 -1
- package/dist/test/studio/studio.examples.js +155 -107
- package/dist/test/studio/studio.examples.js.map +1 -1
- package/package.json +15 -11
package/dist/src/plan/series.js
CHANGED
|
@@ -34,11 +34,12 @@
|
|
|
34
34
|
*
|
|
35
35
|
* @packageDocumentation
|
|
36
36
|
*/
|
|
37
|
-
import { East, Expr, ArrayType, BlobType, BooleanType, DateTimeType, DictType, EastTypeType, FloatType, FunctionType, IntegerType, OptionType, RefType, StringType, StructType, VariantType, isTypeEqual, printFor, toEastTypeValue, variant, some, none, } from "@elaraai/east";
|
|
37
|
+
import { East, Expr, ArrayType, BlobType, BooleanType, DateTimeType, DictType, EastTypeType, FloatType, FunctionType, IntegerType, NullType, OptionType, RefType, StringType, StructType, VariantType, isTypeEqual, isVariant, printFor, printTypeSummary, toEastTypeValue, variant, some, none, } from "@elaraai/east";
|
|
38
38
|
import { IconType, StatusValueType } from "@elaraai/east-ui";
|
|
39
39
|
import { TableAggregateType, TickFormatType, reifyAccessor, resolveTag } from "@elaraai/east-ui/internal";
|
|
40
|
-
import { PlanAggregateType, PlanExpandType, PlanGroupSummaryType, PlanHeatCellsType, PlanLaneType, PlanBucketEventType, PlanCellMarkerType, PlanChipType, PlanEventMarkType, PlanRunType, PlanDecisionMarkType, PlanPortType, PlanRollupType, PlanRowIdType, PlanRowKindType, PlanRowType, PlanRowsCollectionType, PlanBlockType, PlanBlocksType, PlanGestureType, PlanDropType, PlanInstantType, } from "./types.js";
|
|
41
|
-
import { resolveIcon } from "./builders.js";
|
|
40
|
+
import { PlanAggregateType, PlanExpandType, PlanGroupSummaryType, PlanHeatCellsType, PlanLaneType, PlanBucketEventType, PlanCellMarkerType, PlanChipType, PlanEventMarkType, PlanRunType, PlanDecisionMarkType, PlanPortType, PlanRollupType, PlanRowIdType, PlanRowKindType, PlanRowType, PlanRowsCollectionType, PlanBlockType, PlanBlocksType, PlanGestureType, PlanDropType, PlanInstantType, PlanFoldType, } from "./types.js";
|
|
41
|
+
import { PLAN_LOOKS, resolveIcon, resolveInstant, } from "./builders.js";
|
|
42
|
+
import { PlanReadsBrand, PlanUseBrand, planReadOf, planUseOf, } from "./reads.js";
|
|
42
43
|
import { planRow, planGutter, normalizeRows, REBASE_ROWS } from "./assemble.js";
|
|
43
44
|
import { spanKind, bucketsKind, chartKind, heatKind, tableKind, cardsKind, eventsKind, groupKind, groupSummary, } from "./factories.js";
|
|
44
45
|
// ============================================================================
|
|
@@ -106,15 +107,35 @@ export const PlanSeriesType = seriesShape;
|
|
|
106
107
|
/** Marks a {@link PlanChildren} step-down. */
|
|
107
108
|
const PLAN_CHILDREN = Symbol("PlanChildren");
|
|
108
109
|
/**
|
|
109
|
-
* Step down from an entry to
|
|
110
|
+
* Step down from an entry to its child rows (#822, #1436): a `Dict` field of
|
|
111
|
+
* the entry, keyed by an id, its rows laid out by `series` like a top-level
|
|
112
|
+
* list.
|
|
110
113
|
*
|
|
111
114
|
* @typeParam R - The parent entry type (inferred from where the step-down is used)
|
|
112
|
-
* @typeParam
|
|
113
|
-
* @
|
|
114
|
-
* @param
|
|
115
|
+
* @typeParam F - The field holding the child rows
|
|
116
|
+
* @typeParam S - The series over them, as written
|
|
117
|
+
* @param field - A Dict field of the entry holding the child rows, keyed by an id
|
|
118
|
+
* @param series - The series over the child rows
|
|
115
119
|
* @returns The step-down, for a series' `children`
|
|
116
120
|
*/
|
|
117
|
-
export function createChildren(
|
|
121
|
+
export function createChildren(field, series) {
|
|
122
|
+
if (typeof field !== "string") {
|
|
123
|
+
throw new Error("Plan.children: name the field holding the child rows — a Dict of the row, keyed by an id: Plan.children(\"members\", [series…])");
|
|
124
|
+
}
|
|
125
|
+
return { [PLAN_CHILDREN]: true, field, series };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Step down from an entry to a collection an accessor computes (#1194) — the
|
|
129
|
+
* event kinds' own: a resource's events come from another record, so its
|
|
130
|
+
* child rows are no field of it. An author's step-down names its field
|
|
131
|
+
* ({@link createChildren}).
|
|
132
|
+
*
|
|
133
|
+
* @internal
|
|
134
|
+
* @param of - The child collection, from the parent entry's value and key
|
|
135
|
+
* @param series - The series over the child entries
|
|
136
|
+
* @returns The step-down
|
|
137
|
+
*/
|
|
138
|
+
export function createChildrenOf(of, series) {
|
|
118
139
|
return { [PLAN_CHILDREN]: true, of, series };
|
|
119
140
|
}
|
|
120
141
|
function isStepDown(x) {
|
|
@@ -127,6 +148,36 @@ const PathType = ArrayType(StringType);
|
|
|
127
148
|
const IdOptType = OptionType(PlanRowIdType);
|
|
128
149
|
/** Every series value a builder made, with its build spec. */
|
|
129
150
|
const SPECS = new WeakMap();
|
|
151
|
+
/** A spec's facts for the nested checks (#1435). */
|
|
152
|
+
function nestFactsOf(spec) {
|
|
153
|
+
return { key: spec.key, arm: spec.arm, reads: spec.reads, nest: spec.nest };
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The facts a Plan holds to its rows' nested series index (#1435), of a
|
|
157
|
+
* series value a `Plan.series.*` builder made, and of each series its spec
|
|
158
|
+
* nests: `facts(spec)` for any of them.
|
|
159
|
+
*
|
|
160
|
+
* @param series - The series value
|
|
161
|
+
* @returns Its facts; `undefined` for a value no builder made in place (a bound, picked or stored one)
|
|
162
|
+
* @internal
|
|
163
|
+
*/
|
|
164
|
+
export function planSeriesNestOf(series) {
|
|
165
|
+
const spec = specOf(series);
|
|
166
|
+
return spec === undefined ? undefined : nestFactsOf(spec);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The facts of the series a spec nests (#1435), as {@link planSeriesNestOf} gives them.
|
|
170
|
+
*
|
|
171
|
+
* @param nest - A series' nest
|
|
172
|
+
* @returns Its step-downs' series and its members, each with its facts
|
|
173
|
+
* @internal
|
|
174
|
+
*/
|
|
175
|
+
export function planNestedSeriesFacts(nest) {
|
|
176
|
+
return {
|
|
177
|
+
steps: nest.steps.map((step) => ({ field: step.field, of: step.of, series: step.series.map(nestFactsOf) })),
|
|
178
|
+
members: nest.members.map(nestFactsOf),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
130
181
|
/** The spec of a series value a `Plan.series.*` builder made, if it was one. */
|
|
131
182
|
function specOf(s) {
|
|
132
183
|
return typeof s === "object" && s !== null ? SPECS.get(s) : undefined;
|
|
@@ -308,85 +359,34 @@ function collectionOps(collection) {
|
|
|
308
359
|
put: (c, k, v) => array(c).update(index(k), v),
|
|
309
360
|
};
|
|
310
361
|
}
|
|
311
|
-
/** The
|
|
312
|
-
|
|
313
|
-
return arm === "span" || arm === "cards" ? "span" : "point";
|
|
314
|
-
}
|
|
315
|
-
/** The item field types a move can write an instant into (#825). */
|
|
316
|
-
const INSTANT_FIELD_TYPES = [DateTimeType, FloatType, IntegerType, StringType, PlanInstantType];
|
|
362
|
+
/** The item field types a move can write an instant into (#825, #1436). */
|
|
363
|
+
const INSTANT_FIELD_TYPES = [DateTimeType, FloatType, IntegerType, StringType, PlanInstantType, OptionType(DateTimeType)];
|
|
317
364
|
/** Print an item type as the text rows of one item type share (#825) — East's own printer over its type value. */
|
|
318
365
|
const printType = printFor(EastTypeType);
|
|
319
366
|
/**
|
|
320
|
-
*
|
|
321
|
-
*
|
|
367
|
+
* What a move writes, from the read that names the collection (#1436) — its
|
|
368
|
+
* times, checked against the item type by the read's build.
|
|
322
369
|
*
|
|
323
|
-
* @param
|
|
324
|
-
* @param
|
|
325
|
-
* @param arm - The series' kind
|
|
326
|
-
* @param where - The series, for the message
|
|
370
|
+
* @param read - The elements' read
|
|
371
|
+
* @param item - The collection's item type
|
|
327
372
|
* @returns The move's fields
|
|
328
|
-
* @throws {Error} Naming the field and what is wrong with it
|
|
329
373
|
*/
|
|
330
|
-
function moveFieldsOf(
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
const start = shape === "span" ? edit.start : edit.at;
|
|
339
|
-
const end = shape === "span" ? edit.end : undefined;
|
|
340
|
-
if (shape === "span" && (start === undefined) !== (end === undefined)) {
|
|
341
|
-
throw new Error(`${where}: a move writes both ends of a ${arm} element — give \`edit.start\` and \`edit.end\` together`);
|
|
342
|
-
}
|
|
343
|
-
if (start === undefined) {
|
|
344
|
-
if (edit.create === undefined) {
|
|
345
|
-
throw new Error(`${where}: \`edit\` declares neither \`create\` (what a dropped card becomes) nor the item fields a move ` +
|
|
346
|
-
`writes (\`key\` and ${shape === "span" ? "`start` / `end`" : "`at`"}) — declare one or both`);
|
|
347
|
-
}
|
|
348
|
-
return undefined;
|
|
349
|
-
}
|
|
350
|
-
const item = itemType;
|
|
351
|
-
if (item.type !== "Struct") {
|
|
352
|
-
throw new Error(`${where}: a move writes fields of the item — the items in "${edit.items}" must be structs`);
|
|
353
|
-
}
|
|
354
|
-
if (edit.key === undefined) {
|
|
355
|
-
throw new Error(`${where}: a move finds its element's item by key — name the item's String key field with \`edit.key\``);
|
|
356
|
-
}
|
|
357
|
-
const keyType = item.fields[edit.key];
|
|
358
|
-
if (keyType === undefined || !isTypeEqual(keyType, StringType)) {
|
|
359
|
-
throw new Error(`${where}: \`edit.key\` names "${edit.key}", which is not a String field of the item`);
|
|
360
|
-
}
|
|
361
|
-
const instant = (name, what) => {
|
|
362
|
-
const type = item.fields[name];
|
|
363
|
-
if (type === undefined || !INSTANT_FIELD_TYPES.some((t) => isTypeEqual(t, type))) {
|
|
364
|
-
throw new Error(`${where}: \`edit.${what}\` names "${name}", which is not an instant field of the item — a DateTime, a Float or ` +
|
|
365
|
-
"an Integer, a String, or a Plan.Types.Instant");
|
|
366
|
-
}
|
|
367
|
-
if (name === edit.key) {
|
|
368
|
-
throw new Error(`${where}: \`edit.${what}\` names the key field "${name}" — a move would change the item's identity; name its instant field`);
|
|
369
|
-
}
|
|
370
|
-
return { name, type };
|
|
371
|
-
};
|
|
372
|
-
if (end !== undefined && end === start) {
|
|
373
|
-
throw new Error(`${where}: \`edit.start\` and \`edit.end\` both name "${start}" — a move writes two different fields`);
|
|
374
|
-
}
|
|
375
|
-
return {
|
|
376
|
-
item,
|
|
377
|
-
key: edit.key,
|
|
378
|
-
start: instant(start, shape === "span" ? "start" : "at"),
|
|
379
|
-
end: end !== undefined ? instant(end, "end") : undefined,
|
|
380
|
-
edits: { items: printType(toEastTypeValue(itemType)), resize: shape === "span" },
|
|
381
|
-
};
|
|
374
|
+
function moveFieldsOf(read, item) {
|
|
375
|
+
const field = (name) => ({ name, type: item.fields[name] });
|
|
376
|
+
const times = read.times;
|
|
377
|
+
const edits = { items: printType(toEastTypeValue(item)), resize: !("at" in times) };
|
|
378
|
+
return "at" in times
|
|
379
|
+
? { item, start: field(times.at), end: undefined, edits }
|
|
380
|
+
: { item, start: field(times.from), end: field(times.to), edits };
|
|
382
381
|
}
|
|
383
382
|
/** Each instant field type's converter, built once (#825). */
|
|
384
383
|
const instantConverters = new ByType();
|
|
385
384
|
/**
|
|
386
385
|
* The converter from an axis instant into an item field's type (#825): the
|
|
387
|
-
* instant on the arm the field holds — a time instant's DateTime
|
|
388
|
-
*
|
|
389
|
-
* ordinal instant's String, or the
|
|
386
|
+
* instant on the arm the field holds — a time instant's DateTime (an `Option`
|
|
387
|
+
* of one takes it as `some`, #1436), a number instant's Float (an Integer field
|
|
388
|
+
* takes it rounded, half away from zero), an ordinal instant's String, or the
|
|
389
|
+
* instant itself — else `none`.
|
|
390
390
|
*
|
|
391
391
|
* @param fieldType - The field's type (one of {@link INSTANT_FIELD_TYPES})
|
|
392
392
|
* @returns `(instant) → Option<field>`
|
|
@@ -396,6 +396,7 @@ function instantInto(fieldType) {
|
|
|
396
396
|
const out = OptionType(fieldType);
|
|
397
397
|
const kind = fieldType.type;
|
|
398
398
|
const whole = isTypeEqual(fieldType, PlanInstantType);
|
|
399
|
+
const optional = isTypeEqual(fieldType, OptionType(DateTimeType));
|
|
399
400
|
return East.function([PlanInstantType], out, ($, t) => {
|
|
400
401
|
const result = $.let(none, out);
|
|
401
402
|
if (whole) {
|
|
@@ -406,6 +407,9 @@ function instantInto(fieldType) {
|
|
|
406
407
|
time: ($2, d) => {
|
|
407
408
|
if (kind === "DateTime")
|
|
408
409
|
$2.assign(result, some(d));
|
|
410
|
+
// An Option of a DateTime holds the instant as `some` (#1436).
|
|
411
|
+
if (optional)
|
|
412
|
+
$2.assign(result, some(some(d)));
|
|
409
413
|
},
|
|
410
414
|
number: ($2, n) => {
|
|
411
415
|
if (kind === "Float") {
|
|
@@ -425,6 +429,570 @@ function instantInto(fieldType) {
|
|
|
425
429
|
});
|
|
426
430
|
});
|
|
427
431
|
}
|
|
432
|
+
/** An East type's name as a message reads it: `an Array`, `a Dict`. */
|
|
433
|
+
function anOf(type) {
|
|
434
|
+
const name = type.type;
|
|
435
|
+
return /^[AEIOU]/.test(name) ? `an ${name}` : `a ${name}`;
|
|
436
|
+
}
|
|
437
|
+
/** A row type's shape as a message reads it: its fields, `{ name, load }`, or East's summary of another type. */
|
|
438
|
+
function shapeName(type) {
|
|
439
|
+
const t = type;
|
|
440
|
+
if (t.type === "Recursive" && t.node !== undefined)
|
|
441
|
+
return shapeName(t.node);
|
|
442
|
+
if (t.type === "Struct" && t.fields !== undefined)
|
|
443
|
+
return `{ ${Object.keys(t.fields).join(", ")} }`;
|
|
444
|
+
return printTypeSummary(type);
|
|
445
|
+
}
|
|
446
|
+
/** The fields of an entry type — a struct's own, or a recursive entry's node's. */
|
|
447
|
+
function entryFieldsOf(rowType) {
|
|
448
|
+
const t = rowType;
|
|
449
|
+
if (t.type === "Struct")
|
|
450
|
+
return t.fields;
|
|
451
|
+
if (t.type === "Recursive" && t.node?.type === "Struct")
|
|
452
|
+
return t.node.fields;
|
|
453
|
+
return undefined;
|
|
454
|
+
}
|
|
455
|
+
/** The key types a values collection may be keyed by: its instants. */
|
|
456
|
+
const VALUE_KEY_TYPES = [DateTimeType, FloatType, IntegerType, StringType];
|
|
457
|
+
/**
|
|
458
|
+
* The collection a read names, checked against the entry type (#1436).
|
|
459
|
+
*
|
|
460
|
+
* @param rowType - The entry type
|
|
461
|
+
* @param read - The read
|
|
462
|
+
* @param where - The series, for the messages
|
|
463
|
+
* @returns The collection's shape
|
|
464
|
+
* @throws {Error} In the refusals' words: no Dict field of the row, an items collection keyed by an instant or holding no
|
|
465
|
+
* struct, a time that is no instant field of its items, a values collection keyed by no instant
|
|
466
|
+
*/
|
|
467
|
+
function collectionOfRead(rowType, read, where) {
|
|
468
|
+
const what = read.kind === "items" ? "Plan.items" : "Plan.values";
|
|
469
|
+
const fields = entryFieldsOf(rowType);
|
|
470
|
+
const t = fields?.[read.field];
|
|
471
|
+
if (t === undefined) {
|
|
472
|
+
throw new Error(`${where}: ${what} reads "${read.field}", which is no field of its rows — name a Dict field of the row`);
|
|
473
|
+
}
|
|
474
|
+
if (t.type !== "Dict") {
|
|
475
|
+
throw new Error(`${what}: "${read.field}" is ${anOf(t)} — a collection a series reads by name is a Dict, keyed by an id, ` +
|
|
476
|
+
"or by its instant for values");
|
|
477
|
+
}
|
|
478
|
+
const key = t.key;
|
|
479
|
+
const item = t.value;
|
|
480
|
+
const keyName = key.type;
|
|
481
|
+
if (read.kind === "values") {
|
|
482
|
+
if (!VALUE_KEY_TYPES.some((k) => isTypeEqual(k, key))) {
|
|
483
|
+
throw new Error(`Plan.values: "${read.field}" is keyed by ${keyName} — values are keyed by their instant: a DateTime, a Float, ` +
|
|
484
|
+
"an Integer or a String");
|
|
485
|
+
}
|
|
486
|
+
return { dict: t, key, item };
|
|
487
|
+
}
|
|
488
|
+
if (!isTypeEqual(key, StringType)) {
|
|
489
|
+
throw new Error(`Plan.items: "${read.field}" is keyed by ${keyName} — things people drag are keyed by an id, a String: read values ` +
|
|
490
|
+
`with Plan.values("${read.field}", draw)`);
|
|
491
|
+
}
|
|
492
|
+
const struct = item;
|
|
493
|
+
if (struct.type !== "Struct" || struct.fields === undefined) {
|
|
494
|
+
throw new Error(`Plan.items: "${read.field}" holds ${anOf(item)}, not an item — things people drag are structs, their times fields ` +
|
|
495
|
+
`of them: read values with Plan.values("${read.field}", draw)`);
|
|
496
|
+
}
|
|
497
|
+
const names = "at" in read.times ? [read.times.at] : [read.times.from, read.times.to];
|
|
498
|
+
if (names.length === 2 && names[0] === names[1]) {
|
|
499
|
+
throw new Error(`Plan.items: "${read.field}" names "${names[0]}" as both its from and its to — a run's start and end are two fields of its items`);
|
|
500
|
+
}
|
|
501
|
+
for (const name of names) {
|
|
502
|
+
const ft = struct.fields[name];
|
|
503
|
+
if (ft === undefined) {
|
|
504
|
+
throw new Error(`Plan.items: "${read.field}" names its times by "${name}", which is no field of its items — name a time field of the item`);
|
|
505
|
+
}
|
|
506
|
+
if (!INSTANT_FIELD_TYPES.some((k) => isTypeEqual(k, ft))) {
|
|
507
|
+
throw new Error(`Plan.items: "${read.field}" names "${name}", ${anOf(ft)} field of its items, as a time — a time is a DateTime or an ` +
|
|
508
|
+
"Option of one, a Float, an Integer, a String, or a Plan.Types.Instant");
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return { dict: t, key, item };
|
|
512
|
+
}
|
|
513
|
+
/** Each element's look type: its type without its key and its instants. */
|
|
514
|
+
const lookTypes = new Map();
|
|
515
|
+
/** What `draw` gives for an element of `kind`, as an East struct: the element without its key and its instants. */
|
|
516
|
+
function lookTypeOf(kind) {
|
|
517
|
+
let type = lookTypes.get(kind);
|
|
518
|
+
if (type === undefined) {
|
|
519
|
+
const spec = PLAN_LOOKS[kind];
|
|
520
|
+
const omit = [...(spec.key !== undefined ? [spec.key] : []), ...spec.instants];
|
|
521
|
+
type = StructType(Object.fromEntries(Object.entries(spec.type.fields).filter(([f]) => !omit.includes(f))));
|
|
522
|
+
lookTypes.set(kind, type);
|
|
523
|
+
}
|
|
524
|
+
return type;
|
|
525
|
+
}
|
|
526
|
+
/** What each element kind is called where a refusal names it. */
|
|
527
|
+
const ELEMENT_NAMES = {
|
|
528
|
+
run: "runs", decision: "decisions", port: "ports", event: "events", marker: "markers", chip: "chips", mark: "marks",
|
|
529
|
+
heat: "heat cells", weight: "weight cells", segment: "segment cells", table: "table cells", point: "points", band: "band points",
|
|
530
|
+
};
|
|
531
|
+
/** Each read's builds, by what it completes and the entry type. */
|
|
532
|
+
const readBuilds = new WeakMap();
|
|
533
|
+
/**
|
|
534
|
+
* A read built for one entry type (#1436): `(collection, entry) → elements` —
|
|
535
|
+
* each item an element at its times, keyed by its id, or each value one at its
|
|
536
|
+
* instant, its look `draw`'s. The collection is an argument, so the same build
|
|
537
|
+
* draws an inline row's own collection and one read a cell at a time.
|
|
538
|
+
*
|
|
539
|
+
* @param rowType - The entry type
|
|
540
|
+
* @param read - The read
|
|
541
|
+
* @param kind - What each item or value completes to
|
|
542
|
+
* @param where - The series, for messages
|
|
543
|
+
* @returns The built function
|
|
544
|
+
* @throws {Error} When the read does not build for the entry type, in the refusals' words
|
|
545
|
+
*/
|
|
546
|
+
function buildRead(rowType, read, kind, where) {
|
|
547
|
+
let byKind = readBuilds.get(read);
|
|
548
|
+
if (byKind === undefined) {
|
|
549
|
+
byKind = new Map();
|
|
550
|
+
readBuilds.set(read, byKind);
|
|
551
|
+
}
|
|
552
|
+
let byType = byKind.get(kind);
|
|
553
|
+
if (byType === undefined) {
|
|
554
|
+
byType = new ByType();
|
|
555
|
+
byKind.set(kind, byType);
|
|
556
|
+
}
|
|
557
|
+
return byType.get(rowType, () => {
|
|
558
|
+
const shape = collectionOfRead(rowType, read, where);
|
|
559
|
+
const spec = PLAN_LOOKS[kind];
|
|
560
|
+
const elemType = spec.type;
|
|
561
|
+
const lookType = lookTypeOf(kind);
|
|
562
|
+
const recursive = rowType.type === "Recursive";
|
|
563
|
+
const what = read.kind === "items" ? `Plan.items("${read.field}", …)` : `Plan.values("${read.field}", …)`;
|
|
564
|
+
// The look, reified once: `(item or value, id or instant, entry) → look`.
|
|
565
|
+
const look = (() => {
|
|
566
|
+
try {
|
|
567
|
+
return East.function([shape.item, shape.key, rowType], lookType, ($, item, key, row) => {
|
|
568
|
+
const node = recursive ? $.let(row.unwrap()) : row;
|
|
569
|
+
const given = read.draw(item, key, node);
|
|
570
|
+
if (given === null || typeof given !== "object" || given instanceof Expr) {
|
|
571
|
+
throw new Error(`its draw gives the look of each of its ${ELEMENT_NAMES[kind]}, an object of their fields`);
|
|
572
|
+
}
|
|
573
|
+
return East.value(spec.look(given), lookType);
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
catch (err) {
|
|
577
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
578
|
+
throw new Error(`${where}: ${what} does not build for its rows (${reason})`);
|
|
579
|
+
}
|
|
580
|
+
})();
|
|
581
|
+
const fieldsOf = (l) => Object.fromEntries(Object.keys(lookType.fields).map((f) => [f, l[f]]));
|
|
582
|
+
const out = ArrayType(elemType);
|
|
583
|
+
if (read.kind === "values") {
|
|
584
|
+
const at = spec.instants[0];
|
|
585
|
+
return East.function([shape.dict, rowType], out, ($, coll, row) => {
|
|
586
|
+
const draw = $.const(look);
|
|
587
|
+
return coll.toArray(($2, value, key) => {
|
|
588
|
+
const l = $2.let(draw(value, key, row));
|
|
589
|
+
return East.value({ [at]: resolveInstant(key, `${what} key`), ...fieldsOf(l) }, elemType);
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
// Things people drag: each at its named times, keyed by its id. An item whose
|
|
594
|
+
// start is an Option holding none is drawn nowhere; one whose end holds none is
|
|
595
|
+
// an instant (#1434's filing).
|
|
596
|
+
const item = shape.item;
|
|
597
|
+
const times = read.times;
|
|
598
|
+
const names = "at" in times ? [times.at] : [times.from, times.to];
|
|
599
|
+
if (names.length !== spec.instants.length) {
|
|
600
|
+
throw new Error(spec.instants.length === 2
|
|
601
|
+
? `${where}: its ${ELEMENT_NAMES[kind]} run from a start to an end — read them with Plan.items("${read.field}", { from, to }, draw)`
|
|
602
|
+
: `${where}: its ${ELEMENT_NAMES[kind]} sit at an instant — read them with Plan.items("${read.field}", { at }, draw)`);
|
|
603
|
+
}
|
|
604
|
+
const optional = (name) => isTypeEqual(item.fields[name], OptionType(DateTimeType));
|
|
605
|
+
const instantOf = (v) => resolveInstant(v, `${what} times`);
|
|
606
|
+
const keyField = spec.key;
|
|
607
|
+
return East.function([shape.dict, rowType], out, ($, coll, row) => {
|
|
608
|
+
const draw = $.const(look);
|
|
609
|
+
const result = $.let([], out);
|
|
610
|
+
$.for(coll, ($2, value, key) => {
|
|
611
|
+
const fields = value;
|
|
612
|
+
const id = key;
|
|
613
|
+
const emit = ($3, instants) => {
|
|
614
|
+
const l = $3.let(draw(value, id, row));
|
|
615
|
+
$3(result.pushLast(East.value({
|
|
616
|
+
[keyField]: id,
|
|
617
|
+
...Object.fromEntries(spec.instants.map((name, i) => [name, instants[i]])),
|
|
618
|
+
...fieldsOf(l),
|
|
619
|
+
}, elemType)));
|
|
620
|
+
};
|
|
621
|
+
const withStart = ($3, start) => {
|
|
622
|
+
if (names.length === 1) {
|
|
623
|
+
emit($3, [start]);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
const to = names[1];
|
|
627
|
+
if (!optional(to)) {
|
|
628
|
+
emit($3, [start, instantOf(fields[to])]);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
const end = $3.let(fields[to].match({
|
|
632
|
+
some: (_$4, d) => East.value(variant("time", d), PlanInstantType),
|
|
633
|
+
none: () => start,
|
|
634
|
+
}), PlanInstantType);
|
|
635
|
+
emit($3, [start, end]);
|
|
636
|
+
};
|
|
637
|
+
const from = names[0];
|
|
638
|
+
if (!optional(from)) {
|
|
639
|
+
withStart($2, $2.let(instantOf(fields[from]), PlanInstantType));
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
$2.match(fields[from], {
|
|
643
|
+
some: ($3, d) => withStart($3, $3.let(East.value(variant("time", d), PlanInstantType), PlanInstantType)),
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
return result;
|
|
647
|
+
});
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
/** A collection field of the entry, for one row: the field read off its node. */
|
|
651
|
+
function collectionOn(node, field) {
|
|
652
|
+
return node[field];
|
|
653
|
+
}
|
|
654
|
+
/** The record of a read a series makes. */
|
|
655
|
+
function readRecord(rowType, read, via, where) {
|
|
656
|
+
return { field: read.field, read, key: collectionOfRead(rowType, read, where).key, via };
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* An element field of a series config, resolved for its entry type (#1436): a
|
|
660
|
+
* function of the row as before, or a read — of items for the things people
|
|
661
|
+
* drag (runs, decisions, tiles, chips, marks), of values for ports and
|
|
662
|
+
* markers.
|
|
663
|
+
*
|
|
664
|
+
* @param rowType - The entry type
|
|
665
|
+
* @param input - The field as given
|
|
666
|
+
* @param kind - What each element is
|
|
667
|
+
* @param where - The series, for messages
|
|
668
|
+
* @param via - The field's name
|
|
669
|
+
* @returns The field, or `undefined` when it is not given
|
|
670
|
+
* @throws {Error} When a read of the other sort, or a consumer, is given
|
|
671
|
+
*/
|
|
672
|
+
function elementField(rowType, input, kind, where, via) {
|
|
673
|
+
if (input === undefined)
|
|
674
|
+
return undefined;
|
|
675
|
+
if (typeof input === "function") {
|
|
676
|
+
const accessor = input;
|
|
677
|
+
return { at: (node, key) => accessor(node, key), reads: [] };
|
|
678
|
+
}
|
|
679
|
+
const read = planReadOf(input);
|
|
680
|
+
if (read === undefined) {
|
|
681
|
+
throw new Error(`${where}: ${via} is a function of the row, or a read of its collection — ${PLAN_LOOKS[kind].key !== undefined ? "Plan.items(field, times, draw)" : "Plan.values(field, draw)"}`);
|
|
682
|
+
}
|
|
683
|
+
const keyed = PLAN_LOOKS[kind].key !== undefined;
|
|
684
|
+
if (keyed && read.kind !== "items") {
|
|
685
|
+
throw new Error(`${where}: its ${via} are things people drag, keyed by an id — read them with Plan.items(field, ${PLAN_LOOKS[kind].instants.length === 2 ? "{ from, to }" : "{ at }"}, draw)`);
|
|
686
|
+
}
|
|
687
|
+
if (!keyed && read.kind !== "values") {
|
|
688
|
+
throw new Error(`${where}: its ${via} have no id of their own, so each is keyed by its instant — read them with Plan.values(field, draw)`);
|
|
689
|
+
}
|
|
690
|
+
const built = buildRead(rowType, read, kind, where);
|
|
691
|
+
return { at: (node, _key, value) => built(collectionOn(node, read.field), value), reads: [readRecord(rowType, read, via, where)] };
|
|
692
|
+
}
|
|
693
|
+
/** What each consumer is, where a refusal names what a field takes. */
|
|
694
|
+
const CONSUMER_HINTS = {
|
|
695
|
+
heat: "Plan.heatCells, Plan.weightCells or Plan.segmentCells over Plan.values(field, draw)",
|
|
696
|
+
tableCells: "Plan.tableCells(Plan.values(field, draw))",
|
|
697
|
+
tableSeries: "Plan.tableSeries({ cells: Plan.tableCells(Plan.values(field, draw)) })",
|
|
698
|
+
layer: "Plan.line, Plan.area, Plan.column, Plan.scatter or Plan.band over Plan.values(field, draw)",
|
|
699
|
+
};
|
|
700
|
+
/**
|
|
701
|
+
* A consumer of a values read, resolved for the entry type (#1436): the row's
|
|
702
|
+
* values completed into its cells, and what the consumer makes of them.
|
|
703
|
+
*
|
|
704
|
+
* @param rowType - The entry type
|
|
705
|
+
* @param use - The consumer
|
|
706
|
+
* @param makes - What the field takes
|
|
707
|
+
* @param where - The series, for messages
|
|
708
|
+
* @param via - The field's name
|
|
709
|
+
* @returns The field
|
|
710
|
+
*/
|
|
711
|
+
function useField(rowType, use, makes, where, via) {
|
|
712
|
+
if (use.makes !== makes)
|
|
713
|
+
throw new Error(`${where}: ${via} takes ${CONSUMER_HINTS[makes]}`);
|
|
714
|
+
const built = buildRead(rowType, use.read, use.cell, where);
|
|
715
|
+
// The consumer's fold and flag beside the read (#1435): what a parent subtotals its values by.
|
|
716
|
+
const read = { ...readRecord(rowType, use.read, via, where), ...(use.fold !== undefined ? { fold: use.fold } : {}), flagged: use.rollup };
|
|
717
|
+
return {
|
|
718
|
+
at: (node, _key, value) => use.wrap(built(collectionOn(node, use.read.field), value)),
|
|
719
|
+
reads: [read],
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* A field whose value is one consumer's (cells, a group's summary) resolved
|
|
724
|
+
* for the entry type (#1436): a function of the row as before, or a consumer
|
|
725
|
+
* of a values read.
|
|
726
|
+
*
|
|
727
|
+
* @param rowType - The entry type
|
|
728
|
+
* @param input - The field as given
|
|
729
|
+
* @param makes - What it takes
|
|
730
|
+
* @param where - The series, for messages
|
|
731
|
+
* @param via - The field's name
|
|
732
|
+
* @returns The field, or `undefined` when it is not given
|
|
733
|
+
*/
|
|
734
|
+
function consumerField(rowType, input, makes, where, via) {
|
|
735
|
+
if (input === undefined)
|
|
736
|
+
return undefined;
|
|
737
|
+
if (typeof input === "function") {
|
|
738
|
+
const accessor = input;
|
|
739
|
+
return { at: (node, key) => accessor(node, key), reads: [] };
|
|
740
|
+
}
|
|
741
|
+
const use = planUseOf(input);
|
|
742
|
+
if (use !== undefined)
|
|
743
|
+
return useField(rowType, use, makes, where, via);
|
|
744
|
+
const read = planReadOf(input);
|
|
745
|
+
throw new Error(read !== undefined
|
|
746
|
+
? `${where}: ${via} takes ${CONSUMER_HINTS[makes]} — give "${read.field}" to it, as ${via}: ${CONSUMER_HINTS[makes].split(" over ")[0].split(",")[0]}(Plan.values("${read.field}", draw))`
|
|
747
|
+
: `${where}: ${via} is a function of the row, or ${CONSUMER_HINTS[makes]}`);
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* A field whose value is a list (a table's positions, a chart's layers)
|
|
751
|
+
* resolved for the entry type (#1436): a function of the row as before, or a
|
|
752
|
+
* TS array whose consumers are built per row and whose other members pass as
|
|
753
|
+
* they are.
|
|
754
|
+
*
|
|
755
|
+
* @param rowType - The entry type
|
|
756
|
+
* @param input - The field as given
|
|
757
|
+
* @param makes - What its consumers make
|
|
758
|
+
* @param where - The series, for messages
|
|
759
|
+
* @param via - The field's name
|
|
760
|
+
* @returns The field, its value a TS array per row (each consumer's value in its place), or `undefined` when not given
|
|
761
|
+
*/
|
|
762
|
+
function listField(rowType, input, makes, where, via) {
|
|
763
|
+
if (input === undefined)
|
|
764
|
+
return undefined;
|
|
765
|
+
if (typeof input === "function") {
|
|
766
|
+
const accessor = input;
|
|
767
|
+
return { at: (node, key) => accessor(node, key), reads: [] };
|
|
768
|
+
}
|
|
769
|
+
if (!Array.isArray(input))
|
|
770
|
+
throw new Error(`${where}: ${via} is a function of the row, or a list of ${CONSUMER_HINTS[makes]}`);
|
|
771
|
+
const members = input.map((member, i) => {
|
|
772
|
+
const use = planUseOf(member);
|
|
773
|
+
if (use !== undefined)
|
|
774
|
+
return useField(rowType, use, makes, where, `${via}[${i}]`);
|
|
775
|
+
const read = planReadOf(member);
|
|
776
|
+
if (read !== undefined)
|
|
777
|
+
throw new Error(`${where}: ${via}[${i}] takes ${CONSUMER_HINTS[makes]}, not the read "${read.field}" alone`);
|
|
778
|
+
return { at: () => member, reads: [] };
|
|
779
|
+
});
|
|
780
|
+
return {
|
|
781
|
+
at: (node, key, value) => members.map((m) => m.at(node, key, value)),
|
|
782
|
+
reads: members.flatMap((m) => m.reads),
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* A table's positions whose values take typed numbers (#1436): its `cells`
|
|
787
|
+
* when the series declares `edit: true`, and each `Plan.tableSeries` position
|
|
788
|
+
* declaring its own — each drawing every value as it is stored.
|
|
789
|
+
*
|
|
790
|
+
* @param rowType - The entry type
|
|
791
|
+
* @param cfg - The table's `cells`, `series` and `edit`, as given
|
|
792
|
+
* @param where - The series, for messages
|
|
793
|
+
* @returns The typed positions, in order
|
|
794
|
+
* @throws {Error} In the refusals' words: `edit` beside cells given as a function, or over a draw giving another number than each value
|
|
795
|
+
*/
|
|
796
|
+
function tableEditsOf(rowType, cfg, where) {
|
|
797
|
+
const out = [];
|
|
798
|
+
if (cfg.edit !== undefined) {
|
|
799
|
+
if (cfg.edit !== true)
|
|
800
|
+
throw new Error(`${where}: edit is true — its cells' values take typed numbers — or left out`);
|
|
801
|
+
if (cfg.series !== undefined) {
|
|
802
|
+
throw new Error(`${where}: edit is its cells' — with positions, each declares its own: Plan.tableSeries({ cells, edit: true })`);
|
|
803
|
+
}
|
|
804
|
+
const use = planUseOf(cfg.cells);
|
|
805
|
+
if (use === undefined) {
|
|
806
|
+
throw new Error(`${where}: edit writes the values its cells read — give cells as Plan.tableCells(Plan.values(field, draw))`);
|
|
807
|
+
}
|
|
808
|
+
out.push({ position: 0, read: use.read });
|
|
809
|
+
}
|
|
810
|
+
if (Array.isArray(cfg.series)) {
|
|
811
|
+
cfg.series.forEach((member, position) => {
|
|
812
|
+
const use = planUseOf(member);
|
|
813
|
+
if (use?.edit === true)
|
|
814
|
+
out.push({ position, read: use.read });
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
for (const typed of out)
|
|
818
|
+
refuseDrawnOtherwise(rowType, typed.read, where);
|
|
819
|
+
return out;
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Whether a values read's cells draw each value as it is stored (#1436,
|
|
823
|
+
* #1435): a `Float`'s `some(v)` (or `v`), an `Option` of a `Float`'s `v`, an
|
|
824
|
+
* `Integer`'s `some(v.toFloat())` (or `v.toFloat()`) — what a typed number is
|
|
825
|
+
* written as, and what e3 keeps a parent's subtotals of. Read off the draw's
|
|
826
|
+
* expression, as an accessor's field is (`childRefOf`).
|
|
827
|
+
*
|
|
828
|
+
* @param rowType - The entry type
|
|
829
|
+
* @param read - The values read
|
|
830
|
+
* @param where - The series, for messages
|
|
831
|
+
* @returns Whether each value draws as it is stored
|
|
832
|
+
* @internal
|
|
833
|
+
*/
|
|
834
|
+
export function drawsAsStored(rowType, read, where) {
|
|
835
|
+
const shape = collectionOfRead(rowType, read, where);
|
|
836
|
+
const kind = isTypeEqual(shape.item, FloatType) ? "float"
|
|
837
|
+
: isTypeEqual(shape.item, OptionType(FloatType)) ? "option"
|
|
838
|
+
: isTypeEqual(shape.item, IntegerType) ? "integer" : undefined;
|
|
839
|
+
let asStored = false;
|
|
840
|
+
if (kind !== undefined) {
|
|
841
|
+
const recursive = rowType.type === "Recursive";
|
|
842
|
+
East.function([shape.item, shape.key, rowType], NullType, ($, value, at, row) => {
|
|
843
|
+
const node = recursive ? $.let(row.unwrap()) : row;
|
|
844
|
+
const given = read.draw(value, at, node);
|
|
845
|
+
const param = Expr.ast(value);
|
|
846
|
+
const astOf = (x) => (x instanceof Expr ? Expr.ast(x) : undefined);
|
|
847
|
+
const isValue = (x) => astOf(x) === param;
|
|
848
|
+
const isWidened = (x) => {
|
|
849
|
+
const ast = astOf(x);
|
|
850
|
+
return ast !== undefined && ast.ast_type === "Builtin" && ast.builtin === "IntegerToFloat" && ast.arguments?.[0] === param;
|
|
851
|
+
};
|
|
852
|
+
const drawn = given === null || typeof given !== "object" ? undefined : given.value;
|
|
853
|
+
const wrapped = typeof drawn === "object" && drawn !== null && !(drawn instanceof Expr) && drawn.type === "some"
|
|
854
|
+
? drawn.value : undefined;
|
|
855
|
+
asStored = kind === "option" ? isValue(drawn)
|
|
856
|
+
: kind === "float" ? isValue(drawn) || isValue(wrapped)
|
|
857
|
+
: isWidened(drawn) || isWidened(wrapped);
|
|
858
|
+
return null;
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
return asStored;
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Refuse a typed position whose cells draw another number than each value
|
|
865
|
+
* (#1436): a typed number is stored as the value, so each value must draw as
|
|
866
|
+
* it is ({@link drawsAsStored}).
|
|
867
|
+
*
|
|
868
|
+
* @param rowType - The entry type
|
|
869
|
+
* @param read - The position's values read
|
|
870
|
+
* @param where - The series, for the message
|
|
871
|
+
* @throws {Error} In the refusal's words
|
|
872
|
+
*/
|
|
873
|
+
function refuseDrawnOtherwise(rowType, read, where) {
|
|
874
|
+
if (!drawsAsStored(rowType, read, where)) {
|
|
875
|
+
throw new Error(`${where}: edit writes a typed number as its readings, and its cells draw another number than each reading — ` +
|
|
876
|
+
"draw value: some(v), and show it another way through format");
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* A tag given as a literal — its name, or a variant value's case — as the
|
|
881
|
+
* build reads it (#1435); `undefined` for an East expression, known only as
|
|
882
|
+
* the Plan is evaluated.
|
|
883
|
+
*
|
|
884
|
+
* @param given - The tag, as given
|
|
885
|
+
* @param fallback - Its default, where it is not given
|
|
886
|
+
* @returns Its name, or `undefined`
|
|
887
|
+
*/
|
|
888
|
+
function tagOf(given, fallback) {
|
|
889
|
+
if (given === undefined)
|
|
890
|
+
return fallback;
|
|
891
|
+
if (typeof given === "string")
|
|
892
|
+
return given;
|
|
893
|
+
return isVariant(given) ? given.type : undefined;
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* A series config's functions of the row (#1435), each by the field it is
|
|
897
|
+
* given as — its accessors, its element fields and its consumers given as
|
|
898
|
+
* functions, and a list's members that are — what a Plan walks for a
|
|
899
|
+
* collection read in a function.
|
|
900
|
+
*
|
|
901
|
+
* @param cfg - The config, as given
|
|
902
|
+
* @returns Its functions of the row
|
|
903
|
+
*/
|
|
904
|
+
function accessorsOf(cfg) {
|
|
905
|
+
const out = [];
|
|
906
|
+
for (const [via, value] of Object.entries(cfg)) {
|
|
907
|
+
if (typeof value === "function")
|
|
908
|
+
out.push({ via, fn: value });
|
|
909
|
+
else if (Array.isArray(value)) {
|
|
910
|
+
value.forEach((member, i) => {
|
|
911
|
+
if (typeof member === "function")
|
|
912
|
+
out.push({ via: `${via}[${i}]`, fn: member });
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return out;
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Refuse a tree's `children` field that is not a Dict of the row's own type,
|
|
920
|
+
* keyed by an id (#1436).
|
|
921
|
+
*
|
|
922
|
+
* @param rowType - The entry type
|
|
923
|
+
* @param field - The field `children` names
|
|
924
|
+
* @param where - The series, for the message
|
|
925
|
+
*/
|
|
926
|
+
function checkTree(rowType, field, where) {
|
|
927
|
+
const t = entryFieldsOf(rowType)?.[field];
|
|
928
|
+
if (t === undefined) {
|
|
929
|
+
throw new Error(`${where}: children names "${field}", which is no field of its rows — name the Dict field holding more of its ` +
|
|
930
|
+
"rows, or step down to rows of another type with Plan.children(field, [series…])");
|
|
931
|
+
}
|
|
932
|
+
if (t.type !== "Dict") {
|
|
933
|
+
throw new Error(`${where}: children names "${field}", ${anOf(t)} — a tree's children are a Dict of the row's own type, keyed by an id`);
|
|
934
|
+
}
|
|
935
|
+
if (!isTypeEqual(t.value, rowType)) {
|
|
936
|
+
throw new Error(`${where}: children "${field}" holds ${shapeName(t.value)}, not ${shapeName(rowType)} — step down to it with Plan.children("${field}", [series…])`);
|
|
937
|
+
}
|
|
938
|
+
if (!isTypeEqual(t.key, StringType)) {
|
|
939
|
+
throw new Error(`${where}: children "${field}" is keyed by ${t.key.type} — child rows are keyed by an id, a String`);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Refuse a step-down's field that is not a Dict of child rows keyed by an id
|
|
944
|
+
* (#1436).
|
|
945
|
+
*
|
|
946
|
+
* @param rowType - The parent entry type
|
|
947
|
+
* @param field - The field `Plan.children` names
|
|
948
|
+
* @param where - The series, for the message
|
|
949
|
+
*/
|
|
950
|
+
function checkStepField(rowType, field, where) {
|
|
951
|
+
const t = entryFieldsOf(rowType)?.[field];
|
|
952
|
+
if (t === undefined) {
|
|
953
|
+
throw new Error(`${where}: Plan.children("${field}", …) steps down to "${field}", which is no field of its rows — name the Dict field holding the child rows`);
|
|
954
|
+
}
|
|
955
|
+
if (t.type !== "Dict") {
|
|
956
|
+
throw new Error(`Plan.children: "${field}" is ${anOf(t)} — child rows are a Dict, keyed by an id`);
|
|
957
|
+
}
|
|
958
|
+
if (!isTypeEqual(t.key, StringType)) {
|
|
959
|
+
throw new Error(`Plan.children: "${field}" is keyed by ${t.key.type} — child rows are keyed by an id, a String`);
|
|
960
|
+
}
|
|
961
|
+
const child = t.value.type;
|
|
962
|
+
if (child !== "Struct" && child !== "Recursive") {
|
|
963
|
+
throw new Error(`Plan.children: "${field}" holds ${anOf(t.value)}, not rows — child rows are structs`);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* A series' `edit`, checked against the element field it writes through
|
|
968
|
+
* (#1436): `true` or `{ create }`, beside a read of items.
|
|
969
|
+
*
|
|
970
|
+
* @param edit - The declaration as given
|
|
971
|
+
* @param elements - The element field: `runs`, `events`, `chips` or `marks`
|
|
972
|
+
* @param times - The times those elements take
|
|
973
|
+
* @param input - The element field as given
|
|
974
|
+
* @param where - The series, for the messages
|
|
975
|
+
* @returns The recipe, or `undefined` when no `edit` is declared
|
|
976
|
+
* @throws {Error} In the refusals' words
|
|
977
|
+
*/
|
|
978
|
+
function editRecipeOf(edit, elements, times, input, where) {
|
|
979
|
+
if (edit === undefined)
|
|
980
|
+
return undefined;
|
|
981
|
+
const create = typeof edit === "object" && edit !== null ? edit.create : undefined;
|
|
982
|
+
if (edit !== true && (typeof create !== "function" || Object.keys(edit).some((k) => k !== "create"))) {
|
|
983
|
+
throw new Error(`${where}: edit names nothing — its ${elements}' read names the collection and its times: give edit: true, or ` +
|
|
984
|
+
"edit: { create } to take dropped cards too");
|
|
985
|
+
}
|
|
986
|
+
const read = planReadOf(input);
|
|
987
|
+
if (read === undefined) {
|
|
988
|
+
throw new Error(`${where}: edit moves the items its ${elements} name — give ${elements} as Plan.items(field, ${times}, draw)`);
|
|
989
|
+
}
|
|
990
|
+
if (read.kind !== "items") {
|
|
991
|
+
throw new Error(`${where}: edit moves the items its ${elements} name, and they read values — what people move is keyed by an id: ` +
|
|
992
|
+
`give ${elements} as Plan.items(field, ${times}, draw)`);
|
|
993
|
+
}
|
|
994
|
+
return { edit: edit, elements, times, input };
|
|
995
|
+
}
|
|
428
996
|
/**
|
|
429
997
|
* The build spec of a data series (span / buckets / chart / heat / table /
|
|
430
998
|
* cards / events / group).
|
|
@@ -434,25 +1002,12 @@ function instantInto(fieldType) {
|
|
|
434
1002
|
* row facts through the one envelope) and the membership, collapse and
|
|
435
1003
|
* children functions, each reified once and CALLED in the block's loop. The
|
|
436
1004
|
* block walks the collection in order; each matched entry's row is followed by
|
|
437
|
-
* its children's rows — a
|
|
1005
|
+
* its children's rows — a tree (`children: field`) walks the entry's subtree
|
|
438
1006
|
* with an explicit pre-order stack (its depth is data), a step-down calls its
|
|
439
1007
|
* child series' blocks on the child collection.
|
|
440
1008
|
*/
|
|
441
1009
|
function dataSpec(rowType, cfg, recipe) {
|
|
442
1010
|
const where = `Plan.series.${recipe.arm} "${cfg.key}"`;
|
|
443
|
-
const children = cfg.children;
|
|
444
|
-
const steps = children === undefined || typeof children === "function"
|
|
445
|
-
? [] : (Array.isArray(children) ? children : [children]);
|
|
446
|
-
for (const step of steps) {
|
|
447
|
-
if (!isStepDown(step)) {
|
|
448
|
-
throw new Error(`${where}: \`children\` is an accessor for more of this series, or \`Plan.children(of, [series…])\` step-downs`);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
const nested = steps.flatMap((s) => s.series.map((x) => nestedSpec(x, `${where} › children`)));
|
|
452
|
-
// How many chart rows each entry makes (#1398): a chart series' own row, and below it a bare accessor's walk of
|
|
453
|
-
// more of this series' rows, or a step-down's series' over the child collection.
|
|
454
|
-
const ownCharts = recipe.arm === "chart" ? 1 : 0;
|
|
455
|
-
const charts = entryCharts(ownCharts, children === undefined ? 0 : typeof children === "function" ? ownCharts : chartsOf(nested), cfg.match !== undefined);
|
|
456
1011
|
// A recursive entry reaches the accessors as its NODE (`PlanEntryExpr`),
|
|
457
1012
|
// so they read its fields directly at every depth.
|
|
458
1013
|
const recursive = rowType.type === "Recursive";
|
|
@@ -461,6 +1016,33 @@ function dataSpec(rowType, cfg, recipe) {
|
|
|
461
1016
|
: value;
|
|
462
1017
|
/** An accessor over entries, applied to an entry value (its node, when recursive). */
|
|
463
1018
|
const onEntry = (accessor) => (v, k) => accessor(nodeOf(v), k);
|
|
1019
|
+
const children = cfg.children;
|
|
1020
|
+
// A tree (#1436): a field of the entry holding more of its entries — or, the
|
|
1021
|
+
// event kinds' own (#1194), an accessor computing them.
|
|
1022
|
+
const treeField = typeof children === "string" ? children : undefined;
|
|
1023
|
+
const tree = treeField !== undefined
|
|
1024
|
+
? (v) => collectionOn(v, treeField)
|
|
1025
|
+
: typeof children === "function" ? children : undefined;
|
|
1026
|
+
if (treeField !== undefined)
|
|
1027
|
+
checkTree(rowType, treeField, where);
|
|
1028
|
+
const steps = children === undefined || tree !== undefined
|
|
1029
|
+
? [] : (Array.isArray(children) ? children : [children]);
|
|
1030
|
+
for (const step of steps) {
|
|
1031
|
+
if (!isStepDown(step)) {
|
|
1032
|
+
throw new Error(`${where}: \`children\` is a field of the row holding more of its rows, or Plan.children(field, [series…]) step-downs`);
|
|
1033
|
+
}
|
|
1034
|
+
if (step.field !== undefined)
|
|
1035
|
+
checkStepField(rowType, step.field, where);
|
|
1036
|
+
}
|
|
1037
|
+
/** A step-down's child collection, from the entry (its node). */
|
|
1038
|
+
const stepOf = (step) => (step.field !== undefined
|
|
1039
|
+
? (v) => collectionOn(v, step.field)
|
|
1040
|
+
: step.of);
|
|
1041
|
+
const nested = steps.flatMap((s) => s.series.map((x) => nestedSpec(x, `${where} › children`)));
|
|
1042
|
+
// How many chart rows each entry makes (#1398): a chart series' own row, and below it a tree's walk of more of
|
|
1043
|
+
// this series' rows, or a step-down's series' over the child collection.
|
|
1044
|
+
const ownCharts = recipe.arm === "chart" ? 1 : 0;
|
|
1045
|
+
const charts = entryCharts(ownCharts, children === undefined ? 0 : tree !== undefined ? ownCharts : chartsOf(nested), cfg.match !== undefined);
|
|
464
1046
|
// The removed verdict, named (#1260) — a plain JS caller would otherwise
|
|
465
1047
|
// lose it silently.
|
|
466
1048
|
for (const removed of ["review", "approval"]) {
|
|
@@ -468,30 +1050,25 @@ function dataSpec(rowType, cfg, recipe) {
|
|
|
468
1050
|
throw new Error(`${where}: \`${removed}\` is removed (#1260) — a Plan approves and rejects nothing: its changes are drafts of the root's \`editing\` session`);
|
|
469
1051
|
}
|
|
470
1052
|
}
|
|
471
|
-
// What a gesture writes (#880, #825) —
|
|
472
|
-
//
|
|
473
|
-
//
|
|
474
|
-
// fails the build.
|
|
1053
|
+
// What a gesture writes (#880, #825, #1436) — through the collection its
|
|
1054
|
+
// elements' read names: a dropped card's item under its minted id, a moved
|
|
1055
|
+
// element's item re-timed under its id, or taken to another row.
|
|
475
1056
|
const edit = recipe.edit;
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
}
|
|
488
|
-
itemType = t.value;
|
|
489
|
-
move = moveFieldsOf(edit, itemType, recipe.arm, where);
|
|
490
|
-
}
|
|
491
|
-
const edits = edit !== undefined
|
|
1057
|
+
const editRead = edit !== undefined ? planReadOf(edit.input) : undefined;
|
|
1058
|
+
const editShape = editRead !== undefined ? collectionOfRead(rowType, editRead, where) : undefined;
|
|
1059
|
+
const itemType = editShape?.item;
|
|
1060
|
+
const move = editRead !== undefined ? moveFieldsOf(editRead, editShape.item) : undefined;
|
|
1061
|
+
const create = edit !== undefined && edit.edit !== true ? edit.edit.create : undefined;
|
|
1062
|
+
// What a typed number writes (#1436): each typed position's collection, keyed by the bucket's start.
|
|
1063
|
+
const typedWrites = (recipe.values ?? []).map((typed) => {
|
|
1064
|
+
const shape = collectionOfRead(rowType, typed.read, where);
|
|
1065
|
+
return { position: typed.position, field: typed.read.field, key: shape.key, value: shape.item, keyAs: instantInto(shape.key) };
|
|
1066
|
+
});
|
|
1067
|
+
const edits = move !== undefined || typedWrites.length > 0
|
|
492
1068
|
? {
|
|
493
|
-
drop:
|
|
1069
|
+
drop: create !== undefined,
|
|
494
1070
|
move: move !== undefined ? some(move.edits) : none,
|
|
1071
|
+
values: typedWrites.map((typed) => ({ position: BigInt(typed.position), whole: isTypeEqual(typed.value, IntegerType) })),
|
|
495
1072
|
}
|
|
496
1073
|
: undefined;
|
|
497
1074
|
const rows = new ByType();
|
|
@@ -514,7 +1091,7 @@ function dataSpec(rowType, cfg, recipe) {
|
|
|
514
1091
|
...(cfg.value !== undefined ? { value: cfg.value(entry, key) } : {}),
|
|
515
1092
|
...(recipe.swatches !== undefined ? { swatches: recipe.swatches } : {}),
|
|
516
1093
|
}),
|
|
517
|
-
kind: recipe.kind(entry, key, hasChildren),
|
|
1094
|
+
kind: recipe.kind(entry, key, hasChildren, value),
|
|
518
1095
|
collapsed,
|
|
519
1096
|
...(recipe.pinned !== undefined ? { pinned: recipe.pinned(entry, key) } : {}),
|
|
520
1097
|
...(cfg.status !== undefined ? { status: cfg.status(entry, key) } : {}),
|
|
@@ -536,13 +1113,13 @@ function dataSpec(rowType, cfg, recipe) {
|
|
|
536
1113
|
? East.value(collapsed, BooleanType)
|
|
537
1114
|
: East.value(collapsed(nodeOf(value), key), BooleanType)));
|
|
538
1115
|
});
|
|
539
|
-
/**
|
|
540
|
-
const childOf = (kt) => building(where, kt, () => reifyAccessor([rowType, kt], onEntry(
|
|
1116
|
+
/** The tree's children, built for entries keyed by `kt` — the child collection's type is its output. */
|
|
1117
|
+
const childOf = (kt) => building(where, kt, () => reifyAccessor([rowType, kt], onEntry(tree)));
|
|
541
1118
|
/** The subtree walk below one entry — every level's entries in pre-order, over the child collection type. */
|
|
542
1119
|
const walkFor = (cc) => walks.get(cc, () => {
|
|
543
1120
|
const at = `${where} › children`;
|
|
544
1121
|
const { entry, key: kc } = shapeOf(cc, at);
|
|
545
|
-
assertEntry(entry, rowType, `${at} (a
|
|
1122
|
+
assertEntry(entry, rowType, `${at} (a tree's children are more of this series' rows — step down to another type with Plan.children)`);
|
|
546
1123
|
const next = childOf(kc);
|
|
547
1124
|
const nextType = Expr.type(next).output;
|
|
548
1125
|
if (!isTypeEqual(nextType, cc)) {
|
|
@@ -598,7 +1175,7 @@ function dataSpec(rowType, cfg, recipe) {
|
|
|
598
1175
|
const childrenFor = (kt) => kids.get(kt, () => {
|
|
599
1176
|
if (children === undefined)
|
|
600
1177
|
return undefined;
|
|
601
|
-
if (
|
|
1178
|
+
if (tree !== undefined) {
|
|
602
1179
|
const of = childOf(kt);
|
|
603
1180
|
const cc = Expr.type(of).output;
|
|
604
1181
|
const walk = walkFor(cc);
|
|
@@ -606,7 +1183,7 @@ function dataSpec(rowType, cfg, recipe) {
|
|
|
606
1183
|
}
|
|
607
1184
|
const parts = steps.map((step, i) => {
|
|
608
1185
|
const at = `${where} › children[${i}]`;
|
|
609
|
-
const of = building(at, kt, () => reifyAccessor([rowType, kt], onEntry(step
|
|
1186
|
+
const of = building(at, kt, () => reifyAccessor([rowType, kt], onEntry(stepOf(step))));
|
|
610
1187
|
const cc = Expr.type(of).output;
|
|
611
1188
|
const blocks = step.series.map((s) => nestedSpec(s, at).emitter(cc, at));
|
|
612
1189
|
return { of, cc, blocks };
|
|
@@ -622,20 +1199,20 @@ function dataSpec(rowType, cfg, recipe) {
|
|
|
622
1199
|
return out;
|
|
623
1200
|
});
|
|
624
1201
|
});
|
|
625
|
-
// ── Writing a gesture back (#880)
|
|
1202
|
+
// ── Writing a gesture back (#880, #1436) ──────────────────────────────
|
|
626
1203
|
// The entry a writer is handed is DETACHED (the series value's `write`
|
|
627
1204
|
// copies it), so a nested entry is written in place in its collection and
|
|
628
|
-
//
|
|
1205
|
+
// an item joins its collection in place, under its id.
|
|
629
1206
|
const owns = new ByType();
|
|
630
1207
|
const writers = new ByType();
|
|
631
1208
|
const entryAccess = (value) => nodeOf(value);
|
|
632
1209
|
/** The item a dropped card becomes, for entries keyed by `kt`. */
|
|
633
|
-
const createFor = (kt,
|
|
1210
|
+
const createFor = (kt, make) => building(where, kt, () => East.function([PlanDropType, rowType, kt], itemType, (_$, drop, value, key) => make(drop, nodeOf(value), key)));
|
|
634
1211
|
/**
|
|
635
|
-
* An item at a move's instants (#825) —
|
|
636
|
-
* from the axis's instants, each on its own arm, every other
|
|
637
|
-
* was; `none` when an instant rides an arm the field cannot
|
|
638
|
-
* instant into a Float field).
|
|
1212
|
+
* An item at a move's instants (#825, #1436) — the instant fields its read
|
|
1213
|
+
* names set from the axis's instants, each on its own arm, every other
|
|
1214
|
+
* field as it was; `none` when an instant rides an arm the field cannot
|
|
1215
|
+
* hold (a time instant into a Float field).
|
|
639
1216
|
*/
|
|
640
1217
|
const placed = move === undefined ? undefined : (() => {
|
|
641
1218
|
const m = move;
|
|
@@ -667,92 +1244,74 @@ function dataSpec(rowType, cfg, recipe) {
|
|
|
667
1244
|
});
|
|
668
1245
|
})();
|
|
669
1246
|
/**
|
|
670
|
-
*
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
*
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
* This series' own edit on one of its entries — the row at `path` is its
|
|
678
|
-
* own: a dropped card's item into its list, and a moved element's item
|
|
679
|
-
* re-timed where it is, taken out of the list, or put into it (#825).
|
|
1247
|
+
* This series' own edit on one of its entries (#1436) — the row at `path`
|
|
1248
|
+
* is its own: a dropped card's item into the collection its elements'
|
|
1249
|
+
* read names, under the id the Plan minted; a moved element's item, found
|
|
1250
|
+
* by its id (the element's key), re-timed where it is, taken out of the
|
|
1251
|
+
* collection, or put into it under the same id. A collection already
|
|
1252
|
+
* holding the id refuses the item. A number typed into one of its table
|
|
1253
|
+
* positions sets that position's value at the bucket's start.
|
|
680
1254
|
*/
|
|
681
1255
|
const ownFor = (kt) => owns.get(kt, () => {
|
|
682
|
-
|
|
1256
|
+
const moves = editRead !== undefined && placed !== undefined && move !== undefined;
|
|
1257
|
+
if (!moves && typedWrites.length === 0)
|
|
683
1258
|
return undefined;
|
|
684
|
-
const
|
|
1259
|
+
const made = moves && create !== undefined ? createFor(kt, create) : undefined;
|
|
1260
|
+
const field = editRead?.field ?? "";
|
|
1261
|
+
const item = move?.item;
|
|
685
1262
|
return East.function([rowType, kt, PathType, PlanGestureType, PlanCarriedType], OptionType(rowType), ($, value, key, path, gesture, carried) => {
|
|
686
|
-
const make =
|
|
687
|
-
const place =
|
|
1263
|
+
const make = made === undefined ? undefined : $.const(made);
|
|
1264
|
+
const place = moves ? $.const(placed) : undefined;
|
|
688
1265
|
const result = $.let(none, OptionType(rowType));
|
|
689
1266
|
$.match(gesture, {
|
|
690
1267
|
drop: ($2, drop) => {
|
|
691
|
-
if (make === undefined
|
|
1268
|
+
if (make === undefined)
|
|
692
1269
|
return;
|
|
693
|
-
const
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
$3(
|
|
1270
|
+
const coll = $2.let(entryAccess(value)[field]);
|
|
1271
|
+
// The minted id, unless the collection already holds it.
|
|
1272
|
+
$2.if(coll.has(drop.id).not(), ($3) => {
|
|
1273
|
+
$3(coll.insert(drop.id, make(drop, value, key)));
|
|
697
1274
|
$3.assign(result, some(value));
|
|
698
|
-
};
|
|
699
|
-
// With the item's key declared, a card whose item
|
|
700
|
-
// repeats a key in the list is refused (#825).
|
|
701
|
-
if (holds === undefined || move === undefined)
|
|
702
|
-
add($2);
|
|
703
|
-
else
|
|
704
|
-
$2.if(holds(list, item[move.key]).not(), add);
|
|
1275
|
+
});
|
|
705
1276
|
},
|
|
706
1277
|
move: ($2, mv) => {
|
|
707
|
-
if (place === undefined ||
|
|
1278
|
+
if (place === undefined || item === undefined)
|
|
708
1279
|
return;
|
|
709
|
-
const keyField = move.key;
|
|
710
|
-
const itemOf = move.item;
|
|
711
1280
|
const here = $2.let(East.value(variant("entry", { series: cfg.key, path }), PlanRowIdType), PlanRowIdType);
|
|
712
|
-
const
|
|
713
|
-
// The element's item, by its key.
|
|
714
|
-
const at = $2.let(list.firstMap(($3, item, i) => East.equal(item[keyField], mv.key).ifElse(() => East.value(some(i), OptionType(IntegerType)), () => East.value(none, OptionType(IntegerType)))), OptionType(IntegerType));
|
|
1281
|
+
const coll = $2.let(entryAccess(value)[field]);
|
|
715
1282
|
$2.if(East.equal(here, mv.from).and(() => East.equal(here, mv.to)), ($3) => {
|
|
716
|
-
// Along its row, or resized: its
|
|
717
|
-
$3.
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
$
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
});
|
|
726
|
-
},
|
|
1283
|
+
// Along its row, or resized: its times set where it is.
|
|
1284
|
+
$3.if(coll.has(mv.key), ($4) => {
|
|
1285
|
+
const moved = $4.let(place(coll.get(mv.key), mv.start, mv.end), OptionType(item));
|
|
1286
|
+
$4.match(moved, {
|
|
1287
|
+
some: ($5, next) => {
|
|
1288
|
+
$5(coll.update(mv.key, next));
|
|
1289
|
+
$5.assign(result, some(value));
|
|
1290
|
+
},
|
|
1291
|
+
});
|
|
727
1292
|
});
|
|
728
1293
|
}).else(($3) => {
|
|
729
|
-
// Its source: taken out of the
|
|
1294
|
+
// Its source: taken out of the collection, and carried.
|
|
730
1295
|
$3.if(East.equal(here, mv.from), ($4) => {
|
|
731
|
-
$4.
|
|
732
|
-
some
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
const after = $5.let(list.slice(i.add(1n), list.size()));
|
|
736
|
-
$5(list.clear());
|
|
737
|
-
$5(list.append(before));
|
|
738
|
-
$5(list.append(after));
|
|
739
|
-
$5.assign(result, some(value));
|
|
740
|
-
},
|
|
1296
|
+
$4.if(coll.has(mv.key), ($5) => {
|
|
1297
|
+
$5(carried.update(East.value(some(East.Blob.encodeBeast(coll.get(mv.key), "v2")), OptionType(BlobType))));
|
|
1298
|
+
$5(coll.delete(mv.key));
|
|
1299
|
+
$5.assign(result, some(value));
|
|
741
1300
|
});
|
|
742
1301
|
});
|
|
743
|
-
// Its target: the carried item put in, at its new
|
|
744
|
-
//
|
|
745
|
-
//
|
|
1302
|
+
// Its target: the carried item put in under its id, at its new
|
|
1303
|
+
// times — unless the collection already holds the id, when it
|
|
1304
|
+
// is put nowhere and the move lands nowhere.
|
|
746
1305
|
$3.if(East.equal(here, mv.to), ($4) => {
|
|
747
1306
|
const held = $4.let(carried.get(), OptionType(BlobType));
|
|
748
1307
|
$4.match(held, {
|
|
749
1308
|
some: ($5, bytes) => {
|
|
750
|
-
$5.if(
|
|
751
|
-
const
|
|
752
|
-
const moved = $6.let(place(
|
|
1309
|
+
$5.if(coll.has(mv.key).not(), ($6) => {
|
|
1310
|
+
const taken = $6.let(bytes.decodeBeast(item, "v2"), item);
|
|
1311
|
+
const moved = $6.let(place(taken, mv.start, mv.end), OptionType(item));
|
|
753
1312
|
$6.match(moved, {
|
|
754
|
-
some: ($7,
|
|
755
|
-
$7(
|
|
1313
|
+
some: ($7, next) => {
|
|
1314
|
+
$7(coll.insert(mv.key, next));
|
|
756
1315
|
$7(carried.update(East.value(none, OptionType(BlobType))));
|
|
757
1316
|
$7.assign(result, some(value));
|
|
758
1317
|
},
|
|
@@ -763,11 +1322,55 @@ function dataSpec(rowType, cfg, recipe) {
|
|
|
763
1322
|
});
|
|
764
1323
|
});
|
|
765
1324
|
},
|
|
1325
|
+
// A number typed into a cell (#1436): its position's value at the bucket's start set, or added
|
|
1326
|
+
// where the collection holds none; cleared, deleted. An Integer collection takes a whole number,
|
|
1327
|
+
// and an instant on an arm its keys cannot hold writes nothing.
|
|
1328
|
+
value: ($2, typed) => {
|
|
1329
|
+
if (typedWrites.length === 0)
|
|
1330
|
+
return;
|
|
1331
|
+
const here = $2.let(East.value(variant("entry", { series: cfg.key, path }), PlanRowIdType), PlanRowIdType);
|
|
1332
|
+
$2.if(East.equal(here, typed.row), ($3) => {
|
|
1333
|
+
for (const write of typedWrites) {
|
|
1334
|
+
$3.if(typed.position.equal(BigInt(write.position)), ($4) => {
|
|
1335
|
+
const keyAs = $4.const(write.keyAs);
|
|
1336
|
+
const at = $4.let(keyAs(typed.at), OptionType(write.key));
|
|
1337
|
+
$4.match(at, {
|
|
1338
|
+
some: ($5, k) => {
|
|
1339
|
+
const coll = $5.let(entryAccess(value)[write.field]);
|
|
1340
|
+
$5.match(typed.value, {
|
|
1341
|
+
some: ($6, n) => {
|
|
1342
|
+
const put = ($7, stored) => {
|
|
1343
|
+
$7(coll.insertOrUpdate(k, stored));
|
|
1344
|
+
$7.assign(result, some(value));
|
|
1345
|
+
};
|
|
1346
|
+
if (isTypeEqual(write.value, IntegerType)) {
|
|
1347
|
+
$6.if(n.remainder(1).equal(0), ($7) => { put($7, n.toInteger()); });
|
|
1348
|
+
}
|
|
1349
|
+
else if (isTypeEqual(write.value, OptionType(FloatType))) {
|
|
1350
|
+
put($6, some(n));
|
|
1351
|
+
}
|
|
1352
|
+
else {
|
|
1353
|
+
put($6, n);
|
|
1354
|
+
}
|
|
1355
|
+
},
|
|
1356
|
+
none: ($6) => {
|
|
1357
|
+
$6.if(coll.has(k), ($7) => {
|
|
1358
|
+
$7(coll.delete(k));
|
|
1359
|
+
$7.assign(result, some(value));
|
|
1360
|
+
});
|
|
1361
|
+
},
|
|
1362
|
+
});
|
|
1363
|
+
},
|
|
1364
|
+
});
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
},
|
|
766
1369
|
});
|
|
767
1370
|
return result;
|
|
768
1371
|
});
|
|
769
1372
|
});
|
|
770
|
-
/** The
|
|
1373
|
+
/** The tree's writer: the rows below an entry are this series' own. */
|
|
771
1374
|
const walkWriterFor = (kt) => {
|
|
772
1375
|
const at = `${where} › children`;
|
|
773
1376
|
const of = childOf(kt);
|
|
@@ -776,10 +1379,10 @@ function dataSpec(rowType, cfg, recipe) {
|
|
|
776
1379
|
const own = ownFor(kc);
|
|
777
1380
|
if (own === undefined)
|
|
778
1381
|
return undefined;
|
|
779
|
-
const ref = childRefOf(rowType, kt,
|
|
1382
|
+
const ref = treeField !== undefined ? { kind: "field", field: treeField } : childRefOf(rowType, kt, tree);
|
|
780
1383
|
if (ref === undefined) {
|
|
781
1384
|
throw new Error(`${at}: this series takes gestures, and one on a child row is written back into its entry through ` +
|
|
782
|
-
"`children` — which must read a field of the entry
|
|
1385
|
+
"`children` — which must read a field of the entry, not compute a collection");
|
|
783
1386
|
}
|
|
784
1387
|
const ops = collectionOps(cc);
|
|
785
1388
|
const collOf = (entry) => (ref.kind === "field" ? entryAccess(entry)[ref.field] : nodeOf(entry));
|
|
@@ -824,7 +1427,7 @@ function dataSpec(rowType, cfg, recipe) {
|
|
|
824
1427
|
const stepsWriterFor = (kt) => {
|
|
825
1428
|
const parts = steps.flatMap((step, i) => {
|
|
826
1429
|
const at = `${where} › children[${i}]`;
|
|
827
|
-
const accessor = step
|
|
1430
|
+
const accessor = stepOf(step);
|
|
828
1431
|
const of = building(at, kt, () => reifyAccessor([rowType, kt], onEntry(accessor)));
|
|
829
1432
|
const cc = Expr.type(of).output;
|
|
830
1433
|
const writes = step.series
|
|
@@ -832,11 +1435,11 @@ function dataSpec(rowType, cfg, recipe) {
|
|
|
832
1435
|
.filter((w) => w !== undefined);
|
|
833
1436
|
if (writes.length === 0)
|
|
834
1437
|
return [];
|
|
835
|
-
const ref = childRefOf(rowType, kt, accessor);
|
|
1438
|
+
const ref = step.field !== undefined ? { kind: "field", field: step.field } : childRefOf(rowType, kt, accessor);
|
|
836
1439
|
if (ref === undefined) {
|
|
837
1440
|
throw new Error(`${at}: a series under this step-down takes gestures, and one on its rows is written back into ` +
|
|
838
|
-
"the entry through
|
|
839
|
-
"
|
|
1441
|
+
"the entry through the step-down — which must read a field of the entry, or be the entry itself, not " +
|
|
1442
|
+
"compute a collection");
|
|
840
1443
|
}
|
|
841
1444
|
const { entry: childType, key: kc } = shapeOf(cc, at);
|
|
842
1445
|
return [{ ref, cc, kc, childType, writes }];
|
|
@@ -875,7 +1478,7 @@ function dataSpec(rowType, cfg, recipe) {
|
|
|
875
1478
|
const writerFor = (kt) => writers.get(kt, () => {
|
|
876
1479
|
const own = ownFor(kt);
|
|
877
1480
|
const below = children === undefined ? undefined
|
|
878
|
-
:
|
|
1481
|
+
: tree !== undefined ? walkWriterFor(kt) : stepsWriterFor(kt);
|
|
879
1482
|
if (own === undefined && below === undefined)
|
|
880
1483
|
return undefined;
|
|
881
1484
|
return East.function([rowType, kt, PathType, IntegerType, StringType, PlanGestureType, PlanCarriedType], OptionType(rowType), ($, value, key, path, depth, series, gesture, carried) => {
|
|
@@ -901,6 +1504,21 @@ function dataSpec(rowType, cfg, recipe) {
|
|
|
901
1504
|
nested,
|
|
902
1505
|
nests: children !== undefined,
|
|
903
1506
|
charts,
|
|
1507
|
+
reads: recipe.reads ?? [],
|
|
1508
|
+
nest: {
|
|
1509
|
+
rowType,
|
|
1510
|
+
keyType: cfg.keyType ?? StringType,
|
|
1511
|
+
accessors: accessorsOf(cfg),
|
|
1512
|
+
tree: treeField,
|
|
1513
|
+
steps: steps.map((step, i) => ({
|
|
1514
|
+
field: step.field, of: step.of,
|
|
1515
|
+
series: step.series.map((x) => nestedSpec(x, `${where} › children[${i}]`)),
|
|
1516
|
+
})),
|
|
1517
|
+
members: [],
|
|
1518
|
+
aggregate: recipe.aggregate,
|
|
1519
|
+
subtotalsOwn: recipe.subtotalsOwn === true,
|
|
1520
|
+
rollup: recipe.rollup,
|
|
1521
|
+
},
|
|
904
1522
|
emitter(collection, at) {
|
|
905
1523
|
return emitters.get(collection, () => {
|
|
906
1524
|
const { entry, key: kt } = shapeOf(collection, at);
|
|
@@ -993,10 +1611,13 @@ function viewsSpec(rowType, cfg, members) {
|
|
|
993
1611
|
const emitters = new ByType();
|
|
994
1612
|
const blockLists = new ByType();
|
|
995
1613
|
const writerLists = new ByType();
|
|
996
|
-
// ── A
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1614
|
+
// ── A tree: more of the views' own entries (#1219, #1436) — a field of the
|
|
1615
|
+
// entry, or the event kinds' accessor computing them (#1194) ──
|
|
1616
|
+
const children = cfg.children;
|
|
1617
|
+
const treeField = typeof children === "string" ? children : undefined;
|
|
1618
|
+
const bare = treeField !== undefined
|
|
1619
|
+
? (v) => collectionOn(v, treeField)
|
|
1620
|
+
: typeof children === "function" ? children : undefined;
|
|
1000
1621
|
const recursive = rowType.type === "Recursive";
|
|
1001
1622
|
const nodeOf = (value) => recursive
|
|
1002
1623
|
? value.unwrap()
|
|
@@ -1015,7 +1636,7 @@ function viewsSpec(rowType, cfg, members) {
|
|
|
1015
1636
|
const walkFor = (cc) => walks.get(cc, () => {
|
|
1016
1637
|
const at = `${where} › children`;
|
|
1017
1638
|
const { entry, key: kc } = shapeOf(cc, at);
|
|
1018
|
-
assertEntry(entry, rowType, `${at} (a
|
|
1639
|
+
assertEntry(entry, rowType, `${at} (a tree's children are more of this series' rows — step down to another type with Plan.children)`);
|
|
1019
1640
|
const next = childOf(kc);
|
|
1020
1641
|
const nextType = Expr.type(next).output;
|
|
1021
1642
|
if (!isTypeEqual(nextType, cc)) {
|
|
@@ -1123,10 +1744,10 @@ function viewsSpec(rowType, cfg, members) {
|
|
|
1123
1744
|
});
|
|
1124
1745
|
if (owns.length === 0)
|
|
1125
1746
|
return undefined;
|
|
1126
|
-
const ref = childRefOf(rowType, kt, bare);
|
|
1747
|
+
const ref = treeField !== undefined ? { kind: "field", field: treeField } : childRefOf(rowType, kt, bare);
|
|
1127
1748
|
if (ref === undefined) {
|
|
1128
1749
|
throw new Error(`${at}: a member takes gestures, and one on a child row is written back into its entry through ` +
|
|
1129
|
-
"`children` — which must read a field of the entry
|
|
1750
|
+
"`children` — which must read a field of the entry, not compute a collection");
|
|
1130
1751
|
}
|
|
1131
1752
|
const ops = collectionOps(cc);
|
|
1132
1753
|
const collOf = (value) => (ref.kind === "field" ? entryAccess(value)[ref.field] : nodeOf(value));
|
|
@@ -1170,7 +1791,7 @@ function viewsSpec(rowType, cfg, members) {
|
|
|
1170
1791
|
});
|
|
1171
1792
|
});
|
|
1172
1793
|
// How many chart rows each entry makes (#1398): a row a member — a chart member's own `match` deciding by entry
|
|
1173
|
-
// whether its row shows — and below them a
|
|
1794
|
+
// whether its row shows — and below them a tree's walk of more of the views' entries, each its members'
|
|
1174
1795
|
// rows, or a step-down's series' over the child collection.
|
|
1175
1796
|
const memberCharts = chartsOf(memberSpecs);
|
|
1176
1797
|
const charts = entryCharts(memberCharts, cfg.children === undefined ? 0 : bare !== undefined ? memberCharts : chartsOf(host.nested), cfg.match !== undefined);
|
|
@@ -1181,6 +1802,9 @@ function viewsSpec(rowType, cfg, members) {
|
|
|
1181
1802
|
nested: [...memberSpecs, ...host.nested],
|
|
1182
1803
|
nests: cfg.children !== undefined,
|
|
1183
1804
|
charts,
|
|
1805
|
+
reads: [],
|
|
1806
|
+
// Its members read its own entries; its children are the host envelope's (#1435).
|
|
1807
|
+
nest: { ...host.nest, members: memberSpecs },
|
|
1184
1808
|
emitter(collection, at) {
|
|
1185
1809
|
return emitters.get(collection, () => {
|
|
1186
1810
|
const { entry, key: kt } = shapeOf(collection, at);
|
|
@@ -1323,6 +1947,12 @@ function sectionSpec(cfg, members) {
|
|
|
1323
1947
|
nests: false,
|
|
1324
1948
|
// Each entry makes its members' rows; the header is no entry's (#1398).
|
|
1325
1949
|
charts: chartsOf(memberSpecs),
|
|
1950
|
+
reads: [],
|
|
1951
|
+
// Its members read the section's entries; it reads nothing of its own (#1435).
|
|
1952
|
+
nest: {
|
|
1953
|
+
rowType: undefined, keyType: StringType, accessors: [], tree: undefined, steps: [], members: memberSpecs,
|
|
1954
|
+
aggregate: undefined, subtotalsOwn: false, rollup: undefined,
|
|
1955
|
+
},
|
|
1326
1956
|
emitter(collection, at) {
|
|
1327
1957
|
return emitters.get(collection, () => {
|
|
1328
1958
|
const blocks = memberSpecs.map((m) => m.emitter(collection, `${at} › ${where}`));
|
|
@@ -1393,6 +2023,7 @@ function rowsSpec(identity, rows) {
|
|
|
1393
2023
|
// Its rows' kinds are an East value the build does not read (#1398): laid out below an entry, their chart rows are
|
|
1394
2024
|
// not known.
|
|
1395
2025
|
charts: undefined,
|
|
2026
|
+
reads: [],
|
|
1396
2027
|
emitter,
|
|
1397
2028
|
// A FIXED block: no entry produces hand-built rows, so every window
|
|
1398
2029
|
// serves them alike and the canvas draws them once (#823).
|
|
@@ -1520,17 +2151,34 @@ export function applySeriesValue(s, data) {
|
|
|
1520
2151
|
}
|
|
1521
2152
|
/**
|
|
1522
2153
|
* Check a TS series list before it is applied — its keys unique across the
|
|
1523
|
-
* whole tree
|
|
1524
|
-
*
|
|
2154
|
+
* whole tree, and its values collections keyed by the axis's instants (#1436).
|
|
2155
|
+
* An East expression list is checked at render (a repeated id is a row
|
|
2156
|
+
* diagnostic), since its series are not known until it runs.
|
|
1525
2157
|
*
|
|
1526
2158
|
* @param series - The `series` input
|
|
1527
2159
|
* @param where - Who is asking, for the message
|
|
2160
|
+
* @param axisKind - The axis's kind, when its declaration says it
|
|
1528
2161
|
*/
|
|
1529
|
-
export function checkSeries(series, where) {
|
|
2162
|
+
export function checkSeries(series, where, axisKind) {
|
|
1530
2163
|
if (!Array.isArray(series))
|
|
1531
2164
|
return;
|
|
1532
2165
|
const specs = series.map((s) => specOf(s)).filter((s) => s !== undefined);
|
|
1533
2166
|
assertUniqueKeys(specs, where);
|
|
2167
|
+
// A values collection is keyed by its instant (#1436): one keyed by String
|
|
2168
|
+
// rides only an ordinal axis.
|
|
2169
|
+
if (axisKind === undefined || axisKind === "ordinal")
|
|
2170
|
+
return;
|
|
2171
|
+
const walk = (spec) => {
|
|
2172
|
+
for (const use of spec.reads) {
|
|
2173
|
+
if (use.read.kind !== "values" || !isTypeEqual(use.key, StringType))
|
|
2174
|
+
continue;
|
|
2175
|
+
throw new Error(`Plan.values: "${use.field}" is keyed by String, and the axis is ${axisKind} — values are keyed by their ` +
|
|
2176
|
+
`instant, ${axisKind === "time" ? "a DateTime" : "a Float or an Integer"} here: read things people drag ` +
|
|
2177
|
+
`with Plan.items("${use.field}", times, draw)`);
|
|
2178
|
+
}
|
|
2179
|
+
spec.nested.forEach(walk);
|
|
2180
|
+
};
|
|
2181
|
+
specs.forEach(walk);
|
|
1534
2182
|
}
|
|
1535
2183
|
/**
|
|
1536
2184
|
* Reads a series value's facts off the spec its builder recorded (#1190).
|
|
@@ -1669,9 +2317,6 @@ export function seriesWriteFn(series, collection) {
|
|
|
1669
2317
|
return result;
|
|
1670
2318
|
});
|
|
1671
2319
|
}
|
|
1672
|
-
// ============================================================================
|
|
1673
|
-
// Builders — Plan.series.*
|
|
1674
|
-
// ============================================================================
|
|
1675
2320
|
/**
|
|
1676
2321
|
* A span series — one state-run row per entry; a row with children shows its
|
|
1677
2322
|
* own runs plus bands over its subtree's runs (`rollup`) — each band sums its
|
|
@@ -1680,27 +2325,46 @@ export function seriesWriteFn(series, collection) {
|
|
|
1680
2325
|
* @typeParam R - The entry type
|
|
1681
2326
|
* @typeParam K - The axis kind the runs imply (inferred from `runs`; `never` when erased)
|
|
1682
2327
|
* @typeParam KT - The entries' key type (`keyType`; `String` by default)
|
|
2328
|
+
* @typeParam FR - The collection its runs read
|
|
2329
|
+
* @typeParam UR - What its runs read
|
|
2330
|
+
* @typeParam UD - What its decisions read
|
|
2331
|
+
* @typeParam UP - What its ports read
|
|
2332
|
+
* @typeParam CT - A tree's field, as `children` names it
|
|
2333
|
+
* @typeParam CU - One step-down's reads
|
|
2334
|
+
* @typeParam CF - The folds of its cells
|
|
2335
|
+
* @typeParam CS - Several step-downs' reads
|
|
2336
|
+
* @typeParam RL - Its `rollup`, as written
|
|
1683
2337
|
* @param rowType - The entry type (every builder takes it first — the `Slice.config` shape)
|
|
1684
|
-
* @param config - The accessors and nesting ({@link PlanSpanSeriesConfig})
|
|
1685
|
-
* @returns A series value, branded with its kind
|
|
2338
|
+
* @param config - The reads, accessors and nesting ({@link PlanSpanSeriesConfig})
|
|
2339
|
+
* @returns A series value, branded with its kind and what it reads
|
|
1686
2340
|
*/
|
|
1687
2341
|
export function createSeriesSpan(rowType, config) {
|
|
1688
2342
|
const cfg = config;
|
|
2343
|
+
const where = `Plan.series.span "${cfg.key}"`;
|
|
2344
|
+
const edit = editRecipeOf(cfg.edit, "runs", "{ from, to }", cfg.runs, where);
|
|
1689
2345
|
// A row rolls up its children: its own (`children`), or — declared on a
|
|
1690
2346
|
// views member — the ones the views nest under it (#1219).
|
|
1691
2347
|
const rollup = cfg.children !== undefined || cfg.rollup !== undefined ? resolveTag(cfg.rollup ?? "union", PlanRollupType) : undefined;
|
|
2348
|
+
const runs = elementField(rowType, cfg.runs, "run", where, "runs");
|
|
2349
|
+
if (runs === undefined)
|
|
2350
|
+
throw new Error(`${where}: give its runs — Plan.items(field, { from, to }, draw), or a function of the row`);
|
|
2351
|
+
const decisions = elementField(rowType, cfg.decisions, "decision", where, "decisions");
|
|
2352
|
+
const ports = elementField(rowType, cfg.ports, "port", where, "ports");
|
|
1692
2353
|
const spec = dataSpec(rowType, cfg, {
|
|
1693
2354
|
arm: "span",
|
|
1694
|
-
kind: (
|
|
1695
|
-
runs:
|
|
1696
|
-
...(
|
|
1697
|
-
...(
|
|
2355
|
+
kind: (node, key, hasChildren, value) => spanKind({
|
|
2356
|
+
runs: runs.at(node, key, value),
|
|
2357
|
+
...(decisions !== undefined ? { decisions: decisions.at(node, key, value) } : {}),
|
|
2358
|
+
...(ports !== undefined ? { ports: ports.at(node, key, value) } : {}),
|
|
1698
2359
|
}, rollup === undefined
|
|
1699
2360
|
? East.value(none, OptionType(PlanRollupType))
|
|
1700
2361
|
// A row rolls up only when it HAS child rows — a leaf's bands
|
|
1701
2362
|
// would repeat its own runs.
|
|
1702
2363
|
: hasChildren.ifElse(() => East.value(some(rollup), OptionType(PlanRollupType)), () => East.value(none, OptionType(PlanRollupType)))),
|
|
1703
|
-
edit
|
|
2364
|
+
edit,
|
|
2365
|
+
reads: [...runs.reads, ...(decisions?.reads ?? []), ...(ports?.reads ?? [])],
|
|
2366
|
+
// Its runs roll up into its bands where it has children or declares a rollup (#1435).
|
|
2367
|
+
...(cfg.children !== undefined || cfg.rollup !== undefined ? { rollup: tagOf(cfg.rollup, "union") } : {}),
|
|
1704
2368
|
});
|
|
1705
2369
|
return seriesValue(rowType, cfg.keyType, spec, cfg);
|
|
1706
2370
|
}
|
|
@@ -1711,21 +2375,33 @@ export function createSeriesSpan(rowType, config) {
|
|
|
1711
2375
|
* @typeParam R - The entry type
|
|
1712
2376
|
* @typeParam K - The axis kind the cells imply (inferred from `cells`; `never` when erased)
|
|
1713
2377
|
* @typeParam KT - The entries' key type
|
|
2378
|
+
* @typeParam UC - What its cells read
|
|
2379
|
+
* @typeParam FO - How a bucket folds them
|
|
2380
|
+
* @typeParam A - Its `aggregate`, as written
|
|
2381
|
+
* @typeParam CT - A tree's field, as `children` names it
|
|
2382
|
+
* @typeParam CU - One step-down's reads
|
|
2383
|
+
* @typeParam CF - The folds of its cells
|
|
2384
|
+
* @typeParam CS - Several step-downs' reads
|
|
1714
2385
|
* @param rowType - The entry type
|
|
1715
|
-
* @param config - The accessors and nesting ({@link PlanHeatSeriesConfig})
|
|
1716
|
-
* @returns A series value, branded with its kind
|
|
2386
|
+
* @param config - The reads, accessors and nesting ({@link PlanHeatSeriesConfig})
|
|
2387
|
+
* @returns A series value, branded with its kind, what it reads and the fold of its cells
|
|
1717
2388
|
*/
|
|
1718
2389
|
export function createSeriesHeat(rowType, config) {
|
|
1719
2390
|
const cfg = config;
|
|
2391
|
+
const where = `Plan.series.heat "${cfg.key}"`;
|
|
1720
2392
|
const aggregate = cfg.children !== undefined
|
|
1721
2393
|
? East.value(some(resolveTag(cfg.aggregate ?? "mean", PlanAggregateType)), OptionType(PlanAggregateType))
|
|
1722
2394
|
: East.value(none, OptionType(PlanAggregateType));
|
|
2395
|
+
const cells = consumerField(rowType, cfg.cells, "heat", where, "cells");
|
|
1723
2396
|
const spec = dataSpec(rowType, cfg, {
|
|
1724
2397
|
arm: "heat",
|
|
1725
|
-
kind: (
|
|
1726
|
-
cells:
|
|
2398
|
+
kind: (node, key, _hasChildren, value) => heatKind({
|
|
2399
|
+
...(cells !== undefined ? { cells: cells.at(node, key, value) } : {}),
|
|
1727
2400
|
...(cfg.scale !== undefined ? { scale: cfg.scale } : {}),
|
|
1728
2401
|
}, aggregate),
|
|
2402
|
+
reads: cells?.reads ?? [],
|
|
2403
|
+
// Its row derives its children's numbers by its aggregate: a tree's own values, or a step-down's (#1435).
|
|
2404
|
+
...(cfg.children !== undefined ? { aggregate: tagOf(cfg.aggregate, "mean"), subtotalsOwn: true } : {}),
|
|
1729
2405
|
});
|
|
1730
2406
|
return seriesValue(rowType, cfg.keyType, spec, cfg);
|
|
1731
2407
|
}
|
|
@@ -1736,28 +2412,48 @@ export function createSeriesHeat(rowType, config) {
|
|
|
1736
2412
|
* @typeParam R - The entry type
|
|
1737
2413
|
* @typeParam K - The axis kind the cells imply (inferred from `cells` / `series`; `never` when erased)
|
|
1738
2414
|
* @typeParam KT - The entries' key type
|
|
2415
|
+
* @typeParam UC - What its `cells` read
|
|
2416
|
+
* @typeParam S - Its `series` positions, as written
|
|
2417
|
+
* @typeParam FO - Its `cells`' fold, as written
|
|
2418
|
+
* @typeParam A - Its `aggregate`, as written
|
|
2419
|
+
* @typeParam CT - A tree's field, as `children` names it
|
|
2420
|
+
* @typeParam CU - One step-down's reads
|
|
2421
|
+
* @typeParam CF - The folds of its cells
|
|
2422
|
+
* @typeParam CS - Several step-downs' reads
|
|
1739
2423
|
* @param rowType - The entry type
|
|
1740
|
-
* @param config - The accessors and nesting ({@link PlanTableSeriesOfConfig})
|
|
1741
|
-
* @returns A series value, branded with its kind
|
|
2424
|
+
* @param config - The reads, accessors and nesting ({@link PlanTableSeriesOfConfig})
|
|
2425
|
+
* @returns A series value, branded with its kind, what it reads and the folds of its positions
|
|
1742
2426
|
*/
|
|
1743
2427
|
export function createSeriesTable(rowType, config) {
|
|
1744
2428
|
const cfg = config;
|
|
2429
|
+
const where = `Plan.series.table "${cfg.key}"`;
|
|
1745
2430
|
if (cfg.cells !== undefined && cfg.series !== undefined) {
|
|
1746
|
-
throw new Error(
|
|
2431
|
+
throw new Error(`${where}: pass \`cells\` (one plain value series) OR \`series\` — not both`);
|
|
1747
2432
|
}
|
|
1748
2433
|
const aggregate = cfg.children !== undefined
|
|
1749
2434
|
? East.value(some(resolveTag(cfg.aggregate ?? "sum", TableAggregateType)), OptionType(TableAggregateType))
|
|
1750
2435
|
: East.value(none, OptionType(TableAggregateType));
|
|
2436
|
+
const cells = consumerField(rowType, cfg.cells, "tableCells", where, "cells");
|
|
2437
|
+
// Its cells fold as the row declares, `sum` unless it says (#1436): what a parent subtotals them by (#1435, #1438).
|
|
2438
|
+
const rowFold = tagOf(cfg.fold, "sum");
|
|
2439
|
+
const cellReads = (cells?.reads ?? []).map((read) => (read.fold !== undefined || rowFold === undefined ? read : { ...read, fold: rowFold }));
|
|
2440
|
+
const positions = listField(rowType, cfg.series, "tableSeries", where, "series");
|
|
2441
|
+
// The positions a typed number writes (#1436), each read checked as it built above.
|
|
2442
|
+
const values = tableEditsOf(rowType, cfg, where);
|
|
1751
2443
|
const spec = dataSpec(rowType, cfg, {
|
|
1752
2444
|
arm: "table",
|
|
1753
|
-
kind: (
|
|
1754
|
-
...(
|
|
2445
|
+
kind: (node, key, _hasChildren, value) => tableKind({
|
|
2446
|
+
...(cells !== undefined ? { cells: cells.at(node, key, value) } : {}),
|
|
1755
2447
|
...(cfg.fold !== undefined ? { fold: cfg.fold } : {}),
|
|
1756
|
-
...(
|
|
2448
|
+
...(positions !== undefined ? { series: positions.at(node, key, value) } : {}),
|
|
1757
2449
|
...(cfg.split !== undefined ? { split: cfg.split } : {}),
|
|
1758
2450
|
...(cfg.format !== undefined ? { format: cfg.format } : {}),
|
|
1759
2451
|
...(cfg.emphasis !== undefined ? { emphasis: cfg.emphasis } : {}),
|
|
1760
2452
|
}, aggregate),
|
|
2453
|
+
reads: [...cellReads, ...(positions?.reads ?? [])],
|
|
2454
|
+
values,
|
|
2455
|
+
// Its row derives its children's numbers by its aggregate: a tree's own values, or a step-down's (#1435).
|
|
2456
|
+
...(cfg.children !== undefined ? { aggregate: tagOf(cfg.aggregate, "sum"), subtotalsOwn: true } : {}),
|
|
1761
2457
|
});
|
|
1762
2458
|
return seriesValue(rowType, cfg.keyType, spec, cfg);
|
|
1763
2459
|
}
|
|
@@ -1767,20 +2463,34 @@ export function createSeriesTable(rowType, config) {
|
|
|
1767
2463
|
* @typeParam R - The entry type
|
|
1768
2464
|
* @typeParam K - The axis kind the tiles imply (inferred from `events` / `markers`; `never` when erased)
|
|
1769
2465
|
* @typeParam KT - The entries' key type
|
|
2466
|
+
* @typeParam FE - The collection its tiles read
|
|
2467
|
+
* @typeParam UE - What its tiles read
|
|
2468
|
+
* @typeParam UM - What its markers read
|
|
2469
|
+
* @typeParam CT - A tree's field, as `children` names it
|
|
2470
|
+
* @typeParam CU - One step-down's reads
|
|
2471
|
+
* @typeParam CF - The folds of its cells
|
|
2472
|
+
* @typeParam CS - Several step-downs' reads
|
|
1770
2473
|
* @param rowType - The entry type
|
|
1771
|
-
* @param config - The accessors ({@link PlanBucketsSeriesConfig})
|
|
1772
|
-
* @returns A series value, branded with its kind
|
|
2474
|
+
* @param config - The reads and accessors ({@link PlanBucketsSeriesConfig})
|
|
2475
|
+
* @returns A series value, branded with its kind and what it reads
|
|
1773
2476
|
*/
|
|
1774
2477
|
export function createSeriesBuckets(rowType, config) {
|
|
1775
2478
|
const cfg = config;
|
|
2479
|
+
const where = `Plan.series.buckets "${cfg.key}"`;
|
|
2480
|
+
const edit = editRecipeOf(cfg.edit, "events", "{ at }", cfg.events, where);
|
|
2481
|
+
const events = elementField(rowType, cfg.events, "event", where, "events");
|
|
2482
|
+
if (events === undefined)
|
|
2483
|
+
throw new Error(`${where}: give its events — Plan.items(field, { at }, draw), or a function of the row`);
|
|
2484
|
+
const markers = elementField(rowType, cfg.markers, "marker", where, "markers");
|
|
1776
2485
|
const spec = dataSpec(rowType, cfg, {
|
|
1777
2486
|
arm: "buckets",
|
|
1778
|
-
kind: (
|
|
1779
|
-
...(cfg.lanes !== undefined ? { lanes: cfg.lanes(
|
|
1780
|
-
events:
|
|
1781
|
-
...(
|
|
2487
|
+
kind: (node, key, _hasChildren, value) => bucketsKind({
|
|
2488
|
+
...(cfg.lanes !== undefined ? { lanes: cfg.lanes(node, key) } : {}),
|
|
2489
|
+
events: events.at(node, key, value),
|
|
2490
|
+
...(markers !== undefined ? { markers: markers.at(node, key, value) } : {}),
|
|
1782
2491
|
}),
|
|
1783
|
-
edit
|
|
2492
|
+
edit,
|
|
2493
|
+
reads: [...events.reads, ...(markers?.reads ?? [])],
|
|
1784
2494
|
});
|
|
1785
2495
|
return seriesValue(rowType, cfg.keyType, spec, cfg);
|
|
1786
2496
|
}
|
|
@@ -1790,16 +2500,28 @@ export function createSeriesBuckets(rowType, config) {
|
|
|
1790
2500
|
* @typeParam R - The entry type
|
|
1791
2501
|
* @typeParam K - The axis kind the chips imply (inferred from `chips`; `never` when erased)
|
|
1792
2502
|
* @typeParam KT - The entries' key type
|
|
2503
|
+
* @typeParam FC - The collection its chips read
|
|
2504
|
+
* @typeParam UC - What its chips read
|
|
2505
|
+
* @typeParam CT - A tree's field, as `children` names it
|
|
2506
|
+
* @typeParam CU - One step-down's reads
|
|
2507
|
+
* @typeParam CF - The folds of its cells
|
|
2508
|
+
* @typeParam CS - Several step-downs' reads
|
|
1793
2509
|
* @param rowType - The entry type
|
|
1794
|
-
* @param config - The accessors ({@link PlanCardsSeriesConfig})
|
|
1795
|
-
* @returns A series value, branded with its kind
|
|
2510
|
+
* @param config - The reads and accessors ({@link PlanCardsSeriesConfig})
|
|
2511
|
+
* @returns A series value, branded with its kind and what it reads
|
|
1796
2512
|
*/
|
|
1797
2513
|
export function createSeriesCards(rowType, config) {
|
|
1798
2514
|
const cfg = config;
|
|
2515
|
+
const where = `Plan.series.cards "${cfg.key}"`;
|
|
2516
|
+
const edit = editRecipeOf(cfg.edit, "chips", "{ from, to }", cfg.chips, where);
|
|
2517
|
+
const chips = elementField(rowType, cfg.chips, "chip", where, "chips");
|
|
2518
|
+
if (chips === undefined)
|
|
2519
|
+
throw new Error(`${where}: give its chips — Plan.items(field, { from, to }, draw), or a function of the row`);
|
|
1799
2520
|
const spec = dataSpec(rowType, cfg, {
|
|
1800
2521
|
arm: "cards",
|
|
1801
|
-
kind: (
|
|
1802
|
-
edit
|
|
2522
|
+
kind: (node, key, _hasChildren, value) => cardsKind(chips.at(node, key, value)),
|
|
2523
|
+
edit,
|
|
2524
|
+
reads: chips.reads,
|
|
1803
2525
|
});
|
|
1804
2526
|
return seriesValue(rowType, cfg.keyType, spec, cfg);
|
|
1805
2527
|
}
|
|
@@ -1809,37 +2531,61 @@ export function createSeriesCards(rowType, config) {
|
|
|
1809
2531
|
* @typeParam R - The entry type
|
|
1810
2532
|
* @typeParam K - The axis kind the marks imply (inferred from `marks`; `never` when erased)
|
|
1811
2533
|
* @typeParam KT - The entries' key type
|
|
2534
|
+
* @typeParam FM - The collection its marks read
|
|
2535
|
+
* @typeParam UM - What its marks read
|
|
2536
|
+
* @typeParam CT - A tree's field, as `children` names it
|
|
2537
|
+
* @typeParam CU - One step-down's reads
|
|
2538
|
+
* @typeParam CF - The folds of its cells
|
|
2539
|
+
* @typeParam CS - Several step-downs' reads
|
|
1812
2540
|
* @param rowType - The entry type
|
|
1813
|
-
* @param config - The accessors ({@link PlanEventsSeriesConfig})
|
|
1814
|
-
* @returns A series value, branded with its kind
|
|
2541
|
+
* @param config - The reads and accessors ({@link PlanEventsSeriesConfig})
|
|
2542
|
+
* @returns A series value, branded with its kind and what it reads
|
|
1815
2543
|
*/
|
|
1816
2544
|
export function createSeriesEvents(rowType, config) {
|
|
1817
2545
|
const cfg = config;
|
|
2546
|
+
const where = `Plan.series.events "${cfg.key}"`;
|
|
2547
|
+
const edit = editRecipeOf(cfg.edit, "marks", "{ at }", cfg.marks, where);
|
|
2548
|
+
const marks = elementField(rowType, cfg.marks, "mark", where, "marks");
|
|
2549
|
+
if (marks === undefined)
|
|
2550
|
+
throw new Error(`${where}: give its marks — Plan.items(field, { at }, draw), or a function of the row`);
|
|
1818
2551
|
const spec = dataSpec(rowType, cfg, {
|
|
1819
2552
|
arm: "events",
|
|
1820
|
-
kind: (
|
|
1821
|
-
edit
|
|
2553
|
+
kind: (node, key, _hasChildren, value) => eventsKind(marks.at(node, key, value)),
|
|
2554
|
+
edit,
|
|
2555
|
+
reads: marks.reads,
|
|
1822
2556
|
});
|
|
1823
2557
|
return seriesValue(rowType, cfg.keyType, spec, cfg);
|
|
1824
2558
|
}
|
|
1825
2559
|
/**
|
|
1826
|
-
* A chart series — one chart row per entry, layers
|
|
1827
|
-
*
|
|
1828
|
-
*
|
|
1829
|
-
*
|
|
2560
|
+
* A chart series — one chart row per entry, its layers over the entry's own
|
|
2561
|
+
* values (`Plan.line` … `Plan.band` over `Plan.values`), or built from the
|
|
2562
|
+
* entry by a function. Its layers over values brand it with the axis kind
|
|
2563
|
+
* their keys ride; a Chart layer's face hides its x type, so a series whose
|
|
2564
|
+
* layers are all Chart layers constrains no axis at compile time — the
|
|
2565
|
+
* render-time diagnostic holds it to the axis like any row.
|
|
1830
2566
|
*
|
|
1831
2567
|
* @typeParam R - The entry type
|
|
1832
2568
|
* @typeParam KT - The entries' key type
|
|
2569
|
+
* @typeParam K - The axis kind its layers' values ride
|
|
2570
|
+
* @typeParam L - Its `layers`, as written
|
|
2571
|
+
* @typeParam CT - A tree's field, as `children` names it
|
|
2572
|
+
* @typeParam CU - One step-down's reads
|
|
2573
|
+
* @typeParam CF - The folds of its cells
|
|
2574
|
+
* @typeParam CS - Several step-downs' reads
|
|
1833
2575
|
* @param rowType - The entry type
|
|
1834
|
-
* @param config - The accessors and shared axes ({@link PlanChartSeriesConfig})
|
|
1835
|
-
* @returns A series value
|
|
2576
|
+
* @param config - The layers, accessors and shared axes ({@link PlanChartSeriesConfig})
|
|
2577
|
+
* @returns A series value, branded with its kind and what it reads
|
|
1836
2578
|
*/
|
|
1837
2579
|
export function createSeriesChart(rowType, config) {
|
|
1838
2580
|
const cfg = config;
|
|
2581
|
+
const where = `Plan.series.chart "${cfg.key}"`;
|
|
2582
|
+
const layers = listField(rowType, cfg.layers, "layer", where, "layers");
|
|
2583
|
+
if (layers === undefined)
|
|
2584
|
+
throw new Error(`${where}: give its layers — Plan.line(Plan.values(field, draw)) and the like, or a function of the row`);
|
|
1839
2585
|
const spec = dataSpec(rowType, cfg, {
|
|
1840
2586
|
arm: "chart",
|
|
1841
|
-
kind: (
|
|
1842
|
-
layers:
|
|
2587
|
+
kind: (node, key, _hasChildren, value) => chartKind({
|
|
2588
|
+
layers: layers.at(node, key, value),
|
|
1843
2589
|
...(cfg.left !== undefined ? { left: cfg.left } : {}),
|
|
1844
2590
|
...(cfg.right !== undefined ? { right: cfg.right } : {}),
|
|
1845
2591
|
...(cfg.height !== undefined ? { height: cfg.height } : {}),
|
|
@@ -1850,6 +2596,7 @@ export function createSeriesChart(rowType, config) {
|
|
|
1850
2596
|
? { pinned: (value, key) => East.value(cfg.pinned(value, key), BooleanType) }
|
|
1851
2597
|
: {}),
|
|
1852
2598
|
...(cfg.swatches !== undefined ? { swatches: cfg.swatches } : {}),
|
|
2599
|
+
reads: layers.reads,
|
|
1853
2600
|
});
|
|
1854
2601
|
return seriesValue(rowType, cfg.keyType, spec, cfg);
|
|
1855
2602
|
}
|
|
@@ -1860,19 +2607,25 @@ export function createSeriesChart(rowType, config) {
|
|
|
1860
2607
|
* its member count is derived.
|
|
1861
2608
|
*
|
|
1862
2609
|
* @remarks
|
|
1863
|
-
* Hierarchy comes from the data's nesting:
|
|
1864
|
-
*
|
|
1865
|
-
*
|
|
1866
|
-
*
|
|
1867
|
-
*
|
|
1868
|
-
* fixed titled block.
|
|
2610
|
+
* Hierarchy comes from the data's nesting: a group's members are a `Dict` of
|
|
2611
|
+
* the entry (`Plan.children(field, [series…])`), so a flat source is grouped
|
|
2612
|
+
* in the dataflow, or reshaped inline into entries that hold their members.
|
|
2613
|
+
* The `by` form and the static `group(R, chrome, children)` form are gone —
|
|
2614
|
+
* see `Plan.series.section` for a fixed titled block.
|
|
1869
2615
|
*
|
|
1870
2616
|
* @typeParam R - The entry type
|
|
1871
2617
|
* @typeParam K - The axis kind the summary cells imply
|
|
1872
2618
|
* @typeParam KT - The entries' key type
|
|
2619
|
+
* @typeParam US - What its `summary` reads
|
|
2620
|
+
* @typeParam FO - How a bucket folds its summary cells
|
|
2621
|
+
* @typeParam A - Its `summaryAggregate`, as written
|
|
2622
|
+
* @typeParam CT - A tree's field, as `children` names it
|
|
2623
|
+
* @typeParam CU - One step-down's reads
|
|
2624
|
+
* @typeParam CF - The folds of its cells
|
|
2625
|
+
* @typeParam CS - Several step-downs' reads
|
|
1873
2626
|
* @param rowType - The entry type
|
|
1874
2627
|
* @param config - The strip's accessors and its members ({@link PlanGroupSeriesConfig})
|
|
1875
|
-
* @returns A series value, branded with its kind
|
|
2628
|
+
* @returns A series value, branded with its kind and what it reads
|
|
1876
2629
|
* @throws {Error} When called with the removed `by` or static forms
|
|
1877
2630
|
*/
|
|
1878
2631
|
export function createSeriesGroup(rowType, config) {
|
|
@@ -1888,17 +2641,20 @@ export function createSeriesGroup(rowType, config) {
|
|
|
1888
2641
|
if (cfg.children === undefined) {
|
|
1889
2642
|
throw new Error(`${where}: a group strip needs its members — declare \`children\``);
|
|
1890
2643
|
}
|
|
1891
|
-
|
|
1892
|
-
// The strip's ONE summary (#824): the per-entry cells, the declared
|
|
1893
|
-
// aggregate, or a plain band — both at once is refused here.
|
|
1894
|
-
const declared = summary === undefined ? groupSummary(undefined, cfg.summaryAggregate, where) : undefined;
|
|
1895
|
-
if (summary !== undefined && cfg.summaryAggregate !== undefined) {
|
|
2644
|
+
if (cfg.summary !== undefined && cfg.summaryAggregate !== undefined) {
|
|
1896
2645
|
throw new Error(`${where}: give \`summary\` (explicit strip cells) OR \`summaryAggregate\` (cells derived from the ` +
|
|
1897
2646
|
"members) — a collapsed strip shows one or the other");
|
|
1898
2647
|
}
|
|
2648
|
+
const summary = consumerField(rowType, cfg.summary, "heat", where, "summary");
|
|
2649
|
+
// The strip's ONE summary (#824): the per-entry cells, the declared
|
|
2650
|
+
// aggregate, or a plain band.
|
|
2651
|
+
const declared = summary === undefined ? groupSummary(undefined, cfg.summaryAggregate, where) : undefined;
|
|
1899
2652
|
const spec = dataSpec(rowType, cfg, {
|
|
1900
2653
|
arm: "group",
|
|
1901
|
-
kind: (
|
|
2654
|
+
kind: (node, key, _hasChildren, value) => groupKind(declared ?? East.value(variant("cells", East.value(summary.at(node, key, value), PlanHeatCellsType)), PlanGroupSummaryType)),
|
|
2655
|
+
reads: summary?.reads ?? [],
|
|
2656
|
+
// Its strip derives its members' numbers by its summaryAggregate (#1435).
|
|
2657
|
+
...(cfg.summaryAggregate !== undefined ? { aggregate: tagOf(cfg.summaryAggregate, "mean") } : {}),
|
|
1902
2658
|
});
|
|
1903
2659
|
return seriesValue(rowType, cfg.keyType, spec, cfg);
|
|
1904
2660
|
}
|
|
@@ -1910,10 +2666,11 @@ export function createSeriesGroup(rowType, config) {
|
|
|
1910
2666
|
*
|
|
1911
2667
|
* @typeParam R - The entry type (the members' — they share the section's entries)
|
|
1912
2668
|
* @typeParam K - The union of the members' axis kinds (inferred; `never` when all are erased)
|
|
2669
|
+
* @typeParam S - The members, as written
|
|
1913
2670
|
* @param rowType - The entry type
|
|
1914
2671
|
* @param config - The header ({@link PlanSectionSeriesConfig})
|
|
1915
2672
|
* @param members - The member series, in order
|
|
1916
|
-
* @returns A series value, branded with its members' kinds
|
|
2673
|
+
* @returns A series value, branded with its members' kinds and what they read
|
|
1917
2674
|
*/
|
|
1918
2675
|
export function createSeriesSection(rowType, config, members) {
|
|
1919
2676
|
const cfg = config;
|
|
@@ -1932,7 +2689,7 @@ export function createSeriesSection(rowType, config, members) {
|
|
|
1932
2689
|
* view row's id is its member series' key and the entry's path, and a seek on
|
|
1933
2690
|
* the entry lands on its first view row.
|
|
1934
2691
|
*
|
|
1935
|
-
* The views' `children` are a step-down's series, or — a
|
|
2692
|
+
* The views' `children` are a step-down's series, or — a tree's field — more
|
|
1936
2693
|
* of the views' own entries, each drawn as its members' rows, to any depth,
|
|
1937
2694
|
* the views' `match` and `collapsed` at every level (#1219). A span member that
|
|
1938
2695
|
* declares `rollup` rolls up the children nested under its row.
|
|
@@ -1940,10 +2697,15 @@ export function createSeriesSection(rowType, config, members) {
|
|
|
1940
2697
|
* @typeParam R - The entry type
|
|
1941
2698
|
* @typeParam K - The union of the members' axis kinds (inferred; `never` when all are erased)
|
|
1942
2699
|
* @typeParam KT - The entries' key type
|
|
2700
|
+
* @typeParam S - The members, as written
|
|
2701
|
+
* @typeParam CT - A tree's field, as `children` names it
|
|
2702
|
+
* @typeParam CU - One step-down's reads
|
|
2703
|
+
* @typeParam CF - The folds of its cells
|
|
2704
|
+
* @typeParam CS - Several step-downs' reads
|
|
1943
2705
|
* @param rowType - The entry type
|
|
1944
2706
|
* @param config - Membership, collapse and children ({@link PlanViewsSeriesConfig})
|
|
1945
2707
|
* @param members - The member series — one row each per entry, in order
|
|
1946
|
-
* @returns A series value, branded with its members' kinds
|
|
2708
|
+
* @returns A series value, branded with its members' kinds and what it reads
|
|
1947
2709
|
*/
|
|
1948
2710
|
export function createSeriesViews(rowType, config, members) {
|
|
1949
2711
|
const cfg = config;
|