smithkit 0.3.0 → 0.4.0
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 +146 -23
- package/dist/lib/OptionsValidation.d.ts +16 -0
- package/dist/lib/OptionsValidation.js +168 -0
- package/dist/lib/Smith.d.ts +13 -4
- package/dist/lib/Smith.js +99 -11
- package/dist/lib/appearance/Theme.js +5 -0
- package/dist/lib/grid/CircleLayerControl.d.ts +1 -1
- package/dist/lib/grid/CircleLayerControl.js +10 -8
- package/dist/lib/grid/ConstQCircles.d.ts +1 -0
- package/dist/lib/grid/ConstQCircles.js +5 -0
- package/dist/lib/grid/ConstSwrCircles.d.ts +1 -0
- package/dist/lib/grid/ConstSwrCircles.js +5 -0
- package/dist/lib/grid/GridLayerControl.js +5 -4
- package/dist/lib/grid/LayerControl.d.ts +1 -2
- package/dist/lib/grid/LayerControl.js +1 -10
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/layers.d.ts +3 -1
- package/dist/lib/options.d.ts +54 -0
- package/dist/lib/options.js +1 -0
- package/dist/lib/rendering/SvgChartRenderer.d.ts +3 -0
- package/dist/lib/rendering/SvgChartRenderer.js +31 -9
- package/dist/lib/svg/export.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
**`smithkit`** is a framework-independent JavaScript and TypeScript library for
|
|
4
4
|
interactive SVG Smith charts, RF calculations, and S11 measurement exploration.
|
|
5
5
|
|
|
6
|
-
[Try the interactive demo](https://rosek86.github.io/smithkit/)
|
|
6
|
+
[Try the interactive demo](https://rosek86.github.io/smithkit/) or browse the
|
|
7
|
+
[integration examples](https://rosek86.github.io/smithkit/examples/index.html):
|
|
8
|
+
static charts, marker controls, themes, report exports, and lifecycle cleanup.
|
|
9
|
+
Each example includes its TypeScript source.
|
|
10
|
+
|
|
11
|
+
Upgrading from 0.3.x? Version 0.4.0 introduces grouped constructor options and
|
|
12
|
+
`setOptions()`, static chart defaults, and explicit marker creation. See the
|
|
13
|
+
[0.4 migration guide](https://github.com/rosek86/smithkit/blob/v0.4.0/docs/migration-0.4.md).
|
|
7
14
|
|
|
8
15
|
Upgrading from 0.2.x? Version 0.3.0 replaces `GridLayer.setMinorVisible()` with
|
|
9
16
|
`setDetail('basic' | 'standard' | 'detailed')`. Replace `false` with `'standard'`
|
|
@@ -13,6 +20,10 @@ Upgrading from 0.1.x? Version 0.2.0 groups calculation, parsing, formatting, and
|
|
|
13
20
|
comparison functions into classes. See the [migration guide](https://github.com/rosek86/smithkit/blob/v0.2.0/docs/migration-0.2.md)
|
|
14
21
|
for the changed imports.
|
|
15
22
|
|
|
23
|
+
The [API review and compatibility policy](https://github.com/rosek86/smithkit/blob/main/docs/api-review.md)
|
|
24
|
+
define the public contract and the compatibility scope intended for 1.0. During
|
|
25
|
+
0.x development, breaking changes are documented in minor releases.
|
|
26
|
+
|
|
16
27
|
## Features
|
|
17
28
|
|
|
18
29
|
- Labeled resistance, reactance, conductance, and susceptance grids.
|
|
@@ -54,7 +65,7 @@ Give the container an explicit size and run the code after it is mounted:
|
|
|
54
65
|
```ts
|
|
55
66
|
import { Smith } from 'smithkit';
|
|
56
67
|
|
|
57
|
-
const chart = new Smith(50); // Reference impedance in ohms; defaults to 50.
|
|
68
|
+
const chart = new Smith({ referenceImpedanceOhms: 50 }); // Reference impedance in ohms; defaults to 50.
|
|
58
69
|
chart.draw('#smith');
|
|
59
70
|
const traceId = chart.addTrace(
|
|
60
71
|
[
|
|
@@ -71,33 +82,139 @@ coefficient Γ as `[real, imaginary]`. Samples are copied and validated; readonl
|
|
|
71
82
|
must be finite and non-negative; both coordinates must be finite. A trace must
|
|
72
83
|
contain at least one sample.
|
|
73
84
|
|
|
74
|
-
|
|
75
|
-
|
|
85
|
+
Traces start without markers. Call `chart.addMarker(traceId)` to add one at the
|
|
86
|
+
first sample, or pass a sample index to choose its initial position. Drag markers to select
|
|
87
|
+
samples with a mouse or touch. Enable zoom to scroll to zoom and drag the chart to pan. Markers
|
|
76
88
|
and their focus indicators stay above grid labels and sample points. `chart.resetView()` restores the initial view, including peripheral rulers.
|
|
77
89
|
|
|
78
90
|
Construct `Smith` only in a browser, such as your framework's mount hook. Importing
|
|
79
91
|
the package and using its calculation or parsing methods does not require a DOM.
|
|
80
92
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
page scrolling. Disabling during an active gesture freezes the view while that
|
|
87
|
-
gesture finishes normally.
|
|
93
|
+
The default chart is a static presentation: standard impedance grid with labels,
|
|
94
|
+
no peripheral scales, and zoom and cursor tracking disabled. Markers are optional
|
|
95
|
+
and remain interactive when explicitly added.
|
|
96
|
+
|
|
97
|
+
Enable interactions through constructor options or the corresponding setters:
|
|
88
98
|
|
|
89
99
|
```ts
|
|
90
100
|
import { Smith } from 'smithkit';
|
|
91
101
|
|
|
92
|
-
const chart = new Smith(
|
|
93
|
-
chart.setZoomEnabled(false);
|
|
102
|
+
const chart = new Smith({ interaction: { zoom: true, cursor: true } });
|
|
94
103
|
chart.draw('#smith');
|
|
104
|
+
|
|
105
|
+
chart.setZoomEnabled(false); // Freeze the current view; page scrolling is unaffected.
|
|
106
|
+
chart.setCursorEnabled(false); // Hide cursor geometry and cancel pending readings.
|
|
95
107
|
```
|
|
96
108
|
|
|
109
|
+
Zoom controls wheel/double-click zoom and mouse/touch panning and pinch gestures;
|
|
110
|
+
it is independent of cursor tracking and marker interaction. `resetView()` remains
|
|
111
|
+
available with zoom disabled. Disabling during an active gesture freezes the view
|
|
112
|
+
while that gesture finishes normally.
|
|
113
|
+
|
|
114
|
+
Cursor tracking controls the cursor overlay and `Cursor` events. Disabling it
|
|
115
|
+
clears the current readout with one `Cursor` event carrying `undefined`, cancels
|
|
116
|
+
queued readings, and suppresses further cursor events. Enabling waits for a new
|
|
117
|
+
pointer movement. The last `cursorReading` remains available; marker controls and
|
|
118
|
+
events work independently.
|
|
119
|
+
|
|
97
120
|
`draw(selector | HTMLElement)` moves the existing SVG when called again. A missing
|
|
98
121
|
container throws. Call `chart.destroy()` on unmount; it removes the SVG, releases
|
|
99
122
|
event handlers, and cancels queued notifications. Repeated destruction is safe.
|
|
100
123
|
|
|
124
|
+
## Constructor configuration
|
|
125
|
+
|
|
126
|
+
`new Smith(options?)` accepts a typed `SmithOptions` object. Every field is optional:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import { Smith } from 'smithkit';
|
|
130
|
+
|
|
131
|
+
const chart = new Smith({
|
|
132
|
+
referenceImpedanceOhms: 75,
|
|
133
|
+
appearance: { theme: 'dark' },
|
|
134
|
+
interaction: { zoom: false },
|
|
135
|
+
peripheralScales: { visible: false },
|
|
136
|
+
grid: {
|
|
137
|
+
detail: 'basic',
|
|
138
|
+
labelsVisible: true,
|
|
139
|
+
style: { majorWidth: 1.5 },
|
|
140
|
+
layers: {
|
|
141
|
+
resistance: { style: { stroke: '#60a5fa' } },
|
|
142
|
+
reactance: { labelsVisible: false },
|
|
143
|
+
conductance: { visible: true, detail: 'standard' },
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
circles: {
|
|
147
|
+
q: { visible: true, values: [1, 2, 5] },
|
|
148
|
+
vswr: { visible: true, values: [2, 3], style: { stroke: '#fbbf24' } },
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
chart.draw('#smith');
|
|
152
|
+
|
|
153
|
+
// Update all four grids together, including hidden admittance layers.
|
|
154
|
+
chart.setGridDetail('standard');
|
|
155
|
+
// Individual overrides remain available.
|
|
156
|
+
chart.layers.reactance.setDetail('basic');
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Configuration is grouped by responsibility:
|
|
160
|
+
|
|
161
|
+
| Group | Purpose | Runtime API |
|
|
162
|
+
| ------------------------ | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
|
|
163
|
+
| `referenceImpedanceOhms` | Physical reference impedance | `renormalize()` |
|
|
164
|
+
| `appearance` | Theme, fonts, colors, marker/cursor styling | `setAppearance()` |
|
|
165
|
+
| `interaction` | `zoom` and `cursor` tracking switches | `setZoomEnabled()`, `setCursorEnabled()` |
|
|
166
|
+
| `grid` | Shared detail, labels, styles; `layers` overrides for resistance/reactance/conductance/susceptance | `setGridDetail()`, `layers.resistance` and the other grid controls |
|
|
167
|
+
| `circles` | Independent Q and VSWR values, visibility, styles | `layers.q`, `layers.vswr` |
|
|
168
|
+
| `peripheralScales` | Visibility of all peripheral rulers together | `peripheralScales.setVisible()` |
|
|
169
|
+
|
|
170
|
+
Defaults are 50 Ω, the light theme, zoom and cursor disabled, peripheral scales hidden,
|
|
171
|
+
and standard grids with labels. Resistance/reactance are visible;
|
|
172
|
+
conductance/susceptance and Q/VSWR circles are hidden. Q defaults to
|
|
173
|
+
`[0.5, 1, 2, 5, 10]`; VSWR defaults to `[1.2, 1.5, 2, 3, 5, 10]`.
|
|
174
|
+
|
|
175
|
+
Shared `grid` settings apply to all four grids; `grid.layers` overrides individual
|
|
176
|
+
fields afterward. Styles merge by property. Explicit layer styles override theme
|
|
177
|
+
colors and remain in effect when `setAppearance()` changes the theme.
|
|
178
|
+
Options and circle arrays are copied; changing the original object does not update
|
|
179
|
+
the chart. Use the existing chart/layer methods for later changes. Unknown constructor groups
|
|
180
|
+
or structural option names throw `TypeError`; omitted fields use defaults.
|
|
181
|
+
|
|
182
|
+
Export configuration belongs to `toSvg()`/`toPng()` calls, independently of chart
|
|
183
|
+
construction. `scaleReadout` requires `scales`; otherwise export throws/rejects with `TypeError`.
|
|
184
|
+
|
|
185
|
+
The constructor configures presentation and interaction. Add measurement data with
|
|
186
|
+
`addTrace()` and optional markers with `addMarker()`; no markers are created implicitly.
|
|
187
|
+
Radial parameter scales remain independently mounted `SmithScales` instances.
|
|
188
|
+
|
|
189
|
+
### Updating configuration
|
|
190
|
+
|
|
191
|
+
`chart.setOptions(options)` accepts the same `SmithOptions` type as the constructor:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
chart.setOptions({
|
|
195
|
+
interaction: { zoom: true },
|
|
196
|
+
grid: {
|
|
197
|
+
detail: 'basic',
|
|
198
|
+
layers: { reactance: { labelsVisible: false } },
|
|
199
|
+
},
|
|
200
|
+
circles: { vswr: { visible: true, values: [2, 3] } },
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
This is a patch: omitted and `undefined` fields retain their current values;
|
|
205
|
+
`{}` groups do nothing. Shared grid settings apply to all four grids first, then
|
|
206
|
+
per-layer overrides in the same call. A later shared setting also replaces that
|
|
207
|
+
field on previously customized layers. Style fields merge; circle value lists
|
|
208
|
+
replace. `appearance`, when provided, replaces the preset/overrides exactly like
|
|
209
|
+
`setAppearance()` (it is not recursively merged with the previous appearance).
|
|
210
|
+
|
|
211
|
+
The complete patch is validated before application. Invalid settings or impossible
|
|
212
|
+
reference conversions leave the chart unchanged. `referenceImpedanceOhms` invokes
|
|
213
|
+
renormalization of existing traces and retains physical impedance and marker IDs.
|
|
214
|
+
Other configuration updates preserve measurements, markers, selection, and view.
|
|
215
|
+
Options are not retained; subsequent edits to the input object have no effect.
|
|
216
|
+
Individual setters remain available for focused changes.
|
|
217
|
+
|
|
101
218
|
### Keyboard and touch markers
|
|
102
219
|
|
|
103
220
|
Use Tab/Shift+Tab to focus a marker. Arrow keys move through samples in input order;
|
|
@@ -124,7 +241,7 @@ there. They survive renaming, hiding, updating samples, and removing other items
|
|
|
124
241
|
They are not persistent across chart instances or page reloads.
|
|
125
242
|
|
|
126
243
|
```ts
|
|
127
|
-
const markerA = chart.
|
|
244
|
+
const markerA = chart.addMarker(traceId)!; // First sample.
|
|
128
245
|
const markerB = chart.addMarker(traceId, 2)!; // Zero-based sample index.
|
|
129
246
|
|
|
130
247
|
chart.setTraceOptions(traceId, { name: 'Tuned antenna', visible: true });
|
|
@@ -141,7 +258,7 @@ chart.removeMarker(markerB);
|
|
|
141
258
|
|
|
142
259
|
| Method | Result / behavior |
|
|
143
260
|
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
144
|
-
| `addTrace(samples, options?)` | Returns a trace ID. Options: nonblank `name`, solid CSS `color`, and `visible`.
|
|
261
|
+
| `addTrace(samples, options?)` | Returns a trace ID, without creating markers. Options: nonblank `name`, solid CSS `color`, and `visible`. |
|
|
145
262
|
| `getTraces()` | Detached `TraceInfo` snapshots: ID, name, color, visibility, sample count, and marker metadata. |
|
|
146
263
|
| `setTraceOptions(id, options)` | Updates the supplied options only. Invalid options throw before any change. |
|
|
147
264
|
| `updateTrace(id, samples, options?)` | Replaces samples and preserves identity, appearance, and markers. Empty/invalid input throws without changing the trace. |
|
|
@@ -240,8 +357,8 @@ queued `Marker` event and do not emit drag lifecycle events.
|
|
|
240
357
|
Cursor movement is suspended during marker dragging. Drag start/end events fire
|
|
241
358
|
synchronously; marker position notifications are queued and report the latest
|
|
242
359
|
snapshot at delivery. Before delivery, repeated updates to the same marker coalesce
|
|
243
|
-
into one notification; different markers have independent notifications.
|
|
244
|
-
queues an initial marker notification too. Removed markers cannot deliver queued
|
|
360
|
+
into one notification; different markers have independent notifications. `addMarker()`
|
|
361
|
+
queues an initial marker notification too; `addTrace()` does not. Removed markers cannot deliver queued
|
|
245
362
|
position events.
|
|
246
363
|
Trace metadata changes do not emit marker events; refresh metadata after mutations.
|
|
247
364
|
Destruction suppresses application callbacks.
|
|
@@ -368,7 +485,7 @@ const appearance: SmithAppearance = {
|
|
|
368
485
|
traceColors: ['#38bdf8', '#fb923c', '#a78bfa'],
|
|
369
486
|
},
|
|
370
487
|
};
|
|
371
|
-
const chart = new Smith(50, appearance);
|
|
488
|
+
const chart = new Smith({ referenceImpedanceOhms: 50, appearance });
|
|
372
489
|
const scales = new SmithScales(appearance);
|
|
373
490
|
chart.draw('#smith');
|
|
374
491
|
scales.draw('#scales');
|
|
@@ -437,12 +554,13 @@ layout raises it when needed to keep the font size at least 9 CSS pixels in the
|
|
|
437
554
|
default view. Partial updates retain other settings. Invalid numeric styles throw
|
|
438
555
|
before any property is changed.
|
|
439
556
|
|
|
440
|
-
|
|
557
|
+
Use `chart.setGridDetail('basic' | 'standard' | 'detailed')` for all four grid layers,
|
|
558
|
+
or `layer.setDetail(...)` to override one layer:
|
|
441
559
|
|
|
442
560
|
- **Basic:** complete circles and arcs for normalized values 0.2, 0.5, 1, 2, and 5,
|
|
443
561
|
with matching axis/rim labels and the zero label. Intended for small or static charts.
|
|
444
|
-
- **Standard
|
|
445
|
-
- **Detailed
|
|
562
|
+
- **Standard** (default): the full major grid and its labels, without minor lines.
|
|
563
|
+
- **Detailed:** the full major and minor grid.
|
|
446
564
|
|
|
447
565
|
Changing detail preserves layer visibility, styles, and the current view. Label
|
|
448
566
|
collision handling still applies at every level. Peripheral scales and Q/VSWR
|
|
@@ -455,7 +573,10 @@ whole-layer visibility changes and is included in SVG exports. Calling
|
|
|
455
573
|
layout; it does not show a hidden layer. The **Grid labels** checkbox in the demo’s **Chart settings** dialog controls all four grid layers together. Peripheral scale labels are unaffected.
|
|
456
574
|
|
|
457
575
|
`layers.q` and `layers.vswr` support `setVisible`,
|
|
458
|
-
`setStyle({ stroke?, strokeWidth? })`, `addValue`, and `removeValue`.
|
|
576
|
+
`setStyle({ stroke?, strokeWidth? })`, `setValues`, `addValue`, and `removeValue`.
|
|
577
|
+
`setValues([1, 2, 5])` replaces the entire list; `setValues([])` clears it.
|
|
578
|
+
Input arrays are copied, duplicate values are ignored, and invalid input leaves
|
|
579
|
+
the previous values unchanged.
|
|
459
580
|
Circle stroke widths are positive finite numbers in CSS pixels.
|
|
460
581
|
Q values must be positive and finite; VSWR values must be finite and at least 1.
|
|
461
582
|
Duplicate additions and removal of absent values have no effect.
|
|
@@ -515,6 +636,8 @@ assumes a matched line or attenuator terminated in an open or short: the reflect
|
|
|
515
636
|
wave traverses it twice. It does not measure insertion loss of an arbitrary load
|
|
516
637
|
from S11 alone. At \|Γ\| = 0.1 it reads 10 dB, while return loss reads 20 dB.
|
|
517
638
|
|
|
639
|
+
Peripheral rulers are hidden by default. Enable them with
|
|
640
|
+
`{ peripheralScales: { visible: true } }` or `chart.peripheralScales.setVisible(true)`.
|
|
518
641
|
`chart.peripheralScales` supports `setVisible(boolean)` and `update(Complex | null)`.
|
|
519
642
|
Its four rulers zoom with the chart: reflection phase, voltage transmission phase,
|
|
520
643
|
wavelengths toward generator, and wavelengths toward load. Wavelength rulers start
|
|
@@ -565,7 +688,7 @@ import { Smith, Touchstone } from 'smithkit';
|
|
|
565
688
|
|
|
566
689
|
async function showMeasurement(file: File): Promise<Smith> {
|
|
567
690
|
const { samples, referenceImpedanceOhms } = Touchstone.parse(await file.text());
|
|
568
|
-
const chart = new Smith(referenceImpedanceOhms);
|
|
691
|
+
const chart = new Smith({ referenceImpedanceOhms });
|
|
569
692
|
chart.draw('#smith');
|
|
570
693
|
chart.addTrace(samples, { name: file.name });
|
|
571
694
|
return chart;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SmithOptions } from './options.js';
|
|
2
|
+
import type { CircleStyle, GridDetail, GridStyle } from './layers.js';
|
|
3
|
+
/** Shared validation for initial configuration, patches, and individual controls. */
|
|
4
|
+
export declare class OptionsValidation {
|
|
5
|
+
private constructor();
|
|
6
|
+
static object(value: unknown, name: string, keys?: readonly string[]): void;
|
|
7
|
+
static boolean(value: boolean, name: string): void;
|
|
8
|
+
static detail(value: GridDetail): void;
|
|
9
|
+
private static lengths;
|
|
10
|
+
private static strings;
|
|
11
|
+
static gridStyle(style: Partial<GridStyle>): void;
|
|
12
|
+
static circleStyle(style: Partial<CircleStyle>): void;
|
|
13
|
+
static circleValue(value: number, minimum: number): void;
|
|
14
|
+
static circleValues(values: readonly number[], minimum: number): void;
|
|
15
|
+
static chart(options: SmithOptions): void;
|
|
16
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { Theme } from './appearance/Theme.js';
|
|
2
|
+
/** Shared validation for initial configuration, patches, and individual controls. */
|
|
3
|
+
export class OptionsValidation {
|
|
4
|
+
constructor() { }
|
|
5
|
+
static object(value, name, keys) {
|
|
6
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
7
|
+
throw new TypeError(`${name} must be an object.`);
|
|
8
|
+
}
|
|
9
|
+
if (keys) {
|
|
10
|
+
for (const key of Object.keys(value)) {
|
|
11
|
+
if (!keys.includes(key)) {
|
|
12
|
+
throw new TypeError(`Unknown ${name} option: ${key}.`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
static boolean(value, name) {
|
|
18
|
+
if (typeof value !== 'boolean') {
|
|
19
|
+
throw new TypeError(`${name} must be a boolean.`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
static detail(value) {
|
|
23
|
+
if (!['basic', 'standard', 'detailed'].includes(value)) {
|
|
24
|
+
throw new RangeError('Grid detail must be basic, standard, or detailed.');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
static lengths(style, keys) {
|
|
28
|
+
for (const key of keys) {
|
|
29
|
+
const value = style[key];
|
|
30
|
+
if (value !== undefined &&
|
|
31
|
+
(typeof value !== 'number' || !Number.isFinite(value) || value <= 0)) {
|
|
32
|
+
throw new RangeError(`${String(key)} must be a positive finite number.`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
static strings(style, keys) {
|
|
37
|
+
for (const key of keys) {
|
|
38
|
+
if (style[key] !== undefined && typeof style[key] !== 'string') {
|
|
39
|
+
throw new TypeError(`${String(key)} must be a CSS string.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
static gridStyle(style) {
|
|
44
|
+
OptionsValidation.object(style, 'Grid style', [
|
|
45
|
+
'stroke',
|
|
46
|
+
'majorWidth',
|
|
47
|
+
'minorWidth',
|
|
48
|
+
'textColor',
|
|
49
|
+
'textFontFamily',
|
|
50
|
+
'textFontSize',
|
|
51
|
+
]);
|
|
52
|
+
OptionsValidation.lengths(style, ['majorWidth', 'minorWidth', 'textFontSize']);
|
|
53
|
+
OptionsValidation.strings(style, ['stroke', 'textColor', 'textFontFamily']);
|
|
54
|
+
}
|
|
55
|
+
static circleStyle(style) {
|
|
56
|
+
OptionsValidation.object(style, 'Circle style', ['stroke', 'strokeWidth']);
|
|
57
|
+
OptionsValidation.lengths(style, ['strokeWidth']);
|
|
58
|
+
OptionsValidation.strings(style, ['stroke']);
|
|
59
|
+
}
|
|
60
|
+
static circleValue(value, minimum) {
|
|
61
|
+
if (!Number.isFinite(value) || value < minimum || value <= 0) {
|
|
62
|
+
throw new RangeError(`Circle value must be finite, positive, and at least ${minimum}.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
static circleValues(values, minimum) {
|
|
66
|
+
if (!Array.isArray(values)) {
|
|
67
|
+
throw new TypeError('Circle values must be an array.');
|
|
68
|
+
}
|
|
69
|
+
for (const value of values) {
|
|
70
|
+
OptionsValidation.circleValue(value, minimum);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
static chart(options) {
|
|
74
|
+
OptionsValidation.object(options, 'Smith options', [
|
|
75
|
+
'referenceImpedanceOhms',
|
|
76
|
+
'appearance',
|
|
77
|
+
'interaction',
|
|
78
|
+
'grid',
|
|
79
|
+
'circles',
|
|
80
|
+
'peripheralScales',
|
|
81
|
+
]);
|
|
82
|
+
if (options.referenceImpedanceOhms !== undefined &&
|
|
83
|
+
(!Number.isFinite(options.referenceImpedanceOhms) || options.referenceImpedanceOhms <= 0)) {
|
|
84
|
+
throw new RangeError('Reference impedance must be positive and finite.');
|
|
85
|
+
}
|
|
86
|
+
if (options.appearance !== undefined) {
|
|
87
|
+
Theme.resolve(options.appearance);
|
|
88
|
+
}
|
|
89
|
+
if (options.interaction !== undefined) {
|
|
90
|
+
OptionsValidation.object(options.interaction, 'Interaction', ['zoom', 'cursor']);
|
|
91
|
+
for (const name of ['zoom', 'cursor']) {
|
|
92
|
+
const value = options.interaction[name];
|
|
93
|
+
if (value !== undefined) {
|
|
94
|
+
OptionsValidation.boolean(value, name);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (options.peripheralScales !== undefined) {
|
|
99
|
+
OptionsValidation.object(options.peripheralScales, 'Peripheral scales', ['visible']);
|
|
100
|
+
if (options.peripheralScales.visible !== undefined) {
|
|
101
|
+
OptionsValidation.boolean(options.peripheralScales.visible, 'Peripheral scale visibility');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (options.grid !== undefined) {
|
|
105
|
+
OptionsValidation.object(options.grid, 'Grid', [
|
|
106
|
+
'detail',
|
|
107
|
+
'labelsVisible',
|
|
108
|
+
'style',
|
|
109
|
+
'layers',
|
|
110
|
+
]);
|
|
111
|
+
if (options.grid.layers !== undefined) {
|
|
112
|
+
OptionsValidation.object(options.grid.layers, 'Grid layers', [
|
|
113
|
+
'resistance',
|
|
114
|
+
'reactance',
|
|
115
|
+
'conductance',
|
|
116
|
+
'susceptance',
|
|
117
|
+
]);
|
|
118
|
+
}
|
|
119
|
+
for (const name of ['resistance', 'reactance', 'conductance', 'susceptance']) {
|
|
120
|
+
const layer = options.grid.layers?.[name];
|
|
121
|
+
if (layer !== undefined) {
|
|
122
|
+
OptionsValidation.object(layer, 'Grid layer', [
|
|
123
|
+
'detail',
|
|
124
|
+
'labelsVisible',
|
|
125
|
+
'style',
|
|
126
|
+
'visible',
|
|
127
|
+
]);
|
|
128
|
+
if (layer.visible !== undefined) {
|
|
129
|
+
OptionsValidation.boolean(layer.visible, 'Grid visibility');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
for (const grid of [options.grid, ...Object.values(options.grid.layers ?? {})]) {
|
|
134
|
+
if (grid === undefined) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (grid.detail !== undefined) {
|
|
138
|
+
OptionsValidation.detail(grid.detail);
|
|
139
|
+
}
|
|
140
|
+
if (grid.labelsVisible !== undefined) {
|
|
141
|
+
OptionsValidation.boolean(grid.labelsVisible, 'Grid labels visibility');
|
|
142
|
+
}
|
|
143
|
+
if (grid.style !== undefined) {
|
|
144
|
+
OptionsValidation.gridStyle(grid.style);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (options.circles !== undefined) {
|
|
149
|
+
OptionsValidation.object(options.circles, 'Circles', ['q', 'vswr']);
|
|
150
|
+
for (const name of ['q', 'vswr']) {
|
|
151
|
+
const circle = options.circles[name];
|
|
152
|
+
if (circle === undefined) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
OptionsValidation.object(circle, 'Circle', ['visible', 'values', 'style']);
|
|
156
|
+
if (circle.visible !== undefined) {
|
|
157
|
+
OptionsValidation.boolean(circle.visible, 'Circle visibility');
|
|
158
|
+
}
|
|
159
|
+
if (circle.style !== undefined) {
|
|
160
|
+
OptionsValidation.circleStyle(circle.style);
|
|
161
|
+
}
|
|
162
|
+
if (circle.values !== undefined) {
|
|
163
|
+
OptionsValidation.circleValues(circle.values, name === 'vswr' ? 1 : 0);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
package/dist/lib/Smith.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import type { SmithOptions } from './options.js';
|
|
1
2
|
import type { SmithImageExportOptions } from './svg/export.js';
|
|
2
3
|
import type { SmithAppearance } from './appearance/types.js';
|
|
3
4
|
import type { TraceSamples } from './samples.js';
|
|
4
|
-
import type { ChartLayers, PeripheralScales } from './layers.js';
|
|
5
|
+
import type { ChartLayers, GridDetail, PeripheralScales } from './layers.js';
|
|
5
6
|
import type { SmithReading } from './rf/RfCalculations.js';
|
|
6
7
|
import type { TraceOptions, TraceUpdateOptions, TraceInfo, MarkerSnapshot, MarkerComparison } from './measurements.js';
|
|
7
8
|
export declare enum SmithEventType {
|
|
@@ -19,8 +20,8 @@ export type SmithEvent = {
|
|
|
19
20
|
data: MarkerSnapshot;
|
|
20
21
|
};
|
|
21
22
|
export declare class Smith {
|
|
22
|
-
private referenceOhms;
|
|
23
23
|
private theme;
|
|
24
|
+
private referenceOhms;
|
|
24
25
|
private readonly renderer;
|
|
25
26
|
private data;
|
|
26
27
|
private destroyed;
|
|
@@ -33,12 +34,18 @@ export declare class Smith {
|
|
|
33
34
|
private listeners;
|
|
34
35
|
readonly layers: ChartLayers;
|
|
35
36
|
readonly peripheralScales: PeripheralScales;
|
|
36
|
-
constructor(
|
|
37
|
+
constructor(options?: SmithOptions);
|
|
38
|
+
/** Apply a validated patch. Omitted fields retain their current settings. */
|
|
39
|
+
setOptions(options: SmithOptions): void;
|
|
40
|
+
private applyOptions;
|
|
37
41
|
/** Replace the appearance while retaining explicit layer styles and trace colors. */
|
|
38
42
|
setAppearance(appearance: SmithAppearance): void;
|
|
39
43
|
draw(target: string | HTMLElement): void;
|
|
44
|
+
/** Set the same detail level on all impedance and admittance grid layers. */
|
|
45
|
+
setGridDetail(detail: GridDetail): void;
|
|
40
46
|
/** Export the current mounted view as standalone SVG with its configured background. */
|
|
41
47
|
toSvg(options?: SmithImageExportOptions): string;
|
|
48
|
+
private validateExportOptions;
|
|
42
49
|
private exportLegend;
|
|
43
50
|
/** Export the mounted view as PNG, optionally with radial scales and a trace legend. */
|
|
44
51
|
toPng(options?: SmithImageExportOptions): Promise<Blob>;
|
|
@@ -50,8 +57,10 @@ export declare class Smith {
|
|
|
50
57
|
get cursorReading(): SmithReading;
|
|
51
58
|
/** Enable or disable wheel, double-click, and mouse/touch zoom/pan. Keeps the current view. */
|
|
52
59
|
setZoomEnabled(enabled: boolean): void;
|
|
60
|
+
/** Enable or disable the cursor overlay and readings independently of zoom and markers. */
|
|
61
|
+
setCursorEnabled(enabled: boolean): void;
|
|
53
62
|
resetView(): void;
|
|
54
|
-
/** Add a named trace
|
|
63
|
+
/** Add a named trace without markers. Use addMarker to create markers explicitly. Returns a chart-local, stable ID. */
|
|
55
64
|
addTrace(values: TraceSamples, options?: TraceOptions): string;
|
|
56
65
|
/** Detached metadata snapshots; IDs and marker display numbers survive removals. */
|
|
57
66
|
getTraces(): TraceInfo[];
|
package/dist/lib/Smith.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OptionsValidation } from './OptionsValidation.js';
|
|
1
2
|
import { MarkerLegend } from './svg/MarkerLegend.js';
|
|
2
3
|
import { ImageExporter } from './svg/ImageExporter.js';
|
|
3
4
|
import { Theme } from './appearance/Theme.js';
|
|
@@ -15,8 +16,8 @@ export var SmithEventType;
|
|
|
15
16
|
SmithEventType["MarkerDragEnd"] = "marker-drag-end";
|
|
16
17
|
})(SmithEventType || (SmithEventType = {}));
|
|
17
18
|
export class Smith {
|
|
18
|
-
referenceOhms;
|
|
19
19
|
theme;
|
|
20
|
+
referenceOhms;
|
|
20
21
|
renderer;
|
|
21
22
|
data = [];
|
|
22
23
|
destroyed = false;
|
|
@@ -29,12 +30,14 @@ export class Smith {
|
|
|
29
30
|
listeners = new Set();
|
|
30
31
|
layers;
|
|
31
32
|
peripheralScales;
|
|
32
|
-
constructor(
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
constructor(options = {}) {
|
|
34
|
+
OptionsValidation.chart(options);
|
|
35
|
+
this.referenceOhms =
|
|
36
|
+
options.referenceImpedanceOhms === undefined ? 50 : options.referenceImpedanceOhms;
|
|
37
|
+
if (!Number.isFinite(this.referenceOhms) || this.referenceOhms <= 0) {
|
|
35
38
|
throw new RangeError('Reference impedance must be positive and finite.');
|
|
36
39
|
}
|
|
37
|
-
this.theme = Theme.resolve(appearance);
|
|
40
|
+
this.theme = Theme.resolve(options.appearance);
|
|
38
41
|
this.renderer = new SvgChartRenderer((position) => {
|
|
39
42
|
this.emit({
|
|
40
43
|
type: SmithEventType.Cursor,
|
|
@@ -44,6 +47,76 @@ export class Smith {
|
|
|
44
47
|
this.renderer.setTheme(this.theme);
|
|
45
48
|
this.layers = this.renderer.layers;
|
|
46
49
|
this.peripheralScales = this.renderer.peripheralScales;
|
|
50
|
+
try {
|
|
51
|
+
this.applyOptions(options);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
// Invalid constructor options must not retain observers or event handlers.
|
|
55
|
+
this.renderer.destroy();
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Apply a validated patch. Omitted fields retain their current settings. */
|
|
60
|
+
setOptions(options) {
|
|
61
|
+
this.assertAlive();
|
|
62
|
+
OptionsValidation.chart(options);
|
|
63
|
+
// Renormalization can reject otherwise valid options for the loaded samples.
|
|
64
|
+
// Do it before presentation changes so rejection preserves the entire chart.
|
|
65
|
+
if (options.referenceImpedanceOhms !== undefined) {
|
|
66
|
+
this.renormalize(options.referenceImpedanceOhms);
|
|
67
|
+
}
|
|
68
|
+
if (options.appearance !== undefined) {
|
|
69
|
+
this.setAppearance(options.appearance);
|
|
70
|
+
}
|
|
71
|
+
this.applyOptions(options);
|
|
72
|
+
}
|
|
73
|
+
applyOptions(options) {
|
|
74
|
+
if (options.interaction?.zoom !== undefined) {
|
|
75
|
+
this.setZoomEnabled(options.interaction.zoom);
|
|
76
|
+
}
|
|
77
|
+
if (options.interaction?.cursor !== undefined) {
|
|
78
|
+
this.setCursorEnabled(options.interaction.cursor);
|
|
79
|
+
}
|
|
80
|
+
if (options.peripheralScales?.visible !== undefined) {
|
|
81
|
+
this.peripheralScales.setVisible(options.peripheralScales.visible);
|
|
82
|
+
}
|
|
83
|
+
for (const name of ['resistance', 'reactance', 'conductance', 'susceptance']) {
|
|
84
|
+
const layer = this.layers[name];
|
|
85
|
+
for (const settings of [options.grid, options.grid?.layers?.[name]]) {
|
|
86
|
+
if (settings === undefined) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (settings.detail !== undefined) {
|
|
90
|
+
layer.setDetail(settings.detail);
|
|
91
|
+
}
|
|
92
|
+
if (settings.labelsVisible !== undefined) {
|
|
93
|
+
layer.setLabelsVisible(settings.labelsVisible);
|
|
94
|
+
}
|
|
95
|
+
if (settings.style !== undefined) {
|
|
96
|
+
layer.setStyle(settings.style);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const visible = options.grid?.layers?.[name]?.visible;
|
|
100
|
+
if (visible !== undefined) {
|
|
101
|
+
layer.setVisible(visible);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (const name of ['q', 'vswr']) {
|
|
105
|
+
const settings = options.circles?.[name];
|
|
106
|
+
if (settings === undefined) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const layer = this.layers[name];
|
|
110
|
+
if (settings.values !== undefined) {
|
|
111
|
+
layer.setValues(settings.values);
|
|
112
|
+
}
|
|
113
|
+
if (settings.style !== undefined) {
|
|
114
|
+
layer.setStyle(settings.style);
|
|
115
|
+
}
|
|
116
|
+
if (settings.visible !== undefined) {
|
|
117
|
+
layer.setVisible(settings.visible);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
47
120
|
}
|
|
48
121
|
/** Replace the appearance while retaining explicit layer styles and trace colors. */
|
|
49
122
|
setAppearance(appearance) {
|
|
@@ -62,9 +135,17 @@ export class Smith {
|
|
|
62
135
|
this.assertAlive();
|
|
63
136
|
this.renderer.draw(target);
|
|
64
137
|
}
|
|
138
|
+
/** Set the same detail level on all impedance and admittance grid layers. */
|
|
139
|
+
setGridDetail(detail) {
|
|
140
|
+
this.assertAlive();
|
|
141
|
+
for (const name of ['resistance', 'reactance', 'conductance', 'susceptance']) {
|
|
142
|
+
this.layers[name].setDetail(detail);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
65
145
|
/** Export the current mounted view as standalone SVG with its configured background. */
|
|
66
146
|
toSvg(options) {
|
|
67
147
|
this.assertAlive();
|
|
148
|
+
this.validateExportOptions(options);
|
|
68
149
|
const source = this.renderer.toSvg();
|
|
69
150
|
if (options === undefined) {
|
|
70
151
|
return source;
|
|
@@ -75,6 +156,11 @@ export class Smith {
|
|
|
75
156
|
}
|
|
76
157
|
return ImageExporter.svg(sources, options, this.exportLegend(options));
|
|
77
158
|
}
|
|
159
|
+
validateExportOptions(options) {
|
|
160
|
+
if (options?.scaleReadout !== undefined && !options.scales) {
|
|
161
|
+
throw new TypeError('Scale readout requires included radial scales.');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
78
164
|
exportLegend(options) {
|
|
79
165
|
const traces = this.getTraces();
|
|
80
166
|
return {
|
|
@@ -90,6 +176,8 @@ export class Smith {
|
|
|
90
176
|
}
|
|
91
177
|
/** Export the mounted view as PNG, optionally with radial scales and a trace legend. */
|
|
92
178
|
async toPng(options = {}) {
|
|
179
|
+
this.assertAlive();
|
|
180
|
+
this.validateExportOptions(options);
|
|
93
181
|
const sources = [this.toSvg()];
|
|
94
182
|
if (options.scales) {
|
|
95
183
|
sources.push(options.scales.toSvg({ readout: options.scaleReadout }));
|
|
@@ -124,9 +212,6 @@ export class Smith {
|
|
|
124
212
|
return;
|
|
125
213
|
}
|
|
126
214
|
this.renderer.setMarkerDragging(isDragging);
|
|
127
|
-
if (isDragging) {
|
|
128
|
-
this.emit({ type: SmithEventType.Cursor, data: undefined });
|
|
129
|
-
}
|
|
130
215
|
}
|
|
131
216
|
/** Last cursor position. Use cursor events to detect pointer leave. */
|
|
132
217
|
get cursorReading() {
|
|
@@ -137,11 +222,16 @@ export class Smith {
|
|
|
137
222
|
this.assertAlive();
|
|
138
223
|
this.renderer.setZoomEnabled(enabled);
|
|
139
224
|
}
|
|
225
|
+
/** Enable or disable the cursor overlay and readings independently of zoom and markers. */
|
|
226
|
+
setCursorEnabled(enabled) {
|
|
227
|
+
this.assertAlive();
|
|
228
|
+
this.renderer.setCursorEnabled(enabled);
|
|
229
|
+
}
|
|
140
230
|
resetView() {
|
|
141
231
|
this.assertAlive();
|
|
142
232
|
this.renderer.resetView();
|
|
143
233
|
}
|
|
144
|
-
/** Add a named trace
|
|
234
|
+
/** Add a named trace without markers. Use addMarker to create markers explicitly. Returns a chart-local, stable ID. */
|
|
145
235
|
addTrace(values, options = {}) {
|
|
146
236
|
this.assertAlive();
|
|
147
237
|
this.validateTraceOptions(options);
|
|
@@ -334,7 +424,6 @@ export class Smith {
|
|
|
334
424
|
this.referenceOhms = referenceImpedanceOhms;
|
|
335
425
|
this.data.forEach((data, index) => data.update(samples[index], 'sample-index'));
|
|
336
426
|
this.renderer.hideCursor();
|
|
337
|
-
this.emit({ type: SmithEventType.Cursor, data: undefined });
|
|
338
427
|
}
|
|
339
428
|
clearTraces() {
|
|
340
429
|
this.assertAlive();
|
|
@@ -373,7 +462,6 @@ export class Smith {
|
|
|
373
462
|
this.emit({ type: SmithEventType.Marker, data: snapshot });
|
|
374
463
|
}
|
|
375
464
|
});
|
|
376
|
-
data.addMarker();
|
|
377
465
|
return data;
|
|
378
466
|
}
|
|
379
467
|
/** Subscribe to chart events. The returned function removes this subscription. */
|
|
@@ -6,6 +6,11 @@ export class Theme {
|
|
|
6
6
|
if (!appearance || typeof appearance !== 'object' || Array.isArray(appearance)) {
|
|
7
7
|
throw new TypeError('Appearance must be an object.');
|
|
8
8
|
}
|
|
9
|
+
for (const key of Object.keys(appearance)) {
|
|
10
|
+
if (key !== 'theme' && key !== 'overrides') {
|
|
11
|
+
throw new TypeError(`Unknown appearance option: ${key}.`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
9
14
|
const name = appearance.theme === undefined ? 'light' : appearance.theme;
|
|
10
15
|
if (name !== 'light' && name !== 'dark') {
|
|
11
16
|
throw new TypeError('Theme must be light or dark.');
|
|
@@ -9,7 +9,7 @@ export declare class CircleLayerControl extends LayerControl implements CircleLa
|
|
|
9
9
|
constructor(layer: ConstQCircles | ConstSwrCircles, minimum: number, assertAlive: () => void);
|
|
10
10
|
setVisible(visible: boolean): void;
|
|
11
11
|
setStyle(style: Partial<CircleStyle>): void;
|
|
12
|
+
setValues(values: readonly number[]): void;
|
|
12
13
|
addValue(value: number): void;
|
|
13
14
|
removeValue(value: number): void;
|
|
14
|
-
private validateValue;
|
|
15
15
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OptionsValidation } from '../OptionsValidation.js';
|
|
1
2
|
import { LayerControl } from './LayerControl.js';
|
|
2
3
|
/** Exposes Q/VSWR circle controls without exposing the SVG renderer. */
|
|
3
4
|
export class CircleLayerControl extends LayerControl {
|
|
@@ -10,11 +11,12 @@ export class CircleLayerControl extends LayerControl {
|
|
|
10
11
|
}
|
|
11
12
|
setVisible(visible) {
|
|
12
13
|
this.assertAlive();
|
|
14
|
+
OptionsValidation.boolean(visible, 'Circle visibility');
|
|
13
15
|
this.layer.visibility(visible);
|
|
14
16
|
}
|
|
15
17
|
setStyle(style) {
|
|
16
18
|
this.assertAlive();
|
|
17
|
-
|
|
19
|
+
OptionsValidation.circleStyle(style);
|
|
18
20
|
if (style.stroke !== undefined) {
|
|
19
21
|
this.layer.Stroke = style.stroke;
|
|
20
22
|
}
|
|
@@ -22,19 +24,19 @@ export class CircleLayerControl extends LayerControl {
|
|
|
22
24
|
this.layer.StrokeWidth = String(style.strokeWidth);
|
|
23
25
|
}
|
|
24
26
|
}
|
|
27
|
+
setValues(values) {
|
|
28
|
+
this.assertAlive();
|
|
29
|
+
OptionsValidation.circleValues(values, this.minimum);
|
|
30
|
+
this.layer.setValues(values);
|
|
31
|
+
}
|
|
25
32
|
addValue(value) {
|
|
26
33
|
this.assertAlive();
|
|
27
|
-
|
|
34
|
+
OptionsValidation.circleValue(value, this.minimum);
|
|
28
35
|
this.layer.append(value);
|
|
29
36
|
}
|
|
30
37
|
removeValue(value) {
|
|
31
38
|
this.assertAlive();
|
|
32
|
-
|
|
39
|
+
OptionsValidation.circleValue(value, this.minimum);
|
|
33
40
|
this.layer.remove(value);
|
|
34
41
|
}
|
|
35
|
-
validateValue(value) {
|
|
36
|
-
if (!Number.isFinite(value) || value < this.minimum || value <= 0) {
|
|
37
|
-
throw new RangeError(`Circle value must be finite, positive, and at least ${this.minimum}.`);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
42
|
}
|
|
@@ -74,6 +74,11 @@ export class ConstQCircles {
|
|
|
74
74
|
this.circles.push(Q);
|
|
75
75
|
this.drawConstQCircle(Q);
|
|
76
76
|
}
|
|
77
|
+
setValues(values) {
|
|
78
|
+
this.circles = [...new Set(values)];
|
|
79
|
+
this.container.Element.selectAll('*').remove();
|
|
80
|
+
this.drawConstQCircles(this.circles);
|
|
81
|
+
}
|
|
77
82
|
remove(Q) {
|
|
78
83
|
const index = this.circles.indexOf(Q);
|
|
79
84
|
if (index === -1) {
|
|
@@ -73,6 +73,11 @@ export class ConstSwrCircles {
|
|
|
73
73
|
this.circles.push(swr);
|
|
74
74
|
this.drawConstSwrCircle(swr);
|
|
75
75
|
}
|
|
76
|
+
setValues(values) {
|
|
77
|
+
this.circles = [...new Set(values)];
|
|
78
|
+
this.container.Element.selectAll('*').remove();
|
|
79
|
+
this.drawConstSwrCircles(this.circles);
|
|
80
|
+
}
|
|
76
81
|
remove(swr) {
|
|
77
82
|
const index = this.circles.indexOf(swr);
|
|
78
83
|
if (index === -1) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OptionsValidation } from '../OptionsValidation.js';
|
|
1
2
|
import { LayerControl } from './LayerControl.js';
|
|
2
3
|
/** Exposes grid controls without exposing the SVG renderer. */
|
|
3
4
|
export class GridLayerControl extends LayerControl {
|
|
@@ -10,25 +11,25 @@ export class GridLayerControl extends LayerControl {
|
|
|
10
11
|
}
|
|
11
12
|
setVisible(visible) {
|
|
12
13
|
this.assertAlive();
|
|
14
|
+
OptionsValidation.boolean(visible, 'Grid visibility');
|
|
13
15
|
this.layer.visibility(visible);
|
|
14
16
|
this.changed();
|
|
15
17
|
}
|
|
16
18
|
setLabelsVisible(visible) {
|
|
17
19
|
this.assertAlive();
|
|
20
|
+
OptionsValidation.boolean(visible, 'Grid visibility');
|
|
18
21
|
this.layer.setLabelsVisible(visible);
|
|
19
22
|
this.changed();
|
|
20
23
|
}
|
|
21
24
|
setDetail(detail) {
|
|
22
25
|
this.assertAlive();
|
|
23
|
-
|
|
24
|
-
throw new RangeError('Grid detail must be basic, standard, or detailed.');
|
|
25
|
-
}
|
|
26
|
+
OptionsValidation.detail(detail);
|
|
26
27
|
this.layer.setDetail(detail);
|
|
27
28
|
this.changed();
|
|
28
29
|
}
|
|
29
30
|
setStyle(style) {
|
|
30
31
|
this.assertAlive();
|
|
31
|
-
|
|
32
|
+
OptionsValidation.gridStyle(style);
|
|
32
33
|
const properties = {
|
|
33
34
|
stroke: 'Stroke',
|
|
34
35
|
majorWidth: 'MajorWidth',
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
/** Shared lifecycle
|
|
1
|
+
/** Shared lifecycle guard for chart layer adapters. */
|
|
2
2
|
export declare abstract class LayerControl {
|
|
3
3
|
protected readonly assertAlive: () => void;
|
|
4
4
|
protected constructor(assertAlive: () => void);
|
|
5
|
-
protected validateLengths<T>(style: T, keys: readonly (keyof T)[]): void;
|
|
6
5
|
}
|
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
/** Shared lifecycle
|
|
1
|
+
/** Shared lifecycle guard for chart layer adapters. */
|
|
2
2
|
export class LayerControl {
|
|
3
3
|
assertAlive;
|
|
4
4
|
constructor(assertAlive) {
|
|
5
5
|
this.assertAlive = assertAlive;
|
|
6
6
|
}
|
|
7
|
-
validateLengths(style, keys) {
|
|
8
|
-
for (const key of keys) {
|
|
9
|
-
const value = style[key];
|
|
10
|
-
if (value !== undefined &&
|
|
11
|
-
(typeof value !== 'number' || !Number.isFinite(value) || value <= 0)) {
|
|
12
|
-
throw new RangeError(`${String(key)} must be a positive finite number.`);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
7
|
}
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -14,3 +14,4 @@ export { SmithFormatter } from './SmithFormatter.js';
|
|
|
14
14
|
export type { ReactiveComponent } from './rf/RfCalculations.js';
|
|
15
15
|
export type { SmithAppearance, SmithTheme, SmithThemeOverrides } from './appearance/types.js';
|
|
16
16
|
export type { MarkerLegendField, MarkerLegendOptions, ImageExportOptions, SmithImageExportOptions, ScaleImageExportOptions, ScaleExportReadout, } from './svg/export.js';
|
|
17
|
+
export type { SmithOptions, GridOptions, GridLayerOptions, CircleLayerOptions, CircleOptions, InteractionOptions, PeripheralScalesOptions, } from './options.js';
|
package/dist/lib/layers.d.ts
CHANGED
|
@@ -21,13 +21,15 @@ export interface GridLayer {
|
|
|
21
21
|
setVisible(visible: boolean): void;
|
|
22
22
|
/** Show or hide labels independently of grid lines. Default: true. */
|
|
23
23
|
setLabelsVisible(visible: boolean): void;
|
|
24
|
-
/** Default:
|
|
24
|
+
/** Default: standard. Basic shows only the principal normalized values. */
|
|
25
25
|
setDetail(detail: GridDetail): void;
|
|
26
26
|
setStyle(style: Partial<GridStyle>): void;
|
|
27
27
|
}
|
|
28
28
|
export interface CircleLayer {
|
|
29
29
|
setVisible(visible: boolean): void;
|
|
30
30
|
setStyle(style: Partial<CircleStyle>): void;
|
|
31
|
+
/** Replace the complete set after validation; duplicates are ignored and empty clears it. */
|
|
32
|
+
setValues(values: readonly number[]): void;
|
|
31
33
|
addValue(value: number): void;
|
|
32
34
|
removeValue(value: number): void;
|
|
33
35
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { SmithAppearance } from './appearance/types.js';
|
|
2
|
+
import type { CircleStyle, GridDetail, GridStyle } from './layers.js';
|
|
3
|
+
/** Settings for an individual impedance/admittance grid layer. */
|
|
4
|
+
export interface GridLayerOptions {
|
|
5
|
+
visible?: boolean;
|
|
6
|
+
detail?: GridDetail;
|
|
7
|
+
labelsVisible?: boolean;
|
|
8
|
+
style?: Partial<GridStyle>;
|
|
9
|
+
}
|
|
10
|
+
/** Shared settings for all four grids, followed by per-layer overrides. */
|
|
11
|
+
export interface GridOptions {
|
|
12
|
+
/** Default: standard. */
|
|
13
|
+
detail?: GridDetail;
|
|
14
|
+
/** Default: true. */
|
|
15
|
+
labelsVisible?: boolean;
|
|
16
|
+
style?: Partial<GridStyle>;
|
|
17
|
+
layers?: {
|
|
18
|
+
resistance?: GridLayerOptions;
|
|
19
|
+
reactance?: GridLayerOptions;
|
|
20
|
+
conductance?: GridLayerOptions;
|
|
21
|
+
susceptance?: GridLayerOptions;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export interface CircleLayerOptions {
|
|
25
|
+
/** Default: false. */
|
|
26
|
+
visible?: boolean;
|
|
27
|
+
style?: Partial<CircleStyle>;
|
|
28
|
+
/** Replace the default values. Empty hides all circles; duplicate values are ignored. */
|
|
29
|
+
values?: readonly number[];
|
|
30
|
+
}
|
|
31
|
+
export interface CircleOptions {
|
|
32
|
+
q?: CircleLayerOptions;
|
|
33
|
+
vswr?: CircleLayerOptions;
|
|
34
|
+
}
|
|
35
|
+
export interface InteractionOptions {
|
|
36
|
+
/** Enable wheel/pan/pinch gestures. Default: false. */
|
|
37
|
+
zoom?: boolean;
|
|
38
|
+
/** Enable cursor overlay and pointer readings. Default: false. */
|
|
39
|
+
cursor?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface PeripheralScalesOptions {
|
|
42
|
+
/** Show the peripheral rulers as one group. Default: false. */
|
|
43
|
+
visible?: boolean;
|
|
44
|
+
}
|
|
45
|
+
export interface SmithOptions {
|
|
46
|
+
/** Positive real reference impedance in ohms. Default: 50. */
|
|
47
|
+
referenceImpedanceOhms?: number;
|
|
48
|
+
/** Default: the light preset. Use overrides for fonts, colors, and marker/cursor styling. */
|
|
49
|
+
appearance?: SmithAppearance;
|
|
50
|
+
interaction?: InteractionOptions;
|
|
51
|
+
grid?: GridOptions;
|
|
52
|
+
circles?: CircleOptions;
|
|
53
|
+
peripheralScales?: PeripheralScalesOptions;
|
|
54
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -13,6 +13,7 @@ export declare class SvgChartRenderer {
|
|
|
13
13
|
private readonly defaultTransform;
|
|
14
14
|
private transform;
|
|
15
15
|
private zoomEnabled;
|
|
16
|
+
private cursorEnabled;
|
|
16
17
|
private applyingView;
|
|
17
18
|
private zoomBehavior;
|
|
18
19
|
private svg;
|
|
@@ -50,6 +51,8 @@ export declare class SvgChartRenderer {
|
|
|
50
51
|
private cursorMove;
|
|
51
52
|
setMarkerDragging(dragging: boolean): void;
|
|
52
53
|
hideCursor(): void;
|
|
54
|
+
/** Disable tracking and cancel queued readings; enabling waits for the next pointer move. */
|
|
55
|
+
setCursorEnabled(enabled: boolean): void;
|
|
53
56
|
private initCursor;
|
|
54
57
|
get cursorPosition(): Complex;
|
|
55
58
|
private initializeZoom;
|
|
@@ -28,7 +28,8 @@ export class SvgChartRenderer {
|
|
|
28
28
|
scalers;
|
|
29
29
|
defaultTransform = d3.zoomIdentity.translate(62.5, 62.5).scale(0.75);
|
|
30
30
|
transform = d3.zoomIdentity;
|
|
31
|
-
zoomEnabled =
|
|
31
|
+
zoomEnabled = false;
|
|
32
|
+
cursorEnabled = false;
|
|
32
33
|
applyingView = false;
|
|
33
34
|
zoomBehavior = d3.zoom();
|
|
34
35
|
svg;
|
|
@@ -63,25 +64,25 @@ export class SvgChartRenderer {
|
|
|
63
64
|
this.constResistance = new ConstResistance({
|
|
64
65
|
data: gridData,
|
|
65
66
|
scaler: this.scalers.default,
|
|
66
|
-
detail: '
|
|
67
|
+
detail: 'standard',
|
|
67
68
|
});
|
|
68
69
|
this.constResistance.show();
|
|
69
70
|
this.constReactance = new ConstReactance({
|
|
70
71
|
data: gridData,
|
|
71
72
|
scaler: this.scalers.default,
|
|
72
|
-
detail: '
|
|
73
|
+
detail: 'standard',
|
|
73
74
|
});
|
|
74
75
|
this.constReactance.show();
|
|
75
76
|
this.constConductance = new ConstConductance({
|
|
76
77
|
data: gridData,
|
|
77
78
|
scaler: this.scalers.default,
|
|
78
|
-
detail: '
|
|
79
|
+
detail: 'standard',
|
|
79
80
|
});
|
|
80
81
|
this.constConductance.hide();
|
|
81
82
|
this.constSusceptance = new ConstSusceptance({
|
|
82
83
|
data: gridData,
|
|
83
84
|
scaler: this.scalers.default,
|
|
84
|
-
detail: '
|
|
85
|
+
detail: 'standard',
|
|
85
86
|
});
|
|
86
87
|
this.constSusceptance.hide();
|
|
87
88
|
this.constQCircles = new ConstQCircles(this.scalers.default);
|
|
@@ -135,6 +136,9 @@ export class SvgChartRenderer {
|
|
|
135
136
|
this.peripheralScales = {
|
|
136
137
|
setVisible: (visible) => {
|
|
137
138
|
assertAlive();
|
|
139
|
+
if (typeof visible !== 'boolean') {
|
|
140
|
+
throw new TypeError('Peripheral scale visibility must be a boolean.');
|
|
141
|
+
}
|
|
138
142
|
if (visible) {
|
|
139
143
|
this.peripheralScaleRenderer.show();
|
|
140
144
|
}
|
|
@@ -148,6 +152,7 @@ export class SvgChartRenderer {
|
|
|
148
152
|
this.peripheralScaleRenderer.update(gamma);
|
|
149
153
|
},
|
|
150
154
|
};
|
|
155
|
+
this.peripheralScaleRenderer.hide();
|
|
151
156
|
this.initializeZoom();
|
|
152
157
|
this.resizeObserver = new ResizeObserver(() => this.updateViewportScale());
|
|
153
158
|
this.resizeObserver.observe(this.svg.Node);
|
|
@@ -216,7 +221,7 @@ export class SvgChartRenderer {
|
|
|
216
221
|
return { default: impedance, impedance, admittance };
|
|
217
222
|
}
|
|
218
223
|
cursorMove(p) {
|
|
219
|
-
if (this.markerDragging) {
|
|
224
|
+
if (!this.cursorEnabled || this.markerDragging) {
|
|
220
225
|
return;
|
|
221
226
|
}
|
|
222
227
|
this.cursor.Position = Complex.from(this.scalers.default.pointInvert(p));
|
|
@@ -226,7 +231,7 @@ export class SvgChartRenderer {
|
|
|
226
231
|
if (dragging) {
|
|
227
232
|
this.cursorBeforeMarkerDrag = this.svg.Node.style.getPropertyValue('cursor') || null;
|
|
228
233
|
this.svg.Element.style('cursor', 'grabbing').style('--smithkit-marker-cursor', 'grabbing');
|
|
229
|
-
this.
|
|
234
|
+
this.hideCursor();
|
|
230
235
|
}
|
|
231
236
|
else {
|
|
232
237
|
this.svg.Element.style('cursor', () => this.cursorBeforeMarkerDrag).style('--smithkit-marker-cursor', null);
|
|
@@ -235,6 +240,24 @@ export class SvgChartRenderer {
|
|
|
235
240
|
}
|
|
236
241
|
hideCursor() {
|
|
237
242
|
this.cursor.hide();
|
|
243
|
+
if (this.cursorEnabled) {
|
|
244
|
+
this.onCursorChange?.(undefined);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/** Disable tracking and cancel queued readings; enabling waits for the next pointer move. */
|
|
248
|
+
setCursorEnabled(enabled) {
|
|
249
|
+
this.assertAlive();
|
|
250
|
+
if (typeof enabled !== 'boolean') {
|
|
251
|
+
throw new TypeError('Cursor enabled must be a boolean.');
|
|
252
|
+
}
|
|
253
|
+
if (this.cursorEnabled === enabled) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
this.cursorEnabled = enabled;
|
|
257
|
+
if (!enabled) {
|
|
258
|
+
this.cursor.hide();
|
|
259
|
+
this.onCursorChange?.(undefined);
|
|
260
|
+
}
|
|
238
261
|
}
|
|
239
262
|
initCursor() {
|
|
240
263
|
const cursor = new SmithCursor(this.scalers.default);
|
|
@@ -314,8 +337,7 @@ export class SvgChartRenderer {
|
|
|
314
337
|
this.cursorMove(d3.pointer(event));
|
|
315
338
|
})
|
|
316
339
|
.on('pointerleave.smithkit', () => {
|
|
317
|
-
this.
|
|
318
|
-
this.onCursorChange?.(undefined);
|
|
340
|
+
this.hideCursor();
|
|
319
341
|
});
|
|
320
342
|
return shape;
|
|
321
343
|
}
|
package/dist/lib/svg/export.d.ts
CHANGED
|
@@ -31,6 +31,6 @@ export interface SmithImageExportOptions extends ImageExportOptions {
|
|
|
31
31
|
markerLegend?: boolean | MarkerLegendOptions;
|
|
32
32
|
/** Include mounted radial scales centered below the chart, preserving their current layout/readings. */
|
|
33
33
|
scales?: SmithScales;
|
|
34
|
-
/** Override readings in the included radial scales without changing the live view. */
|
|
34
|
+
/** Override readings in the included radial scales without changing the live view. Requires scales. */
|
|
35
35
|
scaleReadout?: ScaleExportReadout;
|
|
36
36
|
}
|