smithkit 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +467 -2
  3. package/dist/lib/Smith.d.ts +103 -0
  4. package/dist/lib/Smith.js +562 -0
  5. package/dist/lib/SmithArcsDefs.d.ts +10 -0
  6. package/dist/lib/SmithArcsDefs.js +10 -0
  7. package/dist/lib/SmithConstantCircle.d.ts +62 -0
  8. package/dist/lib/SmithConstantCircle.js +222 -0
  9. package/dist/lib/arcs/Tick.d.ts +33 -0
  10. package/dist/lib/arcs/Tick.js +40 -0
  11. package/dist/lib/complex/Complex.d.ts +130 -0
  12. package/dist/lib/complex/Complex.js +636 -0
  13. package/dist/lib/components.d.ts +14 -0
  14. package/dist/lib/components.js +26 -0
  15. package/dist/lib/draw/ConstCircles.d.ts +63 -0
  16. package/dist/lib/draw/ConstCircles.js +158 -0
  17. package/dist/lib/draw/ConstConductance.d.ts +4 -0
  18. package/dist/lib/draw/ConstConductance.js +6 -0
  19. package/dist/lib/draw/ConstQCircles.d.ts +29 -0
  20. package/dist/lib/draw/ConstQCircles.js +86 -0
  21. package/dist/lib/draw/ConstReactance.d.ts +4 -0
  22. package/dist/lib/draw/ConstReactance.js +6 -0
  23. package/dist/lib/draw/ConstResistance.d.ts +4 -0
  24. package/dist/lib/draw/ConstResistance.js +6 -0
  25. package/dist/lib/draw/ConstSusceptance.d.ts +4 -0
  26. package/dist/lib/draw/ConstSusceptance.js +6 -0
  27. package/dist/lib/draw/ConstSwrCircles.d.ts +29 -0
  28. package/dist/lib/draw/ConstSwrCircles.js +85 -0
  29. package/dist/lib/draw/MouseGesture.d.ts +6 -0
  30. package/dist/lib/draw/MouseGesture.js +26 -0
  31. package/dist/lib/draw/SmithArc.d.ts +10 -0
  32. package/dist/lib/draw/SmithArc.js +26 -0
  33. package/dist/lib/draw/SmithCircle.d.ts +10 -0
  34. package/dist/lib/draw/SmithCircle.js +24 -0
  35. package/dist/lib/draw/SmithCursor.d.ts +49 -0
  36. package/dist/lib/draw/SmithCursor.js +186 -0
  37. package/dist/lib/draw/SmithData.d.ts +66 -0
  38. package/dist/lib/draw/SmithData.js +366 -0
  39. package/dist/lib/draw/SmithDrawOptions.d.ts +5 -0
  40. package/dist/lib/draw/SmithDrawOptions.js +1 -0
  41. package/dist/lib/draw/SmithGridLayer.d.ts +19 -0
  42. package/dist/lib/draw/SmithGridLayer.js +48 -0
  43. package/dist/lib/draw/SmithGroup.d.ts +11 -0
  44. package/dist/lib/draw/SmithGroup.js +31 -0
  45. package/dist/lib/draw/SmithLine.d.ts +11 -0
  46. package/dist/lib/draw/SmithLine.js +27 -0
  47. package/dist/lib/draw/SmithMarker.d.ts +25 -0
  48. package/dist/lib/draw/SmithMarker.js +122 -0
  49. package/dist/lib/draw/SmithPath.d.ts +8 -0
  50. package/dist/lib/draw/SmithPath.js +20 -0
  51. package/dist/lib/draw/SmithScaler.d.ts +19 -0
  52. package/dist/lib/draw/SmithScaler.js +48 -0
  53. package/dist/lib/draw/SmithShape.d.ts +15 -0
  54. package/dist/lib/draw/SmithShape.js +42 -0
  55. package/dist/lib/draw/SmithSvg.d.ts +9 -0
  56. package/dist/lib/draw/SmithSvg.js +27 -0
  57. package/dist/lib/draw/SmithText.d.ts +22 -0
  58. package/dist/lib/draw/SmithText.js +56 -0
  59. package/dist/lib/formatting.d.ts +7 -0
  60. package/dist/lib/formatting.js +16 -0
  61. package/dist/lib/grid/definitions.d.ts +3 -0
  62. package/dist/lib/grid/definitions.js +123 -0
  63. package/dist/lib/grid/geometry.d.ts +20 -0
  64. package/dist/lib/grid/geometry.js +55 -0
  65. package/dist/lib/grid/labels.d.ts +3 -0
  66. package/dist/lib/grid/labels.js +41 -0
  67. package/dist/lib/grid/types.d.ts +30 -0
  68. package/dist/lib/grid/types.js +6 -0
  69. package/dist/lib/index.d.ts +16 -0
  70. package/dist/lib/index.js +9 -0
  71. package/dist/lib/io/touchstone.d.ts +7 -0
  72. package/dist/lib/io/touchstone.js +72 -0
  73. package/dist/lib/layers.d.ts +48 -0
  74. package/dist/lib/layers.js +73 -0
  75. package/dist/lib/math/complexRatio.d.ts +3 -0
  76. package/dist/lib/math/complexRatio.js +30 -0
  77. package/dist/lib/measurements.d.ts +53 -0
  78. package/dist/lib/measurements.js +19 -0
  79. package/dist/lib/renormalization.d.ts +10 -0
  80. package/dist/lib/renormalization.js +62 -0
  81. package/dist/lib/rf.d.ts +35 -0
  82. package/dist/lib/rf.js +74 -0
  83. package/dist/lib/samples.d.ts +6 -0
  84. package/dist/lib/samples.js +1 -0
  85. package/dist/lib/scales/SmithPeripheralScales.d.ts +13 -0
  86. package/dist/lib/scales/SmithPeripheralScales.js +141 -0
  87. package/dist/lib/scales/SmithScales.d.ts +16 -0
  88. package/dist/lib/scales/SmithScales.js +142 -0
  89. package/dist/lib/scales/peripheralScales.d.ts +12 -0
  90. package/dist/lib/scales/peripheralScales.js +49 -0
  91. package/dist/lib/scales/radialScales.d.ts +12 -0
  92. package/dist/lib/scales/radialScales.js +109 -0
  93. package/dist/lib/shapes/Arc.d.ts +6 -0
  94. package/dist/lib/shapes/Arc.js +1 -0
  95. package/dist/lib/shapes/Circle.d.ts +5 -0
  96. package/dist/lib/shapes/Circle.js +1 -0
  97. package/dist/lib/shapes/Line.d.ts +5 -0
  98. package/dist/lib/shapes/Line.js +1 -0
  99. package/dist/lib/shapes/Point.d.ts +1 -0
  100. package/dist/lib/shapes/Point.js +1 -0
  101. package/package.json +76 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Krzysztof Rosiński
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,468 @@
1
- # Temporary Holding Version
1
+ # Smithkit
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **`smithkit`** is a framework-independent JavaScript and TypeScript library for
4
+ interactive SVG Smith charts, RF calculations, and S11 measurement exploration.
5
+
6
+ [Try the interactive demo](https://rosek86.github.io/smithkit/).
7
+
8
+ ## Features
9
+
10
+ - Labeled resistance, reactance, conductance, and susceptance grids.
11
+ - Configurable constant-Q and constant-VSWR circles.
12
+ - Zoom, pan, view reset, and cursor readings.
13
+ - Named traces with colors, visibility, and draggable markers that snap to samples.
14
+ - Marker comparisons: frequency, complex impedance, and wrapped phase differences.
15
+ - Physical impedance/admittance conversions and DOM-independent RF readings.
16
+ - Twelve parameter scales mounted independently of chart zoom.
17
+ - Four peripheral rulers for phase and electrical length.
18
+ - One-port Touchstone 1.x import with RI, MA, and DB representations.
19
+ - ESM modules and TypeScript declarations.
20
+
21
+ ## Installation
22
+
23
+ Install from npm:
24
+
25
+ ```sh
26
+ npm install smithkit
27
+ ```
28
+
29
+ Browser applications need SVG, ES2022, and an ESM-compatible build tool.
30
+ No library stylesheet is required. To build the library from source, see
31
+ [Contributing](https://github.com/rosek86/smithkit/blob/main/CONTRIBUTING.md).
32
+
33
+ For a complete application using the installed package, see the
34
+ [standalone integration example](https://github.com/rosek86/smithkit/tree/main/examples/basic).
35
+ It demonstrates mounting, replacing samples, event subscriptions, resizing, and cleanup.
36
+
37
+ ## Quick start
38
+
39
+ Give the container an explicit size and run the code after it is mounted:
40
+
41
+ ```html
42
+ <div id="smith" style="width: 100%; max-width: 700px; aspect-ratio: 1;"></div>
43
+ ```
44
+
45
+ ```ts
46
+ import { Smith } from 'smithkit';
47
+
48
+ const chart = new Smith(50); // Reference impedance in ohms; defaults to 50.
49
+ chart.draw('#smith');
50
+ const traceId = chart.addTrace(
51
+ [
52
+ { frequencyHz: 1e9, reflectionCoefficient: [0.5, -0.5] },
53
+ { frequencyHz: 1.5e9, reflectionCoefficient: [0, 0] },
54
+ { frequencyHz: 2e9, reflectionCoefficient: [0.5, 0.5] },
55
+ ],
56
+ { name: 'Antenna', color: '#2563eb' },
57
+ );
58
+ ```
59
+
60
+ Each `TraceSample` contains `frequencyHz` and the dimensionless voltage reflection
61
+ coefficient Γ as `[real, imaginary]`. Samples are copied and validated; readonly tuples and arrays (`as const`) are accepted. Frequencies
62
+ must be finite and non-negative; both coordinates must be finite. A trace must
63
+ contain at least one sample.
64
+
65
+ Each new trace receives one marker on its first sample. Drag markers to select
66
+ samples, scroll to zoom, and drag the chart to pan. Markers stay above all sample
67
+ points. `chart.resetView()` restores the initial view, including peripheral rulers.
68
+
69
+ Construct `Smith` only in a browser, such as your framework's mount hook. Importing
70
+ the package and using its calculation or parsing functions does not require a DOM.
71
+
72
+ Zoom is enabled by default. For a fixed view, call `chart.setZoomEnabled(false)` before or after mounting.
73
+ It disables wheel/double-click zoom and mouse/touch panning and pinch zoom while
74
+ preserving the current view. Set it back to `true` to restore gestures. This is a
75
+ view control: cursor readings and marker dragging stay available. `resetView()`
76
+ also remains available when zoom is disabled. New wheel gestures do not consume
77
+ page scrolling. Disabling during an active gesture freezes the view while that
78
+ gesture finishes normally.
79
+
80
+ ```ts
81
+ import { Smith } from 'smithkit';
82
+
83
+ const chart = new Smith(50);
84
+ chart.setZoomEnabled(false);
85
+ chart.draw('#smith');
86
+ ```
87
+
88
+ `draw(selector | HTMLElement)` moves the existing SVG when called again. A missing
89
+ container throws. Call `chart.destroy()` on unmount; it removes the SVG, releases
90
+ event handlers, and cancels queued notifications. Repeated destruction is safe.
91
+
92
+ ## Traces and markers
93
+
94
+ Trace and marker IDs are opaque strings, unique within a chart and never reused
95
+ there. They survive renaming, hiding, updating samples, and removing other items.
96
+ They are not persistent across chart instances or page reloads.
97
+
98
+ ```ts
99
+ const markerA = chart.getTraces().find((trace) => trace.id === traceId)!.markers[0].id;
100
+ const markerB = chart.addMarker(traceId, 2)!; // Zero-based sample index.
101
+
102
+ chart.setTraceOptions(traceId, { name: 'Tuned antenna', visible: true });
103
+ chart.setMarkerSample(markerB, 1);
104
+ console.log(chart.getMarker(markerB)?.frequencyHz); // 1.5e9
105
+
106
+ const difference = chart.compareMarkers(markerA, markerB);
107
+ console.log(difference?.frequencyDeltaHz); // B − A, in Hz.
108
+ console.log(difference?.impedanceDeltaOhms?.toVector());
109
+ console.log(difference?.phaseDeltaDegrees);
110
+
111
+ chart.removeMarker(markerB);
112
+ ```
113
+
114
+ | Method | Result / behavior |
115
+ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
116
+ | `addTrace(samples, options?)` | Returns a trace ID. Options: nonblank `name`, solid CSS `color`, and `visible`. |
117
+ | `getTraces()` | Detached `TraceInfo` snapshots: ID, name, color, visibility, sample count, and marker metadata. |
118
+ | `setTraceOptions(id, options)` | Updates the supplied options only. Invalid options throw before any change. |
119
+ | `updateTrace(id, samples, options?)` | Replaces samples and preserves identity, appearance, and markers. Empty/invalid input throws without changing the trace. |
120
+ | `removeTrace(id)` | Removes the trace and its markers. |
121
+ | `clearTraces()` | Removes all traces and their markers. |
122
+ | `addMarker(traceId, sampleIndex = 0)` | Returns a marker ID, or `undefined` if the trace is missing. |
123
+ | `getMarker(id)` | Returns a `MarkerSnapshot`, or `undefined` if missing. |
124
+ | `setMarkerSample(id, sampleIndex)` | Selects an existing sample and queues a marker notification. |
125
+ | `removeMarker(id)` | Removes one marker without renumbering the remaining markers. |
126
+ | `compareMarkers(a, b)` | Returns B − A, or `undefined` if either marker is missing. |
127
+ | `referenceImpedanceOhms` | Read-only reference impedance. Must be positive and finite. |
128
+
129
+ Update/remove methods return a boolean: `false` means the ID was not found.
130
+ Sample indices must be zero-based integers within the trace; invalid indices throw
131
+ `RangeError`. Sample replacement follows the nearest measured frequency by default.
132
+ Pass `{ markerSelection: 'sample-index' }` to retain indices (clamped to the new last
133
+ sample if the trace shrinks), or `{ markerSelection: 'reflection' }` to select the
134
+ nearest Γ. Frequency and Γ ties select the earlier sample in input order, including
135
+ unsorted sweeps and duplicate frequencies. Selection uses the actual selected sample,
136
+ not a previous requested frequency. No interpolation is performed.
137
+
138
+ ```ts
139
+ chart.updateTrace(
140
+ traceId,
141
+ [
142
+ { frequencyHz: 1e9, reflectionCoefficient: [0.2, 0] },
143
+ { frequencyHz: 2e9, reflectionCoefficient: [0.3, 0] },
144
+ ],
145
+ { markerSelection: 'frequency' },
146
+ );
147
+ ```
148
+
149
+ Invalid strategies throw `TypeError`; empty or invalid samples throw `RangeError`
150
+ before mutation. Unknown trace IDs return `false` without validating input.
151
+ Renormalization always preserves sample indices because it transforms the existing
152
+ sweep rather than replacing it.
153
+
154
+ Trace snapshots contain `markers: { id, number, sampleIndex }[]`. `number` is the
155
+ stable display number within that trace. A `MarkerSnapshot` includes the common
156
+ RF reading fields below plus `traceId`, `markerId`, `markerNumber`, `sampleIndex`,
157
+ and `frequencyHz`.
158
+
159
+ Hiding a trace hides its points and markers while retaining measurements for
160
+ comparisons. Hiding or deleting a dragged marker/trace ends the drag. Mutations
161
+ after destruction throw; trace/marker lookups return empty or missing results.
162
+
163
+ ## Trace appearance
164
+
165
+ Pass `mode: 'points' | 'line' | 'both'`, `lineWidth`, and `pointRadius` to
166
+ `addTrace` or `setTraceOptions`. Defaults are points, 2 px line width, and 2 px point radius. Both sizes are
167
+ CSS pixels and remain constant under chart zoom and container resize. Widths and radii must be positive and finite.
168
+ `getTraces()` includes the current style.
169
+
170
+ Lines connect all samples in input order, without smoothing or frequency sorting.
171
+ A line-only trace creates one SVG path and no sample circles, suitable for dense
172
+ sweeps. Points/both render every sample for traces up to 5,000 samples. Larger traces
173
+ render one representative per point-radius-sized display cell and skip points
174
+ outside the viewport; representatives are recalculated during zoom/pan.
175
+ This only simplifies the image: all original samples remain available to markers,
176
+ snapping, readouts, and comparisons. Lines still connect every input sample.
177
+ Styles do not change marker identity or layer ordering. Hidden traces retain their style after sample replacement.
178
+ The demo exposes the same controls per trace.
179
+
180
+ ## Events and readings
181
+
182
+ `onEvent(listener)` supports multiple independent subscriptions and returns an
183
+ unsubscribe function. `SmithEvent` is a discriminated union: checking `type`
184
+ narrows `data` without a cast or a property-presence test.
185
+
186
+ ```ts
187
+ import { SmithEventType } from 'smithkit';
188
+
189
+ const unsubscribe = chart.onEvent((event) => {
190
+ if (event.type === SmithEventType.Cursor) {
191
+ // undefined when the pointer leaves or a marker drag starts.
192
+ console.log(event.data?.impedanceOhms?.toVector());
193
+ return;
194
+ }
195
+ // Marker, MarkerDragStart, and MarkerDragEnd always carry a MarkerSnapshot.
196
+ console.log(event.data.markerId, event.data.frequencyHz);
197
+ });
198
+
199
+ // On component unmount:
200
+ unsubscribe();
201
+ chart.destroy();
202
+ ```
203
+
204
+ The event types are `Cursor` (`'cursor'`), `Marker` (`'marker'`),
205
+ `MarkerDragStart` (`'marker-drag-start'`), and `MarkerDragEnd` (`'marker-drag-end'`).
206
+ Cursor movement is suspended during marker dragging. Drag start/end events fire
207
+ synchronously; marker position notifications are queued and report the latest
208
+ snapshot at delivery. Before delivery, repeated updates to the same marker coalesce
209
+ into one notification; different markers have independent notifications. Creation
210
+ queues an initial marker notification too. Removed markers cannot deliver queued
211
+ position events.
212
+ Trace metadata changes do not emit marker events; refresh metadata after mutations.
213
+ Destruction suppresses application callbacks.
214
+
215
+ `chart.cursorReading` returns the last cursor position. Cursor and marker readings
216
+ share `SmithReading`; `readReflection(gamma, referenceImpedanceOhms = 50)` produces
217
+ the same reading without constructing a chart.
218
+
219
+ | Fields | Units / meaning |
220
+ | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
221
+ | `reflectionCoefficient` | Complex, dimensionless voltage Γ. |
222
+ | `impedanceOhms`, `admittanceSiemens` | Physical complex Z in Ω and Y in S. Multiply Y by 1000 for mS display. |
223
+ | `phaseDegrees` | Phase of Γ in degrees; undefined at Γ = 0. |
224
+ | `vswr`, `q` | Dimensionless voltage standing-wave ratio and \|X/R\|. |
225
+ | `returnLossDb`, `reflectionLossDb` | dB. Reflection loss is also called mismatch loss. |
226
+ | `standingWaveRatioDb` | 20 log₁₀(VSWR), in dB. |
227
+ | `attenuationDb` | Half the return loss; interpretation described below. |
228
+ | `powerReflectionCoefficient`, `powerTransmissionCoefficient` | \|Γ\|² and 1 − \|Γ\|². |
229
+ | `voltageReflectionCoefficientMagnitude` | \|Γ\|. |
230
+ | `voltageTransmissionCoefficientMagnitude`, `currentTransmissionCoefficientMagnitude` | \|1 + Γ\| and \|1 − Γ\|, respectively. |
231
+ | `standingWavePeak`, `standingWaveLossCoefficient` | √VSWR and (1 + \|Γ\|²)/(1 − \|Γ\|²). |
232
+
233
+ Singular Z at Γ = +1 and Y at Γ = −1 are `undefined`. Q is undefined at an open
234
+ or short, and infinite for a nonzero purely reactive impedance. Finite points near
235
+ these limits retain finite readings within the supported numeric range. An exact
236
+ infinite scalar limit is preserved: perfect match gives infinite return loss;
237
+ \|Γ\| = 1 gives infinite VSWR and reflection loss.
238
+
239
+ For active loads (\|Γ\| > 1), VSWR, standing-wave quantities, reflection loss,
240
+ attenuation, and power transmission are `undefined`. Other quantities remain
241
+ available. Non-finite complex inputs or invalid reference impedances throw
242
+ `RangeError` in the public RF functions. Unrepresentable complex conversion
243
+ results are `undefined`.
244
+
245
+ ## Selecting a marker by frequency
246
+
247
+ `chart.setMarkerFrequency(markerId, frequencyHz)` selects the nearest measured
248
+ frequency and returns `true`, or `false` if the marker does not exist. It accepts
249
+ finite, non-negative Hz values; other values throw `RangeError`. It works with
250
+ unsorted sweeps and duplicate frequencies. Equal-distance ties choose the earliest
251
+ sample in input order. Requests outside the sweep select the nearest endpoint.
252
+ No interpolation or sample reordering is performed.
253
+
254
+ Read `chart.getMarker(markerId).frequencyHz` for the actual selected frequency.
255
+ The same queued `Marker` event is emitted as for sample-index selection, and
256
+ comparisons update accordingly. The demo accepts MHz and shows the selected
257
+ measurement frequency; dragging and sample selection keep that field synchronized.
258
+
259
+ ## Calculations without a chart
260
+
261
+ ```ts
262
+ import { Complex, readReflection, impedanceToReflection } from 'smithkit';
263
+
264
+ const gamma = impedanceToReflection(Complex.from(75, 25), 50)!;
265
+ const reading = readReflection(gamma, 50);
266
+ console.log(reading.impedanceOhms?.toVector()); // Approximately [75, 25].
267
+ console.log(reading.admittanceSiemens?.toVector());
268
+ ```
269
+
270
+ | Function | Input and output |
271
+ | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
272
+ | `reflectionToImpedance(gamma, referenceImpedanceOhms = 50)` | Γ → physical Z in Ω. |
273
+ | `impedanceToReflection(impedanceOhms, referenceImpedanceOhms = 50)` | Physical Z in Ω → Γ. |
274
+ | `reflectionToAdmittance(gamma, referenceImpedanceOhms = 50)` | Γ → physical Y in S. |
275
+ | `admittanceToReflection(admittanceSiemens, referenceImpedanceOhms = 50)` | Physical Y in S → Γ. |
276
+ | `readReflection(gamma, referenceImpedanceOhms = 50)` | Complete `SmithReading`. |
277
+ | `compareMarkerReadings(a, b)` | Compare readings without a chart. Each input needs `frequencyHz`, `reflectionCoefficient`, and `impedanceOhms`. |
278
+
279
+ Comparison results contain `frequencyDeltaHz`, `impedanceDeltaOhms`, and
280
+ `phaseDeltaDegrees`. Phase differences use B − A wrapped to **[−180°, 180°)**:
281
+ 179° → −179° gives +2°; an exact half turn gives −180°. Phase is undefined if
282
+ either Γ is zero or non-finite. Impedance differences are undefined if either Z
283
+ is singular or non-finite. Frequencies must be finite and non-negative. This is
284
+ a point comparison, not sweep phase unwrapping or group delay.
285
+
286
+ `Complex.from(re, im)` constructs complex numbers. Read components through `re` and `im`. Arithmetic (`add`, `sub`,
287
+ `mul`, `div`, `pow`) returns new values. The class also provides `abs`, `arg`
288
+ (radians), and static functions such as `Complex.conj`, `Complex.sqrt`,
289
+ `Complex.exp`, `Complex.log`, and trigonometric/hyperbolic functions and inverses. `toVector()` returns `[real, imaginary]`; `toString(dp)` formats
290
+ a Cartesian value. See the exported declarations for all operations.
291
+
292
+ Formatting helpers are independent exports: `formatNumber(value)` (three significant
293
+ digits and SI prefixes), `formatComplex(value, unit?, decimalPlaces?)`, and
294
+ `formatComplexPolar(value, unit?, decimalPlaces?)` (phase in degrees). They work
295
+ without a chart or DOM. Complex formatters default to three fractional digits.
296
+ `formatNumber` includes spacing so you can append a unit, e.g. `formatNumber(1e9) + 'Hz'`.
297
+
298
+ `reactanceToComponent(reactanceOhms, frequencyHz)` calculates an ideal equivalent
299
+ series component without formatting or constructing a chart:
300
+
301
+ ```ts
302
+ import { reactanceToComponent, formatNumber } from 'smithkit';
303
+
304
+ const component = reactanceToComponent(-50, 1e9);
305
+ if (component?.kind === 'capacitor') {
306
+ console.log(formatNumber(component.capacitanceFarads) + 'F'); // 3.18 pF
307
+ } else if (component?.kind === 'inductor') {
308
+ console.log(formatNumber(component.inductanceHenries) + 'H');
309
+ }
310
+ ```
311
+
312
+ Use `reading.impedanceOhms.im` for the reactance when impedance is defined.
313
+ Zero frequency, zero reactance, and unrepresentable results return `undefined`.
314
+ Non-finite reactance or negative/non-finite frequency throws `RangeError`.
315
+ This is a single-frequency equivalence, not a fitted circuit model.
316
+
317
+ ## Grid layers
318
+
319
+ Resistance and reactance are initially visible. Conductance, susceptance, Q,
320
+ and VSWR layers start hidden. All controls are under `chart.layers`:
321
+
322
+ ```ts
323
+ chart.layers.resistance.setVisible(false);
324
+ chart.layers.reactance.setVisible(false);
325
+ chart.layers.conductance.setVisible(true);
326
+ chart.layers.susceptance.setVisible(true);
327
+ chart.layers.conductance.setMinorVisible(false);
328
+ chart.layers.susceptance.setStyle({ stroke: '#64748b', textColor: '#2a7f62' });
329
+ chart.layers.vswr.addValue(4);
330
+ chart.layers.vswr.setVisible(true);
331
+ ```
332
+
333
+ The four grid layers support `setVisible`, `setMinorVisible`, and
334
+ `setStyle(Partial<GridStyle>)`. Style fields: `stroke`, `majorWidth`, `minorWidth`,
335
+ `textColor`, `textFontFamily`, `textFontSize`. Widths are positive finite numbers
336
+ in CSS pixels. Font size is a positive finite number in chart units because labels
337
+ scale with the grid geometry. Partial updates retain other settings. Invalid numeric
338
+ styles throw before any property is changed.
339
+
340
+ `layers.q` and `layers.vswr` support `setVisible`,
341
+ `setStyle({ stroke?, strokeWidth? })`, `addValue`, and `removeValue`.
342
+ Circle stroke widths are positive finite numbers in CSS pixels.
343
+ Q values must be positive and finite; VSWR values must be finite and at least 1.
344
+ Duplicate additions and removal of absent values have no effect.
345
+
346
+ Grid labels use normalized values; readings use physical units. Strokes default
347
+ to slate gray with 1 px major and 0.6 px minor lines. Stroke widths stay constant
348
+ under zoom; label sizes use SVG user units. Layer controls do not expose drawing
349
+ objects or SVG nodes.
350
+
351
+ ## Parameter scales
352
+
353
+ Mount `SmithScales` independently so its twelve axes stay outside chart zoom:
354
+
355
+ ```ts
356
+ import { SmithScales, SmithEventType } from 'smithkit';
357
+
358
+ const scales = new SmithScales();
359
+ scales.draw('#smith-scales'); // Provide a separate mounted container.
360
+ const stopScales = chart.onEvent((event) => {
361
+ if (event.type === SmithEventType.Cursor || event.type === SmithEventType.Marker) {
362
+ const gamma = event.data?.reflectionCoefficient ?? null;
363
+ scales.update(gamma);
364
+ chart.peripheralScales.update(gamma);
365
+ }
366
+ });
367
+
368
+ // On unmount: stopScales(); scales.destroy(); chart.destroy();
369
+ ```
370
+
371
+ `update(Complex | null)` synchronizes indicators, values, tooltips, and accessible
372
+ labels. Null, non-finite, or active-load inputs hide indicators. `draw` accepts a
373
+ selector or `HTMLElement`; `destroy` is idempotent. Scales adapt to container width.
374
+
375
+ Labels follow the complete Smith chart terminology: **Reflection loss**,
376
+ **Power reflection coefficient**, **Power transmission coefficient**, and separate
377
+ **Voltage transmission coefficient** and **Current transmission coefficient**.
378
+ Ratios are dimensionless; loss/standing-wave dB axes are in dB. Transmission
379
+ indicators depend on Γ phase as well as magnitude.
380
+
381
+ Attenuation is −10 log₁₀\|Γ\|, half the return loss. Its one-way loss interpretation
382
+ assumes a matched line or attenuator terminated in an open or short: the reflected
383
+ wave traverses it twice. It does not measure insertion loss of an arbitrary load
384
+ from S11 alone. At \|Γ\| = 0.1 it reads 10 dB, while return loss reads 20 dB.
385
+
386
+ `chart.peripheralScales` supports `setVisible(boolean)` and `update(Complex | null)`.
387
+ Its four rulers zoom with the chart: reflection phase, voltage transmission phase,
388
+ wavelengths toward generator, and wavelengths toward load. Wavelength rulers start
389
+ at the negative real axis, with 0.5 λ per revolution (generator clockwise, load
390
+ counterclockwise). These are electrical-length coordinates modulo 0.5 λ.
391
+ Reflection phase and wavelength coordinates are undefined at perfect match;
392
+ transmission phase is undefined at Γ = −1.
393
+
394
+ Scale conventions follow the [complete Smith chart reference](https://www.uiyinc.com/assets/The-Complete-Smith-Chart-Black-Magic-Design.jpg)
395
+ and [CERN's Smith chart introduction](https://arxiv.org/abs/1201.4068).
396
+
397
+ ## Reference impedance and renormalization
398
+
399
+ `chart.renormalize(referenceImpedanceOhms)` changes the positive real reference
400
+ impedance and transforms every loaded Γ to preserve physical Z. Trace/marker IDs,
401
+ names, colors, visibility, frequencies, and selected sample indices are retained.
402
+ Results for all traces are validated before any change; a singular/unrepresentable
403
+ result rejects the operation without changing Z₀ or data. Active marker drags end,
404
+ cursor indicators are cleared, and marker readings are queued with the new values.
405
+ Changing to the current Z₀ has no effect. Mutations after destruction throw.
406
+
407
+ Two DOM-independent helpers are also exported:
408
+
409
+ - `renormalizeReflection(gamma, fromOhms, toOhms)` returns the new complex Γ or
410
+ `undefined` for a singular/unrepresentable result. Exact open and short limits
411
+ remain Γ = +1 and −1. References must be positive, finite, real ohm values.
412
+ - `renormalizeSamples(samples, fromOhms, toOhms)` returns a new sample array in
413
+ the same order, preserving frequencies. Invalid samples or a singular result
414
+ throw `RangeError`; the input is never modified.
415
+
416
+ For example, a matched 75 Ω load has Γ = 0 at 75 Ω and Γ = 0.2 at 50 Ω.
417
+ Renormalization changes Γ, VSWR, and loss readings; physical impedance stays 75 Ω.
418
+ Simply relabeling the same Γ with a new Z₀ would describe a different load.
419
+
420
+ The demo exposes chart Z₀ and explicitly renormalizes existing traces when applied.
421
+ Import renormalization is enabled by a labeled checkbox. Disable it to reject
422
+ files whose reference differs from the chart. Complex reference impedances and
423
+ multiport renormalization are outside the supported scope.
424
+
425
+ ## Touchstone import
426
+
427
+ `parseTouchstone(text)` returns `{ samples, referenceImpedanceOhms }`. It accepts
428
+ one-port Touchstone 1.x, converts RI/MA/DB into Cartesian Γ and all frequencies
429
+ into Hz, and throws for unsupported or invalid input.
430
+
431
+ ```ts
432
+ import { Smith, parseTouchstone } from 'smithkit';
433
+
434
+ async function showMeasurement(file: File): Promise<Smith> {
435
+ const { samples, referenceImpedanceOhms } = parseTouchstone(await file.text());
436
+ const chart = new Smith(referenceImpedanceOhms);
437
+ chart.draw('#smith');
438
+ chart.addTrace(samples, { name: file.name });
439
+ return chart;
440
+ }
441
+ ```
442
+
443
+ Omitted options use Touchstone defaults: GHz, S, MA, 50 Ω. For an existing chart,
444
+ use `renormalizeSamples` when the file reference differs from
445
+ `chart.referenceImpedanceOhms`. The parser itself does not renormalize data.
446
+ Multiport data and Touchstone 2.x are not supported.
447
+
448
+ ## Development and license
449
+
450
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development, testing, release, and demo
451
+ hosting instructions. Licensed under [MIT](LICENSE).
452
+
453
+ ## Numerical limits
454
+
455
+ `Complex` uses JavaScript binary64 numbers. Multiplication and division scale
456
+ intermediate products to avoid premature overflow and underflow; logarithms,
457
+ square roots, and reciprocals also handle extreme finite components. Results still
458
+ round to binary64: unrepresentable magnitudes become infinity and tiny results
459
+ can underflow to signed zero. Division by zero and reciprocals at zero return
460
+ `[NaN, NaN]`. These operations reject non-finite operands with NaN components.
461
+ The principal logarithm of zero retains its `-Infinity` real limit, and signed
462
+ zeros select the side of square-root and logarithm branch cuts. `pow(z, 0)` is one,
463
+ including zero to the zeroth power; negative powers of zero are undefined.
464
+
465
+ RF helpers validate finite inputs and use `undefined` for unrepresentable complex
466
+ readings, including an overflowing impedance difference between markers. These
467
+ conventions do not imply arbitrary precision or numerical certification of every
468
+ transcendental operation.
@@ -0,0 +1,103 @@
1
+ import { TraceSamples } from './samples.js';
2
+ import type { ChartLayers, PeripheralScales } from './layers.js';
3
+ import type { SmithReading } from './rf.js';
4
+ import type { TraceOptions, TraceUpdateOptions, TraceInfo, MarkerSnapshot, MarkerComparison } from './measurements.js';
5
+ export declare enum SmithEventType {
6
+ Cursor = "cursor",
7
+ Marker = "marker",
8
+ MarkerDragStart = "marker-drag-start",
9
+ MarkerDragEnd = "marker-drag-end"
10
+ }
11
+ export type SmithEvent = {
12
+ type: SmithEventType.Cursor;
13
+ data: SmithReading | undefined;
14
+ } | {
15
+ type: SmithEventType.Marker | SmithEventType.MarkerDragStart | SmithEventType.MarkerDragEnd;
16
+ data: MarkerSnapshot;
17
+ };
18
+ export declare class Smith {
19
+ private referenceOhms;
20
+ private calcs;
21
+ private scalers;
22
+ private transform;
23
+ private zoomEnabled;
24
+ private applyingView;
25
+ private zoomBehavior;
26
+ private svg;
27
+ private resizeObserver;
28
+ private container;
29
+ private dataContainer;
30
+ private markerContainer;
31
+ private reactanceAxis;
32
+ private constResistance;
33
+ private constReactance;
34
+ private constConductance;
35
+ private constSusceptance;
36
+ private constSwrCircles;
37
+ private constQCircles;
38
+ private cursor;
39
+ private peripheralScaleRenderer;
40
+ private data;
41
+ private destroyed;
42
+ private mouseGesture;
43
+ private nextDatasetColor;
44
+ private nextTraceId;
45
+ private nextMarkerId;
46
+ private traceMetadata;
47
+ private markerIds;
48
+ private draggedMarkers;
49
+ private cursorBeforeMarkerDrag;
50
+ private listeners;
51
+ readonly layers: ChartLayers;
52
+ readonly peripheralScales: PeripheralScales;
53
+ constructor(referenceOhms?: number);
54
+ draw(target: string | HTMLElement): void;
55
+ /** Remove this chart and release its event handlers. Safe to call more than once. */
56
+ destroy(): void;
57
+ private updateViewportScale;
58
+ private assertAlive;
59
+ private createScalers;
60
+ private cursorMove;
61
+ private markerDragChanged;
62
+ private initCursor;
63
+ /** Last cursor position. Use cursor events to detect pointer leave. */
64
+ get cursorReading(): SmithReading;
65
+ private initializeZoom;
66
+ /** Enable or disable wheel, double-click, and mouse/touch zoom/pan. Keeps the current view. */
67
+ setZoomEnabled(enabled: boolean): void;
68
+ private applyView;
69
+ resetView(): void;
70
+ private onZoom;
71
+ private cursorContainer;
72
+ private drawReactanceAxis;
73
+ /** Add a named trace with one initial marker. Returns a chart-local, stable ID. */
74
+ addTrace(values: TraceSamples, options?: TraceOptions): string;
75
+ /** Detached metadata snapshots; IDs and marker display numbers survive removals. */
76
+ getTraces(): TraceInfo[];
77
+ private traceIndex;
78
+ private markerId;
79
+ private findMarker;
80
+ private validateTraceOptions;
81
+ setTraceOptions(id: string, options: TraceOptions): boolean;
82
+ /** Replace samples; by default each marker follows its nearest measured frequency. */
83
+ updateTrace(id: string, values: TraceSamples, options?: TraceUpdateOptions): boolean;
84
+ removeTrace(id: string): boolean;
85
+ /** Place a marker on an existing sample; no interpolation is performed. */
86
+ addMarker(traceId: string, sampleIndex?: number): string | undefined;
87
+ removeMarker(id: string): boolean;
88
+ setMarkerSample(id: string, sampleIndex: number): boolean;
89
+ /** Select the nearest measured frequency; ties choose the earliest input sample. */
90
+ setMarkerFrequency(id: string, frequencyHz: number): boolean;
91
+ getMarker(id: string): MarkerSnapshot | undefined;
92
+ /** Return B − A, or undefined if either marker no longer exists. */
93
+ compareMarkers(a: string, b: string): MarkerComparison | undefined;
94
+ /** Current positive real reference impedance, in ohms. */
95
+ get referenceImpedanceOhms(): number;
96
+ /** Renormalize all traces while retaining physical impedance, IDs, and selected sample indices. */
97
+ renormalize(referenceImpedanceOhms: number): void;
98
+ clearTraces(): void;
99
+ private createSmithData;
100
+ /** Subscribe to chart events. The returned function removes this subscription. */
101
+ onEvent(listener: (event: SmithEvent) => void): () => void;
102
+ private emit;
103
+ }