Skip to content

Graphical display of track data #4

@jbglaw

Description

@jbglaw

Hi!

There is https://github.com/LenShustek/grapher , but it's a native Windows app. I'm not really into frontend coding at all, but I threw something together using this AI crap which at least can show a plot using basic Python libs. If you happen to have Python, NumPy and matplotlib installed, you may want to give this a try:

#!/usr/bin/env python3

# .tbin -> .csv:
# ../../src/csvtbin -read SRI_SDS_102715028_4secs
#
# ...then call ./show.py SRI_SDS_102715028_4secs.csv

import sys
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FormatStrFormatter
from matplotlib.widgets import Slider

# -------------------------------
# CSV-like file loader
# -------------------------------
def load_track_file(filename):
    with open(filename, "r") as f:
        lines = f.readlines()

    # Ignore first line (title)
    header = lines[1].strip().split(",")
    header = [h.strip() for h in header]

    data = []
    for line in lines[2:]:
        parts = [p.strip() for p in line.split(",") if p.strip() != ""]
        if len(parts) < 2:
            continue
        data.append([float(p) for p in parts])

    data = np.array(data)

    time = data[:, 0]
    tracks = data[:, 1:]
    track_names = header[1:]

    return time, tracks, track_names


# -------------------------------
# Plotting & interaction
# -------------------------------
def plot_tracks(time, tracks, track_names):
    updating = False
    n_tracks = tracks.shape[1]

    fig, axes = plt.subplots(
        n_tracks, 1,
        sharex=True,
        figsize=(10, 2 * n_tracks)
    )

    if n_tracks == 1:
        axes = [axes]

    plt.subplots_adjust(bottom=0.18, hspace=0.15)

    # Precompute min/max
    mins = tracks.min(axis=0)
    maxs = tracks.max(axis=0)

    scatters = []
    for i, ax in enumerate(axes):
        sc = ax.scatter(time, tracks[:, i], s=6)
        scatters.append(sc)

        ax.set_ylabel(
            f"{track_names[i]}\n"
            f"min={mins[i]:.5f}\n"
            f"max={maxs[i]:.5f}",
            rotation=0,
            labelpad=40,
            va="center"
        )

        ax.set_ylim(mins[i], maxs[i])
        ax.grid(True, axis="x", linestyle=":", color="0.7")

    axes[-1].set_xlabel("Time (s)")
    axes[-1].xaxis.set_major_formatter(FormatStrFormatter('%.6f'))

    t_min = time.min()
    t_max = time.max()

    start = t_min
    span = (t_max - t_min) * 0.1

    # Status line
    status = fig.text(
        0.01, 0.01, "",
        ha="left", va="bottom", fontsize=9
    )

    def update_view():
        end = start + span
        for ax in axes:
            ax.set_xlim(start, end)

        # Status line at the bottom.
        status.set_text(
            f"Start: {start:.6f} s | "
            f"Span: {span:.6f} s | "
            f"Max Time: {t_max:.6f} s"
        )
        fig.canvas.draw_idle()

    # -------------------------------
    # Sliders
    # -------------------------------
    ax_start = plt.axes([0.15, 0.08, 0.7, 0.03])
    ax_span = plt.axes([0.15, 0.04, 0.7, 0.03])

    start_slider = Slider(
        ax_start, "Start",
        t_min, t_max,
        valinit=start,
        valfmt="%.6f"
    )

    span_slider = Slider(
        ax_span, "Span",
        (t_max - t_min) * 0.001,
        (t_max - t_min),
        valinit=span,
        valfmt="%.6f"
    )

    def on_slider_change(val):
        nonlocal start, span, updating
        if updating:
            return
        start = start_slider.val
        span = span_slider.val
        update_view()

    start_slider.on_changed(on_slider_change)
    span_slider.on_changed(on_slider_change)

    # -------------------------------
    # Keyboard controls
    # -------------------------------
    def on_key(event):
        nonlocal start, span, updating
        mid = start + 0.5 * span

        if event.key == "up":
            span *= 1.3
            start = mid - 0.5 * span
        elif event.key == "down":
            span *= 0.7
            start = mid - 0.5 * span
        elif event.key == "left":
            start -= 0.3 * span
        elif event.key == "right":
            start += 0.3 * span
        else:
            return

        span = max(span, (t_max - t_min) * 1e-6)
        start = max(t_min, min(start, t_max - span))

        updating = True
        span_slider.set_val(span)
        start_slider.set_val(start)
        updating = False

        update_view()

    fig.canvas.mpl_connect("key_press_event", on_key)

    update_view()
    plt.show()


# -------------------------------
# Main
# -------------------------------
if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python viewer.py <datafile>")
        sys.exit(1)

    filename = sys.argv[1]
    time, tracks, track_names = load_track_file(filename)
    plot_tracks(time, tracks, track_names)

So convert a tbin to csv, then call it like ./show.py sometihing.csv. Cursor left/right to move in time, Cursor up/down to zoom in/out.

As this is only a quick hack with questionable quality and origin, feel free to just close this ticket. But I wanted to at at least show a route to an alternative to a hative application.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions