smithkit 0.2.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 +318 -25
- package/dist/lib/OptionsValidation.d.ts +16 -0
- package/dist/lib/OptionsValidation.js +168 -0
- package/dist/lib/Smith.d.ts +24 -39
- package/dist/lib/Smith.js +175 -287
- package/dist/lib/appearance/SvgTheme.d.ts +6 -0
- package/dist/lib/appearance/SvgTheme.js +21 -0
- package/dist/lib/appearance/Theme.d.ts +8 -0
- package/dist/lib/appearance/Theme.js +125 -0
- package/dist/lib/appearance/types.d.ts +55 -0
- package/dist/lib/appearance/types.js +1 -0
- package/dist/lib/grid/CircleLayerControl.d.ts +1 -1
- package/dist/lib/grid/CircleLayerControl.js +10 -8
- package/dist/lib/grid/ConstCircles.d.ts +0 -3
- package/dist/lib/grid/ConstCircles.js +6 -20
- package/dist/lib/grid/ConstQCircles.d.ts +1 -0
- package/dist/lib/grid/ConstQCircles.js +6 -1
- package/dist/lib/grid/ConstSwrCircles.d.ts +1 -0
- package/dist/lib/grid/ConstSwrCircles.js +6 -1
- package/dist/lib/grid/GridDefinitions.d.ts +7 -1
- package/dist/lib/grid/GridDefinitions.js +29 -0
- package/dist/lib/grid/GridLayerControl.d.ts +5 -4
- package/dist/lib/grid/GridLayerControl.js +13 -3
- package/dist/lib/grid/LayerControl.d.ts +1 -2
- package/dist/lib/grid/LayerControl.js +1 -10
- package/dist/lib/grid/SmithGridLayer.d.ts +7 -1
- package/dist/lib/grid/SmithGridLayer.js +35 -5
- package/dist/lib/index.d.ts +4 -1
- package/dist/lib/interaction/SmithCursor.d.ts +0 -17
- package/dist/lib/interaction/SmithCursor.js +6 -14
- package/dist/lib/layers.d.ts +7 -1
- package/dist/lib/options.d.ts +54 -0
- package/dist/lib/options.js +1 -0
- package/dist/lib/rendering/SvgChartRenderer.d.ts +69 -0
- package/dist/lib/rendering/SvgChartRenderer.js +364 -0
- package/dist/lib/scales/SmithPeripheralScales.js +11 -11
- package/dist/lib/scales/SmithScales.d.ts +10 -3
- package/dist/lib/scales/SmithScales.js +73 -39
- package/dist/lib/svg/ImageExporter.d.ts +18 -0
- package/dist/lib/svg/ImageExporter.js +211 -0
- package/dist/lib/svg/MarkerLegend.d.ts +12 -0
- package/dist/lib/svg/MarkerLegend.js +63 -0
- package/dist/lib/svg/SvgExporter.d.ts +6 -1
- package/dist/lib/svg/SvgExporter.js +50 -5
- package/dist/lib/svg/export.d.ts +36 -0
- package/dist/lib/svg/export.js +1 -0
- package/dist/lib/traces/SmithMarker.js +8 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,12 +3,27 @@
|
|
|
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).
|
|
14
|
+
|
|
15
|
+
Upgrading from 0.2.x? Version 0.3.0 replaces `GridLayer.setMinorVisible()` with
|
|
16
|
+
`setDetail('basic' | 'standard' | 'detailed')`. Replace `false` with `'standard'`
|
|
17
|
+
and `true` with `'detailed'`; `'basic'` shows only the principal circles.
|
|
7
18
|
|
|
8
19
|
Upgrading from 0.1.x? Version 0.2.0 groups calculation, parsing, formatting, and
|
|
9
20
|
comparison functions into classes. See the [migration guide](https://github.com/rosek86/smithkit/blob/v0.2.0/docs/migration-0.2.md)
|
|
10
21
|
for the changed imports.
|
|
11
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
|
+
|
|
12
27
|
## Features
|
|
13
28
|
|
|
14
29
|
- Labeled resistance, reactance, conductance, and susceptance grids.
|
|
@@ -20,7 +35,7 @@ for the changed imports.
|
|
|
20
35
|
- Twelve parameter scales mounted independently of chart zoom.
|
|
21
36
|
- Four peripheral rulers for phase and electrical length.
|
|
22
37
|
- One-port Touchstone 1.x import with RI, MA, and DB representations.
|
|
23
|
-
- Standalone SVG export of the
|
|
38
|
+
- Standalone SVG and configurable PNG export of the chart and radial scales.
|
|
24
39
|
- ESM modules and TypeScript declarations.
|
|
25
40
|
|
|
26
41
|
## Installation
|
|
@@ -50,7 +65,7 @@ Give the container an explicit size and run the code after it is mounted:
|
|
|
50
65
|
```ts
|
|
51
66
|
import { Smith } from 'smithkit';
|
|
52
67
|
|
|
53
|
-
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.
|
|
54
69
|
chart.draw('#smith');
|
|
55
70
|
const traceId = chart.addTrace(
|
|
56
71
|
[
|
|
@@ -67,33 +82,139 @@ coefficient Γ as `[real, imaginary]`. Samples are copied and validated; readonl
|
|
|
67
82
|
must be finite and non-negative; both coordinates must be finite. A trace must
|
|
68
83
|
contain at least one sample.
|
|
69
84
|
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
72
88
|
and their focus indicators stay above grid labels and sample points. `chart.resetView()` restores the initial view, including peripheral rulers.
|
|
73
89
|
|
|
74
90
|
Construct `Smith` only in a browser, such as your framework's mount hook. Importing
|
|
75
91
|
the package and using its calculation or parsing methods does not require a DOM.
|
|
76
92
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
page scrolling. Disabling during an active gesture freezes the view while that
|
|
83
|
-
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:
|
|
84
98
|
|
|
85
99
|
```ts
|
|
86
100
|
import { Smith } from 'smithkit';
|
|
87
101
|
|
|
88
|
-
const chart = new Smith(
|
|
89
|
-
chart.setZoomEnabled(false);
|
|
102
|
+
const chart = new Smith({ interaction: { zoom: true, cursor: true } });
|
|
90
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.
|
|
91
107
|
```
|
|
92
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
|
+
|
|
93
120
|
`draw(selector | HTMLElement)` moves the existing SVG when called again. A missing
|
|
94
121
|
container throws. Call `chart.destroy()` on unmount; it removes the SVG, releases
|
|
95
122
|
event handlers, and cancels queued notifications. Repeated destruction is safe.
|
|
96
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
|
+
|
|
97
218
|
### Keyboard and touch markers
|
|
98
219
|
|
|
99
220
|
Use Tab/Shift+Tab to focus a marker. Arrow keys move through samples in input order;
|
|
@@ -120,7 +241,7 @@ there. They survive renaming, hiding, updating samples, and removing other items
|
|
|
120
241
|
They are not persistent across chart instances or page reloads.
|
|
121
242
|
|
|
122
243
|
```ts
|
|
123
|
-
const markerA = chart.
|
|
244
|
+
const markerA = chart.addMarker(traceId)!; // First sample.
|
|
124
245
|
const markerB = chart.addMarker(traceId, 2)!; // Zero-based sample index.
|
|
125
246
|
|
|
126
247
|
chart.setTraceOptions(traceId, { name: 'Tuned antenna', visible: true });
|
|
@@ -137,7 +258,7 @@ chart.removeMarker(markerB);
|
|
|
137
258
|
|
|
138
259
|
| Method | Result / behavior |
|
|
139
260
|
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
140
|
-
| `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`. |
|
|
141
262
|
| `getTraces()` | Detached `TraceInfo` snapshots: ID, name, color, visibility, sample count, and marker metadata. |
|
|
142
263
|
| `setTraceOptions(id, options)` | Updates the supplied options only. Invalid options throw before any change. |
|
|
143
264
|
| `updateTrace(id, samples, options?)` | Replaces samples and preserves identity, appearance, and markers. Empty/invalid input throws without changing the trace. |
|
|
@@ -236,8 +357,8 @@ queued `Marker` event and do not emit drag lifecycle events.
|
|
|
236
357
|
Cursor movement is suspended during marker dragging. Drag start/end events fire
|
|
237
358
|
synchronously; marker position notifications are queued and report the latest
|
|
238
359
|
snapshot at delivery. Before delivery, repeated updates to the same marker coalesce
|
|
239
|
-
into one notification; different markers have independent notifications.
|
|
240
|
-
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
|
|
241
362
|
position events.
|
|
242
363
|
Trace metadata changes do not emit marker events; refresh metadata after mutations.
|
|
243
364
|
Destruction suppresses application callbacks.
|
|
@@ -344,6 +465,69 @@ Zero frequency, zero reactance, and unrepresentable results return `undefined`.
|
|
|
344
465
|
Non-finite reactance or negative/non-finite frequency throws `RangeError`.
|
|
345
466
|
This is a single-frequency equivalence, not a fitted circuit model.
|
|
346
467
|
|
|
468
|
+
## Appearance and themes
|
|
469
|
+
|
|
470
|
+
`Smith` and `SmithScales` accept the same appearance configuration. The default
|
|
471
|
+
is the light preset with a transparent background. The dark preset includes a
|
|
472
|
+
solid dark background so its labels remain readable when embedded or exported.
|
|
473
|
+
|
|
474
|
+
```ts
|
|
475
|
+
import { Smith, SmithScales } from 'smithkit';
|
|
476
|
+
import type { SmithAppearance } from 'smithkit';
|
|
477
|
+
|
|
478
|
+
const appearance: SmithAppearance = {
|
|
479
|
+
theme: 'dark',
|
|
480
|
+
overrides: {
|
|
481
|
+
fontFamily: 'Arial, sans-serif',
|
|
482
|
+
grid: { majorWidth: 1.2 },
|
|
483
|
+
cursor: { impedanceColor: '#fbbf24' },
|
|
484
|
+
marker: { focusColor: '#38bdf8' },
|
|
485
|
+
traceColors: ['#38bdf8', '#fb923c', '#a78bfa'],
|
|
486
|
+
},
|
|
487
|
+
};
|
|
488
|
+
const chart = new Smith({ referenceImpedanceOhms: 50, appearance });
|
|
489
|
+
const scales = new SmithScales(appearance);
|
|
490
|
+
chart.draw('#smith');
|
|
491
|
+
scales.draw('#scales');
|
|
492
|
+
|
|
493
|
+
// Update both independent components using the same configuration.
|
|
494
|
+
chart.setAppearance({ theme: 'light' });
|
|
495
|
+
scales.setAppearance({ theme: 'light' });
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
Each `setAppearance` call replaces the previous preset and overrides; omitted
|
|
499
|
+
settings return to the selected preset, or light when `theme` is omitted.
|
|
500
|
+
Nested overrides are partial. Appearance is scoped to each component and does not
|
|
501
|
+
change the surrounding application's CSS or other chart instances.
|
|
502
|
+
|
|
503
|
+
| Override | Settings |
|
|
504
|
+
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
505
|
+
| `background`, `fontFamily` | Component background and shared font family. |
|
|
506
|
+
| `grid` | `stroke`, `textColor`, `majorWidth`, `minorWidth`, `fontSize`. |
|
|
507
|
+
| `boundary` | `stroke`, `width` for the Smith chart boundary. |
|
|
508
|
+
| `circles` | `q`, `vswr` colors and shared `width`. |
|
|
509
|
+
| `scales` | `stroke`, `boundaryColor`, `textColor`, `valueColor`, `indicatorColor`, `indicatorOutline`; `fontSize` for radial ticks, `titleFontSize`, `valueFontSize`, and `peripheralFontSize`. |
|
|
510
|
+
| `cursor` | `pointColor`, `impedanceColor`, `admittanceColor`, `pointSize` (dot diameter), `lineWidth`. |
|
|
511
|
+
| `marker` | `outlineColor`, `textColor`, `focusColor`, `focusHaloColor`. Marker fill follows its trace color. |
|
|
512
|
+
| `traceColors` | Non-empty palette for automatically colored traces. |
|
|
513
|
+
|
|
514
|
+
Colors must be solid CSS colors accepted by the library's color parser, including
|
|
515
|
+
hex, RGB, HSL, named colors, and `transparent`; CSS variable references are not
|
|
516
|
+
accepted in the configuration. Numeric values must be positive and finite.
|
|
517
|
+
Line widths and cursor point size are CSS pixels. Font sizes are SVG chart/scale
|
|
518
|
+
units and follow the existing layout; grid labels retain their adaptive minimum
|
|
519
|
+
size. Marker geometry and the 44 px touch target stay unchanged.
|
|
520
|
+
|
|
521
|
+
Appearance updates retain zoom, samples, marker selection/focus, layer visibility,
|
|
522
|
+
and scale readings. They recolor existing and future automatically colored traces.
|
|
523
|
+
Explicit colors passed through trace options and explicit layer styles take
|
|
524
|
+
precedence and survive theme changes. Applying a new appearance does not reset
|
|
525
|
+
those individual overrides. Invalid appearance settings throw before making changes.
|
|
526
|
+
|
|
527
|
+
SVG exports resolve the appearance without requiring theme CSS. A configured
|
|
528
|
+
background is included in the exported image; `transparent` preserves transparency.
|
|
529
|
+
The **Theme** selector in the demo's **Chart settings** dialog updates both library components and the demo UI.
|
|
530
|
+
|
|
347
531
|
## Grid layers
|
|
348
532
|
|
|
349
533
|
Resistance and reactance are initially visible. Conductance, susceptance, Q,
|
|
@@ -354,13 +538,15 @@ chart.layers.resistance.setVisible(false);
|
|
|
354
538
|
chart.layers.reactance.setVisible(false);
|
|
355
539
|
chart.layers.conductance.setVisible(true);
|
|
356
540
|
chart.layers.susceptance.setVisible(true);
|
|
357
|
-
chart.layers.conductance.
|
|
541
|
+
chart.layers.conductance.setDetail('standard');
|
|
542
|
+
chart.layers.conductance.setLabelsVisible(false);
|
|
543
|
+
chart.layers.susceptance.setLabelsVisible(false);
|
|
358
544
|
chart.layers.susceptance.setStyle({ stroke: '#64748b', textColor: '#2a7f62' });
|
|
359
545
|
chart.layers.vswr.addValue(4);
|
|
360
546
|
chart.layers.vswr.setVisible(true);
|
|
361
547
|
```
|
|
362
548
|
|
|
363
|
-
The four grid layers support `setVisible`, `
|
|
549
|
+
The four grid layers support `setVisible`, `setLabelsVisible`, `setDetail`, and
|
|
364
550
|
`setStyle(Partial<GridStyle>)`. Style fields: `stroke`, `majorWidth`, `minorWidth`,
|
|
365
551
|
`textColor`, `textFontFamily`, `textFontSize`. Widths are positive finite numbers
|
|
366
552
|
in CSS pixels. Font size is a positive finite base size in chart units; adaptive
|
|
@@ -368,8 +554,29 @@ layout raises it when needed to keep the font size at least 9 CSS pixels in the
|
|
|
368
554
|
default view. Partial updates retain other settings. Invalid numeric styles throw
|
|
369
555
|
before any property is changed.
|
|
370
556
|
|
|
557
|
+
Use `chart.setGridDetail('basic' | 'standard' | 'detailed')` for all four grid layers,
|
|
558
|
+
or `layer.setDetail(...)` to override one layer:
|
|
559
|
+
|
|
560
|
+
- **Basic:** complete circles and arcs for normalized values 0.2, 0.5, 1, 2, and 5,
|
|
561
|
+
with matching axis/rim labels and the zero label. Intended for small or static charts.
|
|
562
|
+
- **Standard** (default): the full major grid and its labels, without minor lines.
|
|
563
|
+
- **Detailed:** the full major and minor grid.
|
|
564
|
+
|
|
565
|
+
Changing detail preserves layer visibility, styles, and the current view. Label
|
|
566
|
+
collision handling still applies at every level. Peripheral scales and Q/VSWR
|
|
567
|
+
circles are independent. The demo's **Chart settings → Grid detail** selector updates all four grid layers.
|
|
568
|
+
|
|
569
|
+
Use `setLabelsVisible(false)` to hide a grid layer's labels while keeping its lines.
|
|
570
|
+
Labels default to visible. The setting persists through detail, style, theme, and
|
|
571
|
+
whole-layer visibility changes and is included in SVG exports. Calling
|
|
572
|
+
`setLabelsVisible(true)` restores labels allowed by the current detail and collision
|
|
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.
|
|
574
|
+
|
|
371
575
|
`layers.q` and `layers.vswr` support `setVisible`,
|
|
372
|
-
`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.
|
|
373
580
|
Circle stroke widths are positive finite numbers in CSS pixels.
|
|
374
581
|
Q values must be positive and finite; VSWR values must be finite and at least 1.
|
|
375
582
|
Duplicate additions and removal of absent values have no effect.
|
|
@@ -429,6 +636,8 @@ assumes a matched line or attenuator terminated in an open or short: the reflect
|
|
|
429
636
|
wave traverses it twice. It does not measure insertion loss of an arbitrary load
|
|
430
637
|
from S11 alone. At \|Γ\| = 0.1 it reads 10 dB, while return loss reads 20 dB.
|
|
431
638
|
|
|
639
|
+
Peripheral rulers are hidden by default. Enable them with
|
|
640
|
+
`{ peripheralScales: { visible: true } }` or `chart.peripheralScales.setVisible(true)`.
|
|
432
641
|
`chart.peripheralScales` supports `setVisible(boolean)` and `update(Complex | null)`.
|
|
433
642
|
Its four rulers zoom with the chart: reflection phase, voltage transmission phase,
|
|
434
643
|
wavelengths toward generator, and wavelengths toward load. Wavelength rulers start
|
|
@@ -479,7 +688,7 @@ import { Smith, Touchstone } from 'smithkit';
|
|
|
479
688
|
|
|
480
689
|
async function showMeasurement(file: File): Promise<Smith> {
|
|
481
690
|
const { samples, referenceImpedanceOhms } = Touchstone.parse(await file.text());
|
|
482
|
-
const chart = new Smith(referenceImpedanceOhms);
|
|
691
|
+
const chart = new Smith({ referenceImpedanceOhms });
|
|
483
692
|
chart.draw('#smith');
|
|
484
693
|
chart.addTrace(samples, { name: file.name });
|
|
485
694
|
return chart;
|
|
@@ -491,9 +700,92 @@ use `RfCalculations.renormalizeSamples` when the file reference differs from
|
|
|
491
700
|
`chart.referenceImpedanceOhms`. The parser itself does not renormalize data.
|
|
492
701
|
Multiport data and Touchstone 2.x are not supported.
|
|
493
702
|
|
|
703
|
+
## PNG and report export
|
|
704
|
+
|
|
705
|
+
`chart.toPng(options?)` and `scales.toPng(options?)` return `Promise<Blob>` with MIME
|
|
706
|
+
type `image/png`. Call them in a browser after mounting the components at non-zero
|
|
707
|
+
sizes, as with SVG export. The current view, labels, colors, markers, and scale
|
|
708
|
+
readings are captured without modifying the live components.
|
|
709
|
+
|
|
710
|
+
```ts
|
|
711
|
+
import { SmithScales } from 'smithkit';
|
|
712
|
+
|
|
713
|
+
const scales = new SmithScales();
|
|
714
|
+
scales.draw('#scales');
|
|
715
|
+
const png = await chart.toPng({ width: 3200, background: 'white', legend: true });
|
|
716
|
+
// Use the Blob with your application's download, preview, or upload flow.
|
|
717
|
+
const report = await chart.toPng({ width: 2400, scales, legend: true });
|
|
718
|
+
const scalePng = await scales.toPng({ width: 1200, background: 'transparent' });
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
- `width` / `height`: output pixels. Omit both to use the snapshot's natural size;
|
|
722
|
+
specify one to preserve proportions. With both set, the complete image is
|
|
723
|
+
centered and fitted without distortion or cropping. Padding uses the chosen
|
|
724
|
+
background, or transparency when no background is specified.
|
|
725
|
+
- `background`: omit to keep component backgrounds, or pass a concrete CSS color
|
|
726
|
+
(for example `'#fff'`) or `'transparent'` to replace them. Chart colors stay as
|
|
727
|
+
configured; choose a suitable chart theme for your report background.
|
|
728
|
+
- `legend` (chart only): append the names and colors of visible traces. Long names
|
|
729
|
+
wrap; hidden traces are omitted. Default: `false`.
|
|
730
|
+
- `markerLegend` (chart only): independently append marker descriptions below the chart/scales.
|
|
731
|
+
Default: `false`. `true` includes every marker of visible traces, with trace name,
|
|
732
|
+
marker number, frequency, and impedance. Use an options object to select `markerIds`
|
|
733
|
+
and `fields` (`frequency`, `impedance`, `admittance`, `reflectionCoefficient`, `vswr`,
|
|
734
|
+
`returnLoss`). Fields use Hz with SI prefixes, Ω, mS, dimensionless Γ, VSWR ratios,
|
|
735
|
+
and dB respectively. Empty `markerIds` selects none; empty `fields` keeps only names
|
|
736
|
+
and marker numbers. Hidden traces are always omitted, duplicate selections appear
|
|
737
|
+
once, and unknown IDs/fields throw. Long descriptions wrap. Readings are captured at
|
|
738
|
+
export invocation, without moving markers or changing the live view.
|
|
739
|
+
- `scales` (chart only): a mounted `SmithScales` instance to place centered below the chart.
|
|
740
|
+
Its current responsive layout and readings are preserved. Use `scaleReadout` to
|
|
741
|
+
export a specific reading independently of the live view.
|
|
742
|
+
|
|
743
|
+
```ts
|
|
744
|
+
// Works with toSvg() as well. Trace and marker legends are independent.
|
|
745
|
+
const png = await chart.toPng({
|
|
746
|
+
legend: false,
|
|
747
|
+
markerLegend: {
|
|
748
|
+
fields: ['frequency', 'impedance', 'vswr'],
|
|
749
|
+
// Omit markerIds to include all markers of visible traces.
|
|
750
|
+
},
|
|
751
|
+
});
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
Dimensions must be positive integers, at most 8192 px per side and 32 megapixels
|
|
755
|
+
in total. Invalid options and unavailable/destroyed components reject the promise.
|
|
756
|
+
PNG export uses browser SVG decoding and Canvas 2D and releases temporary image URLs.
|
|
757
|
+
Fonts must be available to the browser; external font files are not embedded.
|
|
758
|
+
Export size increases resolution; it does not change the current label density.
|
|
759
|
+
|
|
760
|
+
The demo's **Export** dialog supports chart, scales, or a combined report,
|
|
761
|
+
custom dimensions/background, and independent trace and marker legends. **Include marker legend** reveals field and marker checkboxes; it is disabled for scales-only exports. The default output width is 3200 px. Choose PNG or SVG in the same dialog; both formats support these report options. The demo starts in dark mode. For exports containing parameter scales, **Scale readings** explicitly selects a marker or **None**, independently of the Cursor/Marker tab. Marker exports include the trace name, marker number, and frequency; cursor readings are not exported.
|
|
762
|
+
|
|
763
|
+
To export a specific scale reading without changing the live view, pass `readout` to
|
|
764
|
+
`scales.toSvg()` / `scales.toPng()`, or `scaleReadout` to a combined chart export:
|
|
765
|
+
|
|
766
|
+
```ts
|
|
767
|
+
import { Complex, SmithScales } from 'smithkit';
|
|
768
|
+
|
|
769
|
+
const scales = new SmithScales();
|
|
770
|
+
scales.draw('#scales');
|
|
771
|
+
const report = await chart.toPng({
|
|
772
|
+
scales,
|
|
773
|
+
scaleReadout: {
|
|
774
|
+
reflectionCoefficient: Complex.from(0.2, 0.3),
|
|
775
|
+
label: 'Antenna · Marker 1 · Frequency: 1 GHz',
|
|
776
|
+
},
|
|
777
|
+
});
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
Use `{ reflectionCoefficient: null }` for scales without readings. Omit the override to
|
|
781
|
+
preserve the current readings. Labels wrap to fit the exported scales.
|
|
782
|
+
|
|
494
783
|
## SVG export
|
|
495
784
|
|
|
496
|
-
`chart.toSvg()` and `scales.toSvg()` return SVG strings.
|
|
785
|
+
`chart.toSvg(options?)` and `scales.toSvg(options?)` return SVG strings synchronously.
|
|
786
|
+
They accept the same `SmithImageExportOptions` / `ImageExportOptions` as PNG export,
|
|
787
|
+
including size, background, legend, and scales. Omitting options preserves the
|
|
788
|
+
original standalone SVG snapshot. Call them after `draw()`
|
|
497
789
|
while the component is mounted and has a non-zero rendered size; exporting before
|
|
498
790
|
mounting, from a `display: none` container, or after destruction throws an error.
|
|
499
791
|
|
|
@@ -517,12 +809,13 @@ The scale export combines all twelve axes into one SVG using their current layou
|
|
|
517
809
|
and displayed readings. To export a marker reading, call `scales.update()` with
|
|
518
810
|
that marker's reflection coefficient before `scales.toSvg()`.
|
|
519
811
|
|
|
520
|
-
Files have explicit pixel dimensions and
|
|
812
|
+
Files have explicit pixel dimensions and preserve the configured component background
|
|
813
|
+
(transparent by default). Presentation
|
|
521
814
|
styles are inlined so the application's stylesheet is not required. Text remains
|
|
522
815
|
editable, and curved captions retain their internal path references. Font files
|
|
523
816
|
and surrounding HTML controls/backgrounds are not embedded. Downloading is the
|
|
524
817
|
consumer's responsibility; the library does not create files or modify live nodes.
|
|
525
|
-
|
|
818
|
+
In the demo, choose **Export → Format → SVG**, then select chart, scales, or the combined report.
|
|
526
819
|
|
|
527
820
|
## Development and license
|
|
528
821
|
|
|
@@ -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
|
+
}
|