@tellegen/svelte 0.4.3 → 0.4.4
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 +20 -0
- package/dist/TellegenMap.svelte +63 -22
- package/dist/TellegenProvider.svelte +7 -4
- package/dist/TellegenShell.svelte +10 -1
- package/dist/TellegenViewer.svelte +2 -0
- package/dist/basemap.d.ts +14 -0
- package/dist/basemap.js +27 -0
- package/dist/colors.d.ts +6 -1
- package/dist/colors.js +11 -4
- package/dist/components/FormulationSelector.svelte +1 -1
- package/dist/components/LocalCaseDetails.svelte +18 -3
- package/dist/components/McResults.svelte +18 -3
- package/dist/components/McWorkbench.svelte +688 -0
- package/dist/components/McWorkbench.svelte.d.ts +38 -0
- package/dist/components/MultiWire.svelte +136 -0
- package/dist/components/MultiWire.svelte.d.ts +7 -0
- package/dist/components/MulticonductorDetails.svelte +7 -0
- package/dist/components/OmniSearch.svelte +163 -0
- package/dist/components/OmniSearch.svelte.d.ts +14 -0
- package/dist/components/ResultsTable.svelte +259 -0
- package/dist/components/ResultsTable.svelte.d.ts +13 -0
- package/dist/components/SchematicCanvas.svelte +1896 -0
- package/dist/components/SchematicCanvas.svelte.d.ts +13 -0
- package/dist/components/SolveCard.svelte +6 -0
- package/dist/components/schematic/PickCard.svelte +126 -0
- package/dist/components/schematic/PickCard.svelte.d.ts +56 -0
- package/dist/components/schematic/export.d.ts +21 -0
- package/dist/components/schematic/export.js +110 -0
- package/dist/components/schematic/layers.d.ts +161 -0
- package/dist/components/schematic/layers.js +756 -0
- package/dist/components/schematic/paint.d.ts +28 -0
- package/dist/components/schematic/paint.js +69 -0
- package/dist/components/schematic/palette.d.ts +34 -0
- package/dist/components/schematic/palette.js +54 -0
- package/dist/components/schematic/text.d.ts +83 -0
- package/dist/components/schematic/text.js +190 -0
- package/dist/components/schematic/view-prefs.svelte.d.ts +30 -0
- package/dist/components/schematic/view-prefs.svelte.js +77 -0
- package/dist/components.d.ts +1 -0
- package/dist/components.js +1 -0
- package/dist/context.svelte.d.ts +2 -0
- package/dist/context.svelte.js +2 -0
- package/dist/controller.svelte.d.ts +33 -5
- package/dist/controller.svelte.js +173 -20
- package/dist/diagram/basemap.d.ts +28 -0
- package/dist/diagram/basemap.js +39 -0
- package/dist/diagram/bus-stacks.d.ts +19 -0
- package/dist/diagram/bus-stacks.js +99 -0
- package/dist/diagram/colour-modes.d.ts +18 -0
- package/dist/diagram/colour-modes.js +33 -0
- package/dist/diagram/connections.d.ts +30 -0
- package/dist/diagram/connections.js +129 -0
- package/dist/diagram/display-tree.d.ts +21 -0
- package/dist/diagram/display-tree.js +65 -0
- package/dist/diagram/energisation.d.ts +10 -0
- package/dist/diagram/energisation.js +31 -0
- package/dist/diagram/feeders.d.ts +30 -0
- package/dist/diagram/feeders.js +57 -0
- package/dist/diagram/galvanic-zones.d.ts +60 -0
- package/dist/diagram/galvanic-zones.js +210 -0
- package/dist/diagram/graph-inputs.d.ts +18 -0
- package/dist/diagram/graph-inputs.js +103 -0
- package/dist/diagram/index.d.ts +28 -0
- package/dist/diagram/index.js +28 -0
- package/dist/diagram/landmarks.d.ts +50 -0
- package/dist/diagram/landmarks.js +153 -0
- package/dist/diagram/layout-morph.d.ts +15 -0
- package/dist/diagram/layout-morph.js +32 -0
- package/dist/diagram/multi-wire.d.ts +44 -0
- package/dist/diagram/multi-wire.js +65 -0
- package/dist/diagram/phase-bars.d.ts +35 -0
- package/dist/diagram/phase-bars.js +44 -0
- package/dist/diagram/results-table.d.ts +47 -0
- package/dist/diagram/results-table.js +173 -0
- package/dist/diagram/schematic-equipment.d.ts +133 -0
- package/dist/diagram/schematic-equipment.js +645 -0
- package/dist/diagram/schematic-layout.d.ts +51 -0
- package/dist/diagram/schematic-layout.js +240 -0
- package/dist/diagram/search.d.ts +36 -0
- package/dist/diagram/search.js +48 -0
- package/dist/diagram/shared-routes.d.ts +21 -0
- package/dist/diagram/shared-routes.js +40 -0
- package/dist/diagram/short-names.d.ts +11 -0
- package/dist/diagram/short-names.js +54 -0
- package/dist/diagram/snapshot.d.ts +115 -0
- package/dist/diagram/snapshot.js +242 -0
- package/dist/diagram/summary.d.ts +65 -0
- package/dist/diagram/summary.js +144 -0
- package/dist/diagram/svg-export.d.ts +80 -0
- package/dist/diagram/svg-export.js +108 -0
- package/dist/diagram/tree-walk.d.ts +13 -0
- package/dist/diagram/tree-walk.js +83 -0
- package/dist/diagram/value-labels.d.ts +24 -0
- package/dist/diagram/value-labels.js +69 -0
- package/dist/diagram/view-state.d.ts +12 -0
- package/dist/diagram/view-state.js +22 -0
- package/dist/diagram/voltage-levels.d.ts +14 -0
- package/dist/diagram/voltage-levels.js +20 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/state.svelte.d.ts +37 -1
- package/dist/state.svelte.js +35 -0
- package/dist/transformer-icon.d.ts +22 -13
- package/dist/transformer-icon.js +23 -13
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -39,6 +39,7 @@ remain in the browser on this path.
|
|
|
39
39
|
`TellegenViewer` accepts:
|
|
40
40
|
|
|
41
41
|
- `apiBase`, default `/api`
|
|
42
|
+
- `acOpfWasmUrl`, default `null` (optional experimental AC OPF asset)
|
|
42
43
|
- `loadDefaultCases`, default `true`
|
|
43
44
|
- `docsHref`
|
|
44
45
|
- `orgHref`
|
|
@@ -68,6 +69,20 @@ controller, which frees every case's browser Study and multiconductor session
|
|
|
68
69
|
in the engine worker, so a provider that should keep its cases across pages
|
|
69
70
|
belongs in a layout that stays mounted.
|
|
70
71
|
|
|
72
|
+
## Experimental AC OPF
|
|
73
|
+
|
|
74
|
+
A development host can pass `acOpfWasmUrl="/experimental-acopf/tellegen_acopf_wasi.wasm"`
|
|
75
|
+
to `TellegenViewer` or `TellegenProvider`. The asset must be built and served
|
|
76
|
+
separately; the selector enables AC OPF only after a worker probe succeeds.
|
|
77
|
+
See the [local setup and release gates](../../docs/src/acopf-pounce-decision.md#experimental-browser-abi).
|
|
78
|
+
|
|
79
|
+
Balanced networks and stored canonical AC OPF instances are supported at the
|
|
80
|
+
base operating point. Imported instances retain their objective and constraints.
|
|
81
|
+
Demand/rating editing, LMPs, sensitivities, and saving/exporting the AC OPF case
|
|
82
|
+
are unavailable. Geographic sidecars and layout downloads remain available.
|
|
83
|
+
Without the optional worker, imported AC OPF instances remain viewable but
|
|
84
|
+
cannot be solved. No server solve is substituted for AC OPF.
|
|
85
|
+
|
|
71
86
|
## Panel layout
|
|
72
87
|
|
|
73
88
|
The shell docks Network and Studies on the left, and Solver and Agent on the
|
|
@@ -366,6 +381,11 @@ package metadata. The packed smoke test installs the generated tarballs into a
|
|
|
366
381
|
temporary Svelte consumer and builds it so missing exports, styles, or wasm
|
|
367
382
|
assets fail before publish.
|
|
368
383
|
|
|
384
|
+
Unmounting `TellegenProvider` (including through `TellegenViewer`) disposes its
|
|
385
|
+
controller and terminates pending AC OPF probes and solves, including background
|
|
386
|
+
cases. Custom hosts that construct a `Controller` directly must call
|
|
387
|
+
`controller.dispose()` when their owning view is destroyed.
|
|
388
|
+
|
|
369
389
|
## Citing
|
|
370
390
|
|
|
371
391
|
If you use this package in published work, please cite the tellegen paper:
|
package/dist/TellegenMap.svelte
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
3
3
|
import { tick, untrack } from 'svelte';
|
|
4
4
|
import type { Layer, PickingInfo } from '@deck.gl/core';
|
|
5
|
-
import type { IconLayer, PathLayer, ScatterplotLayer } from '@deck.gl/layers';
|
|
5
|
+
import type { IconLayer, IconLayerProps, PathLayer, ScatterplotLayer } from '@deck.gl/layers';
|
|
6
6
|
import type { MapLibreOverlay } from '@deck.gl/maplibre';
|
|
7
7
|
import type { LngLatBoundsLike, Map as MapLibreMap } from 'maplibre-gl';
|
|
8
8
|
import type { NetworkBranch, NetworkBus } from './api.js';
|
|
@@ -22,9 +22,8 @@
|
|
|
22
22
|
type SensitivityDomain
|
|
23
23
|
} from './colors.js';
|
|
24
24
|
import {
|
|
25
|
+
applyBasemapTuning,
|
|
25
26
|
attachBasemapFallback,
|
|
26
|
-
isOpenFreeMapStyle,
|
|
27
|
-
openFreeMapEdits,
|
|
28
27
|
resolveBasemap,
|
|
29
28
|
type BasemapFallbackReason,
|
|
30
29
|
type BasemapStyle,
|
|
@@ -50,13 +49,18 @@
|
|
|
50
49
|
type TransformerMark,
|
|
51
50
|
multiDiagramNetwork
|
|
52
51
|
} from './multiconductor.js';
|
|
53
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
TRANSFORMER_ICON,
|
|
54
|
+
transformerIconAtlas,
|
|
55
|
+
transformerIconMapping
|
|
56
|
+
} from './transformer-icon.js';
|
|
54
57
|
import { foldMapBounds } from './map-bounds.js';
|
|
55
58
|
import { getAppState, getController } from './context.svelte.js';
|
|
56
59
|
import { displayFmt, fmt, rgbaCss } from './format.js';
|
|
57
60
|
import { getPanelLayout } from './panels.svelte.js';
|
|
58
61
|
import type { RGBA } from './colors.js';
|
|
59
62
|
import DiagramCanvas from './components/DiagramCanvas.svelte';
|
|
63
|
+
import SchematicCanvas from './components/SchematicCanvas.svelte';
|
|
60
64
|
|
|
61
65
|
const DEFAULT_CAMERA: CameraSnapshot = { center: [-85, 36], zoom: 4.5, bearing: 0, pitch: 0 };
|
|
62
66
|
|
|
@@ -148,6 +152,24 @@
|
|
|
148
152
|
const diagramNetwork = $derived(
|
|
149
153
|
multiNetwork ?? (displayedNetwork?.coordinate_space === 'diagram' ? displayedNetwork : null)
|
|
150
154
|
);
|
|
155
|
+
/** A multiconductor case shown as a schematic replaces both the map and the diagram canvas. */
|
|
156
|
+
const schematicCase = $derived(
|
|
157
|
+
!app.studyView && app.activeMulti?.schematic && app.activeMulti.graph ? app.activeMulti : null
|
|
158
|
+
);
|
|
159
|
+
// When a case closes its own diagram (Close, Show map), the map continues from the diagram's
|
|
160
|
+
// geographic view. Switching to another case does not: the map frames the new case instead.
|
|
161
|
+
let shownSchematic: typeof schematicCase = null;
|
|
162
|
+
$effect(() => {
|
|
163
|
+
const now = schematicCase;
|
|
164
|
+
const before = shownSchematic;
|
|
165
|
+
shownSchematic = now;
|
|
166
|
+
untrack(() => {
|
|
167
|
+
if (before && !now && app.activeMulti === before && !before.schematic && before.lastGeoView) {
|
|
168
|
+
app.camera = before.lastGeoView;
|
|
169
|
+
app.cameraHandoff = true;
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
});
|
|
151
173
|
const diagramCaseId = $derived(
|
|
152
174
|
app.studyView?.caseId ?? diagramMulti?.id ?? diagramCase?.id ?? 'diagram'
|
|
153
175
|
);
|
|
@@ -187,6 +209,11 @@
|
|
|
187
209
|
const basemapPrimaryProvider = $derived(basemap.primaryProvider);
|
|
188
210
|
const basemapFallbackProvider = $derived(basemap.fallbackProvider);
|
|
189
211
|
const palette = $derived(MAP_PALETTE[theme]);
|
|
212
|
+
/** The diagram draws on light paper, so its background map is the light basemap whatever the
|
|
213
|
+
* map's theme; a host's own style is used as given. */
|
|
214
|
+
const diagramBasemap = $derived(
|
|
215
|
+
resolveBasemap({ mapStyle, fallbackMapStyle, theme: 'light', hideBasemapLabels, cartoApiKey })
|
|
216
|
+
);
|
|
190
217
|
// Set once a map has existed in this component instance: a rebuild (context
|
|
191
218
|
// loss or basemap change) restores the camera instead of reframing.
|
|
192
219
|
let hadMap = false;
|
|
@@ -875,23 +902,13 @@
|
|
|
875
902
|
// shows every layer again, so both are re-applied on each load.
|
|
876
903
|
const applyStyleExtras = () => {
|
|
877
904
|
m.setProjection({ type: untrack(() => projection) });
|
|
878
|
-
const style = m.getStyle();
|
|
879
|
-
if (spec.hideLabels) {
|
|
880
|
-
for (const layer of style.layers) {
|
|
881
|
-
if (layer.type === 'symbol') m.setLayoutProperty(layer.id, 'visibility', 'none');
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
905
|
// OpenFreeMap renders as the primary or as the fallback; either
|
|
885
906
|
// way it is tuned towards CARTO's quieter look.
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
907
|
+
applyBasemapTuning(
|
|
908
|
+
m,
|
|
909
|
+
spec,
|
|
889
910
|
untrack(() => theme)
|
|
890
911
|
);
|
|
891
|
-
for (const edit of edits) {
|
|
892
|
-
if (edit.kind === 'paint') m.setPaintProperty(edit.layer, edit.property, edit.value);
|
|
893
|
-
else m.setLayerZoomRange(edit.layer, edit.minzoom, edit.maxzoom);
|
|
894
|
-
}
|
|
895
912
|
};
|
|
896
913
|
m.on('style.load', applyStyleExtras);
|
|
897
914
|
const detachFallback = attachBasemapFallback(m, spec, {
|
|
@@ -1004,12 +1021,16 @@
|
|
|
1004
1021
|
overlay = null;
|
|
1005
1022
|
};
|
|
1006
1023
|
detachReady = onMapReady?.(m, o);
|
|
1024
|
+
// A view closing onto this map (the diagram, from geographic positions) hands it its
|
|
1025
|
+
// camera to continue from.
|
|
1026
|
+
const handedOff = app.cameraHandoff;
|
|
1027
|
+
app.cameraHandoff = false;
|
|
1007
1028
|
// Frame the active grid once the map is ready. The initial frame request
|
|
1008
1029
|
// (from the case load) can fire before the async map import finishes and
|
|
1009
1030
|
// be lost; re-issuing it here ensures the first view is the active case,
|
|
1010
1031
|
// not the default world view. A rebuilt map opens at the default view
|
|
1011
1032
|
// too, so it restores the camera the last map saved.
|
|
1012
|
-
if (restoreCamera && app.camera) m.jumpTo(app.camera);
|
|
1033
|
+
if ((restoreCamera || handedOff) && app.camera) m.jumpTo(app.camera);
|
|
1013
1034
|
else if (app.cameraSeq === handledCameraSeq)
|
|
1014
1035
|
app.requestFrame(app.activeCaseId ?? app.activeLocalId ?? 'all');
|
|
1015
1036
|
if (mapGen > 0) {
|
|
@@ -1345,7 +1366,11 @@
|
|
|
1345
1366
|
new IconLayer<TransformerMark>({
|
|
1346
1367
|
id: `multi-xfmr-${c.id}`,
|
|
1347
1368
|
data: transformerMarks(c.view.edges),
|
|
1348
|
-
|
|
1369
|
+
// deck.gl makes a texture from any image, canvas included, but types
|
|
1370
|
+
// iconAtlas as a URL or a Texture.
|
|
1371
|
+
iconAtlas: transformerIconAtlas() as unknown as IconLayerProps['iconAtlas'],
|
|
1372
|
+
iconMapping: transformerIconMapping,
|
|
1373
|
+
getIcon: () => TRANSFORMER_ICON,
|
|
1349
1374
|
getPosition: (m) => m.position,
|
|
1350
1375
|
getAngle: (m) => m.angle,
|
|
1351
1376
|
billboard: false,
|
|
@@ -1455,7 +1480,7 @@
|
|
|
1455
1480
|
$effect(() => {
|
|
1456
1481
|
const seq = app.cameraSeq;
|
|
1457
1482
|
const snapshot = app.cameraRequest;
|
|
1458
|
-
if (diagramNetwork || !map || !snapshot || seq === handledCameraSeq) return;
|
|
1483
|
+
if (diagramNetwork || schematicCase || !map || !snapshot || seq === handledCameraSeq) return;
|
|
1459
1484
|
handledCameraSeq = seq;
|
|
1460
1485
|
handledFrameSeq = app.frameSeq;
|
|
1461
1486
|
map.jumpTo(snapshot);
|
|
@@ -1467,7 +1492,7 @@
|
|
|
1467
1492
|
// until its cue exists; anything unframeable settles immediately.
|
|
1468
1493
|
$effect(() => {
|
|
1469
1494
|
const seq = app.frameSeq;
|
|
1470
|
-
if (diagramNetwork || !map || seq === handledFrameSeq) return;
|
|
1495
|
+
if (diagramNetwork || schematicCase || !map || seq === handledFrameSeq) return;
|
|
1471
1496
|
const m = map;
|
|
1472
1497
|
const target = app.frameTarget;
|
|
1473
1498
|
const settle = () => app.settleFrame();
|
|
@@ -1497,7 +1522,23 @@
|
|
|
1497
1522
|
});
|
|
1498
1523
|
</script>
|
|
1499
1524
|
|
|
1500
|
-
{#if
|
|
1525
|
+
{#if schematicCase}
|
|
1526
|
+
<!-- One canvas per graph: its layouts, camera and geometry belong to that graph, so another
|
|
1527
|
+
case, or the same case with coordinates attached, starts afresh. -->
|
|
1528
|
+
{#key schematicCase.graph}
|
|
1529
|
+
<SchematicCanvas
|
|
1530
|
+
mc={schematicCase}
|
|
1531
|
+
onselectbus={(id) => {
|
|
1532
|
+
if (id !== schematicCase.selectedBusId) onmultibusclick(schematicCase.id, id);
|
|
1533
|
+
}}
|
|
1534
|
+
onselectedge={(id) => {
|
|
1535
|
+
if (id !== schematicCase.selectedEdgeId) onmultiedgeclick?.(schematicCase.id, id);
|
|
1536
|
+
}}
|
|
1537
|
+
onclose={() => (schematicCase.schematic = false)}
|
|
1538
|
+
basemap={diagramBasemap}
|
|
1539
|
+
/>
|
|
1540
|
+
{/key}
|
|
1541
|
+
{:else if diagramNetwork}
|
|
1501
1542
|
<DiagramCanvas
|
|
1502
1543
|
network={diagramNetwork}
|
|
1503
1544
|
multiconductor={diagramMulti}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
let {
|
|
23
23
|
apiBase,
|
|
24
|
+
acOpfWasmUrl,
|
|
24
25
|
loadDefaultCases,
|
|
25
26
|
docsHref,
|
|
26
27
|
orgHref,
|
|
@@ -31,14 +32,15 @@
|
|
|
31
32
|
children
|
|
32
33
|
}: Props = $props();
|
|
33
34
|
|
|
34
|
-
// Read once, at mount: `apiBase` and `loadDefaultCases`
|
|
35
|
-
// controller this provider owns, and the links and footer are
|
|
36
|
-
// `theme` and `cartoApiKey` stay live, so a host can switch them
|
|
37
|
-
// root attribute and the default shell's map follow.
|
|
35
|
+
// Read once, at mount: `apiBase`, `acOpfWasmUrl` and `loadDefaultCases`
|
|
36
|
+
// configure the controller this provider owns, and the links and footer are
|
|
37
|
+
// fixed copy. `theme` and `cartoApiKey` stay live, so a host can switch them
|
|
38
|
+
// and the root attribute and the default shell's map follow.
|
|
38
39
|
const config: TellegenUiConfig = {
|
|
39
40
|
...untrack(() =>
|
|
40
41
|
resolveTellegenUiConfig({
|
|
41
42
|
apiBase,
|
|
43
|
+
acOpfWasmUrl,
|
|
42
44
|
loadDefaultCases,
|
|
43
45
|
docsHref,
|
|
44
46
|
orgHref,
|
|
@@ -56,6 +58,7 @@
|
|
|
56
58
|
const app = createAppState();
|
|
57
59
|
const ctrl = createController(app, {
|
|
58
60
|
apiBase: config.apiBase,
|
|
61
|
+
acOpfWasmUrl: config.acOpfWasmUrl ?? undefined,
|
|
59
62
|
loadDefaultCases: config.loadDefaultCases
|
|
60
63
|
});
|
|
61
64
|
// A host that opens its own cases never calls load(), so the shell must not
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { onMount } from 'svelte';
|
|
2
|
+
import { onMount, untrack } from 'svelte';
|
|
3
3
|
import AppFooter from './components/AppFooter.svelte';
|
|
4
4
|
import NotificationCenter from './components/NotificationCenter.svelte';
|
|
5
5
|
import AppHeader from './components/AppHeader.svelte';
|
|
@@ -19,6 +19,14 @@
|
|
|
19
19
|
|
|
20
20
|
let dragDepth = 0;
|
|
21
21
|
|
|
22
|
+
// The overview and the diagram read the active case's whole operating point once per solve.
|
|
23
|
+
$effect(() => {
|
|
24
|
+
const c = app.activeMulti;
|
|
25
|
+
void c?.result?.solve_count;
|
|
26
|
+
void c?.mcFullResult;
|
|
27
|
+
return untrack(() => ctrl.followOverview(c));
|
|
28
|
+
});
|
|
29
|
+
|
|
22
30
|
// ShellEnvironment drops the affordance when no fine pointer is attached;
|
|
23
31
|
// forget any drag that was in flight so the next one counts from zero.
|
|
24
32
|
$effect(() => {
|
|
@@ -40,6 +48,7 @@
|
|
|
40
48
|
});
|
|
41
49
|
|
|
42
50
|
onMount(() => {
|
|
51
|
+
ctrl.probeAcOpf();
|
|
43
52
|
// The provider already marked the shell loaded when default cases are
|
|
44
53
|
// off, and load() itself is gated; this only frames whatever is there.
|
|
45
54
|
if (config.loadDefaultCases) {
|
package/dist/basemap.d.ts
CHANGED
|
@@ -189,4 +189,18 @@ export interface BasemapFallbackOptions {
|
|
|
189
189
|
* again, as they do when `spec.fallback` is `null`. Returns a detach
|
|
190
190
|
* function. */
|
|
191
191
|
export declare function attachBasemapFallback(map: BasemapMapLike, spec: BasemapSpec, handlers?: BasemapFallbackHandlers, options?: BasemapFallbackOptions): () => void;
|
|
192
|
+
/** The parts of a MapLibre map `applyBasemapTuning` touches. */
|
|
193
|
+
export interface TunableMap {
|
|
194
|
+
getStyle(): StyleSpecification;
|
|
195
|
+
setLayoutProperty(layer: string, name: string, value: unknown): unknown;
|
|
196
|
+
setPaintProperty(layer: string, name: string, value: unknown): unknown;
|
|
197
|
+
setLayerZoomRange(layer: string, minzoom: number, maxzoom: number): unknown;
|
|
198
|
+
}
|
|
199
|
+
/** After a style loads: hide its labels when the spec asks, and tune an OpenFreeMap style (as the
|
|
200
|
+
* primary or the fallback) towards CARTO's quieter look. `setStyle` undoes both, so run this on
|
|
201
|
+
* every `style.load`. */
|
|
202
|
+
export declare function applyBasemapTuning(map: TunableMap, spec: BasemapSpec, theme: BasemapTheme): void;
|
|
203
|
+
/** A credit line for a basemap provider, for views that draw their own instead of MapLibre's
|
|
204
|
+
* attribution control. */
|
|
205
|
+
export declare function basemapCredit(provider: BasemapProvider): string;
|
|
192
206
|
export {};
|
package/dist/basemap.js
CHANGED
|
@@ -257,3 +257,30 @@ export function attachBasemapFallback(map, spec, handlers = {}, options = {}) {
|
|
|
257
257
|
map.off('error', onError);
|
|
258
258
|
};
|
|
259
259
|
}
|
|
260
|
+
/** After a style loads: hide its labels when the spec asks, and tune an OpenFreeMap style (as the
|
|
261
|
+
* primary or the fallback) towards CARTO's quieter look. `setStyle` undoes both, so run this on
|
|
262
|
+
* every `style.load`. */
|
|
263
|
+
export function applyBasemapTuning(map, spec, theme) {
|
|
264
|
+
const style = map.getStyle();
|
|
265
|
+
if (spec.hideLabels)
|
|
266
|
+
for (const layer of style.layers)
|
|
267
|
+
if (layer.type === 'symbol')
|
|
268
|
+
map.setLayoutProperty(layer.id, 'visibility', 'none');
|
|
269
|
+
if (!isOpenFreeMapStyle(style))
|
|
270
|
+
return;
|
|
271
|
+
for (const edit of openFreeMapEdits(style, theme)) {
|
|
272
|
+
if (edit.kind === 'paint')
|
|
273
|
+
map.setPaintProperty(edit.layer, edit.property, edit.value);
|
|
274
|
+
else
|
|
275
|
+
map.setLayerZoomRange(edit.layer, edit.minzoom, edit.maxzoom);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/** A credit line for a basemap provider, for views that draw their own instead of MapLibre's
|
|
279
|
+
* attribution control. */
|
|
280
|
+
export function basemapCredit(provider) {
|
|
281
|
+
return provider === 'carto'
|
|
282
|
+
? '© CARTO © OpenStreetMap contributors'
|
|
283
|
+
: provider === 'openfreemap'
|
|
284
|
+
? '© OpenFreeMap © OpenMapTiles © OpenStreetMap contributors'
|
|
285
|
+
: '© OpenStreetMap contributors';
|
|
286
|
+
}
|
package/dist/colors.d.ts
CHANGED
|
@@ -75,7 +75,12 @@ export declare function sensFlatColor(domain: Pick<SensitivityDomain, 'mean'>):
|
|
|
75
75
|
export declare function previewScaleFor(domain: Pick<SensitivityDomain, 'scale' | 'flat'>, maxAbsStep: number): number | null;
|
|
76
76
|
/** Zero anchored sensitivity domain with a flat column guard. */
|
|
77
77
|
export declare function sensitivityDomain(values: number[]): SensitivityDomain;
|
|
78
|
-
/**
|
|
78
|
+
/** Loading, as a fraction of rating, from which a branch needs attention. Both loading scales
|
|
79
|
+
* share it. An optimal power flow enforces ratings, so its map draws congestion: red from here,
|
|
80
|
+
* because it cannot exceed 1. A multiconductor power flow does not enforce them, so its overview
|
|
81
|
+
* warns from here and calls a violation only above 1 (`LOADING_LIMIT` in `diagram/summary.ts`). */
|
|
82
|
+
export declare const LOADING_ATTENTION = 0.9;
|
|
83
|
+
/** Branch color by loading fraction: warm gray, amber past 0.6, red from `LOADING_ATTENTION`. */
|
|
79
84
|
export declare function branchColor(loading: number, inService: boolean): RGBA;
|
|
80
85
|
export declare function branchWidth(loading: number): number;
|
|
81
86
|
/** Bus radius in px; area tracks max(load, gen) so big plants and big loads
|
package/dist/colors.js
CHANGED
|
@@ -177,14 +177,21 @@ export function sensitivityDomain(values) {
|
|
|
177
177
|
flat
|
|
178
178
|
};
|
|
179
179
|
}
|
|
180
|
-
/**
|
|
180
|
+
/** Loading, as a fraction of rating, from which a branch needs attention. Both loading scales
|
|
181
|
+
* share it. An optimal power flow enforces ratings, so its map draws congestion: red from here,
|
|
182
|
+
* because it cannot exceed 1. A multiconductor power flow does not enforce them, so its overview
|
|
183
|
+
* warns from here and calls a violation only above 1 (`LOADING_LIMIT` in `diagram/summary.ts`). */
|
|
184
|
+
export const LOADING_ATTENTION = 0.9;
|
|
185
|
+
/** Where the optimal power flow map starts shading a branch towards congestion. */
|
|
186
|
+
const LOADING_ELEVATED = 0.6;
|
|
187
|
+
/** Branch color by loading fraction: warm gray, amber past 0.6, red from `LOADING_ATTENTION`. */
|
|
181
188
|
export function branchColor(loading, inService) {
|
|
182
189
|
if (!inService)
|
|
183
190
|
return [197, 191, 178, 110];
|
|
184
|
-
if (loading <
|
|
191
|
+
if (loading < LOADING_ELEVATED)
|
|
185
192
|
return [138, 131, 117, 200];
|
|
186
|
-
if (loading <
|
|
187
|
-
return lerpColor([138, 131, 117, 210], [212, 116, 34, 240], (loading -
|
|
193
|
+
if (loading < LOADING_ATTENTION)
|
|
194
|
+
return lerpColor([138, 131, 117, 210], [212, 116, 34, 240], (loading - LOADING_ELEVATED) / (LOADING_ATTENTION - LOADING_ELEVATED));
|
|
188
195
|
return [179, 38, 30, 250];
|
|
189
196
|
}
|
|
190
197
|
export function branchWidth(loading) {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
>
|
|
20
20
|
{#each FORMULATIONS as f (f.id)}
|
|
21
21
|
{@const unavailable =
|
|
22
|
-
f.disabled ||
|
|
22
|
+
(f.disabled && !(f.id === 'acopf' && ctrl.acOpfAvailable && !c.perturbed)) ||
|
|
23
23
|
('declaredFormulation' in c &&
|
|
24
24
|
c.declaredFormulation != null &&
|
|
25
25
|
c.declaredFormulation !== f.id)}
|
|
@@ -154,15 +154,22 @@
|
|
|
154
154
|
{/if}
|
|
155
155
|
{@render note?.(lc)}
|
|
156
156
|
{#if lc.summary && lc.studyInputJson}
|
|
157
|
+
{@const exportUnavailable = ctrl.caseExportUnavailableReason(lc)}
|
|
157
158
|
<div class="case-actions">
|
|
158
|
-
<button
|
|
159
|
+
<button
|
|
160
|
+
class="reset"
|
|
161
|
+
disabled={busy || !!exportUnavailable}
|
|
162
|
+
title={exportUnavailable ?? undefined}
|
|
163
|
+
onclick={() => saveCase(lc)}>Save case</button
|
|
164
|
+
>
|
|
159
165
|
{#if lc.formulation === 'dcopf' && lc.solution}
|
|
160
166
|
<button class="reset" disabled={busy} onclick={() => saveSolution(lc)}>Save solution</button
|
|
161
167
|
>
|
|
162
168
|
{/if}
|
|
163
169
|
<button
|
|
164
170
|
class="reset"
|
|
165
|
-
disabled={busy}
|
|
171
|
+
disabled={busy || !!exportUnavailable}
|
|
172
|
+
title={exportUnavailable ?? undefined}
|
|
166
173
|
aria-expanded={exportOpen}
|
|
167
174
|
aria-controls="case-export-formats"
|
|
168
175
|
onclick={() => (exportOpen = !exportOpen)}
|
|
@@ -175,7 +182,9 @@
|
|
|
175
182
|
</button>
|
|
176
183
|
{/if}
|
|
177
184
|
</div>
|
|
178
|
-
{#if
|
|
185
|
+
{#if exportUnavailable}
|
|
186
|
+
<p class="unavailable">{exportUnavailable}</p>
|
|
187
|
+
{:else if exportOpen}
|
|
179
188
|
<ul class="case-actions" id="case-export-formats" aria-label="Export formats">
|
|
180
189
|
{#each EXPORT_FORMATS as f (f.token)}
|
|
181
190
|
<li>
|
|
@@ -266,4 +275,10 @@
|
|
|
266
275
|
.case-actions .reset {
|
|
267
276
|
margin-top: 0;
|
|
268
277
|
}
|
|
278
|
+
|
|
279
|
+
.unavailable {
|
|
280
|
+
margin: 8px 0 0;
|
|
281
|
+
font-size: 12px;
|
|
282
|
+
color: var(--text-secondary);
|
|
283
|
+
}
|
|
269
284
|
</style>
|
|
@@ -40,6 +40,17 @@
|
|
|
40
40
|
});
|
|
41
41
|
let busChoice = $state('');
|
|
42
42
|
let edgeChoice = $state('');
|
|
43
|
+
/** One entry per equipment id. A multi-winding transformer projects to several graph edges
|
|
44
|
+
* that share its id (one per winding pair), so the edges are grouped by id. */
|
|
45
|
+
const equipmentOptions = $derived.by(() => {
|
|
46
|
+
const byId = new Map<string, string[]>();
|
|
47
|
+
for (const edge of graph?.edges ?? []) {
|
|
48
|
+
const ends = byId.get(edge.id) ?? [];
|
|
49
|
+
ends.push(`${edge.from} - ${edge.to}`);
|
|
50
|
+
byId.set(edge.id, ends);
|
|
51
|
+
}
|
|
52
|
+
return [...byId].map(([id, ends]) => ({ id, label: `${ends.join(', ')}, ${id}` }));
|
|
53
|
+
});
|
|
43
54
|
const busId = $derived(
|
|
44
55
|
selectedBus ??
|
|
45
56
|
(graph?.buses.some((b) => b.id === busChoice) ? busChoice : graph?.buses[0]?.id) ??
|
|
@@ -127,7 +138,11 @@
|
|
|
127
138
|
</div>
|
|
128
139
|
<div>
|
|
129
140
|
<dt>Load voltage range</dt>
|
|
130
|
-
<dd>
|
|
141
|
+
<dd>
|
|
142
|
+
{summary.min_voltage_pu == null || summary.max_voltage_pu == null
|
|
143
|
+
? 'n/a'
|
|
144
|
+
: `${fixed(summary.min_voltage_pu)}–${fixed(summary.max_voltage_pu)} pu`}
|
|
145
|
+
</dd>
|
|
131
146
|
</div>
|
|
132
147
|
<div>
|
|
133
148
|
<dt>Source P / Q</dt>
|
|
@@ -228,8 +243,8 @@
|
|
|
228
243
|
selectedEdge = null;
|
|
229
244
|
portPage = 0;
|
|
230
245
|
}}
|
|
231
|
-
><option value="">All equipment</option>{#each
|
|
232
|
-
value={
|
|
246
|
+
><option value="">All equipment</option>{#each equipmentOptions as option, i (i)}<option
|
|
247
|
+
value={option.id}>{option.label}</option
|
|
233
248
|
>{/each}</select
|
|
234
249
|
></label
|
|
235
250
|
>
|