@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.
@@ -10,8 +10,10 @@
10
10
  MAP_PALETTE,
11
11
  branchColor,
12
12
  branchWidth,
13
+ busMarkerRadius,
13
14
  busNeutral,
14
- busRadius,
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>&Delta; 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 ? '&mdash;' : fmt.format(price)} objective units/MW`
787
792
  : `${d.scalarLabel} ${
788
793
  scalar === undefined ? '&mdash;' : 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 === 'angle'
1063
- ? (s?.va ?? [])
1064
- : mode === 'voltage'
1065
- ? (s?.vm ??
1066
- (s?.w ?? []).map((v) => ({
1067
- ...v,
1068
- value: Math.sqrt(Math.max(0, v.value))
1069
- })))
1070
- : (s?.lmp ?? []);
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) => busRadius(Math.max(b.demand_mw, b.gen_mw)),
1101
- radiusUnits: 'pixels',
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
- : priceColor((value - domain.lo) / Math.max(domain.hi - domain.lo, 1e-12));
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) => busRadius(Math.max(b.demand_mw, b.gen_mw)),
1167
- radiusUnits: 'pixels',
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) => busRadius(Math.max(b.demand_mw, b.gen_mw)),
1241
- radiusUnits: 'pixels',
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) => busRadius(Math.max(b.load_kw, b.gen_kw)),
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
+ }
@@ -157,9 +157,11 @@
157
157
  {@render selectionBlock()}
158
158
  {/if}
159
159
 
160
- <hr />
160
+ {#if app.busSizing === 'power'}
161
+ <hr />
161
162
 
162
- <SizeLegend />
163
+ <SizeLegend />
164
+ {/if}
163
165
  {/if}
164
166
  {/if}
165
167
  {#if app.compactLayout && config.showFooter}
@@ -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 { branchColor, busNeutral, busRadius, priceColor, scalarDomain } from '../colors.js';
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
- : priceColor((value - domain.lo) / Math.max(domain.hi - domain.lo, 1e-12))
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, busRadius(Math.max(bus.demand_mw, bus.gen_mw))) / scale}
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
- this.app.displayMode = next === 'acpf' ? 'voltage' : 'price';
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, or voltage magnitudes in per unit.
16
- * SOCWR supplies squared voltages, which are converted to magnitudes here. */
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, or voltage magnitudes in per unit.
41
- * SOCWR supplies squared voltages, which are converted to magnitudes here. */
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';
@@ -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
@@ -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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tellegen/svelte",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Svelte components for tellegen power system maps, local case files, and browser solves.",
5
5
  "keywords": [
6
6
  "svelte",