smithkit 0.6.0 → 0.7.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 (33) hide show
  1. package/README.md +2 -2
  2. package/dist/lib/OptionsValidation.d.ts +3 -3
  3. package/dist/lib/OptionsValidation.js +17 -13
  4. package/dist/lib/Smith.d.ts +3 -0
  5. package/dist/lib/Smith.js +40 -9
  6. package/dist/lib/appearance/SvgTheme.d.ts +1 -0
  7. package/dist/lib/appearance/SvgTheme.js +8 -7
  8. package/dist/lib/appearance/Theme.js +49 -52
  9. package/dist/lib/appearance/types.d.ts +75 -31
  10. package/dist/lib/grid/CircleLayerControl.d.ts +2 -2
  11. package/dist/lib/grid/CircleLayerControl.js +4 -4
  12. package/dist/lib/grid/ConstCircles.js +6 -6
  13. package/dist/lib/grid/ConstQCircles.js +4 -1
  14. package/dist/lib/grid/ConstSwrCircles.js +4 -1
  15. package/dist/lib/grid/GridLayerControl.d.ts +2 -2
  16. package/dist/lib/grid/GridLayerControl.js +10 -11
  17. package/dist/lib/index.d.ts +2 -2
  18. package/dist/lib/interaction/SmithCursor.js +6 -6
  19. package/dist/lib/layers.d.ts +28 -14
  20. package/dist/lib/measurements.d.ts +20 -8
  21. package/dist/lib/options.d.ts +4 -4
  22. package/dist/lib/rendering/ChartHost.d.ts +5 -0
  23. package/dist/lib/rendering/ChartHost.js +14 -0
  24. package/dist/lib/rendering/SvgChartRenderer.d.ts +2 -2
  25. package/dist/lib/rendering/SvgChartRenderer.js +1 -1
  26. package/dist/lib/scales/SmithPeripheralScales.js +11 -11
  27. package/dist/lib/scales/SmithScales.d.ts +3 -0
  28. package/dist/lib/scales/SmithScales.js +37 -16
  29. package/dist/lib/traces/SmithData.d.ts +3 -3
  30. package/dist/lib/traces/SmithMarker.js +5 -5
  31. package/dist/lib/traces/TraceRenderer.d.ts +3 -3
  32. package/dist/lib/traces/TraceRenderer.js +19 -18
  33. package/package.json +2 -1
package/README.md CHANGED
@@ -60,8 +60,7 @@ Mount a container with a non-zero size:
60
60
  ```ts
61
61
  import { Smith } from 'smithkit';
62
62
 
63
- const chart = new Smith({ referenceImpedanceOhms: 50 });
64
- chart.draw('#smith');
63
+ const chart = new Smith('#smith', { referenceImpedanceOhms: 50 });
65
64
  const traceId = chart.addTrace(
66
65
  [
67
66
  [1e9, 0.5, -0.5],
@@ -84,6 +83,7 @@ packed `Float64Array` triples are also supported. Inputs are validated and copie
84
83
  By default, the chart shows a labeled standard impedance grid with zoom, cursor
85
84
  tracking, and peripheral scales disabled. No markers are added automatically.
86
85
  The constructor and `setOptions()` share the same grouped configuration schema.
86
+ To mount later, use `new Smith(options)` and then `chart.draw(elementOrSelector)`.
87
87
 
88
88
  Create charts in the browser after mounting the host; call `destroy()` on unmount.
89
89
  Calculation, formatting, and Touchstone classes also work without a DOM.
@@ -1,5 +1,5 @@
1
1
  import type { SmithOptions } from './options.js';
2
- import type { CircleStyle, GridDetail, GridStyle } from './layers.js';
2
+ import type { CircleStyleOptions, GridDetail, GridStyleOptions } from './layers.js';
3
3
  /** Shared validation for initial configuration, patches, and individual controls. */
4
4
  export declare class OptionsValidation {
5
5
  private constructor();
@@ -8,8 +8,8 @@ export declare class OptionsValidation {
8
8
  static detail(value: GridDetail): void;
9
9
  private static lengths;
10
10
  private static strings;
11
- static gridStyle(style: Partial<GridStyle>): void;
12
- static circleStyle(style: Partial<CircleStyle>): void;
11
+ static gridStyle(style: GridStyleOptions): void;
12
+ static circleStyle(style: CircleStyleOptions): void;
13
13
  static circleValue(value: number, minimum: number): void;
14
14
  static circleValues(values: readonly number[], minimum: number): void;
15
15
  static chart(options: SmithOptions): void;
@@ -41,21 +41,25 @@ export class OptionsValidation {
41
41
  }
42
42
  }
43
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']);
44
+ OptionsValidation.object(style, 'Grid style', ['line', 'text']);
45
+ if (style.line !== undefined) {
46
+ OptionsValidation.object(style.line, 'Grid line', ['color', 'majorWidth', 'minorWidth']);
47
+ OptionsValidation.lengths(style.line, ['majorWidth', 'minorWidth']);
48
+ OptionsValidation.strings(style.line, ['color']);
49
+ }
50
+ if (style.text !== undefined) {
51
+ OptionsValidation.object(style.text, 'Grid text', ['color', 'fontFamily', 'fontSize']);
52
+ OptionsValidation.lengths(style.text, ['fontSize']);
53
+ OptionsValidation.strings(style.text, ['color', 'fontFamily']);
54
+ }
54
55
  }
55
56
  static circleStyle(style) {
56
- OptionsValidation.object(style, 'Circle style', ['stroke', 'strokeWidth']);
57
- OptionsValidation.lengths(style, ['strokeWidth']);
58
- OptionsValidation.strings(style, ['stroke']);
57
+ OptionsValidation.object(style, 'Circle style', ['line']);
58
+ if (style.line !== undefined) {
59
+ OptionsValidation.object(style.line, 'Circle line', ['color', 'width']);
60
+ OptionsValidation.lengths(style.line, ['width']);
61
+ OptionsValidation.strings(style.line, ['color']);
62
+ }
59
63
  }
60
64
  static circleValue(value, minimum) {
61
65
  if (!Number.isFinite(value) || value < minimum || value <= 0) {
@@ -34,7 +34,10 @@ export declare class Smith {
34
34
  private listeners;
35
35
  readonly layers: ChartLayers;
36
36
  readonly peripheralScales: PeripheralScales;
37
+ /** Configure a chart for later mounting with draw(). */
37
38
  constructor(options?: SmithOptions);
39
+ /** Configure and mount immediately. String targets are CSS selectors, as in draw(). */
40
+ constructor(target: string | HTMLElement, options?: SmithOptions);
38
41
  /** Apply a validated patch. Omitted fields retain their current settings. */
39
42
  setOptions(options: SmithOptions): void;
40
43
  private applyOptions;
package/dist/lib/Smith.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { OptionsValidation } from './OptionsValidation.js';
2
+ import { ChartHost } from './rendering/ChartHost.js';
2
3
  import { MarkerLegend } from './svg/MarkerLegend.js';
3
4
  import { ImageExporter } from './svg/ImageExporter.js';
4
5
  import { Theme } from './appearance/Theme.js';
@@ -30,7 +31,18 @@ export class Smith {
30
31
  listeners = new Set();
31
32
  layers;
32
33
  peripheralScales;
33
- constructor(options = {}) {
34
+ constructor(targetOrOptions = {}, configuration) {
35
+ const target = typeof targetOrOptions === 'string' || ChartHost.isElement(targetOrOptions)
36
+ ? targetOrOptions
37
+ : undefined;
38
+ if (target === undefined && configuration !== undefined) {
39
+ throw new TypeError('A container is required before the second options argument.');
40
+ }
41
+ const options = target === undefined
42
+ ? targetOrOptions
43
+ : configuration === undefined
44
+ ? {}
45
+ : configuration;
34
46
  OptionsValidation.chart(options);
35
47
  this.referenceOhms =
36
48
  options.referenceImpedanceOhms === undefined ? 50 : options.referenceImpedanceOhms;
@@ -49,6 +61,9 @@ export class Smith {
49
61
  this.peripheralScales = this.renderer.peripheralScales;
50
62
  try {
51
63
  this.applyOptions(options);
64
+ if (target !== undefined) {
65
+ this.draw(target);
66
+ }
52
67
  }
53
68
  catch (error) {
54
69
  // Invalid constructor options must not retain observers or event handlers.
@@ -133,7 +148,7 @@ export class Smith {
133
148
  for (const data of this.data) {
134
149
  const index = this.traceMetadata.get(data).colorIndex;
135
150
  if (index !== undefined) {
136
- data.setColor(theme.traceColors[index % theme.traceColors.length]);
151
+ data.setColor(theme.traces.colors[index % theme.traces.colors.length]);
137
152
  }
138
153
  }
139
154
  }
@@ -176,8 +191,8 @@ export class Smith {
176
191
  ? MarkerLegend.entries(traces, (id) => this.getMarker(id), options.markerLegend)
177
192
  : []),
178
193
  ],
179
- textColor: this.theme.grid.textColor,
180
- fontFamily: this.theme.fontFamily,
194
+ textColor: this.theme.grid.text.color,
195
+ fontFamily: this.theme.text.fontFamily,
181
196
  };
182
197
  }
183
198
  /** Export the mounted view as PNG, optionally with radial scales and a trace legend. */
@@ -287,15 +302,31 @@ export class Smith {
287
302
  return;
288
303
  }
289
304
  validateTraceOptions(options) {
305
+ OptionsValidation.object(options, 'Trace', [
306
+ 'mode',
307
+ 'line',
308
+ 'point',
309
+ 'name',
310
+ 'color',
311
+ 'visible',
312
+ ]);
313
+ if (options.line !== undefined) {
314
+ OptionsValidation.object(options.line, 'Trace line', ['width', 'tolerancePx']);
315
+ }
316
+ if (options.point !== undefined) {
317
+ OptionsValidation.object(options.point, 'Trace point', ['radius']);
318
+ }
290
319
  if (options.mode !== undefined && !['points', 'line', 'both'].includes(options.mode)) {
291
320
  throw new TypeError('Trace mode must be points, line, or both.');
292
321
  }
293
- if (options.lineTolerancePx !== undefined &&
294
- (!Number.isFinite(options.lineTolerancePx) || options.lineTolerancePx < 0)) {
322
+ if (options.line?.tolerancePx !== undefined &&
323
+ (!Number.isFinite(options.line.tolerancePx) || options.line.tolerancePx < 0)) {
295
324
  throw new RangeError('Line tolerance must be finite and non-negative.');
296
325
  }
297
- for (const key of ['lineWidth', 'pointRadius']) {
298
- const value = options[key];
326
+ for (const [key, value] of [
327
+ ['line.width', options.line?.width],
328
+ ['point.radius', options.point?.radius],
329
+ ]) {
299
330
  if (value !== undefined && (!Number.isFinite(value) || value <= 0)) {
300
331
  throw new RangeError(`${key} must be positive and finite.`);
301
332
  }
@@ -439,7 +470,7 @@ export class Smith {
439
470
  this.data = [];
440
471
  }
441
472
  createSmithData(values, dataset, options) {
442
- const color = this.theme.traceColors[dataset % this.theme.traceColors.length];
473
+ const color = this.theme.traces.colors[dataset % this.theme.traces.colors.length];
443
474
  const data = this.renderer.createTrace(values, color, options, (marker, dragging) => {
444
475
  this.markerDragChanged(marker, dragging);
445
476
  const snapshot = this.getMarker(this.markerId(marker));
@@ -3,4 +3,5 @@ import type { SmithTheme } from './types.js';
3
3
  export declare class SvgTheme {
4
4
  private constructor();
5
5
  static apply(element: HTMLElement | SVGElement, theme: SmithTheme): void;
6
+ private static tokens;
6
7
  }
@@ -2,20 +2,21 @@
2
2
  export class SvgTheme {
3
3
  constructor() { }
4
4
  static apply(element, theme) {
5
- for (const [section, value] of Object.entries(theme)) {
5
+ SvgTheme.tokens(element, theme, '--smithkit');
6
+ element.style.backgroundColor = theme.background;
7
+ }
8
+ static tokens(element, values, prefix) {
9
+ for (const [key, value] of Object.entries(values)) {
6
10
  if (Array.isArray(value)) {
7
11
  continue;
8
12
  }
9
13
  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
+ SvgTheme.tokens(element, value, `${prefix}-${key}`);
14
15
  }
15
16
  else {
16
- element.style.setProperty(`--smithkit-${section}`, value);
17
+ const suffix = key === 'fontSize' ? 'px' : '';
18
+ element.style.setProperty(`${prefix}-${key}`, String(value) + suffix);
17
19
  }
18
20
  }
19
- element.style.backgroundColor = theme.background;
20
21
  }
21
22
  }
@@ -18,66 +18,63 @@ export class Theme {
18
18
  const dark = name === 'dark';
19
19
  const theme = {
20
20
  background: dark ? '#0f172a' : 'transparent',
21
- fontFamily: 'Verdana, sans-serif',
21
+ text: { fontFamily: 'Verdana, sans-serif' },
22
22
  grid: {
23
- stroke: dark ? '#94a3b8' : '#64748b',
24
- textColor: dark ? '#e2e8f0' : '#111827',
25
- majorWidth: 1,
26
- minorWidth: 0.6,
27
- fontSize: 7,
23
+ line: { color: dark ? '#94a3b8' : '#64748b', majorWidth: 1, minorWidth: 0.6 },
24
+ text: { color: dark ? '#e2e8f0' : '#111827', fontSize: 7 },
25
+ },
26
+ boundary: { color: dark ? '#cbd5e1' : '#334155', width: 1 },
27
+ circles: {
28
+ q: { color: dark ? '#93c5fd' : 'blue', width: 1 },
29
+ vswr: { color: dark ? '#fbbf24' : 'orange', width: 1 },
28
30
  },
29
- boundary: { stroke: dark ? '#cbd5e1' : '#334155', width: 1 },
30
- circles: { q: dark ? '#93c5fd' : 'blue', vswr: dark ? '#fbbf24' : 'orange', width: 1 },
31
31
  scales: {
32
- stroke: dark ? '#94a3b8' : '#64748b',
33
- boundaryColor: dark ? '#cbd5e1' : '#334155',
34
- textColor: dark ? '#e2e8f0' : '#334155',
35
- valueColor: dark ? '#fca5a5' : '#b42318',
36
- indicatorColor: dark ? '#f87171' : '#dc2626',
37
- indicatorOutline: dark ? '#0f172a' : 'white',
38
- fontSize: 10,
39
- titleFontSize: 12,
40
- valueFontSize: 11,
41
- peripheralFontSize: 10.5,
32
+ line: { color: dark ? '#94a3b8' : '#64748b' },
33
+ boundary: { color: dark ? '#cbd5e1' : '#334155' },
34
+ text: { color: dark ? '#e2e8f0' : '#334155', fontSize: 10 },
35
+ value: { color: dark ? '#fca5a5' : '#b42318', fontSize: 11 },
36
+ indicator: { color: dark ? '#f87171' : '#dc2626', outline: dark ? '#0f172a' : 'white' },
37
+ title: { fontSize: 12 },
38
+ peripheral: { fontSize: 10.5 },
42
39
  },
43
40
  cursor: {
44
- pointColor: dark ? '#fca5a5' : 'red',
45
- impedanceColor: dark ? '#fca5a5' : 'red',
46
- admittanceColor: dark ? '#86efac' : 'green',
47
- pointSize: 5,
48
- lineWidth: 1,
41
+ point: { color: dark ? '#fca5a5' : 'red', size: 5 },
42
+ impedance: { color: dark ? '#fca5a5' : 'red' },
43
+ admittance: { color: dark ? '#86efac' : 'green' },
44
+ line: { width: 1 },
49
45
  },
50
46
  marker: {
51
- outlineColor: dark ? '#e2e8f0' : 'gray',
52
- textColor: 'white',
53
- focusColor: dark ? '#93c5fd' : '#1d4ed8',
54
- focusHaloColor: dark ? '#0f172a' : 'white',
47
+ outline: { color: dark ? '#e2e8f0' : 'gray' },
48
+ text: { color: 'white' },
49
+ focus: { color: dark ? '#93c5fd' : '#1d4ed8', halo: dark ? '#0f172a' : 'white' },
50
+ },
51
+ traces: {
52
+ colors: dark
53
+ ? [
54
+ '#fb923c',
55
+ '#4ade80',
56
+ '#f87171',
57
+ '#c084fc',
58
+ '#d6a681',
59
+ '#f472b6',
60
+ '#cbd5e1',
61
+ '#d4d45c',
62
+ '#22d3ee',
63
+ '#60a5fa',
64
+ ]
65
+ : [
66
+ '#ff7f0e',
67
+ '#2ca02c',
68
+ '#d62728',
69
+ '#9467bd',
70
+ '#8c564b',
71
+ '#e377c2',
72
+ '#7f7f7f',
73
+ '#bcbd22',
74
+ '#17becf',
75
+ '#1f77b4',
76
+ ],
55
77
  },
56
- traceColors: dark
57
- ? [
58
- '#fb923c',
59
- '#4ade80',
60
- '#f87171',
61
- '#c084fc',
62
- '#d6a681',
63
- '#f472b6',
64
- '#cbd5e1',
65
- '#d4d45c',
66
- '#22d3ee',
67
- '#60a5fa',
68
- ]
69
- : [
70
- '#ff7f0e',
71
- '#2ca02c',
72
- '#d62728',
73
- '#9467bd',
74
- '#8c564b',
75
- '#e377c2',
76
- '#7f7f7f',
77
- '#bcbd22',
78
- '#17becf',
79
- '#1f77b4',
80
- ],
81
78
  };
82
79
  if (appearance.overrides !== undefined) {
83
80
  Theme.merge(theme, appearance.overrides);
@@ -1,52 +1,96 @@
1
1
  /** Resolved appearance shared by charts and independent scales. Lengths are positive. */
2
2
  export interface SmithTheme {
3
3
  background: string;
4
- fontFamily: string;
4
+ text: {
5
+ fontFamily: string;
6
+ };
5
7
  grid: {
6
- stroke: string;
7
- textColor: string;
8
- majorWidth: number;
9
- minorWidth: number;
10
- fontSize: number;
8
+ line: {
9
+ color: string;
10
+ majorWidth: number;
11
+ minorWidth: number;
12
+ };
13
+ text: {
14
+ color: string;
15
+ fontSize: number;
16
+ };
11
17
  };
12
18
  boundary: {
13
- stroke: string;
19
+ color: string;
14
20
  width: number;
15
21
  };
16
22
  circles: {
17
- q: string;
18
- vswr: string;
19
- width: number;
23
+ q: {
24
+ color: string;
25
+ width: number;
26
+ };
27
+ vswr: {
28
+ color: string;
29
+ width: number;
30
+ };
20
31
  };
21
32
  scales: {
22
- stroke: string;
23
- boundaryColor: string;
24
- textColor: string;
25
- valueColor: string;
26
- indicatorColor: string;
27
- indicatorOutline: string;
28
- fontSize: number;
29
- titleFontSize: number;
30
- valueFontSize: number;
31
- peripheralFontSize: number;
33
+ line: {
34
+ color: string;
35
+ };
36
+ boundary: {
37
+ color: string;
38
+ };
39
+ text: {
40
+ color: string;
41
+ fontSize: number;
42
+ };
43
+ title: {
44
+ fontSize: number;
45
+ };
46
+ value: {
47
+ color: string;
48
+ fontSize: number;
49
+ };
50
+ indicator: {
51
+ color: string;
52
+ outline: string;
53
+ };
54
+ peripheral: {
55
+ fontSize: number;
56
+ };
32
57
  };
33
58
  cursor: {
34
- pointColor: string;
35
- impedanceColor: string;
36
- admittanceColor: string;
37
- pointSize: number;
38
- lineWidth: number;
59
+ point: {
60
+ color: string;
61
+ size: number;
62
+ };
63
+ impedance: {
64
+ color: string;
65
+ };
66
+ admittance: {
67
+ color: string;
68
+ };
69
+ line: {
70
+ width: number;
71
+ };
39
72
  };
40
73
  marker: {
41
- outlineColor: string;
42
- textColor: string;
43
- focusColor: string;
44
- focusHaloColor: string;
74
+ outline: {
75
+ color: string;
76
+ };
77
+ text: {
78
+ color: string;
79
+ };
80
+ focus: {
81
+ color: string;
82
+ halo: string;
83
+ };
84
+ };
85
+ traces: {
86
+ colors: readonly string[];
45
87
  };
46
- traceColors: readonly string[];
47
88
  }
89
+ /** Each visual group can be overridden independently, down to individual properties. */
48
90
  export type SmithThemeOverrides = {
49
- [K in keyof SmithTheme]?: SmithTheme[K] extends readonly string[] ? readonly string[] : SmithTheme[K] extends object ? Partial<SmithTheme[K]> : SmithTheme[K];
91
+ [K in keyof SmithTheme]?: SmithTheme[K] extends string ? string : {
92
+ [P in keyof SmithTheme[K]]?: SmithTheme[K][P] extends readonly string[] ? readonly string[] : SmithTheme[K][P] extends object ? Partial<SmithTheme[K][P]> : SmithTheme[K][P];
93
+ };
50
94
  };
51
95
  /** Each application replaces the previous appearance, starting from the named preset. */
52
96
  export interface SmithAppearance {
@@ -1,6 +1,6 @@
1
1
  import type { ConstQCircles } from './ConstQCircles.js';
2
2
  import type { ConstSwrCircles } from './ConstSwrCircles.js';
3
- import type { CircleLayer, CircleStyle } from '../layers.js';
3
+ import type { CircleLayer, CircleStyleOptions } from '../layers.js';
4
4
  import { LayerControl } from './LayerControl.js';
5
5
  /** Exposes Q/VSWR circle controls without exposing the SVG renderer. */
6
6
  export declare class CircleLayerControl extends LayerControl implements CircleLayer {
@@ -8,7 +8,7 @@ export declare class CircleLayerControl extends LayerControl implements CircleLa
8
8
  private readonly minimum;
9
9
  constructor(layer: ConstQCircles | ConstSwrCircles, minimum: number, assertAlive: () => void);
10
10
  setVisible(visible: boolean): void;
11
- setStyle(style: Partial<CircleStyle>): void;
11
+ setStyle(style: CircleStyleOptions): void;
12
12
  setValues(values: readonly number[]): void;
13
13
  addValue(value: number): void;
14
14
  removeValue(value: number): void;
@@ -17,11 +17,11 @@ export class CircleLayerControl extends LayerControl {
17
17
  setStyle(style) {
18
18
  this.assertAlive();
19
19
  OptionsValidation.circleStyle(style);
20
- if (style.stroke !== undefined) {
21
- this.layer.Stroke = style.stroke;
20
+ if (style.line?.color !== undefined) {
21
+ this.layer.Stroke = style.line.color;
22
22
  }
23
- if (style.strokeWidth !== undefined) {
24
- this.layer.StrokeWidth = String(style.strokeWidth);
23
+ if (style.line?.width !== undefined) {
24
+ this.layer.StrokeWidth = String(style.line.width);
25
25
  }
26
26
  }
27
27
  setValues(values) {
@@ -94,12 +94,12 @@ export class ConstCircles {
94
94
  defaultDrawingOptions() {
95
95
  return {
96
96
  // Keep curved grid strokes visible at normal browser zoom and low pixel density.
97
- stroke: 'var(--smithkit-grid-stroke)',
98
- majorWidth: 'var(--smithkit-grid-majorWidth)',
99
- minorWidth: 'var(--smithkit-grid-minorWidth)',
100
- textColor: 'var(--smithkit-grid-textColor)',
101
- textFontFamily: 'var(--smithkit-fontFamily)',
102
- textFontSize: 'var(--smithkit-grid-fontSize)',
97
+ stroke: 'var(--smithkit-grid-line-color)',
98
+ majorWidth: 'var(--smithkit-grid-line-majorWidth)',
99
+ minorWidth: 'var(--smithkit-grid-line-minorWidth)',
100
+ textColor: 'var(--smithkit-grid-text-color)',
101
+ textFontFamily: 'var(--smithkit-text-fontFamily)',
102
+ textFontSize: 'var(--smithkit-grid-text-fontSize)',
103
103
  };
104
104
  }
105
105
  scaleArc(scaler, d) {
@@ -28,7 +28,10 @@ export class ConstQCircles {
28
28
  this.container.append(a2);
29
29
  }
30
30
  getDefaultDrawOptions() {
31
- return { stroke: 'var(--smithkit-circles-q)', strokeWidth: 'var(--smithkit-circles-width)' };
31
+ return {
32
+ stroke: 'var(--smithkit-circles-q-color)',
33
+ strokeWidth: 'var(--smithkit-circles-q-width)',
34
+ };
32
35
  }
33
36
  setDrawOptions(opts) {
34
37
  this.opts = opts;
@@ -27,7 +27,10 @@ export class ConstSwrCircles {
27
27
  this.container.append(circle);
28
28
  }
29
29
  getDefaultDrawOptions() {
30
- return { stroke: 'var(--smithkit-circles-vswr)', strokeWidth: 'var(--smithkit-circles-width)' };
30
+ return {
31
+ stroke: 'var(--smithkit-circles-vswr-color)',
32
+ strokeWidth: 'var(--smithkit-circles-vswr-width)',
33
+ };
31
34
  }
32
35
  setDrawOptions(opts) {
33
36
  this.opts = opts;
@@ -1,5 +1,5 @@
1
1
  import type { SmithGridLayer } from './SmithGridLayer.js';
2
- import type { GridDetail, GridLayer, GridStyle } from '../layers.js';
2
+ import type { GridDetail, GridLayer, GridStyleOptions } from '../layers.js';
3
3
  import { LayerControl } from './LayerControl.js';
4
4
  /** Exposes grid controls without exposing the SVG renderer. */
5
5
  export declare class GridLayerControl extends LayerControl implements GridLayer {
@@ -9,5 +9,5 @@ export declare class GridLayerControl extends LayerControl implements GridLayer
9
9
  setVisible(visible: boolean): void;
10
10
  setLabelsVisible(visible: boolean): void;
11
11
  setDetail(detail: GridDetail): void;
12
- setStyle(style: Partial<GridStyle>): void;
12
+ setStyle(style: GridStyleOptions): void;
13
13
  }
@@ -30,18 +30,17 @@ export class GridLayerControl extends LayerControl {
30
30
  setStyle(style) {
31
31
  this.assertAlive();
32
32
  OptionsValidation.gridStyle(style);
33
- const properties = {
34
- stroke: 'Stroke',
35
- majorWidth: 'MajorWidth',
36
- minorWidth: 'MinorWidth',
37
- textColor: 'TextColor',
38
- textFontFamily: 'TextFontFamily',
39
- textFontSize: 'TextFontSize',
40
- };
41
- for (const key of Object.keys(properties)) {
42
- const value = style[key];
33
+ const properties = [
34
+ ['Stroke', style.line?.color],
35
+ ['MajorWidth', style.line?.majorWidth],
36
+ ['MinorWidth', style.line?.minorWidth],
37
+ ['TextColor', style.text?.color],
38
+ ['TextFontFamily', style.text?.fontFamily],
39
+ ['TextFontSize', style.text?.fontSize],
40
+ ];
41
+ for (const [property, value] of properties) {
43
42
  if (value !== undefined) {
44
- this.layer[properties[key]] = String(value);
43
+ this.layer[property] = String(value);
45
44
  }
46
45
  }
47
46
  this.changed();
@@ -6,10 +6,10 @@ export { Touchstone } from './io/Touchstone.js';
6
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
- export type { TraceOptions, TraceUpdateOptions, MarkerSelectionStrategy, TraceStyle, TraceRenderMode, TraceInfo, MarkerInfo, MarkerSnapshot, MarkerComparison, } from './measurements.js';
9
+ export type { TraceOptions, TraceUpdateOptions, MarkerSelectionStrategy, TraceStyle, TraceStyleOptions, TraceRenderMode, TraceInfo, MarkerInfo, MarkerSnapshot, MarkerComparison, } from './measurements.js';
10
10
  export { RfCalculations } from './rf/RfCalculations.js';
11
11
  export type { SmithReading } from './rf/RfCalculations.js';
12
- export type { GridStyle, CircleStyle, GridLayer, GridDetail, CircleLayer, ChartLayers, PeripheralScales, } from './layers.js';
12
+ export type { GridStyle, GridStyleOptions, CircleStyle, CircleStyleOptions, GridLayer, GridDetail, CircleLayer, ChartLayers, PeripheralScales, } from './layers.js';
13
13
  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';
@@ -22,8 +22,8 @@ export class SmithCursor {
22
22
  this.group = new SmithGroup().attr('pointer-events', 'none');
23
23
  this.impedance = {
24
24
  group: new SmithGroup({
25
- stroke: 'var(--smithkit-cursor-impedanceColor)',
26
- strokeWidth: 'var(--smithkit-cursor-lineWidth)',
25
+ stroke: 'var(--smithkit-cursor-impedance-color)',
26
+ strokeWidth: 'var(--smithkit-cursor-line-width)',
27
27
  fill: 'none',
28
28
  }),
29
29
  resistance: { circle: new SmithCircle({ p: [0, 0], r: 1 }) },
@@ -37,8 +37,8 @@ export class SmithCursor {
37
37
  this.impedance.reactance.line.nonScalingStroke();
38
38
  this.admittance = {
39
39
  group: new SmithGroup({
40
- stroke: 'var(--smithkit-cursor-admittanceColor)',
41
- strokeWidth: 'var(--smithkit-cursor-lineWidth)',
40
+ stroke: 'var(--smithkit-cursor-admittance-color)',
41
+ strokeWidth: 'var(--smithkit-cursor-line-width)',
42
42
  fill: 'none',
43
43
  }),
44
44
  conductance: { circle: new SmithCircle({ p: [0, 0], r: 1 }) },
@@ -51,8 +51,8 @@ export class SmithCursor {
51
51
  this.admittance.susceptance.arc.nonScalingStroke();
52
52
  this.admittance.susceptance.line.nonScalingStroke();
53
53
  this.point = new SmithLine([0, 0], [0, 0], {
54
- stroke: 'var(--smithkit-cursor-pointColor)',
55
- strokeWidth: 'var(--smithkit-cursor-pointSize)',
54
+ stroke: 'var(--smithkit-cursor-point-color)',
55
+ strokeWidth: 'var(--smithkit-cursor-point-size)',
56
56
  fill: 'none',
57
57
  });
58
58
  this.point.nonScalingStroke();
@@ -1,19 +1,33 @@
1
1
  import type { Complex } from './math/Complex.js';
2
2
  export interface GridStyle {
3
- stroke: string;
4
- /** Non-scaling stroke width in CSS pixels. */
5
- majorWidth: number;
6
- /** Non-scaling stroke width in CSS pixels. */
7
- minorWidth: number;
8
- textColor: string;
9
- textFontFamily: string;
10
- /** Base font size in chart units; adaptive layout enforces a 9 CSS px minimum in the default view. */
11
- textFontSize: number;
3
+ line: {
4
+ color: string;
5
+ /** Non-scaling stroke width in CSS pixels. */
6
+ majorWidth: number;
7
+ /** Non-scaling stroke width in CSS pixels. */
8
+ minorWidth: number;
9
+ };
10
+ text: {
11
+ color: string;
12
+ fontFamily: string;
13
+ /** Base font size in chart units; adaptive layout enforces a 9 CSS px minimum in the default view. */
14
+ fontSize: number;
15
+ };
16
+ }
17
+ /** A grid style patch. Omitted nested properties retain their existing values. */
18
+ export interface GridStyleOptions {
19
+ line?: Partial<GridStyle['line']>;
20
+ text?: Partial<GridStyle['text']>;
12
21
  }
13
22
  export interface CircleStyle {
14
- stroke: string;
15
- /** Non-scaling stroke width in CSS pixels. */
16
- strokeWidth: number;
23
+ line: {
24
+ color: string;
25
+ /** Non-scaling stroke width in CSS pixels. */
26
+ width: number;
27
+ };
28
+ }
29
+ export interface CircleStyleOptions {
30
+ line?: Partial<CircleStyle['line']>;
17
31
  }
18
32
  export type GridDetail = 'basic' | 'standard' | 'detailed';
19
33
  /** Grid styling uses SVG user units, except non-scaling stroke widths in screen pixels. */
@@ -23,11 +37,11 @@ export interface GridLayer {
23
37
  setLabelsVisible(visible: boolean): void;
24
38
  /** Default: standard. Basic shows only the principal normalized values. */
25
39
  setDetail(detail: GridDetail): void;
26
- setStyle(style: Partial<GridStyle>): void;
40
+ setStyle(style: GridStyleOptions): void;
27
41
  }
28
42
  export interface CircleLayer {
29
43
  setVisible(visible: boolean): void;
30
- setStyle(style: Partial<CircleStyle>): void;
44
+ setStyle(style: CircleStyleOptions): void;
31
45
  /** Replace the complete set after validation; duplicates are ignored and empty clears it. */
32
46
  setValues(values: readonly number[]): void;
33
47
  addValue(value: number): void;
@@ -3,14 +3,24 @@ import type { SmithReading } from './rf/RfCalculations.js';
3
3
  export type TraceRenderMode = 'points' | 'line' | 'both';
4
4
  export interface TraceStyle {
5
5
  mode: TraceRenderMode;
6
- /** Non-scaling SVG stroke width in CSS pixels. Default: 2. */
7
- lineWidth: number;
8
- /** Point radius in CSS pixels; remains constant under chart zoom and container resize. Default: 2. */
9
- pointRadius: number;
10
- /** Maximum additional line simplification error in CSS pixels. Zero disables it (default). */
11
- lineTolerancePx: number;
12
- }
13
- export interface TraceOptions extends Partial<TraceStyle> {
6
+ line: {
7
+ /** Non-scaling SVG stroke width in CSS pixels. Default: 2. */
8
+ width: number;
9
+ /** Maximum additional line simplification error in CSS pixels. Zero disables it (default). */
10
+ tolerancePx: number;
11
+ };
12
+ point: {
13
+ /** Radius in CSS pixels; constant under chart zoom and container resize. Default: 2. */
14
+ radius: number;
15
+ };
16
+ }
17
+ /** A partial style patch; omitted nested properties retain their current values. */
18
+ export interface TraceStyleOptions {
19
+ mode?: TraceRenderMode;
20
+ line?: Partial<TraceStyle['line']>;
21
+ point?: Partial<TraceStyle['point']>;
22
+ }
23
+ export interface TraceOptions extends TraceStyleOptions {
14
24
  name?: string;
15
25
  /** A solid CSS color, such as '#2563eb' or 'orange'. */
16
26
  color?: string;
@@ -29,6 +39,8 @@ export interface MarkerInfo {
29
39
  readonly sampleIndex: number;
30
40
  }
31
41
  export interface TraceInfo extends Readonly<TraceStyle> {
42
+ readonly line: Readonly<TraceStyle['line']>;
43
+ readonly point: Readonly<TraceStyle['point']>;
32
44
  readonly id: string;
33
45
  readonly name: string;
34
46
  readonly color: string;
@@ -1,11 +1,11 @@
1
1
  import type { SmithAppearance } from './appearance/types.js';
2
- import type { CircleStyle, GridDetail, GridStyle } from './layers.js';
2
+ import type { CircleStyleOptions, GridDetail, GridStyleOptions } from './layers.js';
3
3
  /** Settings for an individual impedance/admittance grid layer. */
4
4
  export interface GridLayerOptions {
5
5
  visible?: boolean;
6
6
  detail?: GridDetail;
7
7
  labelsVisible?: boolean;
8
- style?: Partial<GridStyle>;
8
+ style?: GridStyleOptions;
9
9
  }
10
10
  /** Shared settings for all four grids, followed by per-layer overrides. */
11
11
  export interface GridOptions {
@@ -13,7 +13,7 @@ export interface GridOptions {
13
13
  detail?: GridDetail;
14
14
  /** Default: true. */
15
15
  labelsVisible?: boolean;
16
- style?: Partial<GridStyle>;
16
+ style?: GridStyleOptions;
17
17
  layers?: {
18
18
  resistance?: GridLayerOptions;
19
19
  reactance?: GridLayerOptions;
@@ -24,7 +24,7 @@ export interface GridOptions {
24
24
  export interface CircleLayerOptions {
25
25
  /** Default: false. */
26
26
  visible?: boolean;
27
- style?: Partial<CircleStyle>;
27
+ style?: CircleStyleOptions;
28
28
  /** Replace the default values. Empty hides all circles; duplicate values are ignored. */
29
29
  values?: readonly number[];
30
30
  }
@@ -0,0 +1,5 @@
1
+ /** Recognize HTML hosts across windows without accessing browser globals at import time. */
2
+ export declare class ChartHost {
3
+ private constructor();
4
+ static isElement(value: unknown): value is HTMLElement;
5
+ }
@@ -0,0 +1,14 @@
1
+ /** Recognize HTML hosts across windows without accessing browser globals at import time. */
2
+ export class ChartHost {
3
+ constructor() { }
4
+ static isElement(value) {
5
+ if (!value || typeof value !== 'object') {
6
+ return false;
7
+ }
8
+ if (typeof HTMLElement !== 'undefined' && value instanceof HTMLElement) {
9
+ return true;
10
+ }
11
+ const elementType = value.ownerDocument?.defaultView?.HTMLElement;
12
+ return elementType !== undefined && value instanceof elementType;
13
+ }
14
+ }
@@ -4,7 +4,7 @@ import type { SmithMarker } from '../traces/SmithMarker.js';
4
4
  import { TraceInput } from '../samples.js';
5
5
  import { Complex } from '../math/Complex.js';
6
6
  import type { ChartLayers, PeripheralScales } from '../layers.js';
7
- import type { TraceStyle } from '../measurements.js';
7
+ import type { TraceStyleOptions } from '../measurements.js';
8
8
  /** Internal SVG backend. Owns chart elements, view transforms, and SVG gestures. */
9
9
  export declare class SvgChartRenderer {
10
10
  private onCursorChange;
@@ -64,6 +64,6 @@ export declare class SvgChartRenderer {
64
64
  private cursorContainer;
65
65
  private drawReactanceAxis;
66
66
  /** Construct trace rendering without exposing chart containers or scalers to Smith. */
67
- createTrace(values: TraceInput, color: string, style: Partial<TraceStyle>, onMarkerDrag: (marker: SmithMarker, dragging: boolean) => void): SmithData;
67
+ createTrace(values: TraceInput, color: string, style: TraceStyleOptions, onMarkerDrag: (marker: SmithMarker, dragging: boolean) => void): SmithData;
68
68
  removeTrace(trace: SmithData): void;
69
69
  }
@@ -93,7 +93,7 @@ export class SvgChartRenderer {
93
93
  this.cursor = this.initCursor();
94
94
  const cursorContainer = this.cursorContainer();
95
95
  this.reactanceAxis = this.drawReactanceAxis({
96
- stroke: 'var(--smithkit-boundary-stroke)',
96
+ stroke: 'var(--smithkit-boundary-color)',
97
97
  strokeWidth: 'var(--smithkit-boundary-width)',
98
98
  fill: 'none',
99
99
  });
@@ -44,7 +44,7 @@ export class SmithPeripheralScales extends SmithGroup {
44
44
  .attr('cy', 250)
45
45
  .attr('r', (radius) => radius)
46
46
  .attr('fill', 'none')
47
- .attr('stroke', 'var(--smithkit-scales-boundaryColor)')
47
+ .attr('stroke', 'var(--smithkit-scales-boundary-color)')
48
48
  .attr('stroke-width', 1.1)
49
49
  .attr('vector-effect', 'non-scaling-stroke');
50
50
  this.axes.each(function (scale, index) {
@@ -62,7 +62,7 @@ export class SmithPeripheralScales extends SmithGroup {
62
62
  .attr('cy', 250)
63
63
  .attr('r', radius)
64
64
  .attr('fill', 'none')
65
- .attr('stroke', 'var(--smithkit-scales-stroke)')
65
+ .attr('stroke', 'var(--smithkit-scales-line-color)')
66
66
  .attr('stroke-width', 0.6)
67
67
  .attr('vector-effect', 'non-scaling-stroke');
68
68
  }
@@ -77,7 +77,7 @@ export class SmithPeripheralScales extends SmithGroup {
77
77
  .attr('y1', y1)
78
78
  .attr('x2', x2)
79
79
  .attr('y2', y2)
80
- .attr('stroke', 'var(--smithkit-scales-stroke)')
80
+ .attr('stroke', 'var(--smithkit-scales-line-color)')
81
81
  .attr('stroke-width', major ? 0.8 : 0.5)
82
82
  .attr('vector-effect', 'non-scaling-stroke');
83
83
  const captionDistance = Math.abs(((angle - captionAngle + 540) % 360) - 180);
@@ -93,9 +93,9 @@ export class SmithPeripheralScales extends SmithGroup {
93
93
  .attr('transform', `rotate(${rotation},${x},${y})`)
94
94
  .attr('text-anchor', 'middle')
95
95
  .attr('dominant-baseline', 'central')
96
- .attr('font-family', 'var(--smithkit-fontFamily)')
97
- .attr('font-size', 'var(--smithkit-scales-peripheralFontSize)')
98
- .attr('fill', 'var(--smithkit-scales-textColor)')
96
+ .attr('font-family', 'var(--smithkit-text-fontFamily)')
97
+ .attr('font-size', 'var(--smithkit-scales-peripheral-fontSize)')
98
+ .attr('fill', 'var(--smithkit-scales-text-color)')
99
99
  .text(scale.unit === 'λ' ? value.toFixed(2) : String(value));
100
100
  }
101
101
  }
@@ -118,11 +118,11 @@ export class SmithPeripheralScales extends SmithGroup {
118
118
  .append('text')
119
119
  .attr('class', 'peripheral-caption')
120
120
  .attr('text-anchor', 'middle')
121
- .attr('font-family', 'var(--smithkit-fontFamily)')
122
- .attr('font-size', 'var(--smithkit-scales-peripheralFontSize)')
121
+ .attr('font-family', 'var(--smithkit-text-fontFamily)')
122
+ .attr('font-size', 'var(--smithkit-scales-peripheral-fontSize)')
123
123
  .attr('font-weight', 500)
124
124
  .attr('letter-spacing', 0.3)
125
- .attr('fill', 'var(--smithkit-scales-textColor)')
125
+ .attr('fill', 'var(--smithkit-scales-text-color)')
126
126
  .append('textPath')
127
127
  .attr('href', `#${pathId}`)
128
128
  .attr('startOffset', '50%')
@@ -132,8 +132,8 @@ export class SmithPeripheralScales extends SmithGroup {
132
132
  .attr('class', 'peripheral-indicator')
133
133
  .attr('r', 3)
134
134
  .attr('visibility', 'hidden')
135
- .attr('fill', 'var(--smithkit-scales-indicatorColor)')
136
- .attr('stroke', 'var(--smithkit-scales-indicatorOutline)')
135
+ .attr('fill', 'var(--smithkit-scales-indicator-color)')
136
+ .attr('stroke', 'var(--smithkit-scales-indicator-outline)')
137
137
  .attr('stroke-width', 1);
138
138
  });
139
139
  this.updateLabelVisibility();
@@ -9,7 +9,10 @@ export declare class SmithScales {
9
9
  private readonly start;
10
10
  private readonly length;
11
11
  private readonly axes;
12
+ /** Configure independent scales for later mounting with draw(). */
12
13
  constructor(appearance?: SmithAppearance);
14
+ /** Configure and mount immediately. String targets are CSS selectors, as in draw(). */
15
+ constructor(target: string | HTMLElement, appearance?: SmithAppearance);
13
16
  /** Replace the preset and overrides without changing readings or layout. */
14
17
  setAppearance(appearance: SmithAppearance): void;
15
18
  draw(target: string | HTMLElement): void;
@@ -1,4 +1,5 @@
1
1
  import { ImageExporter } from '../svg/ImageExporter.js';
2
+ import { ChartHost } from '../rendering/ChartHost.js';
2
3
  import { Theme } from '../appearance/Theme.js';
3
4
  import { SvgTheme } from '../appearance/SvgTheme.js';
4
5
  import { select, format } from 'd3';
@@ -27,16 +28,27 @@ export class SmithScales {
27
28
  .style('display', 'block')
28
29
  .style('width', '100%')
29
30
  .style('height', 'auto');
30
- constructor(appearance = {}) {
31
+ constructor(targetOrAppearance = {}, configuration) {
32
+ const target = typeof targetOrAppearance === 'string' || ChartHost.isElement(targetOrAppearance)
33
+ ? targetOrAppearance
34
+ : undefined;
35
+ if (target === undefined && configuration !== undefined) {
36
+ throw new TypeError('A container is required before the second appearance argument.');
37
+ }
38
+ const appearance = target === undefined
39
+ ? targetOrAppearance
40
+ : configuration === undefined
41
+ ? {}
42
+ : configuration;
31
43
  this.setAppearance(appearance);
32
44
  const labels = this.axes.append('g').attr('data-role', 'labels').attr('pointer-events', 'none');
33
45
  labels
34
46
  .append('text')
35
47
  .attr('x', this.start)
36
48
  .attr('y', 12)
37
- .attr('font-family', 'var(--smithkit-fontFamily)')
38
- .attr('font-size', 'var(--smithkit-scales-titleFontSize)')
39
- .attr('fill', 'var(--smithkit-scales-textColor)')
49
+ .attr('font-family', 'var(--smithkit-text-fontFamily)')
50
+ .attr('font-size', 'var(--smithkit-scales-title-fontSize)')
51
+ .attr('fill', 'var(--smithkit-scales-text-color)')
40
52
  .text((scale) => scale.title);
41
53
  labels
42
54
  .append('text')
@@ -44,10 +56,10 @@ export class SmithScales {
44
56
  .attr('data-role', 'scale-value')
45
57
  .attr('x', this.start)
46
58
  .attr('y', 25)
47
- .attr('font-family', 'var(--smithkit-fontFamily)')
48
- .attr('font-size', 'var(--smithkit-scales-valueFontSize)')
59
+ .attr('font-family', 'var(--smithkit-text-fontFamily)')
60
+ .attr('font-size', 'var(--smithkit-scales-value-fontSize)')
49
61
  .attr('font-weight', 600)
50
- .attr('fill', 'var(--smithkit-scales-valueColor)')
62
+ .attr('fill', 'var(--smithkit-scales-value-color)')
51
63
  .text('—');
52
64
  this.axes
53
65
  .append('line')
@@ -56,7 +68,7 @@ export class SmithScales {
56
68
  .attr('x2', this.start + this.length)
57
69
  .attr('y1', 36)
58
70
  .attr('y2', 36)
59
- .attr('stroke', 'var(--smithkit-scales-stroke)')
71
+ .attr('stroke', 'var(--smithkit-scales-line-color)')
60
72
  .attr('stroke-width', 1);
61
73
  const start = this.start;
62
74
  const length = this.length;
@@ -70,7 +82,7 @@ export class SmithScales {
70
82
  ticks
71
83
  .append('line')
72
84
  .attr('y2', 5)
73
- .attr('stroke', 'var(--smithkit-scales-stroke)')
85
+ .attr('stroke', 'var(--smithkit-scales-line-color)')
74
86
  .attr('stroke-width', 1);
75
87
  select(this)
76
88
  .select('[data-role=labels]')
@@ -80,9 +92,9 @@ export class SmithScales {
80
92
  .attr('transform', (value) => `translate(${start + length * scale.position(value)},36)`)
81
93
  .append('text')
82
94
  .attr('y', 17)
83
- .attr('font-size', 'var(--smithkit-scales-fontSize)')
84
- .attr('font-family', 'var(--smithkit-fontFamily)')
85
- .attr('fill', 'var(--smithkit-scales-textColor)')
95
+ .attr('font-size', 'var(--smithkit-scales-text-fontSize)')
96
+ .attr('font-family', 'var(--smithkit-text-fontFamily)')
97
+ .attr('fill', 'var(--smithkit-scales-text-color)')
86
98
  .attr('text-anchor', (_, i) => i === 0 ? 'start' : i === scale.values.length - 1 ? 'end' : 'middle')
87
99
  .text((value) => (value === Infinity ? '∞' : String(value)));
88
100
  });
@@ -93,11 +105,20 @@ export class SmithScales {
93
105
  .attr('cx', this.start)
94
106
  .attr('cy', 36)
95
107
  .attr('r', 4)
96
- .attr('fill', 'var(--smithkit-scales-indicatorColor)')
97
- .attr('stroke', 'var(--smithkit-scales-indicatorOutline)')
108
+ .attr('fill', 'var(--smithkit-scales-indicator-color)')
109
+ .attr('stroke', 'var(--smithkit-scales-indicator-outline)')
98
110
  .attr('stroke-width', 1);
99
111
  labels.raise();
100
112
  this.axes.append('title').text((scale) => scale.description ?? scale.title);
113
+ if (target !== undefined) {
114
+ try {
115
+ this.draw(target);
116
+ }
117
+ catch (error) {
118
+ this.destroy();
119
+ throw error;
120
+ }
121
+ }
101
122
  }
102
123
  /** Replace the preset and overrides without changing readings or layout. */
103
124
  setAppearance(appearance) {
@@ -157,8 +178,8 @@ export class SmithScales {
157
178
  : undefined, readout?.label
158
179
  ? {
159
180
  text: readout.label,
160
- color: style.getPropertyValue('--smithkit-scales-textColor').trim(),
161
- fontFamily: style.getPropertyValue('--smithkit-fontFamily').trim(),
181
+ color: style.getPropertyValue('--smithkit-scales-text-color').trim(),
182
+ fontFamily: style.getPropertyValue('--smithkit-text-fontFamily').trim(),
162
183
  }
163
184
  : undefined);
164
185
  return ImageExporter.svg([source], options);
@@ -1,5 +1,5 @@
1
1
  import { SmithGroup } from '../svg/SmithGroup.js';
2
- import type { TraceStyle, MarkerSelectionStrategy } from '../measurements.js';
2
+ import type { TraceStyle, TraceStyleOptions, MarkerSelectionStrategy } from '../measurements.js';
3
3
  import { SmithMarker } from './SmithMarker.js';
4
4
  import { SmithScaler } from '../svg/SmithScaler.js';
5
5
  import type { TraceInput, TraceSample } from '../samples.js';
@@ -22,7 +22,7 @@ export declare class SmithData {
22
22
  private selectHandler;
23
23
  private visible;
24
24
  private handler;
25
- constructor(data: TraceInput, color: string, transform: TraceTransform, container: SmithGroup, scaler: SmithScaler, markerDragHandler?: ((marker: SmithMarker, dragging: boolean) => void) | undefined, markerContainer?: SmithGroup, style?: Partial<TraceStyle>);
25
+ constructor(data: TraceInput, color: string, transform: TraceTransform, container: SmithGroup, scaler: SmithScaler, markerDragHandler?: ((marker: SmithMarker, dragging: boolean) => void) | undefined, markerContainer?: SmithGroup, style?: TraceStyleOptions);
26
26
  setViewportScale(scale: number): void;
27
27
  zoom(transform: TraceTransform): void;
28
28
  addMarker(sampleIndex?: number): number;
@@ -40,7 +40,7 @@ export declare class SmithData {
40
40
  path: string;
41
41
  } | undefined;
42
42
  get Style(): TraceStyle;
43
- setStyle(options: Partial<TraceStyle>): void;
43
+ setStyle(options: TraceStyleOptions): void;
44
44
  setVisible(visible: boolean): void;
45
45
  private moveMarker;
46
46
  private updateReading;
@@ -54,8 +54,8 @@ export class SmithMarker extends SmithShape {
54
54
  .attr('pointer-events', 'none')
55
55
  .attr('aria-hidden', 'true');
56
56
  for (const [stroke, width] of [
57
- ['var(--smithkit-marker-focusHaloColor)', 5],
58
- ['var(--smithkit-marker-focusColor)', 2.5],
57
+ ['var(--smithkit-marker-focus-halo)', 5],
58
+ ['var(--smithkit-marker-focus-color)', 2.5],
59
59
  ]) {
60
60
  this.focusRing
61
61
  .append('circle')
@@ -67,17 +67,17 @@ export class SmithMarker extends SmithShape {
67
67
  this.triangle = g
68
68
  .append('polygon')
69
69
  .attr('stroke', 'none')
70
- .attr('fill', 'var(--smithkit-marker-outlineColor)');
70
+ .attr('fill', 'var(--smithkit-marker-outline-color)');
71
71
  this.inner = g.append('polygon').attr('stroke', 'none').attr('fill', color);
72
72
  this.text = g
73
73
  .append('text')
74
74
  .attr('pointer-events', 'none')
75
75
  .attr('aria-hidden', 'true')
76
- .attr('font-family', 'var(--smithkit-fontFamily)')
76
+ .attr('font-family', 'var(--smithkit-text-fontFamily)')
77
77
  .attr('font-weight', 'normal')
78
78
  .attr('text-anchor', 'middle')
79
79
  .attr('dominant-baseline', 'middle')
80
- .attr('fill', 'var(--smithkit-marker-textColor)')
80
+ .attr('fill', 'var(--smithkit-marker-text-color)')
81
81
  .text(marker.toString());
82
82
  this.zoom(1);
83
83
  const drag = d3
@@ -1,6 +1,6 @@
1
1
  import { SmithGroup } from '../svg/SmithGroup.js';
2
2
  import { SmithScaler } from '../svg/SmithScaler.js';
3
- import type { TraceStyle } from '../measurements.js';
3
+ import type { TraceStyle, TraceStyleOptions } from '../measurements.js';
4
4
  import { TraceBuffer } from './TraceBuffer.js';
5
5
  export interface TraceTransform {
6
6
  x: number;
@@ -19,7 +19,7 @@ export declare class TraceRenderer {
19
19
  private group;
20
20
  private lineDetail;
21
21
  private lineIndices;
22
- constructor(data: TraceBuffer, color: string, transform: TraceTransform, container: SmithGroup, scaler: SmithScaler, style?: Partial<TraceStyle>);
22
+ constructor(data: TraceBuffer, color: string, transform: TraceTransform, container: SmithGroup, scaler: SmithScaler, style?: TraceStyleOptions);
23
23
  private drawTrace;
24
24
  private selectLineIndices;
25
25
  private linePath;
@@ -35,7 +35,7 @@ export declare class TraceRenderer {
35
35
  private zoomDataPoints;
36
36
  setColor(color: string): void;
37
37
  get Style(): TraceStyle;
38
- setStyle(options: Partial<TraceStyle>): void;
38
+ setStyle(options: TraceStyleOptions): void;
39
39
  private redraw;
40
40
  setVisible(visible: boolean): void;
41
41
  update(data: TraceBuffer): void;
@@ -21,9 +21,8 @@ export class TraceRenderer {
21
21
  this.scaler = scaler;
22
22
  this.style = {
23
23
  mode: style.mode ?? 'points',
24
- lineWidth: style.lineWidth ?? 2,
25
- pointRadius: style.pointRadius ?? 2,
26
- lineTolerancePx: style.lineTolerancePx ?? 0,
24
+ line: { width: style.line?.width ?? 2, tolerancePx: style.line?.tolerancePx ?? 0 },
25
+ point: { radius: style.point?.radius ?? 2 },
27
26
  };
28
27
  this.group = this.drawTrace(data).attr('pointer-events', 'none');
29
28
  container.append(this.group);
@@ -43,7 +42,7 @@ export class TraceRenderer {
43
42
  .attr('d', this.linePath(this.lineIndices))
44
43
  .attr('fill', 'none')
45
44
  .attr('stroke', this.color)
46
- .attr('stroke-width', this.style.lineWidth)
45
+ .attr('stroke-width', this.style.line.width)
47
46
  .attr('vector-effect', 'non-scaling-stroke')
48
47
  .attr('stroke-linejoin', 'round')
49
48
  .attr('stroke-linecap', 'round');
@@ -54,14 +53,14 @@ export class TraceRenderer {
54
53
  return group;
55
54
  }
56
55
  selectLineIndices() {
57
- if (this.style.lineTolerancePx === 0) {
56
+ if (this.style.line.tolerancePx === 0) {
58
57
  return;
59
58
  }
60
59
  this.lineDetail ??= new TraceLineDetail(this.data);
61
60
  const unitsToPixels = Math.max(Math.abs(this.scaler.x(1) - this.scaler.x(0)), Math.abs(this.scaler.y(1) - this.scaler.y(0))) *
62
61
  this.transform.k *
63
62
  this.viewportScale;
64
- return this.lineDetail.select(this.style.lineTolerancePx / unitsToPixels);
63
+ return this.lineDetail.select(this.style.line.tolerancePx / unitsToPixels);
65
64
  }
66
65
  linePath(indices) {
67
66
  // Simplified paths retain numeric precision even at high zoom. Full paths keep
@@ -105,9 +104,9 @@ export class TraceRenderer {
105
104
  renderPoints(group, data) {
106
105
  const visible = [];
107
106
  const cells = new Set();
108
- const cellSize = Math.max(0.5, this.style.pointRadius) / this.viewportScale;
107
+ const cellSize = Math.max(0.5, this.style.point.radius) / this.viewportScale;
109
108
  const extent = this.scaler.x(1);
110
- const margin = this.style.pointRadius / this.viewportScale;
109
+ const margin = this.style.point.radius / this.viewportScale;
111
110
  const firstCell = Math.floor(-margin / cellSize);
112
111
  const columns = Math.floor((extent + margin) / cellSize) - firstCell + 1;
113
112
  for (let i = 0; i < data.length; i++) {
@@ -130,7 +129,7 @@ export class TraceRenderer {
130
129
  .join('circle')
131
130
  .attr('cx', (index) => this.scaler.x(data.real(index)))
132
131
  .attr('cy', (index) => this.scaler.y(data.imaginary(index)))
133
- .attr('r', this.style.pointRadius / (this.transform.k * this.viewportScale));
132
+ .attr('r', this.style.point.radius / (this.transform.k * this.viewportScale));
134
133
  }
135
134
  setViewportScale(scale) {
136
135
  if (scale === this.viewportScale || !Number.isFinite(scale) || scale <= 0) {
@@ -153,7 +152,7 @@ export class TraceRenderer {
153
152
  this.renderPoints(this.group, this.data);
154
153
  }
155
154
  else {
156
- this.group.Element.selectAll('circle').attr('r', this.style.pointRadius / (k * this.viewportScale));
155
+ this.group.Element.selectAll('circle').attr('r', this.style.point.radius / (k * this.viewportScale));
157
156
  }
158
157
  }
159
158
  setColor(color) {
@@ -162,19 +161,21 @@ export class TraceRenderer {
162
161
  this.group.Element.select('.trace-line').attr('stroke', color);
163
162
  }
164
163
  get Style() {
165
- return { ...this.style };
164
+ return { ...this.style, line: { ...this.style.line }, point: { ...this.style.point } };
166
165
  }
167
166
  setStyle(options) {
168
167
  const next = {
169
168
  mode: options.mode ?? this.style.mode,
170
- lineWidth: options.lineWidth ?? this.style.lineWidth,
171
- pointRadius: options.pointRadius ?? this.style.pointRadius,
172
- lineTolerancePx: options.lineTolerancePx ?? this.style.lineTolerancePx,
169
+ line: {
170
+ width: options.line?.width ?? this.style.line.width,
171
+ tolerancePx: options.line?.tolerancePx ?? this.style.line.tolerancePx,
172
+ },
173
+ point: { radius: options.point?.radius ?? this.style.point.radius },
173
174
  };
174
175
  if (next.mode === this.style.mode &&
175
- next.lineWidth === this.style.lineWidth &&
176
- next.pointRadius === this.style.pointRadius &&
177
- next.lineTolerancePx === this.style.lineTolerancePx) {
176
+ next.line.width === this.style.line.width &&
177
+ next.point.radius === this.style.point.radius &&
178
+ next.line.tolerancePx === this.style.line.tolerancePx) {
178
179
  return;
179
180
  }
180
181
  const modeChanged = next.mode !== this.style.mode;
@@ -183,7 +184,7 @@ export class TraceRenderer {
183
184
  this.redraw();
184
185
  }
185
186
  else {
186
- this.group.Element.select('.trace-line').attr('stroke-width', next.lineWidth);
187
+ this.group.Element.select('.trace-line').attr('stroke-width', next.line.width);
187
188
  this.zoomDataPoints();
188
189
  }
189
190
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smithkit",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Framework-independent, interactive Smith chart library for JavaScript and TypeScript",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -57,6 +57,7 @@
57
57
  "react": "19.3.0",
58
58
  "react-dom": "19.3.0",
59
59
  "rxjs": "7.8.2",
60
+ "shiki": "4.5.0",
60
61
  "typedoc": "0.28.20",
61
62
  "typescript": "npm:@typescript/typescript6@^6.0.2",
62
63
  "typescript-eslint": "^8.71.0",