smithkit 0.1.1 → 0.2.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 (132) hide show
  1. package/README.md +119 -40
  2. package/dist/lib/MarkerMeasurements.d.ts +6 -0
  3. package/dist/lib/MarkerMeasurements.js +22 -0
  4. package/dist/lib/Smith.d.ts +9 -2
  5. package/dist/lib/Smith.js +68 -36
  6. package/dist/lib/SmithFormatter.d.ts +10 -0
  7. package/dist/lib/SmithFormatter.js +19 -0
  8. package/dist/lib/grid/CircleLayerControl.d.ts +15 -0
  9. package/dist/lib/grid/CircleLayerControl.js +40 -0
  10. package/dist/lib/{draw → grid}/ConstCircles.d.ts +4 -6
  11. package/dist/lib/{draw → grid}/ConstCircles.js +2 -2
  12. package/dist/lib/{draw → grid}/ConstQCircles.d.ts +2 -2
  13. package/dist/lib/{draw → grid}/ConstQCircles.js +3 -3
  14. package/dist/lib/{draw → grid}/ConstSwrCircles.d.ts +2 -2
  15. package/dist/lib/{draw → grid}/ConstSwrCircles.js +3 -3
  16. package/dist/lib/grid/GridDefinitions.d.ts +12 -0
  17. package/dist/lib/grid/GridDefinitions.js +138 -0
  18. package/dist/lib/grid/GridGeometry.d.ts +26 -0
  19. package/dist/lib/grid/GridGeometry.js +58 -0
  20. package/dist/lib/grid/GridLabels.d.ts +8 -0
  21. package/dist/lib/grid/GridLabels.js +45 -0
  22. package/dist/lib/grid/GridLayerControl.d.ts +12 -0
  23. package/dist/lib/grid/GridLayerControl.js +39 -0
  24. package/dist/lib/grid/LayerControl.d.ts +6 -0
  25. package/dist/lib/grid/LayerControl.js +16 -0
  26. package/dist/lib/{draw → grid}/SmithGridLayer.d.ts +5 -4
  27. package/dist/lib/{draw → grid}/SmithGridLayer.js +26 -9
  28. package/dist/lib/grid/types.d.ts +1 -1
  29. package/dist/lib/index.d.ts +8 -10
  30. package/dist/lib/index.js +5 -7
  31. package/dist/lib/{draw → interaction}/SmithCursor.d.ts +3 -3
  32. package/dist/lib/{draw → interaction}/SmithCursor.js +6 -6
  33. package/dist/lib/io/Touchstone.d.ts +10 -0
  34. package/dist/lib/io/Touchstone.js +75 -0
  35. package/dist/lib/layers.d.ts +2 -8
  36. package/dist/lib/layers.js +1 -73
  37. package/dist/lib/{complex → math}/Complex.d.ts +1 -0
  38. package/dist/lib/{complex → math}/Complex.js +1 -1
  39. package/dist/lib/math/geometry.d.ts +14 -0
  40. package/dist/lib/measurements.d.ts +2 -4
  41. package/dist/lib/measurements.js +1 -19
  42. package/dist/lib/rf/RfCalculations.d.ts +65 -0
  43. package/dist/lib/rf/RfCalculations.js +161 -0
  44. package/dist/lib/{SmithConstantCircle.d.ts → rf/SmithConstantCircle.d.ts} +2 -3
  45. package/dist/lib/{SmithConstantCircle.js → rf/SmithConstantCircle.js} +3 -4
  46. package/dist/lib/scales/PeripheralScaleDefinitions.d.ts +16 -0
  47. package/dist/lib/scales/PeripheralScaleDefinitions.js +54 -0
  48. package/dist/lib/scales/RadialScaleDefinitions.d.ts +15 -0
  49. package/dist/lib/scales/RadialScaleDefinitions.js +112 -0
  50. package/dist/lib/scales/SmithPeripheralScales.d.ts +4 -2
  51. package/dist/lib/scales/SmithPeripheralScales.js +46 -26
  52. package/dist/lib/scales/SmithScales.d.ts +3 -1
  53. package/dist/lib/scales/SmithScales.js +8 -2
  54. package/dist/lib/svg/LabelLayout.d.ts +17 -0
  55. package/dist/lib/svg/LabelLayout.js +145 -0
  56. package/dist/lib/{draw → svg}/SmithArc.d.ts +1 -1
  57. package/dist/lib/{draw → svg}/SmithCircle.d.ts +1 -1
  58. package/dist/lib/{draw → svg}/SmithLine.d.ts +1 -1
  59. package/dist/lib/{draw → svg}/SmithScaler.d.ts +1 -4
  60. package/dist/lib/{draw → svg}/SmithText.d.ts +1 -1
  61. package/dist/lib/svg/SvgExporter.d.ts +14 -0
  62. package/dist/lib/svg/SvgExporter.js +137 -0
  63. package/dist/lib/{draw → traces}/SmithData.d.ts +24 -35
  64. package/dist/lib/traces/SmithData.js +195 -0
  65. package/dist/lib/{draw → traces}/SmithMarker.d.ts +14 -3
  66. package/dist/lib/traces/SmithMarker.js +237 -0
  67. package/dist/lib/traces/TraceModel.d.ts +31 -0
  68. package/dist/lib/traces/TraceModel.js +123 -0
  69. package/dist/lib/traces/TraceRenderer.d.ts +33 -0
  70. package/dist/lib/traces/TraceRenderer.js +152 -0
  71. package/package.json +4 -1
  72. package/dist/lib/SmithArcsDefs.d.ts +0 -10
  73. package/dist/lib/SmithArcsDefs.js +0 -10
  74. package/dist/lib/components.d.ts +0 -14
  75. package/dist/lib/components.js +0 -26
  76. package/dist/lib/draw/SmithData.js +0 -366
  77. package/dist/lib/draw/SmithMarker.js +0 -122
  78. package/dist/lib/formatting.d.ts +0 -7
  79. package/dist/lib/formatting.js +0 -16
  80. package/dist/lib/grid/definitions.d.ts +0 -3
  81. package/dist/lib/grid/definitions.js +0 -123
  82. package/dist/lib/grid/geometry.d.ts +0 -20
  83. package/dist/lib/grid/geometry.js +0 -55
  84. package/dist/lib/grid/labels.d.ts +0 -3
  85. package/dist/lib/grid/labels.js +0 -41
  86. package/dist/lib/io/touchstone.d.ts +0 -7
  87. package/dist/lib/io/touchstone.js +0 -72
  88. package/dist/lib/math/complexRatio.d.ts +0 -3
  89. package/dist/lib/math/complexRatio.js +0 -30
  90. package/dist/lib/renormalization.d.ts +0 -10
  91. package/dist/lib/renormalization.js +0 -62
  92. package/dist/lib/rf.d.ts +0 -35
  93. package/dist/lib/rf.js +0 -74
  94. package/dist/lib/scales/peripheralScales.d.ts +0 -12
  95. package/dist/lib/scales/peripheralScales.js +0 -49
  96. package/dist/lib/scales/radialScales.d.ts +0 -12
  97. package/dist/lib/scales/radialScales.js +0 -109
  98. package/dist/lib/shapes/Arc.d.ts +0 -6
  99. package/dist/lib/shapes/Circle.d.ts +0 -5
  100. package/dist/lib/shapes/Circle.js +0 -1
  101. package/dist/lib/shapes/Line.d.ts +0 -5
  102. package/dist/lib/shapes/Line.js +0 -1
  103. package/dist/lib/shapes/Point.d.ts +0 -1
  104. package/dist/lib/shapes/Point.js +0 -1
  105. /package/dist/lib/{draw → grid}/ConstConductance.d.ts +0 -0
  106. /package/dist/lib/{draw → grid}/ConstConductance.js +0 -0
  107. /package/dist/lib/{draw → grid}/ConstReactance.d.ts +0 -0
  108. /package/dist/lib/{draw → grid}/ConstReactance.js +0 -0
  109. /package/dist/lib/{draw → grid}/ConstResistance.d.ts +0 -0
  110. /package/dist/lib/{draw → grid}/ConstResistance.js +0 -0
  111. /package/dist/lib/{draw → grid}/ConstSusceptance.d.ts +0 -0
  112. /package/dist/lib/{draw → grid}/ConstSusceptance.js +0 -0
  113. /package/dist/lib/{arcs → grid}/Tick.d.ts +0 -0
  114. /package/dist/lib/{arcs → grid}/Tick.js +0 -0
  115. /package/dist/lib/{draw → interaction}/MouseGesture.d.ts +0 -0
  116. /package/dist/lib/{draw → interaction}/MouseGesture.js +0 -0
  117. /package/dist/lib/{draw/SmithDrawOptions.js → math/geometry.js} +0 -0
  118. /package/dist/lib/{draw → svg}/SmithArc.js +0 -0
  119. /package/dist/lib/{draw → svg}/SmithCircle.js +0 -0
  120. /package/dist/lib/{draw → svg}/SmithDrawOptions.d.ts +0 -0
  121. /package/dist/lib/{shapes/Arc.js → svg/SmithDrawOptions.js} +0 -0
  122. /package/dist/lib/{draw → svg}/SmithGroup.d.ts +0 -0
  123. /package/dist/lib/{draw → svg}/SmithGroup.js +0 -0
  124. /package/dist/lib/{draw → svg}/SmithLine.js +0 -0
  125. /package/dist/lib/{draw → svg}/SmithPath.d.ts +0 -0
  126. /package/dist/lib/{draw → svg}/SmithPath.js +0 -0
  127. /package/dist/lib/{draw → svg}/SmithScaler.js +0 -0
  128. /package/dist/lib/{draw → svg}/SmithShape.d.ts +0 -0
  129. /package/dist/lib/{draw → svg}/SmithShape.js +0 -0
  130. /package/dist/lib/{draw → svg}/SmithSvg.d.ts +0 -0
  131. /package/dist/lib/{draw → svg}/SmithSvg.js +0 -0
  132. /package/dist/lib/{draw → svg}/SmithText.js +0 -0
@@ -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,26 +32,43 @@ 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', '#334155')
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', '#475569')
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)
@@ -60,7 +80,7 @@ export class SmithPeripheralScales extends SmithGroup {
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')
@@ -76,9 +96,9 @@ export class SmithPeripheralScales extends SmithGroup {
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')
@@ -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,4 @@
1
- import type { Complex } from '../complex/Complex.js';
1
+ import type { Complex } from '../math/Complex.js';
2
2
  /** Independent parameter scales. Connect update() to a chart's cursor events. */
3
3
  export declare class SmithScales {
4
4
  private readonly scales;
@@ -11,6 +11,8 @@ export declare class SmithScales {
11
11
  draw(target: string | HTMLElement): void;
12
12
  /** Show a passive-load reflection coefficient, or hide indicators with null. */
13
13
  update(gamma: Complex | null): void;
14
+ /** Export all mounted scales in their current layout as a standalone SVG. */
15
+ toSvg(): string;
14
16
  destroy(): void;
15
17
  private assertAlive;
16
18
  }
@@ -1,8 +1,9 @@
1
1
  import { select, format } from 'd3';
2
- import { radialScales } from './radialScales.js';
2
+ import { RadialScaleDefinitions } from './RadialScaleDefinitions.js';
3
+ import { SvgExporter } from '../svg/SvgExporter.js';
3
4
  /** Independent parameter scales. Connect update() to a chart's cursor events. */
4
5
  export class SmithScales {
5
- scales = radialScales();
6
+ scales = RadialScaleDefinitions.create();
6
7
  container = select(document.createElement('div'))
7
8
  .attr('class', 'radial-scales')
8
9
  .style('display', 'grid')
@@ -127,6 +128,11 @@ export class SmithScales {
127
128
  .attr('visibility', null);
128
129
  });
129
130
  }
131
+ /** Export all mounted scales in their current layout as a standalone SVG. */
132
+ toSvg() {
133
+ this.assertAlive();
134
+ return SvgExporter.scales(this.container.node(), this.axes.nodes());
135
+ }
130
136
  destroy() {
131
137
  if (this.destroyed) {
132
138
  return;
@@ -0,0 +1,17 @@
1
+ /** Responsive label placement measured in the default view, independent of user zoom. */
2
+ export declare class LabelLayout {
3
+ private readonly viewport;
4
+ private readonly container;
5
+ private readonly baseView;
6
+ private width;
7
+ private height;
8
+ private readonly resizedFonts;
9
+ constructor(viewport: SVGSVGElement, container: SVGGElement, baseView: {
10
+ x: number;
11
+ y: number;
12
+ k: number;
13
+ });
14
+ update(force?: boolean): void;
15
+ private static transformBox;
16
+ private static overlaps;
17
+ }
@@ -0,0 +1,145 @@
1
+ /** Responsive label placement measured in the default view, independent of user zoom. */
2
+ export class LabelLayout {
3
+ viewport;
4
+ container;
5
+ baseView;
6
+ width = 0;
7
+ height = 0;
8
+ resizedFonts = new Map();
9
+ constructor(viewport, container, baseView) {
10
+ this.viewport = viewport;
11
+ this.container = container;
12
+ this.baseView = baseView;
13
+ }
14
+ update(force = false) {
15
+ const viewport = this.viewport.getBoundingClientRect();
16
+ if (!this.viewport.isConnected || viewport.width <= 0 || viewport.height <= 0) {
17
+ return;
18
+ }
19
+ if (!force && viewport.width === this.width && viewport.height === this.height) {
20
+ return;
21
+ }
22
+ this.width = viewport.width;
23
+ this.height = viewport.height;
24
+ const rootMatrix = this.viewport.getScreenCTM();
25
+ const containerMatrix = this.container.getScreenCTM();
26
+ if (!rootMatrix || !containerMatrix) {
27
+ return;
28
+ }
29
+ const reference = rootMatrix.translate(this.baseView.x, this.baseView.y).scale(this.baseView.k);
30
+ const normalize = reference.multiply(containerMatrix.inverse());
31
+ // Peripheral rulers retain their complete, fixed-scale lettering. Only grid
32
+ // labels participate in thinning and minimum-font-size adjustments.
33
+ const labels = [...this.container.querySelectorAll('text')].filter((label) => !label.closest('[data-label-layer=peripheral-scales]'));
34
+ const view = this.viewport.ownerDocument.defaultView;
35
+ // Restore only properties owned by this layout before measuring the base styling.
36
+ for (const label of labels) {
37
+ if (label.hasAttribute('data-label-hidden')) {
38
+ label.removeAttribute('visibility');
39
+ label.removeAttribute('data-label-hidden');
40
+ }
41
+ const previousFont = this.resizedFonts.get(label);
42
+ if (previousFont !== undefined) {
43
+ if (previousFont) {
44
+ label.style.fontSize = previousFont;
45
+ }
46
+ else {
47
+ label.style.removeProperty('font-size');
48
+ }
49
+ }
50
+ }
51
+ this.resizedFonts.clear();
52
+ const styles = new Map();
53
+ const style = (element) => {
54
+ let result = styles.get(element);
55
+ if (!result) {
56
+ result = view.getComputedStyle(element);
57
+ styles.set(element, result);
58
+ }
59
+ return result;
60
+ };
61
+ const candidates = labels.flatMap((label, order) => {
62
+ for (let parent = label; parent; parent = parent.parentElement) {
63
+ const computed = style(parent);
64
+ if (computed.display === 'none' ||
65
+ computed.visibility === 'hidden' ||
66
+ Number(computed.opacity) === 0) {
67
+ return [];
68
+ }
69
+ if (parent === this.viewport) {
70
+ break;
71
+ }
72
+ }
73
+ const screenMatrix = label.getScreenCTM();
74
+ const matrix = screenMatrix ? normalize.multiply(screenMatrix) : null;
75
+ const scale = matrix ? Math.hypot(matrix.a, matrix.b) : 0;
76
+ if (scale <= 0) {
77
+ return [];
78
+ }
79
+ return [
80
+ {
81
+ label,
82
+ order,
83
+ scale,
84
+ matrix: matrix,
85
+ baseSize: parseFloat(style(label).fontSize),
86
+ priority: Number(label.dataset.labelPriority ?? 0),
87
+ },
88
+ ];
89
+ });
90
+ // Batch font changes before reading bounds to avoid alternating layout reads/writes.
91
+ for (const { label, scale, baseSize } of candidates) {
92
+ if (Number.isFinite(baseSize) && baseSize * scale < 9) {
93
+ this.resizedFonts.set(label, label.style.fontSize);
94
+ label.style.fontSize = `${9 / scale}px`;
95
+ }
96
+ }
97
+ const measured = candidates.map((entry) => ({
98
+ ...entry,
99
+ box: LabelLayout.transformBox(entry.label.getBBox(), entry.matrix),
100
+ }));
101
+ measured.sort((a, b) => b.priority - a.priority || a.order - b.order);
102
+ const occupied = [];
103
+ const hidden = [];
104
+ for (const { label, box } of measured) {
105
+ const outside = box.right <= box.left ||
106
+ box.bottom <= box.top ||
107
+ box.left < viewport.left + 1 ||
108
+ box.right > viewport.right - 1 ||
109
+ box.top < viewport.top + 1 ||
110
+ box.bottom > viewport.bottom - 1;
111
+ const collision = occupied.some((other) => LabelLayout.overlaps(box, other));
112
+ if (outside || collision) {
113
+ hidden.push(label);
114
+ }
115
+ else {
116
+ occupied.push(box);
117
+ }
118
+ }
119
+ for (const label of hidden) {
120
+ label.setAttribute('visibility', 'hidden');
121
+ label.setAttribute('data-label-hidden', 'true');
122
+ }
123
+ }
124
+ static transformBox(box, matrix) {
125
+ const points = [
126
+ new DOMPoint(box.x, box.y),
127
+ new DOMPoint(box.x + box.width, box.y),
128
+ new DOMPoint(box.x, box.y + box.height),
129
+ new DOMPoint(box.x + box.width, box.y + box.height),
130
+ ].map((point) => point.matrixTransform(matrix));
131
+ return {
132
+ left: Math.min(...points.map((point) => point.x)),
133
+ right: Math.max(...points.map((point) => point.x)),
134
+ top: Math.min(...points.map((point) => point.y)),
135
+ bottom: Math.max(...points.map((point) => point.y)),
136
+ };
137
+ }
138
+ static overlaps(a, b) {
139
+ const gap = 2;
140
+ return (a.left < b.right + gap &&
141
+ a.right + gap > b.left &&
142
+ a.top < b.bottom + gap &&
143
+ a.bottom + gap > b.top);
144
+ }
145
+ }
@@ -1,4 +1,4 @@
1
- import { Point } from '../shapes/Point.js';
1
+ import { Point } from '../math/geometry.js';
2
2
  import { SmithShape } from './SmithShape.js';
3
3
  import { SmithDrawOptions } from './SmithDrawOptions.js';
4
4
  export declare class SmithArc extends SmithShape {
@@ -1,4 +1,4 @@
1
- import { Circle } from '../shapes/Circle.js';
1
+ import { Circle } from '../math/geometry.js';
2
2
  import { SmithShape } from './SmithShape.js';
3
3
  import { SmithDrawOptions } from './SmithDrawOptions.js';
4
4
  export declare class SmithCircle extends SmithShape {
@@ -1,4 +1,4 @@
1
- import { Point } from '../shapes/Point.js';
1
+ import { Point } from '../math/geometry.js';
2
2
  import { SmithShape } from './SmithShape.js';
3
3
  import { SmithDrawOptions } from './SmithDrawOptions.js';
4
4
  export declare class SmithLine extends SmithShape {
@@ -1,8 +1,5 @@
1
1
  import * as d3 from 'd3';
2
- import { Circle } from '../shapes/Circle.js';
3
- import { Point } from '../shapes/Point.js';
4
- import { Line } from '../shapes/Line.js';
5
- import { Arc } from '../shapes/Arc.js';
2
+ import type { Circle, Point, Line, Arc } from '../math/geometry.js';
6
3
  export declare class SmithScaler {
7
4
  readonly x: d3.ScaleLinear<number, number>;
8
5
  readonly y: d3.ScaleLinear<number, number>;
@@ -1,4 +1,4 @@
1
- import { Point } from '../shapes/Point.js';
1
+ import { Point } from '../math/geometry.js';
2
2
  import { SmithShape } from './SmithShape.js';
3
3
  interface TextOptions {
4
4
  dx?: string;
@@ -0,0 +1,14 @@
1
+ /** Captures rendered SVG with presentation styles resolved in its source document. */
2
+ export declare class SvgExporter {
3
+ private constructor();
4
+ private static readonly namespace;
5
+ private static readonly properties;
6
+ private static readonly nonInherited;
7
+ static chart(source: SVGElement): string;
8
+ /** Preserve the current CSS layout of separate scale SVGs in one SVG document. */
9
+ static scales(container: HTMLElement, axes: readonly SVGSVGElement[]): string;
10
+ private static bounds;
11
+ private static copy;
12
+ private static size;
13
+ private static serialize;
14
+ }
@@ -0,0 +1,137 @@
1
+ /** Captures rendered SVG with presentation styles resolved in its source document. */
2
+ export class SvgExporter {
3
+ constructor() { }
4
+ static namespace = 'http://www.w3.org/2000/svg';
5
+ static properties = [
6
+ 'color',
7
+ 'display',
8
+ 'visibility',
9
+ 'opacity',
10
+ 'fill',
11
+ 'fill-opacity',
12
+ 'fill-rule',
13
+ 'stroke',
14
+ 'stroke-width',
15
+ 'stroke-opacity',
16
+ 'stroke-dasharray',
17
+ 'stroke-dashoffset',
18
+ 'stroke-linecap',
19
+ 'stroke-linejoin',
20
+ 'stroke-miterlimit',
21
+ 'vector-effect',
22
+ 'paint-order',
23
+ 'shape-rendering',
24
+ 'text-rendering',
25
+ 'font-family',
26
+ 'font-size',
27
+ 'font-style',
28
+ 'font-weight',
29
+ 'font-variant',
30
+ 'font-stretch',
31
+ 'letter-spacing',
32
+ 'word-spacing',
33
+ 'text-anchor',
34
+ 'dominant-baseline',
35
+ 'alignment-baseline',
36
+ 'baseline-shift',
37
+ 'text-decoration',
38
+ 'white-space',
39
+ ];
40
+ static nonInherited = new Set([
41
+ 'display',
42
+ 'opacity',
43
+ 'vector-effect',
44
+ 'alignment-baseline',
45
+ 'dominant-baseline',
46
+ 'baseline-shift',
47
+ 'text-decoration',
48
+ ]);
49
+ static chart(source) {
50
+ const bounds = SvgExporter.bounds(source);
51
+ const copy = SvgExporter.copy(source);
52
+ SvgExporter.size(copy, bounds.width, bounds.height);
53
+ return SvgExporter.serialize(copy);
54
+ }
55
+ /** Preserve the current CSS layout of separate scale SVGs in one SVG document. */
56
+ static scales(container, axes) {
57
+ const bounds = SvgExporter.bounds(container);
58
+ const root = container.ownerDocument.createElementNS(SvgExporter.namespace, 'svg');
59
+ root.setAttribute('viewBox', `0 0 ${bounds.width} ${bounds.height}`);
60
+ SvgExporter.size(root, bounds.width, bounds.height);
61
+ for (const axis of axes) {
62
+ const position = SvgExporter.bounds(axis);
63
+ const copy = SvgExporter.copy(axis);
64
+ copy.setAttribute('x', String(position.x - bounds.x));
65
+ copy.setAttribute('y', String(position.y - bounds.y));
66
+ SvgExporter.size(copy, position.width, position.height);
67
+ root.appendChild(copy);
68
+ }
69
+ return SvgExporter.serialize(root);
70
+ }
71
+ static bounds(element) {
72
+ const bounds = element.getBoundingClientRect();
73
+ if (!element.isConnected || bounds.width <= 0 || bounds.height <= 0) {
74
+ throw new Error('SVG export requires a mounted component with a visible, non-zero size.');
75
+ }
76
+ return bounds;
77
+ }
78
+ static copy(source) {
79
+ const copy = source.cloneNode(true);
80
+ const originals = [source, ...source.querySelectorAll('*')];
81
+ const copies = [copy, ...copy.querySelectorAll('*')];
82
+ const view = source.ownerDocument.defaultView;
83
+ const styles = new Map(originals.map((element) => [element, view.getComputedStyle(element)]));
84
+ originals.forEach((original, index) => {
85
+ const element = copies[index];
86
+ const style = styles.get(original);
87
+ const parentStyle = original.parentElement ? styles.get(original.parentElement) : undefined;
88
+ // Keep SVG geometry/transform attributes, but replace page-dependent CSS.
89
+ element.removeAttribute('style');
90
+ for (const property of SvgExporter.properties) {
91
+ const value = style.getPropertyValue(property);
92
+ element.removeAttribute(property);
93
+ // Resolve inherited values at their boundary instead of repeating every font and
94
+ // stroke property on thousands of descendants. CSS overrides may replace attributes.
95
+ if (value &&
96
+ (SvgExporter.nonInherited.has(property) ||
97
+ parentStyle?.getPropertyValue(property) !== value)) {
98
+ element.style.setProperty(property, value);
99
+ }
100
+ }
101
+ // An exported image has no event handlers or interactive controls.
102
+ element.removeAttribute('tabindex');
103
+ if (original.getAttribute('data-role') === 'marker') {
104
+ // A static marker is an image, not an operable sample slider.
105
+ element.setAttribute('role', 'img');
106
+ element.setAttribute('aria-label', `${original.getAttribute('aria-label')}; ${original.getAttribute('aria-valuetext')}`);
107
+ for (const attribute of [
108
+ 'aria-valuemin',
109
+ 'aria-valuemax',
110
+ 'aria-valuenow',
111
+ 'aria-valuetext',
112
+ 'aria-orientation',
113
+ 'aria-description',
114
+ 'aria-disabled',
115
+ ]) {
116
+ element.removeAttribute(attribute);
117
+ }
118
+ }
119
+ element.removeAttribute('class');
120
+ for (const attribute of [...element.attributes]) {
121
+ if (attribute.name.startsWith('on')) {
122
+ element.removeAttribute(attribute.name);
123
+ }
124
+ }
125
+ });
126
+ return copy;
127
+ }
128
+ static size(svg, width, height) {
129
+ svg.setAttribute('width', String(width));
130
+ svg.setAttribute('height', String(height));
131
+ svg.style.setProperty('overflow', 'hidden');
132
+ }
133
+ static serialize(svg) {
134
+ svg.setAttribute('xmlns', SvgExporter.namespace);
135
+ return new XMLSerializer().serializeToString(svg);
136
+ }
137
+ }