smithkit 0.0.0-stage → 0.1.1
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 +467 -2
- package/dist/lib/Smith.d.ts +103 -0
- package/dist/lib/Smith.js +562 -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 +63 -0
- package/dist/lib/draw/ConstCircles.js +158 -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 +13 -0
- package/dist/lib/scales/SmithPeripheralScales.js +141 -0
- package/dist/lib/scales/SmithScales.d.ts +16 -0
- package/dist/lib/scales/SmithScales.js +142 -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
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { TickDefRequired } from '../arcs/Tick.js';
|
|
2
|
+
import type { SmithTicksShapes } from './types.js';
|
|
3
|
+
import type { Point } from '../shapes/Point.js';
|
|
4
|
+
export type GridKind = 'resistance' | 'reactance' | 'conductance' | 'susceptance';
|
|
5
|
+
export type GridArc = [Point, Point, number, boolean, boolean];
|
|
6
|
+
/** Normalized geometry only: no DOM, D3, or pixel coordinates. */
|
|
7
|
+
export declare function gridGeometry(kind: GridKind, data: SmithTicksShapes): {
|
|
8
|
+
lines: import("../shapes/Line.js").Line[];
|
|
9
|
+
circles: import("../shapes/Circle.js").Circle[];
|
|
10
|
+
arcs: GridArc[];
|
|
11
|
+
};
|
|
12
|
+
export declare function gridLabel(kind: GridKind, definition: TickDefRequired): {
|
|
13
|
+
point: [number, number];
|
|
14
|
+
text: string;
|
|
15
|
+
rotate: number;
|
|
16
|
+
dx: number;
|
|
17
|
+
dy: number;
|
|
18
|
+
textAnchor: string;
|
|
19
|
+
dominantBaseline: string;
|
|
20
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { SmithConstantCircle } from '../SmithConstantCircle.js';
|
|
2
|
+
import { Complex } from '../complex/Complex.js';
|
|
3
|
+
const calcs = new SmithConstantCircle();
|
|
4
|
+
const circles = {
|
|
5
|
+
resistance: (value) => calcs.resistanceCircle(value),
|
|
6
|
+
reactance: (value) => calcs.reactanceCircle(value),
|
|
7
|
+
conductance: (value) => calcs.conductanceCircle(value),
|
|
8
|
+
susceptance: (value) => calcs.susceptanceCircle(value),
|
|
9
|
+
};
|
|
10
|
+
const clipping = {
|
|
11
|
+
resistance: 'reactance',
|
|
12
|
+
reactance: 'resistance',
|
|
13
|
+
conductance: 'susceptance',
|
|
14
|
+
susceptance: 'conductance',
|
|
15
|
+
};
|
|
16
|
+
/** Normalized geometry only: no DOM, D3, or pixel coordinates. */
|
|
17
|
+
export function gridGeometry(kind, data) {
|
|
18
|
+
const circle = circles[kind];
|
|
19
|
+
const clip = circles[clipping[kind]];
|
|
20
|
+
return {
|
|
21
|
+
lines: data.lines,
|
|
22
|
+
circles: data.circles.map(circle),
|
|
23
|
+
arcs: data.arcs.map(([value, limits, options]) => {
|
|
24
|
+
const c = circle(value);
|
|
25
|
+
const [a, b] = limits.map(([limit, index]) => calcs.circleCircleIntersection(c, clip(limit))[index]);
|
|
26
|
+
return [a, b, c.r, options[0], options[1]];
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function gridLabel(kind, definition) {
|
|
31
|
+
const { point, transform, dp, textAnchor, dominantBaseline } = definition;
|
|
32
|
+
const admittance = kind === 'conductance' || kind === 'susceptance';
|
|
33
|
+
const real = kind === 'resistance' || kind === 'conductance';
|
|
34
|
+
const rc = admittance
|
|
35
|
+
? calcs.admittanceToRflCoeff(Complex.from(point.r, point.i))
|
|
36
|
+
: calcs.impedanceToRflCoeff(Complex.from(point.r, point.i));
|
|
37
|
+
if (!rc) {
|
|
38
|
+
throw new Error('Invalid grid label coordinates.');
|
|
39
|
+
}
|
|
40
|
+
const value = real ? point.r : point.i;
|
|
41
|
+
const circle = circles[kind](value);
|
|
42
|
+
let rotate = -calcs.tangentToCircleAngle(circle, rc.toVector()) + transform.rotate;
|
|
43
|
+
if (admittance && rotate !== 90) {
|
|
44
|
+
rotate += 180;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
point: rc.toVector(),
|
|
48
|
+
text: value.toFixed(dp),
|
|
49
|
+
rotate,
|
|
50
|
+
dx: transform.dx,
|
|
51
|
+
dy: transform.dy,
|
|
52
|
+
textAnchor,
|
|
53
|
+
dominantBaseline,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Tick } from '../arcs/Tick.js';
|
|
2
|
+
const values = [
|
|
3
|
+
0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.2, 1.4, 1.6, 1.8, 2, 3, 4, 5, 10, 20, 50,
|
|
4
|
+
];
|
|
5
|
+
const interiorValues = [0.2, 0.4, 0.6, 0.8, 1];
|
|
6
|
+
export function resistanceLabels() {
|
|
7
|
+
const axis = [0, ...values].map((r) => new Tick({
|
|
8
|
+
point: { r, i: 0 },
|
|
9
|
+
dp: r === 0 || r >= 10 ? 0 : 1,
|
|
10
|
+
transform: { dx: 0.005, dy: r === 0 ? 0.005 : -0.005 },
|
|
11
|
+
dominantBaseline: r === 0 ? 'hanging' : 'baseline',
|
|
12
|
+
}));
|
|
13
|
+
const interior = [1, -1].flatMap((i) => [...interiorValues].reverse().map((r) => new Tick({
|
|
14
|
+
point: { r, i },
|
|
15
|
+
dp: 1,
|
|
16
|
+
transform: { dx: i * 0.005, dy: -0.005, rotate: i < 0 ? 180 : 0 },
|
|
17
|
+
textAnchor: i < 0 ? 'end' : 'start',
|
|
18
|
+
})));
|
|
19
|
+
return [...axis, ...interior];
|
|
20
|
+
}
|
|
21
|
+
export function reactanceLabels() {
|
|
22
|
+
return [1, -1].flatMap((sign) => [
|
|
23
|
+
...interiorValues.map((i) => ({ r: 1, i: sign * i })),
|
|
24
|
+
...values.map((i) => ({ r: 0, i: sign * i })),
|
|
25
|
+
].map((point) => new Tick({
|
|
26
|
+
point,
|
|
27
|
+
dp: Math.abs(point.i) >= 10 ? 0 : 1,
|
|
28
|
+
transform: {
|
|
29
|
+
dx: -sign * 0.005,
|
|
30
|
+
dy: -0.005,
|
|
31
|
+
rotate: sign > 0
|
|
32
|
+
? point.r === 1 || point.i < 1
|
|
33
|
+
? 180
|
|
34
|
+
: 0
|
|
35
|
+
: point.r === 0 && point.i < -1
|
|
36
|
+
? 180
|
|
37
|
+
: 0,
|
|
38
|
+
},
|
|
39
|
+
textAnchor: sign > 0 ? 'end' : 'start',
|
|
40
|
+
})));
|
|
41
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Line } from '../shapes/Line.js';
|
|
2
|
+
export declare enum SmithArcEntry {
|
|
3
|
+
circle = 0,
|
|
4
|
+
clipCircles = 1,
|
|
5
|
+
arcOptions = 2
|
|
6
|
+
}
|
|
7
|
+
export type SmithArcDef = [number, [number, number][], [boolean, boolean]];
|
|
8
|
+
export type SmithTickDef = [
|
|
9
|
+
number,
|
|
10
|
+
number,
|
|
11
|
+
{
|
|
12
|
+
dx?: string;
|
|
13
|
+
dy?: string;
|
|
14
|
+
}
|
|
15
|
+
];
|
|
16
|
+
export interface SmithTicksShapes {
|
|
17
|
+
lines: Line[];
|
|
18
|
+
circles: number[];
|
|
19
|
+
arcs: SmithArcDef[];
|
|
20
|
+
}
|
|
21
|
+
export interface SmithTicksData {
|
|
22
|
+
resistance: {
|
|
23
|
+
major: SmithTicksShapes;
|
|
24
|
+
minor: SmithTicksShapes;
|
|
25
|
+
};
|
|
26
|
+
reactance: {
|
|
27
|
+
major: SmithTicksShapes;
|
|
28
|
+
minor: SmithTicksShapes;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export var SmithArcEntry;
|
|
2
|
+
(function (SmithArcEntry) {
|
|
3
|
+
SmithArcEntry[SmithArcEntry["circle"] = 0] = "circle";
|
|
4
|
+
SmithArcEntry[SmithArcEntry["clipCircles"] = 1] = "clipCircles";
|
|
5
|
+
SmithArcEntry[SmithArcEntry["arcOptions"] = 2] = "arcOptions";
|
|
6
|
+
})(SmithArcEntry || (SmithArcEntry = {}));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { Smith, SmithEventType } from './Smith.js';
|
|
2
|
+
export type { SmithEvent } from './Smith.js';
|
|
3
|
+
export { Complex } from './complex/Complex.js';
|
|
4
|
+
export type { TraceSamples, TraceSample } from './samples.js';
|
|
5
|
+
export { parseTouchstone } from './io/touchstone.js';
|
|
6
|
+
export type { TouchstoneData } from './io/touchstone.js';
|
|
7
|
+
export { SmithScales } from './scales/SmithScales.js';
|
|
8
|
+
export { compareMarkerReadings } from './measurements.js';
|
|
9
|
+
export type { TraceOptions, TraceUpdateOptions, MarkerSelectionStrategy, TraceStyle, TraceRenderMode, TraceInfo, MarkerInfo, MarkerSnapshot, MarkerComparison, } from './measurements.js';
|
|
10
|
+
export { readReflection, reflectionToImpedance, impedanceToReflection, reflectionToAdmittance, admittanceToReflection, } from './rf.js';
|
|
11
|
+
export type { SmithReading } from './rf.js';
|
|
12
|
+
export type { GridStyle, CircleStyle, GridLayer, CircleLayer, ChartLayers, PeripheralScales, } from './layers.js';
|
|
13
|
+
export { renormalizeReflection, renormalizeSamples } from './renormalization.js';
|
|
14
|
+
export { formatNumber, formatComplex, formatComplexPolar } from './formatting.js';
|
|
15
|
+
export { reactanceToComponent } from './components.js';
|
|
16
|
+
export type { ReactiveComponent } from './components.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { Smith, SmithEventType } from './Smith.js';
|
|
2
|
+
export { Complex } from './complex/Complex.js';
|
|
3
|
+
export { parseTouchstone } from './io/touchstone.js';
|
|
4
|
+
export { SmithScales } from './scales/SmithScales.js';
|
|
5
|
+
export { compareMarkerReadings } from './measurements.js';
|
|
6
|
+
export { readReflection, reflectionToImpedance, impedanceToReflection, reflectionToAdmittance, admittanceToReflection, } from './rf.js';
|
|
7
|
+
export { renormalizeReflection, renormalizeSamples } from './renormalization.js';
|
|
8
|
+
export { formatNumber, formatComplex, formatComplexPolar } from './formatting.js';
|
|
9
|
+
export { reactanceToComponent } from './components.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TraceSamples } from '../samples.js';
|
|
2
|
+
export interface TouchstoneData {
|
|
3
|
+
samples: TraceSamples;
|
|
4
|
+
referenceImpedanceOhms: number;
|
|
5
|
+
}
|
|
6
|
+
/** Read one-port Touchstone 1.x data; frequencies in the result are always Hz. */
|
|
7
|
+
export declare function parseTouchstone(text: string): TouchstoneData;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Read one-port Touchstone 1.x data; frequencies in the result are always Hz. */
|
|
2
|
+
export function parseTouchstone(text) {
|
|
3
|
+
const units = { HZ: 1, KHZ: 1e3, MHZ: 1e6, GHZ: 1e9 };
|
|
4
|
+
let frequencyScale = 1e9;
|
|
5
|
+
let format = 'MA';
|
|
6
|
+
let referenceImpedanceOhms = 50;
|
|
7
|
+
let optionsSeen = false;
|
|
8
|
+
const samples = [];
|
|
9
|
+
for (const [index, raw] of text
|
|
10
|
+
.replace(/^\uFEFF/, '')
|
|
11
|
+
.split(/\r?\n/)
|
|
12
|
+
.entries()) {
|
|
13
|
+
const line = raw.split('!')[0].trim();
|
|
14
|
+
if (!line) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const fail = (message) => {
|
|
18
|
+
throw new Error(`Line ${index + 1}: ${message}`);
|
|
19
|
+
};
|
|
20
|
+
if (line.startsWith('[')) {
|
|
21
|
+
fail('Only Touchstone 1.x one-port files (.s1p) are supported.');
|
|
22
|
+
}
|
|
23
|
+
if (line.startsWith('#')) {
|
|
24
|
+
if (optionsSeen || samples.length) {
|
|
25
|
+
fail('The option line must appear once, before the data.');
|
|
26
|
+
}
|
|
27
|
+
optionsSeen = true;
|
|
28
|
+
const options = line.slice(1).trim().toUpperCase().split(/\s+/).filter(Boolean);
|
|
29
|
+
if (!options.length) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const [unit = 'GHZ', parameter = 'S', representation = 'MA', r = 'R', impedance = '50'] = options;
|
|
33
|
+
if (!(unit in units) ||
|
|
34
|
+
parameter !== 'S' ||
|
|
35
|
+
!['RI', 'MA', 'DB'].includes(representation) ||
|
|
36
|
+
r !== 'R' ||
|
|
37
|
+
options.length > 5) {
|
|
38
|
+
fail('Expected: # Hz|kHz|MHz|GHz S RI|MA|DB R impedance.');
|
|
39
|
+
}
|
|
40
|
+
frequencyScale = units[unit];
|
|
41
|
+
format = representation;
|
|
42
|
+
referenceImpedanceOhms = Number(impedance);
|
|
43
|
+
if (!Number.isFinite(referenceImpedanceOhms) || referenceImpedanceOhms <= 0) {
|
|
44
|
+
fail('Reference impedance must be positive.');
|
|
45
|
+
}
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const fields = line.split(/\s+/).map(Number);
|
|
49
|
+
if (fields.length !== 3 || !fields.every(Number.isFinite)) {
|
|
50
|
+
fail('Expected frequency and two finite S11 components.');
|
|
51
|
+
}
|
|
52
|
+
const [frequency, a, b] = fields;
|
|
53
|
+
const frequencyHz = frequency * frequencyScale;
|
|
54
|
+
if (frequencyHz < 0 || !Number.isFinite(frequencyHz)) {
|
|
55
|
+
fail('Frequency must be finite and non-negative.');
|
|
56
|
+
}
|
|
57
|
+
if (format === 'MA' && a < 0) {
|
|
58
|
+
fail('Magnitude must be non-negative.');
|
|
59
|
+
}
|
|
60
|
+
const magnitude = format === 'DB' ? 10 ** (a / 20) : a;
|
|
61
|
+
const radians = (b * Math.PI) / 180;
|
|
62
|
+
const reflectionCoefficient = format === 'RI' ? [a, b] : [magnitude * Math.cos(radians), magnitude * Math.sin(radians)];
|
|
63
|
+
if (!reflectionCoefficient.every(Number.isFinite)) {
|
|
64
|
+
fail('S11 is outside the supported numeric range.');
|
|
65
|
+
}
|
|
66
|
+
samples.push({ frequencyHz, reflectionCoefficient });
|
|
67
|
+
}
|
|
68
|
+
if (!samples.length) {
|
|
69
|
+
throw new Error('The file contains no S11 samples.');
|
|
70
|
+
}
|
|
71
|
+
return { samples, referenceImpedanceOhms };
|
|
72
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ConstCircles } from './draw/ConstCircles.js';
|
|
2
|
+
import type { ConstQCircles } from './draw/ConstQCircles.js';
|
|
3
|
+
import type { ConstSwrCircles } from './draw/ConstSwrCircles.js';
|
|
4
|
+
import type { Complex } from './complex/Complex.js';
|
|
5
|
+
export interface GridStyle {
|
|
6
|
+
stroke: string;
|
|
7
|
+
/** Non-scaling stroke width in CSS pixels. */
|
|
8
|
+
majorWidth: number;
|
|
9
|
+
/** Non-scaling stroke width in CSS pixels. */
|
|
10
|
+
minorWidth: number;
|
|
11
|
+
textColor: string;
|
|
12
|
+
textFontFamily: string;
|
|
13
|
+
/** Font size in SVG chart units; labels scale with chart geometry. */
|
|
14
|
+
textFontSize: number;
|
|
15
|
+
}
|
|
16
|
+
export interface CircleStyle {
|
|
17
|
+
stroke: string;
|
|
18
|
+
/** Non-scaling stroke width in CSS pixels. */
|
|
19
|
+
strokeWidth: number;
|
|
20
|
+
}
|
|
21
|
+
/** Grid styling uses SVG user units, except non-scaling stroke widths in screen pixels. */
|
|
22
|
+
export interface GridLayer {
|
|
23
|
+
setVisible(visible: boolean): void;
|
|
24
|
+
setMinorVisible(visible: boolean): void;
|
|
25
|
+
setStyle(style: Partial<GridStyle>): void;
|
|
26
|
+
}
|
|
27
|
+
export interface CircleLayer {
|
|
28
|
+
setVisible(visible: boolean): void;
|
|
29
|
+
setStyle(style: Partial<CircleStyle>): void;
|
|
30
|
+
addValue(value: number): void;
|
|
31
|
+
removeValue(value: number): void;
|
|
32
|
+
}
|
|
33
|
+
export interface ChartLayers {
|
|
34
|
+
readonly resistance: GridLayer;
|
|
35
|
+
readonly reactance: GridLayer;
|
|
36
|
+
readonly conductance: GridLayer;
|
|
37
|
+
readonly susceptance: GridLayer;
|
|
38
|
+
readonly q: CircleLayer;
|
|
39
|
+
readonly vswr: CircleLayer;
|
|
40
|
+
}
|
|
41
|
+
export interface PeripheralScales {
|
|
42
|
+
setVisible(visible: boolean): void;
|
|
43
|
+
/** Pass null to clear the indicators. */
|
|
44
|
+
update(reflectionCoefficient: Complex | null): void;
|
|
45
|
+
}
|
|
46
|
+
/** Keep renderer instances out of the public chart API. */
|
|
47
|
+
export declare function gridLayer(layer: ConstCircles, assertAlive: () => void): GridLayer;
|
|
48
|
+
export declare function circleLayer(layer: ConstQCircles | ConstSwrCircles, minimum: number, assertAlive: () => void): CircleLayer;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Keep renderer instances out of the public chart API. */
|
|
2
|
+
export function gridLayer(layer, assertAlive) {
|
|
3
|
+
return {
|
|
4
|
+
setVisible(visible) {
|
|
5
|
+
assertAlive();
|
|
6
|
+
layer.visibility(visible);
|
|
7
|
+
},
|
|
8
|
+
setMinorVisible(visible) {
|
|
9
|
+
assertAlive();
|
|
10
|
+
layer.displayMinor(visible);
|
|
11
|
+
},
|
|
12
|
+
setStyle(style) {
|
|
13
|
+
assertAlive();
|
|
14
|
+
validateLengths(style, ['majorWidth', 'minorWidth', 'textFontSize']);
|
|
15
|
+
const properties = {
|
|
16
|
+
stroke: 'Stroke',
|
|
17
|
+
majorWidth: 'MajorWidth',
|
|
18
|
+
minorWidth: 'MinorWidth',
|
|
19
|
+
textColor: 'TextColor',
|
|
20
|
+
textFontFamily: 'TextFontFamily',
|
|
21
|
+
textFontSize: 'TextFontSize',
|
|
22
|
+
};
|
|
23
|
+
for (const key of Object.keys(properties)) {
|
|
24
|
+
const value = style[key];
|
|
25
|
+
if (value !== undefined) {
|
|
26
|
+
layer[properties[key]] = String(value);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function circleLayer(layer, minimum, assertAlive) {
|
|
33
|
+
const validate = (value) => {
|
|
34
|
+
if (!Number.isFinite(value) || value < minimum || value <= 0) {
|
|
35
|
+
throw new RangeError(`Circle value must be finite, positive, and at least ${minimum}.`);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
setVisible(visible) {
|
|
40
|
+
assertAlive();
|
|
41
|
+
layer.visibility(visible);
|
|
42
|
+
},
|
|
43
|
+
setStyle(style) {
|
|
44
|
+
assertAlive();
|
|
45
|
+
validateLengths(style, ['strokeWidth']);
|
|
46
|
+
if (style.stroke !== undefined) {
|
|
47
|
+
layer.Stroke = style.stroke;
|
|
48
|
+
}
|
|
49
|
+
if (style.strokeWidth !== undefined) {
|
|
50
|
+
layer.StrokeWidth = String(style.strokeWidth);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
addValue(value) {
|
|
54
|
+
assertAlive();
|
|
55
|
+
validate(value);
|
|
56
|
+
layer.append(value);
|
|
57
|
+
},
|
|
58
|
+
removeValue(value) {
|
|
59
|
+
assertAlive();
|
|
60
|
+
validate(value);
|
|
61
|
+
layer.remove(value);
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function validateLengths(style, keys) {
|
|
66
|
+
for (const key of keys) {
|
|
67
|
+
const value = style[key];
|
|
68
|
+
if (value !== undefined &&
|
|
69
|
+
(typeof value !== 'number' || !Number.isFinite(value) || value <= 0)) {
|
|
70
|
+
throw new RangeError(`${String(key)} must be a positive finite number.`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Complex } from '../complex/Complex.js';
|
|
2
|
+
/** Finite complex quotient without squaring the denominator or overflowing its sum. */
|
|
3
|
+
export function complexRatio(nr, ni, dr, di) {
|
|
4
|
+
const scale = Math.max(Math.abs(nr), Math.abs(ni), Math.abs(dr), Math.abs(di));
|
|
5
|
+
if (!Number.isFinite(scale) || (dr === 0 && di === 0)) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
nr /= scale;
|
|
9
|
+
ni /= scale;
|
|
10
|
+
dr /= scale;
|
|
11
|
+
di /= scale;
|
|
12
|
+
let re;
|
|
13
|
+
let im;
|
|
14
|
+
if (Math.abs(dr) >= Math.abs(di)) {
|
|
15
|
+
const ratio = di / dr;
|
|
16
|
+
const denominator = dr + di * ratio;
|
|
17
|
+
re = (nr + ni * ratio) / denominator;
|
|
18
|
+
im = (ni - nr * ratio) / denominator;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
const ratio = dr / di;
|
|
22
|
+
const denominator = di + dr * ratio;
|
|
23
|
+
re = (nr * ratio + ni) / denominator;
|
|
24
|
+
im = (ni * ratio - nr) / denominator;
|
|
25
|
+
}
|
|
26
|
+
if (!Number.isFinite(re) || !Number.isFinite(im)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
return Complex.from(re, im);
|
|
30
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Complex } from './complex/Complex.js';
|
|
2
|
+
import type { SmithReading } from './rf.js';
|
|
3
|
+
export type TraceRenderMode = 'points' | 'line' | 'both';
|
|
4
|
+
export interface TraceStyle {
|
|
5
|
+
mode: TraceRenderMode;
|
|
6
|
+
/** Non-scaling SVG stroke width in CSS pixels. Default: 2. */
|
|
7
|
+
lineWidth: number;
|
|
8
|
+
/** Point radius in CSS pixels; remains constant under chart zoom and container resize. Default: 2. */
|
|
9
|
+
pointRadius: number;
|
|
10
|
+
}
|
|
11
|
+
export interface TraceOptions extends Partial<TraceStyle> {
|
|
12
|
+
name?: string;
|
|
13
|
+
/** A solid CSS color, such as '#2563eb' or 'orange'. */
|
|
14
|
+
color?: string;
|
|
15
|
+
visible?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** How markers select samples when a trace is replaced. */
|
|
18
|
+
export type MarkerSelectionStrategy = 'frequency' | 'sample-index' | 'reflection';
|
|
19
|
+
export interface TraceUpdateOptions {
|
|
20
|
+
/** Default: frequency. Ties select the earliest input sample; indices clamp to the new last sample. */
|
|
21
|
+
markerSelection?: MarkerSelectionStrategy;
|
|
22
|
+
}
|
|
23
|
+
export interface MarkerInfo {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
/** Stable display number within the trace; removed numbers are not reused. */
|
|
26
|
+
readonly number: number;
|
|
27
|
+
readonly sampleIndex: number;
|
|
28
|
+
}
|
|
29
|
+
export interface TraceInfo extends Readonly<TraceStyle> {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly color: string;
|
|
33
|
+
readonly visible: boolean;
|
|
34
|
+
readonly sampleCount: number;
|
|
35
|
+
readonly markers: readonly MarkerInfo[];
|
|
36
|
+
}
|
|
37
|
+
/** A detached reading identified by stable, chart-local trace and marker IDs. */
|
|
38
|
+
export interface MarkerSnapshot extends SmithReading {
|
|
39
|
+
readonly frequencyHz: number;
|
|
40
|
+
readonly traceId: string;
|
|
41
|
+
readonly markerId: string;
|
|
42
|
+
readonly markerNumber: number;
|
|
43
|
+
readonly sampleIndex: number;
|
|
44
|
+
}
|
|
45
|
+
export interface MarkerComparison {
|
|
46
|
+
readonly frequencyDeltaHz: number;
|
|
47
|
+
/** Undefined if either impedance is singular or non-finite. */
|
|
48
|
+
readonly impedanceDeltaOhms: Complex | undefined;
|
|
49
|
+
/** Shortest signed B − A phase difference in [−180, 180); undefined at Γ = 0. */
|
|
50
|
+
readonly phaseDeltaDegrees: number | undefined;
|
|
51
|
+
}
|
|
52
|
+
/** Compare B against A without a chart or browser. Frequencies must be finite and non-negative. */
|
|
53
|
+
export declare function compareMarkerReadings(a: Pick<MarkerSnapshot, 'frequencyHz' | 'impedanceOhms' | 'reflectionCoefficient'>, b: Pick<MarkerSnapshot, 'frequencyHz' | 'impedanceOhms' | 'reflectionCoefficient'>): MarkerComparison;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Compare B against A without a chart or browser. Frequencies must be finite and non-negative. */
|
|
2
|
+
export function compareMarkerReadings(a, b) {
|
|
3
|
+
if (![a.frequencyHz, b.frequencyHz].every((frequencyHz) => Number.isFinite(frequencyHz) && frequencyHz >= 0)) {
|
|
4
|
+
throw new RangeError('Marker frequencies must be finite and non-negative.');
|
|
5
|
+
}
|
|
6
|
+
const isFinite = (value) => Number.isFinite(value.re) && Number.isFinite(value.im);
|
|
7
|
+
const hasPhase = (gamma) => isFinite(gamma) && (gamma.re !== 0 || gamma.im !== 0);
|
|
8
|
+
const phaseDelta = hasPhase(a.reflectionCoefficient) && hasPhase(b.reflectionCoefficient)
|
|
9
|
+
? ((b.reflectionCoefficient.arg() - a.reflectionCoefficient.arg()) * 180) / Math.PI
|
|
10
|
+
: undefined;
|
|
11
|
+
const impedanceDelta = a.impedanceOhms && b.impedanceOhms && isFinite(a.impedanceOhms) && isFinite(b.impedanceOhms)
|
|
12
|
+
? b.impedanceOhms.sub(a.impedanceOhms)
|
|
13
|
+
: undefined;
|
|
14
|
+
return {
|
|
15
|
+
frequencyDeltaHz: b.frequencyHz - a.frequencyHz,
|
|
16
|
+
impedanceDeltaOhms: impedanceDelta && isFinite(impedanceDelta) ? impedanceDelta : undefined,
|
|
17
|
+
phaseDeltaDegrees: phaseDelta === undefined ? undefined : ((phaseDelta + 540) % 360) - 180,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Complex } from './complex/Complex.js';
|
|
2
|
+
import type { TraceSamples } from './samples.js';
|
|
3
|
+
/**
|
|
4
|
+
* Change the positive real reference impedance of a voltage reflection coefficient.
|
|
5
|
+
* Preserves physical impedance, including exact open/short limits. A singular or
|
|
6
|
+
* unrepresentable result is undefined; non-finite inputs throw RangeError.
|
|
7
|
+
*/
|
|
8
|
+
export declare function renormalizeReflection(gamma: Complex, fromOhms: number, toOhms: number): Complex | undefined;
|
|
9
|
+
/** Copy and renormalize every sample; reject invalid/singular data without modifying the input. */
|
|
10
|
+
export declare function renormalizeSamples(samples: TraceSamples, fromOhms: number, toOhms: number): TraceSamples;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Complex } from './complex/Complex.js';
|
|
2
|
+
function validateReference(value) {
|
|
3
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
4
|
+
throw new RangeError('Reference impedance must be positive and finite.');
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Change the positive real reference impedance of a voltage reflection coefficient.
|
|
9
|
+
* Preserves physical impedance, including exact open/short limits. A singular or
|
|
10
|
+
* unrepresentable result is undefined; non-finite inputs throw RangeError.
|
|
11
|
+
*/
|
|
12
|
+
export function renormalizeReflection(gamma, fromOhms, toOhms) {
|
|
13
|
+
validateReference(fromOhms);
|
|
14
|
+
validateReference(toOhms);
|
|
15
|
+
if (!Number.isFinite(gamma.re) || !Number.isFinite(gamma.im)) {
|
|
16
|
+
throw new RangeError('Reflection coefficient components must be finite.');
|
|
17
|
+
}
|
|
18
|
+
if (fromOhms === toOhms || (gamma.im === 0 && Math.abs(gamma.re) === 1)) {
|
|
19
|
+
return gamma;
|
|
20
|
+
}
|
|
21
|
+
// Γnew = (Γold + k) / (1 + k Γold). Scale references before summing.
|
|
22
|
+
const referenceScale = Math.max(fromOhms, toOhms);
|
|
23
|
+
const from = fromOhms / referenceScale;
|
|
24
|
+
const to = toOhms / referenceScale;
|
|
25
|
+
const k = (fromOhms - toOhms) / referenceScale / (from + to);
|
|
26
|
+
const scale = Math.max(1, Math.abs(gamma.re), Math.abs(gamma.im));
|
|
27
|
+
const x = gamma.re / scale;
|
|
28
|
+
const y = gamma.im / scale;
|
|
29
|
+
const inverseScale = 1 / scale;
|
|
30
|
+
const nr = x + k * inverseScale;
|
|
31
|
+
const dr = inverseScale + k * x;
|
|
32
|
+
const di = k * y;
|
|
33
|
+
const denominatorScale = Math.max(Math.abs(dr), Math.abs(di));
|
|
34
|
+
if (denominatorScale === 0) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const real = dr / denominatorScale;
|
|
38
|
+
const imaginary = di / denominatorScale;
|
|
39
|
+
const denominator = real ** 2 + imaginary ** 2;
|
|
40
|
+
const re = ((nr / denominatorScale) * real + (y / denominatorScale) * imaginary) / denominator;
|
|
41
|
+
// Analytic imaginary numerator avoids cancellation for very large finite Γ.
|
|
42
|
+
const im = (((1 - k) * (1 + k) * (y / denominatorScale)) / denominator) *
|
|
43
|
+
(inverseScale / denominatorScale);
|
|
44
|
+
return Number.isFinite(re) && Number.isFinite(im) ? Complex.from(re, im) : undefined;
|
|
45
|
+
}
|
|
46
|
+
/** Copy and renormalize every sample; reject invalid/singular data without modifying the input. */
|
|
47
|
+
export function renormalizeSamples(samples, fromOhms, toOhms) {
|
|
48
|
+
validateReference(fromOhms);
|
|
49
|
+
validateReference(toOhms);
|
|
50
|
+
return samples.map((sample, index) => {
|
|
51
|
+
if (!Number.isFinite(sample.frequencyHz) ||
|
|
52
|
+
sample.frequencyHz < 0 ||
|
|
53
|
+
sample.reflectionCoefficient.length !== 2) {
|
|
54
|
+
throw new RangeError(`Invalid sample at index ${index}.`);
|
|
55
|
+
}
|
|
56
|
+
const gamma = renormalizeReflection(Complex.from(...sample.reflectionCoefficient), fromOhms, toOhms);
|
|
57
|
+
if (!gamma) {
|
|
58
|
+
throw new RangeError(`Renormalization is singular or outside the numeric range at sample ${index}.`);
|
|
59
|
+
}
|
|
60
|
+
return { frequencyHz: sample.frequencyHz, reflectionCoefficient: gamma.toVector() };
|
|
61
|
+
});
|
|
62
|
+
}
|
package/dist/lib/rf.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Complex } from './complex/Complex.js';
|
|
2
|
+
/** Physical quantities derived from a voltage reflection coefficient Γ. */
|
|
3
|
+
export interface SmithReading {
|
|
4
|
+
readonly reflectionCoefficient: Complex;
|
|
5
|
+
readonly impedanceOhms: Complex | undefined;
|
|
6
|
+
readonly admittanceSiemens: Complex | undefined;
|
|
7
|
+
readonly phaseDegrees: number | undefined;
|
|
8
|
+
readonly vswr: number | undefined;
|
|
9
|
+
readonly standingWaveRatioDb: number | undefined;
|
|
10
|
+
readonly returnLossDb: number;
|
|
11
|
+
readonly reflectionLossDb: number | undefined;
|
|
12
|
+
readonly attenuationDb: number | undefined;
|
|
13
|
+
readonly q: number | undefined;
|
|
14
|
+
readonly powerReflectionCoefficient: number;
|
|
15
|
+
readonly voltageReflectionCoefficientMagnitude: number;
|
|
16
|
+
readonly powerTransmissionCoefficient: number | undefined;
|
|
17
|
+
readonly voltageTransmissionCoefficientMagnitude: number;
|
|
18
|
+
readonly currentTransmissionCoefficientMagnitude: number;
|
|
19
|
+
readonly standingWavePeak: number | undefined;
|
|
20
|
+
readonly standingWaveLossCoefficient: number | undefined;
|
|
21
|
+
}
|
|
22
|
+
/** Convert Γ to physical impedance in ohms; undefined at the open circuit Γ = 1. */
|
|
23
|
+
export declare function reflectionToImpedance(gamma: Complex, referenceImpedanceOhms?: number): Complex | undefined;
|
|
24
|
+
/** Convert physical impedance in ohms to Γ. Undefined for a singular result. */
|
|
25
|
+
export declare function impedanceToReflection(impedanceOhms: Complex, referenceImpedanceOhms?: number): Complex | undefined;
|
|
26
|
+
/** Convert Γ to physical admittance in siemens; undefined at the short circuit Γ = −1. */
|
|
27
|
+
export declare function reflectionToAdmittance(gamma: Complex, referenceImpedanceOhms?: number): Complex | undefined;
|
|
28
|
+
/** Convert physical admittance in siemens to Γ. Undefined for a singular result. */
|
|
29
|
+
export declare function admittanceToReflection(admittanceSiemens: Complex, referenceImpedanceOhms?: number): Complex | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Read Γ without a chart or DOM. Undefined means a singular or inapplicable quantity.
|
|
32
|
+
* Passive-load quantities are undefined outside |Γ| ≤ 1; exact infinite limits are retained.
|
|
33
|
+
* Attenuation assumes a fully reflecting termination and is half the return loss.
|
|
34
|
+
*/
|
|
35
|
+
export declare function readReflection(gamma: Complex, referenceImpedanceOhms?: number): SmithReading;
|