smithkit 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +291 -42
  2. package/dist/lib/MarkerMeasurements.d.ts +6 -0
  3. package/dist/lib/MarkerMeasurements.js +22 -0
  4. package/dist/lib/Smith.d.ts +18 -35
  5. package/dist/lib/Smith.js +107 -275
  6. package/dist/lib/SmithFormatter.d.ts +10 -0
  7. package/dist/lib/SmithFormatter.js +19 -0
  8. package/dist/lib/appearance/SvgTheme.d.ts +6 -0
  9. package/dist/lib/appearance/SvgTheme.js +21 -0
  10. package/dist/lib/appearance/Theme.d.ts +8 -0
  11. package/dist/lib/appearance/Theme.js +120 -0
  12. package/dist/lib/appearance/types.d.ts +55 -0
  13. package/dist/lib/grid/CircleLayerControl.d.ts +15 -0
  14. package/dist/lib/grid/CircleLayerControl.js +40 -0
  15. package/dist/lib/{draw → grid}/ConstCircles.d.ts +4 -9
  16. package/dist/lib/{draw → grid}/ConstCircles.js +8 -22
  17. package/dist/lib/{draw → grid}/ConstQCircles.d.ts +2 -2
  18. package/dist/lib/{draw → grid}/ConstQCircles.js +4 -4
  19. package/dist/lib/{draw → grid}/ConstSwrCircles.d.ts +2 -2
  20. package/dist/lib/{draw → grid}/ConstSwrCircles.js +4 -4
  21. package/dist/lib/grid/GridDefinitions.d.ts +18 -0
  22. package/dist/lib/grid/GridDefinitions.js +167 -0
  23. package/dist/lib/grid/GridGeometry.d.ts +26 -0
  24. package/dist/lib/grid/GridGeometry.js +58 -0
  25. package/dist/lib/grid/GridLabels.d.ts +8 -0
  26. package/dist/lib/grid/GridLabels.js +45 -0
  27. package/dist/lib/grid/GridLayerControl.d.ts +13 -0
  28. package/dist/lib/grid/GridLayerControl.js +48 -0
  29. package/dist/lib/grid/LayerControl.d.ts +6 -0
  30. package/dist/lib/grid/LayerControl.js +16 -0
  31. package/dist/lib/grid/SmithGridLayer.d.ts +26 -0
  32. package/dist/lib/grid/SmithGridLayer.js +95 -0
  33. package/dist/lib/grid/types.d.ts +1 -1
  34. package/dist/lib/index.d.ts +11 -11
  35. package/dist/lib/index.js +5 -7
  36. package/dist/lib/{draw → interaction}/SmithCursor.d.ts +3 -20
  37. package/dist/lib/{draw → interaction}/SmithCursor.js +12 -20
  38. package/dist/lib/io/Touchstone.d.ts +10 -0
  39. package/dist/lib/io/Touchstone.js +75 -0
  40. package/dist/lib/layers.d.ts +7 -9
  41. package/dist/lib/layers.js +1 -73
  42. package/dist/lib/{complex → math}/Complex.d.ts +1 -0
  43. package/dist/lib/{complex → math}/Complex.js +1 -1
  44. package/dist/lib/math/geometry.d.ts +14 -0
  45. package/dist/lib/measurements.d.ts +2 -4
  46. package/dist/lib/measurements.js +1 -19
  47. package/dist/lib/rendering/SvgChartRenderer.d.ts +66 -0
  48. package/dist/lib/rendering/SvgChartRenderer.js +342 -0
  49. package/dist/lib/rf/RfCalculations.d.ts +65 -0
  50. package/dist/lib/rf/RfCalculations.js +161 -0
  51. package/dist/lib/{SmithConstantCircle.d.ts → rf/SmithConstantCircle.d.ts} +2 -3
  52. package/dist/lib/{SmithConstantCircle.js → rf/SmithConstantCircle.js} +3 -4
  53. package/dist/lib/scales/PeripheralScaleDefinitions.d.ts +16 -0
  54. package/dist/lib/scales/PeripheralScaleDefinitions.js +54 -0
  55. package/dist/lib/scales/RadialScaleDefinitions.d.ts +15 -0
  56. package/dist/lib/scales/RadialScaleDefinitions.js +112 -0
  57. package/dist/lib/scales/SmithPeripheralScales.d.ts +4 -2
  58. package/dist/lib/scales/SmithPeripheralScales.js +55 -35
  59. package/dist/lib/scales/SmithScales.d.ts +11 -2
  60. package/dist/lib/scales/SmithScales.js +78 -38
  61. package/dist/lib/svg/ImageExporter.d.ts +18 -0
  62. package/dist/lib/svg/ImageExporter.js +211 -0
  63. package/dist/lib/svg/LabelLayout.d.ts +17 -0
  64. package/dist/lib/svg/LabelLayout.js +145 -0
  65. package/dist/lib/svg/MarkerLegend.d.ts +12 -0
  66. package/dist/lib/svg/MarkerLegend.js +63 -0
  67. package/dist/lib/{draw → svg}/SmithArc.d.ts +1 -1
  68. package/dist/lib/{draw → svg}/SmithCircle.d.ts +1 -1
  69. package/dist/lib/{draw → svg}/SmithLine.d.ts +1 -1
  70. package/dist/lib/{draw → svg}/SmithScaler.d.ts +1 -4
  71. package/dist/lib/{draw → svg}/SmithText.d.ts +1 -1
  72. package/dist/lib/svg/SvgExporter.d.ts +19 -0
  73. package/dist/lib/svg/SvgExporter.js +182 -0
  74. package/dist/lib/svg/export.d.ts +36 -0
  75. package/dist/lib/{draw → traces}/SmithData.d.ts +24 -35
  76. package/dist/lib/traces/SmithData.js +195 -0
  77. package/dist/lib/{draw → traces}/SmithMarker.d.ts +14 -3
  78. package/dist/lib/traces/SmithMarker.js +240 -0
  79. package/dist/lib/traces/TraceModel.d.ts +31 -0
  80. package/dist/lib/traces/TraceModel.js +123 -0
  81. package/dist/lib/traces/TraceRenderer.d.ts +33 -0
  82. package/dist/lib/traces/TraceRenderer.js +152 -0
  83. package/package.json +4 -1
  84. package/dist/lib/SmithArcsDefs.d.ts +0 -10
  85. package/dist/lib/SmithArcsDefs.js +0 -10
  86. package/dist/lib/components.d.ts +0 -14
  87. package/dist/lib/components.js +0 -26
  88. package/dist/lib/draw/SmithData.js +0 -366
  89. package/dist/lib/draw/SmithGridLayer.d.ts +0 -19
  90. package/dist/lib/draw/SmithGridLayer.js +0 -48
  91. package/dist/lib/draw/SmithMarker.js +0 -122
  92. package/dist/lib/formatting.d.ts +0 -7
  93. package/dist/lib/formatting.js +0 -16
  94. package/dist/lib/grid/definitions.d.ts +0 -3
  95. package/dist/lib/grid/definitions.js +0 -123
  96. package/dist/lib/grid/geometry.d.ts +0 -20
  97. package/dist/lib/grid/geometry.js +0 -55
  98. package/dist/lib/grid/labels.d.ts +0 -3
  99. package/dist/lib/grid/labels.js +0 -41
  100. package/dist/lib/io/touchstone.d.ts +0 -7
  101. package/dist/lib/io/touchstone.js +0 -72
  102. package/dist/lib/math/complexRatio.d.ts +0 -3
  103. package/dist/lib/math/complexRatio.js +0 -30
  104. package/dist/lib/renormalization.d.ts +0 -10
  105. package/dist/lib/renormalization.js +0 -62
  106. package/dist/lib/rf.d.ts +0 -35
  107. package/dist/lib/rf.js +0 -74
  108. package/dist/lib/scales/peripheralScales.d.ts +0 -12
  109. package/dist/lib/scales/peripheralScales.js +0 -49
  110. package/dist/lib/scales/radialScales.d.ts +0 -12
  111. package/dist/lib/scales/radialScales.js +0 -109
  112. package/dist/lib/shapes/Arc.d.ts +0 -6
  113. package/dist/lib/shapes/Circle.d.ts +0 -5
  114. package/dist/lib/shapes/Line.d.ts +0 -5
  115. package/dist/lib/shapes/Point.d.ts +0 -1
  116. package/dist/lib/shapes/Point.js +0 -1
  117. /package/dist/lib/{draw/SmithDrawOptions.js → appearance/types.js} +0 -0
  118. /package/dist/lib/{draw → grid}/ConstConductance.d.ts +0 -0
  119. /package/dist/lib/{draw → grid}/ConstConductance.js +0 -0
  120. /package/dist/lib/{draw → grid}/ConstReactance.d.ts +0 -0
  121. /package/dist/lib/{draw → grid}/ConstReactance.js +0 -0
  122. /package/dist/lib/{draw → grid}/ConstResistance.d.ts +0 -0
  123. /package/dist/lib/{draw → grid}/ConstResistance.js +0 -0
  124. /package/dist/lib/{draw → grid}/ConstSusceptance.d.ts +0 -0
  125. /package/dist/lib/{draw → grid}/ConstSusceptance.js +0 -0
  126. /package/dist/lib/{arcs → grid}/Tick.d.ts +0 -0
  127. /package/dist/lib/{arcs → grid}/Tick.js +0 -0
  128. /package/dist/lib/{draw → interaction}/MouseGesture.d.ts +0 -0
  129. /package/dist/lib/{draw → interaction}/MouseGesture.js +0 -0
  130. /package/dist/lib/{shapes/Arc.js → math/geometry.js} +0 -0
  131. /package/dist/lib/{draw → svg}/SmithArc.js +0 -0
  132. /package/dist/lib/{draw → svg}/SmithCircle.js +0 -0
  133. /package/dist/lib/{draw → svg}/SmithDrawOptions.d.ts +0 -0
  134. /package/dist/lib/{shapes/Circle.js → svg/SmithDrawOptions.js} +0 -0
  135. /package/dist/lib/{draw → svg}/SmithGroup.d.ts +0 -0
  136. /package/dist/lib/{draw → svg}/SmithGroup.js +0 -0
  137. /package/dist/lib/{draw → svg}/SmithLine.js +0 -0
  138. /package/dist/lib/{draw → svg}/SmithPath.d.ts +0 -0
  139. /package/dist/lib/{draw → svg}/SmithPath.js +0 -0
  140. /package/dist/lib/{draw → svg}/SmithScaler.js +0 -0
  141. /package/dist/lib/{draw → svg}/SmithShape.d.ts +0 -0
  142. /package/dist/lib/{draw → svg}/SmithShape.js +0 -0
  143. /package/dist/lib/{draw → svg}/SmithSvg.d.ts +0 -0
  144. /package/dist/lib/{draw → svg}/SmithSvg.js +0 -0
  145. /package/dist/lib/{draw → svg}/SmithText.js +0 -0
  146. /package/dist/lib/{shapes/Line.js → svg/export.js} +0 -0
@@ -1,5 +1,4 @@
1
- import { complexRatio } from './math/complexRatio.js';
2
- import { Complex } from './complex/Complex.js';
1
+ import { Complex } from '../math/Complex.js';
3
2
  export class SmithConstantCircle {
4
3
  Z0;
5
4
  constructor(Z0 = 50) {
@@ -18,8 +17,8 @@ export class SmithConstantCircle {
18
17
  return this.transform(c, 1 - c.re, -c.im, 1 + c.re, c.im, -2);
19
18
  }
20
19
  transform(value, nr, ni, dr, di, determinant) {
21
- const quotient = complexRatio(nr, ni, dr, di);
22
- if (!quotient) {
20
+ const quotient = Complex.div(Complex.from(nr, ni), Complex.from(dr, di));
21
+ if (!Number.isFinite(quotient.re) || !Number.isFinite(quotient.im)) {
23
22
  return;
24
23
  }
25
24
  // The analytic imaginary numerator avoids cancellation of nearly equal products.
@@ -0,0 +1,16 @@
1
+ import type { Complex } from '../math/Complex.js';
2
+ export interface PeripheralScale {
3
+ id: string;
4
+ title: string;
5
+ unit: '°' | 'λ';
6
+ ticks: number[];
7
+ major: (value: number) => boolean;
8
+ angle: (value: number) => number;
9
+ read: (gamma: Complex) => number | null;
10
+ }
11
+ export declare class PeripheralScaleDefinitions {
12
+ private constructor();
13
+ private static phase;
14
+ /** A full reflection-coefficient rotation represents half a wavelength along the line. */
15
+ static create(): PeripheralScale[];
16
+ }
@@ -0,0 +1,54 @@
1
+ export class PeripheralScaleDefinitions {
2
+ constructor() { }
3
+ static phase(gamma) {
4
+ return gamma.re === 0 && gamma.im === 0
5
+ ? null
6
+ : (Math.atan2(gamma.im, gamma.re) * 180) / Math.PI;
7
+ }
8
+ /** A full reflection-coefficient rotation represents half a wavelength along the line. */
9
+ static create() {
10
+ const wavelengths = (gamma, direction) => {
11
+ const degrees = PeripheralScaleDefinitions.phase(gamma);
12
+ return degrees === null ? null : (0.25 + (direction * degrees) / 720 + 0.5) % 0.5;
13
+ };
14
+ return [
15
+ {
16
+ id: 'transmission-phase',
17
+ title: 'Transmission phase · °',
18
+ unit: '°',
19
+ ticks: Array.from({ length: 179 }, (_, i) => -89 + i),
20
+ major: (v) => v % 5 === 0,
21
+ // Project from Γ = −1 to the perimeter, not from the chart center.
22
+ angle: (v) => 2 * v,
23
+ read: (gamma) => PeripheralScaleDefinitions.phase({ re: 1 + gamma.re, im: gamma.im }),
24
+ },
25
+ {
26
+ id: 'reflection-phase',
27
+ title: 'Reflection phase · °',
28
+ unit: '°',
29
+ ticks: Array.from({ length: 180 }, (_, i) => -178 + i * 2),
30
+ major: (v) => v % 10 === 0,
31
+ angle: (v) => v,
32
+ read: PeripheralScaleDefinitions.phase,
33
+ },
34
+ {
35
+ id: 'wavelengths-load',
36
+ title: 'Wavelengths → load',
37
+ unit: 'λ',
38
+ ticks: Array.from({ length: 250 }, (_, i) => i / 500),
39
+ major: (v) => Math.round(v * 500) % 5 === 0,
40
+ angle: (v) => 720 * v - 180,
41
+ read: (gamma) => wavelengths(gamma, 1),
42
+ },
43
+ {
44
+ id: 'wavelengths-generator',
45
+ title: 'Wavelengths → generator',
46
+ unit: 'λ',
47
+ ticks: Array.from({ length: 250 }, (_, i) => i / 500),
48
+ major: (v) => Math.round(v * 500) % 5 === 0,
49
+ angle: (v) => 180 - 720 * v,
50
+ read: (gamma) => wavelengths(gamma, -1),
51
+ },
52
+ ];
53
+ }
54
+ }
@@ -0,0 +1,15 @@
1
+ import type { Complex } from '../math/Complex.js';
2
+ export interface RadialScale {
3
+ id: string;
4
+ title: string;
5
+ description?: string;
6
+ unit: 'dB' | 'ratio';
7
+ read: (gamma: Complex) => number;
8
+ values: number[];
9
+ position: (value: number) => number;
10
+ }
11
+ export declare class RadialScaleDefinitions {
12
+ private constructor();
13
+ /** Normalized positions along each scale, from zero to one. */
14
+ static create(): RadialScale[];
15
+ }
@@ -0,0 +1,112 @@
1
+ import { SmithConstantCircle } from '../rf/SmithConstantCircle.js';
2
+ export class RadialScaleDefinitions {
3
+ constructor() { }
4
+ /** Normalized positions along each scale, from zero to one. */
5
+ static create() {
6
+ const calcs = new SmithConstantCircle();
7
+ return [
8
+ {
9
+ id: 'vswr',
10
+ unit: 'ratio',
11
+ read: (gamma) => calcs.rflCoeffToSwr(gamma),
12
+ title: 'VSWR',
13
+ values: [1, 1.5, 2, 3, 5, 10, Infinity],
14
+ position: (v) => (v === Infinity ? 1 : calcs.swrToRflCoeffEOrI(v)),
15
+ },
16
+ {
17
+ id: 'vswr-db',
18
+ unit: 'dB',
19
+ read: (gamma) => calcs.rflCoeffToDBS(gamma),
20
+ title: 'Standing-wave ratio · dB',
21
+ values: [0, 3, 6, 10, 15, 20, Infinity],
22
+ position: (v) => (v === Infinity ? 1 : calcs.dBSToAbsRflCoeff(v)),
23
+ },
24
+ {
25
+ id: 'return-loss',
26
+ unit: 'dB',
27
+ read: (gamma) => calcs.rflCoeffToReturnLoss(gamma),
28
+ title: 'Return loss · dB',
29
+ values: [Infinity, 20, 14, 10, 6, 3, 0],
30
+ position: (v) => calcs.returnLossToRflCoeffEOrI(v),
31
+ },
32
+ {
33
+ id: 'mismatch-loss',
34
+ unit: 'dB',
35
+ read: (gamma) => calcs.rflCoeffToMismatchLoss(gamma),
36
+ title: 'Reflection loss · dB',
37
+ description: 'Reflection loss (mismatch loss): −10 log10(1 − |Γ|²), in dB.',
38
+ values: [0, 0.1, 0.5, 1, 2, 4, Infinity],
39
+ position: (v) => calcs.mismatchLossToRflCoeffEOrI(v),
40
+ },
41
+ {
42
+ id: 'reflected-power',
43
+ unit: 'ratio',
44
+ read: (gamma) => calcs.rflCoeffP(gamma),
45
+ title: 'Power reflection coefficient',
46
+ description: 'Reflected power divided by incident power: |Γ|² (dimensionless).',
47
+ values: [0, 0.01, 0.1, 0.25, 0.5, 0.75, 1],
48
+ position: (v) => calcs.rflCoeffPToEOrI(v),
49
+ },
50
+ {
51
+ id: 'transmitted-power',
52
+ unit: 'ratio',
53
+ read: (gamma) => calcs.rflCoeffToTransmCoeffP(gamma),
54
+ title: 'Power transmission coefficient',
55
+ description: 'Net power delivered to the load divided by incident power: 1 − |Γ|² (dimensionless).',
56
+ values: [1, 0.99, 0.9, 0.75, 0.5, 0.25, 0],
57
+ position: (v) => calcs.transmCoeffPToRflCoeffEOrI(v),
58
+ },
59
+ {
60
+ id: 'standing-wave-loss',
61
+ unit: 'ratio',
62
+ read: (gamma) => calcs.rflCoeffToSwLossCoeff(gamma),
63
+ title: 'Standing-wave loss coefficient',
64
+ values: [1, 1.1, 1.5, 2, 3, 5, Infinity],
65
+ position: (v) => (v === Infinity ? 1 : calcs.swLossCoeffToRflCoeffEOrI(v)),
66
+ },
67
+ {
68
+ id: 'standing-wave-peak',
69
+ unit: 'ratio',
70
+ read: (gamma) => calcs.rflCoeffToSwPeakConstP(gamma),
71
+ title: 'Standing-wave peak · constant power',
72
+ values: [1, 1.2, 1.5, 2, 3, Infinity],
73
+ position: (v) => (v === Infinity ? 1 : calcs.swPeakConstPToRflCoeffEOrI(v)),
74
+ },
75
+ {
76
+ id: 'reflection-coefficient',
77
+ unit: 'ratio',
78
+ read: (gamma) => calcs.rflCoeffEOrI(gamma),
79
+ title: 'Reflection coefficient · |Γ|',
80
+ values: [0, 0.2, 0.4, 0.6, 0.8, 1],
81
+ position: (v) => v,
82
+ },
83
+ {
84
+ id: 'attenuation',
85
+ unit: 'dB',
86
+ read: (gamma) => calcs.rflCoeffToAttenuation(gamma),
87
+ title: 'Attenuation · dB',
88
+ description: 'One-way attenuation of a matched line or attenuator terminated in an open or short circuit. Not insertion loss of an arbitrary load.',
89
+ values: [Infinity, 10, 7, 5, 3, 1, 0],
90
+ position: (v) => calcs.attenuationToRflCoeff(v),
91
+ },
92
+ {
93
+ id: 'voltage-transmission',
94
+ unit: 'ratio',
95
+ read: (gamma) => calcs.rflCoeffToTransmCoeffEOrI(gamma),
96
+ title: 'Voltage transmission coefficient',
97
+ description: 'Voltage transmission magnitude: |1 + Γ| (dimensionless).',
98
+ values: [0, 0.4, 0.8, 1.2, 1.6, 2],
99
+ position: (v) => v / 2,
100
+ },
101
+ {
102
+ id: 'current-transmission',
103
+ unit: 'ratio',
104
+ read: (gamma) => calcs.rflCoeffToCurrentTransmission(gamma),
105
+ title: 'Current transmission coefficient',
106
+ description: 'Current transmission magnitude: |1 − Γ|, using the voltage reflection coefficient Γ (dimensionless).',
107
+ values: [0, 0.4, 0.8, 1.2, 1.6, 2],
108
+ position: (v) => v / 2,
109
+ },
110
+ ];
111
+ }
112
+ }
@@ -1,7 +1,9 @@
1
- import type { Complex } from '../complex/Complex.js';
2
- import { SmithGroup } from '../draw/SmithGroup.js';
1
+ import type { Complex } from '../math/Complex.js';
2
+ import { SmithGroup } from '../svg/SmithGroup.js';
3
3
  /** Peripheral rulers in the chart's 500-unit coordinate system. */
4
4
  export declare class SmithPeripheralScales extends SmithGroup {
5
+ private static point;
6
+ private static rulerRadius;
5
7
  readonly labels: SmithGroup;
6
8
  private readonly scales;
7
9
  private readonly axes;
@@ -1,19 +1,22 @@
1
1
  import { select, format } from 'd3';
2
- import { SmithGroup } from '../draw/SmithGroup.js';
3
- import { peripheralScales } from './peripheralScales.js';
2
+ import { SmithGroup } from '../svg/SmithGroup.js';
3
+ import { PeripheralScaleDefinitions } from './PeripheralScaleDefinitions.js';
4
4
  let nextScaleId = 0;
5
- const firstRadius = 250;
6
- const ringSpacing = 20;
7
- function point(radius, degrees) {
8
- const angle = (degrees * Math.PI) / 180;
9
- return [250 + radius * Math.cos(angle), 250 - radius * Math.sin(angle)];
10
- }
5
+ const chartRadius = 250;
6
+ const groupWidth = 40;
11
7
  /** Peripheral rulers in the chart's 500-unit coordinate system. */
12
8
  export class SmithPeripheralScales extends SmithGroup {
9
+ static point(radius, degrees) {
10
+ const angle = (degrees * Math.PI) / 180;
11
+ return [250 + radius * Math.cos(angle), 250 - radius * Math.sin(angle)];
12
+ }
13
+ static rulerRadius(index) {
14
+ return chartRadius + groupWidth / 2 + Math.floor(index / 2) * groupWidth;
15
+ }
13
16
  labels = new SmithGroup()
14
17
  .attr('data-label-layer', 'peripheral-scales')
15
18
  .attr('pointer-events', 'none');
16
- scales = peripheralScales();
19
+ scales = PeripheralScaleDefinitions.create();
17
20
  axes = this.Element.attr('data-layer', 'peripheral-scales')
18
21
  .attr('pointer-events', 'none')
19
22
  .selectAll('g')
@@ -29,38 +32,55 @@ export class SmithPeripheralScales extends SmithGroup {
29
32
  .join('g')
30
33
  .attr('data-label-scale', (scale) => scale.id)
31
34
  .nodes();
35
+ // Two paired rulers: transmission/reflection phase, then electrical length
36
+ // toward load/generator. Strong boundaries enclose each pair.
37
+ this.Element.selectAll('circle.peripheral-boundary')
38
+ .data([chartRadius, chartRadius + groupWidth, chartRadius + 2 * groupWidth])
39
+ .join('circle')
40
+ .attr('class', 'peripheral-boundary')
41
+ .attr('cx', 250)
42
+ .attr('cy', 250)
43
+ .attr('r', (radius) => radius)
44
+ .attr('fill', 'none')
45
+ .attr('stroke', 'var(--smithkit-scales-boundaryColor)')
46
+ .attr('stroke-width', 1.1)
47
+ .attr('vector-effect', 'non-scaling-stroke');
32
48
  this.axes.each(function (scale, index) {
33
49
  const labelAxis = select(labelAxes[index]);
34
50
  const axis = select(this);
35
- const radius = firstRadius + index * ringSpacing;
51
+ const radius = SmithPeripheralScales.rulerRadius(index);
52
+ const side = index % 2 === 0 ? -1 : 1;
36
53
  const captionAngle = scale.unit === 'λ' ? 180 : 0;
37
54
  axis.append('title').text(scale.title);
38
- axis
39
- .append('circle')
40
- .attr('cx', 250)
41
- .attr('cy', 250)
42
- .attr('r', radius)
43
- .attr('fill', 'none')
44
- .attr('stroke', '#475569')
45
- .attr('stroke-width', 0.8)
46
- .attr('vector-effect', 'non-scaling-stroke');
55
+ if (index % 2 === 0) {
56
+ axis
57
+ .append('circle')
58
+ .attr('class', 'peripheral-ruler')
59
+ .attr('cx', 250)
60
+ .attr('cy', 250)
61
+ .attr('r', radius)
62
+ .attr('fill', 'none')
63
+ .attr('stroke', 'var(--smithkit-scales-stroke)')
64
+ .attr('stroke-width', 0.6)
65
+ .attr('vector-effect', 'non-scaling-stroke');
66
+ }
47
67
  for (const value of scale.ticks) {
48
68
  const angle = scale.angle(value);
49
- const [x1, y1] = point(radius, angle);
69
+ const [x1, y1] = SmithPeripheralScales.point(radius, angle);
50
70
  const major = scale.major(value);
51
- const [x2, y2] = point(radius - (major ? 4 : 2), angle);
71
+ const [x2, y2] = SmithPeripheralScales.point(radius + side * (major ? 4 : 2), angle);
52
72
  axis
53
73
  .append('line')
54
74
  .attr('x1', x1)
55
75
  .attr('y1', y1)
56
76
  .attr('x2', x2)
57
77
  .attr('y2', y2)
58
- .attr('stroke', '#475569')
78
+ .attr('stroke', 'var(--smithkit-scales-stroke)')
59
79
  .attr('stroke-width', major ? 0.8 : 0.5)
60
80
  .attr('vector-effect', 'non-scaling-stroke');
61
81
  const captionDistance = Math.abs(((angle - captionAngle + 540) % 360) - 180);
62
82
  if (major && captionDistance > 25) {
63
- const [x, y] = point(radius + 8, angle);
83
+ const [x, y] = SmithPeripheralScales.point(radius + side * 11, angle);
64
84
  const rotation = angle >= 0 ? 90 - angle : -90 - angle;
65
85
  labelAxis
66
86
  .append('text')
@@ -69,16 +89,16 @@ export class SmithPeripheralScales extends SmithGroup {
69
89
  .attr('transform', `rotate(${rotation},${x},${y})`)
70
90
  .attr('text-anchor', 'middle')
71
91
  .attr('dominant-baseline', 'central')
72
- .attr('font-family', 'system-ui, sans-serif')
73
- .attr('font-size', 10.5)
74
- .attr('fill', '#334155')
92
+ .attr('font-family', 'var(--smithkit-fontFamily)')
93
+ .attr('font-size', 'var(--smithkit-scales-peripheralFontSize)')
94
+ .attr('fill', 'var(--smithkit-scales-textColor)')
75
95
  .text(scale.unit === 'λ' ? value.toFixed(2) : String(value));
76
96
  }
77
97
  }
78
98
  // Reserve a label sector for the curved caption without masking the ruler or its ticks.
79
- const captionRadius = radius + 5;
80
- const start = point(captionRadius, captionAngle + 45);
81
- const end = point(captionRadius, captionAngle - 45);
99
+ const captionRadius = radius + (side < 0 ? -15 : 5);
100
+ const start = SmithPeripheralScales.point(captionRadius, captionAngle + 45);
101
+ const end = SmithPeripheralScales.point(captionRadius, captionAngle - 45);
82
102
  const pathId = `smithkit-peripheral-caption-${nextScaleId++}`;
83
103
  axis
84
104
  .append('defs')
@@ -94,11 +114,11 @@ export class SmithPeripheralScales extends SmithGroup {
94
114
  .append('text')
95
115
  .attr('class', 'peripheral-caption')
96
116
  .attr('text-anchor', 'middle')
97
- .attr('font-family', 'system-ui, sans-serif')
98
- .attr('font-size', 10.5)
117
+ .attr('font-family', 'var(--smithkit-fontFamily)')
118
+ .attr('font-size', 'var(--smithkit-scales-peripheralFontSize)')
99
119
  .attr('font-weight', 500)
100
120
  .attr('letter-spacing', 0.3)
101
- .attr('fill', '#334155')
121
+ .attr('fill', 'var(--smithkit-scales-textColor)')
102
122
  .append('textPath')
103
123
  .attr('href', `#${pathId}`)
104
124
  .attr('startOffset', '50%')
@@ -108,8 +128,8 @@ export class SmithPeripheralScales extends SmithGroup {
108
128
  .attr('class', 'peripheral-indicator')
109
129
  .attr('r', 3)
110
130
  .attr('visibility', 'hidden')
111
- .attr('fill', '#dc2626')
112
- .attr('stroke', 'white')
131
+ .attr('fill', 'var(--smithkit-scales-indicatorColor)')
132
+ .attr('stroke', 'var(--smithkit-scales-indicatorOutline)')
113
133
  .attr('stroke-width', 1);
114
134
  });
115
135
  }
@@ -134,7 +154,7 @@ export class SmithPeripheralScales extends SmithGroup {
134
154
  dot.attr('visibility', 'hidden').attr('data-value', null);
135
155
  return;
136
156
  }
137
- const [x, y] = point(firstRadius + index * ringSpacing, scale.angle(value));
157
+ const [x, y] = SmithPeripheralScales.point(SmithPeripheralScales.rulerRadius(index), scale.angle(value));
138
158
  dot.attr('cx', x).attr('cy', y).attr('data-value', value).attr('visibility', null);
139
159
  });
140
160
  }
@@ -1,4 +1,6 @@
1
- import type { Complex } from '../complex/Complex.js';
1
+ import type { ScaleImageExportOptions } from '../svg/export.js';
2
+ import type { SmithAppearance } from '../appearance/types.js';
3
+ import type { Complex } from '../math/Complex.js';
2
4
  /** Independent parameter scales. Connect update() to a chart's cursor events. */
3
5
  export declare class SmithScales {
4
6
  private readonly scales;
@@ -7,10 +9,17 @@ export declare class SmithScales {
7
9
  private readonly start;
8
10
  private readonly length;
9
11
  private readonly axes;
10
- constructor();
12
+ constructor(appearance?: SmithAppearance);
13
+ /** Replace the preset and overrides without changing readings or layout. */
14
+ setAppearance(appearance: SmithAppearance): void;
11
15
  draw(target: string | HTMLElement): void;
12
16
  /** Show a passive-load reflection coefficient, or hide indicators with null. */
13
17
  update(gamma: Complex | null): void;
18
+ private updateAxis;
19
+ /** Export mounted scales, optionally with a separate reading and its description. */
20
+ toSvg(options?: ScaleImageExportOptions): string;
21
+ /** Export mounted scales as PNG without changing their live readings. */
22
+ toPng(options?: ScaleImageExportOptions): Promise<Blob>;
14
23
  destroy(): void;
15
24
  private assertAlive;
16
25
  }
@@ -1,8 +1,12 @@
1
+ import { ImageExporter } from '../svg/ImageExporter.js';
2
+ import { Theme } from '../appearance/Theme.js';
3
+ import { SvgTheme } from '../appearance/SvgTheme.js';
1
4
  import { select, format } from 'd3';
2
- import { radialScales } from './radialScales.js';
5
+ import { RadialScaleDefinitions } from './RadialScaleDefinitions.js';
6
+ import { SvgExporter } from '../svg/SvgExporter.js';
3
7
  /** Independent parameter scales. Connect update() to a chart's cursor events. */
4
8
  export class SmithScales {
5
- scales = radialScales();
9
+ scales = RadialScaleDefinitions.create();
6
10
  container = select(document.createElement('div'))
7
11
  .attr('class', 'radial-scales')
8
12
  .style('display', 'grid')
@@ -23,25 +27,27 @@ export class SmithScales {
23
27
  .style('display', 'block')
24
28
  .style('width', '100%')
25
29
  .style('height', 'auto');
26
- constructor() {
30
+ constructor(appearance = {}) {
31
+ this.setAppearance(appearance);
27
32
  const labels = this.axes.append('g').attr('data-role', 'labels').attr('pointer-events', 'none');
28
33
  labels
29
34
  .append('text')
30
35
  .attr('x', this.start)
31
36
  .attr('y', 12)
32
- .attr('font-family', 'system-ui, sans-serif')
33
- .attr('font-size', 12)
34
- .attr('fill', '#334155')
37
+ .attr('font-family', 'var(--smithkit-fontFamily)')
38
+ .attr('font-size', 'var(--smithkit-scales-titleFontSize)')
39
+ .attr('fill', 'var(--smithkit-scales-textColor)')
35
40
  .text((scale) => scale.title);
36
41
  labels
37
42
  .append('text')
38
43
  .attr('class', 'scale-value')
44
+ .attr('data-role', 'scale-value')
39
45
  .attr('x', this.start)
40
46
  .attr('y', 25)
41
- .attr('font-family', 'system-ui, sans-serif')
42
- .attr('font-size', 11)
47
+ .attr('font-family', 'var(--smithkit-fontFamily)')
48
+ .attr('font-size', 'var(--smithkit-scales-valueFontSize)')
43
49
  .attr('font-weight', 600)
44
- .attr('fill', '#b42318')
50
+ .attr('fill', 'var(--smithkit-scales-valueColor)')
45
51
  .text('—');
46
52
  this.axes
47
53
  .append('line')
@@ -50,7 +56,7 @@ export class SmithScales {
50
56
  .attr('x2', this.start + this.length)
51
57
  .attr('y1', 36)
52
58
  .attr('y2', 36)
53
- .attr('stroke', '#64748b')
59
+ .attr('stroke', 'var(--smithkit-scales-stroke)')
54
60
  .attr('stroke-width', 1);
55
61
  const start = this.start;
56
62
  const length = this.length;
@@ -61,7 +67,11 @@ export class SmithScales {
61
67
  .join('g')
62
68
  .attr('class', 'scale-tick')
63
69
  .attr('transform', (value) => `translate(${start + length * scale.position(value)},36)`);
64
- ticks.append('line').attr('y2', 5).attr('stroke', '#64748b').attr('stroke-width', 1);
70
+ ticks
71
+ .append('line')
72
+ .attr('y2', 5)
73
+ .attr('stroke', 'var(--smithkit-scales-stroke)')
74
+ .attr('stroke-width', 1);
65
75
  select(this)
66
76
  .select('[data-role=labels]')
67
77
  .selectAll('g')
@@ -70,9 +80,9 @@ export class SmithScales {
70
80
  .attr('transform', (value) => `translate(${start + length * scale.position(value)},36)`)
71
81
  .append('text')
72
82
  .attr('y', 17)
73
- .attr('font-size', 10)
74
- .attr('font-family', 'system-ui, sans-serif')
75
- .attr('fill', '#334155')
83
+ .attr('font-size', 'var(--smithkit-scales-fontSize)')
84
+ .attr('font-family', 'var(--smithkit-fontFamily)')
85
+ .attr('fill', 'var(--smithkit-scales-textColor)')
76
86
  .attr('text-anchor', (_, i) => i === 0 ? 'start' : i === scale.values.length - 1 ? 'end' : 'middle')
77
87
  .text((value) => (value === Infinity ? '∞' : String(value)));
78
88
  });
@@ -83,12 +93,17 @@ export class SmithScales {
83
93
  .attr('cx', this.start)
84
94
  .attr('cy', 36)
85
95
  .attr('r', 4)
86
- .attr('fill', '#dc2626')
87
- .attr('stroke', 'white')
96
+ .attr('fill', 'var(--smithkit-scales-indicatorColor)')
97
+ .attr('stroke', 'var(--smithkit-scales-indicatorOutline)')
88
98
  .attr('stroke-width', 1);
89
99
  labels.raise();
90
100
  this.axes.append('title').text((scale) => scale.description ?? scale.title);
91
101
  }
102
+ /** Replace the preset and overrides without changing readings or layout. */
103
+ setAppearance(appearance) {
104
+ this.assertAlive();
105
+ SvgTheme.apply(this.container.node(), Theme.resolve(appearance));
106
+ }
92
107
  draw(target) {
93
108
  this.assertAlive();
94
109
  const host = typeof target === 'string' ? document.querySelector(target) : target;
@@ -100,32 +115,57 @@ export class SmithScales {
100
115
  /** Show a passive-load reflection coefficient, or hide indicators with null. */
101
116
  update(gamma) {
102
117
  this.assertAlive();
118
+ this.axes.each((scale, index, nodes) => this.updateAxis(nodes[index], scale, gamma));
119
+ }
120
+ updateAxis(node, scale, gamma) {
121
+ const axis = select(node);
103
122
  const magnitude = gamma === null ? NaN : Math.hypot(gamma.re, gamma.im);
104
123
  if (!gamma || !Number.isFinite(magnitude) || magnitude > 1) {
105
- this.axes.select('circle').attr('visibility', 'hidden').attr('data-position', null);
106
- this.axes.select('.scale-value').text('—');
107
- this.axes.attr('aria-label', (scale) => `${scale.title}. ${scale.description ?? ''}`.trim());
108
- this.axes.select('title').text((scale) => scale.description ?? scale.title);
109
- return;
110
- }
111
- const start = this.start;
112
- const length = this.length;
113
- this.axes.each(function (scale) {
114
- const value = scale.read(gamma);
115
- const position = Math.max(0, Math.min(1, scale.position(value)));
116
- const axis = select(this);
117
- const number = value === Infinity ? '∞' : format('.4~g')(value);
118
- const suffix = scale.unit === 'dB' ? ' dB' : scale.id === 'vswr' ? ' : 1' : '';
119
- const readout = number + suffix;
120
- const label = `${scale.title}: ${readout}${scale.description ? `. ${scale.description}` : ''}`;
121
- axis.select('.scale-value').text(readout);
122
- axis.attr('aria-label', label).select('title').text(label);
123
124
  axis
124
125
  .select('circle')
125
- .attr('cx', start + position * length)
126
- .attr('data-position', position)
127
- .attr('visibility', null);
128
- });
126
+ .attr('visibility', 'hidden')
127
+ .style('visibility', 'hidden')
128
+ .attr('data-position', null);
129
+ axis.select('[data-role="scale-value"]').text('—');
130
+ axis.attr('aria-label', `${scale.title}. ${scale.description ?? ''}`.trim());
131
+ axis.select('title').text(scale.description ?? scale.title);
132
+ return;
133
+ }
134
+ const value = scale.read(gamma);
135
+ const position = Math.max(0, Math.min(1, scale.position(value)));
136
+ const number = value === Infinity ? '∞' : format('.4~g')(value);
137
+ const suffix = scale.unit === 'dB' ? ' dB' : scale.id === 'vswr' ? ' : 1' : '';
138
+ const readout = number + suffix;
139
+ const label = `${scale.title}: ${readout}${scale.description ? `. ${scale.description}` : ''}`;
140
+ axis.select('[data-role="scale-value"]').text(readout);
141
+ axis.attr('aria-label', label).select('title').text(label);
142
+ axis
143
+ .select('circle')
144
+ .attr('cx', this.start + position * this.length)
145
+ .attr('data-position', position)
146
+ .attr('visibility', null)
147
+ .style('visibility', 'visible');
148
+ }
149
+ /** Export mounted scales, optionally with a separate reading and its description. */
150
+ toSvg(options) {
151
+ this.assertAlive();
152
+ const container = this.container.node();
153
+ const style = getComputedStyle(container);
154
+ const readout = options?.readout;
155
+ const source = SvgExporter.scales(container, this.axes.nodes(), readout
156
+ ? (copy, index) => this.updateAxis(copy, this.scales[index], readout.reflectionCoefficient)
157
+ : undefined, readout?.label
158
+ ? {
159
+ text: readout.label,
160
+ color: style.getPropertyValue('--smithkit-scales-textColor').trim(),
161
+ fontFamily: style.getPropertyValue('--smithkit-fontFamily').trim(),
162
+ }
163
+ : undefined);
164
+ return options === undefined ? source : ImageExporter.svg([source], options);
165
+ }
166
+ /** Export mounted scales as PNG without changing their live readings. */
167
+ async toPng(options = {}) {
168
+ return ImageExporter.png([this.toSvg({ readout: options.readout })], options);
129
169
  }
130
170
  destroy() {
131
171
  if (this.destroyed) {
@@ -0,0 +1,18 @@
1
+ import type { ImageExportOptions } from './export.js';
2
+ interface Legend {
3
+ entries: {
4
+ name: string;
5
+ color: string;
6
+ }[];
7
+ textColor: string;
8
+ fontFamily: string;
9
+ }
10
+ /** Compose SVG snapshots and rasterize PNG reports without changing live chart nodes. */
11
+ export declare class ImageExporter {
12
+ private constructor();
13
+ private static layout;
14
+ static png(sources: string[], options: ImageExportOptions, legend?: Legend): Promise<Blob>;
15
+ static svg(sources: string[], options: ImageExportOptions, legend?: Legend): string;
16
+ private static wrap;
17
+ }
18
+ export {};