Smithkit - v0.5.0
    Preparing search index...

    Traces and markers

    Reference: TraceInput, TraceOptions, MarkerSnapshot.

    addTrace() and updateTrace() accept the same TraceInput union:

    • TraceSamples: an array of objects with frequencyHz and reflectionCoefficient.
    • readonly TraceTuple[]: [frequencyHz, re, im] tuples, including as const arrays.
    • Float64Array: packed triples in the order f, re, im, f, re, im, ….

    In every format, frequency is in Hz and re/im are the dimensionless voltage reflection coefficient Γ, relative to the chart's reference impedance. A single input must use one format throughout; mixed tuples and objects are rejected. Typed-array views are supported; only values inside the supplied view are copied. The packed length must be a positive multiple of three. All formats use the same finite-value checks and retain input order, duplicate frequencies, and full sample precision.

    import { Smith, type TraceTuple } from 'smithkit';

    const chart = new Smith();
    chart.draw('#smith');
    const samples: readonly TraceTuple[] = [
    [1e9, 0.4, -0.4],
    [1.5e9, 0, 0],
    [2e9, 0.4, 0.4],
    ];
    const traceId = chart.addTrace(samples, { name: 'S11', mode: 'line' });

    // A later update may use a different format. Each triple is one sample.
    chart.updateTrace(traceId, new Float64Array([1e9, 0.3, -0.3, 1.5e9, 0, 0, 2e9, 0.3, 0.3]));

    For large datasets, fill the packed array directly to avoid creating intermediate objects or tuples:

    import { Smith } from 'smithkit';

    const count = 100_000;
    const samples = new Float64Array(count * 3);
    for (let i = 0; i < count; i++) {
    samples[3 * i] = 1e9 + i * 1e4;
    samples[3 * i + 1] = 0.7 * Math.cos((i / count) * 10);
    samples[3 * i + 2] = 0.7 * Math.sin((i / count) * 10);
    }
    const chart = new Smith();
    chart.draw('#smith');
    const traceId = chart.addTrace(samples, { mode: 'points' });
    chart.addMarker(traceId, count - 1);

    All inputs are copied into owned packed storage (24 bytes per sample for the numeric payload). Modifying the input after adding or updating a trace does not change the chart. Marker readings and events keep their existing object shape. Touchstone.parse() returns object samples by default, or packed triples with output: 'packed'. RfCalculations.renormalizeSamples() preserves object or packed representation. Invalid replacements leave data, markers, and rendering unchanged.

    For more than 5,000 samples, point rendering groups overlapping points into screen cells and skips offscreen points; markers still select from the full data. Lines retain every sample by default; opt into pixel-based simplification with lineTolerancePx as described under trace appearance. Sorted ascending frequencies accelerate frequency selection; unsorted inputs remain supported without reordering. There is no interpolation or reduction of the stored measurements.

    Trace and marker IDs are opaque strings, unique within a chart and never reused there. They survive renaming, hiding, updating samples, and removing other items. They are not persistent across chart instances or page reloads.

    const markerA = chart.addMarker(traceId)!; // First sample.
    const markerB = chart.addMarker(traceId, 2)!; // Zero-based sample index.

    chart.setTraceOptions(traceId, { name: 'Tuned antenna', visible: true });
    chart.setMarkerSample(markerB, 1);
    console.log(chart.getMarker(markerB)?.frequencyHz); // 1.5e9

    const difference = chart.compareMarkers(markerA, markerB);
    console.log(difference?.frequencyDeltaHz); // B − A, in Hz.
    console.log(difference?.impedanceDeltaOhms?.toVector());
    console.log(difference?.phaseDeltaDegrees);

    chart.removeMarker(markerB);
    Method Result / behavior
    addTrace(samples, options?) Returns a trace ID, without creating markers. Options: nonblank name, solid CSS color, and visible.
    getTraces() Detached TraceInfo snapshots: ID, name, color, visibility, sample count, and marker metadata.
    setTraceOptions(id, options) Updates the supplied options only. Invalid options throw before any change.
    updateTrace(id, samples, options?) Replaces samples and preserves identity, appearance, and markers. Empty/invalid input throws without changing the trace.
    removeTrace(id) Removes the trace and its markers.
    clearTraces() Removes all traces and their markers.
    addMarker(traceId, sampleIndex = 0) Returns a marker ID, or undefined if the trace is missing.
    getMarker(id) Returns a MarkerSnapshot, or undefined if missing.
    focusMarker(id) Moves keyboard focus to a mounted, visible marker; returns false if missing or not focusable.
    setMarkerSample(id, sampleIndex) Selects an existing sample and queues a marker notification.
    removeMarker(id) Removes one marker without renumbering the remaining markers.
    compareMarkers(a, b) Returns B − A, or undefined if either marker is missing.
    referenceImpedanceOhms Read-only reference impedance. Must be positive and finite.

    Update/remove methods return a boolean: false means the ID was not found. Sample indices must be zero-based integers within the trace; invalid indices throw RangeError. Sample replacement follows the nearest measured frequency by default. Pass { markerSelection: 'sample-index' } to retain indices (clamped to the new last sample if the trace shrinks), or { markerSelection: 'reflection' } to select the nearest Γ. Frequency and Γ ties select the earlier sample in input order, including unsorted sweeps and duplicate frequencies. Selection uses the actual selected sample, not a previous requested frequency. No interpolation is performed.

    chart.updateTrace(
    traceId,
    [
    { frequencyHz: 1e9, reflectionCoefficient: [0.2, 0] },
    { frequencyHz: 2e9, reflectionCoefficient: [0.3, 0] },
    ],
    { markerSelection: 'frequency' },
    );

    Invalid strategies throw TypeError; empty or invalid samples throw RangeError before mutation. Unknown trace IDs return false without validating input. Renormalization always preserves sample indices because it transforms the existing sweep rather than replacing it.

    Trace snapshots contain markers: { id, number, sampleIndex }[]. number is the stable display number within that trace. A MarkerSnapshot includes the common RF reading fields plus traceId, markerId, markerNumber, sampleIndex, and frequencyHz.

    Hiding a trace hides its points and markers while retaining measurements for comparisons. Hiding or deleting a dragged marker/trace ends the drag. Mutations after destruction throw; trace/marker lookups return empty or missing results.

    Pass mode: 'points' | 'line' | 'both', lineWidth, pointRadius, and lineTolerancePx to addTrace or setTraceOptions. Defaults are points, 2 px line width, and 2 px point radius. Both sizes are CSS pixels and remain constant under chart zoom and container resize. Widths and radii must be positive and finite. getTraces() includes the current style.

    By default, lines connect all samples in input order, without smoothing or frequency sorting. A line-only trace creates one SVG path and no sample circles, suitable for dense sweeps. Points/both render every sample for traces up to 5,000 samples. Larger traces render one representative per point-radius-sized display cell and skip points outside the viewport; representatives are recalculated during zoom/pan. This only simplifies the image: all original samples remain available to markers, snapping, readouts, and comparisons. Styles do not change marker identity or layer ordering. Hidden traces retain their style after sample replacement. The demo exposes the same controls per trace.

    For dense lines, opt into lineTolerancePx (finite, non-negative; default 0 disables simplification):

    chart.setTraceOptions(traceId, { mode: 'line', lineTolerancePx: 0.5 });
    

    The tolerance bounds additional geometric deviation in CSS pixels, accounting for chart zoom and container size. It does not include SVG coordinate rounding or browser antialiasing. Cached, nested geometry levels retain endpoints and input order; zooming in selects finer geometry, while panning reuses the current level. The cache is rebuilt when samples change, including reference-impedance renormalization. Markers, frequency selection, RF calculations, and sample counts always use the full data. The option applies to the line in line and both modes; point rendering is unchanged.

    Preparing levels takes extra CPU and memory, and noisy traces may simplify very little. This is an opt-in display optimization, not data compression. See the large-trace example to compare full geometry with pixel tolerances and local measurements.

    chart.setMarkerFrequency(markerId, frequencyHz) selects the nearest measured frequency and returns true, or false if the marker does not exist. It accepts finite, non-negative Hz values; other values throw RangeError. It works with unsorted sweeps and duplicate frequencies. Equal-distance ties choose the earliest sample in input order. Requests outside the sweep select the nearest endpoint. No interpolation or sample reordering is performed.

    Read chart.getMarker(markerId).frequencyHz for the actual selected frequency. The same queued Marker event is emitted as for sample-index selection, and comparisons update accordingly. The demo accepts MHz and shows the selected measurement frequency; dragging and sample selection keep that field synchronized.

    Use Tab/Shift+Tab to focus a marker. Arrow keys move through samples in input order; Right/Up advances and Left/Down goes back. Shift+Arrow or Page Up/Down moves ten samples, and Home/End selects the first/last sample. Navigation clamps at the ends and also works when chart zoom is disabled. A single-sample trace remains focusable for reading, with its slider marked disabled.

    A focused marker has a contrasting outline and exposes its trace name, marker number, sample position, and frequency to assistive technology. Hidden traces are excluded from keyboard focus. chart.focusMarker(id) allows a separate marker selector to focus the matching chart control. The demo provides a Focus on chart button beside its marker selector.

    Markers have a 44 CSS px circular hit area and an 18 CSS px triangle, kept constant through zoom and container resizing. A marker touch gesture does not pan the chart; cancelling it releases the drag state. Touch target areas may overlap for nearby markers; use keyboard focus or the demo selector to reach an obscured marker.