smithkit 0.2.0 → 0.4.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 +318 -25
- package/dist/lib/OptionsValidation.d.ts +16 -0
- package/dist/lib/OptionsValidation.js +168 -0
- package/dist/lib/Smith.d.ts +24 -39
- package/dist/lib/Smith.js +175 -287
- 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 +125 -0
- package/dist/lib/appearance/types.d.ts +55 -0
- package/dist/lib/appearance/types.js +1 -0
- package/dist/lib/grid/CircleLayerControl.d.ts +1 -1
- package/dist/lib/grid/CircleLayerControl.js +10 -8
- package/dist/lib/grid/ConstCircles.d.ts +0 -3
- package/dist/lib/grid/ConstCircles.js +6 -20
- package/dist/lib/grid/ConstQCircles.d.ts +1 -0
- package/dist/lib/grid/ConstQCircles.js +6 -1
- package/dist/lib/grid/ConstSwrCircles.d.ts +1 -0
- package/dist/lib/grid/ConstSwrCircles.js +6 -1
- package/dist/lib/grid/GridDefinitions.d.ts +7 -1
- package/dist/lib/grid/GridDefinitions.js +29 -0
- package/dist/lib/grid/GridLayerControl.d.ts +5 -4
- package/dist/lib/grid/GridLayerControl.js +13 -3
- package/dist/lib/grid/LayerControl.d.ts +1 -2
- package/dist/lib/grid/LayerControl.js +1 -10
- package/dist/lib/grid/SmithGridLayer.d.ts +7 -1
- package/dist/lib/grid/SmithGridLayer.js +35 -5
- package/dist/lib/index.d.ts +4 -1
- package/dist/lib/interaction/SmithCursor.d.ts +0 -17
- package/dist/lib/interaction/SmithCursor.js +6 -14
- package/dist/lib/layers.d.ts +7 -1
- package/dist/lib/options.d.ts +54 -0
- package/dist/lib/options.js +1 -0
- package/dist/lib/rendering/SvgChartRenderer.d.ts +69 -0
- package/dist/lib/rendering/SvgChartRenderer.js +364 -0
- package/dist/lib/scales/SmithPeripheralScales.js +11 -11
- package/dist/lib/scales/SmithScales.d.ts +10 -3
- package/dist/lib/scales/SmithScales.js +73 -39
- package/dist/lib/svg/ImageExporter.d.ts +18 -0
- package/dist/lib/svg/ImageExporter.js +211 -0
- package/dist/lib/svg/MarkerLegend.d.ts +12 -0
- package/dist/lib/svg/MarkerLegend.js +63 -0
- package/dist/lib/svg/SvgExporter.d.ts +6 -1
- package/dist/lib/svg/SvgExporter.js +50 -5
- package/dist/lib/svg/export.d.ts +36 -0
- package/dist/lib/svg/export.js +1 -0
- package/dist/lib/traces/SmithMarker.js +8 -5
- package/package.json +1 -1
package/dist/lib/Smith.js
CHANGED
|
@@ -1,25 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { SmithCircle } from './svg/SmithCircle.js';
|
|
8
|
-
import { SmithData } from './traces/SmithData.js';
|
|
9
|
-
import { SmithCursor } from './interaction/SmithCursor.js';
|
|
10
|
-
import { ConstResistance } from './grid/ConstResistance.js';
|
|
11
|
-
import { ConstReactance } from './grid/ConstReactance.js';
|
|
12
|
-
import { ConstConductance } from './grid/ConstConductance.js';
|
|
13
|
-
import { ConstSusceptance } from './grid/ConstSusceptance.js';
|
|
14
|
-
import { ConstQCircles } from './grid/ConstQCircles.js';
|
|
15
|
-
import { ConstSwrCircles } from './grid/ConstSwrCircles.js';
|
|
16
|
-
import { SmithScaler } from './svg/SmithScaler.js';
|
|
17
|
-
import { SmithConstantCircle } from './rf/SmithConstantCircle.js';
|
|
18
|
-
import { GridDefinitions } from './grid/GridDefinitions.js';
|
|
1
|
+
import { OptionsValidation } from './OptionsValidation.js';
|
|
2
|
+
import { MarkerLegend } from './svg/MarkerLegend.js';
|
|
3
|
+
import { ImageExporter } from './svg/ImageExporter.js';
|
|
4
|
+
import { Theme } from './appearance/Theme.js';
|
|
5
|
+
import { color as parseColor } from 'd3';
|
|
6
|
+
import { SvgChartRenderer } from './rendering/SvgChartRenderer.js';
|
|
19
7
|
import { Complex } from './math/Complex.js';
|
|
20
|
-
import { SmithPeripheralScales } from './scales/SmithPeripheralScales.js';
|
|
21
|
-
import { GridLayerControl } from './grid/GridLayerControl.js';
|
|
22
|
-
import { CircleLayerControl } from './grid/CircleLayerControl.js';
|
|
23
8
|
import { RfCalculations } from './rf/RfCalculations.js';
|
|
24
9
|
import { MarkerMeasurements } from './MarkerMeasurements.js';
|
|
25
10
|
export var SmithEventType;
|
|
@@ -31,207 +16,189 @@ export var SmithEventType;
|
|
|
31
16
|
SmithEventType["MarkerDragEnd"] = "marker-drag-end";
|
|
32
17
|
})(SmithEventType || (SmithEventType = {}));
|
|
33
18
|
export class Smith {
|
|
19
|
+
theme;
|
|
34
20
|
referenceOhms;
|
|
35
|
-
|
|
36
|
-
scalers;
|
|
37
|
-
defaultTransform = d3.zoomIdentity.translate(62.5, 62.5).scale(0.75);
|
|
38
|
-
transform = d3.zoomIdentity;
|
|
39
|
-
zoomEnabled = true;
|
|
40
|
-
applyingView = false;
|
|
41
|
-
zoomBehavior = d3.zoom();
|
|
42
|
-
svg;
|
|
43
|
-
resizeObserver;
|
|
44
|
-
labelLayout;
|
|
45
|
-
container;
|
|
46
|
-
dataContainer;
|
|
47
|
-
markerContainer;
|
|
48
|
-
reactanceAxis;
|
|
49
|
-
constResistance;
|
|
50
|
-
constReactance;
|
|
51
|
-
constConductance;
|
|
52
|
-
constSusceptance;
|
|
53
|
-
constSwrCircles;
|
|
54
|
-
constQCircles;
|
|
55
|
-
cursor;
|
|
56
|
-
peripheralScaleRenderer = new SmithPeripheralScales();
|
|
21
|
+
renderer;
|
|
57
22
|
data = [];
|
|
58
23
|
destroyed = false;
|
|
59
|
-
mouseGesture = new MouseGesture();
|
|
60
24
|
nextDatasetColor = 0;
|
|
61
25
|
nextTraceId = 1;
|
|
62
26
|
nextMarkerId = 1;
|
|
63
27
|
traceMetadata = new WeakMap();
|
|
64
28
|
markerIds = new WeakMap();
|
|
65
29
|
draggedMarkers = new Set();
|
|
66
|
-
cursorBeforeMarkerDrag = null;
|
|
67
30
|
listeners = new Set();
|
|
68
31
|
layers;
|
|
69
32
|
peripheralScales;
|
|
70
|
-
constructor(
|
|
71
|
-
|
|
72
|
-
|
|
33
|
+
constructor(options = {}) {
|
|
34
|
+
OptionsValidation.chart(options);
|
|
35
|
+
this.referenceOhms =
|
|
36
|
+
options.referenceImpedanceOhms === undefined ? 50 : options.referenceImpedanceOhms;
|
|
37
|
+
if (!Number.isFinite(this.referenceOhms) || this.referenceOhms <= 0) {
|
|
73
38
|
throw new RangeError('Reference impedance must be positive and finite.');
|
|
74
39
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
data: gridData,
|
|
82
|
-
scaler: this.scalers.default,
|
|
83
|
-
showMinor: true,
|
|
40
|
+
this.theme = Theme.resolve(options.appearance);
|
|
41
|
+
this.renderer = new SvgChartRenderer((position) => {
|
|
42
|
+
this.emit({
|
|
43
|
+
type: SmithEventType.Cursor,
|
|
44
|
+
data: position ? RfCalculations.readReflection(position, this.referenceOhms) : undefined,
|
|
45
|
+
});
|
|
84
46
|
});
|
|
85
|
-
this.
|
|
86
|
-
this.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
this.container.append(this.cursor.Group);
|
|
127
|
-
this.container.append(this.reactanceAxis);
|
|
128
|
-
this.container.append(cursorContainer);
|
|
129
|
-
this.container.append(this.dataContainer);
|
|
130
|
-
const labels = new SmithGroup().attr('data-layer', 'labels').attr('pointer-events', 'none');
|
|
131
|
-
for (const [name, layer] of [
|
|
132
|
-
['conductance', this.constConductance],
|
|
133
|
-
['susceptance', this.constSusceptance],
|
|
134
|
-
['resistance', this.constResistance],
|
|
135
|
-
['reactance', this.constReactance],
|
|
136
|
-
]) {
|
|
137
|
-
labels.append(layer.labels.attr('data-label-layer', name));
|
|
138
|
-
}
|
|
139
|
-
labels.append(this.peripheralScaleRenderer.labels);
|
|
140
|
-
this.container.append(labels);
|
|
141
|
-
this.container.append(this.markerContainer);
|
|
142
|
-
this.labelLayout = new LabelLayout(this.svg.Node, labels.Node, this.defaultTransform);
|
|
143
|
-
const assertAlive = () => this.assertAlive();
|
|
144
|
-
this.layers = {
|
|
145
|
-
resistance: new GridLayerControl(this.constResistance, assertAlive, () => this.labelLayout.update(true)),
|
|
146
|
-
reactance: new GridLayerControl(this.constReactance, assertAlive, () => this.labelLayout.update(true)),
|
|
147
|
-
conductance: new GridLayerControl(this.constConductance, assertAlive, () => this.labelLayout.update(true)),
|
|
148
|
-
susceptance: new GridLayerControl(this.constSusceptance, assertAlive, () => this.labelLayout.update(true)),
|
|
149
|
-
q: new CircleLayerControl(this.constQCircles, 0, assertAlive),
|
|
150
|
-
vswr: new CircleLayerControl(this.constSwrCircles, 1, assertAlive),
|
|
151
|
-
};
|
|
152
|
-
this.peripheralScales = {
|
|
153
|
-
setVisible: (visible) => {
|
|
154
|
-
assertAlive();
|
|
155
|
-
if (visible) {
|
|
156
|
-
this.peripheralScaleRenderer.show();
|
|
47
|
+
this.renderer.setTheme(this.theme);
|
|
48
|
+
this.layers = this.renderer.layers;
|
|
49
|
+
this.peripheralScales = this.renderer.peripheralScales;
|
|
50
|
+
try {
|
|
51
|
+
this.applyOptions(options);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
// Invalid constructor options must not retain observers or event handlers.
|
|
55
|
+
this.renderer.destroy();
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Apply a validated patch. Omitted fields retain their current settings. */
|
|
60
|
+
setOptions(options) {
|
|
61
|
+
this.assertAlive();
|
|
62
|
+
OptionsValidation.chart(options);
|
|
63
|
+
// Renormalization can reject otherwise valid options for the loaded samples.
|
|
64
|
+
// Do it before presentation changes so rejection preserves the entire chart.
|
|
65
|
+
if (options.referenceImpedanceOhms !== undefined) {
|
|
66
|
+
this.renormalize(options.referenceImpedanceOhms);
|
|
67
|
+
}
|
|
68
|
+
if (options.appearance !== undefined) {
|
|
69
|
+
this.setAppearance(options.appearance);
|
|
70
|
+
}
|
|
71
|
+
this.applyOptions(options);
|
|
72
|
+
}
|
|
73
|
+
applyOptions(options) {
|
|
74
|
+
if (options.interaction?.zoom !== undefined) {
|
|
75
|
+
this.setZoomEnabled(options.interaction.zoom);
|
|
76
|
+
}
|
|
77
|
+
if (options.interaction?.cursor !== undefined) {
|
|
78
|
+
this.setCursorEnabled(options.interaction.cursor);
|
|
79
|
+
}
|
|
80
|
+
if (options.peripheralScales?.visible !== undefined) {
|
|
81
|
+
this.peripheralScales.setVisible(options.peripheralScales.visible);
|
|
82
|
+
}
|
|
83
|
+
for (const name of ['resistance', 'reactance', 'conductance', 'susceptance']) {
|
|
84
|
+
const layer = this.layers[name];
|
|
85
|
+
for (const settings of [options.grid, options.grid?.layers?.[name]]) {
|
|
86
|
+
if (settings === undefined) {
|
|
87
|
+
continue;
|
|
157
88
|
}
|
|
158
|
-
|
|
159
|
-
|
|
89
|
+
if (settings.detail !== undefined) {
|
|
90
|
+
layer.setDetail(settings.detail);
|
|
160
91
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
92
|
+
if (settings.labelsVisible !== undefined) {
|
|
93
|
+
layer.setLabelsVisible(settings.labelsVisible);
|
|
94
|
+
}
|
|
95
|
+
if (settings.style !== undefined) {
|
|
96
|
+
layer.setStyle(settings.style);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const visible = options.grid?.layers?.[name]?.visible;
|
|
100
|
+
if (visible !== undefined) {
|
|
101
|
+
layer.setVisible(visible);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (const name of ['q', 'vswr']) {
|
|
105
|
+
const settings = options.circles?.[name];
|
|
106
|
+
if (settings === undefined) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const layer = this.layers[name];
|
|
110
|
+
if (settings.values !== undefined) {
|
|
111
|
+
layer.setValues(settings.values);
|
|
112
|
+
}
|
|
113
|
+
if (settings.style !== undefined) {
|
|
114
|
+
layer.setStyle(settings.style);
|
|
115
|
+
}
|
|
116
|
+
if (settings.visible !== undefined) {
|
|
117
|
+
layer.setVisible(settings.visible);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** Replace the appearance while retaining explicit layer styles and trace colors. */
|
|
122
|
+
setAppearance(appearance) {
|
|
123
|
+
this.assertAlive();
|
|
124
|
+
const theme = Theme.resolve(appearance);
|
|
125
|
+
this.theme = theme;
|
|
126
|
+
this.renderer.setTheme(theme);
|
|
127
|
+
for (const data of this.data) {
|
|
128
|
+
const index = this.traceMetadata.get(data).colorIndex;
|
|
129
|
+
if (index !== undefined) {
|
|
130
|
+
data.setColor(theme.traceColors[index % theme.traceColors.length]);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
171
133
|
}
|
|
172
134
|
draw(target) {
|
|
173
135
|
this.assertAlive();
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
136
|
+
this.renderer.draw(target);
|
|
137
|
+
}
|
|
138
|
+
/** Set the same detail level on all impedance and admittance grid layers. */
|
|
139
|
+
setGridDetail(detail) {
|
|
140
|
+
this.assertAlive();
|
|
141
|
+
for (const name of ['resistance', 'reactance', 'conductance', 'susceptance']) {
|
|
142
|
+
this.layers[name].setDetail(detail);
|
|
177
143
|
}
|
|
178
|
-
host.appendChild(this.svg.Node);
|
|
179
|
-
this.updateViewportScale();
|
|
180
144
|
}
|
|
181
|
-
/** Export the current mounted view as standalone SVG with
|
|
182
|
-
toSvg() {
|
|
145
|
+
/** Export the current mounted view as standalone SVG with its configured background. */
|
|
146
|
+
toSvg(options) {
|
|
183
147
|
this.assertAlive();
|
|
184
|
-
|
|
148
|
+
this.validateExportOptions(options);
|
|
149
|
+
const source = this.renderer.toSvg();
|
|
150
|
+
if (options === undefined) {
|
|
151
|
+
return source;
|
|
152
|
+
}
|
|
153
|
+
const sources = [source];
|
|
154
|
+
if (options.scales) {
|
|
155
|
+
sources.push(options.scales.toSvg({ readout: options.scaleReadout }));
|
|
156
|
+
}
|
|
157
|
+
return ImageExporter.svg(sources, options, this.exportLegend(options));
|
|
158
|
+
}
|
|
159
|
+
validateExportOptions(options) {
|
|
160
|
+
if (options?.scaleReadout !== undefined && !options.scales) {
|
|
161
|
+
throw new TypeError('Scale readout requires included radial scales.');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
exportLegend(options) {
|
|
165
|
+
const traces = this.getTraces();
|
|
166
|
+
return {
|
|
167
|
+
entries: [
|
|
168
|
+
...(options.legend ? traces.filter((trace) => trace.visible) : []),
|
|
169
|
+
...(options.markerLegend
|
|
170
|
+
? MarkerLegend.entries(traces, (id) => this.getMarker(id), options.markerLegend)
|
|
171
|
+
: []),
|
|
172
|
+
],
|
|
173
|
+
textColor: this.theme.grid.textColor,
|
|
174
|
+
fontFamily: this.theme.fontFamily,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/** Export the mounted view as PNG, optionally with radial scales and a trace legend. */
|
|
178
|
+
async toPng(options = {}) {
|
|
179
|
+
this.assertAlive();
|
|
180
|
+
this.validateExportOptions(options);
|
|
181
|
+
const sources = [this.toSvg()];
|
|
182
|
+
if (options.scales) {
|
|
183
|
+
sources.push(options.scales.toSvg({ readout: options.scaleReadout }));
|
|
184
|
+
}
|
|
185
|
+
return ImageExporter.png(sources, options, this.exportLegend(options));
|
|
185
186
|
}
|
|
186
187
|
/** Remove this chart and release its event handlers. Safe to call more than once. */
|
|
187
188
|
destroy() {
|
|
188
189
|
if (this.destroyed) {
|
|
189
190
|
return;
|
|
190
191
|
}
|
|
191
|
-
this.resizeObserver.disconnect();
|
|
192
192
|
this.listeners.clear();
|
|
193
193
|
this.clearTraces();
|
|
194
194
|
this.destroyed = true;
|
|
195
|
-
this.
|
|
196
|
-
this.mouseGesture.destroy();
|
|
197
|
-
this.zoomBehavior.on('start', null).on('zoom', null);
|
|
198
|
-
this.svg.Element.interrupt().on('.zoom', null);
|
|
199
|
-
this.svg.Element.selectAll('*').interrupt().on('.smithkit', null).on('.drag', null);
|
|
200
|
-
this.svg.Element.remove();
|
|
201
|
-
}
|
|
202
|
-
updateViewportScale() {
|
|
203
|
-
if (this.destroyed) {
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
const matrix = this.svg.Node.getScreenCTM();
|
|
207
|
-
if (matrix) {
|
|
208
|
-
const scale = Math.hypot(matrix.a, matrix.b);
|
|
209
|
-
this.data.forEach((trace) => trace.setViewportScale(scale));
|
|
210
|
-
}
|
|
211
|
-
this.labelLayout.update();
|
|
195
|
+
this.renderer.destroy();
|
|
212
196
|
}
|
|
213
197
|
assertAlive() {
|
|
214
198
|
if (this.destroyed) {
|
|
215
199
|
throw new Error('This Smith chart has been destroyed. Create a new instance.');
|
|
216
200
|
}
|
|
217
201
|
}
|
|
218
|
-
createScalers(size) {
|
|
219
|
-
const impedance = new SmithScaler(d3.scaleLinear().domain([-1, 1]).range([0, size]), d3.scaleLinear().domain([1, -1]).range([0, size]), d3
|
|
220
|
-
.scaleLinear()
|
|
221
|
-
.domain([0, 1])
|
|
222
|
-
.range([0, size / 2]));
|
|
223
|
-
const admittance = new SmithScaler(d3.scaleLinear().domain([1, -1]).range([0, size]), d3.scaleLinear().domain([-1, 1]).range([0, size]), d3
|
|
224
|
-
.scaleLinear()
|
|
225
|
-
.domain([0, 1])
|
|
226
|
-
.range([0, size / 2]));
|
|
227
|
-
return { default: impedance, impedance, admittance };
|
|
228
|
-
}
|
|
229
|
-
cursorMove(p) {
|
|
230
|
-
if (this.draggedMarkers.size > 0) {
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
this.cursor.Position = Complex.from(this.scalers.default.pointInvert(p));
|
|
234
|
-
}
|
|
235
202
|
markerDragChanged(marker, dragging) {
|
|
236
203
|
const wasDragging = this.draggedMarkers.size > 0;
|
|
237
204
|
if (dragging) {
|
|
@@ -244,109 +211,27 @@ export class Smith {
|
|
|
244
211
|
if (isDragging === wasDragging) {
|
|
245
212
|
return;
|
|
246
213
|
}
|
|
247
|
-
|
|
248
|
-
this.cursorBeforeMarkerDrag = this.svg.Node.style.getPropertyValue('cursor') || null;
|
|
249
|
-
this.svg.Element.style('cursor', 'grabbing').style('--smithkit-marker-cursor', 'grabbing');
|
|
250
|
-
this.cursor.hide();
|
|
251
|
-
this.emit({ type: SmithEventType.Cursor, data: undefined });
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
this.svg.Element.style('cursor', () => this.cursorBeforeMarkerDrag).style('--smithkit-marker-cursor', null);
|
|
255
|
-
this.cursorBeforeMarkerDrag = null;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
initCursor() {
|
|
259
|
-
const cursor = new SmithCursor(this.scalers.default);
|
|
260
|
-
cursor.Group.attr('class', 'smith-cursor');
|
|
261
|
-
cursor.setMoveHandler(() => {
|
|
262
|
-
this.emit({ type: SmithEventType.Cursor, data: this.cursorReading });
|
|
263
|
-
});
|
|
264
|
-
return cursor;
|
|
214
|
+
this.renderer.setMarkerDragging(isDragging);
|
|
265
215
|
}
|
|
266
216
|
/** Last cursor position. Use cursor events to detect pointer leave. */
|
|
267
217
|
get cursorReading() {
|
|
268
|
-
return RfCalculations.readReflection(this.
|
|
269
|
-
}
|
|
270
|
-
initializeZoom() {
|
|
271
|
-
const zoom = this.zoomBehavior
|
|
272
|
-
.scaleExtent([0.6, 1000])
|
|
273
|
-
.filter((event) => this.zoomEnabled &&
|
|
274
|
-
this.draggedMarkers.size === 0 &&
|
|
275
|
-
(!event.ctrlKey || event.type === 'wheel') &&
|
|
276
|
-
!event.button)
|
|
277
|
-
.on('start', (event) => {
|
|
278
|
-
if (event.sourceEvent) {
|
|
279
|
-
this.mouseGesture.capture(event.sourceEvent, 'zoom');
|
|
280
|
-
}
|
|
281
|
-
})
|
|
282
|
-
.on('zoom', (event) => {
|
|
283
|
-
if ((!this.zoomEnabled || this.draggedMarkers.size > 0) && !this.applyingView) {
|
|
284
|
-
// An already active mouse/touch gesture still needs its normal end event.
|
|
285
|
-
// Restore D3's view through its public API while that gesture finishes.
|
|
286
|
-
this.applyView(this.transform);
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
this.onZoom(event.transform);
|
|
290
|
-
});
|
|
291
|
-
this.svg.Element.call(zoom);
|
|
292
|
-
this.resetView();
|
|
218
|
+
return RfCalculations.readReflection(this.renderer.cursorPosition, this.referenceOhms);
|
|
293
219
|
}
|
|
294
220
|
/** Enable or disable wheel, double-click, and mouse/touch zoom/pan. Keeps the current view. */
|
|
295
221
|
setZoomEnabled(enabled) {
|
|
296
222
|
this.assertAlive();
|
|
297
|
-
|
|
298
|
-
throw new TypeError('Zoom enabled must be a boolean.');
|
|
299
|
-
}
|
|
300
|
-
if (this.zoomEnabled === enabled) {
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
this.zoomEnabled = enabled;
|
|
304
|
-
if (!enabled) {
|
|
305
|
-
this.svg.Element.interrupt();
|
|
306
|
-
}
|
|
223
|
+
this.renderer.setZoomEnabled(enabled);
|
|
307
224
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
finally {
|
|
314
|
-
this.applyingView = false;
|
|
315
|
-
}
|
|
225
|
+
/** Enable or disable the cursor overlay and readings independently of zoom and markers. */
|
|
226
|
+
setCursorEnabled(enabled) {
|
|
227
|
+
this.assertAlive();
|
|
228
|
+
this.renderer.setCursorEnabled(enabled);
|
|
316
229
|
}
|
|
317
230
|
resetView() {
|
|
318
231
|
this.assertAlive();
|
|
319
|
-
|
|
320
|
-
this.applyView(transform);
|
|
232
|
+
this.renderer.resetView();
|
|
321
233
|
}
|
|
322
|
-
|
|
323
|
-
if (this.destroyed) {
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
this.transform = transform;
|
|
327
|
-
this.container.Element.attr('transform', transform.toString());
|
|
328
|
-
this.data.forEach((d) => d.zoom(transform));
|
|
329
|
-
}
|
|
330
|
-
cursorContainer() {
|
|
331
|
-
const shape = this.drawReactanceAxis({ fill: 'transparent', stroke: 'none' });
|
|
332
|
-
shape.Element.style('pointer-events', 'all')
|
|
333
|
-
.on('pointermove.smithkit', (event) => {
|
|
334
|
-
this.cursorMove(d3.pointer(event));
|
|
335
|
-
})
|
|
336
|
-
.on('pointerleave.smithkit', () => {
|
|
337
|
-
this.cursor.hide();
|
|
338
|
-
this.emit({ type: SmithEventType.Cursor, data: undefined });
|
|
339
|
-
});
|
|
340
|
-
return shape;
|
|
341
|
-
}
|
|
342
|
-
drawReactanceAxis(opts) {
|
|
343
|
-
const c = this.calcs.resistanceCircle(0);
|
|
344
|
-
c.p[0] = this.scalers.default.x(c.p[0]);
|
|
345
|
-
c.p[1] = this.scalers.default.y(c.p[1]);
|
|
346
|
-
c.r = this.scalers.default.r(c.r);
|
|
347
|
-
return new SmithCircle(c, opts);
|
|
348
|
-
}
|
|
349
|
-
/** Add a named trace with one initial marker. Returns a chart-local, stable ID. */
|
|
234
|
+
/** Add a named trace without markers. Use addMarker to create markers explicitly. Returns a chart-local, stable ID. */
|
|
350
235
|
addTrace(values, options = {}) {
|
|
351
236
|
this.assertAlive();
|
|
352
237
|
this.validateTraceOptions(options);
|
|
@@ -356,7 +241,6 @@ export class Smith {
|
|
|
356
241
|
const data = this.createSmithData(values, this.nextDatasetColor, options);
|
|
357
242
|
this.nextDatasetColor++;
|
|
358
243
|
this.data.push(data);
|
|
359
|
-
this.updateViewportScale();
|
|
360
244
|
const id = this.traceMetadata.get(data).id;
|
|
361
245
|
this.setTraceOptions(id, options);
|
|
362
246
|
return id;
|
|
@@ -364,7 +248,8 @@ export class Smith {
|
|
|
364
248
|
/** Detached metadata snapshots; IDs and marker display numbers survive removals. */
|
|
365
249
|
getTraces() {
|
|
366
250
|
return this.data.map((data) => ({
|
|
367
|
-
|
|
251
|
+
id: this.traceMetadata.get(data).id,
|
|
252
|
+
name: this.traceMetadata.get(data).name,
|
|
368
253
|
...data.Style,
|
|
369
254
|
color: data.Color,
|
|
370
255
|
visible: data.Visible,
|
|
@@ -410,7 +295,7 @@ export class Smith {
|
|
|
410
295
|
throw new TypeError('Trace name must not be empty.');
|
|
411
296
|
}
|
|
412
297
|
if (options.color !== undefined &&
|
|
413
|
-
(typeof options.color !== 'string' || !
|
|
298
|
+
(typeof options.color !== 'string' || !parseColor(options.color))) {
|
|
414
299
|
throw new TypeError('Trace color must be a solid CSS color.');
|
|
415
300
|
}
|
|
416
301
|
if (options.visible !== undefined && typeof options.visible !== 'boolean') {
|
|
@@ -431,6 +316,7 @@ export class Smith {
|
|
|
431
316
|
}
|
|
432
317
|
if (options.color !== undefined) {
|
|
433
318
|
data.setColor(options.color);
|
|
319
|
+
delete this.traceMetadata.get(data).colorIndex;
|
|
434
320
|
}
|
|
435
321
|
if (options.visible !== undefined) {
|
|
436
322
|
data.setVisible(options.visible);
|
|
@@ -457,7 +343,7 @@ export class Smith {
|
|
|
457
343
|
if (index < 0) {
|
|
458
344
|
return false;
|
|
459
345
|
}
|
|
460
|
-
this.data[index]
|
|
346
|
+
this.renderer.removeTrace(this.data[index]);
|
|
461
347
|
this.data.splice(index, 1);
|
|
462
348
|
return true;
|
|
463
349
|
}
|
|
@@ -537,17 +423,16 @@ export class Smith {
|
|
|
537
423
|
this.data.forEach((data) => data.Markers.forEach((entry) => entry.marker.cancelDrag()));
|
|
538
424
|
this.referenceOhms = referenceImpedanceOhms;
|
|
539
425
|
this.data.forEach((data, index) => data.update(samples[index], 'sample-index'));
|
|
540
|
-
this.
|
|
541
|
-
this.emit({ type: SmithEventType.Cursor, data: undefined });
|
|
426
|
+
this.renderer.hideCursor();
|
|
542
427
|
}
|
|
543
428
|
clearTraces() {
|
|
544
429
|
this.assertAlive();
|
|
545
|
-
this.data.forEach((dataset) =>
|
|
430
|
+
this.data.forEach((dataset) => this.renderer.removeTrace(dataset));
|
|
546
431
|
this.data = [];
|
|
547
432
|
}
|
|
548
433
|
createSmithData(values, dataset, options) {
|
|
549
|
-
const color =
|
|
550
|
-
const data =
|
|
434
|
+
const color = this.theme.traceColors[dataset % this.theme.traceColors.length];
|
|
435
|
+
const data = this.renderer.createTrace(values, color, options, (marker, dragging) => {
|
|
551
436
|
this.markerDragChanged(marker, dragging);
|
|
552
437
|
const snapshot = this.getMarker(this.markerId(marker));
|
|
553
438
|
if (snapshot) {
|
|
@@ -556,9 +441,13 @@ export class Smith {
|
|
|
556
441
|
data: snapshot,
|
|
557
442
|
});
|
|
558
443
|
}
|
|
559
|
-
}
|
|
444
|
+
});
|
|
560
445
|
const number = this.nextTraceId++;
|
|
561
|
-
this.traceMetadata.set(data, {
|
|
446
|
+
this.traceMetadata.set(data, {
|
|
447
|
+
id: `trace-${number}`,
|
|
448
|
+
name: `Trace ${number}`,
|
|
449
|
+
colorIndex: dataset,
|
|
450
|
+
});
|
|
562
451
|
data.setName(`Trace ${number}`);
|
|
563
452
|
data.setMarkerSelectHandler((marker) => {
|
|
564
453
|
const snapshot = this.getMarker(this.markerId(marker));
|
|
@@ -573,7 +462,6 @@ export class Smith {
|
|
|
573
462
|
this.emit({ type: SmithEventType.Marker, data: snapshot });
|
|
574
463
|
}
|
|
575
464
|
});
|
|
576
|
-
data.addMarker();
|
|
577
465
|
return data;
|
|
578
466
|
}
|
|
579
467
|
/** Subscribe to chart events. The returned function removes this subscription. */
|
|
@@ -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
|
+
}
|