@tellegen/svelte 0.3.0 → 0.4.1
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 +233 -0
- package/dist/Sparkline.svelte +2 -2
- package/dist/TellegenMap.svelte +239 -66
- package/dist/TellegenMap.svelte.d.ts +45 -0
- package/dist/TellegenProvider.svelte +36 -4
- package/dist/TellegenShell.svelte +14 -42
- package/dist/TellegenViewer.svelte +12 -2
- package/dist/api.d.ts +10 -2
- package/dist/api.js +2 -0
- package/dist/basemap.d.ts +192 -0
- package/dist/basemap.js +259 -0
- package/dist/colors.d.ts +55 -1
- package/dist/colors.js +89 -0
- package/dist/components/AppFooter.svelte +3 -2
- package/dist/components/AppHeader.svelte +21 -8
- package/dist/components/BindingLines.svelte +1 -1
- package/dist/components/BusPicker.svelte +3 -3
- package/dist/components/ControlPanel.svelte +4 -2
- package/dist/components/DemandSlider.svelte +16 -7
- package/dist/components/DiagramCanvas.svelte +17 -5
- package/dist/components/DisplayControls.svelte +5 -1
- package/dist/components/DropZone.svelte +1 -1
- package/dist/components/FormulationSelector.svelte +1 -1
- package/dist/components/LocalCaseDetails.svelte +6 -3
- package/dist/components/McResults.svelte +98 -35
- package/dist/components/McResults.svelte.d.ts +8 -2
- package/dist/components/ModelDetails.svelte +5 -5
- package/dist/components/MulticonductorDetails.svelte +9 -9
- package/dist/components/NotificationCenter.svelte +4 -2
- package/dist/components/PanelCard.svelte +72 -7
- package/dist/components/PanelHost.svelte +59 -34
- package/dist/components/PanelHost.svelte.d.ts +4 -1
- package/dist/components/PlacementCue.svelte +1 -1
- package/dist/components/PowerFlowDetails.svelte +1 -1
- package/dist/components/RatingSlider.svelte +14 -5
- package/dist/components/RestoreDefaultsButton.svelte +1 -1
- package/dist/components/SavedNetworkDetails.svelte +3 -3
- package/dist/components/SensitivityReadout.svelte +2 -2
- package/dist/components/ShellEnvironment.svelte +51 -0
- package/dist/components/ShellEnvironment.svelte.d.ts +18 -0
- package/dist/components/SizeLegend.svelte +2 -2
- package/dist/components/SolveCard.svelte +3 -3
- package/dist/components/TopMovers.svelte +1 -1
- package/dist/components.d.ts +3 -0
- package/dist/components.js +3 -0
- package/dist/context.svelte.d.ts +8 -1
- package/dist/context.svelte.js +8 -3
- package/dist/controller.svelte.d.ts +65 -5
- package/dist/controller.svelte.js +266 -78
- package/dist/display.d.ts +3 -2
- package/dist/display.js +22 -4
- package/dist/format.d.ts +9 -0
- package/dist/format.js +13 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +2 -0
- package/dist/map.d.ts +4 -2
- package/dist/map.js +2 -1
- package/dist/maplibre-worker.d.ts +5 -0
- package/dist/maplibre-worker.dev.d.ts +11 -0
- package/dist/maplibre-worker.dev.js +15 -0
- package/dist/maplibre-worker.js +8 -0
- package/dist/notices.svelte.js +5 -1
- package/dist/panels.svelte.d.ts +6 -0
- package/dist/panels.svelte.js +31 -1
- package/dist/slider.d.ts +12 -0
- package/dist/slider.js +31 -0
- package/dist/state.svelte.d.ts +25 -3
- package/dist/state.svelte.js +13 -0
- package/dist/styles.css +59 -6
- package/dist/types.d.ts +1 -1
- package/package.json +13 -8
package/dist/colors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DisplayMode } from './state.svelte.js';
|
|
1
|
+
import type { BusSizing, DisplayMode } from './state.svelte.js';
|
|
2
2
|
export type RGBA = [number, number, number, number];
|
|
3
3
|
export interface SensitivityDomain {
|
|
4
4
|
min: number;
|
|
@@ -10,6 +10,13 @@ export interface SensitivityDomain {
|
|
|
10
10
|
}
|
|
11
11
|
export declare const priceColor: (t: number) => RGBA;
|
|
12
12
|
export declare const priceGradient: string;
|
|
13
|
+
/** Demand colour for a fraction `t` of the demand domain, on a square-root
|
|
14
|
+
* scale: bus demand is heavy tailed (a few large cities, many small towns),
|
|
15
|
+
* and a linear scale would paint everything but the largest buses one hue. */
|
|
16
|
+
export declare const demandColor: (t: number) => RGBA;
|
|
17
|
+
/** The demand ramp as a CSS gradient whose stops sit where the square-root
|
|
18
|
+
* scale puts them, so a legend bar reads linearly from its low to high label. */
|
|
19
|
+
export declare const demandGradient: string;
|
|
13
20
|
/** Trimmed nodal value color domain: 5th to 95th percentile, with a one-unit
|
|
14
21
|
* minimum span. Nodal values are heavy tailed when one binding line pins a
|
|
15
22
|
* handful of buses far from the pack) so min-max scaling compresses the pack
|
|
@@ -20,6 +27,14 @@ export declare function priceDomain(values: number[]): {
|
|
|
20
27
|
lo: number;
|
|
21
28
|
hi: number;
|
|
22
29
|
};
|
|
30
|
+
/** Demand colour domain: zero to the 98th percentile, at least 1 MW. Zero
|
|
31
|
+
* anchors the scale so a bus without load reads as none; the percentile keeps
|
|
32
|
+
* one metropolis from washing out every other bus, and the few above it clamp
|
|
33
|
+
* to the top of the ramp. */
|
|
34
|
+
export declare function demandDomain(values: number[]): {
|
|
35
|
+
lo: number;
|
|
36
|
+
hi: number;
|
|
37
|
+
};
|
|
23
38
|
/** Center a scalar display variable (bus voltage angle, |V|) on a symmetric domain
|
|
24
39
|
* with a per-mode minimum span; nodal values keep their own robust quantile domain. Shared by
|
|
25
40
|
* the control panel readout and the map color scale. */
|
|
@@ -32,6 +47,24 @@ export declare const sensColor: (t: number) => RGBA;
|
|
|
32
47
|
export declare const sensGradient: string;
|
|
33
48
|
export declare const sensNeutral: RGBA;
|
|
34
49
|
export declare const busNeutral: RGBA;
|
|
50
|
+
/** Overlay colours that depend on the basemap's lightness: bus outlines,
|
|
51
|
+
* hover highlights, the selected outline, the "not yet solved" grey, and
|
|
52
|
+
* the `.pwd` substation marker. The selection blue is the same in both. */
|
|
53
|
+
export interface MapPalette {
|
|
54
|
+
outline: RGBA;
|
|
55
|
+
/** The multiconductor view's bus outline, a shade stronger. */
|
|
56
|
+
outlineStrong: RGBA;
|
|
57
|
+
highlight: RGBA;
|
|
58
|
+
highlightSoft: RGBA;
|
|
59
|
+
selected: RGBA;
|
|
60
|
+
grey: RGBA;
|
|
61
|
+
greyLine: RGBA;
|
|
62
|
+
substation: RGBA;
|
|
63
|
+
substationLine: RGBA;
|
|
64
|
+
/** Fill of a neutral (non-phase) terminal in the multiconductor detail. */
|
|
65
|
+
neutral: RGBA;
|
|
66
|
+
}
|
|
67
|
+
export declare const MAP_PALETTE: Record<'light' | 'dark', MapPalette>;
|
|
35
68
|
export declare function sensFlatColor(domain: Pick<SensitivityDomain, 'mean'>): RGBA;
|
|
36
69
|
/** The fixed normalization for a slider drag: predicted nodal value change divides by
|
|
37
70
|
* scale × maxAbsStep, so color intensity is linear in the step and saturates at
|
|
@@ -48,3 +81,24 @@ export declare function branchWidth(loading: number): number;
|
|
|
48
81
|
/** Bus radius in px; area tracks max(load, gen) so big plants and big loads
|
|
49
82
|
* both read at a glance. Shared by the map layers and the size legend. */
|
|
50
83
|
export declare function busRadius(maxMw: number): number;
|
|
84
|
+
/** Radius in px of every bus marker under `uniform` sizing, and the most a
|
|
85
|
+
* uniform marker grows to on the map. */
|
|
86
|
+
export declare const UNIFORM_BUS_RADIUS = 4.5;
|
|
87
|
+
/** The least a uniform marker shrinks to on the map, in px. */
|
|
88
|
+
export declare const UNIFORM_BUS_MIN_RADIUS = 2;
|
|
89
|
+
/** The ground radius of a uniform marker on the map, in metres. */
|
|
90
|
+
export declare const UNIFORM_BUS_GROUND_RADIUS = 9000;
|
|
91
|
+
/** Bus radius in px under a sizing choice: `power` scales with max(load, gen)
|
|
92
|
+
* through `busRadius`, `uniform` draws every bus alike. */
|
|
93
|
+
export declare function busRadiusFor(sizing: BusSizing, maxMw: number): number;
|
|
94
|
+
/** Radius settings for the map's bus markers under a sizing choice. `power`
|
|
95
|
+
* sizes in pixels through `busRadius`. `uniform` gives every bus one ground
|
|
96
|
+
* radius, drawn between `UNIFORM_BUS_MIN_RADIUS` and `UNIFORM_BUS_RADIUS`
|
|
97
|
+
* pixels, so a whole country reads as a mesh of dots with its lines showing
|
|
98
|
+
* and a closer view as distinct buses. */
|
|
99
|
+
export declare function busMarkerRadius(sizing: BusSizing): {
|
|
100
|
+
radius: (maxMw: number) => number;
|
|
101
|
+
radiusUnits: 'meters' | 'pixels';
|
|
102
|
+
radiusMinPixels: number;
|
|
103
|
+
radiusMaxPixels: number;
|
|
104
|
+
};
|
package/dist/colors.js
CHANGED
|
@@ -31,6 +31,24 @@ const PRICE_STOPS = [
|
|
|
31
31
|
];
|
|
32
32
|
export const priceColor = (t) => ramp(PRICE_STOPS, t);
|
|
33
33
|
export const priceGradient = cssGradient(PRICE_STOPS);
|
|
34
|
+
/** Sequential demand ramp: the upper three quarters of viridis, slate blue
|
|
35
|
+
* through teal and green to yellow. Lightness rises monotonically, the dimmest
|
|
36
|
+
* stop still reads on a dark basemap, and the cool hues cannot be mistaken for
|
|
37
|
+
* the warm nodal value ramp. */
|
|
38
|
+
const DEMAND_STOPS = [
|
|
39
|
+
[59, 82, 139, 235],
|
|
40
|
+
[44, 114, 142, 235],
|
|
41
|
+
[33, 145, 140, 240],
|
|
42
|
+
[94, 201, 98, 245],
|
|
43
|
+
[253, 231, 37, 250]
|
|
44
|
+
];
|
|
45
|
+
/** Demand colour for a fraction `t` of the demand domain, on a square-root
|
|
46
|
+
* scale: bus demand is heavy tailed (a few large cities, many small towns),
|
|
47
|
+
* and a linear scale would paint everything but the largest buses one hue. */
|
|
48
|
+
export const demandColor = (t) => ramp(DEMAND_STOPS, Math.sqrt(Math.max(0, t)));
|
|
49
|
+
/** The demand ramp as a CSS gradient whose stops sit where the square-root
|
|
50
|
+
* scale puts them, so a legend bar reads linearly from its low to high label. */
|
|
51
|
+
export const demandGradient = `linear-gradient(90deg, ${DEMAND_STOPS.map((s, i) => `rgb(${s[0]} ${s[1]} ${s[2]}) ${((i / (DEMAND_STOPS.length - 1)) ** 2 * 100).toFixed(2)}%`).join(', ')})`;
|
|
34
52
|
/** Trimmed nodal value color domain: 5th to 95th percentile, with a one-unit
|
|
35
53
|
* minimum span. Nodal values are heavy tailed when one binding line pins a
|
|
36
54
|
* handful of buses far from the pack) so min-max scaling compresses the pack
|
|
@@ -48,12 +66,25 @@ export function priceDomain(values) {
|
|
|
48
66
|
const span = Math.max(hi - lo, 1);
|
|
49
67
|
return { lo: mid - span / 2, hi: mid + span / 2 };
|
|
50
68
|
}
|
|
69
|
+
/** Demand colour domain: zero to the 98th percentile, at least 1 MW. Zero
|
|
70
|
+
* anchors the scale so a bus without load reads as none; the percentile keeps
|
|
71
|
+
* one metropolis from washing out every other bus, and the few above it clamp
|
|
72
|
+
* to the top of the ramp. */
|
|
73
|
+
export function demandDomain(values) {
|
|
74
|
+
if (values.length === 0)
|
|
75
|
+
return { lo: 0, hi: 1 };
|
|
76
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
77
|
+
const hi = sorted[Math.min(sorted.length - 1, Math.floor(0.98 * sorted.length))];
|
|
78
|
+
return { lo: 0, hi: Math.max(hi, 1) };
|
|
79
|
+
}
|
|
51
80
|
/** Center a scalar display variable (bus voltage angle, |V|) on a symmetric domain
|
|
52
81
|
* with a per-mode minimum span; nodal values keep their own robust quantile domain. Shared by
|
|
53
82
|
* the control panel readout and the map color scale. */
|
|
54
83
|
export function scalarDomain(mode, values) {
|
|
55
84
|
if (mode === 'price')
|
|
56
85
|
return priceDomain(values);
|
|
86
|
+
if (mode === 'demand')
|
|
87
|
+
return demandDomain(values);
|
|
57
88
|
if (values.length === 0)
|
|
58
89
|
return { lo: 0, hi: 1 };
|
|
59
90
|
const { min: rawLo, max: rawHi } = extent(values);
|
|
@@ -78,6 +109,32 @@ export const sensColor = (t) => ramp(SENS_STOPS, (t + 1) / 2);
|
|
|
78
109
|
export const sensGradient = cssGradient(SENS_STOPS);
|
|
79
110
|
export const sensNeutral = SENS_STOPS[2];
|
|
80
111
|
export const busNeutral = [180, 175, 165, 200];
|
|
112
|
+
export const MAP_PALETTE = {
|
|
113
|
+
light: {
|
|
114
|
+
outline: [46, 42, 34, 110],
|
|
115
|
+
outlineStrong: [46, 42, 34, 120],
|
|
116
|
+
highlight: [32, 36, 43, 90],
|
|
117
|
+
highlightSoft: [32, 36, 43, 70],
|
|
118
|
+
selected: [32, 36, 43, 255],
|
|
119
|
+
grey: [110, 115, 120, 200],
|
|
120
|
+
greyLine: [138, 131, 117, 150],
|
|
121
|
+
substation: [70, 92, 124, 190],
|
|
122
|
+
substationLine: [38, 52, 78, 220],
|
|
123
|
+
neutral: [120, 114, 102, 255]
|
|
124
|
+
},
|
|
125
|
+
dark: {
|
|
126
|
+
outline: [232, 238, 244, 120],
|
|
127
|
+
outlineStrong: [232, 238, 244, 130],
|
|
128
|
+
highlight: [232, 238, 244, 90],
|
|
129
|
+
highlightSoft: [232, 238, 244, 70],
|
|
130
|
+
selected: [255, 255, 255, 255],
|
|
131
|
+
grey: [150, 160, 170, 200],
|
|
132
|
+
greyLine: [150, 160, 170, 150],
|
|
133
|
+
substation: [120, 150, 200, 190],
|
|
134
|
+
substationLine: [180, 205, 235, 220],
|
|
135
|
+
neutral: [150, 160, 170, 255]
|
|
136
|
+
}
|
|
137
|
+
};
|
|
81
138
|
const FLAT_SENSITIVITY_TINT = 0.55;
|
|
82
139
|
export function sensFlatColor(domain) {
|
|
83
140
|
if (domain.mean === 0)
|
|
@@ -138,3 +195,35 @@ export function branchWidth(loading) {
|
|
|
138
195
|
export function busRadius(maxMw) {
|
|
139
196
|
return 3.2 + 0.45 * Math.sqrt(Math.max(maxMw, 1));
|
|
140
197
|
}
|
|
198
|
+
/** Radius in px of every bus marker under `uniform` sizing, and the most a
|
|
199
|
+
* uniform marker grows to on the map. */
|
|
200
|
+
export const UNIFORM_BUS_RADIUS = 4.5;
|
|
201
|
+
/** The least a uniform marker shrinks to on the map, in px. */
|
|
202
|
+
export const UNIFORM_BUS_MIN_RADIUS = 2;
|
|
203
|
+
/** The ground radius of a uniform marker on the map, in metres. */
|
|
204
|
+
export const UNIFORM_BUS_GROUND_RADIUS = 9000;
|
|
205
|
+
/** Bus radius in px under a sizing choice: `power` scales with max(load, gen)
|
|
206
|
+
* through `busRadius`, `uniform` draws every bus alike. */
|
|
207
|
+
export function busRadiusFor(sizing, maxMw) {
|
|
208
|
+
return sizing === 'uniform' ? UNIFORM_BUS_RADIUS : busRadius(maxMw);
|
|
209
|
+
}
|
|
210
|
+
/** Radius settings for the map's bus markers under a sizing choice. `power`
|
|
211
|
+
* sizes in pixels through `busRadius`. `uniform` gives every bus one ground
|
|
212
|
+
* radius, drawn between `UNIFORM_BUS_MIN_RADIUS` and `UNIFORM_BUS_RADIUS`
|
|
213
|
+
* pixels, so a whole country reads as a mesh of dots with its lines showing
|
|
214
|
+
* and a closer view as distinct buses. */
|
|
215
|
+
export function busMarkerRadius(sizing) {
|
|
216
|
+
return sizing === 'uniform'
|
|
217
|
+
? {
|
|
218
|
+
radius: () => UNIFORM_BUS_GROUND_RADIUS,
|
|
219
|
+
radiusUnits: 'meters',
|
|
220
|
+
radiusMinPixels: UNIFORM_BUS_MIN_RADIUS,
|
|
221
|
+
radiusMaxPixels: UNIFORM_BUS_RADIUS
|
|
222
|
+
}
|
|
223
|
+
: {
|
|
224
|
+
radius: busRadius,
|
|
225
|
+
radiusUnits: 'pixels',
|
|
226
|
+
radiusMinPixels: 0,
|
|
227
|
+
radiusMaxPixels: Number.MAX_SAFE_INTEGER
|
|
228
|
+
};
|
|
229
|
+
}
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
padding: 8px 20px;
|
|
32
32
|
font-size: 10.5px;
|
|
33
33
|
color: var(--text-secondary);
|
|
34
|
-
background: linear-gradient(
|
|
34
|
+
background: linear-gradient(rgb(var(--bg-rgb) / 0), rgb(var(--bg-rgb) / 0.9));
|
|
35
35
|
animation: rise 0.5s 0.24s ease-out both;
|
|
36
36
|
pointer-events: none;
|
|
37
37
|
}
|
|
@@ -53,10 +53,12 @@
|
|
|
53
53
|
color: var(--text-accent);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/* Inside the Network panel on narrow screens, panel text size applies. */
|
|
56
57
|
footer.inline {
|
|
57
58
|
position: static;
|
|
58
59
|
margin-top: 14px;
|
|
59
60
|
padding: 0;
|
|
61
|
+
font-size: 12px;
|
|
60
62
|
background: none;
|
|
61
63
|
animation: none;
|
|
62
64
|
pointer-events: auto;
|
|
@@ -71,7 +73,6 @@
|
|
|
71
73
|
|
|
72
74
|
footer.inline {
|
|
73
75
|
margin-top: 4px;
|
|
74
|
-
font-size: 11px;
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
<header bind:this={headerEl}>
|
|
27
27
|
<div class="brand">
|
|
28
28
|
<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
|
|
29
|
-
<path d="M4 18 L12 6 L20 18" stroke
|
|
30
|
-
<circle cx="4" cy="18" r="2.4"
|
|
31
|
-
<circle cx="12" cy="6" r="2.4"
|
|
32
|
-
<circle cx="20" cy="18" r="2.4"
|
|
29
|
+
<path class="brand-accent-stroke" d="M4 18 L12 6 L20 18" stroke-width="1.6" fill="none" />
|
|
30
|
+
<circle class="brand-accent" cx="4" cy="18" r="2.4" />
|
|
31
|
+
<circle class="brand-ink" cx="12" cy="6" r="2.4" />
|
|
32
|
+
<circle class="brand-accent" cx="20" cy="18" r="2.4" />
|
|
33
33
|
</svg>
|
|
34
34
|
<h1>tellegen</h1>
|
|
35
35
|
</div>
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
justify-content: space-between;
|
|
131
131
|
gap: 16px;
|
|
132
132
|
padding: 10px 20px;
|
|
133
|
-
background: linear-gradient(
|
|
133
|
+
background: linear-gradient(rgb(var(--bg-rgb) / 0.95), rgb(var(--bg-rgb) / 0));
|
|
134
134
|
animation: drop 0.5s ease-out both;
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -141,6 +141,19 @@
|
|
|
141
141
|
gap: 10px;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
/* The brand mark follows the theme: accent legs, ink apex. */
|
|
145
|
+
.brand-accent {
|
|
146
|
+
fill: var(--accent);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.brand-accent-stroke {
|
|
150
|
+
stroke: var(--accent);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.brand-ink {
|
|
154
|
+
fill: var(--ink);
|
|
155
|
+
}
|
|
156
|
+
|
|
144
157
|
h1 {
|
|
145
158
|
margin: 0;
|
|
146
159
|
font-size: 22px;
|
|
@@ -305,8 +318,8 @@
|
|
|
305
318
|
|
|
306
319
|
/* Ghost chip: standing invitation to drop or pick a case file. */
|
|
307
320
|
.cases > button.ghost {
|
|
308
|
-
background:
|
|
309
|
-
border: 1px dashed
|
|
321
|
+
background: rgb(var(--paper-rgb) / 0.36);
|
|
322
|
+
border: 1px dashed rgb(var(--accent-rgb) / 0.55);
|
|
310
323
|
color: var(--text-secondary);
|
|
311
324
|
box-shadow: inset 0 0 0 1px rgba(212, 116, 34, 0.08);
|
|
312
325
|
}
|
|
@@ -351,7 +364,7 @@
|
|
|
351
364
|
flex-wrap: wrap;
|
|
352
365
|
gap: 8px;
|
|
353
366
|
padding: 8px 10px 12px;
|
|
354
|
-
background: linear-gradient(
|
|
367
|
+
background: linear-gradient(rgb(var(--bg-rgb) / 0.97), rgb(var(--bg-rgb) / 0.72));
|
|
355
368
|
}
|
|
356
369
|
|
|
357
370
|
.brand {
|
|
@@ -155,7 +155,7 @@
|
|
|
155
155
|
|
|
156
156
|
label {
|
|
157
157
|
display: block;
|
|
158
|
-
font-size:
|
|
158
|
+
font-size: 12px;
|
|
159
159
|
text-transform: uppercase;
|
|
160
160
|
margin-bottom: 3px;
|
|
161
161
|
}
|
|
@@ -220,7 +220,7 @@
|
|
|
220
220
|
background: var(--panel);
|
|
221
221
|
border: 1px solid var(--line);
|
|
222
222
|
border-radius: var(--radius-xs, 2px);
|
|
223
|
-
box-shadow:
|
|
223
|
+
box-shadow: var(--elev-menu);
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
li {
|
|
@@ -244,7 +244,7 @@
|
|
|
244
244
|
border: 1px solid var(--line);
|
|
245
245
|
border-radius: var(--radius-xs, 2px);
|
|
246
246
|
box-shadow: var(--elev-1);
|
|
247
|
-
font-size:
|
|
247
|
+
font-size: 12.5px;
|
|
248
248
|
margin: 4px 0 0;
|
|
249
249
|
}
|
|
250
250
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getAppState, getController } from '../context.svelte.js';
|
|
3
3
|
import { fmt, signed } from '../format.js';
|
|
4
|
+
import { sliderKeyValue } from '../slider.js';
|
|
4
5
|
|
|
5
6
|
const app = getAppState();
|
|
6
7
|
const ctrl = getController();
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
<span class="val">{signed(ctrl.sliderValue)} MW</span>
|
|
38
39
|
</div>
|
|
39
40
|
<div class="range-mode">
|
|
40
|
-
<div class="segment mono" aria-label="demand range">
|
|
41
|
+
<div class="segment mono" role="group" aria-label="demand range">
|
|
41
42
|
<button
|
|
42
43
|
type="button"
|
|
43
44
|
class:active={app.demandRangeMode === 'local'}
|
|
@@ -51,7 +52,7 @@
|
|
|
51
52
|
type="button"
|
|
52
53
|
class:active={app.demandRangeMode === 'full'}
|
|
53
54
|
aria-pressed={app.demandRangeMode === 'full'}
|
|
54
|
-
aria-label="full demand
|
|
55
|
+
aria-label="full range of demand"
|
|
55
56
|
{disabled}
|
|
56
57
|
title="range from zero load to the local physical limit"
|
|
57
58
|
onclick={() => ctrl.setDemandRangeMode('full')}>full range</button
|
|
@@ -79,7 +80,15 @@
|
|
|
79
80
|
bind:value={ctrl.sliderCurrent, ctrl.setSliderPreview}
|
|
80
81
|
aria-label="demand delta at selected bus"
|
|
81
82
|
onpointerdown={() => ctrl.setSliderPreview(ctrl.sliderValue)}
|
|
82
|
-
onkeydown={() =>
|
|
83
|
+
onkeydown={(e) => {
|
|
84
|
+
const next = sliderKeyValue(e.key, ctrl.sliderValue, ctrl.sliderMin, ctrl.sliderMax);
|
|
85
|
+
if (next === null) {
|
|
86
|
+
ctrl.setSliderPreview(ctrl.sliderValue);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
e.preventDefault();
|
|
90
|
+
ctrl.setSliderPreview(next);
|
|
91
|
+
}}
|
|
83
92
|
onpointerup={(e) => ctrl.finishDemandInput(Number(e.currentTarget.value))}
|
|
84
93
|
onkeyup={(e) => ctrl.finishDemandInput(Number(e.currentTarget.value))}
|
|
85
94
|
onchange={(e) => ctrl.finishDemandInput(Number(e.currentTarget.value))}
|
|
@@ -120,7 +129,7 @@
|
|
|
120
129
|
.slider-head {
|
|
121
130
|
display: flex;
|
|
122
131
|
justify-content: space-between;
|
|
123
|
-
font-size:
|
|
132
|
+
font-size: 13.5px;
|
|
124
133
|
color: var(--text-secondary);
|
|
125
134
|
margin-bottom: 4px;
|
|
126
135
|
}
|
|
@@ -135,7 +144,7 @@
|
|
|
135
144
|
justify-content: space-between;
|
|
136
145
|
gap: 8px;
|
|
137
146
|
margin: 6px 0 7px;
|
|
138
|
-
font-size:
|
|
147
|
+
font-size: 12.5px;
|
|
139
148
|
}
|
|
140
149
|
|
|
141
150
|
.slider-track {
|
|
@@ -256,13 +265,13 @@
|
|
|
256
265
|
|
|
257
266
|
.pred {
|
|
258
267
|
margin: 2px 0 0;
|
|
259
|
-
font-size:
|
|
268
|
+
font-size: 13px;
|
|
260
269
|
min-height: 16px;
|
|
261
270
|
}
|
|
262
271
|
|
|
263
272
|
.score {
|
|
264
273
|
margin: 8px 0 0;
|
|
265
|
-
font-size:
|
|
274
|
+
font-size: 13px;
|
|
266
275
|
color: var(--ink);
|
|
267
276
|
min-height: 16px;
|
|
268
277
|
}
|
|
@@ -4,7 +4,14 @@
|
|
|
4
4
|
import type { Network, NetworkBus } from '../api.js';
|
|
5
5
|
import { getAppState, getController } from '../context.svelte.js';
|
|
6
6
|
import { getPanelLayout } from '../panels.svelte.js';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
branchColor,
|
|
9
|
+
busNeutral,
|
|
10
|
+
busRadiusFor,
|
|
11
|
+
demandColor,
|
|
12
|
+
priceColor,
|
|
13
|
+
scalarDomain
|
|
14
|
+
} from '../colors.js';
|
|
8
15
|
import { rgbaCss } from '../format.js';
|
|
9
16
|
import type { MulticonductorCase } from '../state.svelte.js';
|
|
10
17
|
import { edgeColor, attachmentColor } from '../multiconductor.js';
|
|
@@ -66,6 +73,8 @@
|
|
|
66
73
|
if (multiconductor) return [];
|
|
67
74
|
if (!app.studyView) return ctrl.activeDisplay?.values ?? [];
|
|
68
75
|
const solution = app.studyView.solution;
|
|
76
|
+
if (app.displayMode === 'demand')
|
|
77
|
+
return app.studyView.network.buses.map((bus) => ({ bus: bus.id, value: bus.demand_mw }));
|
|
69
78
|
if (app.displayMode === 'angle') return solution?.va ?? [];
|
|
70
79
|
if (app.displayMode === 'voltage')
|
|
71
80
|
return (
|
|
@@ -107,7 +116,9 @@
|
|
|
107
116
|
return rgbaCss(
|
|
108
117
|
value === undefined
|
|
109
118
|
? busNeutral
|
|
110
|
-
:
|
|
119
|
+
: (displayMode === 'demand' ? demandColor : priceColor)(
|
|
120
|
+
(value - domain.lo) / Math.max(domain.hi - domain.lo, 1e-12)
|
|
121
|
+
)
|
|
111
122
|
);
|
|
112
123
|
}
|
|
113
124
|
function branchLabel(id: number) {
|
|
@@ -348,9 +359,10 @@
|
|
|
348
359
|
<circle
|
|
349
360
|
cx={bus.lon}
|
|
350
361
|
cy={bus.lat}
|
|
351
|
-
r={Math.max(5,
|
|
362
|
+
r={Math.max(5, busRadiusFor(app.busSizing, Math.max(bus.demand_mw, bus.gen_mw))) /
|
|
363
|
+
scale}
|
|
352
364
|
fill={color(bus)}
|
|
353
|
-
stroke={selectedBus === bus.id ? '#2f6fbb' : '
|
|
365
|
+
style:stroke={selectedBus === bus.id ? '#2f6fbb' : 'var(--diagram-bus-stroke)'}
|
|
354
366
|
stroke-width={selectedBus === bus.id ? 3 : 1}
|
|
355
367
|
vector-effect="non-scaling-stroke"
|
|
356
368
|
/>
|
|
@@ -364,7 +376,7 @@
|
|
|
364
376
|
>Bus {multiconductor ? bus.uid : bus.id}{!multiconductor && bus.name
|
|
365
377
|
? `, ${bus.name}`
|
|
366
378
|
: ''}, {(bus.demand_mw * (multiconductor ? 1000 : 1)).toFixed(1)}
|
|
367
|
-
{multiconductor ? 'kW' : 'MW'} demand{values.has(bus.id)
|
|
379
|
+
{multiconductor ? 'kW' : 'MW'} demand{displayMode !== 'demand' && values.has(bus.id)
|
|
368
380
|
? `, ${values.get(bus.id)!.toFixed(4)} ${displayMode === 'price' ? 'objective units/MW LMP' : displayMode === 'voltage' ? 'pu voltage' : 'rad voltage angle'}`
|
|
369
381
|
: ''}</title
|
|
370
382
|
>
|
|
@@ -59,10 +59,14 @@
|
|
|
59
59
|
</span>
|
|
60
60
|
{/if}
|
|
61
61
|
</div>
|
|
62
|
-
{:else}
|
|
62
|
+
{:else if ctrl.activeSolvable?.solving}
|
|
63
63
|
<p class="dim small blink">
|
|
64
64
|
Solving {formulationLabel(ctrl.activeFormulation)}…
|
|
65
65
|
</p>
|
|
66
|
+
{:else}
|
|
67
|
+
<p class="dim small">
|
|
68
|
+
No {formulationLabel(ctrl.activeFormulation)} results available.
|
|
69
|
+
</p>
|
|
66
70
|
{/if}
|
|
67
71
|
|
|
68
72
|
<style>
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
const EXPORT_FORMATS = [
|
|
12
12
|
{ token: 'matpower', label: 'MATPOWER (.m)' },
|
|
13
13
|
{ token: 'psse', label: 'PSS/E (.raw)' },
|
|
14
|
+
{ token: 'psse35', label: 'PSS/E v35 (.raw)' },
|
|
14
15
|
{ token: 'powerworld', label: 'PowerWorld (.aux)' },
|
|
15
16
|
{ token: 'pslf', label: 'PSLF (.epc)' },
|
|
16
17
|
{ token: 'powermodels-json', label: 'PowerModels (.json)' },
|
|
@@ -117,6 +118,8 @@
|
|
|
117
118
|
<p class="footnote mono">
|
|
118
119
|
no coordinates in this file: click the map or drop a geographic file
|
|
119
120
|
</p>
|
|
121
|
+
{:else if lc.coordsKind === 'synthetic' && lc.summary.coords_method === 'generated'}
|
|
122
|
+
<p class="footnote mono">coordinates: synthetic grid sited on real geography</p>
|
|
120
123
|
{:else if lc.coordsKind === 'synthetic' || lc.coordsKind === 'manual'}
|
|
121
124
|
<p class="footnote mono">
|
|
122
125
|
coordinates: {lc.coordsKind} topology layout centered where it was placed
|
|
@@ -222,7 +225,7 @@
|
|
|
222
225
|
|
|
223
226
|
.footnote {
|
|
224
227
|
margin: 8px 0 0;
|
|
225
|
-
font-size:
|
|
228
|
+
font-size: 12px;
|
|
226
229
|
color: var(--text-tertiary);
|
|
227
230
|
letter-spacing: 0;
|
|
228
231
|
}
|
|
@@ -231,7 +234,7 @@
|
|
|
231
234
|
margin: 8px 0 0;
|
|
232
235
|
padding: 0;
|
|
233
236
|
list-style: none;
|
|
234
|
-
font-size:
|
|
237
|
+
font-size: 12.5px;
|
|
235
238
|
line-height: 1.5;
|
|
236
239
|
color: var(--text-accent);
|
|
237
240
|
}
|
|
@@ -254,7 +257,7 @@
|
|
|
254
257
|
padding: 4px;
|
|
255
258
|
list-style: none;
|
|
256
259
|
min-width: 100%;
|
|
257
|
-
background: var(--surface,
|
|
260
|
+
background: var(--surface, var(--surface-menu));
|
|
258
261
|
border: 1px solid var(--border, rgba(0, 0, 0, 0.15));
|
|
259
262
|
border-radius: 6px;
|
|
260
263
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
|