smithkit 0.1.1 → 0.3.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.
- package/README.md +291 -42
- package/dist/lib/MarkerMeasurements.d.ts +6 -0
- package/dist/lib/MarkerMeasurements.js +22 -0
- package/dist/lib/Smith.d.ts +18 -35
- package/dist/lib/Smith.js +107 -275
- package/dist/lib/SmithFormatter.d.ts +10 -0
- package/dist/lib/SmithFormatter.js +19 -0
- package/dist/lib/appearance/SvgTheme.d.ts +6 -0
- package/dist/lib/appearance/SvgTheme.js +21 -0
- package/dist/lib/appearance/Theme.d.ts +8 -0
- package/dist/lib/appearance/Theme.js +120 -0
- package/dist/lib/appearance/types.d.ts +55 -0
- package/dist/lib/grid/CircleLayerControl.d.ts +15 -0
- package/dist/lib/grid/CircleLayerControl.js +40 -0
- package/dist/lib/{draw → grid}/ConstCircles.d.ts +4 -9
- package/dist/lib/{draw → grid}/ConstCircles.js +8 -22
- package/dist/lib/{draw → grid}/ConstQCircles.d.ts +2 -2
- package/dist/lib/{draw → grid}/ConstQCircles.js +4 -4
- package/dist/lib/{draw → grid}/ConstSwrCircles.d.ts +2 -2
- package/dist/lib/{draw → grid}/ConstSwrCircles.js +4 -4
- package/dist/lib/grid/GridDefinitions.d.ts +18 -0
- package/dist/lib/grid/GridDefinitions.js +167 -0
- package/dist/lib/grid/GridGeometry.d.ts +26 -0
- package/dist/lib/grid/GridGeometry.js +58 -0
- package/dist/lib/grid/GridLabels.d.ts +8 -0
- package/dist/lib/grid/GridLabels.js +45 -0
- package/dist/lib/grid/GridLayerControl.d.ts +13 -0
- package/dist/lib/grid/GridLayerControl.js +48 -0
- package/dist/lib/grid/LayerControl.d.ts +6 -0
- package/dist/lib/grid/LayerControl.js +16 -0
- package/dist/lib/grid/SmithGridLayer.d.ts +26 -0
- package/dist/lib/grid/SmithGridLayer.js +95 -0
- package/dist/lib/grid/types.d.ts +1 -1
- package/dist/lib/index.d.ts +11 -11
- package/dist/lib/index.js +5 -7
- package/dist/lib/{draw → interaction}/SmithCursor.d.ts +3 -20
- package/dist/lib/{draw → interaction}/SmithCursor.js +12 -20
- package/dist/lib/io/Touchstone.d.ts +10 -0
- package/dist/lib/io/Touchstone.js +75 -0
- package/dist/lib/layers.d.ts +7 -9
- package/dist/lib/layers.js +1 -73
- package/dist/lib/{complex → math}/Complex.d.ts +1 -0
- package/dist/lib/{complex → math}/Complex.js +1 -1
- package/dist/lib/math/geometry.d.ts +14 -0
- package/dist/lib/measurements.d.ts +2 -4
- package/dist/lib/measurements.js +1 -19
- package/dist/lib/rendering/SvgChartRenderer.d.ts +66 -0
- package/dist/lib/rendering/SvgChartRenderer.js +342 -0
- package/dist/lib/rf/RfCalculations.d.ts +65 -0
- package/dist/lib/rf/RfCalculations.js +161 -0
- package/dist/lib/{SmithConstantCircle.d.ts → rf/SmithConstantCircle.d.ts} +2 -3
- package/dist/lib/{SmithConstantCircle.js → rf/SmithConstantCircle.js} +3 -4
- package/dist/lib/scales/PeripheralScaleDefinitions.d.ts +16 -0
- package/dist/lib/scales/PeripheralScaleDefinitions.js +54 -0
- package/dist/lib/scales/RadialScaleDefinitions.d.ts +15 -0
- package/dist/lib/scales/RadialScaleDefinitions.js +112 -0
- package/dist/lib/scales/SmithPeripheralScales.d.ts +4 -2
- package/dist/lib/scales/SmithPeripheralScales.js +55 -35
- package/dist/lib/scales/SmithScales.d.ts +11 -2
- package/dist/lib/scales/SmithScales.js +78 -38
- package/dist/lib/svg/ImageExporter.d.ts +18 -0
- package/dist/lib/svg/ImageExporter.js +211 -0
- package/dist/lib/svg/LabelLayout.d.ts +17 -0
- package/dist/lib/svg/LabelLayout.js +145 -0
- package/dist/lib/svg/MarkerLegend.d.ts +12 -0
- package/dist/lib/svg/MarkerLegend.js +63 -0
- package/dist/lib/{draw → svg}/SmithArc.d.ts +1 -1
- package/dist/lib/{draw → svg}/SmithCircle.d.ts +1 -1
- package/dist/lib/{draw → svg}/SmithLine.d.ts +1 -1
- package/dist/lib/{draw → svg}/SmithScaler.d.ts +1 -4
- package/dist/lib/{draw → svg}/SmithText.d.ts +1 -1
- package/dist/lib/svg/SvgExporter.d.ts +19 -0
- package/dist/lib/svg/SvgExporter.js +182 -0
- package/dist/lib/svg/export.d.ts +36 -0
- package/dist/lib/{draw → traces}/SmithData.d.ts +24 -35
- package/dist/lib/traces/SmithData.js +195 -0
- package/dist/lib/{draw → traces}/SmithMarker.d.ts +14 -3
- package/dist/lib/traces/SmithMarker.js +240 -0
- package/dist/lib/traces/TraceModel.d.ts +31 -0
- package/dist/lib/traces/TraceModel.js +123 -0
- package/dist/lib/traces/TraceRenderer.d.ts +33 -0
- package/dist/lib/traces/TraceRenderer.js +152 -0
- package/package.json +4 -1
- package/dist/lib/SmithArcsDefs.d.ts +0 -10
- package/dist/lib/SmithArcsDefs.js +0 -10
- package/dist/lib/components.d.ts +0 -14
- package/dist/lib/components.js +0 -26
- package/dist/lib/draw/SmithData.js +0 -366
- package/dist/lib/draw/SmithGridLayer.d.ts +0 -19
- package/dist/lib/draw/SmithGridLayer.js +0 -48
- package/dist/lib/draw/SmithMarker.js +0 -122
- package/dist/lib/formatting.d.ts +0 -7
- package/dist/lib/formatting.js +0 -16
- package/dist/lib/grid/definitions.d.ts +0 -3
- package/dist/lib/grid/definitions.js +0 -123
- package/dist/lib/grid/geometry.d.ts +0 -20
- package/dist/lib/grid/geometry.js +0 -55
- package/dist/lib/grid/labels.d.ts +0 -3
- package/dist/lib/grid/labels.js +0 -41
- package/dist/lib/io/touchstone.d.ts +0 -7
- package/dist/lib/io/touchstone.js +0 -72
- package/dist/lib/math/complexRatio.d.ts +0 -3
- package/dist/lib/math/complexRatio.js +0 -30
- package/dist/lib/renormalization.d.ts +0 -10
- package/dist/lib/renormalization.js +0 -62
- package/dist/lib/rf.d.ts +0 -35
- package/dist/lib/rf.js +0 -74
- package/dist/lib/scales/peripheralScales.d.ts +0 -12
- package/dist/lib/scales/peripheralScales.js +0 -49
- package/dist/lib/scales/radialScales.d.ts +0 -12
- package/dist/lib/scales/radialScales.js +0 -109
- package/dist/lib/shapes/Arc.d.ts +0 -6
- package/dist/lib/shapes/Circle.d.ts +0 -5
- package/dist/lib/shapes/Line.d.ts +0 -5
- package/dist/lib/shapes/Point.d.ts +0 -1
- package/dist/lib/shapes/Point.js +0 -1
- /package/dist/lib/{draw/SmithDrawOptions.js → appearance/types.js} +0 -0
- /package/dist/lib/{draw → grid}/ConstConductance.d.ts +0 -0
- /package/dist/lib/{draw → grid}/ConstConductance.js +0 -0
- /package/dist/lib/{draw → grid}/ConstReactance.d.ts +0 -0
- /package/dist/lib/{draw → grid}/ConstReactance.js +0 -0
- /package/dist/lib/{draw → grid}/ConstResistance.d.ts +0 -0
- /package/dist/lib/{draw → grid}/ConstResistance.js +0 -0
- /package/dist/lib/{draw → grid}/ConstSusceptance.d.ts +0 -0
- /package/dist/lib/{draw → grid}/ConstSusceptance.js +0 -0
- /package/dist/lib/{arcs → grid}/Tick.d.ts +0 -0
- /package/dist/lib/{arcs → grid}/Tick.js +0 -0
- /package/dist/lib/{draw → interaction}/MouseGesture.d.ts +0 -0
- /package/dist/lib/{draw → interaction}/MouseGesture.js +0 -0
- /package/dist/lib/{shapes/Arc.js → math/geometry.js} +0 -0
- /package/dist/lib/{draw → svg}/SmithArc.js +0 -0
- /package/dist/lib/{draw → svg}/SmithCircle.js +0 -0
- /package/dist/lib/{draw → svg}/SmithDrawOptions.d.ts +0 -0
- /package/dist/lib/{shapes/Circle.js → svg/SmithDrawOptions.js} +0 -0
- /package/dist/lib/{draw → svg}/SmithGroup.d.ts +0 -0
- /package/dist/lib/{draw → svg}/SmithGroup.js +0 -0
- /package/dist/lib/{draw → svg}/SmithLine.js +0 -0
- /package/dist/lib/{draw → svg}/SmithPath.d.ts +0 -0
- /package/dist/lib/{draw → svg}/SmithPath.js +0 -0
- /package/dist/lib/{draw → svg}/SmithScaler.js +0 -0
- /package/dist/lib/{draw → svg}/SmithShape.d.ts +0 -0
- /package/dist/lib/{draw → svg}/SmithShape.js +0 -0
- /package/dist/lib/{draw → svg}/SmithSvg.d.ts +0 -0
- /package/dist/lib/{draw → svg}/SmithSvg.js +0 -0
- /package/dist/lib/{draw → svg}/SmithText.js +0 -0
- /package/dist/lib/{shapes/Line.js → svg/export.js} +0 -0
package/dist/lib/Smith.js
CHANGED
|
@@ -1,224 +1,116 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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';
|
|
1
|
+
import { MarkerLegend } from './svg/MarkerLegend.js';
|
|
2
|
+
import { ImageExporter } from './svg/ImageExporter.js';
|
|
3
|
+
import { Theme } from './appearance/Theme.js';
|
|
4
|
+
import { color as parseColor } from 'd3';
|
|
5
|
+
import { SvgChartRenderer } from './rendering/SvgChartRenderer.js';
|
|
6
|
+
import { Complex } from './math/Complex.js';
|
|
7
|
+
import { RfCalculations } from './rf/RfCalculations.js';
|
|
8
|
+
import { MarkerMeasurements } from './MarkerMeasurements.js';
|
|
23
9
|
export var SmithEventType;
|
|
24
10
|
(function (SmithEventType) {
|
|
25
11
|
SmithEventType["Cursor"] = "cursor";
|
|
26
12
|
SmithEventType["Marker"] = "marker";
|
|
13
|
+
SmithEventType["MarkerSelect"] = "marker-select";
|
|
27
14
|
SmithEventType["MarkerDragStart"] = "marker-drag-start";
|
|
28
15
|
SmithEventType["MarkerDragEnd"] = "marker-drag-end";
|
|
29
16
|
})(SmithEventType || (SmithEventType = {}));
|
|
30
17
|
export class Smith {
|
|
31
18
|
referenceOhms;
|
|
32
|
-
|
|
33
|
-
|
|
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();
|
|
19
|
+
theme;
|
|
20
|
+
renderer;
|
|
52
21
|
data = [];
|
|
53
22
|
destroyed = false;
|
|
54
|
-
mouseGesture = new MouseGesture();
|
|
55
23
|
nextDatasetColor = 0;
|
|
56
24
|
nextTraceId = 1;
|
|
57
25
|
nextMarkerId = 1;
|
|
58
26
|
traceMetadata = new WeakMap();
|
|
59
27
|
markerIds = new WeakMap();
|
|
60
28
|
draggedMarkers = new Set();
|
|
61
|
-
cursorBeforeMarkerDrag = null;
|
|
62
29
|
listeners = new Set();
|
|
63
30
|
layers;
|
|
64
31
|
peripheralScales;
|
|
65
|
-
constructor(referenceOhms = 50) {
|
|
32
|
+
constructor(referenceOhms = 50, appearance = {}) {
|
|
66
33
|
this.referenceOhms = referenceOhms;
|
|
67
34
|
if (!Number.isFinite(referenceOhms) || referenceOhms <= 0) {
|
|
68
35
|
throw new RangeError('Reference impedance must be positive and finite.');
|
|
69
36
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
data: gridData,
|
|
77
|
-
scaler: this.scalers.default,
|
|
78
|
-
showMinor: true,
|
|
37
|
+
this.theme = Theme.resolve(appearance);
|
|
38
|
+
this.renderer = new SvgChartRenderer((position) => {
|
|
39
|
+
this.emit({
|
|
40
|
+
type: SmithEventType.Cursor,
|
|
41
|
+
data: position ? RfCalculations.readReflection(position, this.referenceOhms) : undefined,
|
|
42
|
+
});
|
|
79
43
|
});
|
|
80
|
-
this.
|
|
81
|
-
this.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
this.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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));
|
|
44
|
+
this.renderer.setTheme(this.theme);
|
|
45
|
+
this.layers = this.renderer.layers;
|
|
46
|
+
this.peripheralScales = this.renderer.peripheralScales;
|
|
47
|
+
}
|
|
48
|
+
/** Replace the appearance while retaining explicit layer styles and trace colors. */
|
|
49
|
+
setAppearance(appearance) {
|
|
50
|
+
this.assertAlive();
|
|
51
|
+
const theme = Theme.resolve(appearance);
|
|
52
|
+
this.theme = theme;
|
|
53
|
+
this.renderer.setTheme(theme);
|
|
54
|
+
for (const data of this.data) {
|
|
55
|
+
const index = this.traceMetadata.get(data).colorIndex;
|
|
56
|
+
if (index !== undefined) {
|
|
57
|
+
data.setColor(theme.traceColors[index % theme.traceColors.length]);
|
|
58
|
+
}
|
|
134
59
|
}
|
|
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
60
|
}
|
|
165
61
|
draw(target) {
|
|
166
62
|
this.assertAlive();
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
63
|
+
this.renderer.draw(target);
|
|
64
|
+
}
|
|
65
|
+
/** Export the current mounted view as standalone SVG with its configured background. */
|
|
66
|
+
toSvg(options) {
|
|
67
|
+
this.assertAlive();
|
|
68
|
+
const source = this.renderer.toSvg();
|
|
69
|
+
if (options === undefined) {
|
|
70
|
+
return source;
|
|
71
|
+
}
|
|
72
|
+
const sources = [source];
|
|
73
|
+
if (options.scales) {
|
|
74
|
+
sources.push(options.scales.toSvg({ readout: options.scaleReadout }));
|
|
75
|
+
}
|
|
76
|
+
return ImageExporter.svg(sources, options, this.exportLegend(options));
|
|
77
|
+
}
|
|
78
|
+
exportLegend(options) {
|
|
79
|
+
const traces = this.getTraces();
|
|
80
|
+
return {
|
|
81
|
+
entries: [
|
|
82
|
+
...(options.legend ? traces.filter((trace) => trace.visible) : []),
|
|
83
|
+
...(options.markerLegend
|
|
84
|
+
? MarkerLegend.entries(traces, (id) => this.getMarker(id), options.markerLegend)
|
|
85
|
+
: []),
|
|
86
|
+
],
|
|
87
|
+
textColor: this.theme.grid.textColor,
|
|
88
|
+
fontFamily: this.theme.fontFamily,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/** Export the mounted view as PNG, optionally with radial scales and a trace legend. */
|
|
92
|
+
async toPng(options = {}) {
|
|
93
|
+
const sources = [this.toSvg()];
|
|
94
|
+
if (options.scales) {
|
|
95
|
+
sources.push(options.scales.toSvg({ readout: options.scaleReadout }));
|
|
170
96
|
}
|
|
171
|
-
|
|
172
|
-
this.updateViewportScale();
|
|
97
|
+
return ImageExporter.png(sources, options, this.exportLegend(options));
|
|
173
98
|
}
|
|
174
99
|
/** Remove this chart and release its event handlers. Safe to call more than once. */
|
|
175
100
|
destroy() {
|
|
176
101
|
if (this.destroyed) {
|
|
177
102
|
return;
|
|
178
103
|
}
|
|
179
|
-
this.resizeObserver.disconnect();
|
|
180
104
|
this.listeners.clear();
|
|
181
105
|
this.clearTraces();
|
|
182
106
|
this.destroyed = true;
|
|
183
|
-
this.
|
|
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
|
-
}
|
|
107
|
+
this.renderer.destroy();
|
|
199
108
|
}
|
|
200
109
|
assertAlive() {
|
|
201
110
|
if (this.destroyed) {
|
|
202
111
|
throw new Error('This Smith chart has been destroyed. Create a new instance.');
|
|
203
112
|
}
|
|
204
113
|
}
|
|
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
114
|
markerDragChanged(marker, dragging) {
|
|
223
115
|
const wasDragging = this.draggedMarkers.size > 0;
|
|
224
116
|
if (dragging) {
|
|
@@ -231,104 +123,23 @@ export class Smith {
|
|
|
231
123
|
if (isDragging === wasDragging) {
|
|
232
124
|
return;
|
|
233
125
|
}
|
|
126
|
+
this.renderer.setMarkerDragging(isDragging);
|
|
234
127
|
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
128
|
this.emit({ type: SmithEventType.Cursor, data: undefined });
|
|
239
129
|
}
|
|
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
130
|
}
|
|
253
131
|
/** Last cursor position. Use cursor events to detect pointer leave. */
|
|
254
132
|
get cursorReading() {
|
|
255
|
-
return readReflection(this.
|
|
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();
|
|
133
|
+
return RfCalculations.readReflection(this.renderer.cursorPosition, this.referenceOhms);
|
|
277
134
|
}
|
|
278
135
|
/** Enable or disable wheel, double-click, and mouse/touch zoom/pan. Keeps the current view. */
|
|
279
136
|
setZoomEnabled(enabled) {
|
|
280
137
|
this.assertAlive();
|
|
281
|
-
|
|
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
|
-
}
|
|
138
|
+
this.renderer.setZoomEnabled(enabled);
|
|
300
139
|
}
|
|
301
140
|
resetView() {
|
|
302
141
|
this.assertAlive();
|
|
303
|
-
|
|
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);
|
|
142
|
+
this.renderer.resetView();
|
|
332
143
|
}
|
|
333
144
|
/** Add a named trace with one initial marker. Returns a chart-local, stable ID. */
|
|
334
145
|
addTrace(values, options = {}) {
|
|
@@ -340,7 +151,6 @@ export class Smith {
|
|
|
340
151
|
const data = this.createSmithData(values, this.nextDatasetColor, options);
|
|
341
152
|
this.nextDatasetColor++;
|
|
342
153
|
this.data.push(data);
|
|
343
|
-
this.updateViewportScale();
|
|
344
154
|
const id = this.traceMetadata.get(data).id;
|
|
345
155
|
this.setTraceOptions(id, options);
|
|
346
156
|
return id;
|
|
@@ -348,7 +158,8 @@ export class Smith {
|
|
|
348
158
|
/** Detached metadata snapshots; IDs and marker display numbers survive removals. */
|
|
349
159
|
getTraces() {
|
|
350
160
|
return this.data.map((data) => ({
|
|
351
|
-
|
|
161
|
+
id: this.traceMetadata.get(data).id,
|
|
162
|
+
name: this.traceMetadata.get(data).name,
|
|
352
163
|
...data.Style,
|
|
353
164
|
color: data.Color,
|
|
354
165
|
visible: data.Visible,
|
|
@@ -394,7 +205,7 @@ export class Smith {
|
|
|
394
205
|
throw new TypeError('Trace name must not be empty.');
|
|
395
206
|
}
|
|
396
207
|
if (options.color !== undefined &&
|
|
397
|
-
(typeof options.color !== 'string' || !
|
|
208
|
+
(typeof options.color !== 'string' || !parseColor(options.color))) {
|
|
398
209
|
throw new TypeError('Trace color must be a solid CSS color.');
|
|
399
210
|
}
|
|
400
211
|
if (options.visible !== undefined && typeof options.visible !== 'boolean') {
|
|
@@ -411,9 +222,11 @@ export class Smith {
|
|
|
411
222
|
data.setStyle(options);
|
|
412
223
|
if (options.name !== undefined) {
|
|
413
224
|
this.traceMetadata.get(data).name = options.name.trim();
|
|
225
|
+
data.setName(options.name.trim());
|
|
414
226
|
}
|
|
415
227
|
if (options.color !== undefined) {
|
|
416
228
|
data.setColor(options.color);
|
|
229
|
+
delete this.traceMetadata.get(data).colorIndex;
|
|
417
230
|
}
|
|
418
231
|
if (options.visible !== undefined) {
|
|
419
232
|
data.setVisible(options.visible);
|
|
@@ -440,7 +253,7 @@ export class Smith {
|
|
|
440
253
|
if (index < 0) {
|
|
441
254
|
return false;
|
|
442
255
|
}
|
|
443
|
-
this.data[index]
|
|
256
|
+
this.renderer.removeTrace(this.data[index]);
|
|
444
257
|
this.data.splice(index, 1);
|
|
445
258
|
return true;
|
|
446
259
|
}
|
|
@@ -459,6 +272,14 @@ export class Smith {
|
|
|
459
272
|
const location = this.findMarker(id);
|
|
460
273
|
return location ? this.data[location.datasetNo].removeMarker(location.markerNo) : false;
|
|
461
274
|
}
|
|
275
|
+
/** Focus a mounted, visible marker for keyboard interaction. */
|
|
276
|
+
focusMarker(id) {
|
|
277
|
+
this.assertAlive();
|
|
278
|
+
const location = this.findMarker(id);
|
|
279
|
+
return location
|
|
280
|
+
? this.data[location.datasetNo].Markers[location.markerNo].marker.focus()
|
|
281
|
+
: false;
|
|
282
|
+
}
|
|
462
283
|
setMarkerSample(id, sampleIndex) {
|
|
463
284
|
this.assertAlive();
|
|
464
285
|
const location = this.findMarker(id);
|
|
@@ -482,7 +303,7 @@ export class Smith {
|
|
|
482
303
|
const data = this.data[location.datasetNo];
|
|
483
304
|
const marker = data.Markers[location.markerNo];
|
|
484
305
|
return {
|
|
485
|
-
...readReflection(Complex.from(...marker.selectedPoint.reflectionCoefficient), this.referenceImpedanceOhms),
|
|
306
|
+
...RfCalculations.readReflection(Complex.from(...marker.selectedPoint.reflectionCoefficient), this.referenceImpedanceOhms),
|
|
486
307
|
frequencyHz: marker.selectedPoint.frequencyHz,
|
|
487
308
|
traceId: this.traceMetadata.get(data).id,
|
|
488
309
|
markerId: id,
|
|
@@ -494,7 +315,7 @@ export class Smith {
|
|
|
494
315
|
compareMarkers(a, b) {
|
|
495
316
|
const first = this.getMarker(a);
|
|
496
317
|
const second = this.getMarker(b);
|
|
497
|
-
return first && second ?
|
|
318
|
+
return first && second ? MarkerMeasurements.compare(first, second) : undefined;
|
|
498
319
|
}
|
|
499
320
|
/** Current positive real reference impedance, in ohms. */
|
|
500
321
|
get referenceImpedanceOhms() {
|
|
@@ -504,25 +325,25 @@ export class Smith {
|
|
|
504
325
|
renormalize(referenceImpedanceOhms) {
|
|
505
326
|
this.assertAlive();
|
|
506
327
|
// Validate even with no traces, and prepare every result before changing any state.
|
|
507
|
-
renormalizeSamples([], this.referenceOhms, referenceImpedanceOhms);
|
|
328
|
+
RfCalculations.renormalizeSamples([], this.referenceOhms, referenceImpedanceOhms);
|
|
508
329
|
if (referenceImpedanceOhms === this.referenceOhms) {
|
|
509
330
|
return;
|
|
510
331
|
}
|
|
511
|
-
const samples = this.data.map((data) => renormalizeSamples(data.Samples, this.referenceOhms, referenceImpedanceOhms));
|
|
332
|
+
const samples = this.data.map((data) => RfCalculations.renormalizeSamples(data.Samples, this.referenceOhms, referenceImpedanceOhms));
|
|
512
333
|
this.data.forEach((data) => data.Markers.forEach((entry) => entry.marker.cancelDrag()));
|
|
513
334
|
this.referenceOhms = referenceImpedanceOhms;
|
|
514
335
|
this.data.forEach((data, index) => data.update(samples[index], 'sample-index'));
|
|
515
|
-
this.
|
|
336
|
+
this.renderer.hideCursor();
|
|
516
337
|
this.emit({ type: SmithEventType.Cursor, data: undefined });
|
|
517
338
|
}
|
|
518
339
|
clearTraces() {
|
|
519
340
|
this.assertAlive();
|
|
520
|
-
this.data.forEach((dataset) =>
|
|
341
|
+
this.data.forEach((dataset) => this.renderer.removeTrace(dataset));
|
|
521
342
|
this.data = [];
|
|
522
343
|
}
|
|
523
344
|
createSmithData(values, dataset, options) {
|
|
524
|
-
const color =
|
|
525
|
-
const data =
|
|
345
|
+
const color = this.theme.traceColors[dataset % this.theme.traceColors.length];
|
|
346
|
+
const data = this.renderer.createTrace(values, color, options, (marker, dragging) => {
|
|
526
347
|
this.markerDragChanged(marker, dragging);
|
|
527
348
|
const snapshot = this.getMarker(this.markerId(marker));
|
|
528
349
|
if (snapshot) {
|
|
@@ -531,9 +352,20 @@ export class Smith {
|
|
|
531
352
|
data: snapshot,
|
|
532
353
|
});
|
|
533
354
|
}
|
|
534
|
-
}
|
|
355
|
+
});
|
|
535
356
|
const number = this.nextTraceId++;
|
|
536
|
-
this.traceMetadata.set(data, {
|
|
357
|
+
this.traceMetadata.set(data, {
|
|
358
|
+
id: `trace-${number}`,
|
|
359
|
+
name: `Trace ${number}`,
|
|
360
|
+
colorIndex: dataset,
|
|
361
|
+
});
|
|
362
|
+
data.setName(`Trace ${number}`);
|
|
363
|
+
data.setMarkerSelectHandler((marker) => {
|
|
364
|
+
const snapshot = this.getMarker(this.markerId(marker));
|
|
365
|
+
if (snapshot) {
|
|
366
|
+
this.emit({ type: SmithEventType.MarkerSelect, data: snapshot });
|
|
367
|
+
}
|
|
368
|
+
});
|
|
537
369
|
data.setMarkerMoveHandler((index) => {
|
|
538
370
|
const marker = data.Markers[index];
|
|
539
371
|
const snapshot = marker && this.getMarker(this.markerId(marker.marker));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Complex } from './math/Complex.js';
|
|
2
|
+
export declare class SmithFormatter {
|
|
3
|
+
private constructor();
|
|
4
|
+
/** Format with three significant digits and an SI prefix; includes spacing before the unit. */
|
|
5
|
+
static number(value: number): string;
|
|
6
|
+
/** Format Cartesian components to decimalPlaces fractional digits. */
|
|
7
|
+
static complex(value: Complex, unit?: string, decimalPlaces?: number): string;
|
|
8
|
+
/** Format magnitude and phase in degrees to decimalPlaces fractional digits. */
|
|
9
|
+
static polar(value: Complex, unit?: string, decimalPlaces?: number): string;
|
|
10
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { format } from 'd3';
|
|
2
|
+
export class SmithFormatter {
|
|
3
|
+
constructor() { }
|
|
4
|
+
/** Format with three significant digits and an SI prefix; includes spacing before the unit. */
|
|
5
|
+
static number(value) {
|
|
6
|
+
const formatted = format('.3~s')(value);
|
|
7
|
+
return Number.isFinite(value) && /[a-zA-Zµ]$/.test(formatted)
|
|
8
|
+
? formatted.replace(/([a-zA-Zµ])$/, ' $1')
|
|
9
|
+
: formatted + ' ';
|
|
10
|
+
}
|
|
11
|
+
/** Format Cartesian components to decimalPlaces fractional digits. */
|
|
12
|
+
static complex(value, unit = '', decimalPlaces = 3) {
|
|
13
|
+
return `${value.toString(decimalPlaces)} ${unit === '' ? '' : `[${unit}]`}`;
|
|
14
|
+
}
|
|
15
|
+
/** Format magnitude and phase in degrees to decimalPlaces fractional digits. */
|
|
16
|
+
static polar(value, unit = '', decimalPlaces = 3) {
|
|
17
|
+
return `${value.abs().toFixed(decimalPlaces)} ${unit} ∠${((value.arg() * 180) / Math.PI).toFixed(decimalPlaces)}°`;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SmithTheme } from './types.js';
|
|
2
|
+
/** Scope presentation tokens to one chart or scale component. Explicit SVG styles take priority. */
|
|
3
|
+
export declare class SvgTheme {
|
|
4
|
+
private constructor();
|
|
5
|
+
static apply(element: HTMLElement | SVGElement, theme: SmithTheme): void;
|
|
6
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Scope presentation tokens to one chart or scale component. Explicit SVG styles take priority. */
|
|
2
|
+
export class SvgTheme {
|
|
3
|
+
constructor() { }
|
|
4
|
+
static apply(element, theme) {
|
|
5
|
+
for (const [section, value] of Object.entries(theme)) {
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
continue;
|
|
8
|
+
}
|
|
9
|
+
if (typeof value === 'object') {
|
|
10
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
11
|
+
const suffix = key.toLowerCase().endsWith('fontsize') ? 'px' : '';
|
|
12
|
+
element.style.setProperty(`--smithkit-${section}-${key}`, String(entry) + suffix);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
element.style.setProperty(`--smithkit-${section}`, value);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
element.style.backgroundColor = theme.background;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SmithAppearance, SmithTheme } from './types.js';
|
|
2
|
+
/** Resolve and validate a detached theme before touching any chart state or DOM. */
|
|
3
|
+
export declare class Theme {
|
|
4
|
+
private constructor();
|
|
5
|
+
static resolve(appearance?: SmithAppearance): SmithTheme;
|
|
6
|
+
private static merge;
|
|
7
|
+
private static isColor;
|
|
8
|
+
}
|