@tellegen/svelte 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +269 -5
  2. package/dist/Sparkline.svelte +2 -2
  3. package/dist/TellegenMap.svelte +518 -202
  4. package/dist/TellegenMap.svelte.d.ts +45 -0
  5. package/dist/TellegenProvider.svelte +40 -4
  6. package/dist/TellegenShell.svelte +24 -46
  7. package/dist/TellegenViewer.svelte +12 -2
  8. package/dist/api.d.ts +18 -3
  9. package/dist/api.js +8 -2
  10. package/dist/basemap.d.ts +192 -0
  11. package/dist/basemap.js +259 -0
  12. package/dist/colors.d.ts +25 -7
  13. package/dist/colors.js +39 -13
  14. package/dist/components/AppFooter.svelte +3 -2
  15. package/dist/components/AppHeader.svelte +44 -19
  16. package/dist/components/BindingLines.svelte +4 -7
  17. package/dist/components/BusPicker.svelte +11 -5
  18. package/dist/components/ControlPanel.svelte +106 -343
  19. package/dist/components/DemandSlider.svelte +36 -29
  20. package/dist/components/DiagramCanvas.svelte +486 -0
  21. package/dist/components/DiagramCanvas.svelte.d.ts +13 -0
  22. package/dist/components/DisplayControls.svelte +8 -2
  23. package/dist/components/DropZone.svelte +1 -1
  24. package/dist/components/FormulationSelector.svelte +9 -4
  25. package/dist/components/LocalCaseDetails.svelte +61 -48
  26. package/dist/components/McResults.svelte +428 -0
  27. package/dist/components/McResults.svelte.d.ts +17 -0
  28. package/dist/components/ModelDetails.svelte +230 -0
  29. package/dist/components/ModelDetails.svelte.d.ts +7 -0
  30. package/dist/components/MulticonductorDetails.svelte +232 -83
  31. package/dist/components/NetworkStats.svelte +20 -18
  32. package/dist/components/NotificationCenter.svelte +245 -0
  33. package/dist/components/NotificationCenter.svelte.d.ts +3 -0
  34. package/dist/components/PanelCard.svelte +384 -0
  35. package/dist/components/PanelCard.svelte.d.ts +9 -0
  36. package/dist/components/PanelFrame.svelte +46 -0
  37. package/dist/components/PanelFrame.svelte.d.ts +16 -0
  38. package/dist/components/PanelHost.svelte +237 -0
  39. package/dist/components/PanelHost.svelte.d.ts +6 -0
  40. package/dist/components/PlacementCue.svelte +1 -1
  41. package/dist/components/PowerFlowDetails.svelte +99 -0
  42. package/dist/components/PowerFlowDetails.svelte.d.ts +3 -0
  43. package/dist/components/RatingSlider.svelte +19 -14
  44. package/dist/components/RestoreDefaultsButton.svelte +1 -1
  45. package/dist/components/SavedNetworkDetails.svelte +214 -0
  46. package/dist/components/SavedNetworkDetails.svelte.d.ts +3 -0
  47. package/dist/components/SensitivityReadout.svelte +26 -18
  48. package/dist/components/ShellEnvironment.svelte +51 -0
  49. package/dist/components/ShellEnvironment.svelte.d.ts +18 -0
  50. package/dist/components/SizeLegend.svelte +2 -2
  51. package/dist/components/SolveCard.svelte +48 -51
  52. package/dist/components/TopMovers.svelte +9 -4
  53. package/dist/components/TopMovers.svelte.d.ts +2 -17
  54. package/dist/components.d.ts +5 -0
  55. package/dist/components.js +5 -0
  56. package/dist/context.svelte.d.ts +8 -1
  57. package/dist/context.svelte.js +8 -3
  58. package/dist/controller.svelte.d.ts +117 -36
  59. package/dist/controller.svelte.js +1133 -253
  60. package/dist/diagram-labels.d.ts +9 -0
  61. package/dist/diagram-labels.js +40 -0
  62. package/dist/display.d.ts +3 -7
  63. package/dist/display.js +31 -27
  64. package/dist/format.d.ts +9 -0
  65. package/dist/format.js +17 -4
  66. package/dist/index.d.ts +15 -5
  67. package/dist/index.js +9 -2
  68. package/dist/map.d.ts +4 -2
  69. package/dist/map.js +2 -1
  70. package/dist/maplibre-worker.d.ts +5 -0
  71. package/dist/maplibre-worker.dev.d.ts +11 -0
  72. package/dist/maplibre-worker.dev.js +15 -0
  73. package/dist/maplibre-worker.js +8 -0
  74. package/dist/multiconductor.d.ts +11 -2
  75. package/dist/multiconductor.js +107 -14
  76. package/dist/notices.svelte.d.ts +29 -0
  77. package/dist/notices.svelte.js +86 -0
  78. package/dist/panels.svelte.d.ts +71 -0
  79. package/dist/panels.svelte.js +245 -0
  80. package/dist/slider.d.ts +12 -0
  81. package/dist/slider.js +31 -0
  82. package/dist/state.svelte.d.ts +107 -23
  83. package/dist/state.svelte.js +106 -20
  84. package/dist/styles.css +59 -6
  85. package/dist/synthetic-layout.js +1 -0
  86. package/package.json +29 -11
  87. package/dist/study-package.d.ts +0 -8
  88. package/dist/study-package.js +0 -17
@@ -0,0 +1,486 @@
1
+ <script lang="ts">
2
+ import { untrack } from 'svelte';
3
+ import { on } from 'svelte/events';
4
+ import type { Network, NetworkBus } from '../api.js';
5
+ import { getAppState, getController } from '../context.svelte.js';
6
+ import { getPanelLayout } from '../panels.svelte.js';
7
+ import { branchColor, busNeutral, busRadius, priceColor, scalarDomain } from '../colors.js';
8
+ import { rgbaCss } from '../format.js';
9
+ import type { MulticonductorCase } from '../state.svelte.js';
10
+ import { edgeColor, attachmentColor } from '../multiconductor.js';
11
+ import { diagramLabels } from '../diagram-labels.js';
12
+
13
+ let {
14
+ network,
15
+ multiconductor = null,
16
+ caseId,
17
+ onbusclick,
18
+ onbranchclick,
19
+ onclear
20
+ }: {
21
+ network: Network;
22
+ multiconductor?: MulticonductorCase | null;
23
+ caseId: string;
24
+ onbusclick: (id: number) => void;
25
+ onbranchclick: (id: number) => void;
26
+ onclear: () => void;
27
+ } = $props();
28
+ const app = getAppState();
29
+ const ctrl = getController();
30
+ const panels = getPanelLayout();
31
+ let width = $state(0),
32
+ height = $state(0);
33
+ let scale = $state(1),
34
+ x = $state(0),
35
+ y = $state(0);
36
+ let dragging = $state(false);
37
+ let gesture: { x: number; y: number; offsetX: number; offsetY: number; moved: boolean } | null =
38
+ null;
39
+ let fittedCase: string | null = null;
40
+ let lastSize = { width: 0, height: 0 };
41
+ let handledFrame = -1;
42
+ let handledCamera = -1;
43
+ const buses = $derived(
44
+ network.buses.filter((bus) => Number.isFinite(bus.lon) && Number.isFinite(bus.lat))
45
+ );
46
+ const paths = $derived(
47
+ network.branches
48
+ .map((branch) => ({
49
+ branch,
50
+ points: branch.path.filter((point) => point.every(Number.isFinite))
51
+ }))
52
+ .filter((path) => path.points.length > 1)
53
+ );
54
+ const selectedBus = $derived(
55
+ multiconductor
56
+ ? (network.buses.find((bus) => bus.uid === multiconductor.selectedBusId)?.id ?? null)
57
+ : app.selectedBus
58
+ );
59
+ const selectedBranch = $derived(
60
+ multiconductor
61
+ ? (network.branches.find((branch) => branch.uid === multiconductor.selectedEdgeId)?.id ??
62
+ null)
63
+ : app.selectedBranch
64
+ );
65
+ const series = $derived.by(() => {
66
+ if (multiconductor) return [];
67
+ if (!app.studyView) return ctrl.activeDisplay?.values ?? [];
68
+ const solution = app.studyView.solution;
69
+ if (app.displayMode === 'angle') return solution?.va ?? [];
70
+ if (app.displayMode === 'voltage')
71
+ return (
72
+ solution?.vm ??
73
+ (solution?.w ?? []).map((value) => ({
74
+ ...value,
75
+ value: Math.sqrt(Math.max(0, value.value))
76
+ }))
77
+ );
78
+ return solution?.lmp ?? [];
79
+ });
80
+ const displayMode = $derived(
81
+ app.studyView ? app.displayMode : (ctrl.activeDisplay?.mode ?? app.displayMode)
82
+ );
83
+ const domain = $derived(
84
+ scalarDomain(
85
+ displayMode,
86
+ series.map((value) => value.value)
87
+ )
88
+ );
89
+ const values = $derived(new Map(series.map((value) => [value.bus, value.value])));
90
+ const labels = $derived(diagramLabels(buses, scale, x, y, width, height, selectedBus));
91
+ const loading = $derived(
92
+ new Map(
93
+ (app.studyView
94
+ ? (app.studyView.solution?.flows ?? [])
95
+ : (ctrl.activeSolvable?.solution?.flows ?? [])
96
+ ).map((flow) => [flow.branch, flow.loading])
97
+ )
98
+ );
99
+ function color(bus: NetworkBus) {
100
+ if (multiconductor) {
101
+ const source = multiconductor.view?.buses[bus.id];
102
+ return source?.attachmentKinds.length
103
+ ? rgbaCss([...attachmentColor(source.attachmentKinds[0])])
104
+ : rgbaCss(busNeutral);
105
+ }
106
+ const value = values.get(bus.id);
107
+ return rgbaCss(
108
+ value === undefined
109
+ ? busNeutral
110
+ : priceColor((value - domain.lo) / Math.max(domain.hi - domain.lo, 1e-12))
111
+ );
112
+ }
113
+ function branchLabel(id: number) {
114
+ const edge = multiconductor?.view?.edges[id];
115
+ const branch = network.branches.find((branch) => branch.id === id)!;
116
+ return edge
117
+ ? `${edge.kind} ${edge.id}, bus ${edge.from} to bus ${edge.to}`
118
+ : `Line ${branch.id}, bus ${branch.from} to bus ${branch.to}`;
119
+ }
120
+
121
+ function bounds(points: [number, number][]) {
122
+ let minX = Infinity,
123
+ maxX = -Infinity,
124
+ minY = Infinity,
125
+ maxY = -Infinity;
126
+ for (const [px, py] of points) {
127
+ if (!Number.isFinite(px) || !Number.isFinite(py)) continue;
128
+ minX = Math.min(minX, px);
129
+ maxX = Math.max(maxX, px);
130
+ minY = Math.min(minY, py);
131
+ maxY = Math.max(maxY, py);
132
+ }
133
+ return Number.isFinite(minX) ? { minX, maxX, minY, maxY } : null;
134
+ }
135
+ function available() {
136
+ const left = panels.compact ? 28 : panels.dockWidth('left') + 44;
137
+ const right = panels.compact ? 28 : panels.dockWidth('right') + 44;
138
+ const top = panels.top + 24;
139
+ const bottom = panels.compact ? app.sheetInset + 24 : 88;
140
+ return {
141
+ left,
142
+ top,
143
+ width: Math.max(80, width - left - right),
144
+ height: Math.max(60, height - top - bottom)
145
+ };
146
+ }
147
+ function fit(points?: [number, number][], maxScale = Infinity) {
148
+ const extent = bounds(
149
+ points ?? [
150
+ ...buses.map((bus): [number, number] => [bus.lon, bus.lat]),
151
+ ...paths.flatMap((path) => path.points)
152
+ ]
153
+ );
154
+ if (!extent || width <= 0 || height <= 0) return;
155
+ const space = available();
156
+ const spanX = Math.max(extent.maxX - extent.minX, 1),
157
+ spanY = Math.max(extent.maxY - extent.minY, 1);
158
+ scale = Math.min(maxScale, 0.85 * Math.min(space.width / spanX, space.height / spanY));
159
+ x = space.left + space.width / 2 - ((extent.minX + extent.maxX) * scale) / 2;
160
+ y = space.top + space.height / 2 - ((extent.minY + extent.maxY) * scale) / 2;
161
+ }
162
+ function zoom(factor: number, px = width / 2, py = height / 2) {
163
+ const next = Math.max(1e-6, Math.min(1e6, scale * factor));
164
+ const ratio = next / scale;
165
+ x = px - (px - x) * ratio;
166
+ y = py - (py - y) * ratio;
167
+ scale = next;
168
+ }
169
+ function restore(snapshot: { center: [number, number]; scale: number }) {
170
+ scale = snapshot.scale;
171
+ x = width / 2 - snapshot.center[0] * scale;
172
+ y = height / 2 - snapshot.center[1] * scale;
173
+ }
174
+ function start(event: PointerEvent) {
175
+ if (
176
+ !event.isPrimary ||
177
+ event.button !== 0 ||
178
+ (event.target as Element).closest('[data-element]')
179
+ )
180
+ return;
181
+ gesture = { x: event.clientX, y: event.clientY, offsetX: x, offsetY: y, moved: false };
182
+ (event.currentTarget as HTMLElement).setPointerCapture(event.pointerId);
183
+ }
184
+ function move(event: PointerEvent) {
185
+ if (!gesture) return;
186
+ const dx = event.clientX - gesture.x,
187
+ dy = event.clientY - gesture.y;
188
+ gesture.moved ||= Math.hypot(dx, dy) > 3;
189
+ dragging = gesture.moved;
190
+ x = gesture.offsetX + dx;
191
+ y = gesture.offsetY + dy;
192
+ }
193
+ function stop() {
194
+ if (gesture && !gesture.moved) onclear();
195
+ gesture = null;
196
+ dragging = false;
197
+ }
198
+ function wheel(node: HTMLDivElement) {
199
+ return {
200
+ destroy: on(
201
+ node,
202
+ 'wheel',
203
+ (event) => {
204
+ if (!(event instanceof WheelEvent)) return;
205
+ event.preventDefault();
206
+ const rect = node.getBoundingClientRect();
207
+ zoom(
208
+ Math.exp(-Math.max(-100, Math.min(100, event.deltaY)) * 0.003),
209
+ event.clientX - rect.x,
210
+ event.clientY - rect.y
211
+ );
212
+ },
213
+ { passive: false }
214
+ )
215
+ };
216
+ }
217
+ function key(event: KeyboardEvent) {
218
+ if ((event.target as Element).closest('[data-element]')) return;
219
+ const step = event.shiftKey ? 80 : 30;
220
+ if (event.key === 'ArrowLeft') x += step;
221
+ else if (event.key === 'ArrowRight') x -= step;
222
+ else if (event.key === 'ArrowUp') y += step;
223
+ else if (event.key === 'ArrowDown') y -= step;
224
+ else if (event.key === '+' || event.key === '=') zoom(1.25);
225
+ else if (event.key === '-') zoom(0.8);
226
+ else if (event.key === '0' || event.key.toLowerCase() === 'f') fit();
227
+ else return;
228
+ event.preventDefault();
229
+ }
230
+ function choose(event: KeyboardEvent, callback: () => void) {
231
+ if (event.key !== 'Enter' && event.key !== ' ') return;
232
+ event.preventDefault();
233
+ event.stopPropagation();
234
+ callback();
235
+ }
236
+ $effect(() => {
237
+ const id = caseId,
238
+ w = width,
239
+ h = height;
240
+ if (w <= 0 || h <= 0) return;
241
+ untrack(() => {
242
+ if (fittedCase !== id) {
243
+ if (app.diagramCamera?.caseId === id) restore(app.diagramCamera);
244
+ else fit();
245
+ fittedCase = id;
246
+ } else {
247
+ x += (w - lastSize.width) / 2;
248
+ y += (h - lastSize.height) / 2;
249
+ }
250
+ lastSize = { width: w, height: h };
251
+ });
252
+ });
253
+ $effect(() => {
254
+ const seq = app.frameSeq;
255
+ if (!width || !height || seq === handledFrame) return;
256
+ const target = app.frameTarget;
257
+ handledFrame = seq;
258
+ untrack(() => {
259
+ if (typeof target === 'string') {
260
+ if (target === 'all' || target === caseId) fit();
261
+ } else if (target.caseId === caseId) {
262
+ if ('busId' in target) {
263
+ const bus = buses.find((bus) => bus.id === target.busId);
264
+ if (bus) fit([[bus.lon, bus.lat]], scale * 2);
265
+ } else {
266
+ const branch = paths.find(({ branch }) => branch.id === target.branchId);
267
+ if (branch) fit(branch.points, scale * 2);
268
+ }
269
+ }
270
+ app.settleFrame();
271
+ });
272
+ });
273
+ $effect(() => {
274
+ const seq = app.diagramCameraSeq;
275
+ const request = app.diagramCameraRequest;
276
+ if (!width || !height || !request || request.caseId !== caseId || handledCamera === seq) return;
277
+ untrack(() => {
278
+ restore(request);
279
+ handledCamera = seq;
280
+ app.diagramCameraRequest = null;
281
+ });
282
+ });
283
+ $effect(() => {
284
+ if (width > 0 && height > 0 && scale > 0) {
285
+ app.diagramCamera = {
286
+ caseId,
287
+ center: [(width / 2 - x) / scale, (height / 2 - y) / scale],
288
+ scale
289
+ };
290
+ }
291
+ });
292
+ </script>
293
+
294
+ <div class="diagram-stage" bind:clientWidth={width} bind:clientHeight={height}>
295
+ <p id="diagram-keys" class="sr-only">Arrow keys pan; plus and minus zoom; F fits the drawing.</p>
296
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex, a11y_no_noninteractive_element_interactions (The diagram supports keyboard panning, zooming, and equipment selection.) -->
297
+ <div
298
+ class="interaction"
299
+ use:wheel
300
+ class:dragging
301
+ role="application"
302
+ aria-label="Network diagram"
303
+ aria-describedby="diagram-keys"
304
+ tabindex="0"
305
+ onpointerdown={start}
306
+ onpointermove={move}
307
+ onpointerup={stop}
308
+ onpointercancel={() => {
309
+ gesture = null;
310
+ dragging = false;
311
+ }}
312
+ onkeydown={key}
313
+ >
314
+ <svg aria-label="Network diagram" viewBox={`0 0 ${Math.max(1, width)} ${Math.max(1, height)}`}>
315
+ <g transform={`translate(${x} ${y}) scale(${scale})`} data-diagram-transform>
316
+ {#each paths as { branch, points } (branch.id)}
317
+ <path
318
+ d={`M${points.map((point) => point.join(',')).join(' L')}`}
319
+ fill="none"
320
+ stroke={selectedBranch === branch.id
321
+ ? '#2f6fbb'
322
+ : multiconductor?.view?.edges[branch.id]
323
+ ? rgbaCss(edgeColor(multiconductor.view.edges[branch.id].kind, branch.status !== 0))
324
+ : rgbaCss(branchColor(loading.get(branch.id) ?? 0, branch.status !== 0))}
325
+ stroke-width={selectedBranch === branch.id ? 4 : 2}
326
+ vector-effect="non-scaling-stroke"
327
+ role="button"
328
+ tabindex={selectedBranch === branch.id ? 0 : -1}
329
+ aria-label={branchLabel(branch.id)}
330
+ data-element="branch"
331
+ data-branch-id={branch.id}
332
+ onclick={() => onbranchclick(branch.id)}
333
+ onkeydown={(event) => choose(event, () => onbranchclick(branch.id))}
334
+ ><title>{branchLabel(branch.id)}</title></path
335
+ >
336
+ {/each}
337
+ {#each buses as bus, index (bus.id)}
338
+ <g
339
+ class="bus"
340
+ role="button"
341
+ tabindex={selectedBus === bus.id || (selectedBus === null && index === 0) ? 0 : -1}
342
+ aria-label={`Bus ${multiconductor ? bus.uid : bus.id}${!multiconductor && bus.name ? `, ${bus.name}` : ''}`}
343
+ data-element="bus"
344
+ data-bus-id={bus.id}
345
+ onclick={() => onbusclick(bus.id)}
346
+ onkeydown={(event) => choose(event, () => onbusclick(bus.id))}
347
+ >
348
+ <circle
349
+ cx={bus.lon}
350
+ cy={bus.lat}
351
+ r={Math.max(5, busRadius(Math.max(bus.demand_mw, bus.gen_mw))) / scale}
352
+ fill={color(bus)}
353
+ style:stroke={selectedBus === bus.id ? '#2f6fbb' : 'var(--diagram-bus-stroke)'}
354
+ stroke-width={selectedBus === bus.id ? 3 : 1}
355
+ vector-effect="non-scaling-stroke"
356
+ />
357
+ <text
358
+ class:hidden={!labels.has(bus.id)}
359
+ x={bus.lon + 10 / scale}
360
+ y={bus.lat - 10 / scale}
361
+ font-size={11 / scale}>{bus.name || bus.id}</text
362
+ >
363
+ <title
364
+ >Bus {multiconductor ? bus.uid : bus.id}{!multiconductor && bus.name
365
+ ? `, ${bus.name}`
366
+ : ''}, {(bus.demand_mw * (multiconductor ? 1000 : 1)).toFixed(1)}
367
+ {multiconductor ? 'kW' : 'MW'} demand{values.has(bus.id)
368
+ ? `, ${values.get(bus.id)!.toFixed(4)} ${displayMode === 'price' ? 'objective units/MW LMP' : displayMode === 'voltage' ? 'pu voltage' : 'rad voltage angle'}`
369
+ : ''}</title
370
+ >
371
+ </g>
372
+ {/each}
373
+ </g>
374
+ </svg>
375
+ </div>
376
+ {#if !buses.length}<p class="empty">This drawing has no matched bus positions.</p>{/if}
377
+ <div class="controls" aria-label="Diagram controls">
378
+ <button onclick={() => fit()} title="Fit diagram (F)">Fit diagram</button>
379
+ <div class="zoom">
380
+ <button aria-label="Zoom in" onclick={() => zoom(1.25)}>+</button><button
381
+ aria-label="Zoom out"
382
+ onclick={() => zoom(0.8)}>-</button
383
+ >
384
+ </div>
385
+ </div>
386
+ <p class="coordinate-label">Diagram coordinates</p>
387
+ </div>
388
+
389
+ <style>
390
+ .bus text.hidden {
391
+ visibility: hidden;
392
+ }
393
+ .bus:hover text.hidden,
394
+ .bus:focus-visible text.hidden {
395
+ visibility: visible;
396
+ }
397
+ .sr-only {
398
+ position: absolute;
399
+ width: 1px;
400
+ height: 1px;
401
+ padding: 0;
402
+ margin: -1px;
403
+ overflow: hidden;
404
+ clip-path: inset(50%);
405
+ white-space: nowrap;
406
+ }
407
+ .diagram-stage {
408
+ position: absolute;
409
+ inset: 0;
410
+ background: var(--bg);
411
+ color: var(--ink);
412
+ }
413
+ svg {
414
+ width: 100%;
415
+ height: 100%;
416
+ }
417
+ .interaction {
418
+ position: absolute;
419
+ inset: 0;
420
+ touch-action: none;
421
+ cursor: grab;
422
+ }
423
+ .interaction.dragging {
424
+ cursor: grabbing;
425
+ }
426
+ .interaction:focus-visible {
427
+ outline: 2px solid var(--focus-ring);
428
+ outline-offset: -3px;
429
+ }
430
+ [data-element] {
431
+ cursor: pointer;
432
+ }
433
+ [data-element]:focus-visible {
434
+ outline: none;
435
+ stroke: var(--accent);
436
+ }
437
+ text {
438
+ fill: var(--text-secondary);
439
+ font-family: var(--font-mono);
440
+ pointer-events: none;
441
+ }
442
+ .controls {
443
+ position: absolute;
444
+ right: 20px;
445
+ bottom: 12px;
446
+ display: flex;
447
+ gap: 12px;
448
+ align-items: flex-end;
449
+ }
450
+ button {
451
+ padding: 6px 10px;
452
+ border: 1px solid var(--line);
453
+ border-radius: 4px;
454
+ color: var(--ink);
455
+ background: var(--paper);
456
+ font: 12px/1.5 var(--font-display);
457
+ cursor: pointer;
458
+ }
459
+ button:focus-visible {
460
+ outline: 2px solid var(--focus-ring);
461
+ outline-offset: 2px;
462
+ }
463
+ .zoom {
464
+ display: grid;
465
+ }
466
+ .zoom button {
467
+ width: 32px;
468
+ height: 30px;
469
+ font: 18px/1 var(--font-display);
470
+ }
471
+ .coordinate-label {
472
+ position: absolute;
473
+ bottom: 44px;
474
+ left: 20px;
475
+ margin: 0;
476
+ font: 10px/1.4 var(--font-mono);
477
+ color: var(--text-secondary);
478
+ }
479
+ .empty {
480
+ position: absolute;
481
+ top: 50%;
482
+ left: 50%;
483
+ transform: translate(-50%, -50%);
484
+ font: 13px var(--font-display);
485
+ }
486
+ </style>
@@ -0,0 +1,13 @@
1
+ import type { Network } from '../api.js';
2
+ import type { MulticonductorCase } from '../state.svelte.js';
3
+ type $$ComponentProps = {
4
+ network: Network;
5
+ multiconductor?: MulticonductorCase | null;
6
+ caseId: string;
7
+ onbusclick: (id: number) => void;
8
+ onbranchclick: (id: number) => void;
9
+ onclear: () => void;
10
+ };
11
+ declare const DiagramCanvas: import("svelte").Component<$$ComponentProps, {}, "">;
12
+ type DiagramCanvas = ReturnType<typeof DiagramCanvas>;
13
+ export default DiagramCanvas;
@@ -23,7 +23,7 @@
23
23
  {/if}
24
24
  </div>
25
25
  {#if ctrl.activeDisplay && ctrl.displayStats}
26
- {#if ctrl.activeDisplay.mode === 'lmp'}
26
+ {#if ctrl.activeDisplay.mode === 'price'}
27
27
  <div class="legend-heading">
28
28
  <span class="legend-title">Locational marginal price</span>
29
29
  <span
@@ -59,8 +59,14 @@
59
59
  </span>
60
60
  {/if}
61
61
  </div>
62
+ {:else if ctrl.activeSolvable?.solving}
63
+ <p class="dim small blink">
64
+ Solving {formulationLabel(ctrl.activeFormulation)}&hellip;
65
+ </p>
62
66
  {:else}
63
- <p class="dim small blink">Solving {formulationLabel(ctrl.activeFormulation)}&hellip;</p>
67
+ <p class="dim small">
68
+ No {formulationLabel(ctrl.activeFormulation)} results available.
69
+ </p>
64
70
  {/if}
65
71
 
66
72
  <style>
@@ -19,7 +19,7 @@
19
19
  inset: 0;
20
20
  z-index: 20;
21
21
  pointer-events: none;
22
- background: rgba(236, 233, 226, 0.75);
22
+ background: rgb(var(--bg-rgb) / 0.75);
23
23
  }
24
24
 
25
25
  .dropframe {
@@ -14,7 +14,7 @@
14
14
  for="formulation-select"
15
15
  title={formulationHint(c.formulation)}
16
16
  >
17
- <span>formulation</span>
17
+ <span>Calculation</span>
18
18
  <select
19
19
  id="formulation-select"
20
20
  class="mono"
@@ -23,8 +23,13 @@
23
23
  onchange={(e) => ctrl.changeFormulation(c, e.currentTarget.value as Formulation)}
24
24
  >
25
25
  {#each FORMULATIONS as f (f.id)}
26
- <option value={f.id} disabled={f.disabled}>
27
- {f.label}{f.disabled ? ' (coming soon)' : ''}
26
+ {@const unavailable =
27
+ f.disabled ||
28
+ ('declaredFormulation' in c &&
29
+ c.declaredFormulation != null &&
30
+ c.declaredFormulation !== f.id)}
31
+ <option value={f.id} disabled={unavailable}>
32
+ {f.label}{unavailable ? ' (unavailable)' : ''}
28
33
  </option>
29
34
  {/each}
30
35
  </select>
@@ -48,7 +53,7 @@
48
53
 
49
54
  .formulation-row select {
50
55
  font-family: var(--font-mono);
51
- font-size: 11px;
56
+ font-size: 13px;
52
57
  padding: 3px 22px 3px 8px;
53
58
  border: 1px solid var(--line);
54
59
  border-radius: 2px;