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