smithkit 0.1.0 → 0.2.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 (132) hide show
  1. package/README.md +124 -59
  2. package/dist/lib/MarkerMeasurements.d.ts +6 -0
  3. package/dist/lib/MarkerMeasurements.js +22 -0
  4. package/dist/lib/Smith.d.ts +9 -2
  5. package/dist/lib/Smith.js +78 -35
  6. package/dist/lib/SmithFormatter.d.ts +10 -0
  7. package/dist/lib/SmithFormatter.js +19 -0
  8. package/dist/lib/grid/CircleLayerControl.d.ts +15 -0
  9. package/dist/lib/grid/CircleLayerControl.js +40 -0
  10. package/dist/lib/{draw → grid}/ConstCircles.d.ts +6 -6
  11. package/dist/lib/{draw → grid}/ConstCircles.js +12 -5
  12. package/dist/lib/{draw → grid}/ConstQCircles.d.ts +2 -2
  13. package/dist/lib/{draw → grid}/ConstQCircles.js +3 -3
  14. package/dist/lib/{draw → grid}/ConstSwrCircles.d.ts +2 -2
  15. package/dist/lib/{draw → grid}/ConstSwrCircles.js +3 -3
  16. package/dist/lib/grid/GridDefinitions.d.ts +12 -0
  17. package/dist/lib/grid/GridDefinitions.js +138 -0
  18. package/dist/lib/grid/GridGeometry.d.ts +26 -0
  19. package/dist/lib/grid/GridGeometry.js +58 -0
  20. package/dist/lib/grid/GridLabels.d.ts +8 -0
  21. package/dist/lib/grid/GridLabels.js +45 -0
  22. package/dist/lib/grid/GridLayerControl.d.ts +12 -0
  23. package/dist/lib/grid/GridLayerControl.js +39 -0
  24. package/dist/lib/grid/LayerControl.d.ts +6 -0
  25. package/dist/lib/grid/LayerControl.js +16 -0
  26. package/dist/lib/{draw → grid}/SmithGridLayer.d.ts +5 -4
  27. package/dist/lib/{draw → grid}/SmithGridLayer.js +26 -9
  28. package/dist/lib/grid/types.d.ts +1 -1
  29. package/dist/lib/index.d.ts +8 -10
  30. package/dist/lib/index.js +5 -7
  31. package/dist/lib/{draw → interaction}/SmithCursor.d.ts +3 -3
  32. package/dist/lib/{draw → interaction}/SmithCursor.js +6 -6
  33. package/dist/lib/io/Touchstone.d.ts +10 -0
  34. package/dist/lib/io/Touchstone.js +75 -0
  35. package/dist/lib/layers.d.ts +2 -8
  36. package/dist/lib/layers.js +1 -73
  37. package/dist/lib/{complex → math}/Complex.d.ts +1 -0
  38. package/dist/lib/{complex → math}/Complex.js +1 -1
  39. package/dist/lib/math/geometry.d.ts +14 -0
  40. package/dist/lib/measurements.d.ts +2 -4
  41. package/dist/lib/measurements.js +1 -19
  42. package/dist/lib/rf/RfCalculations.d.ts +65 -0
  43. package/dist/lib/rf/RfCalculations.js +161 -0
  44. package/dist/lib/{SmithConstantCircle.d.ts → rf/SmithConstantCircle.d.ts} +2 -3
  45. package/dist/lib/{SmithConstantCircle.js → rf/SmithConstantCircle.js} +3 -4
  46. package/dist/lib/scales/PeripheralScaleDefinitions.d.ts +16 -0
  47. package/dist/lib/scales/PeripheralScaleDefinitions.js +54 -0
  48. package/dist/lib/scales/RadialScaleDefinitions.d.ts +15 -0
  49. package/dist/lib/scales/RadialScaleDefinitions.js +112 -0
  50. package/dist/lib/scales/SmithPeripheralScales.d.ts +7 -2
  51. package/dist/lib/scales/SmithPeripheralScales.js +65 -28
  52. package/dist/lib/scales/SmithScales.d.ts +3 -1
  53. package/dist/lib/scales/SmithScales.js +20 -6
  54. package/dist/lib/svg/LabelLayout.d.ts +17 -0
  55. package/dist/lib/svg/LabelLayout.js +145 -0
  56. package/dist/lib/{draw → svg}/SmithArc.d.ts +1 -1
  57. package/dist/lib/{draw → svg}/SmithCircle.d.ts +1 -1
  58. package/dist/lib/{draw → svg}/SmithLine.d.ts +1 -1
  59. package/dist/lib/{draw → svg}/SmithScaler.d.ts +1 -4
  60. package/dist/lib/{draw → svg}/SmithText.d.ts +1 -1
  61. package/dist/lib/svg/SvgExporter.d.ts +14 -0
  62. package/dist/lib/svg/SvgExporter.js +137 -0
  63. package/dist/lib/{draw → traces}/SmithData.d.ts +24 -35
  64. package/dist/lib/traces/SmithData.js +195 -0
  65. package/dist/lib/{draw → traces}/SmithMarker.d.ts +14 -3
  66. package/dist/lib/traces/SmithMarker.js +237 -0
  67. package/dist/lib/traces/TraceModel.d.ts +31 -0
  68. package/dist/lib/traces/TraceModel.js +123 -0
  69. package/dist/lib/traces/TraceRenderer.d.ts +33 -0
  70. package/dist/lib/traces/TraceRenderer.js +152 -0
  71. package/package.json +4 -1
  72. package/dist/lib/SmithArcsDefs.d.ts +0 -10
  73. package/dist/lib/SmithArcsDefs.js +0 -10
  74. package/dist/lib/components.d.ts +0 -14
  75. package/dist/lib/components.js +0 -26
  76. package/dist/lib/draw/SmithData.js +0 -366
  77. package/dist/lib/draw/SmithMarker.js +0 -122
  78. package/dist/lib/formatting.d.ts +0 -7
  79. package/dist/lib/formatting.js +0 -16
  80. package/dist/lib/grid/definitions.d.ts +0 -3
  81. package/dist/lib/grid/definitions.js +0 -123
  82. package/dist/lib/grid/geometry.d.ts +0 -20
  83. package/dist/lib/grid/geometry.js +0 -55
  84. package/dist/lib/grid/labels.d.ts +0 -3
  85. package/dist/lib/grid/labels.js +0 -41
  86. package/dist/lib/io/touchstone.d.ts +0 -7
  87. package/dist/lib/io/touchstone.js +0 -72
  88. package/dist/lib/math/complexRatio.d.ts +0 -3
  89. package/dist/lib/math/complexRatio.js +0 -30
  90. package/dist/lib/renormalization.d.ts +0 -10
  91. package/dist/lib/renormalization.js +0 -62
  92. package/dist/lib/rf.d.ts +0 -35
  93. package/dist/lib/rf.js +0 -74
  94. package/dist/lib/scales/peripheralScales.d.ts +0 -12
  95. package/dist/lib/scales/peripheralScales.js +0 -49
  96. package/dist/lib/scales/radialScales.d.ts +0 -12
  97. package/dist/lib/scales/radialScales.js +0 -109
  98. package/dist/lib/shapes/Arc.d.ts +0 -6
  99. package/dist/lib/shapes/Circle.d.ts +0 -5
  100. package/dist/lib/shapes/Circle.js +0 -1
  101. package/dist/lib/shapes/Line.d.ts +0 -5
  102. package/dist/lib/shapes/Line.js +0 -1
  103. package/dist/lib/shapes/Point.d.ts +0 -1
  104. package/dist/lib/shapes/Point.js +0 -1
  105. /package/dist/lib/{draw → grid}/ConstConductance.d.ts +0 -0
  106. /package/dist/lib/{draw → grid}/ConstConductance.js +0 -0
  107. /package/dist/lib/{draw → grid}/ConstReactance.d.ts +0 -0
  108. /package/dist/lib/{draw → grid}/ConstReactance.js +0 -0
  109. /package/dist/lib/{draw → grid}/ConstResistance.d.ts +0 -0
  110. /package/dist/lib/{draw → grid}/ConstResistance.js +0 -0
  111. /package/dist/lib/{draw → grid}/ConstSusceptance.d.ts +0 -0
  112. /package/dist/lib/{draw → grid}/ConstSusceptance.js +0 -0
  113. /package/dist/lib/{arcs → grid}/Tick.d.ts +0 -0
  114. /package/dist/lib/{arcs → grid}/Tick.js +0 -0
  115. /package/dist/lib/{draw → interaction}/MouseGesture.d.ts +0 -0
  116. /package/dist/lib/{draw → interaction}/MouseGesture.js +0 -0
  117. /package/dist/lib/{draw/SmithDrawOptions.js → math/geometry.js} +0 -0
  118. /package/dist/lib/{draw → svg}/SmithArc.js +0 -0
  119. /package/dist/lib/{draw → svg}/SmithCircle.js +0 -0
  120. /package/dist/lib/{draw → svg}/SmithDrawOptions.d.ts +0 -0
  121. /package/dist/lib/{shapes/Arc.js → svg/SmithDrawOptions.js} +0 -0
  122. /package/dist/lib/{draw → svg}/SmithGroup.d.ts +0 -0
  123. /package/dist/lib/{draw → svg}/SmithGroup.js +0 -0
  124. /package/dist/lib/{draw → svg}/SmithLine.js +0 -0
  125. /package/dist/lib/{draw → svg}/SmithPath.d.ts +0 -0
  126. /package/dist/lib/{draw → svg}/SmithPath.js +0 -0
  127. /package/dist/lib/{draw → svg}/SmithScaler.js +0 -0
  128. /package/dist/lib/{draw → svg}/SmithShape.d.ts +0 -0
  129. /package/dist/lib/{draw → svg}/SmithShape.js +0 -0
  130. /package/dist/lib/{draw → svg}/SmithSvg.d.ts +0 -0
  131. /package/dist/lib/{draw → svg}/SmithSvg.js +0 -0
  132. /package/dist/lib/{draw → svg}/SmithText.js +0 -0
package/README.md CHANGED
@@ -5,44 +5,35 @@ interactive SVG Smith charts, RF calculations, and S11 measurement exploration.
5
5
 
6
6
  [Try the interactive demo](https://rosek86.github.io/smithkit/).
7
7
 
8
+ Upgrading from 0.1.x? Version 0.2.0 groups calculation, parsing, formatting, and
9
+ comparison functions into classes. See the [migration guide](https://github.com/rosek86/smithkit/blob/v0.2.0/docs/migration-0.2.md)
10
+ for the changed imports.
11
+
8
12
  ## Features
9
13
 
10
14
  - Labeled resistance, reactance, conductance, and susceptance grids.
11
15
  - Configurable constant-Q and constant-VSWR circles.
12
16
  - Zoom, pan, view reset, and cursor readings.
13
- - Named traces with colors, visibility, and draggable markers that snap to samples.
17
+ - Named traces with colors, visibility, and keyboard/touch/mouse markers that snap to samples.
14
18
  - Marker comparisons: frequency, complex impedance, and wrapped phase differences.
15
19
  - Physical impedance/admittance conversions and DOM-independent RF readings.
16
20
  - Twelve parameter scales mounted independently of chart zoom.
17
21
  - Four peripheral rulers for phase and electrical length.
18
22
  - One-port Touchstone 1.x import with RI, MA, and DB representations.
23
+ - Standalone SVG export of the current chart and radial scales.
19
24
  - ESM modules and TypeScript declarations.
20
25
 
21
26
  ## Installation
22
27
 
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:
28
+ Install from npm:
30
29
 
31
30
  ```sh
32
- git clone https://github.com/rosek86/smithkit.git
33
- cd smithkit
34
- npm ci
35
- npm pack
31
+ npm install smithkit
36
32
  ```
37
33
 
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.
34
+ Browser applications need SVG, ES2022, and an ESM-compatible build tool.
35
+ No library stylesheet is required. To build the library from source, see
36
+ [Contributing](https://github.com/rosek86/smithkit/blob/main/CONTRIBUTING.md).
46
37
 
47
38
  For a complete application using the installed package, see the
48
39
  [standalone integration example](https://github.com/rosek86/smithkit/tree/main/examples/basic).
@@ -77,11 +68,11 @@ must be finite and non-negative; both coordinates must be finite. A trace must
77
68
  contain at least one sample.
78
69
 
79
70
  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.
71
+ samples with a mouse or touch, scroll to zoom, and drag the chart to pan. Markers
72
+ and their focus indicators stay above grid labels and sample points. `chart.resetView()` restores the initial view, including peripheral rulers.
82
73
 
83
74
  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.
75
+ the package and using its calculation or parsing methods does not require a DOM.
85
76
 
86
77
  Zoom is enabled by default. For a fixed view, call `chart.setZoomEnabled(false)` before or after mounting.
87
78
  It disables wheel/double-click zoom and mouse/touch panning and pinch zoom while
@@ -103,6 +94,25 @@ chart.draw('#smith');
103
94
  container throws. Call `chart.destroy()` on unmount; it removes the SVG, releases
104
95
  event handlers, and cancels queued notifications. Repeated destruction is safe.
105
96
 
97
+ ### Keyboard and touch markers
98
+
99
+ Use Tab/Shift+Tab to focus a marker. Arrow keys move through samples in input order;
100
+ Right/Up advances and Left/Down goes back. Shift+Arrow or Page Up/Down moves ten
101
+ samples, and Home/End selects the first/last sample. Navigation clamps at the ends
102
+ and also works when chart zoom is disabled. A single-sample trace remains focusable
103
+ for reading, with its slider marked disabled.
104
+
105
+ A focused marker has a contrasting outline and exposes its trace name, marker
106
+ number, sample position, and frequency to assistive technology. Hidden traces are
107
+ excluded from keyboard focus. `chart.focusMarker(id)` allows a separate marker
108
+ selector to focus the matching chart control. The demo provides a **Focus on chart**
109
+ button beside its marker selector.
110
+
111
+ Markers have a 44 CSS px circular hit area and an 18 CSS px triangle, kept constant
112
+ through zoom and container resizing. A marker touch gesture does not pan the chart;
113
+ cancelling it releases the drag state. Touch target areas may overlap for nearby
114
+ markers; use keyboard focus or the demo selector to reach an obscured marker.
115
+
106
116
  ## Traces and markers
107
117
 
108
118
  Trace and marker IDs are opaque strings, unique within a chart and never reused
@@ -135,6 +145,7 @@ chart.removeMarker(markerB);
135
145
  | `clearTraces()` | Removes all traces and their markers. |
136
146
  | `addMarker(traceId, sampleIndex = 0)` | Returns a marker ID, or `undefined` if the trace is missing. |
137
147
  | `getMarker(id)` | Returns a `MarkerSnapshot`, or `undefined` if missing. |
148
+ | `focusMarker(id)` | Moves keyboard focus to a mounted, visible marker; returns `false` if missing or not focusable. |
138
149
  | `setMarkerSample(id, sampleIndex)` | Selects an existing sample and queues a marker notification. |
139
150
  | `removeMarker(id)` | Removes one marker without renumbering the remaining markers. |
140
151
  | `compareMarkers(a, b)` | Returns B − A, or `undefined` if either marker is missing. |
@@ -206,7 +217,7 @@ const unsubscribe = chart.onEvent((event) => {
206
217
  console.log(event.data?.impedanceOhms?.toVector());
207
218
  return;
208
219
  }
209
- // Marker, MarkerDragStart, and MarkerDragEnd always carry a MarkerSnapshot.
220
+ // Marker, MarkerSelect, MarkerDragStart, and MarkerDragEnd carry a MarkerSnapshot.
210
221
  console.log(event.data.markerId, event.data.frequencyHz);
211
222
  });
212
223
 
@@ -216,7 +227,12 @@ chart.destroy();
216
227
  ```
217
228
 
218
229
  The event types are `Cursor` (`'cursor'`), `Marker` (`'marker'`),
230
+ `MarkerSelect` (`'marker-select'`),
219
231
  `MarkerDragStart` (`'marker-drag-start'`), and `MarkerDragEnd` (`'marker-drag-end'`).
232
+ `MarkerSelect` fires synchronously when a marker receives focus or is engaged by
233
+ keyboard navigation or a pointer press. Use it to select the corresponding
234
+ readout; it does not indicate a position change. Keyboard changes use the normal
235
+ queued `Marker` event and do not emit drag lifecycle events.
220
236
  Cursor movement is suspended during marker dragging. Drag start/end events fire
221
237
  synchronously; marker position notifications are queued and report the latest
222
238
  snapshot at delivery. Before delivery, repeated updates to the same marker coalesce
@@ -227,7 +243,7 @@ Trace metadata changes do not emit marker events; refresh metadata after mutatio
227
243
  Destruction suppresses application callbacks.
228
244
 
229
245
  `chart.cursorReading` returns the last cursor position. Cursor and marker readings
230
- share `SmithReading`; `readReflection(gamma, referenceImpedanceOhms = 50)` produces
246
+ share `SmithReading`; `RfCalculations.readReflection(gamma, referenceImpedanceOhms = 50)` produces
231
247
  the same reading without constructing a chart.
232
248
 
233
249
  | Fields | Units / meaning |
@@ -253,7 +269,7 @@ infinite scalar limit is preserved: perfect match gives infinite return loss;
253
269
  For active loads (\|Γ\| > 1), VSWR, standing-wave quantities, reflection loss,
254
270
  attenuation, and power transmission are `undefined`. Other quantities remain
255
271
  available. Non-finite complex inputs or invalid reference impedances throw
256
- `RangeError` in the public RF functions. Unrepresentable complex conversion
272
+ `RangeError` in the public RF methods. Unrepresentable complex conversion
257
273
  results are `undefined`.
258
274
 
259
275
  ## Selecting a marker by frequency
@@ -273,22 +289,22 @@ measurement frequency; dragging and sample selection keep that field synchronize
273
289
  ## Calculations without a chart
274
290
 
275
291
  ```ts
276
- import { Complex, readReflection, impedanceToReflection } from 'smithkit';
292
+ import { Complex, RfCalculations } from 'smithkit';
277
293
 
278
- const gamma = impedanceToReflection(Complex.from(75, 25), 50)!;
279
- const reading = readReflection(gamma, 50);
294
+ const gamma = RfCalculations.impedanceToReflection(Complex.from(75, 25), 50)!;
295
+ const reading = RfCalculations.readReflection(gamma, 50);
280
296
  console.log(reading.impedanceOhms?.toVector()); // Approximately [75, 25].
281
297
  console.log(reading.admittanceSiemens?.toVector());
282
298
  ```
283
299
 
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`. |
300
+ | Static method | Input and output |
301
+ | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
302
+ | `RfCalculations.reflectionToImpedance(gamma, referenceImpedanceOhms = 50)` | Γ → physical Z in Ω. |
303
+ | `RfCalculations.impedanceToReflection(impedanceOhms, referenceImpedanceOhms = 50)` | Physical Z in Ω → Γ. |
304
+ | `RfCalculations.reflectionToAdmittance(gamma, referenceImpedanceOhms = 50)` | Γ → physical Y in S. |
305
+ | `RfCalculations.admittanceToReflection(admittanceSiemens, referenceImpedanceOhms = 50)` | Physical Y in S → Γ. |
306
+ | `RfCalculations.readReflection(gamma, referenceImpedanceOhms = 50)` | Complete `SmithReading`. |
307
+ | `MarkerMeasurements.compare(a, b)` | Compare readings without a chart. Each input needs `frequencyHz`, `reflectionCoefficient`, and `impedanceOhms`. |
292
308
 
293
309
  Comparison results contain `frequencyDeltaHz`, `impedanceDeltaOhms`, and
294
310
  `phaseDeltaDegrees`. Phase differences use B − A wrapped to **[−180°, 180°)**:
@@ -299,27 +315,27 @@ a point comparison, not sweep phase unwrapping or group delay.
299
315
 
300
316
  `Complex.from(re, im)` constructs complex numbers. Read components through `re` and `im`. Arithmetic (`add`, `sub`,
301
317
  `mul`, `div`, `pow`) returns new values. The class also provides `abs`, `arg`
302
- (radians), and static functions such as `Complex.conj`, `Complex.sqrt`,
318
+ (radians), and static methods such as `Complex.conj`, `Complex.sqrt`,
303
319
  `Complex.exp`, `Complex.log`, and trigonometric/hyperbolic functions and inverses. `toVector()` returns `[real, imaginary]`; `toString(dp)` formats
304
320
  a Cartesian value. See the exported declarations for all operations.
305
321
 
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
322
+ The `SmithFormatter` class provides static methods: `SmithFormatter.number(value)` (three significant
323
+ digits and SI prefixes), `SmithFormatter.complex(value, unit?, decimalPlaces?)`, and
324
+ `SmithFormatter.polar(value, unit?, decimalPlaces?)` (phase in degrees). They work
309
325
  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'`.
326
+ `SmithFormatter.number` includes spacing so you can append a unit, e.g. `SmithFormatter.number(1e9) + 'Hz'`.
311
327
 
312
- `reactanceToComponent(reactanceOhms, frequencyHz)` calculates an ideal equivalent
328
+ `RfCalculations.reactanceToComponent(reactanceOhms, frequencyHz)` calculates an ideal equivalent
313
329
  series component without formatting or constructing a chart:
314
330
 
315
331
  ```ts
316
- import { reactanceToComponent, formatNumber } from 'smithkit';
332
+ import { RfCalculations, SmithFormatter } from 'smithkit';
317
333
 
318
- const component = reactanceToComponent(-50, 1e9);
334
+ const component = RfCalculations.reactanceToComponent(-50, 1e9);
319
335
  if (component?.kind === 'capacitor') {
320
- console.log(formatNumber(component.capacitanceFarads) + 'F'); // 3.18 pF
336
+ console.log(SmithFormatter.number(component.capacitanceFarads) + 'F'); // 3.18 pF
321
337
  } else if (component?.kind === 'inductor') {
322
- console.log(formatNumber(component.inductanceHenries) + 'H');
338
+ console.log(SmithFormatter.number(component.inductanceHenries) + 'H');
323
339
  }
324
340
  ```
325
341
 
@@ -347,9 +363,10 @@ chart.layers.vswr.setVisible(true);
347
363
  The four grid layers support `setVisible`, `setMinorVisible`, and
348
364
  `setStyle(Partial<GridStyle>)`. Style fields: `stroke`, `majorWidth`, `minorWidth`,
349
365
  `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.
366
+ in CSS pixels. Font size is a positive finite base size in chart units; adaptive
367
+ layout raises it when needed to keep the font size at least 9 CSS pixels in the
368
+ default view. Partial updates retain other settings. Invalid numeric styles throw
369
+ before any property is changed.
353
370
 
354
371
  `layers.q` and `layers.vswr` support `setVisible`,
355
372
  `setStyle({ stroke?, strokeWidth? })`, `addValue`, and `removeValue`.
@@ -359,8 +376,23 @@ Duplicate additions and removal of absent values have no effect.
359
376
 
360
377
  Grid labels use normalized values; readings use physical units. Strokes default
361
378
  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.
379
+ under zoom. Layer controls do not expose drawing objects or SVG nodes.
380
+
381
+ Grid labels adapt automatically to the chart's rendered size,
382
+ including static charts with zoom disabled. The layout prioritizes key values,
383
+ hides labels that overlap or extend beyond the default view, and prioritizes zero
384
+ and the highest labeled axis value. It recalculates on resize and layer visibility
385
+ or style changes. Zoom and pan preserve the chosen labels and scale them smoothly
386
+ with the grid. This changes only labels; grid lines and ticks remain unchanged.
387
+ SVG exports preserve the current label layout.
388
+
389
+ For small static charts, hide minor grid lines and, when unnecessary, peripheral
390
+ scales using the controls above. Very small charts necessarily show fewer values;
391
+ use a larger container for detailed readings. Peripheral labels retain their full
392
+ wording and scale with the chart. Their rulers form two outlined bands: phase
393
+ (transmission/reflection) and electrical length (toward load/generator), with a
394
+ shared tick circle inside each band.
395
+ The independent `SmithScales` panel uses its own responsive layout.
364
396
 
365
397
  ## Parameter scales
366
398
 
@@ -418,12 +450,12 @@ result rejects the operation without changing Z₀ or data. Active marker drags
418
450
  cursor indicators are cleared, and marker readings are queued with the new values.
419
451
  Changing to the current Z₀ has no effect. Mutations after destruction throw.
420
452
 
421
- Two DOM-independent helpers are also exported:
453
+ Two DOM-independent methods are available on `RfCalculations`:
422
454
 
423
- - `renormalizeReflection(gamma, fromOhms, toOhms)` returns the new complex Γ or
455
+ - `RfCalculations.renormalizeReflection(gamma, fromOhms, toOhms)` returns the new complex Γ or
424
456
  `undefined` for a singular/unrepresentable result. Exact open and short limits
425
457
  remain Γ = +1 and −1. References must be positive, finite, real ohm values.
426
- - `renormalizeSamples(samples, fromOhms, toOhms)` returns a new sample array in
458
+ - `RfCalculations.renormalizeSamples(samples, fromOhms, toOhms)` returns a new sample array in
427
459
  the same order, preserving frequencies. Invalid samples or a singular result
428
460
  throw `RangeError`; the input is never modified.
429
461
 
@@ -438,15 +470,15 @@ multiport renormalization are outside the supported scope.
438
470
 
439
471
  ## Touchstone import
440
472
 
441
- `parseTouchstone(text)` returns `{ samples, referenceImpedanceOhms }`. It accepts
473
+ `Touchstone.parse(text)` returns `{ samples, referenceImpedanceOhms }`. It accepts
442
474
  one-port Touchstone 1.x, converts RI/MA/DB into Cartesian Γ and all frequencies
443
475
  into Hz, and throws for unsupported or invalid input.
444
476
 
445
477
  ```ts
446
- import { Smith, parseTouchstone } from 'smithkit';
478
+ import { Smith, Touchstone } from 'smithkit';
447
479
 
448
480
  async function showMeasurement(file: File): Promise<Smith> {
449
- const { samples, referenceImpedanceOhms } = parseTouchstone(await file.text());
481
+ const { samples, referenceImpedanceOhms } = Touchstone.parse(await file.text());
450
482
  const chart = new Smith(referenceImpedanceOhms);
451
483
  chart.draw('#smith');
452
484
  chart.addTrace(samples, { name: file.name });
@@ -455,10 +487,43 @@ async function showMeasurement(file: File): Promise<Smith> {
455
487
  ```
456
488
 
457
489
  Omitted options use Touchstone defaults: GHz, S, MA, 50 Ω. For an existing chart,
458
- use `renormalizeSamples` when the file reference differs from
490
+ use `RfCalculations.renormalizeSamples` when the file reference differs from
459
491
  `chart.referenceImpedanceOhms`. The parser itself does not renormalize data.
460
492
  Multiport data and Touchstone 2.x are not supported.
461
493
 
494
+ ## SVG export
495
+
496
+ `chart.toSvg()` and `scales.toSvg()` return SVG strings. Call them after `draw()`
497
+ while the component is mounted and has a non-zero rendered size; exporting before
498
+ mounting, from a `display: none` container, or after destruction throws an error.
499
+
500
+ ```ts
501
+ const svg = chart.toSvg();
502
+ const blob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });
503
+ const url = URL.createObjectURL(blob);
504
+ const link = document.createElement('a');
505
+ link.href = url;
506
+ link.download = 'smith-chart.svg';
507
+ document.body.appendChild(link);
508
+ link.click();
509
+ link.remove();
510
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
511
+ ```
512
+
513
+ The chart export preserves the current zoom/pan, layer visibility, labels, traces,
514
+ markers, and visible indicators. It captures the rendered points, including dense
515
+ trace point reduction; it is an image export, not a measurement-data export.
516
+ The scale export combines all twelve axes into one SVG using their current layout
517
+ and displayed readings. To export a marker reading, call `scales.update()` with
518
+ that marker's reflection coefficient before `scales.toSvg()`.
519
+
520
+ Files have explicit pixel dimensions and a transparent background. Presentation
521
+ styles are inlined so the application's stylesheet is not required. Text remains
522
+ editable, and curved captions retain their internal path references. Font files
523
+ and surrounding HTML controls/backgrounds are not embedded. Downloading is the
524
+ consumer's responsibility; the library does not create files or modify live nodes.
525
+ The demo provides separate **Export chart SVG** and **Export scales SVG** buttons.
526
+
462
527
  ## Development and license
463
528
 
464
529
  See [CONTRIBUTING.md](CONTRIBUTING.md) for development, testing, release, and demo
@@ -0,0 +1,6 @@
1
+ import type { MarkerSnapshot, MarkerComparison } from './measurements.js';
2
+ export declare class MarkerMeasurements {
3
+ private constructor();
4
+ /** Compare B against A without a chart or browser. Frequencies must be finite and non-negative. */
5
+ static compare(a: Pick<MarkerSnapshot, 'frequencyHz' | 'impedanceOhms' | 'reflectionCoefficient'>, b: Pick<MarkerSnapshot, 'frequencyHz' | 'impedanceOhms' | 'reflectionCoefficient'>): MarkerComparison;
6
+ }
@@ -0,0 +1,22 @@
1
+ export class MarkerMeasurements {
2
+ constructor() { }
3
+ /** Compare B against A without a chart or browser. Frequencies must be finite and non-negative. */
4
+ static compare(a, b) {
5
+ if (![a.frequencyHz, b.frequencyHz].every((frequencyHz) => Number.isFinite(frequencyHz) && frequencyHz >= 0)) {
6
+ throw new RangeError('Marker frequencies must be finite and non-negative.');
7
+ }
8
+ const isFinite = (value) => Number.isFinite(value.re) && Number.isFinite(value.im);
9
+ const hasPhase = (gamma) => isFinite(gamma) && (gamma.re !== 0 || gamma.im !== 0);
10
+ const phaseDelta = hasPhase(a.reflectionCoefficient) && hasPhase(b.reflectionCoefficient)
11
+ ? ((b.reflectionCoefficient.arg() - a.reflectionCoefficient.arg()) * 180) / Math.PI
12
+ : undefined;
13
+ const impedanceDelta = a.impedanceOhms && b.impedanceOhms && isFinite(a.impedanceOhms) && isFinite(b.impedanceOhms)
14
+ ? b.impedanceOhms.sub(a.impedanceOhms)
15
+ : undefined;
16
+ return {
17
+ frequencyDeltaHz: b.frequencyHz - a.frequencyHz,
18
+ impedanceDeltaOhms: impedanceDelta && isFinite(impedanceDelta) ? impedanceDelta : undefined,
19
+ phaseDeltaDegrees: phaseDelta === undefined ? undefined : ((phaseDelta + 540) % 360) - 180,
20
+ };
21
+ }
22
+ }
@@ -1,10 +1,11 @@
1
1
  import { TraceSamples } from './samples.js';
2
2
  import type { ChartLayers, PeripheralScales } from './layers.js';
3
- import type { SmithReading } from './rf.js';
3
+ import type { SmithReading } from './rf/RfCalculations.js';
4
4
  import type { TraceOptions, TraceUpdateOptions, TraceInfo, MarkerSnapshot, MarkerComparison } from './measurements.js';
5
5
  export declare enum SmithEventType {
6
6
  Cursor = "cursor",
7
7
  Marker = "marker",
8
+ MarkerSelect = "marker-select",
8
9
  MarkerDragStart = "marker-drag-start",
9
10
  MarkerDragEnd = "marker-drag-end"
10
11
  }
@@ -12,19 +13,21 @@ export type SmithEvent = {
12
13
  type: SmithEventType.Cursor;
13
14
  data: SmithReading | undefined;
14
15
  } | {
15
- type: SmithEventType.Marker | SmithEventType.MarkerDragStart | SmithEventType.MarkerDragEnd;
16
+ type: SmithEventType.Marker | SmithEventType.MarkerSelect | SmithEventType.MarkerDragStart | SmithEventType.MarkerDragEnd;
16
17
  data: MarkerSnapshot;
17
18
  };
18
19
  export declare class Smith {
19
20
  private referenceOhms;
20
21
  private calcs;
21
22
  private scalers;
23
+ private readonly defaultTransform;
22
24
  private transform;
23
25
  private zoomEnabled;
24
26
  private applyingView;
25
27
  private zoomBehavior;
26
28
  private svg;
27
29
  private resizeObserver;
30
+ private readonly labelLayout;
28
31
  private container;
29
32
  private dataContainer;
30
33
  private markerContainer;
@@ -52,6 +55,8 @@ export declare class Smith {
52
55
  readonly peripheralScales: PeripheralScales;
53
56
  constructor(referenceOhms?: number);
54
57
  draw(target: string | HTMLElement): void;
58
+ /** Export the current mounted view as standalone SVG with a transparent background. */
59
+ toSvg(): string;
55
60
  /** Remove this chart and release its event handlers. Safe to call more than once. */
56
61
  destroy(): void;
57
62
  private updateViewportScale;
@@ -85,6 +90,8 @@ export declare class Smith {
85
90
  /** Place a marker on an existing sample; no interpolation is performed. */
86
91
  addMarker(traceId: string, sampleIndex?: number): string | undefined;
87
92
  removeMarker(id: string): boolean;
93
+ /** Focus a mounted, visible marker for keyboard interaction. */
94
+ focusMarker(id: string): boolean;
88
95
  setMarkerSample(id: string, sampleIndex: number): boolean;
89
96
  /** Select the nearest measured frequency; ties choose the earliest input sample. */
90
97
  setMarkerFrequency(id: string, frequencyHz: number): boolean;