Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Unreleased

- Simplify the Touchstone integration example to use the file reference for rendering and export. Remove its reference selector and renormalization, and clarify the basic import flow in the guide. The public API is unchanged.

## 0.7.0 — 2026-10-14

- **Breaking:** group trace style options and snapshots into `line: { width, tolerancePx }` and `point: { radius }`. Add `TraceStyleOptions` for nested partial patches, reject unknown keys, and return detached readonly style snapshots.
Expand Down
37 changes: 19 additions & 18 deletions docs/guide/touchstone.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ group: Guides

Reference: {@link Touchstone}, {@link TouchstoneParseOptions}, {@link TouchstoneWriteOptions}.

## Touchstone import and export
## Import using the file reference

`Touchstone.parse(text, options?)` returns `{ samples, referenceImpedanceOhms }`.
It reads Touchstone 1.x S-parameters, converts RI/MA/DB into Cartesian Γ and
Expand All @@ -30,13 +30,16 @@ async function showMeasurement(file: File): Promise<Smith> {
parameter: ports === 2 ? 'S22' : 'S11',
output: 'packed',
});
const chart = new Smith({ referenceImpedanceOhms });
chart.draw('#smith');
const chart = new Smith('#smith', { referenceImpedanceOhms });
chart.addTrace(samples, { name: file.name });
return chart;
}
```

**The file already specifies its reference impedance.** Use the returned
`referenceImpedanceOhms` to configure the chart, as above. No renormalization is
needed.

Packed parsing writes directly into growing typed storage, without creating an
object or coordinate array per sample. It scans lines incrementally within the
supplied string; this is not a streaming file API. The returned buffer has exact
Expand All @@ -51,6 +54,8 @@ per-port/complex reference impedances, noise blocks and Touchstone 2.x keywords
are rejected. See the [IBIS Touchstone specification](https://www.ibis.org/touchstone_ver2.1/touchstone_ver2_1.pdf)
for the legacy 1.x data layout.

## Export a one-port file

`Touchstone.stringify(samples, { referenceImpedanceOhms })` returns `.s1p` text
with a `# Hz S RI R …` header and a final newline. It accepts object samples,
tuples, or packed triples, writes numeric values without a precision limit, and
Expand All @@ -60,25 +65,21 @@ reference impedance, and duplicate or descending frequencies throw `RangeError`.
Samples are never silently sorted or discarded.

```ts
import { RfCalculations, Touchstone } from 'smithkit';

const sourceText = '# GHz S RI R 50\n1 0 0\n2 0.5 0';
const parsed = Touchstone.parse(sourceText, { output: 'packed' });
const samples = RfCalculations.renormalizeSamples(
parsed.samples,
parsed.referenceImpedanceOhms,
75,
);
const text = Touchstone.stringify(samples, { referenceImpedanceOhms: 75 });
import { Touchstone } from 'smithkit';

const sourceText = '# GHz S RI R 75\n1 0.2 -0.3\n2 0.2 0.3';
const { samples, referenceImpedanceOhms } = Touchstone.parse(sourceText);
const text = Touchstone.stringify(samples, { referenceImpedanceOhms });
// Save text as measurement.s1p in your application.
```

For an extracted S11/S22 trace, this is **one-port renormalization**: the other
port remains terminated in the original file reference. It is not renormalization
of the complete two-port network. Exporting S22 to `.s1p` represents that selected
port as the single port of the new file; transmission data is not exported.
When you intentionally need a different reference, use the separate
[reference impedance conversion API](rf.md#reference-impedance-and-renormalization). It is not needed
for loading a file at its original reference. Exporting S22 to `.s1p` represents
that selected port as the single port of the new file; transmission data is not
exported.

The [Touchstone example](https://rosek86.github.io/smithkit/examples/touchstone/index.html)
shows parameter selection, packed import, renormalization and download. In the demo,
shows parameter selection, packed import at the file reference, and download. In the demo,
choose **Two-port reflection** before importing `.s2p`; the trace name includes the
selected parameter. Both `.s1p` and `.s2p` use packed import.
2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ the TypeScript it executes, rather than a separately maintained snippet.
| [Static chart](static/main.ts) | constructor options, `draw`, grid detail, traces |
| [External marker controls](markers/main.ts) | stable marker IDs, `onEvent`, sample selection, focus |
| [Themes and overrides](appearance/main.ts) | `setOptions`, presets, overrides |
| [Touchstone](touchstone/main.ts) | S11/S22 packed import, one-port renormalization, `.s1p` download |
| [Touchstone](touchstone/main.ts) | S11/S22 packed import, file reference, `.s1p` download |
| [Report export](export/main.ts) | SVG/PNG, image size, background, marker legends |
| [Mount, update, and destroy](basic/main.ts) | subscriptions, data replacement, resize, cleanup |
| [React component](react/SmithChart.tsx) | effects, immutable props, events, StrictMode cleanup |
Expand Down
2 changes: 1 addition & 1 deletion examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ <h1>One example, one task.</h1>
>
<a class="card" href="./touchstone/index.html"
><h2>Touchstone</h2>
<p>Read S11 or S22 into packed samples, renormalize, and download a one-port file.</p>
<p>Read S11 or S22 using the file reference and download a one-port file.</p>
<span>Open example →</span></a
>
<a class="card" href="./export/index.html"
Expand Down
11 changes: 3 additions & 8 deletions examples/touchstone/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<header>
<p class="eyebrow">SMITHKIT EXAMPLES</p>
<h1>Touchstone</h1>
<p>Extract S11 or S22, renormalize the reflection trace, and download a one-port file.</p>
<p>Read S11 or S22 using the file’s reference impedance and download a one-port file.</p>
</header>
<section class="preview" aria-label="Example preview">
<div class="controls">
Expand All @@ -24,17 +24,12 @@ <h1>Touchstone</h1>
<option>S11</option>
<option>S22</option>
</select>
<label for="reference">Trace reference</label>
<select id="reference">
<option value="50">50 Ω</option>
<option value="75">75 Ω</option>
</select>
<button id="download" type="button">Download .s1p</button>
</div>
<div id="chart" aria-label="Smith chart"></div>
<p class="hint">
The other port remains terminated in the source file’s 50 Ω reference. This is one-port
renormalization, not a conversion of the full two-port network.
The chart uses the reference impedance from the file header. The selected reflection is
displayed and exported without conversion.
</p>
<p id="status" role="status"></p>
</section>
Expand Down
56 changes: 29 additions & 27 deletions examples/touchstone/main.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,48 @@
import { Smith, Touchstone, RfCalculations } from 'smithkit';
import { Smith, Touchstone } from 'smithkit';

// Touchstone 1.x two-port order: frequency, S11, S21, S12, S22.
const source = `! Synthetic two-port measurement
# GHz S RI R 50
# GHz S RI R 75
1 0.4 -0.4 0.8 0 0.1 0 0.2 -0.3
1.5 0.0 0.0 0.9 0 0.1 0 0.1 0.0
2 0.4 0.4 0.8 0 0.1 0 0.2 0.3
`;
const chart = new Smith();
chart.draw('#chart');
let traceId: string | undefined;
let samples: Float64Array = new Float64Array();
const parameter = document.querySelector<HTMLSelectElement>('#parameter')!;
const reference = document.querySelector<HTMLSelectElement>('#reference')!;
const status = document.querySelector<HTMLParagraphElement>('#status')!;

function update(): void {
const selected = parameter.value === 'S22' ? 'S22' : 'S11';
const parsed = Touchstone.parse(source, { ports: 2, parameter: selected, output: 'packed' });
const referenceImpedanceOhms = Number(reference.value);
samples = RfCalculations.renormalizeSamples(
parsed.samples,
parsed.referenceImpedanceOhms,
referenceImpedanceOhms,
);
if (traceId) {
chart.removeTrace(traceId);
}
chart.setOptions({ referenceImpedanceOhms });
traceId = chart.addTrace(samples, { name: selected, mode: 'both' });
status.textContent = `${selected}: ${samples.length / 3} packed samples at ${referenceImpedanceOhms} Ω.`;
function readMeasurement() {
return Touchstone.parse(source, {
ports: 2,
parameter: parameter.value === 'S22' ? 'S22' : 'S11',
output: 'packed',
});
}
parameter.addEventListener('change', update);
reference.addEventListener('change', update);
update();

let measurement = readMeasurement();
const chart = new Smith('#chart', {
referenceImpedanceOhms: measurement.referenceImpedanceOhms,
});
const traceId = chart.addTrace(measurement.samples, { name: parameter.value, mode: 'both' });

function showStatus(): void {
status.textContent = `${parameter.value}: ${measurement.samples.length / 3} packed samples at ${measurement.referenceImpedanceOhms} Ω.`;
}
parameter.addEventListener('change', () => {
measurement = readMeasurement();
chart.updateTrace(traceId, measurement.samples);
chart.setTraceOptions(traceId, { name: parameter.value });
showStatus();
});
showStatus();

document.querySelector('#download')!.addEventListener('click', () => {
const text = Touchstone.stringify(samples, { referenceImpedanceOhms: Number(reference.value) });
const text = Touchstone.stringify(measurement.samples, {
referenceImpedanceOhms: measurement.referenceImpedanceOhms,
});
const url = URL.createObjectURL(new Blob([text], { type: 'text/plain' }));
const link = document.createElement('a');
link.href = url;
link.download = `${parameter.value}-${reference.value}ohm.s1p`;
link.download = `${parameter.value}-${measurement.referenceImpedanceOhms}ohm.s1p`;
document.body.append(link);
link.click();
link.remove();
Expand Down
5 changes: 2 additions & 3 deletions tests/browser/examples.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,12 +276,11 @@ test('large-trace example switches to one million samples, compares detail and r
await page.screenshot({ path: 'test-results/examples-large-trace.png', fullPage: true });
});

test('Touchstone example downloads the selected, renormalized reflection in s1p format', async ({
test('Touchstone example preserves the file reference when displaying and exporting a selected reflection', async ({
page,
}) => {
await page.goto('examples/touchstone/index.html');
await page.getByLabel('Reflection parameter').selectOption('S22');
await page.getByLabel('Trace reference').selectOption('75');
await expect(page.locator('#status')).toHaveText('S22: 3 packed samples at 75 Ω.');
const downloading = page.waitForEvent('download');
await page.getByRole('button', { name: 'Download .s1p' }).click();
Expand All @@ -293,7 +292,7 @@ test('Touchstone example downloads the selected, renormalized reflection in s1p
expect(lines).toHaveLength(4);
const [frequency, re, im] = lines[2].split(' ').map(Number);
expect(frequency).toBe(1.5e9);
expect(re).toBeCloseTo((0.1 - 0.2) / (1 - 0.2 * 0.1), 14);
expect(re).toBe(0.1);
expect(im).toBe(0);
await page.screenshot({ path: 'test-results/examples-touchstone.png', fullPage: true });
});
Loading