@tellegen/svelte 0.4.3 → 0.4.5
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 +45 -0
- package/dist/TellegenMap.svelte +63 -22
- package/dist/TellegenProvider.svelte +7 -4
- package/dist/TellegenShell.svelte +12 -1
- package/dist/TellegenViewer.svelte +2 -0
- package/dist/basemap.d.ts +14 -0
- package/dist/basemap.js +27 -0
- package/dist/colors.d.ts +6 -1
- package/dist/colors.js +11 -4
- package/dist/components/DiagnosticLogView.svelte +176 -0
- package/dist/components/DiagnosticLogView.svelte.d.ts +11 -0
- package/dist/components/FormulationSelector.svelte +1 -1
- package/dist/components/LocalCaseDetails.svelte +18 -3
- package/dist/components/LogEntryView.svelte +188 -0
- package/dist/components/LogEntryView.svelte.d.ts +11 -0
- package/dist/components/LogPanel.svelte +25 -0
- package/dist/components/LogPanel.svelte.d.ts +3 -0
- package/dist/components/McResults.svelte +18 -3
- package/dist/components/McWorkbench.svelte +688 -0
- package/dist/components/McWorkbench.svelte.d.ts +38 -0
- package/dist/components/MultiWire.svelte +136 -0
- package/dist/components/MultiWire.svelte.d.ts +7 -0
- package/dist/components/MulticonductorDetails.svelte +7 -0
- package/dist/components/NotificationCenter.svelte +37 -6
- package/dist/components/OmniSearch.svelte +163 -0
- package/dist/components/OmniSearch.svelte.d.ts +14 -0
- package/dist/components/ResultsTable.svelte +259 -0
- package/dist/components/ResultsTable.svelte.d.ts +13 -0
- package/dist/components/SchematicCanvas.svelte +1896 -0
- package/dist/components/SchematicCanvas.svelte.d.ts +13 -0
- package/dist/components/SolveCard.svelte +6 -0
- package/dist/components/schematic/PickCard.svelte +126 -0
- package/dist/components/schematic/PickCard.svelte.d.ts +56 -0
- package/dist/components/schematic/export.d.ts +21 -0
- package/dist/components/schematic/export.js +110 -0
- package/dist/components/schematic/layers.d.ts +161 -0
- package/dist/components/schematic/layers.js +756 -0
- package/dist/components/schematic/paint.d.ts +28 -0
- package/dist/components/schematic/paint.js +69 -0
- package/dist/components/schematic/palette.d.ts +34 -0
- package/dist/components/schematic/palette.js +54 -0
- package/dist/components/schematic/text.d.ts +83 -0
- package/dist/components/schematic/text.js +190 -0
- package/dist/components/schematic/view-prefs.svelte.d.ts +30 -0
- package/dist/components/schematic/view-prefs.svelte.js +77 -0
- package/dist/components.d.ts +4 -0
- package/dist/components.js +4 -0
- package/dist/context.svelte.d.ts +2 -0
- package/dist/context.svelte.js +2 -0
- package/dist/controller.svelte.d.ts +40 -5
- package/dist/controller.svelte.js +230 -25
- package/dist/diagram/basemap.d.ts +28 -0
- package/dist/diagram/basemap.js +39 -0
- package/dist/diagram/bus-stacks.d.ts +19 -0
- package/dist/diagram/bus-stacks.js +99 -0
- package/dist/diagram/colour-modes.d.ts +18 -0
- package/dist/diagram/colour-modes.js +33 -0
- package/dist/diagram/connections.d.ts +30 -0
- package/dist/diagram/connections.js +129 -0
- package/dist/diagram/display-tree.d.ts +21 -0
- package/dist/diagram/display-tree.js +65 -0
- package/dist/diagram/energisation.d.ts +10 -0
- package/dist/diagram/energisation.js +31 -0
- package/dist/diagram/feeders.d.ts +30 -0
- package/dist/diagram/feeders.js +57 -0
- package/dist/diagram/galvanic-zones.d.ts +60 -0
- package/dist/diagram/galvanic-zones.js +210 -0
- package/dist/diagram/graph-inputs.d.ts +18 -0
- package/dist/diagram/graph-inputs.js +103 -0
- package/dist/diagram/index.d.ts +28 -0
- package/dist/diagram/index.js +28 -0
- package/dist/diagram/landmarks.d.ts +50 -0
- package/dist/diagram/landmarks.js +153 -0
- package/dist/diagram/layout-morph.d.ts +15 -0
- package/dist/diagram/layout-morph.js +32 -0
- package/dist/diagram/multi-wire.d.ts +44 -0
- package/dist/diagram/multi-wire.js +65 -0
- package/dist/diagram/phase-bars.d.ts +35 -0
- package/dist/diagram/phase-bars.js +44 -0
- package/dist/diagram/results-table.d.ts +47 -0
- package/dist/diagram/results-table.js +173 -0
- package/dist/diagram/schematic-equipment.d.ts +133 -0
- package/dist/diagram/schematic-equipment.js +645 -0
- package/dist/diagram/schematic-layout.d.ts +51 -0
- package/dist/diagram/schematic-layout.js +240 -0
- package/dist/diagram/search.d.ts +36 -0
- package/dist/diagram/search.js +48 -0
- package/dist/diagram/shared-routes.d.ts +21 -0
- package/dist/diagram/shared-routes.js +40 -0
- package/dist/diagram/short-names.d.ts +11 -0
- package/dist/diagram/short-names.js +54 -0
- package/dist/diagram/snapshot.d.ts +115 -0
- package/dist/diagram/snapshot.js +242 -0
- package/dist/diagram/summary.d.ts +65 -0
- package/dist/diagram/summary.js +144 -0
- package/dist/diagram/svg-export.d.ts +80 -0
- package/dist/diagram/svg-export.js +108 -0
- package/dist/diagram/tree-walk.d.ts +13 -0
- package/dist/diagram/tree-walk.js +83 -0
- package/dist/diagram/value-labels.d.ts +24 -0
- package/dist/diagram/value-labels.js +69 -0
- package/dist/diagram/view-state.d.ts +12 -0
- package/dist/diagram/view-state.js +22 -0
- package/dist/diagram/voltage-levels.d.ts +14 -0
- package/dist/diagram/voltage-levels.js +20 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/log.svelte.d.ts +77 -0
- package/dist/log.svelte.js +149 -0
- package/dist/notices.svelte.d.ts +3 -0
- package/dist/state.svelte.d.ts +42 -5
- package/dist/state.svelte.js +56 -4
- package/dist/transformer-icon.d.ts +22 -13
- package/dist/transformer-icon.js +23 -13
- package/package.json +2 -2
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
import { branchGlyph, conductorSet, conductorTicks, routeMiddle } from '../../diagram/index.js';
|
|
2
|
+
import { hiddenUnderFan, landmarkColours, segmentColours, segmentWidths, strokeColours } from './paint.js';
|
|
3
|
+
import { ACCENT, CORRIDOR_PX, INK, MONO, PAPER, PATH, SYMBOL_INK, ZONE_COLOURS, ZONE_INK, alpha, lineWidths, stateInk } from './palette.js';
|
|
4
|
+
/** Screen radius of a spread stack of `n` buses: wider for more, so their symbols stay apart. */
|
|
5
|
+
export const stackRadius = (n) => 8 + 4 * n;
|
|
6
|
+
/** Pixel size of the paper behind text. */
|
|
7
|
+
const paperText = (a) => alpha(PAPER, a);
|
|
8
|
+
/** Screen width of the hover halos: wide enough to read under MV and LV lines alike. */
|
|
9
|
+
const HALO_PX = 10;
|
|
10
|
+
const cartesian = (kit) => ({ coordinateSystem: kit.core.COORDINATE_SYSTEM.CARTESIAN });
|
|
11
|
+
/** Buses gliding between layouts along straight chords. */
|
|
12
|
+
export function morphLayers(kit, morph, busCount) {
|
|
13
|
+
const common = cartesian(kit);
|
|
14
|
+
return [
|
|
15
|
+
new kit.layers.LineLayer({
|
|
16
|
+
...common,
|
|
17
|
+
id: 'morph-chords',
|
|
18
|
+
data: {
|
|
19
|
+
length: morph.endpoints.length / 2,
|
|
20
|
+
attributes: {
|
|
21
|
+
getSourcePosition: { value: morph.s, size: 3 },
|
|
22
|
+
getTargetPosition: { value: morph.t, size: 3 }
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
getColor: [120, 114, 102, 200],
|
|
26
|
+
getWidth: 1,
|
|
27
|
+
widthUnits: 'pixels'
|
|
28
|
+
}),
|
|
29
|
+
new kit.layers.ScatterplotLayer({
|
|
30
|
+
...common,
|
|
31
|
+
id: 'morph-buses',
|
|
32
|
+
data: { length: busCount, attributes: { getPosition: { value: morph.cur, size: 3 } } },
|
|
33
|
+
getFillColor: INK,
|
|
34
|
+
getRadius: 2,
|
|
35
|
+
radiusUnits: 'pixels'
|
|
36
|
+
})
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
/** The last value for the last keys, compared by identity. */
|
|
40
|
+
function keyed() {
|
|
41
|
+
let last = null;
|
|
42
|
+
let value;
|
|
43
|
+
return (keys, compute) => {
|
|
44
|
+
if (!last || keys.length !== last.length || keys.some((k, i) => k !== last[i])) {
|
|
45
|
+
value = compute();
|
|
46
|
+
last = keys;
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function createLayerCache() {
|
|
52
|
+
return {
|
|
53
|
+
branches: keyed(),
|
|
54
|
+
underlay: keyed(),
|
|
55
|
+
equipment: keyed(),
|
|
56
|
+
ticks: keyed(),
|
|
57
|
+
arrows: keyed(),
|
|
58
|
+
itemTexts: keyed(),
|
|
59
|
+
transformerTexts: keyed(),
|
|
60
|
+
zoneTags: keyed(),
|
|
61
|
+
corridors: keyed(),
|
|
62
|
+
segmentsOf: keyed()
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function sceneLayers(kit, s) {
|
|
66
|
+
const { LineLayer, ScatterplotLayer, TextLayer, PolygonLayer } = kit.layers;
|
|
67
|
+
const common = cartesian(kit);
|
|
68
|
+
const g = s.geometry;
|
|
69
|
+
const d = s.drawn;
|
|
70
|
+
const cache = s.cache;
|
|
71
|
+
const segmentsOf = cache.segmentsOf([d], () => segmentIndex(d));
|
|
72
|
+
const point = (i) => [
|
|
73
|
+
g.positions[i * 3],
|
|
74
|
+
g.positions[i * 3 + 1],
|
|
75
|
+
0
|
|
76
|
+
];
|
|
77
|
+
const segment = (i, end) => [
|
|
78
|
+
end[i * 3],
|
|
79
|
+
end[i * 3 + 1],
|
|
80
|
+
0
|
|
81
|
+
];
|
|
82
|
+
const symbols = s.symbols;
|
|
83
|
+
return [
|
|
84
|
+
// Zone outlines sit beneath everything; they pick only where nothing else is.
|
|
85
|
+
...corridorLayers(kit, s),
|
|
86
|
+
...hoverHalos(kit, s),
|
|
87
|
+
new PolygonLayer({
|
|
88
|
+
...common,
|
|
89
|
+
id: 'zones',
|
|
90
|
+
data: s.zones.shapes,
|
|
91
|
+
getPolygon: (z) => z.polygon,
|
|
92
|
+
getFillColor: (z) => ZONE_COLOURS[s.zones.states[z.z]][0],
|
|
93
|
+
getLineColor: (z) => ZONE_COLOURS[s.zones.states[z.z]][1],
|
|
94
|
+
getLineWidth: 1,
|
|
95
|
+
lineWidthUnits: 'pixels',
|
|
96
|
+
lineWidthMinPixels: 1,
|
|
97
|
+
pickable: true,
|
|
98
|
+
updateTriggers: { getFillColor: s.zones.states, getLineColor: s.zones.states }
|
|
99
|
+
}),
|
|
100
|
+
new TextLayer({
|
|
101
|
+
...common,
|
|
102
|
+
id: 'zone-tags',
|
|
103
|
+
data: cache.zoneTags([s.zones.tags, s.zones.tagZoom, s.layout === 'source' && !!symbols], () => s.layout === 'source' && symbols
|
|
104
|
+
? []
|
|
105
|
+
: s.zones.tags.filter((t) => t.width * 2 ** s.zones.tagZoom >= 150)),
|
|
106
|
+
getPosition: (t) => [...t.position, 0],
|
|
107
|
+
getText: (t) => t.text,
|
|
108
|
+
getColor: (t) => {
|
|
109
|
+
const state = s.zones.states[t.z];
|
|
110
|
+
return state === 'violation' || state === 'warning' ? stateInk(state) : ZONE_INK;
|
|
111
|
+
},
|
|
112
|
+
getSize: 10,
|
|
113
|
+
getTextAnchor: 'start',
|
|
114
|
+
getAlignmentBaseline: 'bottom',
|
|
115
|
+
getPixelOffset: [2, -2],
|
|
116
|
+
background: true,
|
|
117
|
+
getBackgroundColor: paperText(220),
|
|
118
|
+
backgroundPadding: [3, 1],
|
|
119
|
+
fontFamily: MONO,
|
|
120
|
+
characterSet: 'auto',
|
|
121
|
+
updateTriggers: { getColor: s.zones.states }
|
|
122
|
+
}),
|
|
123
|
+
// Result colours are quiet for normal values; a faint underlay keeps the topology legible.
|
|
124
|
+
...(s.colour === 'none'
|
|
125
|
+
? []
|
|
126
|
+
: [
|
|
127
|
+
new LineLayer({
|
|
128
|
+
...common,
|
|
129
|
+
id: 'topology-underlay',
|
|
130
|
+
data: cache.underlay([d], () => ({
|
|
131
|
+
length: d.segmentEdge.length,
|
|
132
|
+
attributes: {
|
|
133
|
+
getSourcePosition: { value: d.sources, size: 3 },
|
|
134
|
+
getTargetPosition: { value: d.targets, size: 3 }
|
|
135
|
+
}
|
|
136
|
+
})),
|
|
137
|
+
getColor: [120, 114, 102, 70],
|
|
138
|
+
getWidth: 1,
|
|
139
|
+
widthUnits: 'pixels'
|
|
140
|
+
})
|
|
141
|
+
]),
|
|
142
|
+
new LineLayer({
|
|
143
|
+
...common,
|
|
144
|
+
id: `branches-${s.layout}`,
|
|
145
|
+
data: cache.branches([d, s.edgeColours, s.edgeMv, s.zoomedOut, s.fan, s.colour !== 'none'], () => ({
|
|
146
|
+
length: d.segmentEdge.length,
|
|
147
|
+
attributes: {
|
|
148
|
+
getSourcePosition: { value: d.sources, size: 3 },
|
|
149
|
+
getTargetPosition: { value: d.targets, size: 3 },
|
|
150
|
+
getColor: {
|
|
151
|
+
value: segmentColours(d, s.edgeColours, s.edgeMv, s.zoomedOut, s.fan),
|
|
152
|
+
size: 4,
|
|
153
|
+
normalized: true
|
|
154
|
+
},
|
|
155
|
+
getWidth: { value: segmentWidths(d, s.edgeMv, s.colour !== 'none'), size: 1 }
|
|
156
|
+
}
|
|
157
|
+
})),
|
|
158
|
+
widthUnits: 'pixels',
|
|
159
|
+
pickable: true
|
|
160
|
+
}),
|
|
161
|
+
new LineLayer({
|
|
162
|
+
...common,
|
|
163
|
+
id: 'selected-branch',
|
|
164
|
+
data: highlighted(d, segmentsOf, s.selectedEdges, s.fan),
|
|
165
|
+
getSourcePosition: (i) => segment(i, d.sources),
|
|
166
|
+
getTargetPosition: (i) => segment(i, d.targets),
|
|
167
|
+
getColor: ACCENT,
|
|
168
|
+
getWidth: 4,
|
|
169
|
+
widthUnits: 'pixels'
|
|
170
|
+
}),
|
|
171
|
+
...sharedRouteLayers(kit, s),
|
|
172
|
+
...stackLayers(kit, s),
|
|
173
|
+
...(symbols
|
|
174
|
+
? [
|
|
175
|
+
new LineLayer({
|
|
176
|
+
...common,
|
|
177
|
+
id: 'equipment',
|
|
178
|
+
data: cache.equipment([symbols, s.fan.edges, s.live], () => ({
|
|
179
|
+
length: symbols.sources.length / 3,
|
|
180
|
+
attributes: {
|
|
181
|
+
getSourcePosition: { value: symbols.sources, size: 3 },
|
|
182
|
+
getTargetPosition: { value: symbols.targets, size: 3 },
|
|
183
|
+
getColor: {
|
|
184
|
+
value: strokeColours(symbols.owner, s.fan.edges, s.live),
|
|
185
|
+
size: 4,
|
|
186
|
+
normalized: true
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
})),
|
|
190
|
+
getWidth: 1.25,
|
|
191
|
+
widthUnits: 'pixels'
|
|
192
|
+
}),
|
|
193
|
+
...(s.valuesOn && s.arrows ? [flowArrowLayer(kit, s.arrows, s.fan.edges, cache)] : []),
|
|
194
|
+
...(s.ticksOn
|
|
195
|
+
? [
|
|
196
|
+
new LineLayer({
|
|
197
|
+
...common,
|
|
198
|
+
id: 'conductor-ticks',
|
|
199
|
+
data: cache.ticks([symbols, s.fan.edges, s.live], () => ({
|
|
200
|
+
length: symbols.ticks.edge.length,
|
|
201
|
+
attributes: {
|
|
202
|
+
getSourcePosition: { value: symbols.ticks.sources, size: 3 },
|
|
203
|
+
getTargetPosition: { value: symbols.ticks.targets, size: 3 },
|
|
204
|
+
getColor: {
|
|
205
|
+
value: strokeColours(symbols.ticks.edge, s.fan.edges, s.live),
|
|
206
|
+
size: 4,
|
|
207
|
+
normalized: true
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
})),
|
|
211
|
+
getWidth: 1,
|
|
212
|
+
widthUnits: 'pixels'
|
|
213
|
+
})
|
|
214
|
+
]
|
|
215
|
+
: []),
|
|
216
|
+
// Invisible discs over each symbol make it clickable and give it a tooltip.
|
|
217
|
+
new ScatterplotLayer({
|
|
218
|
+
...common,
|
|
219
|
+
id: 'equipment-hits',
|
|
220
|
+
data: symbols.items,
|
|
221
|
+
getPosition: (item) => [item.x, item.y, 0],
|
|
222
|
+
getRadius: symbols.unit * 1.3,
|
|
223
|
+
radiusUnits: 'common',
|
|
224
|
+
getFillColor: [0, 0, 0, 1],
|
|
225
|
+
pickable: true
|
|
226
|
+
}),
|
|
227
|
+
new TextLayer({
|
|
228
|
+
...common,
|
|
229
|
+
id: 'equipment-text',
|
|
230
|
+
data: cache.itemTexts([symbols.items, s.textSource], () => symbols.items.filter((item) => item.bus >= 0 && s.itemText(item))),
|
|
231
|
+
getPosition: (item) => [item.x + symbols.unit * 1.1, item.y, 0],
|
|
232
|
+
getText: s.itemText,
|
|
233
|
+
updateTriggers: { getText: [s.valuesOn, s.labelsOn, s.powers] },
|
|
234
|
+
getColor: SYMBOL_INK,
|
|
235
|
+
getSize: 10,
|
|
236
|
+
getTextAnchor: 'start',
|
|
237
|
+
getAlignmentBaseline: 'center',
|
|
238
|
+
// Legible where a label crosses a lead (a neutral earth beside a shunt).
|
|
239
|
+
background: true,
|
|
240
|
+
getBackgroundColor: paperText(200),
|
|
241
|
+
backgroundPadding: [2, 0],
|
|
242
|
+
fontFamily: MONO,
|
|
243
|
+
characterSet: 'auto'
|
|
244
|
+
}),
|
|
245
|
+
// A transformer's vector group and rating, above its symbol: below sits a centre tap.
|
|
246
|
+
new TextLayer({
|
|
247
|
+
...common,
|
|
248
|
+
id: 'transformer-text',
|
|
249
|
+
data: cache.transformerTexts([symbols.items, s.labelsOn], () => s.labelsOn ? symbols.items.filter((item) => item.label) : []),
|
|
250
|
+
getPosition: (item) => [item.x, item.y, 0],
|
|
251
|
+
getText: (item) => item.label ?? '',
|
|
252
|
+
getColor: SYMBOL_INK,
|
|
253
|
+
getSize: 10,
|
|
254
|
+
getTextAnchor: 'middle',
|
|
255
|
+
getAlignmentBaseline: 'bottom',
|
|
256
|
+
getPixelOffset: [0, -12],
|
|
257
|
+
background: true,
|
|
258
|
+
getBackgroundColor: paperText(220),
|
|
259
|
+
backgroundPadding: [2, 1],
|
|
260
|
+
fontFamily: MONO,
|
|
261
|
+
characterSet: 'auto'
|
|
262
|
+
})
|
|
263
|
+
]
|
|
264
|
+
: []),
|
|
265
|
+
new ScatterplotLayer({
|
|
266
|
+
...common,
|
|
267
|
+
id: `buses-${s.layout}`,
|
|
268
|
+
// Zoomed out, LV bus dots are noise unless they carry the voltage colouring.
|
|
269
|
+
data: s.zoomedOut && s.colour !== 'voltage-band' ? s.mvBuses : s.allBuses,
|
|
270
|
+
getPosition: point,
|
|
271
|
+
getFillColor: (i) => s.busColours[i],
|
|
272
|
+
// Zoomed out, small dots keep dense chains from merging into bars.
|
|
273
|
+
getRadius: symbols ? 2.6 : 1.4,
|
|
274
|
+
radiusUnits: 'pixels',
|
|
275
|
+
radiusMinPixels: 1,
|
|
276
|
+
pickable: true,
|
|
277
|
+
updateTriggers: {
|
|
278
|
+
getFillColor: [s.colour, s.snapshot],
|
|
279
|
+
getPosition: g,
|
|
280
|
+
getRadius: !!symbols
|
|
281
|
+
}
|
|
282
|
+
}),
|
|
283
|
+
new ScatterplotLayer({
|
|
284
|
+
...common,
|
|
285
|
+
id: 'selected-bus',
|
|
286
|
+
data: s.selectedBus >= 0 ? [s.selectedBus] : [],
|
|
287
|
+
getPosition: point,
|
|
288
|
+
getFillColor: [0, 0, 0, 0],
|
|
289
|
+
getLineColor: ACCENT,
|
|
290
|
+
stroked: true,
|
|
291
|
+
filled: false,
|
|
292
|
+
getRadius: 7,
|
|
293
|
+
radiusUnits: 'pixels',
|
|
294
|
+
lineWidthUnits: 'pixels',
|
|
295
|
+
getLineWidth: 2.5
|
|
296
|
+
}),
|
|
297
|
+
// Hovered in a linked view (profile, table) or here: a soft halo.
|
|
298
|
+
new ScatterplotLayer({
|
|
299
|
+
...common,
|
|
300
|
+
id: 'hovered-bus',
|
|
301
|
+
data: s.hoveredBus >= 0 ? [s.hoveredBus] : [],
|
|
302
|
+
getPosition: point,
|
|
303
|
+
getFillColor: alpha(ACCENT, 60),
|
|
304
|
+
getRadius: 9,
|
|
305
|
+
radiusUnits: 'pixels'
|
|
306
|
+
}),
|
|
307
|
+
new LineLayer({
|
|
308
|
+
...common,
|
|
309
|
+
id: 'hovered-branch',
|
|
310
|
+
data: highlighted(d, segmentsOf, s.hoveredEdges, s.fan),
|
|
311
|
+
getSourcePosition: (i) => segment(i, d.sources),
|
|
312
|
+
getTargetPosition: (i) => segment(i, d.targets),
|
|
313
|
+
getColor: alpha(ACCENT, 110),
|
|
314
|
+
getWidth: 7,
|
|
315
|
+
widthUnits: 'pixels'
|
|
316
|
+
}),
|
|
317
|
+
...hoverMarks(kit, s),
|
|
318
|
+
...landmarkLayers(kit, s),
|
|
319
|
+
new ScatterplotLayer({
|
|
320
|
+
...common,
|
|
321
|
+
id: 'keyboard-cursor',
|
|
322
|
+
data: s.cursor >= 0 ? [s.cursor] : [],
|
|
323
|
+
getPosition: point,
|
|
324
|
+
getLineColor: INK,
|
|
325
|
+
stroked: true,
|
|
326
|
+
filled: false,
|
|
327
|
+
getRadius: 11,
|
|
328
|
+
radiusUnits: 'pixels',
|
|
329
|
+
lineWidthUnits: 'pixels',
|
|
330
|
+
getLineWidth: 1.5
|
|
331
|
+
}),
|
|
332
|
+
new TextLayer({
|
|
333
|
+
...common,
|
|
334
|
+
id: 'labels',
|
|
335
|
+
data: s.labels,
|
|
336
|
+
getPosition: (l) => [...l.position, 0],
|
|
337
|
+
getText: (l) => l.text,
|
|
338
|
+
getColor: (l) => stateInk(l.state),
|
|
339
|
+
getSize: 11,
|
|
340
|
+
getTextAnchor: 'start',
|
|
341
|
+
// Above-right of the point: attachment symbols hang below buses.
|
|
342
|
+
getAlignmentBaseline: 'bottom',
|
|
343
|
+
getPixelOffset: [6, -5],
|
|
344
|
+
background: true,
|
|
345
|
+
getBackgroundColor: paperText(230),
|
|
346
|
+
backgroundPadding: [3, 1],
|
|
347
|
+
fontFamily: MONO,
|
|
348
|
+
characterSet: 'auto'
|
|
349
|
+
})
|
|
350
|
+
];
|
|
351
|
+
}
|
|
352
|
+
/** Each edge's drawn segments, built once per drawing. */
|
|
353
|
+
function segmentIndex(d) {
|
|
354
|
+
const out = new Map();
|
|
355
|
+
for (let i = 0; i < d.segmentEdge.length; i++) {
|
|
356
|
+
const k = d.segmentEdge[i];
|
|
357
|
+
const list = out.get(k);
|
|
358
|
+
if (list)
|
|
359
|
+
list.push(i);
|
|
360
|
+
else
|
|
361
|
+
out.set(k, [i]);
|
|
362
|
+
}
|
|
363
|
+
return out;
|
|
364
|
+
}
|
|
365
|
+
/** Drawn segments of `edges`, except those replaced by a fanned route: proportional to the edges
|
|
366
|
+
* asked for, not to the drawing. */
|
|
367
|
+
function highlighted(d, segmentsOf, edges, fan) {
|
|
368
|
+
const out = [];
|
|
369
|
+
for (const k of edges)
|
|
370
|
+
for (const i of segmentsOf.get(k) ?? [])
|
|
371
|
+
if (!hiddenUnderFan(d, i, fan))
|
|
372
|
+
out.push(i);
|
|
373
|
+
return out;
|
|
374
|
+
}
|
|
375
|
+
function flowArrowLayer(kit, arrows, fannedEdges, cache) {
|
|
376
|
+
return new kit.layers.LineLayer({
|
|
377
|
+
...cartesian(kit),
|
|
378
|
+
id: 'flow-arrows',
|
|
379
|
+
data: cache.arrows([arrows, fannedEdges], () => ({
|
|
380
|
+
length: arrows.edge.length,
|
|
381
|
+
attributes: {
|
|
382
|
+
getSourcePosition: { value: arrows.sources, size: 3 },
|
|
383
|
+
getTargetPosition: { value: arrows.targets, size: 3 },
|
|
384
|
+
getColor: {
|
|
385
|
+
value: Uint8Array.from({ length: arrows.edge.length * 4 }, (_, i) => fannedEdges.has(arrows.edge[i >> 2]) ? 0 : ACCENT[i & 3]),
|
|
386
|
+
size: 4,
|
|
387
|
+
normalized: true
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
})),
|
|
391
|
+
getWidth: 1.5,
|
|
392
|
+
widthUnits: 'pixels'
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
/** Fixed-size markers for sources, generators, IBRs and transformers while zoomed out. */
|
|
396
|
+
function landmarkLayers(kit, s) {
|
|
397
|
+
const marks = s.marks;
|
|
398
|
+
if (!marks?.placed.length)
|
|
399
|
+
return [];
|
|
400
|
+
const { LineLayer, ScatterplotLayer, TextLayer } = kit.layers;
|
|
401
|
+
const common = cartesian(kit);
|
|
402
|
+
return [
|
|
403
|
+
// A paper halo lifts each marker off the faded drawing; it is also the pick target.
|
|
404
|
+
new ScatterplotLayer({
|
|
405
|
+
...common,
|
|
406
|
+
id: 'landmark-halos',
|
|
407
|
+
data: marks.placed,
|
|
408
|
+
getPosition: (l) => [l.x, l.y, 0],
|
|
409
|
+
// Semi-transparent: lifts the marker without blanking the drawing under it.
|
|
410
|
+
getFillColor: paperText(150),
|
|
411
|
+
getRadius: 11,
|
|
412
|
+
radiusUnits: 'pixels',
|
|
413
|
+
pickable: true
|
|
414
|
+
}),
|
|
415
|
+
new LineLayer({
|
|
416
|
+
...common,
|
|
417
|
+
id: 'landmarks',
|
|
418
|
+
data: {
|
|
419
|
+
length: marks.strokes.owner.length,
|
|
420
|
+
attributes: {
|
|
421
|
+
getSourcePosition: { value: marks.strokes.sources, size: 3 },
|
|
422
|
+
getTargetPosition: { value: marks.strokes.targets, size: 3 },
|
|
423
|
+
getColor: { value: landmarkColours(marks), size: 4, normalized: true }
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
getWidth: 1.6,
|
|
427
|
+
widthUnits: 'pixels'
|
|
428
|
+
}),
|
|
429
|
+
new TextLayer({
|
|
430
|
+
...common,
|
|
431
|
+
id: 'landmark-names',
|
|
432
|
+
data: marks.placed.filter((l) => l.kind === 'source' || l.kind === 'generator'),
|
|
433
|
+
getPosition: (l) => [l.x, l.y, 0],
|
|
434
|
+
getText: (l) => (s.markName.get(l.ids[0]) ?? l.ids[0]) + (l.ids.length > 1 ? ` +${l.ids.length - 1}` : ''),
|
|
435
|
+
getColor: INK,
|
|
436
|
+
getSize: 10,
|
|
437
|
+
getTextAnchor: 'middle',
|
|
438
|
+
getAlignmentBaseline: 'top',
|
|
439
|
+
getPixelOffset: [0, 14],
|
|
440
|
+
background: true,
|
|
441
|
+
getBackgroundColor: paperText(220),
|
|
442
|
+
backgroundPadding: [2, 1],
|
|
443
|
+
fontFamily: MONO,
|
|
444
|
+
characterSet: 'auto'
|
|
445
|
+
})
|
|
446
|
+
];
|
|
447
|
+
}
|
|
448
|
+
/** Fanned members of shared routes (with their marks), and the count badges of the others. */
|
|
449
|
+
function sharedRouteLayers(kit, s) {
|
|
450
|
+
const eq = s.symbols;
|
|
451
|
+
if (!eq)
|
|
452
|
+
return [];
|
|
453
|
+
const { LineLayer, TextLayer } = kit.layers;
|
|
454
|
+
const common = cartesian(kit);
|
|
455
|
+
const src = [];
|
|
456
|
+
const dst = [];
|
|
457
|
+
const owner = [];
|
|
458
|
+
for (const f of s.fans)
|
|
459
|
+
for (let i = 1; i < f.path.length; i++) {
|
|
460
|
+
src.push(f.path[i - 1][0], f.path[i - 1][1], 0);
|
|
461
|
+
dst.push(f.path[i][0], f.path[i][1], 0);
|
|
462
|
+
owner.push(f.edge);
|
|
463
|
+
}
|
|
464
|
+
// Marks on the fanned paths: the branch's symbol, or its conductor marks.
|
|
465
|
+
const mSrc = [];
|
|
466
|
+
const mDst = [];
|
|
467
|
+
for (const f of s.fans) {
|
|
468
|
+
const e = s.graph.edges[f.edge];
|
|
469
|
+
const glyph = branchGlyph(e.kind, e.closed);
|
|
470
|
+
const { phases, neutral } = conductorSet(s.graph, f.edge);
|
|
471
|
+
const shape = glyph ?? (s.ticksOn ? conductorTicks(phases, neutral) : null);
|
|
472
|
+
if (!shape)
|
|
473
|
+
continue;
|
|
474
|
+
const [cos, sin] = f.dir;
|
|
475
|
+
const u = eq.unit * (glyph ? 0.8 : 1);
|
|
476
|
+
for (const line of shape)
|
|
477
|
+
for (let i = 1; i < line.length; i++) {
|
|
478
|
+
const [ax, ay] = line[i - 1];
|
|
479
|
+
const [bx, by] = line[i];
|
|
480
|
+
mSrc.push(f.mid[0] + u * (ax * cos - ay * sin), f.mid[1] + u * (ax * sin + ay * cos), 0);
|
|
481
|
+
mDst.push(f.mid[0] + u * (bx * cos - by * sin), f.mid[1] + u * (bx * sin + by * cos), 0);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const colours = new Uint8Array(owner.length * 4);
|
|
485
|
+
const widths = new Float32Array(owner.length);
|
|
486
|
+
const [mv, lv] = lineWidths(s.colour !== 'none');
|
|
487
|
+
owner.forEach((k, i) => {
|
|
488
|
+
const selected = s.selectedEdges.has(k);
|
|
489
|
+
const hovered = s.hoveredEdges.has(k);
|
|
490
|
+
colours.set(selected ? ACCENT : hovered ? alpha(ACCENT, 150) : s.edgeColours[k], i * 4);
|
|
491
|
+
widths[i] = selected ? 3.5 : hovered ? 4 : s.edgeMv[k] ? mv : lv;
|
|
492
|
+
});
|
|
493
|
+
const badges = s.ticksOn
|
|
494
|
+
? eq.routeGroups.flatMap((members, g) => {
|
|
495
|
+
if (s.fannedGroups.includes(g))
|
|
496
|
+
return [];
|
|
497
|
+
const p = routeMiddle(members, eq.host, s.geometry);
|
|
498
|
+
return p ? [{ g, n: members.length, position: p }] : [];
|
|
499
|
+
})
|
|
500
|
+
: [];
|
|
501
|
+
return [
|
|
502
|
+
...(owner.length
|
|
503
|
+
? [
|
|
504
|
+
new LineLayer({
|
|
505
|
+
...common,
|
|
506
|
+
id: 'fan-branches',
|
|
507
|
+
data: {
|
|
508
|
+
length: owner.length,
|
|
509
|
+
attributes: {
|
|
510
|
+
getSourcePosition: { value: Float32Array.from(src), size: 3 },
|
|
511
|
+
getTargetPosition: { value: Float32Array.from(dst), size: 3 },
|
|
512
|
+
getColor: { value: colours, size: 4, normalized: true },
|
|
513
|
+
getWidth: { value: widths, size: 1 }
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
widthUnits: 'pixels',
|
|
517
|
+
pickable: true
|
|
518
|
+
}),
|
|
519
|
+
new LineLayer({
|
|
520
|
+
...common,
|
|
521
|
+
id: 'fan-marks',
|
|
522
|
+
data: {
|
|
523
|
+
length: mSrc.length / 3,
|
|
524
|
+
attributes: {
|
|
525
|
+
getSourcePosition: { value: Float32Array.from(mSrc), size: 3 },
|
|
526
|
+
getTargetPosition: { value: Float32Array.from(mDst), size: 3 }
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
getColor: SYMBOL_INK,
|
|
530
|
+
getWidth: 1,
|
|
531
|
+
widthUnits: 'pixels'
|
|
532
|
+
})
|
|
533
|
+
]
|
|
534
|
+
: []),
|
|
535
|
+
...(badges.length
|
|
536
|
+
? [
|
|
537
|
+
new TextLayer({
|
|
538
|
+
...common,
|
|
539
|
+
id: 'route-badges',
|
|
540
|
+
data: badges,
|
|
541
|
+
getPosition: (b) => [...b.position, 0],
|
|
542
|
+
getText: (b) => `×${b.n}`,
|
|
543
|
+
getColor: SYMBOL_INK,
|
|
544
|
+
getSize: 11,
|
|
545
|
+
getPixelOffset: [0, -10],
|
|
546
|
+
background: true,
|
|
547
|
+
getBackgroundColor: paperText(230),
|
|
548
|
+
backgroundPadding: [3, 1],
|
|
549
|
+
fontFamily: MONO,
|
|
550
|
+
characterSet: 'auto',
|
|
551
|
+
pickable: true
|
|
552
|
+
})
|
|
553
|
+
]
|
|
554
|
+
: [])
|
|
555
|
+
];
|
|
556
|
+
}
|
|
557
|
+
/** Zone corridors on geographic positions while zoomed out: a band of fixed screen width along
|
|
558
|
+
* each LV network's branches, with discs at its buses to round the joints, quiet for networks
|
|
559
|
+
* within limits and in their state's colour otherwise. A translucent band would darken wherever
|
|
560
|
+
* its pieces overlap, so the bands test depth strictly, all at one depth (no per-layer polygon
|
|
561
|
+
* offset): each pixel is tinted once. Close in, the lines speak for themselves. */
|
|
562
|
+
function corridorLayers(kit, s) {
|
|
563
|
+
const { corridors, states } = s.zones;
|
|
564
|
+
if (!corridors.length || s.symbols)
|
|
565
|
+
return [];
|
|
566
|
+
const { LineLayer, ScatterplotLayer } = kit.layers;
|
|
567
|
+
const common = cartesian(kit);
|
|
568
|
+
const g = s.shown;
|
|
569
|
+
const once = { depthWriteEnabled: true, depthCompare: 'less' };
|
|
570
|
+
const band = (z) => {
|
|
571
|
+
const state = states[z];
|
|
572
|
+
if (state === 'warning' || state === 'violation') {
|
|
573
|
+
const fill = ZONE_COLOURS[state][0];
|
|
574
|
+
return alpha(fill, Math.min(255, fill[3] * 3));
|
|
575
|
+
}
|
|
576
|
+
return alpha(ZONE_INK, 56);
|
|
577
|
+
};
|
|
578
|
+
const { segments, buses } = s.cache.corridors([corridors], () => ({
|
|
579
|
+
segments: corridors.flatMap((c) => c.segments.map((i) => ({ z: c.z, s: i }))),
|
|
580
|
+
buses: corridors.flatMap((c) => c.buses.map((i) => ({ z: c.z, i })))
|
|
581
|
+
}));
|
|
582
|
+
return [
|
|
583
|
+
new LineLayer({
|
|
584
|
+
...common,
|
|
585
|
+
id: 'zones',
|
|
586
|
+
data: segments,
|
|
587
|
+
getSourcePosition: (d) => [g.sources[d.s * 3], g.sources[d.s * 3 + 1], 0],
|
|
588
|
+
getTargetPosition: (d) => [g.targets[d.s * 3], g.targets[d.s * 3 + 1], 0],
|
|
589
|
+
getColor: (d) => band(d.z),
|
|
590
|
+
getWidth: CORRIDOR_PX,
|
|
591
|
+
widthUnits: 'pixels',
|
|
592
|
+
parameters: once,
|
|
593
|
+
getPolygonOffset: null,
|
|
594
|
+
pickable: true,
|
|
595
|
+
updateTriggers: { getColor: states }
|
|
596
|
+
}),
|
|
597
|
+
new ScatterplotLayer({
|
|
598
|
+
...common,
|
|
599
|
+
id: 'zones-buses',
|
|
600
|
+
data: buses,
|
|
601
|
+
getPosition: (d) => [g.positions[d.i * 3], g.positions[d.i * 3 + 1], 0],
|
|
602
|
+
getFillColor: (d) => band(d.z),
|
|
603
|
+
getRadius: CORRIDOR_PX / 2,
|
|
604
|
+
radiusUnits: 'pixels',
|
|
605
|
+
parameters: once,
|
|
606
|
+
getPolygonOffset: null,
|
|
607
|
+
pickable: true,
|
|
608
|
+
updateTriggers: { getFillColor: states }
|
|
609
|
+
})
|
|
610
|
+
];
|
|
611
|
+
}
|
|
612
|
+
/** The hovered network and feeder, as halos beneath the lines: the network faint, the feeder
|
|
613
|
+
* strong, the same on MV and LV. */
|
|
614
|
+
function hoverHalos(kit, s) {
|
|
615
|
+
const h = s.hover;
|
|
616
|
+
if (!h)
|
|
617
|
+
return [];
|
|
618
|
+
const d = s.drawn;
|
|
619
|
+
const halo = (id, edges, a) => new kit.layers.LineLayer({
|
|
620
|
+
...cartesian(kit),
|
|
621
|
+
id,
|
|
622
|
+
data: highlighted(d, s.cache.segmentsOf([d], () => segmentIndex(d)), edges, s.fan),
|
|
623
|
+
getSourcePosition: (i) => [d.sources[i * 3], d.sources[i * 3 + 1], 0],
|
|
624
|
+
getTargetPosition: (i) => [d.targets[i * 3], d.targets[i * 3 + 1], 0],
|
|
625
|
+
getColor: alpha(h.tint, a),
|
|
626
|
+
getWidth: HALO_PX,
|
|
627
|
+
widthUnits: 'pixels'
|
|
628
|
+
});
|
|
629
|
+
return [halo('hover-network', h.network, 90), halo('hover-feeder', h.feeder, 190)];
|
|
630
|
+
}
|
|
631
|
+
/** Over the lines: the path back to the source, when shown, and a ring at the supply point. */
|
|
632
|
+
function hoverMarks(kit, s) {
|
|
633
|
+
const h = s.hover;
|
|
634
|
+
if (!h)
|
|
635
|
+
return [];
|
|
636
|
+
const d = s.drawn;
|
|
637
|
+
const common = cartesian(kit);
|
|
638
|
+
return [
|
|
639
|
+
...(h.path.size
|
|
640
|
+
? [
|
|
641
|
+
new kit.layers.LineLayer({
|
|
642
|
+
...common,
|
|
643
|
+
id: 'hover-path',
|
|
644
|
+
data: highlighted(d, s.cache.segmentsOf([d], () => segmentIndex(d)), h.path, s.fan),
|
|
645
|
+
getSourcePosition: (i) => [d.sources[i * 3], d.sources[i * 3 + 1], 0],
|
|
646
|
+
getTargetPosition: (i) => [d.targets[i * 3], d.targets[i * 3 + 1], 0],
|
|
647
|
+
getColor: PATH,
|
|
648
|
+
getWidth: 3,
|
|
649
|
+
widthUnits: 'pixels'
|
|
650
|
+
})
|
|
651
|
+
]
|
|
652
|
+
: []),
|
|
653
|
+
...(h.supply
|
|
654
|
+
? [
|
|
655
|
+
new kit.layers.ScatterplotLayer({
|
|
656
|
+
...common,
|
|
657
|
+
id: 'hover-supply',
|
|
658
|
+
data: [h.supply],
|
|
659
|
+
getPosition: (p) => [...p, 0],
|
|
660
|
+
getFillColor: alpha(h.tint, 50),
|
|
661
|
+
getLineColor: alpha(h.tint, 220),
|
|
662
|
+
stroked: true,
|
|
663
|
+
getRadius: 13,
|
|
664
|
+
radiusUnits: 'pixels',
|
|
665
|
+
getLineWidth: 2,
|
|
666
|
+
lineWidthUnits: 'pixels'
|
|
667
|
+
})
|
|
668
|
+
]
|
|
669
|
+
: [])
|
|
670
|
+
];
|
|
671
|
+
}
|
|
672
|
+
/** Spokes from each spread stack's true point to its buses; a ring and ×n badge on the others. */
|
|
673
|
+
function stackLayers(kit, s) {
|
|
674
|
+
const stacks = s.stacks;
|
|
675
|
+
if (!stacks)
|
|
676
|
+
return [];
|
|
677
|
+
const { LineLayer, ScatterplotLayer, TextLayer } = kit.layers;
|
|
678
|
+
const common = cartesian(kit);
|
|
679
|
+
const base = s.shown;
|
|
680
|
+
const point = (g) => {
|
|
681
|
+
const i = stacks.groups[g][0];
|
|
682
|
+
return [base.positions[i * 3], base.positions[i * 3 + 1]];
|
|
683
|
+
};
|
|
684
|
+
const spokes = [];
|
|
685
|
+
for (const g of s.spreadStacks)
|
|
686
|
+
for (const i of stacks.groups[g])
|
|
687
|
+
spokes.push({
|
|
688
|
+
from: point(g),
|
|
689
|
+
to: [s.geometry.positions[i * 3], s.geometry.positions[i * 3 + 1]]
|
|
690
|
+
});
|
|
691
|
+
// A spread stack keeps a faint ring around its circle, so the gaps between its buses still
|
|
692
|
+
// belong to it: the hover holds and a click there pins it.
|
|
693
|
+
const rings = stacks.groups.flatMap((members, g) => s.spreadStacks.includes(g)
|
|
694
|
+
? [{ g, n: members.length, position: point(g), spread: true }]
|
|
695
|
+
: s.symbols
|
|
696
|
+
? [{ g, n: members.length, position: point(g), spread: false }]
|
|
697
|
+
: []);
|
|
698
|
+
const closed = rings.filter((r) => !r.spread);
|
|
699
|
+
return [
|
|
700
|
+
...(spokes.length
|
|
701
|
+
? [
|
|
702
|
+
new LineLayer({
|
|
703
|
+
...common,
|
|
704
|
+
id: 'stack-spokes',
|
|
705
|
+
data: spokes,
|
|
706
|
+
getSourcePosition: (d) => [...d.from, 0],
|
|
707
|
+
getTargetPosition: (d) => [...d.to, 0],
|
|
708
|
+
getColor: [120, 114, 102, 140],
|
|
709
|
+
getWidth: 1,
|
|
710
|
+
widthUnits: 'pixels'
|
|
711
|
+
})
|
|
712
|
+
]
|
|
713
|
+
: []),
|
|
714
|
+
...(rings.length
|
|
715
|
+
? [
|
|
716
|
+
new ScatterplotLayer({
|
|
717
|
+
...common,
|
|
718
|
+
id: 'stack-rings',
|
|
719
|
+
data: rings,
|
|
720
|
+
getPosition: (d) => [...d.position, 0],
|
|
721
|
+
stroked: true,
|
|
722
|
+
filled: true,
|
|
723
|
+
getFillColor: [0, 0, 0, 1],
|
|
724
|
+
getLineColor: (d) => d.spread ? alpha(SYMBOL_INK, 70) : SYMBOL_INK,
|
|
725
|
+
getRadius: (d) => (d.spread ? stackRadius(d.n) + 6 : 6),
|
|
726
|
+
radiusUnits: 'pixels',
|
|
727
|
+
lineWidthUnits: 'pixels',
|
|
728
|
+
getLineWidth: 1,
|
|
729
|
+
pickable: true,
|
|
730
|
+
updateTriggers: { getLineColor: s.spreadStacks, getRadius: s.spreadStacks }
|
|
731
|
+
})
|
|
732
|
+
]
|
|
733
|
+
: []),
|
|
734
|
+
...(closed.length
|
|
735
|
+
? [
|
|
736
|
+
new TextLayer({
|
|
737
|
+
...common,
|
|
738
|
+
id: 'stack-badges',
|
|
739
|
+
data: closed,
|
|
740
|
+
getPosition: (d) => [...d.position, 0],
|
|
741
|
+
getText: (d) => `×${d.n}`,
|
|
742
|
+
getColor: SYMBOL_INK,
|
|
743
|
+
getSize: 10,
|
|
744
|
+
getTextAnchor: 'end',
|
|
745
|
+
getAlignmentBaseline: 'bottom',
|
|
746
|
+
getPixelOffset: [-6, -5],
|
|
747
|
+
background: true,
|
|
748
|
+
getBackgroundColor: paperText(220),
|
|
749
|
+
backgroundPadding: [2, 1],
|
|
750
|
+
fontFamily: MONO,
|
|
751
|
+
characterSet: 'auto'
|
|
752
|
+
})
|
|
753
|
+
]
|
|
754
|
+
: [])
|
|
755
|
+
];
|
|
756
|
+
}
|