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.
Files changed (45) hide show
  1. package/README.md +324 -37
  2. package/dist/lib/OptionsValidation.d.ts +16 -0
  3. package/dist/lib/OptionsValidation.js +172 -0
  4. package/dist/lib/Smith.d.ts +20 -5
  5. package/dist/lib/Smith.js +114 -15
  6. package/dist/lib/appearance/Theme.js +5 -0
  7. package/dist/lib/grid/CircleLayerControl.d.ts +1 -1
  8. package/dist/lib/grid/CircleLayerControl.js +10 -8
  9. package/dist/lib/grid/ConstQCircles.d.ts +1 -0
  10. package/dist/lib/grid/ConstQCircles.js +5 -0
  11. package/dist/lib/grid/ConstSwrCircles.d.ts +1 -0
  12. package/dist/lib/grid/ConstSwrCircles.js +5 -0
  13. package/dist/lib/grid/GridLayerControl.js +5 -4
  14. package/dist/lib/grid/LayerControl.d.ts +1 -2
  15. package/dist/lib/grid/LayerControl.js +1 -10
  16. package/dist/lib/index.d.ts +3 -2
  17. package/dist/lib/io/Touchstone.d.ts +13 -7
  18. package/dist/lib/io/Touchstone.js +146 -46
  19. package/dist/lib/io/types.d.ts +17 -0
  20. package/dist/lib/io/types.js +1 -0
  21. package/dist/lib/layers.d.ts +7 -1
  22. package/dist/lib/measurements.d.ts +2 -0
  23. package/dist/lib/options.d.ts +58 -0
  24. package/dist/lib/options.js +1 -0
  25. package/dist/lib/rendering/SvgChartRenderer.d.ts +6 -3
  26. package/dist/lib/rendering/SvgChartRenderer.js +58 -11
  27. package/dist/lib/rf/RfCalculations.d.ts +3 -1
  28. package/dist/lib/rf/RfCalculations.js +23 -6
  29. package/dist/lib/samples.d.ts +5 -1
  30. package/dist/lib/scales/SmithPeripheralScales.d.ts +5 -0
  31. package/dist/lib/scales/SmithPeripheralScales.js +20 -1
  32. package/dist/lib/svg/SvgExporter.d.ts +1 -1
  33. package/dist/lib/svg/SvgExporter.js +4 -3
  34. package/dist/lib/svg/export.d.ts +3 -1
  35. package/dist/lib/traces/SmithData.d.ts +10 -4
  36. package/dist/lib/traces/SmithData.js +14 -4
  37. package/dist/lib/traces/TraceBuffer.d.ts +19 -0
  38. package/dist/lib/traces/TraceBuffer.js +176 -0
  39. package/dist/lib/traces/TraceLineDetail.d.ts +8 -0
  40. package/dist/lib/traces/TraceLineDetail.js +52 -0
  41. package/dist/lib/traces/TraceModel.d.ts +6 -8
  42. package/dist/lib/traces/TraceModel.js +24 -49
  43. package/dist/lib/traces/TraceRenderer.d.ts +13 -3
  44. package/dist/lib/traces/TraceRenderer.js +83 -23
  45. 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
+ }
@@ -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(referenceOhms?: number, appearance?: SmithAppearance);
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 with one initial marker. Returns a chart-local, stable ID. */
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(referenceOhms = 50, appearance = {}) {
33
- this.referenceOhms = referenceOhms;
34
- if (!Number.isFinite(referenceOhms) || referenceOhms <= 0) {
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
- const source = this.renderer.toSvg();
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
- const sources = [this.toSvg()];
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) => RfCalculations.renormalizeSamples(data.Samples, this.referenceOhms, referenceImpedanceOhms));
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
- this.validateLengths(style, ['strokeWidth']);
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
- this.validateValue(value);
34
+ OptionsValidation.circleValue(value, this.minimum);
28
35
  this.layer.append(value);
29
36
  }
30
37
  removeValue(value) {
31
38
  this.assertAlive();
32
- this.validateValue(value);
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
  }
@@ -24,6 +24,7 @@ export declare class ConstQCircles {
24
24
  show(): void;
25
25
  hide(): void;
26
26
  append(Q: number): void;
27
+ setValues(values: readonly number[]): void;
27
28
  remove(Q: number): void;
28
29
  }
29
30
  export {};
@@ -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) {
@@ -24,6 +24,7 @@ export declare class ConstSwrCircles {
24
24
  show(): void;
25
25
  hide(): void;
26
26
  append(swr: number): void;
27
+ setValues(values: readonly number[]): void;
27
28
  remove(swr: number): void;
28
29
  }
29
30
  export {};
@@ -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
- if (!['basic', 'standard', 'detailed'].includes(detail)) {
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
- this.validateLengths(style, ['majorWidth', 'minorWidth', 'textFontSize']);
32
+ OptionsValidation.gridStyle(style);
32
33
  const properties = {
33
34
  stroke: 'Stroke',
34
35
  majorWidth: 'MajorWidth',
@@ -1,6 +1,5 @@
1
- /** Shared lifecycle and style validation for chart layer adapters. */
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 and style validation for chart layer adapters. */
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
  }
@@ -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/Touchstone.js';
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';