Skip to content

visualdynamics.gui.app_icon

app_icon

The application's icon: four FRFs on the waterfall stage.

The 3-D view of data (the waterfall stage, with channels receding and colored by level) is what the application looks like, so it is what the tile shows. The curves are accelerances synthesized from the demonstration plate's own modes, without noise, and drawn from the viewpoint viz.waterfall.place_camera gives the stage.

The icon is drawn with QPainter and numpy only, so it can be built on a machine with no GL context. Under Qt's offscreen platform, which is where an icon build runs, VTK's render widget crashes the process. The synthesis takes about a fifth of a second and the drawing is cached, so each size is drawn once per run.

The tile differs from a plot in two ways. The vertical is stretched to fill the tile, because the stage's view projects wide and short. And the color and height range is clipped to the top DECADES decades: without the clip, one deep antiresonance takes up the whole range and every peak reads flat green.

Functions:

Name Description
colormap

values in 0..1 as an (N, 3) array of RGB on theme.VIRIDIS,

synthesized_levels

The four curves as levels in 0..1, used for both color and

stage_basis

(right, up), the stage's home view, from

curve_count

How many curves a tile of size pixels shows: three below

screen_curves

The curves as (N, 2) pixel paths, front first.

draw_app_icon

The icon at size pixels square, as a QImage.

app_icon

The icon at the sizes a window manager asks for, 16 to 512

Functions:

colormap

colormap(values: ArrayLike) -> ndarray

values in 0..1 as an (N, 3) array of RGB on theme.VIRIDIS, the ramp everything that paints by level uses.

Source code in src/visualdynamics/gui/app_icon.py
def colormap(values: ArrayLike) -> np.ndarray:
    """`values` in 0..1 as an (N, 3) array of RGB on `theme.VIRIDIS`,
    the ramp everything that paints by level uses."""
    from ..theme import colormap as ramp

    return ramp(values)

synthesized_levels cached

synthesized_levels() -> ndarray

The four curves as levels in 0..1, used for both color and height.

Accelerances without noise from the demonstration plate's own modes (ShapeSet.synthesize_frf), as log magnitude, clipped to the top DECADES decades of the set and normalized. Cached.

Source code in src/visualdynamics/gui/app_icon.py
@cache
def synthesized_levels() -> np.ndarray:
    """The four curves as levels in 0..1, used for both color and
    height.

    Accelerances without noise from the demonstration plate's own modes
    (`ShapeSet.synthesize_frf`), as log magnitude, clipped to the top
    `DECADES` decades of the set and normalized. Cached.
    """
    from ..demo import plate

    shapes = plate.build().eigensolution(
        maximum_frequency=2000.0, damping=DAMPING)
    freq = np.linspace(BAND[0], BAND[1], POINTS)
    magnitudes = np.stack([
        np.abs(shapes.synthesize_frf(freq, [dof], [DRIVE], power=2)[0])
        for dof in RESPONSES])
    rows = np.log10(magnitudes)
    return np.clip((rows - (rows.max() - DECADES)) / DECADES, 0.0, 1.0)

stage_basis cached

stage_basis() -> tuple[ndarray, ndarray]

(right, up), the stage's home view, from viz.waterfall.stage_basis. The icon uses the same view as the application's default 3-D view of data and the report's 3-D figure.

Source code in src/visualdynamics/gui/app_icon.py
@cache
def stage_basis() -> tuple[np.ndarray, np.ndarray]:
    """(right, up), the stage's home view, from
    `viz.waterfall.stage_basis`. The icon uses the same view as the
    application's default 3-D view of data and the report's 3-D
    figure."""
    from ..viz.waterfall import stage_basis as basis

    return basis()

curve_count

curve_count(size: int) -> int

How many curves a tile of size pixels shows: three below SMALL (96 pixels), four otherwise.

Source code in src/visualdynamics/gui/app_icon.py
def curve_count(size: int) -> int:
    """How many curves a tile of `size` pixels shows: three below
    `SMALL` (96 pixels), four otherwise."""
    return 3 if size < SMALL else 4

screen_curves

screen_curves(size: int) -> list[ndarray]

The curves as (N, 2) pixel paths, front first.

Projected through the stage basis, then fitted to the tile on each axis separately. The stage's view projects wide and short, and a uniform fit would leave the tile empty at the top and bottom; an icon may be stretched where a plot may not.

Source code in src/visualdynamics/gui/app_icon.py
def screen_curves(size: int) -> list[np.ndarray]:
    """The curves as (N, 2) pixel paths, front first.

    Projected through the stage basis, then fitted to the tile on each
    axis separately. The stage's view projects wide and short, and a
    uniform fit would leave the tile empty at the top and bottom; an
    icon may be stretched where a plot may not.
    """
    from ..viz.waterfall import STAGE

    sx, sy, sz = STAGE
    level = synthesized_levels()
    n = curve_count(size)
    right, up = stage_basis()
    u = np.linspace(0.0, 1.0, level.shape[1])
    screens = []
    for k in range(n):
        station = (k / max(n - 1, 1)) * sy
        world = np.stack([u * sx, np.full_like(u, station),
                          level[k] * sz * AMPLITUDE], axis=-1)
        screens.append(np.stack([world @ right, -(world @ up)], axis=-1))
    stacked = np.vstack(screens)
    low, high = stacked.min(axis=0), stacked.max(axis=0)
    tile = size * (1.0 - 2.0 * TILE_INSET)
    margin = (size * TILE_INSET + tile * MARGIN
              + size * THICKNESS / 2.0)
    scale = np.array([(size - 2 * margin) / (high[0] - low[0]),
                      (size - 2 * margin) / (high[1] - low[1])])
    center = (low + high) / 2.0
    return [(screen - center) * scale + size / 2.0 for screen in screens]

draw_app_icon cached

draw_app_icon(size: int) -> QImage

The icon at size pixels square, as a QImage.

Cached: the synthesis takes about a fifth of a second, the drawing less, and every caller asks for the same few sizes.

Source code in src/visualdynamics/gui/app_icon.py
@cache
def draw_app_icon(size: int) -> QImage:
    """The icon at `size` pixels square, as a QImage.

    Cached: the synthesis takes about a fifth of a second, the drawing
    less, and every caller asks for the same few sizes.
    """
    from PySide6.QtCore import QPointF, QRectF, Qt
    from PySide6.QtGui import (
        QBrush,
        QColor,
        QImage,
        QPainter,
        QPainterPath,
        QPen,
    )

    image = QImage(size, size, QImage.Format.Format_ARGB32)
    image.fill(Qt.GlobalColor.transparent)
    painter = QPainter(image)
    painter.setRenderHint(QPainter.RenderHint.Antialiasing)

    # the tile: macOS insets its icons, and one that fills its square
    # sits visibly larger than everything beside it in the Dock. Pure
    # black, no gradient — the curves are the mark, and black is what
    # makes viridis carry (Brandon, 2026-08-23)
    inset = size * TILE_INSET
    tile = QRectF(inset, inset, size - 2 * inset, size - 2 * inset)
    painter.setPen(Qt.PenStyle.NoPen)
    painter.setBrush(QBrush(QColor(0, 0, 0)))
    radius = size * 0.225
    painter.drawRoundedRect(tile, radius, radius)
    # a curve may reach the tile's edge but never past its corners
    clip = QPainterPath()
    clip.addRoundedRect(tile, radius, radius)
    painter.setClipPath(clip)

    level = synthesized_levels()
    curves = screen_curves(size)
    pen = QPen()
    pen.setWidthF(max(size * THICKNESS, 1.5))
    pen.setCapStyle(Qt.PenCapStyle.RoundCap)
    for k in range(len(curves) - 1, -1, -1):      # back to front
        points = curves[k]
        rgb = colormap(level[k])
        for i in range(len(points) - 1):
            pen.setColor(QColor(int(rgb[i, 0]), int(rgb[i, 1]),
                                int(rgb[i, 2])))
            painter.setPen(pen)
            painter.drawLine(QPointF(*points[i]), QPointF(*points[i + 1]))
    painter.end()
    return image

app_icon cached

app_icon() -> QIcon

The icon at the sizes a window manager asks for, 16 to 512 pixels.

Each size is drawn, not scaled from one image: below 96 pixels the fourth curve is left out, because four curves in thirty pixels cannot be told apart. tools/make_app.py renders the same drawing into a Mac .icns, so the Dock and the window show the same icon.

Source code in src/visualdynamics/gui/app_icon.py
@cache
def app_icon() -> QIcon:
    """The icon at the sizes a window manager asks for, 16 to 512
    pixels.

    Each size is drawn, not scaled from one image: below 96 pixels the
    fourth curve is left out, because four curves in thirty pixels
    cannot be told apart. `tools/make_app.py` renders the same drawing
    into a Mac `.icns`, so the Dock and the window show the same icon.
    """
    from PySide6.QtGui import QIcon, QPixmap

    icon = QIcon()
    for size in (16, 32, 64, 128, 256, 512):
        icon.addPixmap(QPixmap.fromImage(draw_app_icon(size)))
    return icon