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
@@ -0,0 +1,562 @@
1
+ import * as d3 from 'd3';
2
+ import { MouseGesture } from './draw/MouseGesture.js';
3
+ import { SmithSvg } from './draw/SmithSvg.js';
4
+ import { SmithGroup } from './draw/SmithGroup.js';
5
+ import { SmithCircle } from './draw/SmithCircle.js';
6
+ import { SmithData } from './draw/SmithData.js';
7
+ import { SmithCursor } from './draw/SmithCursor.js';
8
+ import { ConstResistance } from './draw/ConstResistance.js';
9
+ import { ConstReactance } from './draw/ConstReactance.js';
10
+ import { ConstConductance } from './draw/ConstConductance.js';
11
+ import { ConstSusceptance } from './draw/ConstSusceptance.js';
12
+ import { ConstQCircles } from './draw/ConstQCircles.js';
13
+ import { ConstSwrCircles } from './draw/ConstSwrCircles.js';
14
+ import { SmithScaler } from './draw/SmithScaler.js';
15
+ import { SmithConstantCircle } from './SmithConstantCircle.js';
16
+ import { SmithArcsDefs } from './SmithArcsDefs.js';
17
+ import { Complex } from './complex/Complex.js';
18
+ import { SmithPeripheralScales } from './scales/SmithPeripheralScales.js';
19
+ import { gridLayer, circleLayer } from './layers.js';
20
+ import { renormalizeSamples } from './renormalization.js';
21
+ import { readReflection } from './rf.js';
22
+ import { compareMarkerReadings } from './measurements.js';
23
+ export var SmithEventType;
24
+ (function (SmithEventType) {
25
+ SmithEventType["Cursor"] = "cursor";
26
+ SmithEventType["Marker"] = "marker";
27
+ SmithEventType["MarkerDragStart"] = "marker-drag-start";
28
+ SmithEventType["MarkerDragEnd"] = "marker-drag-end";
29
+ })(SmithEventType || (SmithEventType = {}));
30
+ export class Smith {
31
+ referenceOhms;
32
+ calcs = new SmithConstantCircle();
33
+ scalers;
34
+ transform = d3.zoomIdentity;
35
+ zoomEnabled = true;
36
+ applyingView = false;
37
+ zoomBehavior = d3.zoom();
38
+ svg;
39
+ resizeObserver;
40
+ container;
41
+ dataContainer;
42
+ markerContainer;
43
+ reactanceAxis;
44
+ constResistance;
45
+ constReactance;
46
+ constConductance;
47
+ constSusceptance;
48
+ constSwrCircles;
49
+ constQCircles;
50
+ cursor;
51
+ peripheralScaleRenderer = new SmithPeripheralScales();
52
+ data = [];
53
+ destroyed = false;
54
+ mouseGesture = new MouseGesture();
55
+ nextDatasetColor = 0;
56
+ nextTraceId = 1;
57
+ nextMarkerId = 1;
58
+ traceMetadata = new WeakMap();
59
+ markerIds = new WeakMap();
60
+ draggedMarkers = new Set();
61
+ cursorBeforeMarkerDrag = null;
62
+ listeners = new Set();
63
+ layers;
64
+ peripheralScales;
65
+ constructor(referenceOhms = 50) {
66
+ this.referenceOhms = referenceOhms;
67
+ if (!Number.isFinite(referenceOhms) || referenceOhms <= 0) {
68
+ throw new RangeError('Reference impedance must be positive and finite.');
69
+ }
70
+ const viewBoxSize = 500;
71
+ const gridData = SmithArcsDefs.getData();
72
+ this.scalers = this.createScalers(viewBoxSize);
73
+ this.svg = new SmithSvg(viewBoxSize);
74
+ this.container = new SmithGroup();
75
+ this.constResistance = new ConstResistance({
76
+ data: gridData,
77
+ scaler: this.scalers.default,
78
+ showMinor: true,
79
+ });
80
+ this.constResistance.show();
81
+ this.constReactance = new ConstReactance({
82
+ data: gridData,
83
+ scaler: this.scalers.default,
84
+ showMinor: true,
85
+ });
86
+ this.constReactance.show();
87
+ this.constConductance = new ConstConductance({
88
+ data: gridData,
89
+ scaler: this.scalers.default,
90
+ showMinor: true,
91
+ });
92
+ this.constConductance.hide();
93
+ this.constSusceptance = new ConstSusceptance({
94
+ data: gridData,
95
+ scaler: this.scalers.default,
96
+ showMinor: true,
97
+ });
98
+ this.constSusceptance.hide();
99
+ this.constQCircles = new ConstQCircles(this.scalers.default);
100
+ this.constQCircles.hide();
101
+ this.constSwrCircles = new ConstSwrCircles(this.scalers.default);
102
+ this.constSwrCircles.hide();
103
+ this.cursor = this.initCursor();
104
+ const cursorContainer = this.cursorContainer();
105
+ this.reactanceAxis = this.drawReactanceAxis({
106
+ stroke: '#334155',
107
+ strokeWidth: '1',
108
+ fill: 'none',
109
+ });
110
+ this.dataContainer = new SmithGroup().attr('data-layer', 'samples');
111
+ this.markerContainer = new SmithGroup().attr('data-layer', 'markers');
112
+ // build chart
113
+ this.svg.append(this.container);
114
+ this.container.append(this.constConductance.draw().attr('data-layer', 'conductance'));
115
+ this.container.append(this.constSusceptance.draw().attr('data-layer', 'susceptance'));
116
+ this.container.append(this.constResistance.draw().attr('data-layer', 'resistance'));
117
+ this.container.append(this.constReactance.draw().attr('data-layer', 'reactance'));
118
+ this.container.append(this.constQCircles.draw());
119
+ this.container.append(this.constSwrCircles.draw());
120
+ this.container.append(this.peripheralScaleRenderer);
121
+ this.container.append(this.cursor.Group);
122
+ this.container.append(this.reactanceAxis);
123
+ this.container.append(cursorContainer);
124
+ this.container.append(this.dataContainer);
125
+ this.container.append(this.markerContainer);
126
+ const labels = new SmithGroup().attr('data-layer', 'labels').attr('pointer-events', 'none');
127
+ for (const [name, layer] of [
128
+ ['conductance', this.constConductance],
129
+ ['susceptance', this.constSusceptance],
130
+ ['resistance', this.constResistance],
131
+ ['reactance', this.constReactance],
132
+ ]) {
133
+ labels.append(layer.labels.attr('data-label-layer', name));
134
+ }
135
+ labels.append(this.peripheralScaleRenderer.labels);
136
+ this.container.append(labels);
137
+ const assertAlive = () => this.assertAlive();
138
+ this.layers = {
139
+ resistance: gridLayer(this.constResistance, assertAlive),
140
+ reactance: gridLayer(this.constReactance, assertAlive),
141
+ conductance: gridLayer(this.constConductance, assertAlive),
142
+ susceptance: gridLayer(this.constSusceptance, assertAlive),
143
+ q: circleLayer(this.constQCircles, 0, assertAlive),
144
+ vswr: circleLayer(this.constSwrCircles, 1, assertAlive),
145
+ };
146
+ this.peripheralScales = {
147
+ setVisible: (visible) => {
148
+ assertAlive();
149
+ if (visible) {
150
+ this.peripheralScaleRenderer.show();
151
+ }
152
+ else {
153
+ this.peripheralScaleRenderer.hide();
154
+ }
155
+ },
156
+ update: (gamma) => {
157
+ assertAlive();
158
+ this.peripheralScaleRenderer.update(gamma);
159
+ },
160
+ };
161
+ this.initializeZoom();
162
+ this.resizeObserver = new ResizeObserver(() => this.updateViewportScale());
163
+ this.resizeObserver.observe(this.svg.Node);
164
+ }
165
+ draw(target) {
166
+ this.assertAlive();
167
+ const host = typeof target === 'string' ? document.querySelector(target) : target;
168
+ if (!host) {
169
+ throw new Error('Chart container was not found.');
170
+ }
171
+ host.appendChild(this.svg.Node);
172
+ this.updateViewportScale();
173
+ }
174
+ /** Remove this chart and release its event handlers. Safe to call more than once. */
175
+ destroy() {
176
+ if (this.destroyed) {
177
+ return;
178
+ }
179
+ this.resizeObserver.disconnect();
180
+ this.listeners.clear();
181
+ this.clearTraces();
182
+ this.destroyed = true;
183
+ this.cursor.setMoveHandler(null);
184
+ this.mouseGesture.destroy();
185
+ this.zoomBehavior.on('start', null).on('zoom', null);
186
+ this.svg.Element.interrupt().on('.zoom', null);
187
+ this.svg.Element.selectAll('*').interrupt().on('.smithkit', null).on('.drag', null);
188
+ this.svg.Element.remove();
189
+ }
190
+ updateViewportScale() {
191
+ if (this.destroyed) {
192
+ return;
193
+ }
194
+ const matrix = this.svg.Node.getScreenCTM();
195
+ if (matrix) {
196
+ const scale = Math.hypot(matrix.a, matrix.b);
197
+ this.data.forEach((trace) => trace.setViewportScale(scale));
198
+ }
199
+ }
200
+ assertAlive() {
201
+ if (this.destroyed) {
202
+ throw new Error('This Smith chart has been destroyed. Create a new instance.');
203
+ }
204
+ }
205
+ createScalers(size) {
206
+ const impedance = new SmithScaler(d3.scaleLinear().domain([-1, 1]).range([0, size]), d3.scaleLinear().domain([1, -1]).range([0, size]), d3
207
+ .scaleLinear()
208
+ .domain([0, 1])
209
+ .range([0, size / 2]));
210
+ const admittance = new SmithScaler(d3.scaleLinear().domain([1, -1]).range([0, size]), d3.scaleLinear().domain([-1, 1]).range([0, size]), d3
211
+ .scaleLinear()
212
+ .domain([0, 1])
213
+ .range([0, size / 2]));
214
+ return { default: impedance, impedance, admittance };
215
+ }
216
+ cursorMove(p) {
217
+ if (this.draggedMarkers.size > 0) {
218
+ return;
219
+ }
220
+ this.cursor.Position = Complex.from(this.scalers.default.pointInvert(p));
221
+ }
222
+ markerDragChanged(marker, dragging) {
223
+ const wasDragging = this.draggedMarkers.size > 0;
224
+ if (dragging) {
225
+ this.draggedMarkers.add(marker);
226
+ }
227
+ else {
228
+ this.draggedMarkers.delete(marker);
229
+ }
230
+ const isDragging = this.draggedMarkers.size > 0;
231
+ if (isDragging === wasDragging) {
232
+ return;
233
+ }
234
+ if (isDragging) {
235
+ this.cursorBeforeMarkerDrag = this.svg.Node.style.getPropertyValue('cursor') || null;
236
+ this.svg.Element.style('cursor', 'grabbing').style('--smithkit-marker-cursor', 'grabbing');
237
+ this.cursor.hide();
238
+ this.emit({ type: SmithEventType.Cursor, data: undefined });
239
+ }
240
+ else {
241
+ this.svg.Element.style('cursor', () => this.cursorBeforeMarkerDrag).style('--smithkit-marker-cursor', null);
242
+ this.cursorBeforeMarkerDrag = null;
243
+ }
244
+ }
245
+ initCursor() {
246
+ const cursor = new SmithCursor(this.scalers.default);
247
+ cursor.Group.attr('class', 'smith-cursor');
248
+ cursor.setMoveHandler(() => {
249
+ this.emit({ type: SmithEventType.Cursor, data: this.cursorReading });
250
+ });
251
+ return cursor;
252
+ }
253
+ /** Last cursor position. Use cursor events to detect pointer leave. */
254
+ get cursorReading() {
255
+ return readReflection(this.cursor.Position, this.referenceImpedanceOhms);
256
+ }
257
+ initializeZoom() {
258
+ const zoom = this.zoomBehavior
259
+ .scaleExtent([0.6, 1000])
260
+ .filter((event) => this.zoomEnabled && (!event.ctrlKey || event.type === 'wheel') && !event.button)
261
+ .on('start', (event) => {
262
+ if (event.sourceEvent) {
263
+ this.mouseGesture.capture(event.sourceEvent, 'zoom');
264
+ }
265
+ })
266
+ .on('zoom', (event) => {
267
+ if (!this.zoomEnabled && !this.applyingView) {
268
+ // An already active mouse/touch gesture still needs its normal end event.
269
+ // Restore D3's view through its public API while that gesture finishes.
270
+ this.applyView(this.transform);
271
+ return;
272
+ }
273
+ this.onZoom(event.transform);
274
+ });
275
+ this.svg.Element.call(zoom);
276
+ this.resetView();
277
+ }
278
+ /** Enable or disable wheel, double-click, and mouse/touch zoom/pan. Keeps the current view. */
279
+ setZoomEnabled(enabled) {
280
+ this.assertAlive();
281
+ if (typeof enabled !== 'boolean') {
282
+ throw new TypeError('Zoom enabled must be a boolean.');
283
+ }
284
+ if (this.zoomEnabled === enabled) {
285
+ return;
286
+ }
287
+ this.zoomEnabled = enabled;
288
+ if (!enabled) {
289
+ this.svg.Element.interrupt();
290
+ }
291
+ }
292
+ applyView(transform) {
293
+ this.applyingView = true;
294
+ try {
295
+ this.svg.Element.call(this.zoomBehavior.transform, transform);
296
+ }
297
+ finally {
298
+ this.applyingView = false;
299
+ }
300
+ }
301
+ resetView() {
302
+ this.assertAlive();
303
+ const transform = d3.zoomIdentity.translate(62.5, 62.5).scale(0.75);
304
+ this.applyView(transform);
305
+ }
306
+ onZoom(transform) {
307
+ if (this.destroyed) {
308
+ return;
309
+ }
310
+ this.transform = transform;
311
+ this.container.Element.attr('transform', transform.toString());
312
+ this.data.forEach((d) => d.zoom(transform));
313
+ }
314
+ cursorContainer() {
315
+ const shape = this.drawReactanceAxis({ fill: 'transparent', stroke: 'none' });
316
+ shape.Element.style('pointer-events', 'all')
317
+ .on('pointermove.smithkit', (event) => {
318
+ this.cursorMove(d3.pointer(event));
319
+ })
320
+ .on('pointerleave.smithkit', () => {
321
+ this.cursor.hide();
322
+ this.emit({ type: SmithEventType.Cursor, data: undefined });
323
+ });
324
+ return shape;
325
+ }
326
+ drawReactanceAxis(opts) {
327
+ const c = this.calcs.resistanceCircle(0);
328
+ c.p[0] = this.scalers.default.x(c.p[0]);
329
+ c.p[1] = this.scalers.default.y(c.p[1]);
330
+ c.r = this.scalers.default.r(c.r);
331
+ return new SmithCircle(c, opts);
332
+ }
333
+ /** Add a named trace with one initial marker. Returns a chart-local, stable ID. */
334
+ addTrace(values, options = {}) {
335
+ this.assertAlive();
336
+ this.validateTraceOptions(options);
337
+ if (!values.length) {
338
+ throw new RangeError('A trace requires at least one sample.');
339
+ }
340
+ const data = this.createSmithData(values, this.nextDatasetColor, options);
341
+ this.nextDatasetColor++;
342
+ this.data.push(data);
343
+ this.updateViewportScale();
344
+ const id = this.traceMetadata.get(data).id;
345
+ this.setTraceOptions(id, options);
346
+ return id;
347
+ }
348
+ /** Detached metadata snapshots; IDs and marker display numbers survive removals. */
349
+ getTraces() {
350
+ return this.data.map((data) => ({
351
+ ...this.traceMetadata.get(data),
352
+ ...data.Style,
353
+ color: data.Color,
354
+ visible: data.Visible,
355
+ sampleCount: data.SampleCount,
356
+ markers: data.Markers.map((entry, index) => ({
357
+ id: this.markerId(entry.marker),
358
+ number: entry.number,
359
+ sampleIndex: data.markerSampleIndex(index),
360
+ })),
361
+ }));
362
+ }
363
+ traceIndex(id) {
364
+ return this.data.findIndex((data) => this.traceMetadata.get(data).id === id);
365
+ }
366
+ markerId(marker) {
367
+ let id = this.markerIds.get(marker);
368
+ if (!id) {
369
+ id = `marker-${this.nextMarkerId++}`;
370
+ this.markerIds.set(marker, id);
371
+ }
372
+ return id;
373
+ }
374
+ findMarker(id) {
375
+ for (const [datasetNo, data] of this.data.entries()) {
376
+ const markerNo = data.Markers.findIndex((entry) => this.markerId(entry.marker) === id);
377
+ if (markerNo >= 0) {
378
+ return { datasetNo, markerNo };
379
+ }
380
+ }
381
+ return;
382
+ }
383
+ validateTraceOptions(options) {
384
+ if (options.mode !== undefined && !['points', 'line', 'both'].includes(options.mode)) {
385
+ throw new TypeError('Trace mode must be points, line, or both.');
386
+ }
387
+ for (const key of ['lineWidth', 'pointRadius']) {
388
+ const value = options[key];
389
+ if (value !== undefined && (!Number.isFinite(value) || value <= 0)) {
390
+ throw new RangeError(`${key} must be positive and finite.`);
391
+ }
392
+ }
393
+ if (options.name !== undefined && (typeof options.name !== 'string' || !options.name.trim())) {
394
+ throw new TypeError('Trace name must not be empty.');
395
+ }
396
+ if (options.color !== undefined &&
397
+ (typeof options.color !== 'string' || !d3.color(options.color))) {
398
+ throw new TypeError('Trace color must be a solid CSS color.');
399
+ }
400
+ if (options.visible !== undefined && typeof options.visible !== 'boolean') {
401
+ throw new TypeError('Trace visibility must be a boolean.');
402
+ }
403
+ }
404
+ setTraceOptions(id, options) {
405
+ this.assertAlive();
406
+ const data = this.data[this.traceIndex(id)];
407
+ if (!data) {
408
+ return false;
409
+ }
410
+ this.validateTraceOptions(options);
411
+ data.setStyle(options);
412
+ if (options.name !== undefined) {
413
+ this.traceMetadata.get(data).name = options.name.trim();
414
+ }
415
+ if (options.color !== undefined) {
416
+ data.setColor(options.color);
417
+ }
418
+ if (options.visible !== undefined) {
419
+ data.setVisible(options.visible);
420
+ }
421
+ return true;
422
+ }
423
+ /** Replace samples; by default each marker follows its nearest measured frequency. */
424
+ updateTrace(id, values, options = {}) {
425
+ this.assertAlive();
426
+ const data = this.data[this.traceIndex(id)];
427
+ if (!data) {
428
+ return false;
429
+ }
430
+ const strategy = options.markerSelection ?? 'frequency';
431
+ if (!['frequency', 'sample-index', 'reflection'].includes(strategy)) {
432
+ throw new TypeError('Marker selection must be frequency, sample-index, or reflection.');
433
+ }
434
+ data.update(values, strategy);
435
+ return true;
436
+ }
437
+ removeTrace(id) {
438
+ this.assertAlive();
439
+ const index = this.traceIndex(id);
440
+ if (index < 0) {
441
+ return false;
442
+ }
443
+ this.data[index].destroy();
444
+ this.data.splice(index, 1);
445
+ return true;
446
+ }
447
+ /** Place a marker on an existing sample; no interpolation is performed. */
448
+ addMarker(traceId, sampleIndex = 0) {
449
+ this.assertAlive();
450
+ const data = this.data[this.traceIndex(traceId)];
451
+ if (!data) {
452
+ return;
453
+ }
454
+ const index = data.addMarker(sampleIndex);
455
+ return this.markerId(data.Markers[index].marker);
456
+ }
457
+ removeMarker(id) {
458
+ this.assertAlive();
459
+ const location = this.findMarker(id);
460
+ return location ? this.data[location.datasetNo].removeMarker(location.markerNo) : false;
461
+ }
462
+ setMarkerSample(id, sampleIndex) {
463
+ this.assertAlive();
464
+ const location = this.findMarker(id);
465
+ return location
466
+ ? this.data[location.datasetNo].setMarkerSample(location.markerNo, sampleIndex)
467
+ : false;
468
+ }
469
+ /** Select the nearest measured frequency; ties choose the earliest input sample. */
470
+ setMarkerFrequency(id, frequencyHz) {
471
+ this.assertAlive();
472
+ const location = this.findMarker(id);
473
+ return location
474
+ ? this.data[location.datasetNo].setMarkerFrequency(location.markerNo, frequencyHz)
475
+ : false;
476
+ }
477
+ getMarker(id) {
478
+ const location = this.findMarker(id);
479
+ if (!location) {
480
+ return;
481
+ }
482
+ const data = this.data[location.datasetNo];
483
+ const marker = data.Markers[location.markerNo];
484
+ return {
485
+ ...readReflection(Complex.from(...marker.selectedPoint.reflectionCoefficient), this.referenceImpedanceOhms),
486
+ frequencyHz: marker.selectedPoint.frequencyHz,
487
+ traceId: this.traceMetadata.get(data).id,
488
+ markerId: id,
489
+ markerNumber: marker.number,
490
+ sampleIndex: data.markerSampleIndex(location.markerNo),
491
+ };
492
+ }
493
+ /** Return B − A, or undefined if either marker no longer exists. */
494
+ compareMarkers(a, b) {
495
+ const first = this.getMarker(a);
496
+ const second = this.getMarker(b);
497
+ return first && second ? compareMarkerReadings(first, second) : undefined;
498
+ }
499
+ /** Current positive real reference impedance, in ohms. */
500
+ get referenceImpedanceOhms() {
501
+ return this.referenceOhms;
502
+ }
503
+ /** Renormalize all traces while retaining physical impedance, IDs, and selected sample indices. */
504
+ renormalize(referenceImpedanceOhms) {
505
+ this.assertAlive();
506
+ // Validate even with no traces, and prepare every result before changing any state.
507
+ renormalizeSamples([], this.referenceOhms, referenceImpedanceOhms);
508
+ if (referenceImpedanceOhms === this.referenceOhms) {
509
+ return;
510
+ }
511
+ const samples = this.data.map((data) => renormalizeSamples(data.Samples, this.referenceOhms, referenceImpedanceOhms));
512
+ this.data.forEach((data) => data.Markers.forEach((entry) => entry.marker.cancelDrag()));
513
+ this.referenceOhms = referenceImpedanceOhms;
514
+ this.data.forEach((data, index) => data.update(samples[index], 'sample-index'));
515
+ this.cursor.hide();
516
+ this.emit({ type: SmithEventType.Cursor, data: undefined });
517
+ }
518
+ clearTraces() {
519
+ this.assertAlive();
520
+ this.data.forEach((dataset) => dataset.destroy());
521
+ this.data = [];
522
+ }
523
+ createSmithData(values, dataset, options) {
524
+ const color = d3.schemeCategory10[(1 + dataset) % d3.schemeCategory10.length];
525
+ const data = new SmithData(values, color, this.transform, this.dataContainer, this.scalers.default, (marker, dragging) => {
526
+ this.markerDragChanged(marker, dragging);
527
+ const snapshot = this.getMarker(this.markerId(marker));
528
+ if (snapshot) {
529
+ this.emit({
530
+ type: dragging ? SmithEventType.MarkerDragStart : SmithEventType.MarkerDragEnd,
531
+ data: snapshot,
532
+ });
533
+ }
534
+ }, this.markerContainer, options);
535
+ const number = this.nextTraceId++;
536
+ this.traceMetadata.set(data, { id: `trace-${number}`, name: `Trace ${number}` });
537
+ data.setMarkerMoveHandler((index) => {
538
+ const marker = data.Markers[index];
539
+ const snapshot = marker && this.getMarker(this.markerId(marker.marker));
540
+ if (snapshot) {
541
+ this.emit({ type: SmithEventType.Marker, data: snapshot });
542
+ }
543
+ });
544
+ data.addMarker();
545
+ return data;
546
+ }
547
+ /** Subscribe to chart events. The returned function removes this subscription. */
548
+ onEvent(listener) {
549
+ this.assertAlive();
550
+ // Separate registrations of the same function remain independently removable.
551
+ const subscription = (event) => listener(event);
552
+ this.listeners.add(subscription);
553
+ return () => {
554
+ this.listeners.delete(subscription);
555
+ };
556
+ }
557
+ emit(event) {
558
+ for (const listener of this.listeners) {
559
+ listener(event);
560
+ }
561
+ }
562
+ }
@@ -0,0 +1,10 @@
1
+ import { createGridDefinitions } from './grid/definitions.js';
2
+ import { resistanceLabels, reactanceLabels } from './grid/labels.js';
3
+ export { SmithArcEntry } from './grid/types.js';
4
+ export type { SmithArcDef, SmithTickDef, SmithTicksShapes, SmithTicksData } from './grid/types.js';
5
+ export declare class SmithArcsDefs {
6
+ private constructor();
7
+ static getData: typeof createGridDefinitions;
8
+ static resistanceLabels: typeof resistanceLabels;
9
+ static reactanceLabels: typeof reactanceLabels;
10
+ }
@@ -0,0 +1,10 @@
1
+ import { createGridDefinitions } from './grid/definitions.js';
2
+ import { resistanceLabels, reactanceLabels } from './grid/labels.js';
3
+ // Compatibility facade; definitions and label rules are independent of SVG rendering.
4
+ export { SmithArcEntry } from './grid/types.js';
5
+ export class SmithArcsDefs {
6
+ constructor() { }
7
+ static getData = createGridDefinitions;
8
+ static resistanceLabels = resistanceLabels;
9
+ static reactanceLabels = reactanceLabels;
10
+ }
@@ -0,0 +1,62 @@
1
+ import { Circle } from './shapes/Circle.js';
2
+ import { Point } from './shapes/Point.js';
3
+ import { Complex } from './complex/Complex.js';
4
+ export declare class SmithConstantCircle {
5
+ Z0: number;
6
+ constructor(Z0?: number);
7
+ rflCoeffToImpedance(c: Complex): Complex | undefined;
8
+ impedanceToRflCoeff(c: Complex): Complex | undefined;
9
+ rflCoeffToAdmittance(c: Complex): Complex | undefined;
10
+ admittanceToRflCoeff(c: Complex): Complex | undefined;
11
+ private transform;
12
+ resistanceCircle(n: number): Circle;
13
+ reactanceCircle(n: number): Circle;
14
+ conductanceCircle(n: number): Circle;
15
+ susceptanceCircle(n: number): Circle;
16
+ constQCircle(q: number): Circle;
17
+ rflCoeffToSwr(rc: Complex): number;
18
+ rflCoeffToDBS(rc: Complex): number;
19
+ swrToRflCoeffEOrI(swr: number): number;
20
+ swrTodBS(swr: number): number;
21
+ dBSToSwr(dBS: number): number;
22
+ dBSToAbsRflCoeff(dBS: number): number;
23
+ rflCoeffToReturnLoss(rc: Complex): number;
24
+ returnLossToRflCoeffEOrI(rl: number): number;
25
+ /** One-way attenuation scale, referenced to a fully reflecting termination. */
26
+ rflCoeffToAttenuation(rc: Complex): number;
27
+ attenuationToRflCoeff(attenuation: number): number;
28
+ rflCoeffToMismatchLoss(rc: Complex): number;
29
+ mismatchLossToRflCoeffEOrI(ml: number): number;
30
+ rflCoeffEOrI(rc: Complex): number;
31
+ rflCoeffP(rc: Complex): number;
32
+ rflCoeffPToEOrI(p: number): number;
33
+ rflCoeffToQ(rc: Complex): number | undefined;
34
+ rflCoeffToTransmCoeffP(rc: Complex): number;
35
+ transmCoeffPToRflCoeffEOrI(tcp: number): number;
36
+ rflCoeffToTransmCoeff(rc: Complex): Complex;
37
+ transmCoeffToRflCoeff(tc: Complex): Complex;
38
+ rflCoeffToTransmCoeffEOrI(rc: Complex): number;
39
+ /** Current transmission magnitude using the voltage reflection coefficient. */
40
+ rflCoeffToCurrentTransmission(rc: Complex): number;
41
+ rflCoeffToSwPeakConstP(rc: Complex): number;
42
+ swPeakConstPToRflCoeffEOrI(swPeak: number): number;
43
+ rflCoeffToSwLossCoeff(rc: Complex): number;
44
+ swLossCoeffToRflCoeffEOrI(swl: number): number;
45
+ circleCircleIntersection(c1: Circle, c2: Circle): Point[];
46
+ isPointWithinCircle(p: Point, c: Circle): boolean;
47
+ normalize(c: Complex): Complex;
48
+ denormalize(c: Complex): Complex;
49
+ waveLengthFromFrequency(frequency: number): number;
50
+ frequencyFromWaveLength(waveLength: number): number;
51
+ magnitude(c: Complex): number;
52
+ dB(value: number): number;
53
+ reactanceToCapacitance(x: number, f: number): number | null;
54
+ capacitanceToReactance(C: number, f: number): Complex;
55
+ reactanceToInductance(x: number, f: number): number | null;
56
+ inductanceToReactance(L: number, f: number): Complex;
57
+ addImpedance(rc: Complex, imp: Complex): Complex | undefined;
58
+ addAdmittance(rc: Complex, adm: Complex): Complex | undefined;
59
+ tangentToCircleAngle(c: Circle, p: Point): number;
60
+ rad2deg(rad: number): number;
61
+ deg2rad(deg: number): number;
62
+ }