コンテンツにスキップ

Multi-Series

このコンテンツはまだ日本語訳がありません。

Two sine waves with different frequencies plotted on the same chart using alignTimeSeries.

examples/multi-series/server.ts
import { WebSocketServer } from "ws";
const PORT = 9004;
const wss = new WebSocketServer({ port: PORT });
console.log(`Multi-series WebSocket server listening on ws://localhost:${PORT}`);
wss.on("connection", (ws) => {
console.log("Client connected");
ws.send(
JSON.stringify({
type: "info",
description: "Two sine waves with different frequencies",
}),
);
const startTime = Date.now();
let toggle = false;
// Alternate between two series at different rates to produce
// independent time arrays (as real multi-satellite data would).
const interval = setInterval(() => {
const t = (Date.now() - startTime) / 1000;
toggle = !toggle;
if (toggle) {
ws.send(
JSON.stringify({
type: "state",
series: "slow",
t,
value: Math.sin(t),
}),
);
} else {
ws.send(
JSON.stringify({
type: "state",
series: "fast",
t,
value: Math.sin(t * 3),
}),
);
}
}, 50);
ws.on("close", () => {
console.log("Client disconnected");
clearInterval(interval);
});
ws.on("error", (err) => {
console.error("WebSocket error:", err);
clearInterval(interval);
});
});
examples/multi-series/App.tsx
import { useCallback, useEffect, useRef, useState } from "react";
import { type MultiSeriesData, TimeSeriesChart } from "../../src/components/TimeSeriesChart.js";
import { alignTimeSeries, type NamedTimeSeries } from "../../src/utils/alignTimeSeries.js";
/** Bounded buffer for a single named time series. */
class SeriesBuffer {
readonly label: string;
private times: number[] = [];
private vals: number[] = [];
private maxLen = 500;
constructor(label: string) {
this.label = label;
}
push(t: number, v: number) {
this.times.push(t);
this.vals.push(v);
if (this.times.length > this.maxLen) {
this.times.splice(0, this.times.length - this.maxLen);
this.vals.splice(0, this.vals.length - this.maxLen);
}
}
toNamedTimeSeries(): NamedTimeSeries {
return {
label: this.label,
t: Float64Array.from(this.times),
values: Float64Array.from(this.vals),
};
}
}
export function App() {
const buffersRef = useRef(new Map<string, SeriesBuffer>());
const [multiData, setMultiData] = useState<MultiSeriesData | null>(null);
const COLORS: Record<string, string> = {
slow: "#4af",
fast: "#f84",
};
const buildMultiData = useCallback(() => {
const buffers = buffersRef.current;
if (buffers.size === 0) return;
const inputs: NamedTimeSeries[] = [];
for (const buf of buffers.values()) {
inputs.push(buf.toNamedTimeSeries());
}
const aligned = alignTimeSeries(inputs);
if (aligned.t.length < 2) return;
setMultiData({
t: aligned.t,
values: aligned.values,
series: aligned.labels.map((label) => ({
label,
color: COLORS[label] ?? "#0f0",
})),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
const isMock = new URLSearchParams(window.location.search).has("mock");
let tickCount = 0;
const pushPoint = (series: string, t: number, value: number) => {
let buf = buffersRef.current.get(series);
if (!buf) {
buf = new SeriesBuffer(series);
buffersRef.current.set(series, buf);
}
buf.push(t, value);
tickCount++;
if (tickCount % 10 === 0) buildMultiData();
};
if (isMock) {
const startTime = Date.now();
let toggle = false;
const interval = setInterval(() => {
const t = (Date.now() - startTime) / 1000;
toggle = !toggle;
if (toggle) {
pushPoint("slow", t, Math.sin(t));
} else {
pushPoint("fast", t, Math.sin(t * 3));
}
}, 50);
return () => clearInterval(interval);
}
const ws = new WebSocket("ws://localhost:9004");
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === "state") {
pushPoint(msg.series as string, msg.t, msg.value);
}
};
return () => ws.close();
}, [buildMultiData]);
return (
<div
style={{
padding: "1rem",
background: "#1a1a2e",
color: "#eee",
minHeight: "100vh",
}}
>
<h1>uneri Example: Multi-Series</h1>
<p>Two sine waves with different frequencies on the same chart.</p>
<TimeSeriesChart title="sin(t) vs sin(3t)" yLabel="" multiData={multiData} />
</div>
);
}