@tellegen/svelte 0.4.1 → 0.4.2
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 +38 -10
- package/dist/Sparkline.svelte +4 -4
- package/dist/TellegenMap.svelte +41 -46
- package/dist/TellegenProvider.svelte +29 -13
- package/dist/components/AppFooter.svelte +4 -4
- package/dist/components/AppHeader.svelte +15 -19
- package/dist/components/BindingLines.svelte +10 -9
- package/dist/components/BusPicker.svelte +5 -6
- package/dist/components/ControlPanel.svelte +12 -9
- package/dist/components/ControlPanel.svelte.d.ts +6 -1
- package/dist/components/DemandSlider.svelte +26 -29
- package/dist/components/DisplayControls.svelte +5 -24
- package/dist/components/DropZone.svelte +7 -3
- package/dist/components/DropZone.svelte.d.ts +6 -17
- package/dist/components/FormulationSelector.svelte +2 -7
- package/dist/components/LocalCaseDetails.svelte +126 -126
- package/dist/components/LocalCaseDetails.svelte.d.ts +6 -1
- package/dist/components/MulticonductorDetails.svelte +48 -38
- package/dist/components/NetworkStats.svelte +14 -16
- package/dist/components/PanelCard.svelte +1 -1
- package/dist/components/PlacementCue.svelte +1 -1
- package/dist/components/RatingSlider.svelte +22 -23
- package/dist/components/RestoreDefaultsButton.svelte +2 -3
- package/dist/components/SensitivityReadout.svelte +9 -27
- package/dist/components/SolveCard.svelte +69 -35
- package/dist/components/TopMovers.svelte +5 -5
- package/dist/context.svelte.d.ts +6 -2
- package/dist/context.svelte.js +4 -2
- package/dist/controller.svelte.d.ts +7 -0
- package/dist/controller.svelte.js +33 -6
- package/dist/display.js +8 -9
- package/dist/format.d.ts +1 -9
- package/dist/format.js +0 -13
- package/dist/styles.css +3 -0
- package/package.json +10 -6
|
@@ -46,51 +46,60 @@
|
|
|
46
46
|
{#if app.activeMulti}
|
|
47
47
|
{@const mc = app.activeMulti}
|
|
48
48
|
{@const s = mc.summary}
|
|
49
|
-
<h2>{mc.label}
|
|
49
|
+
<h2>{mc.label}</h2>
|
|
50
50
|
{#if s}
|
|
51
|
-
<p class="tag
|
|
51
|
+
<p class="tag">Multiconductor</p>
|
|
52
52
|
<details class="case-details">
|
|
53
53
|
<summary>{s.n_bus} buses, {s.n_edge} {s.n_edge === 1 ? 'branch' : 'branches'}</summary>
|
|
54
|
-
<dl
|
|
54
|
+
<dl>
|
|
55
55
|
<div>
|
|
56
|
-
<dt>
|
|
57
|
-
<dd>{s.n_bus}</dd>
|
|
56
|
+
<dt>Buses</dt>
|
|
57
|
+
<dd class="mono">{s.n_bus}</dd>
|
|
58
58
|
</div>
|
|
59
59
|
<div>
|
|
60
60
|
<dt>Lines / switches / transformers</dt>
|
|
61
|
-
<dd>{s.n_line} / {s.n_switch} / {s.n_transformer}</dd>
|
|
61
|
+
<dd class="mono">{s.n_line} / {s.n_switch} / {s.n_transformer}</dd>
|
|
62
62
|
</div>
|
|
63
63
|
<div>
|
|
64
|
-
<dt>
|
|
65
|
-
<dd>{fmt.format(s.load_kw)} kW</dd>
|
|
64
|
+
<dt>Load</dt>
|
|
65
|
+
<dd class="mono">{fmt.format(s.load_kw)} kW</dd>
|
|
66
66
|
</div>
|
|
67
67
|
<div>
|
|
68
68
|
<dt>Generation capacity</dt>
|
|
69
|
-
<dd>{fmt.format(s.gen_kw)} kW</dd>
|
|
69
|
+
<dd class="mono">{fmt.format(s.gen_kw)} kW</dd>
|
|
70
70
|
</div>
|
|
71
71
|
<div>
|
|
72
|
-
<dt>
|
|
73
|
-
<dd>{s.n_source} / {s.n_load} / {s.n_generator}</dd>
|
|
72
|
+
<dt>Sources / loads / generators</dt>
|
|
73
|
+
<dd class="mono">{s.n_source} / {s.n_load} / {s.n_generator}</dd>
|
|
74
74
|
</div>
|
|
75
75
|
{#if s.n_ibr > 0 || s.n_shunt > 0 || (s.n_capacitor ?? 0) > 0}
|
|
76
76
|
<div>
|
|
77
|
-
<dt>IBRs / shunts /
|
|
78
|
-
<dd>{s.n_ibr} / {s.n_shunt} / {s.n_capacitor ?? 0}</dd>
|
|
77
|
+
<dt>IBRs / shunts / capacitors</dt>
|
|
78
|
+
<dd class="mono">{s.n_ibr} / {s.n_shunt} / {s.n_capacitor ?? 0}</dd>
|
|
79
79
|
</div>
|
|
80
80
|
{/if}
|
|
81
|
+
<div>
|
|
82
|
+
<dt>File</dt>
|
|
83
|
+
<dd class="mono">{mc.fileName}</dd>
|
|
84
|
+
</div>
|
|
81
85
|
</dl>
|
|
82
86
|
</details>
|
|
83
87
|
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
<dl>
|
|
89
|
+
<div>
|
|
90
|
+
<dt>Coordinates</dt>
|
|
91
|
+
<dd>
|
|
92
|
+
{mc.coordsKind === 'geographic'
|
|
93
|
+
? 'Geographic'
|
|
94
|
+
: mc.coordsKind === 'planar'
|
|
95
|
+
? 'Drawing'
|
|
96
|
+
: 'Generated diagram'}
|
|
97
|
+
</dd>
|
|
98
|
+
</div>
|
|
99
|
+
</dl>
|
|
91
100
|
<div class="calculation-actions">
|
|
92
101
|
{#if mc.solving}
|
|
93
|
-
<span role="status">Calculating
|
|
102
|
+
<span role="status">Calculating…</span>{#if mc.solveAbort}<button
|
|
94
103
|
onclick={() => mc.solveAbort?.abort()}>Cancel</button
|
|
95
104
|
>{/if}
|
|
96
105
|
{:else}
|
|
@@ -110,27 +119,27 @@
|
|
|
110
119
|
notices.push({
|
|
111
120
|
kind: 'warning',
|
|
112
121
|
title: 'AC power flow unavailable',
|
|
113
|
-
details: mc.mcPfReason ?? '
|
|
122
|
+
details: mc.mcPfReason ?? 'AC power flow does not support this case'
|
|
114
123
|
})}>Why unavailable</button
|
|
115
124
|
>
|
|
116
125
|
{/if}
|
|
117
126
|
{/if}
|
|
118
127
|
</div>
|
|
119
128
|
{#if mc.result}<p class="footnote">Converged, {mc.result.iterations} iterations</p>
|
|
120
|
-
<button class="reset
|
|
129
|
+
<button class="reset" onclick={openResults}>View results in Studies</button>{/if}
|
|
121
130
|
{/if}
|
|
122
131
|
|
|
123
132
|
{#if mc.selectedBus}
|
|
124
133
|
{@const b = mc.selectedBus}
|
|
125
134
|
<hr />
|
|
126
|
-
<h3 class="mono">
|
|
135
|
+
<h3>Bus <span class="mono">{b.id}</span></h3>
|
|
127
136
|
<div class="terminals">
|
|
128
137
|
{#each b.terminals as t (t)}
|
|
129
138
|
<span
|
|
130
139
|
class="term"
|
|
131
140
|
class:grounded={b.grounded.includes(t)}
|
|
132
141
|
style={`--tc:${terminalColor(t)}`}
|
|
133
|
-
title={b.grounded.includes(t) ? `
|
|
142
|
+
title={b.grounded.includes(t) ? `Terminal ${t}, grounded` : `Terminal ${t}`}
|
|
134
143
|
>
|
|
135
144
|
{t}{#if b.grounded.includes(t)}<span class="gnd" aria-hidden="true">⏚</span>{/if}
|
|
136
145
|
</span>
|
|
@@ -145,7 +154,7 @@
|
|
|
145
154
|
{/each}
|
|
146
155
|
</div>
|
|
147
156
|
{:else}
|
|
148
|
-
<p class="footnote
|
|
157
|
+
<p class="footnote">No attachments</p>
|
|
149
158
|
{/if}
|
|
150
159
|
{@const loadBranches = mc.mcLoadBranches.filter((load) => load.bus === b.id)}
|
|
151
160
|
{#if loadBranches.length > 0}
|
|
@@ -154,7 +163,7 @@
|
|
|
154
163
|
{#each loadBranches as load (`${load.load}:${load.branch}`)}
|
|
155
164
|
<div class="load-row">
|
|
156
165
|
<span class="mono"
|
|
157
|
-
>{load.load}{loadBranches.length > 1 ? `
|
|
166
|
+
>{load.load}{loadBranches.length > 1 ? ` (${load.branch + 1})` : ''}</span
|
|
158
167
|
>
|
|
159
168
|
<label
|
|
160
169
|
>P kW<input
|
|
@@ -200,7 +209,7 @@
|
|
|
200
209
|
>
|
|
201
210
|
</div>
|
|
202
211
|
{/each}
|
|
203
|
-
<p class="footnote">Changes
|
|
212
|
+
<p class="footnote">Changes solve automatically.</p>
|
|
204
213
|
</div>
|
|
205
214
|
{/if}
|
|
206
215
|
{:else if mc.selectedEdge}
|
|
@@ -211,15 +220,19 @@
|
|
|
211
220
|
{e.kind}
|
|
212
221
|
{e.from} – {e.to}
|
|
213
222
|
</h3>
|
|
214
|
-
<dl
|
|
223
|
+
<dl>
|
|
224
|
+
<div>
|
|
225
|
+
<dt>Phases</dt>
|
|
226
|
+
<dd class="mono">{e.n_phases}</dd>
|
|
227
|
+
</div>
|
|
215
228
|
<div>
|
|
216
|
-
<dt>
|
|
217
|
-
<dd>{e.
|
|
229
|
+
<dt>Conductors</dt>
|
|
230
|
+
<dd class="mono">{e.conductors.length}</dd>
|
|
218
231
|
</div>
|
|
219
232
|
{#if e.kind === 'switch'}
|
|
220
233
|
<div>
|
|
221
|
-
<dt>
|
|
222
|
-
<dd>{e.closed ? '
|
|
234
|
+
<dt>State</dt>
|
|
235
|
+
<dd>{e.closed ? 'Closed' : 'Open'}</dd>
|
|
223
236
|
</div>
|
|
224
237
|
{/if}
|
|
225
238
|
</dl>
|
|
@@ -232,8 +245,6 @@
|
|
|
232
245
|
</span>
|
|
233
246
|
{/each}
|
|
234
247
|
</div>
|
|
235
|
-
{:else if mc.placed}
|
|
236
|
-
<p class="footnote mono">select a bus or a line to expand its detail</p>
|
|
237
248
|
{/if}
|
|
238
249
|
|
|
239
250
|
<div class="mc-legend mono">
|
|
@@ -251,8 +262,8 @@
|
|
|
251
262
|
</span>
|
|
252
263
|
</div>
|
|
253
264
|
|
|
254
|
-
<button class="reset
|
|
255
|
-
<button class="reset
|
|
265
|
+
<button class="reset" onclick={() => app.requestFrame(mc.id)}>Fit case</button>
|
|
266
|
+
<button class="reset" onclick={() => ctrl.removeMultiCase(mc)}>Remove</button>
|
|
256
267
|
{/if}
|
|
257
268
|
|
|
258
269
|
<style>
|
|
@@ -309,7 +320,6 @@
|
|
|
309
320
|
font-size: 12px;
|
|
310
321
|
color: var(--text-secondary);
|
|
311
322
|
letter-spacing: 0;
|
|
312
|
-
text-transform: uppercase;
|
|
313
323
|
}
|
|
314
324
|
|
|
315
325
|
dl {
|
|
@@ -11,34 +11,32 @@
|
|
|
11
11
|
{@const [cname, cregion] = splitName(app.active?.name ?? '')}
|
|
12
12
|
{@const deltaObjective = stats.deltaObjective}
|
|
13
13
|
<h2>{cname} <span class="region mono">{cregion}</span></h2>
|
|
14
|
-
<dl
|
|
14
|
+
<dl>
|
|
15
15
|
<div>
|
|
16
|
-
<dt>
|
|
17
|
-
<dd>{stats.buses}</dd>
|
|
16
|
+
<dt>Buses</dt>
|
|
17
|
+
<dd class="mono">{stats.buses}</dd>
|
|
18
18
|
</div>
|
|
19
19
|
<div>
|
|
20
|
-
<dt>
|
|
21
|
-
<dd>{stats.branches}</dd>
|
|
20
|
+
<dt>Lines</dt>
|
|
21
|
+
<dd class="mono">{stats.branches}</dd>
|
|
22
22
|
</div>
|
|
23
23
|
{#if ctrl.activeFormulation !== 'acpf'}
|
|
24
24
|
<div>
|
|
25
|
-
<dt>
|
|
26
|
-
<dd>{stats.binding ?? '
|
|
25
|
+
<dt>Binding lines</dt>
|
|
26
|
+
<dd class="mono">{stats.binding ?? '…'}</dd>
|
|
27
27
|
</div>
|
|
28
28
|
<div>
|
|
29
29
|
<dt>OPF objective</dt>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
{
|
|
34
|
-
|
|
35
|
-
{/if}
|
|
36
|
-
</dd>
|
|
30
|
+
{#if stats.objective === null}
|
|
31
|
+
<dd class="blink">Solving…</dd>
|
|
32
|
+
{:else}
|
|
33
|
+
<dd class="mono">{fmt.format(stats.objective)}</dd>
|
|
34
|
+
{/if}
|
|
37
35
|
</div>
|
|
38
36
|
{#if ctrl.isPerturbed(ctrl.activeSolvable) && deltaObjective !== null}
|
|
39
37
|
<div class="delta">
|
|
40
|
-
<dt>
|
|
41
|
-
<dd>{signed(deltaObjective)}</dd>
|
|
38
|
+
<dt>Change from base</dt>
|
|
39
|
+
<dd class="mono">{signed(deltaObjective)}</dd>
|
|
42
40
|
</div>
|
|
43
41
|
{/if}
|
|
44
42
|
{/if}
|
|
@@ -158,7 +158,7 @@
|
|
|
158
158
|
<button
|
|
159
159
|
class="title"
|
|
160
160
|
aria-label={`Move ${panel.title} panel`}
|
|
161
|
-
title="
|
|
161
|
+
title="Drag or use arrow keys to move"
|
|
162
162
|
onpointerdown={(event) => start(event, 'move')}
|
|
163
163
|
onkeydown={key}
|
|
164
164
|
onclick={() => layout.activate(panel)}>{panel.title}</button
|
|
@@ -22,34 +22,33 @@
|
|
|
22
22
|
const neutralPos = $derived(thumbAlignedPos(neutralFrac));
|
|
23
23
|
const fillLo = $derived(valFrac < neutralFrac ? valPos : neutralPos);
|
|
24
24
|
const fillHi = $derived(valFrac < neutralFrac ? neutralPos : valPos);
|
|
25
|
-
const sliderTip =
|
|
26
|
-
|
|
27
|
-
const disabledTip = "this line's rating is synthesized; not adjustable";
|
|
25
|
+
const sliderTip = 'Drag to preview, release to solve. The tick marks the committed rating.';
|
|
26
|
+
const disabledTip = 'Synthesized rating; not adjustable';
|
|
28
27
|
const scoreTip =
|
|
29
|
-
'Gradient
|
|
28
|
+
'Gradient: first-order estimate from the committed point. Exact: solved objective change.';
|
|
30
29
|
</script>
|
|
31
30
|
|
|
32
31
|
{#if ctrl.activeSolvable}
|
|
33
32
|
{@const c = ctrl.activeSolvable}
|
|
34
33
|
{@const branch = ctrl.selectedBranchData}
|
|
35
34
|
<div class="slider-block">
|
|
36
|
-
<div class="slider-head
|
|
37
|
-
<span
|
|
38
|
-
<span class="val">{signed(ctrl.ratingSliderValue)} MW</span>
|
|
35
|
+
<div class="slider-head">
|
|
36
|
+
<span>Rating change</span>
|
|
37
|
+
<span class="val mono">{signed(ctrl.ratingSliderValue)} MW</span>
|
|
39
38
|
</div>
|
|
40
|
-
<div class="line-row
|
|
39
|
+
<div class="line-row">
|
|
41
40
|
{#if branch}
|
|
42
41
|
<span class="dim"
|
|
43
|
-
>
|
|
42
|
+
>Line <span class="mono"
|
|
43
|
+
>{branch.from} – {branch.to}, {fmt.format(branch.rate_mw)} MW</span
|
|
44
|
+
></span
|
|
44
45
|
>
|
|
45
46
|
{/if}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
{
|
|
50
|
-
|
|
51
|
-
{/if}
|
|
52
|
-
</span>
|
|
47
|
+
{#if bounds.disabled}
|
|
48
|
+
<span class="dim">Not adjustable</span>
|
|
49
|
+
{:else}
|
|
50
|
+
<span class="mono dim">{fmt.format(bounds.min)} to +{fmt.format(bounds.max)} MW</span>
|
|
51
|
+
{/if}
|
|
53
52
|
</div>
|
|
54
53
|
<div
|
|
55
54
|
class="slider-track"
|
|
@@ -63,7 +62,7 @@
|
|
|
63
62
|
step="any"
|
|
64
63
|
disabled={bounds.disabled}
|
|
65
64
|
bind:value={ctrl.ratingSliderCurrent, ctrl.setRatingPreview}
|
|
66
|
-
aria-label="
|
|
65
|
+
aria-label="Rating delta at selected line"
|
|
67
66
|
onpointerdown={() => ctrl.setRatingPreview(ctrl.ratingSliderValue)}
|
|
68
67
|
onkeydown={(e) => {
|
|
69
68
|
const next = sliderKeyValue(e.key, ctrl.ratingSliderValue, bounds.min, bounds.max);
|
|
@@ -80,18 +79,18 @@
|
|
|
80
79
|
/>
|
|
81
80
|
</div>
|
|
82
81
|
<div class="rating-feedback" class:idle={!ctrl.previewing && !ctrl.isPerturbed(c)}>
|
|
83
|
-
<p class="pred
|
|
82
|
+
<p class="pred dim" aria-hidden={!(ctrl.predictedDeltaObj !== null && ctrl.previewing)}>
|
|
84
83
|
{#if ctrl.predictedDeltaObj !== null && ctrl.previewing}
|
|
85
|
-
|
|
84
|
+
Predicted objective change <span class="mono">{signed(ctrl.predictedDeltaObj)}</span>
|
|
86
85
|
{:else}
|
|
87
86
|
|
|
88
87
|
{/if}
|
|
89
88
|
</p>
|
|
90
|
-
<p class="score
|
|
89
|
+
<p class="score" aria-hidden={!(ctrl.gradientScore && ctrl.isPerturbed(c))}>
|
|
91
90
|
{#if ctrl.gradientScore && ctrl.isPerturbed(c)}
|
|
92
91
|
<span title={scoreTip}>
|
|
93
|
-
|
|
94
|
-
units
|
|
92
|
+
Gradient <span class="mono">{signed(ctrl.gradientScore.pred)}</span>, exact
|
|
93
|
+
<span class="mono">{signed(ctrl.gradientScore.exact)}</span> objective units
|
|
95
94
|
</span>
|
|
96
95
|
{:else}
|
|
97
96
|
|
|
@@ -99,7 +98,7 @@
|
|
|
99
98
|
</p>
|
|
100
99
|
<div class="reset-row">
|
|
101
100
|
{#if ctrl.isPerturbed(c)}
|
|
102
|
-
<button class="reset
|
|
101
|
+
<button class="reset" onclick={() => ctrl.resetCase(c)}>Reset rating</button>
|
|
103
102
|
{/if}
|
|
104
103
|
</div>
|
|
105
104
|
</div>
|
|
@@ -6,9 +6,7 @@
|
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
8
|
{#if config.loadDefaultCases && ctrl.casesLoaded && ctrl.hiddenDefaults.size > 0}
|
|
9
|
-
<button class="restore-defaults
|
|
10
|
-
↺ restore default cases
|
|
11
|
-
</button>
|
|
9
|
+
<button class="restore-defaults" onclick={ctrl.restoreDefaultCases}>Restore default cases</button>
|
|
12
10
|
{/if}
|
|
13
11
|
|
|
14
12
|
<style>
|
|
@@ -22,6 +20,7 @@
|
|
|
22
20
|
border: 1px solid var(--line);
|
|
23
21
|
border-radius: 3px;
|
|
24
22
|
color: var(--text-secondary);
|
|
23
|
+
font-family: inherit;
|
|
25
24
|
font-size: 11px;
|
|
26
25
|
cursor: pointer;
|
|
27
26
|
box-shadow: var(--elev-1);
|
|
@@ -11,17 +11,11 @@
|
|
|
11
11
|
'objective units'
|
|
12
12
|
)
|
|
13
13
|
);
|
|
14
|
-
const
|
|
15
|
-
`LMP response to active demand, in ${reportedUnits}. The response is small while the active set stays unchanged.`
|
|
16
|
-
);
|
|
17
|
-
const branchUnitTitle = $derived(
|
|
18
|
-
`LMP response to the thermal rating, in ${reportedUnits}. The column is zero when the limit is inactive.`
|
|
19
|
-
);
|
|
14
|
+
const unitTitle = $derived(`Units: ${reportedUnits}`);
|
|
20
15
|
|
|
21
16
|
// Branch mode: the selection is a line and the column is price/rating; the
|
|
22
17
|
// legend and preview pipeline are identical (both columns are bus-keyed).
|
|
23
18
|
const branchMode = $derived(app.selectedBranch !== null);
|
|
24
|
-
const unitTitle = $derived(branchMode ? branchUnitTitle : busUnitTitle);
|
|
25
19
|
const previewUnits = $derived(app.previewPrices?.units ?? 'objective units/MW');
|
|
26
20
|
|
|
27
21
|
const previewStep = $derived(
|
|
@@ -42,25 +36,22 @@
|
|
|
42
36
|
<span class="chip">
|
|
43
37
|
{ctrl.previewing ? 'LMP preview' : branchMode ? '∂LMP/∂rating' : '∂LMP/∂d'}
|
|
44
38
|
</span>
|
|
45
|
-
<span class="
|
|
46
|
-
{#if branchMode}
|
|
39
|
+
<span class="dim">
|
|
40
|
+
{#if branchMode}Line <span class="mono">{branchLabel}</span>{:else}Bus
|
|
41
|
+
<span class="mono">{app.selectedBus}</span>{/if}
|
|
47
42
|
</span>
|
|
48
|
-
<button
|
|
49
|
-
><span class="key-hint">esc </span>clear</button
|
|
50
|
-
>
|
|
43
|
+
<button onclick={ctrl.clearSelection} aria-keyshortcuts="Escape" title="Esc">Clear</button>
|
|
51
44
|
</div>
|
|
52
45
|
<div class="sensitivity-readout" aria-live="polite">
|
|
53
46
|
{#if ctrl.previewing}
|
|
54
47
|
<p class="dim small">
|
|
55
|
-
{ctrl.activeSolvable?.solving
|
|
56
|
-
? 'Exact solve running; the map keeps the LMP preview.'
|
|
57
|
-
: 'First order LMP preview. Release for the exact solve.'}
|
|
48
|
+
{ctrl.activeSolvable?.solving ? 'Solving…' : 'First-order preview. Release to solve.'}
|
|
58
49
|
</p>
|
|
59
50
|
{#if ctrl.sensSummary?.flat}
|
|
60
51
|
<div class="legend flat" style:background={ctrl.flatSensBackground}></div>
|
|
61
52
|
<div class="legend-labels mono single">
|
|
62
53
|
<span
|
|
63
|
-
>
|
|
54
|
+
>Uniform {signedExp(ctrl.sensSummary.mean * previewStep)}
|
|
64
55
|
{previewUnits}</span
|
|
65
56
|
>
|
|
66
57
|
</div>
|
|
@@ -88,7 +79,7 @@
|
|
|
88
79
|
{#if ctrl.sensSummary?.flat}
|
|
89
80
|
<div class="legend flat" style:background={ctrl.flatSensBackground}></div>
|
|
90
81
|
<div class="legend-labels mono single">
|
|
91
|
-
<span>
|
|
82
|
+
<span>Uniform {signedExp(ctrl.sensSummary.mean)} {reportedUnits}</span>
|
|
92
83
|
</div>
|
|
93
84
|
{:else if ctrl.sensSummary}
|
|
94
85
|
<div class="legend" style:background={sensGradient}></div>
|
|
@@ -100,9 +91,7 @@
|
|
|
100
91
|
{:else if app.sensitivityLoading}
|
|
101
92
|
<div class="legend" style:background="var(--line)" style:opacity="0.4"></div>
|
|
102
93
|
<div class="legend-labels mono single">
|
|
103
|
-
<span class="blink">
|
|
104
|
-
computing {#if branchMode}∂value/∂fmax{:else}∂value/∂d{/if}…
|
|
105
|
-
</span>
|
|
94
|
+
<span class="blink">Computing…</span>
|
|
106
95
|
</div>
|
|
107
96
|
{/if}
|
|
108
97
|
{/if}
|
|
@@ -133,11 +122,4 @@
|
|
|
133
122
|
line-height: 1.35;
|
|
134
123
|
white-space: nowrap;
|
|
135
124
|
}
|
|
136
|
-
|
|
137
|
-
/* No Esc key to name on a touch device. */
|
|
138
|
-
@media (hover: none), (pointer: coarse) {
|
|
139
|
-
.key-hint {
|
|
140
|
-
display: none;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
125
|
</style>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getAppState, getController } from '../context.svelte.js';
|
|
3
|
-
import { formulationLabel
|
|
3
|
+
import { formulationLabel } from '../format.js';
|
|
4
4
|
import Sparkline from '../Sparkline.svelte';
|
|
5
5
|
import PanelFrame from './PanelFrame.svelte';
|
|
6
6
|
let open = $state(true);
|
|
@@ -16,67 +16,101 @@
|
|
|
16
16
|
: []
|
|
17
17
|
: (active?.iterations ?? [])
|
|
18
18
|
);
|
|
19
|
+
/** Named only when a server took part; the browser engine is the default. */
|
|
20
|
+
const server = $derived(
|
|
21
|
+
active?.solveBackend === 'rust-server'
|
|
22
|
+
? 'Server'
|
|
23
|
+
: active?.solveBackend === 'clarabel-wasm-server-sensitivity'
|
|
24
|
+
? 'Server price sensitivity'
|
|
25
|
+
: null
|
|
26
|
+
);
|
|
19
27
|
</script>
|
|
20
28
|
|
|
21
29
|
{#if saved || (active && (active.solving || active.solveMs != null))}
|
|
22
30
|
<PanelFrame id="solver" title="Solver" side="right" order={10} width={320} bind:open>
|
|
23
31
|
<div class="solvecard">
|
|
24
|
-
<div class="solve-label mono">
|
|
25
|
-
{saved ? 'Saved result' : active?.formulation === 'acpf' ? 'Power flow' : 'OPF solve'}
|
|
26
|
-
</div>
|
|
27
32
|
{#if iterations.length > 1}
|
|
28
33
|
{#key iterations}
|
|
29
34
|
<Sparkline {iterations} />
|
|
30
35
|
{/key}
|
|
31
36
|
{/if}
|
|
32
37
|
{#if saved}
|
|
33
|
-
<
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
<dl>
|
|
39
|
+
<div>
|
|
40
|
+
<dt>Calculation</dt>
|
|
41
|
+
<dd>{formulationLabel(saved.formulation)}</dd>
|
|
42
|
+
</div>
|
|
43
|
+
<div>
|
|
44
|
+
<dt>Result</dt>
|
|
45
|
+
<dd>{saved.solution?.status ?? 'Not solved'}</dd>
|
|
46
|
+
</div>
|
|
47
|
+
<div>
|
|
48
|
+
<dt>Saved state</dt>
|
|
49
|
+
<dd>{saved.label}</dd>
|
|
50
|
+
</div>
|
|
51
|
+
</dl>
|
|
38
52
|
{:else if active}
|
|
39
|
-
<
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
<dl>
|
|
54
|
+
<div>
|
|
55
|
+
<dt>Calculation</dt>
|
|
56
|
+
<dd>{formulationLabel(active.formulation)}</dd>
|
|
57
|
+
</div>
|
|
58
|
+
{#if iterations.length > 1}
|
|
59
|
+
<div>
|
|
60
|
+
<dt>Iterations</dt>
|
|
61
|
+
<dd class="mono">{iterations.length}</dd>
|
|
62
|
+
</div>
|
|
63
|
+
{/if}
|
|
64
|
+
{#if server}
|
|
65
|
+
<div>
|
|
66
|
+
<dt>Solver</dt>
|
|
67
|
+
<dd>{server}</dd>
|
|
68
|
+
</div>
|
|
69
|
+
{/if}
|
|
70
|
+
{#if active.solveMs != null}
|
|
71
|
+
<div>
|
|
72
|
+
<dt>Time</dt>
|
|
73
|
+
<dd class="mono">{active.solveMs} ms</dd>
|
|
74
|
+
</div>
|
|
75
|
+
{/if}
|
|
76
|
+
{#if active.solveBackend === 'rust-server' && active.solveFallbackReason}
|
|
77
|
+
<div>
|
|
78
|
+
<dt>Fallback</dt>
|
|
79
|
+
<dd title={active.solveFallbackReason}>{active.solveFallbackReason}</dd>
|
|
80
|
+
</div>
|
|
81
|
+
{/if}
|
|
82
|
+
</dl>
|
|
49
83
|
{/if}
|
|
50
84
|
</div>
|
|
51
85
|
</PanelFrame>
|
|
52
86
|
{/if}
|
|
53
87
|
|
|
54
88
|
<style>
|
|
55
|
-
.solve-label {
|
|
56
|
-
font-size: 13px;
|
|
57
|
-
margin-bottom: 8px;
|
|
58
|
-
}
|
|
59
89
|
.solvecard {
|
|
60
90
|
padding: 13px 15px 11px;
|
|
61
91
|
}
|
|
62
92
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
align-items: center;
|
|
66
|
-
flex-wrap: wrap;
|
|
67
|
-
gap: 10px 14px;
|
|
93
|
+
dl {
|
|
94
|
+
margin: 6px 0 0;
|
|
68
95
|
font-size: 12px;
|
|
69
|
-
margin-top: 6px;
|
|
70
96
|
}
|
|
71
97
|
|
|
72
|
-
|
|
73
|
-
|
|
98
|
+
dl div {
|
|
99
|
+
display: flex;
|
|
100
|
+
justify-content: space-between;
|
|
101
|
+
gap: 12px;
|
|
102
|
+
padding: 2px 0;
|
|
74
103
|
}
|
|
75
104
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
105
|
+
dt {
|
|
106
|
+
flex: none;
|
|
107
|
+
color: var(--text-secondary);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
dd {
|
|
111
|
+
min-width: 0;
|
|
112
|
+
margin: 0;
|
|
113
|
+
text-align: right;
|
|
80
114
|
overflow-wrap: anywhere;
|
|
81
115
|
}
|
|
82
116
|
</style>
|
|
@@ -14,15 +14,15 @@
|
|
|
14
14
|
|
|
15
15
|
<div class="movers-block">
|
|
16
16
|
{#if !ctrl.previewing && ctrl.topMovers.length > 0}
|
|
17
|
-
<table
|
|
18
|
-
<caption class="
|
|
19
|
-
|
|
17
|
+
<table>
|
|
18
|
+
<caption class="dim" title={unitTitle}>
|
|
19
|
+
Largest LMP response <span class="mono unit">{units}</span>
|
|
20
20
|
</caption>
|
|
21
21
|
<tbody>
|
|
22
22
|
{#each ctrl.topMovers as mover (mover.bus)}
|
|
23
23
|
<tr>
|
|
24
|
-
<td>
|
|
25
|
-
<td class:pos={mover.value > 0} class:neg={mover.value < 0}>
|
|
24
|
+
<td>Bus <span class="mono">{mover.bus}</span></td>
|
|
25
|
+
<td class="mono" class:pos={mover.value > 0} class:neg={mover.value < 0}>
|
|
26
26
|
{signedExp(mover.value)}
|
|
27
27
|
</td>
|
|
28
28
|
</tr>
|
package/dist/context.svelte.d.ts
CHANGED
|
@@ -9,15 +9,19 @@ export interface TellegenUiConfig {
|
|
|
9
9
|
orgLabel: string;
|
|
10
10
|
showFooter: boolean;
|
|
11
11
|
/** `'dark'` sets `data-theme="dark"` on the document root while the
|
|
12
|
-
* provider is mounted, which switches the `styles.css` tokens.
|
|
12
|
+
* provider is mounted, which switches the `styles.css` tokens. The
|
|
13
|
+
* provider keeps it in step with its prop. */
|
|
13
14
|
theme: 'light' | 'dark';
|
|
14
15
|
/** CARTO basemap key for the default shell's map, trimmed; empty for
|
|
15
|
-
* none. See `TellegenMap`'s `cartoApiKey`.
|
|
16
|
+
* none. See `TellegenMap`'s `cartoApiKey`. The provider keeps it in step
|
|
17
|
+
* with its prop. */
|
|
16
18
|
cartoApiKey: string;
|
|
17
19
|
}
|
|
18
20
|
export type TellegenUiOptions = Partial<TellegenUiConfig>;
|
|
19
21
|
export declare const DEFAULT_TELLEGEN_UI_CONFIG: TellegenUiConfig;
|
|
20
22
|
export declare function resolveTellegenUiConfig(options?: TellegenUiOptions): TellegenUiConfig;
|
|
23
|
+
export declare const resolveTheme: (theme: TellegenUiOptions["theme"]) => TellegenUiConfig["theme"];
|
|
24
|
+
export declare const resolveCartoApiKey: (key: TellegenUiOptions["cartoApiKey"]) => string;
|
|
21
25
|
export declare const setAppState: (a: AppState) => AppState;
|
|
22
26
|
export declare const setController: (c: Controller) => Controller;
|
|
23
27
|
export declare const setUiConfig: (config: TellegenUiConfig) => TellegenUiConfig;
|
package/dist/context.svelte.js
CHANGED
|
@@ -21,10 +21,12 @@ export function resolveTellegenUiConfig(options = {}) {
|
|
|
21
21
|
orgHref: options.orgHref ?? DEFAULT_TELLEGEN_UI_CONFIG.orgHref,
|
|
22
22
|
orgLabel: options.orgLabel ?? DEFAULT_TELLEGEN_UI_CONFIG.orgLabel,
|
|
23
23
|
showFooter: options.showFooter ?? DEFAULT_TELLEGEN_UI_CONFIG.showFooter,
|
|
24
|
-
theme: options.theme
|
|
25
|
-
cartoApiKey:
|
|
24
|
+
theme: resolveTheme(options.theme),
|
|
25
|
+
cartoApiKey: resolveCartoApiKey(options.cartoApiKey)
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
+
export const resolveTheme = (theme) => theme ?? DEFAULT_TELLEGEN_UI_CONFIG.theme;
|
|
29
|
+
export const resolveCartoApiKey = (key) => normalizeCartoApiKey(key) ?? DEFAULT_TELLEGEN_UI_CONFIG.cartoApiKey;
|
|
28
30
|
export const setAppState = (a) => setContext(APP, a);
|
|
29
31
|
export const setController = (c) => setContext(CTRL, c);
|
|
30
32
|
export const setUiConfig = (config) => setContext(CONFIG, config);
|