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
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/** Captures rendered SVG with presentation styles resolved in its source document. */
|
|
2
|
+
export class SvgExporter {
|
|
3
|
+
constructor() { }
|
|
4
|
+
static namespace = 'http://www.w3.org/2000/svg';
|
|
5
|
+
static properties = [
|
|
6
|
+
'color',
|
|
7
|
+
'display',
|
|
8
|
+
'visibility',
|
|
9
|
+
'opacity',
|
|
10
|
+
'fill',
|
|
11
|
+
'fill-opacity',
|
|
12
|
+
'fill-rule',
|
|
13
|
+
'stroke',
|
|
14
|
+
'stroke-width',
|
|
15
|
+
'stroke-opacity',
|
|
16
|
+
'stroke-dasharray',
|
|
17
|
+
'stroke-dashoffset',
|
|
18
|
+
'stroke-linecap',
|
|
19
|
+
'stroke-linejoin',
|
|
20
|
+
'stroke-miterlimit',
|
|
21
|
+
'vector-effect',
|
|
22
|
+
'paint-order',
|
|
23
|
+
'shape-rendering',
|
|
24
|
+
'text-rendering',
|
|
25
|
+
'font-family',
|
|
26
|
+
'font-size',
|
|
27
|
+
'font-style',
|
|
28
|
+
'font-weight',
|
|
29
|
+
'font-variant',
|
|
30
|
+
'font-stretch',
|
|
31
|
+
'letter-spacing',
|
|
32
|
+
'word-spacing',
|
|
33
|
+
'text-anchor',
|
|
34
|
+
'dominant-baseline',
|
|
35
|
+
'alignment-baseline',
|
|
36
|
+
'baseline-shift',
|
|
37
|
+
'text-decoration',
|
|
38
|
+
'white-space',
|
|
39
|
+
];
|
|
40
|
+
static nonInherited = new Set([
|
|
41
|
+
'display',
|
|
42
|
+
'opacity',
|
|
43
|
+
'vector-effect',
|
|
44
|
+
'alignment-baseline',
|
|
45
|
+
'dominant-baseline',
|
|
46
|
+
'baseline-shift',
|
|
47
|
+
'text-decoration',
|
|
48
|
+
]);
|
|
49
|
+
static chart(source) {
|
|
50
|
+
const bounds = SvgExporter.bounds(source);
|
|
51
|
+
const copy = SvgExporter.copy(source);
|
|
52
|
+
SvgExporter.background(copy, source);
|
|
53
|
+
SvgExporter.size(copy, bounds.width, bounds.height);
|
|
54
|
+
return SvgExporter.serialize(copy);
|
|
55
|
+
}
|
|
56
|
+
/** Preserve the current CSS layout of separate scale SVGs in one SVG document. */
|
|
57
|
+
static scales(container, axes, customize, caption) {
|
|
58
|
+
const bounds = SvgExporter.bounds(container);
|
|
59
|
+
const root = container.ownerDocument.createElementNS(SvgExporter.namespace, 'svg');
|
|
60
|
+
const lines = [];
|
|
61
|
+
if (caption?.text) {
|
|
62
|
+
const context = document.createElement('canvas').getContext('2d');
|
|
63
|
+
context.font = `14px ${caption.fontFamily}`;
|
|
64
|
+
let line = '';
|
|
65
|
+
for (const character of caption.text) {
|
|
66
|
+
if (character === '\n' ||
|
|
67
|
+
(line && context.measureText(line + character).width > Math.max(1, bounds.width - 32))) {
|
|
68
|
+
lines.push(line);
|
|
69
|
+
line = '';
|
|
70
|
+
}
|
|
71
|
+
if (character !== '\n') {
|
|
72
|
+
line += character;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
lines.push(line);
|
|
76
|
+
}
|
|
77
|
+
const captionHeight = lines.length ? 16 + lines.length * 22 : 0;
|
|
78
|
+
root.setAttribute('viewBox', `0 0 ${bounds.width} ${bounds.height + captionHeight}`);
|
|
79
|
+
SvgExporter.size(root, bounds.width, bounds.height + captionHeight);
|
|
80
|
+
SvgExporter.background(root, container);
|
|
81
|
+
lines.forEach((line, index) => {
|
|
82
|
+
const text = container.ownerDocument.createElementNS(SvgExporter.namespace, 'text');
|
|
83
|
+
text.setAttribute('data-role', 'scale-readout-label');
|
|
84
|
+
text.setAttribute('x', '16');
|
|
85
|
+
text.setAttribute('y', String(22 + index * 22));
|
|
86
|
+
text.setAttribute('fill', caption.color);
|
|
87
|
+
text.setAttribute('font-family', caption.fontFamily);
|
|
88
|
+
text.setAttribute('font-size', '14');
|
|
89
|
+
text.textContent = line;
|
|
90
|
+
root.appendChild(text);
|
|
91
|
+
});
|
|
92
|
+
for (const [index, axis] of axes.entries()) {
|
|
93
|
+
const position = SvgExporter.bounds(axis);
|
|
94
|
+
const copy = SvgExporter.copy(axis);
|
|
95
|
+
copy.setAttribute('x', String(position.x - bounds.x));
|
|
96
|
+
copy.setAttribute('y', String(position.y - bounds.y + captionHeight));
|
|
97
|
+
SvgExporter.size(copy, position.width, position.height);
|
|
98
|
+
customize?.(copy, index);
|
|
99
|
+
root.appendChild(copy);
|
|
100
|
+
}
|
|
101
|
+
return SvgExporter.serialize(root);
|
|
102
|
+
}
|
|
103
|
+
static background(root, source) {
|
|
104
|
+
const color = source.ownerDocument.defaultView.getComputedStyle(source).backgroundColor;
|
|
105
|
+
if (!color || color === 'transparent' || color === 'rgba(0, 0, 0, 0)') {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const rect = source.ownerDocument.createElementNS(SvgExporter.namespace, 'rect');
|
|
109
|
+
rect.setAttribute('data-export-background', 'true');
|
|
110
|
+
rect.setAttribute('width', '100%');
|
|
111
|
+
rect.setAttribute('height', '100%');
|
|
112
|
+
rect.setAttribute('fill', color);
|
|
113
|
+
rect.setAttribute('stroke', 'none');
|
|
114
|
+
root.insertBefore(rect, root.firstChild);
|
|
115
|
+
}
|
|
116
|
+
static bounds(element) {
|
|
117
|
+
const bounds = element.getBoundingClientRect();
|
|
118
|
+
if (!element.isConnected || bounds.width <= 0 || bounds.height <= 0) {
|
|
119
|
+
throw new Error('SVG export requires a mounted component with a visible, non-zero size.');
|
|
120
|
+
}
|
|
121
|
+
return bounds;
|
|
122
|
+
}
|
|
123
|
+
static copy(source) {
|
|
124
|
+
const copy = source.cloneNode(true);
|
|
125
|
+
const originals = [source, ...source.querySelectorAll('*')];
|
|
126
|
+
const copies = [copy, ...copy.querySelectorAll('*')];
|
|
127
|
+
const view = source.ownerDocument.defaultView;
|
|
128
|
+
const styles = new Map(originals.map((element) => [element, view.getComputedStyle(element)]));
|
|
129
|
+
originals.forEach((original, index) => {
|
|
130
|
+
const element = copies[index];
|
|
131
|
+
const style = styles.get(original);
|
|
132
|
+
const parentStyle = original.parentElement ? styles.get(original.parentElement) : undefined;
|
|
133
|
+
// Keep SVG geometry/transform attributes, but replace page-dependent CSS.
|
|
134
|
+
element.removeAttribute('style');
|
|
135
|
+
for (const property of SvgExporter.properties) {
|
|
136
|
+
const value = style.getPropertyValue(property);
|
|
137
|
+
element.removeAttribute(property);
|
|
138
|
+
// Resolve inherited values at their boundary instead of repeating every font and
|
|
139
|
+
// stroke property on thousands of descendants. CSS overrides may replace attributes.
|
|
140
|
+
if (value &&
|
|
141
|
+
(SvgExporter.nonInherited.has(property) ||
|
|
142
|
+
parentStyle?.getPropertyValue(property) !== value)) {
|
|
143
|
+
element.style.setProperty(property, value);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// An exported image has no event handlers or interactive controls.
|
|
147
|
+
element.removeAttribute('tabindex');
|
|
148
|
+
if (original.getAttribute('data-role') === 'marker') {
|
|
149
|
+
// A static marker is an image, not an operable sample slider.
|
|
150
|
+
element.setAttribute('role', 'img');
|
|
151
|
+
element.setAttribute('aria-label', `${original.getAttribute('aria-label')}; ${original.getAttribute('aria-valuetext')}`);
|
|
152
|
+
for (const attribute of [
|
|
153
|
+
'aria-valuemin',
|
|
154
|
+
'aria-valuemax',
|
|
155
|
+
'aria-valuenow',
|
|
156
|
+
'aria-valuetext',
|
|
157
|
+
'aria-orientation',
|
|
158
|
+
'aria-description',
|
|
159
|
+
'aria-disabled',
|
|
160
|
+
]) {
|
|
161
|
+
element.removeAttribute(attribute);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
element.removeAttribute('class');
|
|
165
|
+
for (const attribute of [...element.attributes]) {
|
|
166
|
+
if (attribute.name.startsWith('on')) {
|
|
167
|
+
element.removeAttribute(attribute.name);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
return copy;
|
|
172
|
+
}
|
|
173
|
+
static size(svg, width, height) {
|
|
174
|
+
svg.setAttribute('width', String(width));
|
|
175
|
+
svg.setAttribute('height', String(height));
|
|
176
|
+
svg.style.setProperty('overflow', 'hidden');
|
|
177
|
+
}
|
|
178
|
+
static serialize(svg) {
|
|
179
|
+
svg.setAttribute('xmlns', SvgExporter.namespace);
|
|
180
|
+
return new XMLSerializer().serializeToString(svg);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Complex } from '../math/Complex.js';
|
|
2
|
+
import type { SmithScales } from '../scales/SmithScales.js';
|
|
3
|
+
export interface ImageExportOptions {
|
|
4
|
+
/** Output pixels. Default: the snapshot dimensions. With only one dimension, preserve the complete image's aspect ratio. */
|
|
5
|
+
width?: number;
|
|
6
|
+
/** With both dimensions, center and fit the image without stretching or cropping. */
|
|
7
|
+
height?: number;
|
|
8
|
+
/** A concrete CSS color, or transparent. Omit to preserve configured SVG backgrounds. */
|
|
9
|
+
background?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ScaleExportReadout {
|
|
12
|
+
/** Use this reading instead of the live cursor/marker state; null exports empty scales. */
|
|
13
|
+
reflectionCoefficient: Complex | null;
|
|
14
|
+
/** Visible description of the reading, such as trace, marker, and frequency. */
|
|
15
|
+
label?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ScaleImageExportOptions extends ImageExportOptions {
|
|
18
|
+
readout?: ScaleExportReadout;
|
|
19
|
+
}
|
|
20
|
+
export type MarkerLegendField = 'frequency' | 'impedance' | 'admittance' | 'reflectionCoefficient' | 'vswr' | 'returnLoss';
|
|
21
|
+
export interface MarkerLegendOptions {
|
|
22
|
+
/** Default: all markers of visible traces, in trace/marker order. Empty selects none. Unknown IDs throw. */
|
|
23
|
+
markerIds?: readonly string[];
|
|
24
|
+
/** Default: frequency and impedance. Empty keeps only the trace name and marker number. */
|
|
25
|
+
fields?: readonly MarkerLegendField[];
|
|
26
|
+
}
|
|
27
|
+
export interface SmithImageExportOptions extends ImageExportOptions {
|
|
28
|
+
/** Include a footer with the names and colors of visible traces. Default: false. */
|
|
29
|
+
legend?: boolean;
|
|
30
|
+
/** Include a marker legend below the chart/scales, independently of the trace legend. Default: false. */
|
|
31
|
+
markerLegend?: boolean | MarkerLegendOptions;
|
|
32
|
+
/** Include mounted radial scales centered below the chart, preserving their current layout/readings. */
|
|
33
|
+
scales?: SmithScales;
|
|
34
|
+
/** Override readings in the included radial scales without changing the live view. */
|
|
35
|
+
scaleReadout?: ScaleExportReadout;
|
|
36
|
+
}
|
|
@@ -1,48 +1,33 @@
|
|
|
1
|
-
import { SmithGroup } from '
|
|
1
|
+
import { SmithGroup } from '../svg/SmithGroup.js';
|
|
2
2
|
import type { TraceStyle, MarkerSelectionStrategy } from '../measurements.js';
|
|
3
3
|
import { SmithMarker } from './SmithMarker.js';
|
|
4
|
-
import { SmithScaler } from '
|
|
5
|
-
import { TraceSamples, TraceSample } from '../samples.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
selectedPoint: TraceSample;
|
|
9
|
-
number: number;
|
|
10
|
-
}
|
|
11
|
-
interface Transform {
|
|
12
|
-
x: number;
|
|
13
|
-
y: number;
|
|
14
|
-
k: number;
|
|
15
|
-
}
|
|
4
|
+
import { SmithScaler } from '../svg/SmithScaler.js';
|
|
5
|
+
import type { TraceSamples, TraceSample } from '../samples.js';
|
|
6
|
+
import { type TraceTransform } from './TraceRenderer.js';
|
|
7
|
+
/** Coordinates trace rendering, draggable markers, and deferred marker events. */
|
|
16
8
|
export declare class SmithData {
|
|
17
|
-
private data;
|
|
18
9
|
private color;
|
|
19
10
|
private transform;
|
|
20
|
-
private fgContainer;
|
|
21
11
|
private scaler;
|
|
22
12
|
private markerDragHandler?;
|
|
23
13
|
private markerContainer;
|
|
24
|
-
private
|
|
25
|
-
private
|
|
14
|
+
private readonly model;
|
|
15
|
+
private readonly renderer;
|
|
16
|
+
private readonly markers;
|
|
17
|
+
private readonly pendingEvents;
|
|
26
18
|
private destroyed;
|
|
19
|
+
private viewportScale;
|
|
20
|
+
private name;
|
|
21
|
+
private selectHandler;
|
|
27
22
|
private visible;
|
|
28
|
-
private pendingEvents;
|
|
29
|
-
private group;
|
|
30
|
-
private markersCount;
|
|
31
|
-
private markers;
|
|
32
23
|
private handler;
|
|
33
|
-
constructor(data: TraceSamples, color: string, transform:
|
|
34
|
-
private drawTrace;
|
|
35
|
-
private renderPoints;
|
|
24
|
+
constructor(data: TraceSamples, color: string, transform: TraceTransform, container: SmithGroup, scaler: SmithScaler, markerDragHandler?: ((marker: SmithMarker, dragging: boolean) => void) | undefined, markerContainer?: SmithGroup, style?: Partial<TraceStyle>);
|
|
36
25
|
setViewportScale(scale: number): void;
|
|
37
|
-
zoom(transform:
|
|
38
|
-
private zoomDataPoints;
|
|
39
|
-
private zoomAllMarkers;
|
|
26
|
+
zoom(transform: TraceTransform): void;
|
|
40
27
|
addMarker(sampleIndex?: number): number;
|
|
41
28
|
removeMarker(index: number): boolean;
|
|
42
29
|
setMarkerSample(index: number, sampleIndex: number): boolean;
|
|
43
30
|
setMarkerFrequency(index: number, frequencyHz: number): boolean;
|
|
44
|
-
private nearestFrequencyIndex;
|
|
45
|
-
private validateSampleIndex;
|
|
46
31
|
get Samples(): TraceSamples;
|
|
47
32
|
get SampleCount(): number;
|
|
48
33
|
get Color(): string;
|
|
@@ -51,16 +36,20 @@ export declare class SmithData {
|
|
|
51
36
|
setColor(color: string): void;
|
|
52
37
|
get Style(): TraceStyle;
|
|
53
38
|
setStyle(options: Partial<TraceStyle>): void;
|
|
54
|
-
private redraw;
|
|
55
39
|
setVisible(visible: boolean): void;
|
|
56
|
-
private
|
|
40
|
+
private moveMarker;
|
|
41
|
+
private updateReading;
|
|
42
|
+
setName(name: string): void;
|
|
43
|
+
setMarkerSelectHandler(handler: (marker: SmithMarker) => void): void;
|
|
57
44
|
private notifyMarker;
|
|
58
45
|
private cancelEvents;
|
|
59
46
|
update(values: TraceSamples, strategy?: MarkerSelectionStrategy): void;
|
|
60
47
|
destroy(): void;
|
|
61
|
-
|
|
62
|
-
|
|
48
|
+
get Markers(): {
|
|
49
|
+
number: number;
|
|
50
|
+
selectedPoint: TraceSample;
|
|
51
|
+
marker: SmithMarker;
|
|
52
|
+
}[];
|
|
63
53
|
setMarkerMoveHandler(handler: (marker: number, data: TraceSample) => void): void;
|
|
64
|
-
private
|
|
54
|
+
private assertAlive;
|
|
65
55
|
}
|
|
66
|
-
export {};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { SmithMarker } from './SmithMarker.js';
|
|
2
|
+
import { TraceModel } from './TraceModel.js';
|
|
3
|
+
import { TraceRenderer } from './TraceRenderer.js';
|
|
4
|
+
/** Coordinates trace rendering, draggable markers, and deferred marker events. */
|
|
5
|
+
export class SmithData {
|
|
6
|
+
color;
|
|
7
|
+
transform;
|
|
8
|
+
scaler;
|
|
9
|
+
markerDragHandler;
|
|
10
|
+
markerContainer;
|
|
11
|
+
model;
|
|
12
|
+
renderer;
|
|
13
|
+
markers = new Map();
|
|
14
|
+
pendingEvents = new Map();
|
|
15
|
+
destroyed = false;
|
|
16
|
+
viewportScale = 1;
|
|
17
|
+
name = 'Trace';
|
|
18
|
+
selectHandler = null;
|
|
19
|
+
visible = true;
|
|
20
|
+
handler = null;
|
|
21
|
+
constructor(data, color, transform, container, scaler, markerDragHandler, markerContainer = container, style = {}) {
|
|
22
|
+
this.color = color;
|
|
23
|
+
this.transform = transform;
|
|
24
|
+
this.scaler = scaler;
|
|
25
|
+
this.markerDragHandler = markerDragHandler;
|
|
26
|
+
this.markerContainer = markerContainer;
|
|
27
|
+
this.model = new TraceModel(data);
|
|
28
|
+
this.renderer = new TraceRenderer(this.model.Samples, color, transform, container, scaler, style);
|
|
29
|
+
}
|
|
30
|
+
setViewportScale(scale) {
|
|
31
|
+
this.viewportScale = scale;
|
|
32
|
+
this.renderer.setViewportScale(scale);
|
|
33
|
+
this.markers.forEach((marker) => marker.zoom(this.transform.k * scale));
|
|
34
|
+
}
|
|
35
|
+
zoom(transform) {
|
|
36
|
+
if (this.destroyed) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.transform = transform;
|
|
40
|
+
this.renderer.zoom(transform);
|
|
41
|
+
this.markers.forEach((marker) => marker.zoom(transform.k * this.viewportScale));
|
|
42
|
+
}
|
|
43
|
+
addMarker(sampleIndex = 0) {
|
|
44
|
+
this.assertAlive();
|
|
45
|
+
const entry = this.model.addMarker(sampleIndex);
|
|
46
|
+
const marker = new SmithMarker(entry.number, this.color, (dragging) => this.markerDragHandler?.(marker, dragging));
|
|
47
|
+
this.markers.set(entry, marker);
|
|
48
|
+
marker.Element.attr('data-role', 'marker');
|
|
49
|
+
marker.setSampleHandler((sampleIndex) => this.setMarkerSample(this.model.markerIndex(entry), sampleIndex));
|
|
50
|
+
marker.setSelectHandler(() => this.selectHandler?.(marker));
|
|
51
|
+
this.markerContainer.append(marker);
|
|
52
|
+
marker.setDragHandler((point) => {
|
|
53
|
+
if (this.model.selectNearestPoint(entry, this.scaler.pointInvert(point))) {
|
|
54
|
+
this.moveMarker(entry);
|
|
55
|
+
marker.zoom(this.transform.k * this.viewportScale);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
marker.zoom(this.transform.k * this.viewportScale);
|
|
59
|
+
marker.show();
|
|
60
|
+
marker.Element.style('display', () => (this.visible ? null : 'none'));
|
|
61
|
+
this.moveMarker(entry);
|
|
62
|
+
return this.model.markerIndex(entry);
|
|
63
|
+
}
|
|
64
|
+
removeMarker(index) {
|
|
65
|
+
const entry = this.model.getMarker(index);
|
|
66
|
+
if (!entry) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
clearTimeout(this.pendingEvents.get(entry));
|
|
70
|
+
this.pendingEvents.delete(entry);
|
|
71
|
+
// Ending a drag must still be able to read the marker snapshot.
|
|
72
|
+
this.markers.get(entry).destroy();
|
|
73
|
+
this.markers.delete(entry);
|
|
74
|
+
this.model.removeMarker(index);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
setMarkerSample(index, sampleIndex) {
|
|
78
|
+
const entry = this.model.setMarkerSample(index, sampleIndex);
|
|
79
|
+
if (!entry) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
this.moveMarker(entry);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
setMarkerFrequency(index, frequencyHz) {
|
|
86
|
+
const entry = this.model.setMarkerFrequency(index, frequencyHz);
|
|
87
|
+
if (!entry) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
this.moveMarker(entry);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
get Samples() {
|
|
94
|
+
return this.model.Samples;
|
|
95
|
+
}
|
|
96
|
+
get SampleCount() {
|
|
97
|
+
return this.model.Samples.length;
|
|
98
|
+
}
|
|
99
|
+
get Color() {
|
|
100
|
+
return this.color;
|
|
101
|
+
}
|
|
102
|
+
get Visible() {
|
|
103
|
+
return this.visible;
|
|
104
|
+
}
|
|
105
|
+
markerSampleIndex(index) {
|
|
106
|
+
return this.model.markerSampleIndex(index);
|
|
107
|
+
}
|
|
108
|
+
setColor(color) {
|
|
109
|
+
this.color = color;
|
|
110
|
+
this.renderer.setColor(color);
|
|
111
|
+
this.markers.forEach((marker) => marker.setColor(color));
|
|
112
|
+
}
|
|
113
|
+
get Style() {
|
|
114
|
+
return this.renderer.Style;
|
|
115
|
+
}
|
|
116
|
+
setStyle(options) {
|
|
117
|
+
this.renderer.setStyle(options);
|
|
118
|
+
}
|
|
119
|
+
setVisible(visible) {
|
|
120
|
+
this.visible = visible;
|
|
121
|
+
this.renderer.setVisible(visible);
|
|
122
|
+
this.markers.forEach((marker) => {
|
|
123
|
+
if (!visible) {
|
|
124
|
+
marker.cancelDrag();
|
|
125
|
+
marker.Node?.blur();
|
|
126
|
+
}
|
|
127
|
+
marker.Element.style('display', () => (visible ? null : 'none'));
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
moveMarker(entry) {
|
|
131
|
+
const marker = this.markers.get(entry);
|
|
132
|
+
marker.move(this.scaler.point(entry.selectedPoint.reflectionCoefficient));
|
|
133
|
+
this.updateReading(entry, marker);
|
|
134
|
+
this.notifyMarker(entry);
|
|
135
|
+
}
|
|
136
|
+
updateReading(entry, marker) {
|
|
137
|
+
marker.setReading(`${this.name}, marker ${entry.number}`, this.model.markerSampleIndex(this.model.markerIndex(entry)), this.SampleCount, entry.selectedPoint.frequencyHz);
|
|
138
|
+
}
|
|
139
|
+
setName(name) {
|
|
140
|
+
this.name = name;
|
|
141
|
+
this.markers.forEach((marker, entry) => this.updateReading(entry, marker));
|
|
142
|
+
}
|
|
143
|
+
setMarkerSelectHandler(handler) {
|
|
144
|
+
this.selectHandler = handler;
|
|
145
|
+
}
|
|
146
|
+
notifyMarker(entry) {
|
|
147
|
+
if (this.pendingEvents.has(entry)) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const timer = setTimeout(() => {
|
|
151
|
+
this.pendingEvents.delete(entry);
|
|
152
|
+
const index = this.model.markerIndex(entry);
|
|
153
|
+
if (!this.destroyed && index >= 0) {
|
|
154
|
+
this.handler?.(index, entry.selectedPoint);
|
|
155
|
+
}
|
|
156
|
+
}, 0);
|
|
157
|
+
this.pendingEvents.set(entry, timer);
|
|
158
|
+
}
|
|
159
|
+
cancelEvents() {
|
|
160
|
+
this.pendingEvents.forEach((timer) => clearTimeout(timer));
|
|
161
|
+
this.pendingEvents.clear();
|
|
162
|
+
}
|
|
163
|
+
update(values, strategy = 'frequency') {
|
|
164
|
+
this.assertAlive();
|
|
165
|
+
this.model.update(values, strategy);
|
|
166
|
+
this.cancelEvents();
|
|
167
|
+
this.renderer.update(this.model.Samples);
|
|
168
|
+
this.model.Markers.forEach((entry) => this.moveMarker(entry));
|
|
169
|
+
}
|
|
170
|
+
destroy() {
|
|
171
|
+
if (this.destroyed) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
this.destroyed = true;
|
|
175
|
+
this.cancelEvents();
|
|
176
|
+
this.handler = null;
|
|
177
|
+
this.selectHandler = null;
|
|
178
|
+
this.markers.forEach((marker) => marker.destroy());
|
|
179
|
+
this.markers.clear();
|
|
180
|
+
this.model.clear();
|
|
181
|
+
this.markerDragHandler = undefined;
|
|
182
|
+
this.renderer.destroy();
|
|
183
|
+
}
|
|
184
|
+
get Markers() {
|
|
185
|
+
return this.model.Markers.map((entry) => ({ ...entry, marker: this.markers.get(entry) }));
|
|
186
|
+
}
|
|
187
|
+
setMarkerMoveHandler(handler) {
|
|
188
|
+
this.handler = handler;
|
|
189
|
+
}
|
|
190
|
+
assertAlive() {
|
|
191
|
+
if (this.destroyed) {
|
|
192
|
+
throw new Error('This dataset has been removed.');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -1,25 +1,36 @@
|
|
|
1
|
-
import { SmithShape } from '
|
|
2
|
-
import { Point } from '../
|
|
1
|
+
import { SmithShape } from '../svg/SmithShape.js';
|
|
2
|
+
import { Point } from '../math/geometry.js';
|
|
3
3
|
export declare class SmithMarker extends SmithShape {
|
|
4
4
|
private dragStateHandler;
|
|
5
5
|
private readonly size;
|
|
6
6
|
private mouseGesture;
|
|
7
7
|
private activeDrags;
|
|
8
8
|
private destroyed;
|
|
9
|
+
private sampleIndex;
|
|
10
|
+
private sampleCount;
|
|
11
|
+
private scale;
|
|
9
12
|
private triangle;
|
|
10
13
|
private inner;
|
|
11
14
|
private text;
|
|
15
|
+
private hitArea;
|
|
16
|
+
private focusRing;
|
|
12
17
|
private rc;
|
|
13
18
|
private dragHandler;
|
|
19
|
+
private sampleHandler;
|
|
20
|
+
private selectHandler;
|
|
14
21
|
constructor(marker: number, color: string, dragStateHandler?: ((dragging: boolean) => void) | null);
|
|
22
|
+
focus(): boolean;
|
|
23
|
+
setReading(name: string, index: number, count: number, frequencyHz: number): void;
|
|
24
|
+
private keydown;
|
|
15
25
|
destroy(): void;
|
|
16
26
|
cancelDrag(): void;
|
|
17
27
|
setColor(color: string): void;
|
|
18
|
-
private onDrag;
|
|
19
28
|
get Position(): Point;
|
|
20
29
|
move(p: Point): void;
|
|
21
30
|
zoom(k: number): void;
|
|
22
31
|
show(): void;
|
|
23
32
|
hide(): void;
|
|
24
33
|
setDragHandler(handler: (p: Point) => void): void;
|
|
34
|
+
setSampleHandler(handler: (index: number) => void): void;
|
|
35
|
+
setSelectHandler(handler: () => void): void;
|
|
25
36
|
}
|