smithkit 0.5.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 (37) hide show
  1. package/README.md +47 -938
  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 +42 -14
  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/grid/SmithGridLayer.js +3 -1
  18. package/dist/lib/index.d.ts +2 -2
  19. package/dist/lib/interaction/SmithCursor.js +6 -6
  20. package/dist/lib/layers.d.ts +28 -14
  21. package/dist/lib/math/Complex.d.ts +1 -1
  22. package/dist/lib/math/Complex.js +2 -2
  23. package/dist/lib/measurements.d.ts +20 -8
  24. package/dist/lib/options.d.ts +4 -4
  25. package/dist/lib/rendering/ChartHost.d.ts +5 -0
  26. package/dist/lib/rendering/ChartHost.js +14 -0
  27. package/dist/lib/rendering/SvgChartRenderer.d.ts +2 -2
  28. package/dist/lib/rendering/SvgChartRenderer.js +2 -2
  29. package/dist/lib/scales/SmithPeripheralScales.js +11 -11
  30. package/dist/lib/scales/SmithScales.d.ts +3 -0
  31. package/dist/lib/scales/SmithScales.js +39 -18
  32. package/dist/lib/svg/LabelLayout.js +9 -2
  33. package/dist/lib/traces/SmithData.d.ts +3 -3
  34. package/dist/lib/traces/SmithMarker.js +6 -5
  35. package/dist/lib/traces/TraceRenderer.d.ts +3 -3
  36. package/dist/lib/traces/TraceRenderer.js +19 -18
  37. package/package.json +24 -6
@@ -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;
@@ -117,7 +117,7 @@ export declare class Complex {
117
117
  private static hyperbolicProduct;
118
118
  private static hyperbolicReciprocal;
119
119
  /**
120
- * Elliptic coordinates for principal inverse functions (x,y >= 0).
120
+ * Elliptic coordinates for principal inverse functions (non-negative x and y).
121
121
  * With a = (|z+1| + |z-1|)/2, return acosh(a) and sqrt(a²-x²).
122
122
  * Rationalized hypot differences retain tiny components near the branch cuts.
123
123
  * Principal branches: https://dlmf.nist.gov/4.37
@@ -34,7 +34,7 @@ export class Complex {
34
34
  }
35
35
  if (args.length === 2) {
36
36
  const arg0 = args[0];
37
- const arg1 = args[1];
37
+ const arg1 = args[1] === undefined ? 0 : args[1];
38
38
  if (typeof arg0 === 'number' && typeof arg1 === 'number') {
39
39
  return new Complex(arg0, arg1);
40
40
  }
@@ -598,7 +598,7 @@ export class Complex {
598
598
  return Complex.reciprocal(denominator).mul(Math.exp(Math.LN2 - x));
599
599
  }
600
600
  /**
601
- * Elliptic coordinates for principal inverse functions (x,y >= 0).
601
+ * Elliptic coordinates for principal inverse functions (non-negative x and y).
602
602
  * With a = (|z+1| + |z-1|)/2, return acosh(a) and sqrt(a²-x²).
603
603
  * Rationalized hypot differences retain tiny components near the branch cuts.
604
604
  * Principal branches: https://dlmf.nist.gov/4.37
@@ -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
  });
@@ -356,7 +356,7 @@ export class SvgChartRenderer {
356
356
  this.traces.forEach((d) => d.zoom(transform));
357
357
  }
358
358
  cursorContainer() {
359
- const shape = this.drawReactanceAxis({ fill: 'transparent', stroke: 'none' });
359
+ const shape = this.drawReactanceAxis({ fill: 'transparent', stroke: 'none', strokeWidth: '0' });
360
360
  shape.Element.style('pointer-events', 'all')
361
361
  .on('pointermove.smithkit', (event) => {
362
362
  this.cursorMove(d3.pointer(event));
@@ -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) {
@@ -147,7 +168,7 @@ export class SmithScales {
147
168
  .style('visibility', 'visible');
148
169
  }
149
170
  /** Export mounted scales, optionally with a separate reading and its description. */
150
- toSvg(options) {
171
+ toSvg(options = {}) {
151
172
  this.assertAlive();
152
173
  const container = this.container.node();
153
174
  const style = getComputedStyle(container);
@@ -157,11 +178,11 @@ 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
- return options === undefined ? source : ImageExporter.svg([source], options);
185
+ return ImageExporter.svg([source], options);
165
186
  }
166
187
  /** Export mounted scales as PNG without changing their live readings. */
167
188
  async toPng(options = {}) {
@@ -81,7 +81,6 @@ export class LabelLayout {
81
81
  label,
82
82
  order,
83
83
  scale,
84
- matrix: matrix,
85
84
  baseSize: parseFloat(style(label).fontSize),
86
85
  priority: Number(label.dataset.labelPriority ?? 0),
87
86
  },
@@ -93,10 +92,18 @@ export class LabelLayout {
93
92
  this.resizedFonts.set(label, label.style.fontSize);
94
93
  label.style.fontSize = `${9 / scale}px`;
95
94
  }
95
+ // WebKit can retain the old em offset after the font size changes.
96
+ // Reapply it before measuring so live and exported SVG use the same position.
97
+ const dx = label.getAttribute('dx');
98
+ if (dx?.endsWith('em')) {
99
+ label.setAttribute('dx', dx);
100
+ }
96
101
  }
97
102
  const measured = candidates.map((entry) => ({
98
103
  ...entry,
99
- box: LabelLayout.transformBox(entry.label.getBBox(), entry.matrix),
104
+ // Screen bounds include the browser's glyph/paint envelope. Normalizing
105
+ // them keeps collision decisions independent of the current zoom.
106
+ box: LabelLayout.transformBox(entry.label.getBoundingClientRect(), normalize),
100
107
  }));
101
108
  measured.sort((a, b) => b.priority - a.priority || a.order - b.order);
102
109
  const occupied = [];
@@ -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;
@@ -45,6 +45,7 @@ export class SmithMarker extends SmithShape {
45
45
  .attr('fill', 'none')
46
46
  .attr('pointer-events', 'all')
47
47
  .attr('stroke', 'none')
48
+ .attr('stroke-width', 0)
48
49
  .attr('aria-hidden', 'true');
49
50
  this.focusRing = g
50
51
  .append('g')
@@ -53,8 +54,8 @@ export class SmithMarker extends SmithShape {
53
54
  .attr('pointer-events', 'none')
54
55
  .attr('aria-hidden', 'true');
55
56
  for (const [stroke, width] of [
56
- ['var(--smithkit-marker-focusHaloColor)', 5],
57
- ['var(--smithkit-marker-focusColor)', 2.5],
57
+ ['var(--smithkit-marker-focus-halo)', 5],
58
+ ['var(--smithkit-marker-focus-color)', 2.5],
58
59
  ]) {
59
60
  this.focusRing
60
61
  .append('circle')
@@ -66,17 +67,17 @@ export class SmithMarker extends SmithShape {
66
67
  this.triangle = g
67
68
  .append('polygon')
68
69
  .attr('stroke', 'none')
69
- .attr('fill', 'var(--smithkit-marker-outlineColor)');
70
+ .attr('fill', 'var(--smithkit-marker-outline-color)');
70
71
  this.inner = g.append('polygon').attr('stroke', 'none').attr('fill', color);
71
72
  this.text = g
72
73
  .append('text')
73
74
  .attr('pointer-events', 'none')
74
75
  .attr('aria-hidden', 'true')
75
- .attr('font-family', 'var(--smithkit-fontFamily)')
76
+ .attr('font-family', 'var(--smithkit-text-fontFamily)')
76
77
  .attr('font-weight', 'normal')
77
78
  .attr('text-anchor', 'middle')
78
79
  .attr('dominant-baseline', 'middle')
79
- .attr('fill', 'var(--smithkit-marker-textColor)')
80
+ .attr('fill', 'var(--smithkit-marker-text-color)')
80
81
  .text(marker.toString());
81
82
  this.zoom(1);
82
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;