smithkit 0.3.0 → 0.5.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 +324 -37
- package/dist/lib/OptionsValidation.d.ts +16 -0
- package/dist/lib/OptionsValidation.js +172 -0
- package/dist/lib/Smith.d.ts +20 -5
- package/dist/lib/Smith.js +114 -15
- package/dist/lib/appearance/Theme.js +5 -0
- package/dist/lib/grid/CircleLayerControl.d.ts +1 -1
- package/dist/lib/grid/CircleLayerControl.js +10 -8
- package/dist/lib/grid/ConstQCircles.d.ts +1 -0
- package/dist/lib/grid/ConstQCircles.js +5 -0
- package/dist/lib/grid/ConstSwrCircles.d.ts +1 -0
- package/dist/lib/grid/ConstSwrCircles.js +5 -0
- package/dist/lib/grid/GridLayerControl.js +5 -4
- package/dist/lib/grid/LayerControl.d.ts +1 -2
- package/dist/lib/grid/LayerControl.js +1 -10
- package/dist/lib/index.d.ts +3 -2
- package/dist/lib/io/Touchstone.d.ts +13 -7
- package/dist/lib/io/Touchstone.js +146 -46
- package/dist/lib/io/types.d.ts +17 -0
- package/dist/lib/io/types.js +1 -0
- package/dist/lib/layers.d.ts +7 -1
- package/dist/lib/measurements.d.ts +2 -0
- package/dist/lib/options.d.ts +58 -0
- package/dist/lib/options.js +1 -0
- package/dist/lib/rendering/SvgChartRenderer.d.ts +6 -3
- package/dist/lib/rendering/SvgChartRenderer.js +58 -11
- package/dist/lib/rf/RfCalculations.d.ts +3 -1
- package/dist/lib/rf/RfCalculations.js +23 -6
- package/dist/lib/samples.d.ts +5 -1
- package/dist/lib/scales/SmithPeripheralScales.d.ts +5 -0
- package/dist/lib/scales/SmithPeripheralScales.js +20 -1
- package/dist/lib/svg/SvgExporter.d.ts +1 -1
- package/dist/lib/svg/SvgExporter.js +4 -3
- package/dist/lib/svg/export.d.ts +3 -1
- package/dist/lib/traces/SmithData.d.ts +10 -4
- package/dist/lib/traces/SmithData.js +14 -4
- package/dist/lib/traces/TraceBuffer.d.ts +19 -0
- package/dist/lib/traces/TraceBuffer.js +176 -0
- package/dist/lib/traces/TraceLineDetail.d.ts +8 -0
- package/dist/lib/traces/TraceLineDetail.js +52 -0
- package/dist/lib/traces/TraceModel.d.ts +6 -8
- package/dist/lib/traces/TraceModel.js +24 -49
- package/dist/lib/traces/TraceRenderer.d.ts +13 -3
- package/dist/lib/traces/TraceRenderer.js +83 -23
- package/package.json +4 -2
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { Theme } from './appearance/Theme.js';
|
|
2
|
+
/** Shared validation for initial configuration, patches, and individual controls. */
|
|
3
|
+
export class OptionsValidation {
|
|
4
|
+
constructor() { }
|
|
5
|
+
static object(value, name, keys) {
|
|
6
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
7
|
+
throw new TypeError(`${name} must be an object.`);
|
|
8
|
+
}
|
|
9
|
+
if (keys) {
|
|
10
|
+
for (const key of Object.keys(value)) {
|
|
11
|
+
if (!keys.includes(key)) {
|
|
12
|
+
throw new TypeError(`Unknown ${name} option: ${key}.`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
static boolean(value, name) {
|
|
18
|
+
if (typeof value !== 'boolean') {
|
|
19
|
+
throw new TypeError(`${name} must be a boolean.`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
static detail(value) {
|
|
23
|
+
if (!['basic', 'standard', 'detailed'].includes(value)) {
|
|
24
|
+
throw new RangeError('Grid detail must be basic, standard, or detailed.');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
static lengths(style, keys) {
|
|
28
|
+
for (const key of keys) {
|
|
29
|
+
const value = style[key];
|
|
30
|
+
if (value !== undefined &&
|
|
31
|
+
(typeof value !== 'number' || !Number.isFinite(value) || value <= 0)) {
|
|
32
|
+
throw new RangeError(`${String(key)} must be a positive finite number.`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
static strings(style, keys) {
|
|
37
|
+
for (const key of keys) {
|
|
38
|
+
if (style[key] !== undefined && typeof style[key] !== 'string') {
|
|
39
|
+
throw new TypeError(`${String(key)} must be a CSS string.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
static gridStyle(style) {
|
|
44
|
+
OptionsValidation.object(style, 'Grid style', [
|
|
45
|
+
'stroke',
|
|
46
|
+
'majorWidth',
|
|
47
|
+
'minorWidth',
|
|
48
|
+
'textColor',
|
|
49
|
+
'textFontFamily',
|
|
50
|
+
'textFontSize',
|
|
51
|
+
]);
|
|
52
|
+
OptionsValidation.lengths(style, ['majorWidth', 'minorWidth', 'textFontSize']);
|
|
53
|
+
OptionsValidation.strings(style, ['stroke', 'textColor', 'textFontFamily']);
|
|
54
|
+
}
|
|
55
|
+
static circleStyle(style) {
|
|
56
|
+
OptionsValidation.object(style, 'Circle style', ['stroke', 'strokeWidth']);
|
|
57
|
+
OptionsValidation.lengths(style, ['strokeWidth']);
|
|
58
|
+
OptionsValidation.strings(style, ['stroke']);
|
|
59
|
+
}
|
|
60
|
+
static circleValue(value, minimum) {
|
|
61
|
+
if (!Number.isFinite(value) || value < minimum || value <= 0) {
|
|
62
|
+
throw new RangeError(`Circle value must be finite, positive, and at least ${minimum}.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
static circleValues(values, minimum) {
|
|
66
|
+
if (!Array.isArray(values)) {
|
|
67
|
+
throw new TypeError('Circle values must be an array.');
|
|
68
|
+
}
|
|
69
|
+
for (const value of values) {
|
|
70
|
+
OptionsValidation.circleValue(value, minimum);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
static chart(options) {
|
|
74
|
+
OptionsValidation.object(options, 'Smith options', [
|
|
75
|
+
'referenceImpedanceOhms',
|
|
76
|
+
'appearance',
|
|
77
|
+
'interaction',
|
|
78
|
+
'grid',
|
|
79
|
+
'circles',
|
|
80
|
+
'peripheralScales',
|
|
81
|
+
]);
|
|
82
|
+
if (options.referenceImpedanceOhms !== undefined &&
|
|
83
|
+
(!Number.isFinite(options.referenceImpedanceOhms) || options.referenceImpedanceOhms <= 0)) {
|
|
84
|
+
throw new RangeError('Reference impedance must be positive and finite.');
|
|
85
|
+
}
|
|
86
|
+
if (options.appearance !== undefined) {
|
|
87
|
+
Theme.resolve(options.appearance);
|
|
88
|
+
}
|
|
89
|
+
if (options.interaction !== undefined) {
|
|
90
|
+
OptionsValidation.object(options.interaction, 'Interaction', ['zoom', 'cursor']);
|
|
91
|
+
for (const name of ['zoom', 'cursor']) {
|
|
92
|
+
const value = options.interaction[name];
|
|
93
|
+
if (value !== undefined) {
|
|
94
|
+
OptionsValidation.boolean(value, name);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (options.peripheralScales !== undefined) {
|
|
99
|
+
const keys = ['visible', 'captionsVisible', 'tickLabelsVisible'];
|
|
100
|
+
OptionsValidation.object(options.peripheralScales, 'Peripheral scales', keys);
|
|
101
|
+
for (const key of keys) {
|
|
102
|
+
const value = options.peripheralScales[key];
|
|
103
|
+
if (value !== undefined) {
|
|
104
|
+
OptionsValidation.boolean(value, `Peripheral scales ${key}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (options.grid !== undefined) {
|
|
109
|
+
OptionsValidation.object(options.grid, 'Grid', [
|
|
110
|
+
'detail',
|
|
111
|
+
'labelsVisible',
|
|
112
|
+
'style',
|
|
113
|
+
'layers',
|
|
114
|
+
]);
|
|
115
|
+
if (options.grid.layers !== undefined) {
|
|
116
|
+
OptionsValidation.object(options.grid.layers, 'Grid layers', [
|
|
117
|
+
'resistance',
|
|
118
|
+
'reactance',
|
|
119
|
+
'conductance',
|
|
120
|
+
'susceptance',
|
|
121
|
+
]);
|
|
122
|
+
}
|
|
123
|
+
for (const name of ['resistance', 'reactance', 'conductance', 'susceptance']) {
|
|
124
|
+
const layer = options.grid.layers?.[name];
|
|
125
|
+
if (layer !== undefined) {
|
|
126
|
+
OptionsValidation.object(layer, 'Grid layer', [
|
|
127
|
+
'detail',
|
|
128
|
+
'labelsVisible',
|
|
129
|
+
'style',
|
|
130
|
+
'visible',
|
|
131
|
+
]);
|
|
132
|
+
if (layer.visible !== undefined) {
|
|
133
|
+
OptionsValidation.boolean(layer.visible, 'Grid visibility');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
for (const grid of [options.grid, ...Object.values(options.grid.layers ?? {})]) {
|
|
138
|
+
if (grid === undefined) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (grid.detail !== undefined) {
|
|
142
|
+
OptionsValidation.detail(grid.detail);
|
|
143
|
+
}
|
|
144
|
+
if (grid.labelsVisible !== undefined) {
|
|
145
|
+
OptionsValidation.boolean(grid.labelsVisible, 'Grid labels visibility');
|
|
146
|
+
}
|
|
147
|
+
if (grid.style !== undefined) {
|
|
148
|
+
OptionsValidation.gridStyle(grid.style);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (options.circles !== undefined) {
|
|
153
|
+
OptionsValidation.object(options.circles, 'Circles', ['q', 'vswr']);
|
|
154
|
+
for (const name of ['q', 'vswr']) {
|
|
155
|
+
const circle = options.circles[name];
|
|
156
|
+
if (circle === undefined) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
OptionsValidation.object(circle, 'Circle', ['visible', 'values', 'style']);
|
|
160
|
+
if (circle.visible !== undefined) {
|
|
161
|
+
OptionsValidation.boolean(circle.visible, 'Circle visibility');
|
|
162
|
+
}
|
|
163
|
+
if (circle.style !== undefined) {
|
|
164
|
+
OptionsValidation.circleStyle(circle.style);
|
|
165
|
+
}
|
|
166
|
+
if (circle.values !== undefined) {
|
|
167
|
+
OptionsValidation.circleValues(circle.values, name === 'vswr' ? 1 : 0);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
package/dist/lib/Smith.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import type { SmithOptions } from './options.js';
|
|
1
2
|
import type { SmithImageExportOptions } from './svg/export.js';
|
|
2
3
|
import type { SmithAppearance } from './appearance/types.js';
|
|
3
|
-
import type { TraceSamples } from './samples.js';
|
|
4
|
-
import type { ChartLayers, PeripheralScales } from './layers.js';
|
|
4
|
+
import type { TraceInput, TraceSamples, TraceTuple } from './samples.js';
|
|
5
|
+
import type { ChartLayers, GridDetail, PeripheralScales } from './layers.js';
|
|
5
6
|
import type { SmithReading } from './rf/RfCalculations.js';
|
|
6
7
|
import type { TraceOptions, TraceUpdateOptions, TraceInfo, MarkerSnapshot, MarkerComparison } from './measurements.js';
|
|
7
8
|
export declare enum SmithEventType {
|
|
@@ -19,8 +20,8 @@ export type SmithEvent = {
|
|
|
19
20
|
data: MarkerSnapshot;
|
|
20
21
|
};
|
|
21
22
|
export declare class Smith {
|
|
22
|
-
private referenceOhms;
|
|
23
23
|
private theme;
|
|
24
|
+
private referenceOhms;
|
|
24
25
|
private readonly renderer;
|
|
25
26
|
private data;
|
|
26
27
|
private destroyed;
|
|
@@ -33,12 +34,18 @@ export declare class Smith {
|
|
|
33
34
|
private listeners;
|
|
34
35
|
readonly layers: ChartLayers;
|
|
35
36
|
readonly peripheralScales: PeripheralScales;
|
|
36
|
-
constructor(
|
|
37
|
+
constructor(options?: SmithOptions);
|
|
38
|
+
/** Apply a validated patch. Omitted fields retain their current settings. */
|
|
39
|
+
setOptions(options: SmithOptions): void;
|
|
40
|
+
private applyOptions;
|
|
37
41
|
/** Replace the appearance while retaining explicit layer styles and trace colors. */
|
|
38
42
|
setAppearance(appearance: SmithAppearance): void;
|
|
39
43
|
draw(target: string | HTMLElement): void;
|
|
44
|
+
/** Set the same detail level on all impedance and admittance grid layers. */
|
|
45
|
+
setGridDetail(detail: GridDetail): void;
|
|
40
46
|
/** Export the current mounted view as standalone SVG with its configured background. */
|
|
41
47
|
toSvg(options?: SmithImageExportOptions): string;
|
|
48
|
+
private validateExportOptions;
|
|
42
49
|
private exportLegend;
|
|
43
50
|
/** Export the mounted view as PNG, optionally with radial scales and a trace legend. */
|
|
44
51
|
toPng(options?: SmithImageExportOptions): Promise<Blob>;
|
|
@@ -50,9 +57,14 @@ export declare class Smith {
|
|
|
50
57
|
get cursorReading(): SmithReading;
|
|
51
58
|
/** Enable or disable wheel, double-click, and mouse/touch zoom/pan. Keeps the current view. */
|
|
52
59
|
setZoomEnabled(enabled: boolean): void;
|
|
60
|
+
/** Enable or disable the cursor overlay and readings independently of zoom and markers. */
|
|
61
|
+
setCursorEnabled(enabled: boolean): void;
|
|
53
62
|
resetView(): void;
|
|
54
|
-
/** Add a named trace
|
|
63
|
+
/** Add a named trace without markers. Use addMarker to create markers explicitly. Returns a chart-local, stable ID. */
|
|
55
64
|
addTrace(values: TraceSamples, options?: TraceOptions): string;
|
|
65
|
+
addTrace(values: readonly TraceTuple[], options?: TraceOptions): string;
|
|
66
|
+
addTrace(values: Float64Array, options?: TraceOptions): string;
|
|
67
|
+
addTrace(values: TraceInput, options?: TraceOptions): string;
|
|
56
68
|
/** Detached metadata snapshots; IDs and marker display numbers survive removals. */
|
|
57
69
|
getTraces(): TraceInfo[];
|
|
58
70
|
private traceIndex;
|
|
@@ -62,6 +74,9 @@ export declare class Smith {
|
|
|
62
74
|
setTraceOptions(id: string, options: TraceOptions): boolean;
|
|
63
75
|
/** Replace samples; by default each marker follows its nearest measured frequency. */
|
|
64
76
|
updateTrace(id: string, values: TraceSamples, options?: TraceUpdateOptions): boolean;
|
|
77
|
+
updateTrace(id: string, values: readonly TraceTuple[], options?: TraceUpdateOptions): boolean;
|
|
78
|
+
updateTrace(id: string, values: Float64Array, options?: TraceUpdateOptions): boolean;
|
|
79
|
+
updateTrace(id: string, values: TraceInput, options?: TraceUpdateOptions): boolean;
|
|
65
80
|
removeTrace(id: string): boolean;
|
|
66
81
|
/** Place a marker on an existing sample; no interpolation is performed. */
|
|
67
82
|
addMarker(traceId: string, sampleIndex?: number): string | undefined;
|
package/dist/lib/Smith.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OptionsValidation } from './OptionsValidation.js';
|
|
1
2
|
import { MarkerLegend } from './svg/MarkerLegend.js';
|
|
2
3
|
import { ImageExporter } from './svg/ImageExporter.js';
|
|
3
4
|
import { Theme } from './appearance/Theme.js';
|
|
@@ -15,8 +16,8 @@ export var SmithEventType;
|
|
|
15
16
|
SmithEventType["MarkerDragEnd"] = "marker-drag-end";
|
|
16
17
|
})(SmithEventType || (SmithEventType = {}));
|
|
17
18
|
export class Smith {
|
|
18
|
-
referenceOhms;
|
|
19
19
|
theme;
|
|
20
|
+
referenceOhms;
|
|
20
21
|
renderer;
|
|
21
22
|
data = [];
|
|
22
23
|
destroyed = false;
|
|
@@ -29,12 +30,14 @@ export class Smith {
|
|
|
29
30
|
listeners = new Set();
|
|
30
31
|
layers;
|
|
31
32
|
peripheralScales;
|
|
32
|
-
constructor(
|
|
33
|
-
|
|
34
|
-
|
|
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) {
|
|
35
38
|
throw new RangeError('Reference impedance must be positive and finite.');
|
|
36
39
|
}
|
|
37
|
-
this.theme = Theme.resolve(appearance);
|
|
40
|
+
this.theme = Theme.resolve(options.appearance);
|
|
38
41
|
this.renderer = new SvgChartRenderer((position) => {
|
|
39
42
|
this.emit({
|
|
40
43
|
type: SmithEventType.Cursor,
|
|
@@ -44,6 +47,82 @@ export class Smith {
|
|
|
44
47
|
this.renderer.setTheme(this.theme);
|
|
45
48
|
this.layers = this.renderer.layers;
|
|
46
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
|
+
if (options.peripheralScales?.captionsVisible !== undefined) {
|
|
84
|
+
this.peripheralScales.setCaptionsVisible(options.peripheralScales.captionsVisible);
|
|
85
|
+
}
|
|
86
|
+
if (options.peripheralScales?.tickLabelsVisible !== undefined) {
|
|
87
|
+
this.peripheralScales.setTickLabelsVisible(options.peripheralScales.tickLabelsVisible);
|
|
88
|
+
}
|
|
89
|
+
for (const name of ['resistance', 'reactance', 'conductance', 'susceptance']) {
|
|
90
|
+
const layer = this.layers[name];
|
|
91
|
+
for (const settings of [options.grid, options.grid?.layers?.[name]]) {
|
|
92
|
+
if (settings === undefined) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (settings.detail !== undefined) {
|
|
96
|
+
layer.setDetail(settings.detail);
|
|
97
|
+
}
|
|
98
|
+
if (settings.labelsVisible !== undefined) {
|
|
99
|
+
layer.setLabelsVisible(settings.labelsVisible);
|
|
100
|
+
}
|
|
101
|
+
if (settings.style !== undefined) {
|
|
102
|
+
layer.setStyle(settings.style);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const visible = options.grid?.layers?.[name]?.visible;
|
|
106
|
+
if (visible !== undefined) {
|
|
107
|
+
layer.setVisible(visible);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
for (const name of ['q', 'vswr']) {
|
|
111
|
+
const settings = options.circles?.[name];
|
|
112
|
+
if (settings === undefined) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const layer = this.layers[name];
|
|
116
|
+
if (settings.values !== undefined) {
|
|
117
|
+
layer.setValues(settings.values);
|
|
118
|
+
}
|
|
119
|
+
if (settings.style !== undefined) {
|
|
120
|
+
layer.setStyle(settings.style);
|
|
121
|
+
}
|
|
122
|
+
if (settings.visible !== undefined) {
|
|
123
|
+
layer.setVisible(settings.visible);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
47
126
|
}
|
|
48
127
|
/** Replace the appearance while retaining explicit layer styles and trace colors. */
|
|
49
128
|
setAppearance(appearance) {
|
|
@@ -62,10 +141,18 @@ export class Smith {
|
|
|
62
141
|
this.assertAlive();
|
|
63
142
|
this.renderer.draw(target);
|
|
64
143
|
}
|
|
144
|
+
/** Set the same detail level on all impedance and admittance grid layers. */
|
|
145
|
+
setGridDetail(detail) {
|
|
146
|
+
this.assertAlive();
|
|
147
|
+
for (const name of ['resistance', 'reactance', 'conductance', 'susceptance']) {
|
|
148
|
+
this.layers[name].setDetail(detail);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
65
151
|
/** Export the current mounted view as standalone SVG with its configured background. */
|
|
66
152
|
toSvg(options) {
|
|
67
153
|
this.assertAlive();
|
|
68
|
-
|
|
154
|
+
this.validateExportOptions(options);
|
|
155
|
+
const source = this.renderer.toSvg(options?.lineDetail ?? 'full');
|
|
69
156
|
if (options === undefined) {
|
|
70
157
|
return source;
|
|
71
158
|
}
|
|
@@ -75,6 +162,14 @@ export class Smith {
|
|
|
75
162
|
}
|
|
76
163
|
return ImageExporter.svg(sources, options, this.exportLegend(options));
|
|
77
164
|
}
|
|
165
|
+
validateExportOptions(options) {
|
|
166
|
+
if (options?.lineDetail !== undefined && !['full', 'view'].includes(options.lineDetail)) {
|
|
167
|
+
throw new TypeError('Export line detail must be full or view.');
|
|
168
|
+
}
|
|
169
|
+
if (options?.scaleReadout !== undefined && !options.scales) {
|
|
170
|
+
throw new TypeError('Scale readout requires included radial scales.');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
78
173
|
exportLegend(options) {
|
|
79
174
|
const traces = this.getTraces();
|
|
80
175
|
return {
|
|
@@ -90,7 +185,9 @@ export class Smith {
|
|
|
90
185
|
}
|
|
91
186
|
/** Export the mounted view as PNG, optionally with radial scales and a trace legend. */
|
|
92
187
|
async toPng(options = {}) {
|
|
93
|
-
|
|
188
|
+
this.assertAlive();
|
|
189
|
+
this.validateExportOptions(options);
|
|
190
|
+
const sources = [this.renderer.toSvg(options.lineDetail ?? 'full')];
|
|
94
191
|
if (options.scales) {
|
|
95
192
|
sources.push(options.scales.toSvg({ readout: options.scaleReadout }));
|
|
96
193
|
}
|
|
@@ -124,9 +221,6 @@ export class Smith {
|
|
|
124
221
|
return;
|
|
125
222
|
}
|
|
126
223
|
this.renderer.setMarkerDragging(isDragging);
|
|
127
|
-
if (isDragging) {
|
|
128
|
-
this.emit({ type: SmithEventType.Cursor, data: undefined });
|
|
129
|
-
}
|
|
130
224
|
}
|
|
131
225
|
/** Last cursor position. Use cursor events to detect pointer leave. */
|
|
132
226
|
get cursorReading() {
|
|
@@ -137,11 +231,15 @@ export class Smith {
|
|
|
137
231
|
this.assertAlive();
|
|
138
232
|
this.renderer.setZoomEnabled(enabled);
|
|
139
233
|
}
|
|
234
|
+
/** Enable or disable the cursor overlay and readings independently of zoom and markers. */
|
|
235
|
+
setCursorEnabled(enabled) {
|
|
236
|
+
this.assertAlive();
|
|
237
|
+
this.renderer.setCursorEnabled(enabled);
|
|
238
|
+
}
|
|
140
239
|
resetView() {
|
|
141
240
|
this.assertAlive();
|
|
142
241
|
this.renderer.resetView();
|
|
143
242
|
}
|
|
144
|
-
/** Add a named trace with one initial marker. Returns a chart-local, stable ID. */
|
|
145
243
|
addTrace(values, options = {}) {
|
|
146
244
|
this.assertAlive();
|
|
147
245
|
this.validateTraceOptions(options);
|
|
@@ -195,6 +293,10 @@ export class Smith {
|
|
|
195
293
|
if (options.mode !== undefined && !['points', 'line', 'both'].includes(options.mode)) {
|
|
196
294
|
throw new TypeError('Trace mode must be points, line, or both.');
|
|
197
295
|
}
|
|
296
|
+
if (options.lineTolerancePx !== undefined &&
|
|
297
|
+
(!Number.isFinite(options.lineTolerancePx) || options.lineTolerancePx < 0)) {
|
|
298
|
+
throw new RangeError('Line tolerance must be finite and non-negative.');
|
|
299
|
+
}
|
|
198
300
|
for (const key of ['lineWidth', 'pointRadius']) {
|
|
199
301
|
const value = options[key];
|
|
200
302
|
if (value !== undefined && (!Number.isFinite(value) || value <= 0)) {
|
|
@@ -233,7 +335,6 @@ export class Smith {
|
|
|
233
335
|
}
|
|
234
336
|
return true;
|
|
235
337
|
}
|
|
236
|
-
/** Replace samples; by default each marker follows its nearest measured frequency. */
|
|
237
338
|
updateTrace(id, values, options = {}) {
|
|
238
339
|
this.assertAlive();
|
|
239
340
|
const data = this.data[this.traceIndex(id)];
|
|
@@ -329,12 +430,11 @@ export class Smith {
|
|
|
329
430
|
if (referenceImpedanceOhms === this.referenceOhms) {
|
|
330
431
|
return;
|
|
331
432
|
}
|
|
332
|
-
const samples = this.data.map((data) =>
|
|
433
|
+
const samples = this.data.map((data) => data.Samples.renormalize(this.referenceOhms, referenceImpedanceOhms));
|
|
333
434
|
this.data.forEach((data) => data.Markers.forEach((entry) => entry.marker.cancelDrag()));
|
|
334
435
|
this.referenceOhms = referenceImpedanceOhms;
|
|
335
436
|
this.data.forEach((data, index) => data.update(samples[index], 'sample-index'));
|
|
336
437
|
this.renderer.hideCursor();
|
|
337
|
-
this.emit({ type: SmithEventType.Cursor, data: undefined });
|
|
338
438
|
}
|
|
339
439
|
clearTraces() {
|
|
340
440
|
this.assertAlive();
|
|
@@ -373,7 +473,6 @@ export class Smith {
|
|
|
373
473
|
this.emit({ type: SmithEventType.Marker, data: snapshot });
|
|
374
474
|
}
|
|
375
475
|
});
|
|
376
|
-
data.addMarker();
|
|
377
476
|
return data;
|
|
378
477
|
}
|
|
379
478
|
/** Subscribe to chart events. The returned function removes this subscription. */
|
|
@@ -6,6 +6,11 @@ export class Theme {
|
|
|
6
6
|
if (!appearance || typeof appearance !== 'object' || Array.isArray(appearance)) {
|
|
7
7
|
throw new TypeError('Appearance must be an object.');
|
|
8
8
|
}
|
|
9
|
+
for (const key of Object.keys(appearance)) {
|
|
10
|
+
if (key !== 'theme' && key !== 'overrides') {
|
|
11
|
+
throw new TypeError(`Unknown appearance option: ${key}.`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
9
14
|
const name = appearance.theme === undefined ? 'light' : appearance.theme;
|
|
10
15
|
if (name !== 'light' && name !== 'dark') {
|
|
11
16
|
throw new TypeError('Theme must be light or dark.');
|
|
@@ -9,7 +9,7 @@ export declare class CircleLayerControl extends LayerControl implements CircleLa
|
|
|
9
9
|
constructor(layer: ConstQCircles | ConstSwrCircles, minimum: number, assertAlive: () => void);
|
|
10
10
|
setVisible(visible: boolean): void;
|
|
11
11
|
setStyle(style: Partial<CircleStyle>): void;
|
|
12
|
+
setValues(values: readonly number[]): void;
|
|
12
13
|
addValue(value: number): void;
|
|
13
14
|
removeValue(value: number): void;
|
|
14
|
-
private validateValue;
|
|
15
15
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OptionsValidation } from '../OptionsValidation.js';
|
|
1
2
|
import { LayerControl } from './LayerControl.js';
|
|
2
3
|
/** Exposes Q/VSWR circle controls without exposing the SVG renderer. */
|
|
3
4
|
export class CircleLayerControl extends LayerControl {
|
|
@@ -10,11 +11,12 @@ export class CircleLayerControl extends LayerControl {
|
|
|
10
11
|
}
|
|
11
12
|
setVisible(visible) {
|
|
12
13
|
this.assertAlive();
|
|
14
|
+
OptionsValidation.boolean(visible, 'Circle visibility');
|
|
13
15
|
this.layer.visibility(visible);
|
|
14
16
|
}
|
|
15
17
|
setStyle(style) {
|
|
16
18
|
this.assertAlive();
|
|
17
|
-
|
|
19
|
+
OptionsValidation.circleStyle(style);
|
|
18
20
|
if (style.stroke !== undefined) {
|
|
19
21
|
this.layer.Stroke = style.stroke;
|
|
20
22
|
}
|
|
@@ -22,19 +24,19 @@ export class CircleLayerControl extends LayerControl {
|
|
|
22
24
|
this.layer.StrokeWidth = String(style.strokeWidth);
|
|
23
25
|
}
|
|
24
26
|
}
|
|
27
|
+
setValues(values) {
|
|
28
|
+
this.assertAlive();
|
|
29
|
+
OptionsValidation.circleValues(values, this.minimum);
|
|
30
|
+
this.layer.setValues(values);
|
|
31
|
+
}
|
|
25
32
|
addValue(value) {
|
|
26
33
|
this.assertAlive();
|
|
27
|
-
|
|
34
|
+
OptionsValidation.circleValue(value, this.minimum);
|
|
28
35
|
this.layer.append(value);
|
|
29
36
|
}
|
|
30
37
|
removeValue(value) {
|
|
31
38
|
this.assertAlive();
|
|
32
|
-
|
|
39
|
+
OptionsValidation.circleValue(value, this.minimum);
|
|
33
40
|
this.layer.remove(value);
|
|
34
41
|
}
|
|
35
|
-
validateValue(value) {
|
|
36
|
-
if (!Number.isFinite(value) || value < this.minimum || value <= 0) {
|
|
37
|
-
throw new RangeError(`Circle value must be finite, positive, and at least ${this.minimum}.`);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
42
|
}
|
|
@@ -74,6 +74,11 @@ export class ConstQCircles {
|
|
|
74
74
|
this.circles.push(Q);
|
|
75
75
|
this.drawConstQCircle(Q);
|
|
76
76
|
}
|
|
77
|
+
setValues(values) {
|
|
78
|
+
this.circles = [...new Set(values)];
|
|
79
|
+
this.container.Element.selectAll('*').remove();
|
|
80
|
+
this.drawConstQCircles(this.circles);
|
|
81
|
+
}
|
|
77
82
|
remove(Q) {
|
|
78
83
|
const index = this.circles.indexOf(Q);
|
|
79
84
|
if (index === -1) {
|
|
@@ -73,6 +73,11 @@ export class ConstSwrCircles {
|
|
|
73
73
|
this.circles.push(swr);
|
|
74
74
|
this.drawConstSwrCircle(swr);
|
|
75
75
|
}
|
|
76
|
+
setValues(values) {
|
|
77
|
+
this.circles = [...new Set(values)];
|
|
78
|
+
this.container.Element.selectAll('*').remove();
|
|
79
|
+
this.drawConstSwrCircles(this.circles);
|
|
80
|
+
}
|
|
76
81
|
remove(swr) {
|
|
77
82
|
const index = this.circles.indexOf(swr);
|
|
78
83
|
if (index === -1) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OptionsValidation } from '../OptionsValidation.js';
|
|
1
2
|
import { LayerControl } from './LayerControl.js';
|
|
2
3
|
/** Exposes grid controls without exposing the SVG renderer. */
|
|
3
4
|
export class GridLayerControl extends LayerControl {
|
|
@@ -10,25 +11,25 @@ export class GridLayerControl extends LayerControl {
|
|
|
10
11
|
}
|
|
11
12
|
setVisible(visible) {
|
|
12
13
|
this.assertAlive();
|
|
14
|
+
OptionsValidation.boolean(visible, 'Grid visibility');
|
|
13
15
|
this.layer.visibility(visible);
|
|
14
16
|
this.changed();
|
|
15
17
|
}
|
|
16
18
|
setLabelsVisible(visible) {
|
|
17
19
|
this.assertAlive();
|
|
20
|
+
OptionsValidation.boolean(visible, 'Grid visibility');
|
|
18
21
|
this.layer.setLabelsVisible(visible);
|
|
19
22
|
this.changed();
|
|
20
23
|
}
|
|
21
24
|
setDetail(detail) {
|
|
22
25
|
this.assertAlive();
|
|
23
|
-
|
|
24
|
-
throw new RangeError('Grid detail must be basic, standard, or detailed.');
|
|
25
|
-
}
|
|
26
|
+
OptionsValidation.detail(detail);
|
|
26
27
|
this.layer.setDetail(detail);
|
|
27
28
|
this.changed();
|
|
28
29
|
}
|
|
29
30
|
setStyle(style) {
|
|
30
31
|
this.assertAlive();
|
|
31
|
-
|
|
32
|
+
OptionsValidation.gridStyle(style);
|
|
32
33
|
const properties = {
|
|
33
34
|
stroke: 'Stroke',
|
|
34
35
|
majorWidth: 'MajorWidth',
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
/** Shared lifecycle
|
|
1
|
+
/** Shared lifecycle guard for chart layer adapters. */
|
|
2
2
|
export declare abstract class LayerControl {
|
|
3
3
|
protected readonly assertAlive: () => void;
|
|
4
4
|
protected constructor(assertAlive: () => void);
|
|
5
|
-
protected validateLengths<T>(style: T, keys: readonly (keyof T)[]): void;
|
|
6
5
|
}
|
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
/** Shared lifecycle
|
|
1
|
+
/** Shared lifecycle guard for chart layer adapters. */
|
|
2
2
|
export class LayerControl {
|
|
3
3
|
assertAlive;
|
|
4
4
|
constructor(assertAlive) {
|
|
5
5
|
this.assertAlive = assertAlive;
|
|
6
6
|
}
|
|
7
|
-
validateLengths(style, keys) {
|
|
8
|
-
for (const key of keys) {
|
|
9
|
-
const value = style[key];
|
|
10
|
-
if (value !== undefined &&
|
|
11
|
-
(typeof value !== 'number' || !Number.isFinite(value) || value <= 0)) {
|
|
12
|
-
throw new RangeError(`${String(key)} must be a positive finite number.`);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
7
|
}
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export { Smith, SmithEventType } from './Smith.js';
|
|
2
2
|
export type { SmithEvent } from './Smith.js';
|
|
3
3
|
export { Complex } from './math/Complex.js';
|
|
4
|
-
export type { TraceSamples, TraceSample } from './samples.js';
|
|
4
|
+
export type { TraceSamples, TraceSample, TraceTuple, TraceInput } from './samples.js';
|
|
5
5
|
export { Touchstone } from './io/Touchstone.js';
|
|
6
|
-
export type { TouchstoneData } from './io/
|
|
6
|
+
export type { TouchstoneData, TouchstoneParseOptions, TouchstoneWriteOptions } from './io/types.js';
|
|
7
7
|
export { SmithScales } from './scales/SmithScales.js';
|
|
8
8
|
export { MarkerMeasurements } from './MarkerMeasurements.js';
|
|
9
9
|
export type { TraceOptions, TraceUpdateOptions, MarkerSelectionStrategy, TraceStyle, TraceRenderMode, TraceInfo, MarkerInfo, MarkerSnapshot, MarkerComparison, } from './measurements.js';
|
|
@@ -14,3 +14,4 @@ export { SmithFormatter } from './SmithFormatter.js';
|
|
|
14
14
|
export type { ReactiveComponent } from './rf/RfCalculations.js';
|
|
15
15
|
export type { SmithAppearance, SmithTheme, SmithThemeOverrides } from './appearance/types.js';
|
|
16
16
|
export type { MarkerLegendField, MarkerLegendOptions, ImageExportOptions, SmithImageExportOptions, ScaleImageExportOptions, ScaleExportReadout, } from './svg/export.js';
|
|
17
|
+
export type { SmithOptions, GridOptions, GridLayerOptions, CircleLayerOptions, CircleOptions, InteractionOptions, PeripheralScalesOptions, } from './options.js';
|