@tellegen/svelte 0.4.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/dist/TellegenMap.svelte +39 -20
- package/dist/colors.d.ts +37 -1
- package/dist/colors.js +63 -0
- package/dist/components/ControlPanel.svelte +4 -2
- package/dist/components/DiagramCanvas.svelte +16 -4
- package/dist/controller.svelte.js +4 -2
- package/dist/display.d.ts +3 -2
- package/dist/display.js +22 -4
- package/dist/format.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/map.d.ts +1 -1
- package/dist/map.js +1 -1
- package/dist/state.svelte.d.ts +6 -1
- package/dist/state.svelte.js +1 -0
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
package/dist/TellegenMap.svelte
CHANGED
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
MAP_PALETTE,
|
|
11
11
|
branchColor,
|
|
12
12
|
branchWidth,
|
|
13
|
+
busMarkerRadius,
|
|
13
14
|
busNeutral,
|
|
14
|
-
|
|
15
|
+
busRadiusFor,
|
|
16
|
+
demandColor,
|
|
15
17
|
priceColor,
|
|
16
18
|
scalarDomain,
|
|
17
19
|
sensColor,
|
|
@@ -391,6 +393,8 @@
|
|
|
391
393
|
if (d.previewDomain.flat) return sensFlatColor(d.previewDomain);
|
|
392
394
|
return sensColor((d.preview.get(bus.id) ?? 0) / d.previewDomain.scale);
|
|
393
395
|
}
|
|
396
|
+
if (d.scalarMode === 'demand')
|
|
397
|
+
return demandColor(((d.scalar.get(bus.id) ?? 0) - d.scalarLo) / (d.scalarHi - d.scalarLo));
|
|
394
398
|
const mid = (d.scalarLo + d.scalarHi) / 2;
|
|
395
399
|
return priceColor(((d.scalar.get(bus.id) ?? mid) - d.scalarLo) / (d.scalarHi - d.scalarLo));
|
|
396
400
|
};
|
|
@@ -781,8 +785,9 @@
|
|
|
781
785
|
: `<br>Δ LMP ${preview >= 0 ? '+' : ''}${preview.toExponential(2)} objective units/MW`;
|
|
782
786
|
const localRow =
|
|
783
787
|
c instanceof CaseState ? '' : '<br><span style="opacity:0.6">local file</span>';
|
|
788
|
+
// Demand already has its own row below, so the demand colouring shows LMP here alone.
|
|
784
789
|
const scalarRow =
|
|
785
|
-
!d || d.scalarMode === 'price'
|
|
790
|
+
!d || d.scalarMode === 'price' || d.scalarMode === 'demand'
|
|
786
791
|
? `LMP ${price === undefined ? '—' : fmt.format(price)} objective units/MW`
|
|
787
792
|
: `${d.scalarLabel} ${
|
|
788
793
|
scalar === undefined ? '—' : displayFmt(d.scalarMode, scalar)
|
|
@@ -1054,20 +1059,23 @@
|
|
|
1054
1059
|
if (!overlay || !layerCtors) return;
|
|
1055
1060
|
const { PathLayer, ScatterplotLayer, IconLayer } = layerCtors;
|
|
1056
1061
|
const layers: Layer[] = [];
|
|
1062
|
+
const marker = busMarkerRadius(app.busSizing);
|
|
1057
1063
|
const study = app.studyView;
|
|
1058
1064
|
if (study) {
|
|
1059
1065
|
const s = study.solution;
|
|
1060
1066
|
const mode = app.displayMode;
|
|
1061
1067
|
const series =
|
|
1062
|
-
mode === '
|
|
1063
|
-
? (
|
|
1064
|
-
: mode === '
|
|
1065
|
-
? (s?.
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1068
|
+
mode === 'demand'
|
|
1069
|
+
? study.network.buses.map((b) => ({ bus: b.id, value: b.demand_mw }))
|
|
1070
|
+
: mode === 'angle'
|
|
1071
|
+
? (s?.va ?? [])
|
|
1072
|
+
: mode === 'voltage'
|
|
1073
|
+
? (s?.vm ??
|
|
1074
|
+
(s?.w ?? []).map((v) => ({
|
|
1075
|
+
...v,
|
|
1076
|
+
value: Math.sqrt(Math.max(0, v.value))
|
|
1077
|
+
})))
|
|
1078
|
+
: (s?.lmp ?? []);
|
|
1071
1079
|
const values = new Map(series.map((v) => [v.bus, v.value]));
|
|
1072
1080
|
const domain = scalarDomain(
|
|
1073
1081
|
mode,
|
|
@@ -1097,13 +1105,17 @@
|
|
|
1097
1105
|
id: 'study-buses',
|
|
1098
1106
|
data: study.network.buses,
|
|
1099
1107
|
getPosition: (b) => [b.lon, b.lat],
|
|
1100
|
-
getRadius: (b) =>
|
|
1101
|
-
radiusUnits:
|
|
1108
|
+
getRadius: (b) => marker.radius(Math.max(b.demand_mw, b.gen_mw)),
|
|
1109
|
+
radiusUnits: marker.radiusUnits,
|
|
1110
|
+
radiusMinPixels: marker.radiusMinPixels,
|
|
1111
|
+
radiusMaxPixels: marker.radiusMaxPixels,
|
|
1102
1112
|
getFillColor: (b) => {
|
|
1103
1113
|
const value = values.get(b.id);
|
|
1104
1114
|
return value === undefined
|
|
1105
1115
|
? palette.grey
|
|
1106
|
-
:
|
|
1116
|
+
: (mode === 'demand' ? demandColor : priceColor)(
|
|
1117
|
+
(value - domain.lo) / Math.max(domain.hi - domain.lo, 1e-12)
|
|
1118
|
+
);
|
|
1107
1119
|
},
|
|
1108
1120
|
pickable: true,
|
|
1109
1121
|
stroked: true,
|
|
@@ -1116,7 +1128,7 @@
|
|
|
1116
1128
|
app.selectedBus = bus.id;
|
|
1117
1129
|
return true;
|
|
1118
1130
|
},
|
|
1119
|
-
updateTriggers: { getFillColor: [study, mode, theme] }
|
|
1131
|
+
updateTriggers: { getFillColor: [study, mode, theme], getRadius: [app.busSizing] }
|
|
1120
1132
|
})
|
|
1121
1133
|
);
|
|
1122
1134
|
overlay.setProps({ layers: [...layers, ...extraLayers] });
|
|
@@ -1163,8 +1175,10 @@
|
|
|
1163
1175
|
id: `buses-${c.id}`,
|
|
1164
1176
|
data: c.network.buses,
|
|
1165
1177
|
getPosition: (b) => [b.lon, b.lat],
|
|
1166
|
-
getRadius: (b) =>
|
|
1167
|
-
radiusUnits:
|
|
1178
|
+
getRadius: (b) => marker.radius(Math.max(b.demand_mw, b.gen_mw)),
|
|
1179
|
+
radiusUnits: marker.radiusUnits,
|
|
1180
|
+
radiusMinPixels: marker.radiusMinPixels,
|
|
1181
|
+
radiusMaxPixels: marker.radiusMaxPixels,
|
|
1168
1182
|
getFillColor: busFill(c.id),
|
|
1169
1183
|
stroked: true,
|
|
1170
1184
|
billboard: true,
|
|
@@ -1184,6 +1198,7 @@
|
|
|
1184
1198
|
return true;
|
|
1185
1199
|
},
|
|
1186
1200
|
updateTriggers: {
|
|
1201
|
+
getRadius: [app.busSizing],
|
|
1187
1202
|
getFillColor: [display],
|
|
1188
1203
|
getLineColor: [app.selectedBus, app.activeCaseId, theme],
|
|
1189
1204
|
getLineWidth: [app.selectedBus, app.activeCaseId]
|
|
@@ -1237,8 +1252,10 @@
|
|
|
1237
1252
|
id: `local-buses-${c.id}`,
|
|
1238
1253
|
data: c.view.buses,
|
|
1239
1254
|
getPosition: (b) => [b.lon, b.lat],
|
|
1240
|
-
getRadius: (b) =>
|
|
1241
|
-
radiusUnits:
|
|
1255
|
+
getRadius: (b) => marker.radius(Math.max(b.demand_mw, b.gen_mw)),
|
|
1256
|
+
radiusUnits: marker.radiusUnits,
|
|
1257
|
+
radiusMinPixels: marker.radiusMinPixels,
|
|
1258
|
+
radiusMaxPixels: marker.radiusMaxPixels,
|
|
1242
1259
|
getFillColor: d ? busFill(c.id) : palette.grey,
|
|
1243
1260
|
stroked: true,
|
|
1244
1261
|
billboard: true,
|
|
@@ -1258,6 +1275,7 @@
|
|
|
1258
1275
|
return true;
|
|
1259
1276
|
},
|
|
1260
1277
|
updateTriggers: {
|
|
1278
|
+
getRadius: [app.busSizing],
|
|
1261
1279
|
getFillColor: [display],
|
|
1262
1280
|
getLineColor: [app.selectedBus, app.activeLocalId, theme],
|
|
1263
1281
|
getLineWidth: [app.selectedBus, app.activeLocalId]
|
|
@@ -1351,7 +1369,7 @@
|
|
|
1351
1369
|
id: `multi-buses-${c.id}`,
|
|
1352
1370
|
data: c.view.buses,
|
|
1353
1371
|
getPosition: (b) => [b.lon, b.lat],
|
|
1354
|
-
getRadius: (b) =>
|
|
1372
|
+
getRadius: (b) => busRadiusFor(app.busSizing, Math.max(b.load_kw, b.gen_kw)),
|
|
1355
1373
|
radiusUnits: 'pixels',
|
|
1356
1374
|
radiusMinPixels: 3,
|
|
1357
1375
|
getFillColor: (b) => multiBusFill(b),
|
|
@@ -1375,6 +1393,7 @@
|
|
|
1375
1393
|
return true;
|
|
1376
1394
|
},
|
|
1377
1395
|
updateTriggers: {
|
|
1396
|
+
getRadius: [app.busSizing],
|
|
1378
1397
|
getLineColor: [selectedId, theme],
|
|
1379
1398
|
getLineWidth: [selectedId]
|
|
1380
1399
|
}
|
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. */
|
|
@@ -66,3 +81,24 @@ export declare function branchWidth(loading: number): number;
|
|
|
66
81
|
/** Bus radius in px; area tracks max(load, gen) so big plants and big loads
|
|
67
82
|
* both read at a glance. Shared by the map layers and the size legend. */
|
|
68
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);
|
|
@@ -164,3 +195,35 @@ export function branchWidth(loading) {
|
|
|
164
195
|
export function busRadius(maxMw) {
|
|
165
196
|
return 3.2 + 0.45 * Math.sqrt(Math.max(maxMw, 1));
|
|
166
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
|
+
}
|
|
@@ -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,7 +359,8 @@
|
|
|
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
365
|
style:stroke={selectedBus === bus.id ? '#2f6fbb' : 'var(--diagram-bus-stroke)'}
|
|
354
366
|
stroke-width={selectedBus === bus.id ? 3 : 1}
|
|
@@ -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
|
>
|
|
@@ -487,7 +487,7 @@ export class Controller {
|
|
|
487
487
|
return null;
|
|
488
488
|
const domain = scalarDomain(this.activeDisplay.mode, values);
|
|
489
489
|
const { min, max } = extent(values);
|
|
490
|
-
const flatThreshold = this.activeDisplay.mode === 'price' ? 1 : 1e-5;
|
|
490
|
+
const flatThreshold = this.activeDisplay.mode === 'price' || this.activeDisplay.mode === 'demand' ? 1 : 1e-5;
|
|
491
491
|
return {
|
|
492
492
|
lo: { value: domain.lo, clamped: min < domain.lo - flatThreshold / 20 },
|
|
493
493
|
hi: { value: domain.hi, clamped: max > domain.hi + flatThreshold / 20 },
|
|
@@ -1740,7 +1740,9 @@ export class Controller {
|
|
|
1740
1740
|
if (FORMULATIONS.find((f) => f.id === next)?.disabled)
|
|
1741
1741
|
return;
|
|
1742
1742
|
c.formulation = next;
|
|
1743
|
-
|
|
1743
|
+
// Demand does not depend on the calculation, so a demand colouring stays.
|
|
1744
|
+
if (this.app.displayMode !== 'demand')
|
|
1745
|
+
this.app.displayMode = next === 'acpf' ? 'voltage' : 'price';
|
|
1744
1746
|
this.bumpRevision(c);
|
|
1745
1747
|
// The committed point carries over (same demand), but the model and its solution do
|
|
1746
1748
|
// not; drop the Study and the cached solutions so they rebuild under `next`.
|
package/dist/display.d.ts
CHANGED
|
@@ -12,8 +12,9 @@ export type DisplayOption = {
|
|
|
12
12
|
};
|
|
13
13
|
/** Variables present in the current result. Power flow has no LMP values. */
|
|
14
14
|
export declare function displayMetaFor(c: SolvableCase | null): DisplayOption[];
|
|
15
|
-
/** Bus LMPs, voltage angles in radians,
|
|
16
|
-
*
|
|
15
|
+
/** Bus LMPs, voltage angles in radians, voltage magnitudes in per unit, or
|
|
16
|
+
* demand in MW with the case's committed changes. SOCWR supplies squared
|
|
17
|
+
* voltages, which are converted to magnitudes here. */
|
|
17
18
|
export declare function displaySeriesFor(c: SolvableCase | null, mode: DisplayMode): {
|
|
18
19
|
bus: number;
|
|
19
20
|
value: number;
|
package/dist/display.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { priceGradient } from './colors.js';
|
|
1
|
+
import { demandGradient, priceGradient } from './colors.js';
|
|
2
2
|
import { priceCopy } from './format.js';
|
|
3
3
|
import { CaseState } from './state.svelte.js';
|
|
4
4
|
/** Variables present in the current result. Power flow has no LMP values. */
|
|
@@ -35,14 +35,32 @@ export function displayMetaFor(c) {
|
|
|
35
35
|
gradient: priceGradient
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
|
+
// Last, so a mode the result cannot show still falls back to a result variable.
|
|
39
|
+
if (c.network?.buses.length) {
|
|
40
|
+
options.push({
|
|
41
|
+
mode: 'demand',
|
|
42
|
+
label: 'demand',
|
|
43
|
+
unit: 'MW',
|
|
44
|
+
copy: 'Bus demand, including committed changes, on a square-root scale.',
|
|
45
|
+
gradient: demandGradient
|
|
46
|
+
});
|
|
47
|
+
}
|
|
38
48
|
return options;
|
|
39
49
|
}
|
|
40
|
-
/** Bus LMPs, voltage angles in radians,
|
|
41
|
-
*
|
|
50
|
+
/** Bus LMPs, voltage angles in radians, voltage magnitudes in per unit, or
|
|
51
|
+
* demand in MW with the case's committed changes. SOCWR supplies squared
|
|
52
|
+
* voltages, which are converted to magnitudes here. */
|
|
42
53
|
export function displaySeriesFor(c, mode) {
|
|
43
54
|
const sol = c?.solution;
|
|
44
|
-
if (!sol)
|
|
55
|
+
if (!c || !sol)
|
|
45
56
|
return [];
|
|
57
|
+
if (mode === 'demand') {
|
|
58
|
+
const deltas = caseDeltas(c);
|
|
59
|
+
return (c.network?.buses ?? []).map((b) => ({
|
|
60
|
+
bus: b.id,
|
|
61
|
+
value: b.demand_mw + (deltas[b.id] ?? 0)
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
46
64
|
if (mode === 'angle')
|
|
47
65
|
return sol.va;
|
|
48
66
|
if (mode === 'voltage') {
|
package/dist/format.js
CHANGED
|
@@ -59,4 +59,4 @@ export function extent(values) {
|
|
|
59
59
|
export const fmt = new Intl.NumberFormat('en-US', { maximumFractionDigits: 1 });
|
|
60
60
|
export const signed = (v) => `${v < 0 ? '−' : '+'}${fmt.format(Math.abs(v))}`;
|
|
61
61
|
export const signedExp = (v) => `${v < 0 ? '−' : '+'}${Math.abs(v).toExponential(2)}`;
|
|
62
|
-
export const displayFmt = (mode, value) => mode === 'price' ? fmt.format(value) : value.toFixed(3);
|
|
62
|
+
export const displayFmt = (mode, value) => mode === 'price' || mode === 'demand' ? fmt.format(value) : value.toFixed(3);
|
package/dist/index.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export type { ControllerOptions, OpenBalancedModuleInput } from './controller.sv
|
|
|
23
23
|
export type { BasemapFallbackReason, BasemapMapLike, BasemapProvider, BasemapSpec, BasemapStyle, BasemapSwitch, BasemapTheme, DefaultBasemap, MapProjection, MapTheme, ResolvedBasemap, StyleEdit } from './basemap.js';
|
|
24
24
|
export type { MapPalette } from './colors.js';
|
|
25
25
|
export type { DisplayOption } from './display.js';
|
|
26
|
-
export type { CameraSnapshot, DiagramCameraSnapshot, DemandRangeMode, DisplayMode, FallbackTarget, LocalCaseInit, LocalSubstations, MultiCaseSummary, MultiCoordsKind, SolvableCase, StudyDisplaySnapshot, SolveBackend } from './state.svelte.js';
|
|
26
|
+
export type { BusSizing, CameraSnapshot, DiagramCameraSnapshot, DemandRangeMode, DisplayMode, FallbackTarget, LocalCaseInit, LocalSubstations, MultiCaseSummary, MultiCoordsKind, SolvableCase, StudyDisplaySnapshot, SolveBackend } from './state.svelte.js';
|
|
27
27
|
export type { MultiView, PlacedMultiBus, PlacedMultiEdge } from './multiconductor.js';
|
|
28
28
|
export type { CaseFileSummary, DistAttachmentKind, DistEdgeKind, DistGraph, DistGraphAttachment, DistGraphBus, DistGraphEdge, DisplayPreview, Formulation, IngestedCase, IngestedDistCase, IngestedJsonDrop, JsonDropClassification, JsonDropKind, CapacityPlanBusWeightJson, CapacityPlanIterationJson, CapacityPlanOutcomeJson, CapacityPlanRatingChangeJson, CapacityPlanResultSummaryJson, CapacityPlanSpecJson, ImplicitObjectiveJson, SensTarget, Topology, TopologyBranch, TopologyBus } from '@tellegen/engine';
|
|
29
29
|
export { default as McResults } from './components/McResults.svelte';
|
package/dist/map.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import TellegenMap from './TellegenMap.svelte';
|
|
|
3
3
|
export { default as TellegenMap } from './TellegenMap.svelte';
|
|
4
4
|
export { CARTO_STYLES, OPENFREEMAP_STYLES, attachBasemapFallback, cartoStyleUrl, defaultBasemap, isOpenFreeMapStyle, openFreeMapEdits, resolveBasemap } from './basemap.js';
|
|
5
5
|
export type { BasemapFallbackReason, BasemapMapLike, BasemapProvider, BasemapSpec, BasemapStyle, BasemapSwitch, BasemapTheme, DefaultBasemap, MapProjection, MapTheme, ResolvedBasemap, StyleEdit } from './basemap.js';
|
|
6
|
-
export { MAP_PALETTE, branchColor, branchWidth, busNeutral, busRadius, priceColor, priceDomain, priceGradient, scalarDomain, sensColor, sensFlatColor, sensGradient, sensNeutral, sensitivityDomain } from './colors.js';
|
|
6
|
+
export { MAP_PALETTE, branchColor, branchWidth, busNeutral, busRadius, busMarkerRadius, busRadiusFor, demandColor, demandDomain, demandGradient, priceColor, priceDomain, priceGradient, scalarDomain, sensColor, sensFlatColor, sensGradient, sensNeutral, sensitivityDomain, UNIFORM_BUS_GROUND_RADIUS, UNIFORM_BUS_MIN_RADIUS, UNIFORM_BUS_RADIUS } from './colors.js';
|
|
7
7
|
export { caseDeltas, displayMetaFor, displaySeriesFor } from './display.js';
|
|
8
8
|
export type { MapPalette, RGBA, SensitivityDomain } from './colors.js';
|
|
9
9
|
export type { DisplayOption } from './display.js';
|
package/dist/map.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import TellegenMap from './TellegenMap.svelte';
|
|
2
2
|
export { default as TellegenMap } from './TellegenMap.svelte';
|
|
3
3
|
export { CARTO_STYLES, OPENFREEMAP_STYLES, attachBasemapFallback, cartoStyleUrl, defaultBasemap, isOpenFreeMapStyle, openFreeMapEdits, resolveBasemap } from './basemap.js';
|
|
4
|
-
export { MAP_PALETTE, branchColor, branchWidth, busNeutral, busRadius, priceColor, priceDomain, priceGradient, scalarDomain, sensColor, sensFlatColor, sensGradient, sensNeutral, sensitivityDomain } from './colors.js';
|
|
4
|
+
export { MAP_PALETTE, branchColor, branchWidth, busNeutral, busRadius, busMarkerRadius, busRadiusFor, demandColor, demandDomain, demandGradient, priceColor, priceDomain, priceGradient, scalarDomain, sensColor, sensFlatColor, sensGradient, sensNeutral, sensitivityDomain, UNIFORM_BUS_GROUND_RADIUS, UNIFORM_BUS_MIN_RADIUS, UNIFORM_BUS_RADIUS } from './colors.js';
|
|
5
5
|
export { caseDeltas, displayMetaFor, displaySeriesFor } from './display.js';
|
package/dist/state.svelte.d.ts
CHANGED
|
@@ -22,7 +22,11 @@ export interface DiagramCameraSnapshot {
|
|
|
22
22
|
scale: number;
|
|
23
23
|
}
|
|
24
24
|
export type DemandRangeMode = 'local' | 'full';
|
|
25
|
-
export type DisplayMode = 'price' | 'angle' | 'voltage';
|
|
25
|
+
export type DisplayMode = 'price' | 'angle' | 'voltage' | 'demand';
|
|
26
|
+
/** How bus markers are sized on the map: by the larger of demand and generation
|
|
27
|
+
* (`power`), or all at one size (`uniform`) so dense networks keep their lines
|
|
28
|
+
* readable and colour carries the variable. */
|
|
29
|
+
export type BusSizing = 'power' | 'uniform';
|
|
26
30
|
/** The case a removal promoted to active, so the caller can hydrate it: a backend
|
|
27
31
|
* case needs its network/solution loaded, a local needs a browser solve, and
|
|
28
32
|
* `none` means nothing remains (or the removal left the active case untouched). */
|
|
@@ -293,6 +297,7 @@ export declare class AppState {
|
|
|
293
297
|
} | null;
|
|
294
298
|
demandRangeMode: DemandRangeMode;
|
|
295
299
|
displayMode: DisplayMode;
|
|
300
|
+
busSizing: BusSizing;
|
|
296
301
|
sensitivityLoading: boolean;
|
|
297
302
|
errorRevision: number;
|
|
298
303
|
/** Re-runs the operation behind the current `error`, when one applies. Every
|
package/dist/state.svelte.js
CHANGED
|
@@ -252,6 +252,7 @@ export class AppState {
|
|
|
252
252
|
previewPrices = $state.raw(null);
|
|
253
253
|
demandRangeMode = $state('local');
|
|
254
254
|
displayMode = $state('price');
|
|
255
|
+
busSizing = $state('power');
|
|
255
256
|
sensitivityLoading = $state(false);
|
|
256
257
|
#error = $state(null);
|
|
257
258
|
errorRevision = $state(0);
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { CaseSummary, DemandDeltas, Network, NetworkBranch, NetworkBus, SensitivityColumn, Solution, SolveIteration, SolveStreamHandlers } from './api.js';
|
|
2
2
|
export type { DisplayOption } from './display.js';
|
|
3
3
|
export type { PlacedNetwork, PlacementCenter } from './synthetic-layout.js';
|
|
4
|
-
export type { DemandRangeMode, DisplayMode, FallbackTarget, LocalCaseInit, LocalSubstations, SolvableCase, SolveBackend } from './state.svelte.js';
|
|
4
|
+
export type { BusSizing, DemandRangeMode, DisplayMode, FallbackTarget, LocalCaseInit, LocalSubstations, SolvableCase, SolveBackend } from './state.svelte.js';
|
|
5
5
|
export type { AppliedGeoCase, CaseFileSummary, DisplayPreview, Formulation, GeoApplyReport, IngestedCase, ParsedGeoLayer, StampedLayoutCase, Topology, TopologyBranch, TopologyBus } from '@tellegen/engine';
|