Skip to content

visualdynamics.plot.bars

bars

The comparison as bar charts: a bar per control channel.

A table of six channels is read; a table of sixty is scanned, and the one channel that matters is somewhere in it. A bar chart is the same numbers arranged so the answer is the shape rather than a row — which channel is worst, how many are out, and by how much, before anything is read at all.

Two charts, because a random vibration run is judged two ways and they disagree often enough to be worth seeing apart. RMS error is the level: how far the whole channel sits from what was asked for, in dB. Lines outside abort is the shape: a channel can sit at exactly the right level and still be out of tolerance across half its band, and one that is 2 dB low everywhere may never cross an abort limit at all.

A threshold is drawn as shaded ground rather than as a line: what is being said is "past here is out", and a filled region says it where a line leaves it to be inferred — red past the upper, blue past the lower, the same two colors the specification plot shades its abort zones with. The shading is the handle, and dragging it snaps to a tenth of a dB or of a percent. A tolerance is a judgment — ±3 dB and a tenth of the band are where most specifications land, not where they all do — so those are a starting point, and everything reads off wherever they are put. In a report they are drawn and not moved: there is nobody on the other end of a drag in a file.

Classes:

Name Description
BarChart

A bar per channel, a threshold that can be dragged, and a count.

Functions:

Name Description
error_chart

How far each channel's RMS sits from what was asked for, in dB.

lines_chart

How much of each channel's band fell outside its abort limits.

level_chart

The RMS level each channel of a specification asks for, a bar

kurtosis_chart

How Gaussian each channel is: Pearson kurtosis, a bar apiece.

snr_chart

How far each channel's signal stands above its noise: RMS

replication_chart

One reading of a transient replication, a bar per control channel.

Classes

BarChart

BarChart(plot: Any, rows: Sequence[tuple[str, float]], colors: Mapping[str, str], low: float | None, high: float | None = None, changed: Callable[..., None] | None = None, label: str = '', units: str = '', neutral: str = 'response_curve', baseline: float = 0.0, summary: str = 'over', side: str = 'over')

A bar per channel, a threshold that can be dragged, and a count.

Owns its plot. changed is called with the thresholds whenever one is moved, so whoever put the chart up can remember where they were put — a threshold that reset on every redraw would be no threshold at all.

Methods:

Name Description
beyond

Which way this bar is out, or None.

share

The percentage of channels outside, wherever the lines are.

summary_text

The count the chart states: how many channels are out, of how

key

What the colors mean, into legend (plot.legend_below): the

set_threshold

Move a threshold, snapped, and restate everything from it.

Source code in src/visualdynamics/plot/bars.py
def __init__(self, plot: Any, rows: Sequence[tuple[str, float]], colors: Mapping[str, str],
             low: float | None, high: float | None = None,
             changed: Callable[..., None] | None = None,
             label: str = '', units: str = '',
             neutral: str = 'response_curve',
             baseline: float = 0.0, summary: str = 'over',
             side: str = 'over') -> None:
    self.plot: Any = plot
    self.rows: list[Any] = list(rows)
    self.colors: dict[str, str] = colors
    #: the thresholds; `low` None is a chart with none at all — a
    #: level chart, where nothing is out and nothing is colored
    self.low: float | None = None if low is None else float(low)
    self.high: float | None = None if high is None else float(high)
    self.changed: Callable[..., None] | None = changed
    self.label: str | None = label
    self.units: str | None = units
    #: what a bar inside the thresholds is painted. The ink by
    #: default — a comparison's in-tolerance channels are the
    #: subject of the chart — but a reading whose whole point is
    #: the *exception* stands its ordinary channels back in gray
    #: (the kurtosis chart, Brandon 2026-08-24), which is also
    #: what the report has always drawn there.
    self.neutral: str = neutral
    #: where a bar grows from. Zero for a reading whose zero means
    #: 'no error at all'; the *nominal* for one that has a nominal
    #: — a kurtosis bar drawn from zero is three units of
    #: agreement dressed up as a measurement, where drawn from
    #: three it is the departure from Gaussian and reads both ways
    #: (Brandon, 2026-08-24).
    self.baseline: float = float(baseline)
    #: where the count goes: 'over' the bars, as the app has always
    #: put it; 'title', above the plot — at a quarter of a printed
    #: page wide the count over the bars ran into them and was cut
    #: off (the band-average paper, 2026-09-26); or 'none'
    if summary not in ('over', 'title', 'none'):
        raise ValueError(f"summary {summary!r}: 'over', 'title' or 'none'")
    self.summary_place: str = summary
    #: which way a one-sided chart's single threshold faults:
    #: 'over' for a ceiling (an error, a share outside the limits),
    #: 'under' for a floor — a signal-to-noise, where too *little*
    #: is the fault (2026-10-03). Two-sided charts ignore it.
    if side not in ('over', 'under'):
        raise ValueError(f"side {side!r}: 'over' or 'under'")
    self.side: str = side
    #: the bars themselves, one item holding all of them
    self.bars: Any = None
    self.lines: list[Any] = []
    self.summary: Any = None
    self._moving = False
    self._draw()
Methods:
beyond
beyond(value: float) -> str | None

Which way this bar is out, or None.

A one-sided chart has a ceiling and no floor — no amount of staying inside the abort limits is a fault — or, with side 'under', a floor and no ceiling. On a floor chart a value with no number (NaN) is under it: a signal-to-noise with no signal above the noise is the worst a channel can read, not a blank.

Source code in src/visualdynamics/plot/bars.py
def beyond(self, value: float) -> str | None:
    """Which way this bar is out, or None.

    A one-sided chart has a ceiling and no floor — no amount of
    staying inside the abort limits is a fault — or, with `side`
    'under', a floor and no ceiling. On a floor chart a value with
    no number (NaN) is under it: a signal-to-noise with no signal
    above the noise is the worst a channel can read, not a blank.
    """
    if self.low is None:
        return None
    if self.high is None and self.side == 'under':
        return 'under' if not np.isfinite(value) or value < self.low \
            else None
    if not np.isfinite(value):
        return None
    if self.high is None:
        return 'over' if value >= self.low else None
    if value > self.high:
        return 'over'
    if value < self.low:
        return 'under'
    return None
share
share() -> float

The percentage of channels outside, wherever the lines are.

Source code in src/visualdynamics/plot/bars.py
def share(self) -> float:
    """The percentage of channels outside, wherever the lines are."""
    if self.low is None:
        return 0.0
    if self.high is None and self.side == 'under':
        values = self.values()
        out = sum(1 for v in values if self.beyond(v))
        return 100.0 * out / len(values) if values else 0.0
    return outside_fraction(self.values(), self.low, self.high)
summary_text
summary_text() -> str

The count the chart states: how many channels are out, of how many, and the share — or, with no thresholds, how many there are and which is highest, the way a table's eye runs down its column.

Source code in src/visualdynamics/plot/bars.py
def summary_text(self) -> str:
    """The count the chart states: how many channels are out, of how
    many, and the share — or, with no thresholds, how many there are
    and which is highest, the way a table's eye runs down its
    column."""
    if self.low is None:
        finite = [(v, label) for label, v in self.rows if np.isfinite(v)]
        top = max(finite, default=None)
        return (f'{len(self.rows)} channel{"s" * (len(self.rows) != 1)}'
                + (f' — {top[1]} highest at {top[0]:.4g} {self.units}'
                   .rstrip() if top else ''))
    share = self.share()
    out = sum(1 for v in self.values() if self.beyond(v))
    where = (f'outside {self.low:g} to {self.high:g}{self.units}'
             if self.high is not None
             else f'under {self.low:g}{self.units}'
             if self.side == 'under'
             else f'over {self.low:g}{self.units}')
    return f'{out} of {len(self.rows)} channels {where} — {share:.0f}%'
key
key(legend: Any) -> None

What the colors mean, into legend (plot.legend_below): the bars inside, over and under the thresholds, and the ground past each — a figure read on its own had no key for any of it (2026-09-26). A chart with no thresholds colors nothing, and adds nothing.

Source code in src/visualdynamics/plot/bars.py
def key(self, legend: Any) -> None:
    """What the colors mean, into `legend` (`plot.legend_below`): the
    bars inside, over and under the thresholds, and the ground past
    each — a figure read on its own had no key for any of it
    (2026-09-26). A chart with no thresholds colors nothing, and
    adds nothing."""
    import pyqtgraph as pg

    if self.low is None:
        return
    # a word takes a space and a sign does not: 3 dB, 10%
    units = (f' {self.units}' if self.units and self.units[0].isalpha()
             else self.units or '')
    one_sided = self.high is None
    top = self.low if one_sided else self.high
    if one_sided and self.side == 'under':
        entries = [(self._brush(self.low + 1.0), 'within tolerance'),
                   (self._brush(self.low - 1.0), 'under tolerance'),
                   (self._zone_brush('low'),
                    f'past {self.low:+g}{units}'.replace('-', '\u2212'))]
        for brush, name in entries:
            legend.addItem(pg.BarGraphItem(x=[0.0], height=[1.0],
                                           width=1.0, pen=None,
                                           brush=brush), name)
        return
    entries = [(self._brush(self.baseline if one_sided
                            else (self.low + self.high) / 2.0),
                'within tolerance'),
               (self._brush(top + 1.0), 'over tolerance')]
    if not one_sided:
        entries.append((self._brush(self.low - 1.0), 'under tolerance'))
    entries.append((self._zone_brush('high' if not one_sided else 'low'),
                    f'past {top:+g}{units}'.replace('-', '\u2212')))
    if not one_sided:
        entries.append((self._zone_brush('low'),
                        f'past {self.low:+g}{units}'.replace('-', '\u2212')))
    for brush, name in entries:
        swatch = pg.BarGraphItem(x=[0.0], height=[1.0], width=1.0,
                                 pen=None, brush=brush)
        legend.addItem(swatch, name)
set_threshold
set_threshold(which: str, value: float) -> None

Move a threshold, snapped, and restate everything from it.

What dragging the shading does, and what a caller does to set one outright — one path, so a dragged threshold and a stored one cannot land on different numbers.

Source code in src/visualdynamics/plot/bars.py
def set_threshold(self, which: str, value: float) -> None:
    """Move a threshold, snapped, and restate everything from it.

    What dragging the shading does, and what a caller does to set
    one outright — one path, so a dragged threshold and a stored one
    cannot land on different numbers.
    """
    if self._moving:
        return
    # rounded twice: the second one is only to keep binary
    # floating point from turning 2.9 into 2.9000000000000004
    position = round(round(float(value) / SNAP) * SNAP, 10)
    if which == 'low':
        self.low = position
    else:
        self.high = position
    self._moving = True
    try:
        self._restate()
        if self.changed is not None:
            self.changed(self.low, self.high)
    finally:
        self._moving = False

Functions:

error_chart

error_chart(plot: Any, rows: Sequence[tuple[str, float, float]], colors: Mapping[str, str], low: float = -ERROR_DB, high: float = ERROR_DB, changed: Callable[..., None] | None = None, **options: Any) -> BarChart

How far each channel's RMS sits from what was asked for, in dB.

Two thresholds, moved independently: a specification is not always written symmetrically, and over-testing and under-testing are not the same fault.

Source code in src/visualdynamics/plot/bars.py
def error_chart(plot: Any, rows: Sequence[tuple[str, float, float]],
                colors: Mapping[str, str], low: float = -ERROR_DB,
                high: float = ERROR_DB,
                changed: Callable[..., None] | None = None,
                **options: Any) -> BarChart:
    """How far each channel's RMS sits from what was asked for, in dB.

    Two thresholds, moved independently: a specification is not always
    written symmetrically, and over-testing and under-testing are not
    the same fault.
    """
    return BarChart(plot, [(label, value) for label, value, _p in rows],
                    colors, low=low, high=high, changed=changed,
                    label='RMS error', units='dB', **options)

lines_chart

lines_chart(plot: Any, rows: Sequence[tuple[str, float, float]], colors: Mapping[str, str], low: float = LINES_PERCENT, changed: Callable[..., None] | None = None, **options: Any) -> BarChart

How much of each channel's band fell outside its abort limits.

Source code in src/visualdynamics/plot/bars.py
def lines_chart(plot: Any, rows: Sequence[tuple[str, float, float]],
                colors: Mapping[str, str], low: float = LINES_PERCENT,
                changed: Callable[..., None] | None = None,
                **options: Any) -> BarChart:
    """How much of each channel's band fell outside its abort limits."""
    return BarChart(plot, [(label, percent) for label, _v, percent in rows],
                    colors, low=low, high=None, changed=changed,
                    label='lines outside abort', units='%', **options)

level_chart

level_chart(plot: Any, rows: Sequence[tuple[str, float]], colors: Mapping[str, str], units: str = '') -> BarChart

The RMS level each channel of a specification asks for, a bar apiece — the table's column as a picture, so which channel is loudest is seen before anything is read. No thresholds: a specification on its own has nothing to be out of (Brandon, 2026-09-06: the RMS error reading without the ±3 dB coloring).

Source code in src/visualdynamics/plot/bars.py
def level_chart(plot: Any, rows: Sequence[tuple[str, float]],
                colors: Mapping[str, str], units: str = '') -> BarChart:
    """The RMS level each channel of a specification asks for, a bar
    apiece — the table's column as a picture, so which channel is
    loudest is seen before anything is read. No thresholds: a
    specification on its own has nothing to be out of (Brandon,
    2026-09-06: the RMS error reading without the ±3 dB coloring).
    """
    return BarChart(plot, list(rows), colors, low=None, high=None,
                    label='RMS level', units=units,
                    neutral='specification_curve')

kurtosis_chart

kurtosis_chart(plot: Any, rows: Sequence[tuple[str, float]], colors: Mapping[str, str], low: float = LOW, high: float = HIGH, changed: Callable[..., None] | None = None) -> BarChart

How Gaussian each channel is: Pearson kurtosis, a bar apiece.

Two thresholds around the nominal three, because both directions say something — above, peaks the spectrum never predicted; below, a record that has been clipped or was never random. The ordinary channels stand back in gray so the exceptions are the chart.

Every channel whatever it measures: kurtosis is dimensionless, so this is the one reading here where accelerations and forces share an axis honestly.

Source code in src/visualdynamics/plot/bars.py
def kurtosis_chart(plot: Any, rows: Sequence[tuple[str, float]],
                   colors: Mapping[str, str],
                   low: float = KURTOSIS_LOW, high: float = KURTOSIS_HIGH,
                   changed: Callable[..., None] | None = None) -> BarChart:
    """How Gaussian each channel is: Pearson kurtosis, a bar apiece.

    Two thresholds around the nominal three, because both directions
    say something — above, peaks the spectrum never predicted; below,
    a record that has been clipped or was never random. The ordinary
    channels stand back in gray so the exceptions are the chart.

    Every channel whatever it measures: kurtosis is dimensionless, so
    this is the one reading here where accelerations and forces share
    an axis honestly.
    """
    return BarChart(plot, list(rows), colors, low=low, high=high,
                    changed=changed, label='Pearson kurtosis', units='',
                    neutral='specification_curve', baseline=NOMINAL)

snr_chart

snr_chart(plot: Any, rows: Sequence[tuple[str, float]], colors: Mapping[str, str], low: float | None = None, changed: Callable[..., None] | None = None, **options: Any) -> BarChart

How far each channel's signal stands above its noise: RMS signal-to-noise in dB, a bar apiece (core.snr, 2026-10-03).

One threshold, and it is a floor: a margin can be too small and never too large. A channel with no signal above its noise (NaN) has no bar and counts as under it — it is the worst reading there is, not a missing one. The ordinary channels stand back in gray so the weak ones are the chart, as on the kurtosis chart.

Source code in src/visualdynamics/plot/bars.py
def snr_chart(plot: Any, rows: Sequence[tuple[str, float]],
              colors: Mapping[str, str], low: float | None = None,
              changed: Callable[..., None] | None = None,
              **options: Any) -> BarChart:
    """How far each channel's signal stands above its noise: RMS
    signal-to-noise in dB, a bar apiece (`core.snr`, 2026-10-03).

    One threshold, and it is a floor: a margin can be too small and
    never too large. A channel with no signal above its noise (NaN)
    has no bar and counts as under it — it is the worst reading there
    is, not a missing one. The ordinary channels stand back in gray
    so the weak ones are the chart, as on the kurtosis chart.
    """
    from ..core.snr import THRESHOLD_DB, at_floor

    return BarChart(plot, at_floor(rows), colors,
                    low=THRESHOLD_DB if low is None else low, high=None,
                    changed=changed, label='RMS signal to noise',
                    units='dB', neutral='specification_curve',
                    side='under', **options)

replication_chart

replication_chart(plot: Any, rows: Sequence[tuple[str, float]], colors: Mapping[str, str], which: str, low: float, high: float | None, changed: Callable[..., None] | None = None, **options: Any) -> BarChart

One reading of a transient replication, a bar per control channel.

rows is [(label, value)] already reduced to the reading wanted — which repeat those values came from is the caller's business, and on screen it is the one the event box is pointing at.

Source code in src/visualdynamics/plot/bars.py
def replication_chart(plot: Any, rows: Sequence[tuple[str, float]], colors: Mapping[str, str],
                      which: str, low: float, high: float | None,
                      changed: Callable[..., None] | None = None,
                      **options: Any) -> BarChart:
    """One reading of a transient replication, a bar per control channel.

    `rows` is [(label, value)] already reduced to the reading wanted —
    which repeat those values came from is the caller's business, and
    on screen it is the one the event box is pointing at.
    """
    label, units = REPLICATION_LABELS[which]
    return BarChart(plot, list(rows), colors, low=low, high=high,
                    changed=changed, label=label, units=units, **options)