smithkit 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +481 -2
- package/dist/lib/Smith.d.ts +103 -0
- package/dist/lib/Smith.js +551 -0
- package/dist/lib/SmithArcsDefs.d.ts +10 -0
- package/dist/lib/SmithArcsDefs.js +10 -0
- package/dist/lib/SmithConstantCircle.d.ts +62 -0
- package/dist/lib/SmithConstantCircle.js +222 -0
- package/dist/lib/arcs/Tick.d.ts +33 -0
- package/dist/lib/arcs/Tick.js +40 -0
- package/dist/lib/complex/Complex.d.ts +130 -0
- package/dist/lib/complex/Complex.js +636 -0
- package/dist/lib/components.d.ts +14 -0
- package/dist/lib/components.js +26 -0
- package/dist/lib/draw/ConstCircles.d.ts +61 -0
- package/dist/lib/draw/ConstCircles.js +151 -0
- package/dist/lib/draw/ConstConductance.d.ts +4 -0
- package/dist/lib/draw/ConstConductance.js +6 -0
- package/dist/lib/draw/ConstQCircles.d.ts +29 -0
- package/dist/lib/draw/ConstQCircles.js +86 -0
- package/dist/lib/draw/ConstReactance.d.ts +4 -0
- package/dist/lib/draw/ConstReactance.js +6 -0
- package/dist/lib/draw/ConstResistance.d.ts +4 -0
- package/dist/lib/draw/ConstResistance.js +6 -0
- package/dist/lib/draw/ConstSusceptance.d.ts +4 -0
- package/dist/lib/draw/ConstSusceptance.js +6 -0
- package/dist/lib/draw/ConstSwrCircles.d.ts +29 -0
- package/dist/lib/draw/ConstSwrCircles.js +85 -0
- package/dist/lib/draw/MouseGesture.d.ts +6 -0
- package/dist/lib/draw/MouseGesture.js +26 -0
- package/dist/lib/draw/SmithArc.d.ts +10 -0
- package/dist/lib/draw/SmithArc.js +26 -0
- package/dist/lib/draw/SmithCircle.d.ts +10 -0
- package/dist/lib/draw/SmithCircle.js +24 -0
- package/dist/lib/draw/SmithCursor.d.ts +49 -0
- package/dist/lib/draw/SmithCursor.js +186 -0
- package/dist/lib/draw/SmithData.d.ts +66 -0
- package/dist/lib/draw/SmithData.js +366 -0
- package/dist/lib/draw/SmithDrawOptions.d.ts +5 -0
- package/dist/lib/draw/SmithDrawOptions.js +1 -0
- package/dist/lib/draw/SmithGridLayer.d.ts +19 -0
- package/dist/lib/draw/SmithGridLayer.js +48 -0
- package/dist/lib/draw/SmithGroup.d.ts +11 -0
- package/dist/lib/draw/SmithGroup.js +31 -0
- package/dist/lib/draw/SmithLine.d.ts +11 -0
- package/dist/lib/draw/SmithLine.js +27 -0
- package/dist/lib/draw/SmithMarker.d.ts +25 -0
- package/dist/lib/draw/SmithMarker.js +122 -0
- package/dist/lib/draw/SmithPath.d.ts +8 -0
- package/dist/lib/draw/SmithPath.js +20 -0
- package/dist/lib/draw/SmithScaler.d.ts +19 -0
- package/dist/lib/draw/SmithScaler.js +48 -0
- package/dist/lib/draw/SmithShape.d.ts +15 -0
- package/dist/lib/draw/SmithShape.js +42 -0
- package/dist/lib/draw/SmithSvg.d.ts +9 -0
- package/dist/lib/draw/SmithSvg.js +27 -0
- package/dist/lib/draw/SmithText.d.ts +22 -0
- package/dist/lib/draw/SmithText.js +56 -0
- package/dist/lib/formatting.d.ts +7 -0
- package/dist/lib/formatting.js +16 -0
- package/dist/lib/grid/definitions.d.ts +3 -0
- package/dist/lib/grid/definitions.js +123 -0
- package/dist/lib/grid/geometry.d.ts +20 -0
- package/dist/lib/grid/geometry.js +55 -0
- package/dist/lib/grid/labels.d.ts +3 -0
- package/dist/lib/grid/labels.js +41 -0
- package/dist/lib/grid/types.d.ts +30 -0
- package/dist/lib/grid/types.js +6 -0
- package/dist/lib/index.d.ts +16 -0
- package/dist/lib/index.js +9 -0
- package/dist/lib/io/touchstone.d.ts +7 -0
- package/dist/lib/io/touchstone.js +72 -0
- package/dist/lib/layers.d.ts +48 -0
- package/dist/lib/layers.js +73 -0
- package/dist/lib/math/complexRatio.d.ts +3 -0
- package/dist/lib/math/complexRatio.js +30 -0
- package/dist/lib/measurements.d.ts +53 -0
- package/dist/lib/measurements.js +19 -0
- package/dist/lib/renormalization.d.ts +10 -0
- package/dist/lib/renormalization.js +62 -0
- package/dist/lib/rf.d.ts +35 -0
- package/dist/lib/rf.js +74 -0
- package/dist/lib/samples.d.ts +6 -0
- package/dist/lib/samples.js +1 -0
- package/dist/lib/scales/SmithPeripheralScales.d.ts +10 -0
- package/dist/lib/scales/SmithPeripheralScales.js +124 -0
- package/dist/lib/scales/SmithScales.d.ts +16 -0
- package/dist/lib/scales/SmithScales.js +134 -0
- package/dist/lib/scales/peripheralScales.d.ts +12 -0
- package/dist/lib/scales/peripheralScales.js +49 -0
- package/dist/lib/scales/radialScales.d.ts +12 -0
- package/dist/lib/scales/radialScales.js +109 -0
- package/dist/lib/shapes/Arc.d.ts +6 -0
- package/dist/lib/shapes/Arc.js +1 -0
- package/dist/lib/shapes/Circle.d.ts +5 -0
- package/dist/lib/shapes/Circle.js +1 -0
- package/dist/lib/shapes/Line.d.ts +5 -0
- package/dist/lib/shapes/Line.js +1 -0
- package/dist/lib/shapes/Point.d.ts +1 -0
- package/dist/lib/shapes/Point.js +1 -0
- package/package.json +76 -4
package/dist/lib/rf.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Complex } from './complex/Complex.js';
|
|
2
|
+
import { SmithConstantCircle } from './SmithConstantCircle.js';
|
|
3
|
+
const calcs = new SmithConstantCircle();
|
|
4
|
+
function validateReference(referenceImpedanceOhms) {
|
|
5
|
+
if (!Number.isFinite(referenceImpedanceOhms) || referenceImpedanceOhms <= 0) {
|
|
6
|
+
throw new RangeError('Reference impedance must be positive and finite.');
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function validateComplex(value) {
|
|
10
|
+
if (!Number.isFinite(value.re) || !Number.isFinite(value.im)) {
|
|
11
|
+
throw new RangeError('Complex components must be finite.');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function finite(value) {
|
|
15
|
+
return value && Number.isFinite(value.re) && Number.isFinite(value.im) ? value : undefined;
|
|
16
|
+
}
|
|
17
|
+
/** Convert Γ to physical impedance in ohms; undefined at the open circuit Γ = 1. */
|
|
18
|
+
export function reflectionToImpedance(gamma, referenceImpedanceOhms = 50) {
|
|
19
|
+
validateReference(referenceImpedanceOhms);
|
|
20
|
+
validateComplex(gamma);
|
|
21
|
+
return finite(calcs.rflCoeffToImpedance(gamma)?.mul(referenceImpedanceOhms));
|
|
22
|
+
}
|
|
23
|
+
/** Convert physical impedance in ohms to Γ. Undefined for a singular result. */
|
|
24
|
+
export function impedanceToReflection(impedanceOhms, referenceImpedanceOhms = 50) {
|
|
25
|
+
validateReference(referenceImpedanceOhms);
|
|
26
|
+
validateComplex(impedanceOhms);
|
|
27
|
+
return finite(calcs.impedanceToRflCoeff(impedanceOhms.div(referenceImpedanceOhms)));
|
|
28
|
+
}
|
|
29
|
+
/** Convert Γ to physical admittance in siemens; undefined at the short circuit Γ = −1. */
|
|
30
|
+
export function reflectionToAdmittance(gamma, referenceImpedanceOhms = 50) {
|
|
31
|
+
validateReference(referenceImpedanceOhms);
|
|
32
|
+
validateComplex(gamma);
|
|
33
|
+
return finite(calcs.rflCoeffToAdmittance(gamma)?.div(referenceImpedanceOhms));
|
|
34
|
+
}
|
|
35
|
+
/** Convert physical admittance in siemens to Γ. Undefined for a singular result. */
|
|
36
|
+
export function admittanceToReflection(admittanceSiemens, referenceImpedanceOhms = 50) {
|
|
37
|
+
validateReference(referenceImpedanceOhms);
|
|
38
|
+
validateComplex(admittanceSiemens);
|
|
39
|
+
return finite(calcs.admittanceToRflCoeff(admittanceSiemens.mul(referenceImpedanceOhms)));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Read Γ without a chart or DOM. Undefined means a singular or inapplicable quantity.
|
|
43
|
+
* Passive-load quantities are undefined outside |Γ| ≤ 1; exact infinite limits are retained.
|
|
44
|
+
* Attenuation assumes a fully reflecting termination and is half the return loss.
|
|
45
|
+
*/
|
|
46
|
+
export function readReflection(gamma, referenceImpedanceOhms = 50) {
|
|
47
|
+
const impedanceOhms = reflectionToImpedance(gamma, referenceImpedanceOhms);
|
|
48
|
+
const admittanceSiemens = reflectionToAdmittance(gamma, referenceImpedanceOhms);
|
|
49
|
+
const magnitude = gamma.abs();
|
|
50
|
+
const passive = magnitude <= 1;
|
|
51
|
+
const returnLossDb = -20 * Math.log10(magnitude);
|
|
52
|
+
const vswr = passive ? (1 + magnitude) / (1 - magnitude) : undefined;
|
|
53
|
+
return {
|
|
54
|
+
reflectionCoefficient: gamma,
|
|
55
|
+
impedanceOhms,
|
|
56
|
+
admittanceSiemens,
|
|
57
|
+
phaseDegrees: magnitude === 0 ? undefined : (gamma.arg() * 180) / Math.PI,
|
|
58
|
+
vswr,
|
|
59
|
+
standingWaveRatioDb: vswr === undefined ? undefined : 20 * Math.log10(vswr),
|
|
60
|
+
returnLossDb,
|
|
61
|
+
reflectionLossDb: passive ? (-10 / Math.LN10) * Math.log1p(-(magnitude ** 2)) : undefined,
|
|
62
|
+
attenuationDb: passive ? returnLossDb / 2 : undefined,
|
|
63
|
+
q: impedanceOhms && (impedanceOhms.re !== 0 || impedanceOhms.im !== 0)
|
|
64
|
+
? Math.abs(impedanceOhms.im / impedanceOhms.re)
|
|
65
|
+
: undefined,
|
|
66
|
+
powerReflectionCoefficient: magnitude ** 2,
|
|
67
|
+
voltageReflectionCoefficientMagnitude: magnitude,
|
|
68
|
+
powerTransmissionCoefficient: passive ? 1 - magnitude ** 2 : undefined,
|
|
69
|
+
voltageTransmissionCoefficientMagnitude: gamma.add(1).abs(),
|
|
70
|
+
currentTransmissionCoefficientMagnitude: Complex.one().sub(gamma).abs(),
|
|
71
|
+
standingWavePeak: vswr === undefined ? undefined : Math.sqrt(vswr),
|
|
72
|
+
standingWaveLossCoefficient: passive ? (1 + magnitude ** 2) / (1 - magnitude ** 2) : undefined,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Complex } from '../complex/Complex.js';
|
|
2
|
+
import { SmithGroup } from '../draw/SmithGroup.js';
|
|
3
|
+
/** Peripheral rulers in the chart's 500-unit coordinate system. */
|
|
4
|
+
export declare class SmithPeripheralScales extends SmithGroup {
|
|
5
|
+
private readonly scales;
|
|
6
|
+
private readonly axes;
|
|
7
|
+
constructor();
|
|
8
|
+
/** Connect to the same cursor or marker data as the independent parameter scales. */
|
|
9
|
+
update(gamma: Complex | null): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { select, format } from 'd3';
|
|
2
|
+
import { SmithGroup } from '../draw/SmithGroup.js';
|
|
3
|
+
import { peripheralScales } from './peripheralScales.js';
|
|
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
|
+
}
|
|
11
|
+
/** Peripheral rulers in the chart's 500-unit coordinate system. */
|
|
12
|
+
export class SmithPeripheralScales extends SmithGroup {
|
|
13
|
+
scales = peripheralScales();
|
|
14
|
+
axes = this.Element.attr('data-layer', 'peripheral-scales')
|
|
15
|
+
.attr('pointer-events', 'none')
|
|
16
|
+
.selectAll('g')
|
|
17
|
+
.data(this.scales)
|
|
18
|
+
.join('g')
|
|
19
|
+
.attr('data-scale', (scale) => scale.id)
|
|
20
|
+
.attr('role', 'img')
|
|
21
|
+
.attr('aria-label', (scale) => scale.title);
|
|
22
|
+
constructor() {
|
|
23
|
+
super();
|
|
24
|
+
this.axes.each(function (scale, index) {
|
|
25
|
+
const axis = select(this);
|
|
26
|
+
const radius = firstRadius + index * ringSpacing;
|
|
27
|
+
const captionAngle = scale.unit === 'λ' ? 180 : 0;
|
|
28
|
+
axis.append('title').text(scale.title);
|
|
29
|
+
axis
|
|
30
|
+
.append('circle')
|
|
31
|
+
.attr('cx', 250)
|
|
32
|
+
.attr('cy', 250)
|
|
33
|
+
.attr('r', radius)
|
|
34
|
+
.attr('fill', 'none')
|
|
35
|
+
.attr('stroke', '#475569')
|
|
36
|
+
.attr('stroke-width', 0.8)
|
|
37
|
+
.attr('vector-effect', 'non-scaling-stroke');
|
|
38
|
+
for (const value of scale.ticks) {
|
|
39
|
+
const angle = scale.angle(value);
|
|
40
|
+
const [x1, y1] = point(radius, angle);
|
|
41
|
+
const major = scale.major(value);
|
|
42
|
+
const [x2, y2] = point(radius - (major ? 4 : 2), angle);
|
|
43
|
+
axis
|
|
44
|
+
.append('line')
|
|
45
|
+
.attr('x1', x1)
|
|
46
|
+
.attr('y1', y1)
|
|
47
|
+
.attr('x2', x2)
|
|
48
|
+
.attr('y2', y2)
|
|
49
|
+
.attr('stroke', '#475569')
|
|
50
|
+
.attr('stroke-width', major ? 0.8 : 0.5)
|
|
51
|
+
.attr('vector-effect', 'non-scaling-stroke');
|
|
52
|
+
const captionDistance = Math.abs(((angle - captionAngle + 540) % 360) - 180);
|
|
53
|
+
if (major && captionDistance > 25) {
|
|
54
|
+
const [x, y] = point(radius + 8, angle);
|
|
55
|
+
const rotation = angle >= 0 ? 90 - angle : -90 - angle;
|
|
56
|
+
axis
|
|
57
|
+
.append('text')
|
|
58
|
+
.attr('x', x)
|
|
59
|
+
.attr('y', y)
|
|
60
|
+
.attr('transform', `rotate(${rotation},${x},${y})`)
|
|
61
|
+
.attr('text-anchor', 'middle')
|
|
62
|
+
.attr('dominant-baseline', 'central')
|
|
63
|
+
.attr('font-family', 'system-ui, sans-serif')
|
|
64
|
+
.attr('font-size', 10.5)
|
|
65
|
+
.attr('fill', '#334155')
|
|
66
|
+
.text(scale.unit === 'λ' ? value.toFixed(2) : String(value));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Reserve a label sector for the curved caption without masking the ruler or its ticks.
|
|
70
|
+
const captionRadius = radius + 5;
|
|
71
|
+
const start = point(captionRadius, captionAngle + 45);
|
|
72
|
+
const end = point(captionRadius, captionAngle - 45);
|
|
73
|
+
const pathId = `smithkit-peripheral-caption-${nextScaleId++}`;
|
|
74
|
+
axis
|
|
75
|
+
.append('defs')
|
|
76
|
+
.append('path')
|
|
77
|
+
.attr('id', pathId)
|
|
78
|
+
.attr('d', `M${start} A${captionRadius},${captionRadius} 0 0 1 ${end}`);
|
|
79
|
+
const caption = scale.unit === 'λ'
|
|
80
|
+
? scale.id === 'wavelengths-generator'
|
|
81
|
+
? 'WAVELENGTHS TOWARD GENERATOR →'
|
|
82
|
+
: '← WAVELENGTHS TOWARD LOAD'
|
|
83
|
+
: scale.title.toUpperCase();
|
|
84
|
+
axis
|
|
85
|
+
.append('text')
|
|
86
|
+
.attr('class', 'peripheral-caption')
|
|
87
|
+
.attr('text-anchor', 'middle')
|
|
88
|
+
.attr('font-family', 'system-ui, sans-serif')
|
|
89
|
+
.attr('font-size', 10.5)
|
|
90
|
+
.attr('font-weight', 500)
|
|
91
|
+
.attr('letter-spacing', 0.3)
|
|
92
|
+
.attr('fill', '#334155')
|
|
93
|
+
.append('textPath')
|
|
94
|
+
.attr('href', `#${pathId}`)
|
|
95
|
+
.attr('startOffset', '50%')
|
|
96
|
+
.text(caption);
|
|
97
|
+
axis
|
|
98
|
+
.append('circle')
|
|
99
|
+
.attr('class', 'peripheral-indicator')
|
|
100
|
+
.attr('r', 3)
|
|
101
|
+
.attr('visibility', 'hidden')
|
|
102
|
+
.attr('fill', '#dc2626')
|
|
103
|
+
.attr('stroke', 'white')
|
|
104
|
+
.attr('stroke-width', 1);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/** Connect to the same cursor or marker data as the independent parameter scales. */
|
|
108
|
+
update(gamma) {
|
|
109
|
+
const valid = gamma !== null && Number.isFinite(gamma.abs()) && gamma.abs() <= 1;
|
|
110
|
+
this.axes.each(function (scale, index) {
|
|
111
|
+
const value = valid ? scale.read(gamma) : null;
|
|
112
|
+
const axis = select(this);
|
|
113
|
+
const label = value === null ? scale.title : `${scale.title}: ${format('.4~g')(value)} ${scale.unit}`;
|
|
114
|
+
axis.attr('aria-label', label).select('title').text(label);
|
|
115
|
+
const dot = axis.select('.peripheral-indicator');
|
|
116
|
+
if (value === null) {
|
|
117
|
+
dot.attr('visibility', 'hidden').attr('data-value', null);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const [x, y] = point(firstRadius + index * ringSpacing, scale.angle(value));
|
|
121
|
+
dot.attr('cx', x).attr('cy', y).attr('data-value', value).attr('visibility', null);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Complex } from '../complex/Complex.js';
|
|
2
|
+
/** Independent parameter scales. Connect update() to a chart's cursor events. */
|
|
3
|
+
export declare class SmithScales {
|
|
4
|
+
private readonly scales;
|
|
5
|
+
private readonly container;
|
|
6
|
+
private destroyed;
|
|
7
|
+
private readonly start;
|
|
8
|
+
private readonly length;
|
|
9
|
+
private readonly axes;
|
|
10
|
+
constructor();
|
|
11
|
+
draw(target: string | HTMLElement): void;
|
|
12
|
+
/** Show a passive-load reflection coefficient, or hide indicators with null. */
|
|
13
|
+
update(gamma: Complex | null): void;
|
|
14
|
+
destroy(): void;
|
|
15
|
+
private assertAlive;
|
|
16
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { select, format } from 'd3';
|
|
2
|
+
import { radialScales } from './radialScales.js';
|
|
3
|
+
/** Independent parameter scales. Connect update() to a chart's cursor events. */
|
|
4
|
+
export class SmithScales {
|
|
5
|
+
scales = radialScales();
|
|
6
|
+
container = select(document.createElement('div'))
|
|
7
|
+
.attr('class', 'radial-scales')
|
|
8
|
+
.style('display', 'grid')
|
|
9
|
+
.style('grid-template-columns', 'repeat(auto-fit, minmax(min(100%, 300px), 1fr))')
|
|
10
|
+
.style('gap', '8px 20px');
|
|
11
|
+
destroyed = false;
|
|
12
|
+
start = 8;
|
|
13
|
+
length = 304;
|
|
14
|
+
axes = this.container
|
|
15
|
+
.selectAll('svg')
|
|
16
|
+
.data(this.scales)
|
|
17
|
+
.join('svg')
|
|
18
|
+
.attr('xmlns', 'http://www.w3.org/2000/svg')
|
|
19
|
+
.attr('viewBox', '0 0 320 60')
|
|
20
|
+
.attr('role', 'img')
|
|
21
|
+
.attr('aria-label', (scale) => scale.title)
|
|
22
|
+
.attr('data-scale', (scale) => scale.id)
|
|
23
|
+
.style('display', 'block')
|
|
24
|
+
.style('width', '100%')
|
|
25
|
+
.style('height', 'auto');
|
|
26
|
+
constructor() {
|
|
27
|
+
this.axes
|
|
28
|
+
.append('text')
|
|
29
|
+
.attr('x', this.start)
|
|
30
|
+
.attr('y', 12)
|
|
31
|
+
.attr('font-family', 'system-ui, sans-serif')
|
|
32
|
+
.attr('font-size', 12)
|
|
33
|
+
.attr('fill', '#334155')
|
|
34
|
+
.text((scale) => scale.title);
|
|
35
|
+
this.axes
|
|
36
|
+
.append('text')
|
|
37
|
+
.attr('class', 'scale-value')
|
|
38
|
+
.attr('x', this.start)
|
|
39
|
+
.attr('y', 25)
|
|
40
|
+
.attr('font-family', 'system-ui, sans-serif')
|
|
41
|
+
.attr('font-size', 11)
|
|
42
|
+
.attr('font-weight', 600)
|
|
43
|
+
.attr('fill', '#b42318')
|
|
44
|
+
.text('—');
|
|
45
|
+
this.axes
|
|
46
|
+
.append('line')
|
|
47
|
+
.attr('class', 'scale-axis')
|
|
48
|
+
.attr('x1', this.start)
|
|
49
|
+
.attr('x2', this.start + this.length)
|
|
50
|
+
.attr('y1', 36)
|
|
51
|
+
.attr('y2', 36)
|
|
52
|
+
.attr('stroke', '#64748b')
|
|
53
|
+
.attr('stroke-width', 1);
|
|
54
|
+
const start = this.start;
|
|
55
|
+
const length = this.length;
|
|
56
|
+
this.axes.each(function (scale) {
|
|
57
|
+
const ticks = select(this)
|
|
58
|
+
.selectAll('g')
|
|
59
|
+
.data(scale.values)
|
|
60
|
+
.join('g')
|
|
61
|
+
.attr('transform', (value) => `translate(${start + length * scale.position(value)},36)`);
|
|
62
|
+
ticks.append('line').attr('y2', 5).attr('stroke', '#64748b').attr('stroke-width', 1);
|
|
63
|
+
ticks
|
|
64
|
+
.append('text')
|
|
65
|
+
.attr('y', 17)
|
|
66
|
+
.attr('font-size', 10)
|
|
67
|
+
.attr('font-family', 'system-ui, sans-serif')
|
|
68
|
+
.attr('fill', '#334155')
|
|
69
|
+
.attr('text-anchor', (_, i) => i === 0 ? 'start' : i === scale.values.length - 1 ? 'end' : 'middle')
|
|
70
|
+
.text((value) => (value === Infinity ? '∞' : String(value)));
|
|
71
|
+
});
|
|
72
|
+
this.axes
|
|
73
|
+
.append('circle')
|
|
74
|
+
.attr('class', 'scale-indicator')
|
|
75
|
+
.attr('visibility', 'hidden')
|
|
76
|
+
.attr('cx', this.start)
|
|
77
|
+
.attr('cy', 36)
|
|
78
|
+
.attr('r', 4)
|
|
79
|
+
.attr('fill', '#dc2626')
|
|
80
|
+
.attr('stroke', 'white')
|
|
81
|
+
.attr('stroke-width', 1);
|
|
82
|
+
this.axes.append('title').text((scale) => scale.description ?? scale.title);
|
|
83
|
+
}
|
|
84
|
+
draw(target) {
|
|
85
|
+
this.assertAlive();
|
|
86
|
+
const host = typeof target === 'string' ? document.querySelector(target) : target;
|
|
87
|
+
if (!host) {
|
|
88
|
+
throw new Error('Scale container was not found.');
|
|
89
|
+
}
|
|
90
|
+
host.appendChild(this.container.node());
|
|
91
|
+
}
|
|
92
|
+
/** Show a passive-load reflection coefficient, or hide indicators with null. */
|
|
93
|
+
update(gamma) {
|
|
94
|
+
this.assertAlive();
|
|
95
|
+
const magnitude = gamma === null ? NaN : Math.hypot(gamma.re, gamma.im);
|
|
96
|
+
if (!gamma || !Number.isFinite(magnitude) || magnitude > 1) {
|
|
97
|
+
this.axes.select('circle').attr('visibility', 'hidden').attr('data-position', null);
|
|
98
|
+
this.axes.select('.scale-value').text('—');
|
|
99
|
+
this.axes.attr('aria-label', (scale) => `${scale.title}. ${scale.description ?? ''}`.trim());
|
|
100
|
+
this.axes.select('title').text((scale) => scale.description ?? scale.title);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const start = this.start;
|
|
104
|
+
const length = this.length;
|
|
105
|
+
this.axes.each(function (scale) {
|
|
106
|
+
const value = scale.read(gamma);
|
|
107
|
+
const position = Math.max(0, Math.min(1, scale.position(value)));
|
|
108
|
+
const axis = select(this);
|
|
109
|
+
const number = value === Infinity ? '∞' : format('.4~g')(value);
|
|
110
|
+
const suffix = scale.unit === 'dB' ? ' dB' : scale.id === 'vswr' ? ' : 1' : '';
|
|
111
|
+
const readout = number + suffix;
|
|
112
|
+
const label = `${scale.title}: ${readout}${scale.description ? `. ${scale.description}` : ''}`;
|
|
113
|
+
axis.select('.scale-value').text(readout);
|
|
114
|
+
axis.attr('aria-label', label).select('title').text(label);
|
|
115
|
+
axis
|
|
116
|
+
.select('circle')
|
|
117
|
+
.attr('cx', start + position * length)
|
|
118
|
+
.attr('data-position', position)
|
|
119
|
+
.attr('visibility', null);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
destroy() {
|
|
123
|
+
if (this.destroyed) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
this.destroyed = true;
|
|
127
|
+
this.container.remove();
|
|
128
|
+
}
|
|
129
|
+
assertAlive() {
|
|
130
|
+
if (this.destroyed) {
|
|
131
|
+
throw new Error('These Smith scales have been destroyed. Create a new instance.');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Complex } from '../complex/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
|
+
/** A full reflection-coefficient rotation represents half a wavelength along the line. */
|
|
12
|
+
export declare function peripheralScales(): PeripheralScale[];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
function phase(gamma) {
|
|
2
|
+
return gamma.re === 0 && gamma.im === 0 ? null : (Math.atan2(gamma.im, gamma.re) * 180) / Math.PI;
|
|
3
|
+
}
|
|
4
|
+
/** A full reflection-coefficient rotation represents half a wavelength along the line. */
|
|
5
|
+
export function peripheralScales() {
|
|
6
|
+
const wavelengths = (gamma, direction) => {
|
|
7
|
+
const degrees = phase(gamma);
|
|
8
|
+
return degrees === null ? null : (0.25 + (direction * degrees) / 720 + 0.5) % 0.5;
|
|
9
|
+
};
|
|
10
|
+
return [
|
|
11
|
+
{
|
|
12
|
+
id: 'transmission-phase',
|
|
13
|
+
title: 'Transmission phase · °',
|
|
14
|
+
unit: '°',
|
|
15
|
+
ticks: Array.from({ length: 179 }, (_, i) => -89 + i),
|
|
16
|
+
major: (v) => v % 5 === 0,
|
|
17
|
+
// Project from Γ = −1 to the perimeter, not from the chart center.
|
|
18
|
+
angle: (v) => 2 * v,
|
|
19
|
+
read: (gamma) => phase({ re: 1 + gamma.re, im: gamma.im }),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'reflection-phase',
|
|
23
|
+
title: 'Reflection phase · °',
|
|
24
|
+
unit: '°',
|
|
25
|
+
ticks: Array.from({ length: 180 }, (_, i) => -178 + i * 2),
|
|
26
|
+
major: (v) => v % 10 === 0,
|
|
27
|
+
angle: (v) => v,
|
|
28
|
+
read: phase,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'wavelengths-load',
|
|
32
|
+
title: 'Wavelengths → load',
|
|
33
|
+
unit: 'λ',
|
|
34
|
+
ticks: Array.from({ length: 250 }, (_, i) => i / 500),
|
|
35
|
+
major: (v) => Math.round(v * 500) % 5 === 0,
|
|
36
|
+
angle: (v) => 720 * v - 180,
|
|
37
|
+
read: (gamma) => wavelengths(gamma, 1),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'wavelengths-generator',
|
|
41
|
+
title: 'Wavelengths → generator',
|
|
42
|
+
unit: 'λ',
|
|
43
|
+
ticks: Array.from({ length: 250 }, (_, i) => i / 500),
|
|
44
|
+
major: (v) => Math.round(v * 500) % 5 === 0,
|
|
45
|
+
angle: (v) => 180 - 720 * v,
|
|
46
|
+
read: (gamma) => wavelengths(gamma, -1),
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Complex } from '../complex/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
|
+
/** Normalized positions along each scale, from zero to one. */
|
|
12
|
+
export declare function radialScales(): RadialScale[];
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { SmithConstantCircle } from '../SmithConstantCircle.js';
|
|
2
|
+
/** Normalized positions along each scale, from zero to one. */
|
|
3
|
+
export function radialScales() {
|
|
4
|
+
const calcs = new SmithConstantCircle();
|
|
5
|
+
return [
|
|
6
|
+
{
|
|
7
|
+
id: 'vswr',
|
|
8
|
+
unit: 'ratio',
|
|
9
|
+
read: (gamma) => calcs.rflCoeffToSwr(gamma),
|
|
10
|
+
title: 'VSWR',
|
|
11
|
+
values: [1, 1.5, 2, 3, 5, 10, Infinity],
|
|
12
|
+
position: (v) => (v === Infinity ? 1 : calcs.swrToRflCoeffEOrI(v)),
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: 'vswr-db',
|
|
16
|
+
unit: 'dB',
|
|
17
|
+
read: (gamma) => calcs.rflCoeffToDBS(gamma),
|
|
18
|
+
title: 'Standing-wave ratio · dB',
|
|
19
|
+
values: [0, 3, 6, 10, 15, 20, Infinity],
|
|
20
|
+
position: (v) => (v === Infinity ? 1 : calcs.dBSToAbsRflCoeff(v)),
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'return-loss',
|
|
24
|
+
unit: 'dB',
|
|
25
|
+
read: (gamma) => calcs.rflCoeffToReturnLoss(gamma),
|
|
26
|
+
title: 'Return loss · dB',
|
|
27
|
+
values: [Infinity, 20, 14, 10, 6, 3, 0],
|
|
28
|
+
position: (v) => calcs.returnLossToRflCoeffEOrI(v),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'mismatch-loss',
|
|
32
|
+
unit: 'dB',
|
|
33
|
+
read: (gamma) => calcs.rflCoeffToMismatchLoss(gamma),
|
|
34
|
+
title: 'Reflection loss · dB',
|
|
35
|
+
description: 'Reflection loss (mismatch loss): −10 log10(1 − |Γ|²), in dB.',
|
|
36
|
+
values: [0, 0.1, 0.5, 1, 2, 4, Infinity],
|
|
37
|
+
position: (v) => calcs.mismatchLossToRflCoeffEOrI(v),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'reflected-power',
|
|
41
|
+
unit: 'ratio',
|
|
42
|
+
read: (gamma) => calcs.rflCoeffP(gamma),
|
|
43
|
+
title: 'Power reflection coefficient',
|
|
44
|
+
description: 'Reflected power divided by incident power: |Γ|² (dimensionless).',
|
|
45
|
+
values: [0, 0.01, 0.1, 0.25, 0.5, 0.75, 1],
|
|
46
|
+
position: (v) => calcs.rflCoeffPToEOrI(v),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'transmitted-power',
|
|
50
|
+
unit: 'ratio',
|
|
51
|
+
read: (gamma) => calcs.rflCoeffToTransmCoeffP(gamma),
|
|
52
|
+
title: 'Power transmission coefficient',
|
|
53
|
+
description: 'Net power delivered to the load divided by incident power: 1 − |Γ|² (dimensionless).',
|
|
54
|
+
values: [1, 0.99, 0.9, 0.75, 0.5, 0.25, 0],
|
|
55
|
+
position: (v) => calcs.transmCoeffPToRflCoeffEOrI(v),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'standing-wave-loss',
|
|
59
|
+
unit: 'ratio',
|
|
60
|
+
read: (gamma) => calcs.rflCoeffToSwLossCoeff(gamma),
|
|
61
|
+
title: 'Standing-wave loss coefficient',
|
|
62
|
+
values: [1, 1.1, 1.5, 2, 3, 5, Infinity],
|
|
63
|
+
position: (v) => (v === Infinity ? 1 : calcs.swLossCoeffToRflCoeffEOrI(v)),
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'standing-wave-peak',
|
|
67
|
+
unit: 'ratio',
|
|
68
|
+
read: (gamma) => calcs.rflCoeffToSwPeakConstP(gamma),
|
|
69
|
+
title: 'Standing-wave peak · constant power',
|
|
70
|
+
values: [1, 1.2, 1.5, 2, 3, Infinity],
|
|
71
|
+
position: (v) => (v === Infinity ? 1 : calcs.swPeakConstPToRflCoeffEOrI(v)),
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'reflection-coefficient',
|
|
75
|
+
unit: 'ratio',
|
|
76
|
+
read: (gamma) => calcs.rflCoeffEOrI(gamma),
|
|
77
|
+
title: 'Reflection coefficient · |Γ|',
|
|
78
|
+
values: [0, 0.2, 0.4, 0.6, 0.8, 1],
|
|
79
|
+
position: (v) => v,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'attenuation',
|
|
83
|
+
unit: 'dB',
|
|
84
|
+
read: (gamma) => calcs.rflCoeffToAttenuation(gamma),
|
|
85
|
+
title: 'Attenuation · dB',
|
|
86
|
+
description: 'One-way attenuation of a matched line or attenuator terminated in an open or short circuit. Not insertion loss of an arbitrary load.',
|
|
87
|
+
values: [Infinity, 10, 7, 5, 3, 1, 0],
|
|
88
|
+
position: (v) => calcs.attenuationToRflCoeff(v),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: 'voltage-transmission',
|
|
92
|
+
unit: 'ratio',
|
|
93
|
+
read: (gamma) => calcs.rflCoeffToTransmCoeffEOrI(gamma),
|
|
94
|
+
title: 'Voltage transmission coefficient',
|
|
95
|
+
description: 'Voltage transmission magnitude: |1 + Γ| (dimensionless).',
|
|
96
|
+
values: [0, 0.4, 0.8, 1.2, 1.6, 2],
|
|
97
|
+
position: (v) => v / 2,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: 'current-transmission',
|
|
101
|
+
unit: 'ratio',
|
|
102
|
+
read: (gamma) => calcs.rflCoeffToCurrentTransmission(gamma),
|
|
103
|
+
title: 'Current transmission coefficient',
|
|
104
|
+
description: 'Current transmission magnitude: |1 − Γ|, using the voltage reflection coefficient Γ (dimensionless).',
|
|
105
|
+
values: [0, 0.4, 0.8, 1.2, 1.6, 2],
|
|
106
|
+
position: (v) => v / 2,
|
|
107
|
+
},
|
|
108
|
+
];
|
|
109
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type Point = [number, number];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|