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
@@ -0,0 +1,186 @@
1
+ import { SmithGroup } from './SmithGroup.js';
2
+ import { SmithCircle } from './SmithCircle.js';
3
+ import { SmithLine } from './SmithLine.js';
4
+ import { SmithArc } from './SmithArc.js';
5
+ import { SmithConstantCircle } from '../SmithConstantCircle.js';
6
+ import { Complex } from '../complex/Complex.js';
7
+ export class SmithCursor {
8
+ scaler;
9
+ epsilon = 1.5e-4;
10
+ pendingMove;
11
+ drawingOpts = {
12
+ point: { radius: 5, color: 'red' },
13
+ impedance: { width: 1, color: 'red' },
14
+ admittance: { width: 1, color: 'green' },
15
+ };
16
+ calcs = new SmithConstantCircle();
17
+ zClipCircle = this.calcs.resistanceCircle(0);
18
+ yClipCircle = this.calcs.conductanceCircle(0);
19
+ rc = Complex.from(0, 0);
20
+ group;
21
+ point;
22
+ impedance;
23
+ admittance;
24
+ moveHandler = null;
25
+ constructor(scaler) {
26
+ this.scaler = scaler;
27
+ this.group = new SmithGroup().attr('pointer-events', 'none');
28
+ this.impedance = {
29
+ group: new SmithGroup({
30
+ stroke: this.drawingOpts.impedance.color,
31
+ strokeWidth: this.drawingOpts.impedance.width.toString(),
32
+ fill: 'none',
33
+ }),
34
+ resistance: { circle: new SmithCircle({ p: [0, 0], r: 1 }) },
35
+ reactance: {
36
+ arc: new SmithArc([1, 0], [0, 1], 5, false, false),
37
+ line: new SmithLine([-1, 0], [1, 0]),
38
+ },
39
+ };
40
+ this.impedance.resistance.circle.nonScalingStroke();
41
+ this.impedance.reactance.arc.nonScalingStroke();
42
+ this.impedance.reactance.line.nonScalingStroke();
43
+ this.admittance = {
44
+ group: new SmithGroup({
45
+ stroke: this.drawingOpts.admittance.color,
46
+ strokeWidth: this.drawingOpts.admittance.width.toString(),
47
+ fill: 'none',
48
+ }),
49
+ conductance: { circle: new SmithCircle({ p: [0, 0], r: 1 }) },
50
+ susceptance: {
51
+ arc: new SmithArc([1, 0], [0, 1], 5, false, false),
52
+ line: new SmithLine([-1, 0], [1, 0]),
53
+ },
54
+ };
55
+ this.admittance.conductance.circle.nonScalingStroke();
56
+ this.admittance.susceptance.arc.nonScalingStroke();
57
+ this.admittance.susceptance.line.nonScalingStroke();
58
+ this.point = new SmithLine([0, 0], [0, 0], {
59
+ stroke: this.drawingOpts.point.color,
60
+ strokeWidth: this.drawingOpts.point.radius.toString(),
61
+ fill: 'none',
62
+ });
63
+ this.point.nonScalingStroke();
64
+ this.point.setStrokeLinecap('round');
65
+ this.hide();
66
+ this.appendAll();
67
+ }
68
+ appendAll() {
69
+ this.impedance.group
70
+ .append(this.impedance.resistance.circle)
71
+ .append(this.impedance.reactance.arc)
72
+ .append(this.impedance.reactance.line);
73
+ this.admittance.group
74
+ .append(this.admittance.conductance.circle)
75
+ .append(this.admittance.susceptance.arc)
76
+ .append(this.admittance.susceptance.line);
77
+ this.group.append(this.admittance.group).append(this.impedance.group).append(this.point);
78
+ }
79
+ get Group() {
80
+ return this.group;
81
+ }
82
+ get Position() {
83
+ return this.rc;
84
+ }
85
+ set Position(rc) {
86
+ if (this.isWithinPlot(rc) === false) {
87
+ this.hide();
88
+ return;
89
+ }
90
+ this.rc = rc;
91
+ const z = this.calcs.rflCoeffToImpedance(rc);
92
+ const y = this.calcs.rflCoeffToAdmittance(rc);
93
+ this.movePoint(rc);
94
+ this.moveResistance(z);
95
+ this.moveReactance(z);
96
+ this.moveConductance(y);
97
+ this.moveSusceptance(y);
98
+ this.show();
99
+ this.cancelPendingMove();
100
+ this.pendingMove = setTimeout(() => {
101
+ this.pendingMove = undefined;
102
+ if (this.moveHandler) {
103
+ this.moveHandler(rc);
104
+ }
105
+ }, 0);
106
+ }
107
+ isWithinPlot(p) {
108
+ return this.calcs.isPointWithinCircle([p.re, p.im], this.zClipCircle);
109
+ }
110
+ movePoint(rc) {
111
+ const p = rc.toVector();
112
+ this.point.move(this.scaler.point(p), this.scaler.point(p));
113
+ }
114
+ moveResistance(z) {
115
+ if (z === undefined) {
116
+ this.impedance.resistance.circle.hide();
117
+ return;
118
+ }
119
+ this.impedance.resistance.circle.show();
120
+ const c = this.scaler.circle(this.calcs.resistanceCircle(z.re));
121
+ this.impedance.resistance.circle.move(c);
122
+ }
123
+ moveReactance(z) {
124
+ if (z === undefined || Math.abs(z.im) < this.epsilon) {
125
+ this.impedance.reactance.line.move(this.scaler.point([-1, 0]), this.scaler.point([1, 0]));
126
+ this.impedance.reactance.line.show();
127
+ this.impedance.reactance.arc.hide();
128
+ return;
129
+ }
130
+ this.impedance.reactance.line.hide();
131
+ this.impedance.reactance.arc.show();
132
+ const c = this.calcs.reactanceCircle(z.im);
133
+ const p = this.calcs.circleCircleIntersection(c, this.zClipCircle);
134
+ p[0] = this.scaler.point(p[0]);
135
+ p[1] = this.scaler.point(p[1]);
136
+ c.r = this.scaler.r(c.r);
137
+ this.impedance.reactance.arc.move(p[0], p[1], c.r, false, true);
138
+ }
139
+ moveConductance(y) {
140
+ if (y === undefined) {
141
+ this.admittance.conductance.circle.hide();
142
+ return;
143
+ }
144
+ this.admittance.conductance.circle.show();
145
+ const c = this.scaler.circle(this.calcs.conductanceCircle(y.re));
146
+ this.admittance.conductance.circle.move(c);
147
+ }
148
+ moveSusceptance(y) {
149
+ if (y === undefined || Math.abs(y.im) < this.epsilon) {
150
+ this.admittance.susceptance.line.move(this.scaler.point([-1, 0]), this.scaler.point([1, 0]));
151
+ this.admittance.susceptance.line.show();
152
+ this.admittance.susceptance.arc.hide();
153
+ return;
154
+ }
155
+ this.admittance.susceptance.line.hide();
156
+ this.admittance.susceptance.arc.show();
157
+ const c = this.calcs.susceptanceCircle(y.im);
158
+ const p = this.calcs.circleCircleIntersection(c, this.yClipCircle);
159
+ p[0] = this.scaler.point(p[0]);
160
+ p[1] = this.scaler.point(p[1]);
161
+ c.r = this.scaler.r(c.r);
162
+ this.admittance.susceptance.arc.move(p[0], p[1], c.r, false, true);
163
+ }
164
+ show() {
165
+ this.group.show();
166
+ }
167
+ hide() {
168
+ this.cancelPendingMove();
169
+ this.group.hide();
170
+ }
171
+ cancelPendingMove() {
172
+ if (this.pendingMove !== undefined) {
173
+ clearTimeout(this.pendingMove);
174
+ this.pendingMove = undefined;
175
+ }
176
+ }
177
+ setMoveHandler(handler) {
178
+ this.moveHandler = handler;
179
+ if (handler === null) {
180
+ this.cancelPendingMove();
181
+ }
182
+ }
183
+ setDrawOptions(opts) {
184
+ this.drawingOpts = opts;
185
+ }
186
+ }
@@ -0,0 +1,66 @@
1
+ import { SmithGroup } from './SmithGroup.js';
2
+ import type { TraceStyle, MarkerSelectionStrategy } from '../measurements.js';
3
+ import { SmithMarker } from './SmithMarker.js';
4
+ import { SmithScaler } from './SmithScaler.js';
5
+ import { TraceSamples, TraceSample } from '../samples.js';
6
+ interface Marker {
7
+ marker: SmithMarker;
8
+ selectedPoint: TraceSample;
9
+ number: number;
10
+ }
11
+ interface Transform {
12
+ x: number;
13
+ y: number;
14
+ k: number;
15
+ }
16
+ export declare class SmithData {
17
+ private data;
18
+ private color;
19
+ private transform;
20
+ private fgContainer;
21
+ private scaler;
22
+ private markerDragHandler?;
23
+ private markerContainer;
24
+ private style;
25
+ private viewportScale;
26
+ private destroyed;
27
+ private visible;
28
+ private pendingEvents;
29
+ private group;
30
+ private markersCount;
31
+ private markers;
32
+ private handler;
33
+ constructor(data: TraceSamples, color: string, transform: Transform, fgContainer: SmithGroup, scaler: SmithScaler, markerDragHandler?: ((marker: SmithMarker, dragging: boolean) => void) | undefined, markerContainer?: SmithGroup, style?: Partial<TraceStyle>);
34
+ private drawTrace;
35
+ private renderPoints;
36
+ setViewportScale(scale: number): void;
37
+ zoom(transform: Transform): void;
38
+ private zoomDataPoints;
39
+ private zoomAllMarkers;
40
+ addMarker(sampleIndex?: number): number;
41
+ removeMarker(index: number): boolean;
42
+ setMarkerSample(index: number, sampleIndex: number): boolean;
43
+ setMarkerFrequency(index: number, frequencyHz: number): boolean;
44
+ private nearestFrequencyIndex;
45
+ private validateSampleIndex;
46
+ get Samples(): TraceSamples;
47
+ get SampleCount(): number;
48
+ get Color(): string;
49
+ get Visible(): boolean;
50
+ markerSampleIndex(index: number): number;
51
+ setColor(color: string): void;
52
+ get Style(): TraceStyle;
53
+ setStyle(options: Partial<TraceStyle>): void;
54
+ private redraw;
55
+ setVisible(visible: boolean): void;
56
+ private copySamples;
57
+ private notifyMarker;
58
+ private cancelEvents;
59
+ update(values: TraceSamples, strategy?: MarkerSelectionStrategy): void;
60
+ destroy(): void;
61
+ getMarker(index: number): Marker | undefined;
62
+ get Markers(): Marker[];
63
+ setMarkerMoveHandler(handler: (marker: number, data: TraceSample) => void): void;
64
+ private findClosestPointTo;
65
+ }
66
+ export {};
@@ -0,0 +1,366 @@
1
+ import { SmithGroup } from './SmithGroup.js';
2
+ import { line } from 'd3';
3
+ import { SmithMarker } from './SmithMarker.js';
4
+ export class SmithData {
5
+ data;
6
+ color;
7
+ transform;
8
+ fgContainer;
9
+ scaler;
10
+ markerDragHandler;
11
+ markerContainer;
12
+ style;
13
+ viewportScale = 1;
14
+ destroyed = false;
15
+ visible = true;
16
+ pendingEvents = new Map();
17
+ group;
18
+ markersCount = 0;
19
+ markers = [];
20
+ handler = null;
21
+ constructor(data, color, transform, fgContainer, scaler, markerDragHandler, markerContainer = fgContainer, style = {}) {
22
+ this.data = data;
23
+ this.color = color;
24
+ this.transform = transform;
25
+ this.fgContainer = fgContainer;
26
+ this.scaler = scaler;
27
+ this.markerDragHandler = markerDragHandler;
28
+ this.markerContainer = markerContainer;
29
+ this.style = {
30
+ mode: style.mode ?? 'points',
31
+ lineWidth: style.lineWidth ?? 2,
32
+ pointRadius: style.pointRadius ?? 2,
33
+ };
34
+ this.data = this.copySamples(data);
35
+ this.group = this.drawTrace(this.data);
36
+ this.group.attr('pointer-events', 'none');
37
+ this.fgContainer.append(this.group);
38
+ this.zoomDataPoints();
39
+ }
40
+ drawTrace(data) {
41
+ const group = new SmithGroup({
42
+ stroke: 'none',
43
+ strokeWidth: 'none',
44
+ fill: this.color,
45
+ });
46
+ group.attr('data-role', 'samples');
47
+ group.attr('data-mode', this.style.mode);
48
+ if (this.style.mode !== 'points') {
49
+ const path = line()
50
+ .x((sample) => this.scaler.x(sample.reflectionCoefficient[0]))
51
+ .y((sample) => this.scaler.y(sample.reflectionCoefficient[1]));
52
+ group.Element.append('path')
53
+ .attr('class', 'trace-line')
54
+ .attr('d', path(data))
55
+ .attr('fill', 'none')
56
+ .attr('stroke', this.color)
57
+ .attr('stroke-width', this.style.lineWidth)
58
+ .attr('vector-effect', 'non-scaling-stroke')
59
+ .attr('stroke-linejoin', 'round')
60
+ .attr('stroke-linecap', 'round');
61
+ }
62
+ if (this.style.mode !== 'line') {
63
+ this.renderPoints(group, data);
64
+ }
65
+ return group;
66
+ }
67
+ renderPoints(group, data) {
68
+ let visible = data;
69
+ if (data.length > 5000) {
70
+ const cells = new Set();
71
+ const cellSize = Math.max(0.5, this.style.pointRadius) / this.viewportScale;
72
+ const extent = this.scaler.x(1);
73
+ const margin = this.style.pointRadius / this.viewportScale;
74
+ visible = data.filter((sample) => {
75
+ const x = this.scaler.x(sample.reflectionCoefficient[0]) * this.transform.k + this.transform.x;
76
+ const y = this.scaler.y(sample.reflectionCoefficient[1]) * this.transform.k + this.transform.y;
77
+ if (x < -margin || y < -margin || x > extent + margin || y > extent + margin) {
78
+ return false;
79
+ }
80
+ const cell = `${Math.floor(x / cellSize)},${Math.floor(y / cellSize)}`;
81
+ if (cells.has(cell)) {
82
+ return false;
83
+ }
84
+ cells.add(cell);
85
+ return true;
86
+ });
87
+ }
88
+ group.Element.selectAll('circle')
89
+ .data(visible)
90
+ .join('circle')
91
+ .attr('cx', (sample) => this.scaler.x(sample.reflectionCoefficient[0]))
92
+ .attr('cy', (sample) => this.scaler.y(sample.reflectionCoefficient[1]))
93
+ .attr('r', this.style.pointRadius / (this.transform.k * this.viewportScale));
94
+ }
95
+ setViewportScale(scale) {
96
+ if (scale === this.viewportScale || !Number.isFinite(scale) || scale <= 0) {
97
+ return;
98
+ }
99
+ this.viewportScale = scale;
100
+ this.zoomDataPoints();
101
+ }
102
+ zoom(transform) {
103
+ if (this.destroyed) {
104
+ return;
105
+ }
106
+ this.transform = transform;
107
+ this.zoomDataPoints();
108
+ this.zoomAllMarkers();
109
+ }
110
+ zoomDataPoints() {
111
+ const k = this.transform.k;
112
+ if (this.style.mode === 'line') {
113
+ return;
114
+ }
115
+ if (this.data.length > 5000) {
116
+ this.renderPoints(this.group, this.data);
117
+ }
118
+ else {
119
+ this.group.Element.selectAll('circle').attr('r', this.style.pointRadius / (k * this.viewportScale));
120
+ }
121
+ }
122
+ zoomAllMarkers() {
123
+ const k = this.transform.k;
124
+ for (const marker of this.markers) {
125
+ const entry = marker.selectedPoint;
126
+ if (entry) {
127
+ marker.marker.zoom(k);
128
+ }
129
+ }
130
+ }
131
+ addMarker(sampleIndex = 0) {
132
+ if (this.destroyed) {
133
+ throw new Error('This dataset has been removed.');
134
+ }
135
+ this.validateSampleIndex(sampleIndex);
136
+ const markerIndex = this.markersCount++;
137
+ const marker = new SmithMarker(markerIndex + 1, this.color, (dragging) => this.markerDragHandler?.(marker, dragging));
138
+ const markerDesc = {
139
+ marker,
140
+ selectedPoint: this.data[sampleIndex],
141
+ number: markerIndex + 1,
142
+ };
143
+ this.markers.push(markerDesc);
144
+ marker.Element.attr('data-role', 'marker');
145
+ this.markerContainer.append(marker);
146
+ marker.setDragHandler((mp) => {
147
+ const dp = this.findClosestPointTo(this.scaler.pointInvert(mp));
148
+ if (markerDesc.selectedPoint === dp) {
149
+ return;
150
+ }
151
+ markerDesc.selectedPoint = dp;
152
+ marker.move(this.scaler.point(dp.reflectionCoefficient));
153
+ marker.zoom(this.transform.k);
154
+ this.notifyMarker(markerDesc);
155
+ });
156
+ marker.zoom(this.transform.k);
157
+ marker.show();
158
+ marker.move(this.scaler.point(markerDesc.selectedPoint.reflectionCoefficient));
159
+ marker.Element.style('display', () => (this.visible ? null : 'none'));
160
+ this.notifyMarker(markerDesc);
161
+ return this.markers.length - 1;
162
+ }
163
+ removeMarker(index) {
164
+ const entry = this.markers[index];
165
+ if (!entry) {
166
+ return false;
167
+ }
168
+ clearTimeout(this.pendingEvents.get(entry));
169
+ this.pendingEvents.delete(entry);
170
+ entry.marker.destroy();
171
+ this.markers.splice(index, 1);
172
+ return true;
173
+ }
174
+ setMarkerSample(index, sampleIndex) {
175
+ const entry = this.markers[index];
176
+ if (!entry) {
177
+ return false;
178
+ }
179
+ this.validateSampleIndex(sampleIndex);
180
+ entry.selectedPoint = this.data[sampleIndex];
181
+ entry.marker.move(this.scaler.point(entry.selectedPoint.reflectionCoefficient));
182
+ this.notifyMarker(entry);
183
+ return true;
184
+ }
185
+ setMarkerFrequency(index, frequencyHz) {
186
+ if (!this.markers[index]) {
187
+ return false;
188
+ }
189
+ if (!Number.isFinite(frequencyHz) || frequencyHz < 0) {
190
+ throw new RangeError('Marker frequency must be finite and non-negative.');
191
+ }
192
+ return this.setMarkerSample(index, this.nearestFrequencyIndex(frequencyHz));
193
+ }
194
+ nearestFrequencyIndex(frequencyHz) {
195
+ let closest = 0;
196
+ let distance = Math.abs(this.data[0].frequencyHz - frequencyHz);
197
+ for (let i = 1; i < this.data.length; i++) {
198
+ const nextDistance = Math.abs(this.data[i].frequencyHz - frequencyHz);
199
+ if (nextDistance < distance) {
200
+ closest = i;
201
+ distance = nextDistance;
202
+ }
203
+ }
204
+ return closest;
205
+ }
206
+ validateSampleIndex(index) {
207
+ if (!Number.isInteger(index) || index < 0 || index >= this.data.length) {
208
+ throw new RangeError('Sample index is outside this trace.');
209
+ }
210
+ }
211
+ get Samples() {
212
+ return this.data;
213
+ }
214
+ get SampleCount() {
215
+ return this.data.length;
216
+ }
217
+ get Color() {
218
+ return this.color;
219
+ }
220
+ get Visible() {
221
+ return this.visible;
222
+ }
223
+ markerSampleIndex(index) {
224
+ const entry = this.markers[index];
225
+ return entry ? this.data.indexOf(entry.selectedPoint) : -1;
226
+ }
227
+ setColor(color) {
228
+ this.color = color;
229
+ this.group.attr('fill', color);
230
+ this.group.Element.select('.trace-line').attr('stroke', color);
231
+ this.markers.forEach(({ marker }) => marker.setColor(color));
232
+ }
233
+ get Style() {
234
+ return { ...this.style };
235
+ }
236
+ setStyle(options) {
237
+ const next = {
238
+ mode: options.mode ?? this.style.mode,
239
+ lineWidth: options.lineWidth ?? this.style.lineWidth,
240
+ pointRadius: options.pointRadius ?? this.style.pointRadius,
241
+ };
242
+ if (next.mode === this.style.mode &&
243
+ next.lineWidth === this.style.lineWidth &&
244
+ next.pointRadius === this.style.pointRadius) {
245
+ return;
246
+ }
247
+ const modeChanged = next.mode !== this.style.mode;
248
+ this.style = next;
249
+ if (modeChanged) {
250
+ this.redraw();
251
+ }
252
+ else {
253
+ this.group.Element.select('.trace-line').attr('stroke-width', next.lineWidth);
254
+ this.zoomDataPoints();
255
+ }
256
+ }
257
+ redraw() {
258
+ const group = this.drawTrace(this.data).attr('pointer-events', 'none');
259
+ // Replace in place to retain trace ordering beneath the separate marker layer.
260
+ this.group.Node.replaceWith(group.Node);
261
+ this.group = group;
262
+ this.group.Element.style('display', () => (this.visible ? null : 'none'));
263
+ }
264
+ setVisible(visible) {
265
+ this.visible = visible;
266
+ this.group.Element.style('display', () => (visible ? null : 'none'));
267
+ this.markers.forEach(({ marker }) => {
268
+ if (!visible) {
269
+ marker.cancelDrag();
270
+ }
271
+ marker.Element.style('display', () => (visible ? null : 'none'));
272
+ });
273
+ }
274
+ copySamples(values) {
275
+ if (!values.length ||
276
+ values.some(({ frequencyHz, reflectionCoefficient }) => !Number.isFinite(frequencyHz) ||
277
+ frequencyHz < 0 ||
278
+ reflectionCoefficient.length !== 2 ||
279
+ !reflectionCoefficient.every(Number.isFinite))) {
280
+ throw new RangeError('A dataset requires samples with a non-negative finite frequency and two finite coordinates.');
281
+ }
282
+ return values.map(({ frequencyHz, reflectionCoefficient }) => ({
283
+ frequencyHz,
284
+ reflectionCoefficient: [reflectionCoefficient[0], reflectionCoefficient[1]],
285
+ }));
286
+ }
287
+ notifyMarker(entry) {
288
+ if (this.pendingEvents.has(entry)) {
289
+ return;
290
+ }
291
+ const timer = setTimeout(() => {
292
+ this.pendingEvents.delete(entry);
293
+ const index = this.markers.indexOf(entry);
294
+ if (!this.destroyed && index >= 0) {
295
+ this.handler?.(index, entry.selectedPoint);
296
+ }
297
+ }, 0);
298
+ this.pendingEvents.set(entry, timer);
299
+ }
300
+ cancelEvents() {
301
+ this.pendingEvents.forEach((timer) => clearTimeout(timer));
302
+ this.pendingEvents.clear();
303
+ }
304
+ update(values, strategy = 'frequency') {
305
+ if (this.destroyed) {
306
+ throw new Error('This dataset has been removed.');
307
+ }
308
+ const samples = this.copySamples(values);
309
+ const selectedIndices = this.markers.map((entry) => this.data.indexOf(entry.selectedPoint));
310
+ this.cancelEvents();
311
+ this.data = samples;
312
+ const group = this.drawTrace(samples).attr('pointer-events', 'none');
313
+ this.group.Element.remove();
314
+ this.group = group;
315
+ this.group.Element.style('display', () => (this.visible ? null : 'none'));
316
+ this.fgContainer.append(group);
317
+ this.zoomDataPoints();
318
+ this.markers.forEach((entry, index) => {
319
+ if (strategy === 'sample-index') {
320
+ entry.selectedPoint = samples[Math.min(selectedIndices[index], samples.length - 1)];
321
+ }
322
+ else if (strategy === 'reflection') {
323
+ entry.selectedPoint = this.findClosestPointTo(entry.selectedPoint.reflectionCoefficient);
324
+ }
325
+ else {
326
+ entry.selectedPoint = samples[this.nearestFrequencyIndex(entry.selectedPoint.frequencyHz)];
327
+ }
328
+ entry.marker.move(this.scaler.point(entry.selectedPoint.reflectionCoefficient));
329
+ this.notifyMarker(entry);
330
+ });
331
+ }
332
+ destroy() {
333
+ if (this.destroyed) {
334
+ return;
335
+ }
336
+ this.destroyed = true;
337
+ this.cancelEvents();
338
+ this.handler = null;
339
+ this.markers.forEach(({ marker }) => marker.destroy());
340
+ this.markers = [];
341
+ this.markerDragHandler = undefined;
342
+ this.data = [];
343
+ this.group.Element.remove();
344
+ }
345
+ getMarker(index) {
346
+ return this.markers[index];
347
+ }
348
+ get Markers() {
349
+ return this.markers.slice();
350
+ }
351
+ setMarkerMoveHandler(handler) {
352
+ this.handler = handler;
353
+ }
354
+ findClosestPointTo(p) {
355
+ const dist = (p1, p2) => {
356
+ const xd = p1[0] - p2[0];
357
+ const yd = p1[1] - p2[1];
358
+ return Math.hypot(xd, yd);
359
+ };
360
+ return this.data.reduce((prev, curr) => {
361
+ const d1 = dist(p, prev.reflectionCoefficient);
362
+ const d2 = dist(p, curr.reflectionCoefficient);
363
+ return d1 <= d2 ? prev : curr;
364
+ });
365
+ }
366
+ }
@@ -0,0 +1,5 @@
1
+ export interface SmithDrawOptions {
2
+ stroke?: string;
3
+ strokeWidth?: string;
4
+ fill?: string;
5
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { ConstCircles } from './ConstCircles.js';
2
+ import { SmithGroup } from './SmithGroup.js';
3
+ import type { SmithScaler } from './SmithScaler.js';
4
+ import type { SmithTicksData } from '../grid/types.js';
5
+ import { type GridKind } from '../grid/geometry.js';
6
+ export interface GridLayerParams {
7
+ scaler: SmithScaler;
8
+ showMinor: boolean;
9
+ data: SmithTicksData;
10
+ }
11
+ /** SVG adapter shared by the four constant impedance/admittance layers. */
12
+ export declare class SmithGridLayer extends ConstCircles {
13
+ private kind;
14
+ protected major: SmithGroup;
15
+ protected minor: SmithGroup;
16
+ protected texts: SmithGroup;
17
+ constructor(params: GridLayerParams, kind: GridKind);
18
+ private drawGrid;
19
+ }
@@ -0,0 +1,48 @@
1
+ import { ConstCircles } from './ConstCircles.js';
2
+ import { SmithGroup } from './SmithGroup.js';
3
+ import { SmithText } from './SmithText.js';
4
+ import { gridGeometry, gridLabel } from '../grid/geometry.js';
5
+ import { resistanceLabels, reactanceLabels } from '../grid/labels.js';
6
+ /** SVG adapter shared by the four constant impedance/admittance layers. */
7
+ export class SmithGridLayer extends ConstCircles {
8
+ kind;
9
+ major;
10
+ minor;
11
+ texts;
12
+ constructor(params, kind) {
13
+ super(params.scaler);
14
+ this.kind = kind;
15
+ const real = kind === 'resistance' || kind === 'conductance';
16
+ const definitions = params.data[real ? 'resistance' : 'reactance'];
17
+ this.major = this.drawGrid(definitions.major, this.opts.majorWidth);
18
+ this.minor = this.drawGrid(definitions.minor, this.opts.minorWidth);
19
+ this.texts = new SmithGroup()
20
+ .attr('stroke', 'none')
21
+ .attr('font-size', '7')
22
+ .attr('font-family', 'Verdana');
23
+ for (const tick of real ? resistanceLabels() : reactanceLabels()) {
24
+ const label = gridLabel(kind, tick.definition);
25
+ this.texts.append(new SmithText(this.scaler.point(label.point), label.text, {
26
+ rotate: label.rotate,
27
+ dx: this.scaler.r(label.dx).toString(),
28
+ dy: this.scaler.r(label.dy).toString(),
29
+ textAnchor: label.textAnchor,
30
+ dominantBaseline: label.dominantBaseline,
31
+ }));
32
+ }
33
+ this.build();
34
+ if (!params.showMinor) {
35
+ this.minor.hide();
36
+ }
37
+ }
38
+ drawGrid(definitions, width) {
39
+ const geometry = gridGeometry(this.kind, definitions);
40
+ const group = new SmithGroup();
41
+ this.drawShapes(group.Element, this.opts.stroke, width, {
42
+ lines: geometry.lines.map((line) => this.scaler.line(line)),
43
+ circles: geometry.circles.map((circle) => this.scaler.circle(circle)),
44
+ arcs: geometry.arcs.map((arc) => this.scaleArc(this.scaler, arc)),
45
+ });
46
+ return group;
47
+ }
48
+ }