Skip to content

basic pane setup and data visualisation#1

Open
sileneer wants to merge 8 commits intomainfrom
dev/zihao/panel-setup
Open

basic pane setup and data visualisation#1
sileneer wants to merge 8 commits intomainfrom
dev/zihao/panel-setup

Conversation

@sileneer
Copy link

No description provided.

Copilot AI review requested due to automatic review settings March 15, 2026 17:41
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an initial telemetry visualisation UI (tabbed shell + split panes + Chart.js graphs) and introduces tooling + data-layer support for generating, converting, loading, and parsing FlatBuffer telemetry frames.

Changes:

  • Add mock telemetry generators (CSV + size-prefixed FlatBuffer .bin) and a CSV→FlatBuffer converter tool.
  • Introduce a FlatBuffer-backed DataProvider + reader that converts FlatBuffer Frames into an internal TelemetryStore model.
  • Add a new UI shell with tabs, split panes, and Chart.js-based graph panels driven by the loaded telemetry.

Reviewed changes

Copilot reviewed 28 out of 30 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
tools/mock-data.ts Shared mock telemetry row generation used by the tooling scripts.
tools/generate-mock-csv.ts Writes mock telemetry to public/data/telemetry.csv.
tools/generate-mock-bin.ts Writes mock telemetry to size-prefixed FlatBuffer public/data/telemetry.bin.
tools/csv-to-bin.ts Converts an input CSV to size-prefixed FlatBuffer telemetry.bin with column auto-detection.
src/tabs/index.ts Barrel exports for tab components.
src/tabs/GraphsTab.tsx Split-pane telemetry UI + chart tab selection + data loading.
src/tabs/GpsTab.tsx Placeholder GPS tab/panel.
src/tabs/DeserialisationTab.tsx Placeholder deserialisation tab.
src/index.tsx Adds global CSS imports (split-pane styles + app CSS).
src/index.css Global layout + split-pane divider styling + shared pane/button styles.
src/hooks/use-data-provider.ts Hook to drive loading lifecycle from a DataProvider.
src/hooks/use-chart-data.ts Memoized adapter from TelemetryStore to Chart.js ChartData.
src/hooks/index.ts Hook barrel exports.
src/data/types.ts Internal telemetry domain model (TelemetryFrame, metadata, etc.).
src/data/provider.ts DataProvider interface + result/state types.
src/data/index.ts Data-layer barrel exports.
src/data/flatbuffer/index.ts FlatBuffer data-layer barrel exports.
src/data/flatbuffer/fb-reader.ts Parses concatenated size-prefixed Frames into internal frame types.
src/data/flatbuffer/fb-provider.ts Fetches .bin, parses frames, and builds TelemetryStore + metadata.
src/charts/presets.ts Predefined series selectors for common charts.
src/charts/index.ts Charts barrel exports.
src/charts/colors.ts Shared chart color palette.
src/charts/adapter.ts Pure adapter to build Chart.js datasets from telemetry frames.
src/charts/ChartTabs.tsx Chart panel rendering and tab filtering (“all” vs single chart).
src/App.tsx New app shell with nav tabs and main content routing.
src/App.css New layout + nav styling (replaces starter styles).
pnpm-workspace.yaml Adds pnpm workspace configuration related to builds.
pnpm-lock.yaml Locks added dependencies (chart.js, flatbuffers, split pane, etc.).
package.json Adds scripts for FlatBuffer generation + mock data generation/conversion; adds deps/devDeps.
.gitignore Ignores FlatBuffers tool/generated output and generated telemetry data outputs.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +19 to +27
provider.load(url).then((result) => {
if (cancelled) return;

if (result.status === 'success') {
setState({ status: 'ready', store: result.store });
} else {
setState({ status: 'error', error: result.error });
}
});
// Timestamp
{ pattern: /^(time_?)?seconds?$/i, field: 'time_seconds' },
{ pattern: /^(time_?)?nano(second)?s?$/i, field: 'time_nanoseconds' },
{ pattern: /^clock(_?nanos)?|^nanos$/i, field: 'clock_nanos' },
Comment on lines +223 to +226
const value = Number(cells[colIndex]);
if (!Number.isNaN(value)) {
row[field as keyof ParsedRow] = value;
}
Comment on lines +11 to +18
import { Frame } from '../src/generated/telemetry/frame.js';
import { Timestamp } from '../src/generated/telemetry/timestamp.js';
import { Nanos } from '../src/generated/telemetry/nanos.js';
import { GPSFrame } from '../src/generated/telemetry/gpsframe.js';
import { IMUFrame } from '../src/generated/telemetry/imuframe.js';
import { WheelSpeedFrame } from '../src/generated/telemetry/wheel-speed-frame.js';
import { DamperPositionFrame } from '../src/generated/telemetry/damper-position-frame.js';
import { generateAllRows, FRAME_COUNT, SAMPLE_RATE_HZ } from './mock-data.js';
Comment on lines +11 to +14
"convert:csv": "tsx tools/csv-to-bin.ts",
"generate:fbs": "bin/flatc --ts -o src/generated/ telemetry.fbs",
"generate:mock-bin": "tsx tools/generate-mock-bin.ts",
"generate:mock-csv": "tsx tools/generate-mock-csv.ts",
Comment on lines +96 to +113
function convertTimestamp(fb: Frame): Timestamp | undefined {
const ts = fb.time();
if (!ts) return undefined;
return {
seconds: Number(ts.seconds()),
nanoseconds: Number(ts.nanoseconds()),
};
}

function convertClockNanos(fb: Frame): number {
const clocks = fb.clocks();
if (!clocks) {
// clocks is required in the schema, so this should not happen
// with valid data. Return 0 as a safe fallback.
return 0;
}
return Number(clocks.time());
}
Comment on lines +11 to +13
import { Frame } from '../../generated/telemetry/frame';
import type { Vec3 as FbVec3 } from '../../generated/telemetry/vec3';
import type {
Comment on lines +20 to +69
export interface Timestamp {
readonly seconds: number;
readonly nanoseconds: number;
}

// ---- Sensor data per frame ----

export interface GpsData {
readonly lat: number;
readonly lng: number;
readonly speed: number;
readonly altitude: number;
readonly heading: number;
}

export interface ImuData {
readonly acceleration: Vec3;
readonly angularVelocity: Vec3;
readonly magnetometer: Vec3;
}

export interface WheelSpeedData {
readonly frontLeft: number;
readonly frontRight: number;
readonly backLeft: number;
readonly backRight: number;
}

export interface DamperPositionData {
readonly frontLeft: number;
readonly frontRight: number;
readonly backLeft: number;
readonly backRight: number;
}

export interface CanBusMessage {
readonly id: number;
readonly rtr: boolean;
readonly dataLen: number;
readonly data: readonly number[];
}

// ---- Single telemetry frame ----

export interface TelemetryFrame {
/** Wall-clock time. Optional because the schema does not mark it required. */
readonly timestamp?: Timestamp;
/** Monotonic clock in nanoseconds. Always present (required in schema). */
readonly clockNanos: number;
readonly gps?: GpsData;
Comment on lines +2 to +63
import { Pane, SplitPane } from 'react-split-pane';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
} from 'chart.js';
import { FlatBufferDataProvider } from '../data/flatbuffer';
import { useDataProvider } from '../hooks/use-data-provider';
import {
WHEEL_SPEED_ALL,
DAMPER_POSITION_ALL,
ACCELERATION,
GPS_SPEED,
} from '../charts/presets';
import type { ChartTabConfig } from '../charts/ChartTabs';
import { ChartPanels } from '../charts/ChartTabs';
import { GpsTab } from './GpsTab';

// Register Chart.js components (once at module level)
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
);

const DATA_URL = '/data/telemetry.bin';

const CHART_TABS: ChartTabConfig[] = [
{ id: 'wheelSpeed', label: 'Wheel Speed', preset: WHEEL_SPEED_ALL, title: 'Wheel Speed - All Wheels' },
{ id: 'damper', label: 'Damper Position', preset: DAMPER_POSITION_ALL, title: 'Damper Position - All' },
{ id: 'accel', label: 'Acceleration', preset: ACCELERATION, title: 'Acceleration (X, Y, Z)' },
{ id: 'gpsSpeed', label: 'GPS Speed', preset: GPS_SPEED, title: 'GPS Speed' },
];

export const GraphsTab = () => {
const [showBottom, setShowBottom] = useState(false);
const [activeTab, setActiveTab] = useState('all');

const provider = useMemo(() => new FlatBufferDataProvider(), []);
const state = useDataProvider(provider, DATA_URL);
const store = state.status === 'ready' ? state.store : undefined;

const chartContent = store && (
<ChartPanels tabs={CHART_TABS} store={store} activeTab={activeTab} />
);

return (
<SplitPane direction="horizontal">
<Pane minSize={100} defaultSize="20%">
<div className="pane gray">
<h3>Data</h3>
{state.status === 'idle' && <p>Idle</p>}
{state.status === 'loading' && <p>Loading telemetry...</p>}
Comment on lines +20 to +64
/* Split pane divider - 1px line with expanded hit area */
.split-pane-divider {
background: #ddd;
position: relative;
}

.split-pane-divider::before {
content: "";
position: absolute;
background: transparent;
}

.split-pane-divider:hover::before,
.split-pane-divider.dragging::before {
background: rgba(0, 0, 0, 0.08);
}

.split-pane-divider:focus {
outline: 2px solid #0066cc;
outline-offset: -1px;
}

.split-pane-divider.horizontal {
width: 1px;
cursor: col-resize;
}

.split-pane-divider.horizontal::before {
top: 0;
bottom: 0;
left: -4px;
right: -4px;
}

.split-pane-divider.vertical {
height: 1px;
cursor: row-resize;
}

.split-pane-divider.vertical::before {
left: 0;
right: 0;
top: -4px;
bottom: -4px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants