コンテンツにスキップ

Mixed Density

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

Streams with different sampling rates ingested into the same table, demonstrating how DuckDB handles irregular time series.

examples/mixed-density/server.ts
import { WebSocketServer } from "ws";
const PORT = 9003;
const wss = new WebSocketServer({ port: PORT });
console.log(`Mixed-density WebSocket server listening on ws://localhost:${PORT}`);
wss.on("connection", (ws) => {
console.log("Client connected");
// Send info message
ws.send(
JSON.stringify({
type: "info",
description: "Mixed-density generator (sparse overview + dense stream)",
dt: 0.1,
stream_interval: 0.1,
}),
);
// Phase 1: Send 100 sparse "overview" points immediately (t=0..4950, step=50)
// This simulates the server's history overview message.
for (let i = 0; i < 100; i++) {
const t = i * 50;
ws.send(
JSON.stringify({
type: "state",
t,
value: Math.sin(t * 0.001),
derivative: Math.cos(t * 0.001),
}),
);
}
console.log("Sent 100 sparse overview points (t=0..4950)");
// Phase 2: Dense streaming starting at t=5000, advancing by 0.1 per message
let streamT = 5000;
const interval = setInterval(() => {
ws.send(
JSON.stringify({
type: "state",
t: streamT,
value: Math.sin(streamT * 0.001),
derivative: Math.cos(streamT * 0.001),
}),
);
streamT += 0.1;
}, 10); // 100 messages/sec
ws.on("close", () => {
console.log("Client disconnected");
clearInterval(interval);
});
ws.on("error", (err) => {
console.error("WebSocket error:", err);
clearInterval(interval);
});
});
examples/mixed-density/App.tsx
import { useEffect, useMemo, useRef, useState } from "react";
import {
type ChartDataWorkerClient,
IngestBuffer,
type TableSchema,
type TimeRange,
TimeSeriesChart,
useTimeSeriesStoreWorker,
} from "../../src/index.js";
interface MixedPoint {
t: number;
value: number;
derivative: number;
}
const mixedSchema: TableSchema<MixedPoint> = {
tableName: "mixed_data",
columns: [
{ name: "t", type: "DOUBLE" },
{ name: "value", type: "DOUBLE" },
{ name: "derivative", type: "DOUBLE" },
],
derived: [
{ name: "sine", sql: "value", unit: "" },
{ name: "cosine", sql: "derivative", unit: "" },
{
name: "amplitude",
sql: "sqrt(value*value + derivative*derivative)",
unit: "",
},
],
toRow: (p) => [p.t, p.value, p.derivative],
};
declare global {
interface Window {
__uneriDebug: {
chartData: Record<string, Float64Array> | null;
rowCount: number;
queryRowCount: () => Promise<number>;
};
}
}
export function App() {
const bufferRef = useRef(new IngestBuffer<MixedPoint>());
const [timeRange, setTimeRange] = useState<TimeRange>(null);
const [pointsReceived, setPointsReceived] = useState(0);
const workerClientRef = useRef<ChartDataWorkerClient | null>(null);
// Data source: WebSocket or mock (when ?mock is in URL)
useEffect(() => {
const isMock = new URLSearchParams(window.location.search).has("mock");
let count = 0;
const pushPoint = (t: number, value: number, derivative: number) => {
bufferRef.current.push({ t, value, derivative });
count++;
if (count % 100 === 0) setPointsReceived(count);
};
if (isMock) {
// Phase 1: sparse overview
for (let i = 0; i < 100; i++) {
const t = i * 50;
pushPoint(t, Math.sin(t * 0.001), Math.cos(t * 0.001));
}
// Phase 2: dense streaming
let streamT = 5000;
const interval = setInterval(() => {
pushPoint(streamT, Math.sin(streamT * 0.001), Math.cos(streamT * 0.001));
streamT += 0.1;
}, 10);
return () => clearInterval(interval);
}
const ws = new WebSocket("ws://localhost:9003");
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === "state") {
pushPoint(msg.t, msg.value, msg.derivative);
}
};
return () => ws.close();
}, []);
const { data } = useTimeSeriesStoreWorker({
schema: mixedSchema,
ingestBufferRef: bufferRef,
timeRange,
drainInterval: 100,
tickInterval: 100,
coldRefreshEveryN: 1,
clientRef: workerClientRef,
});
// Expose debug data on window for Playwright tests
// queryRowCount now goes through the Worker client instead of direct conn.query()
useEffect(() => {
window.__uneriDebug = {
chartData: data,
rowCount: pointsReceived,
queryRowCount: async () => {
const client = workerClientRef.current;
if (!client) return 0;
return client.queryRowCount();
},
};
}, [data, pointsReceived]);
// Slice data for individual charts
const sineData = useMemo(
() => (data ? ([data.t, data.sine] as [Float64Array, Float64Array]) : null),
[data],
);
const cosineData = useMemo(
() => (data ? ([data.t, data.cosine] as [Float64Array, Float64Array]) : null),
[data],
);
const amplitudeData = useMemo(
() => (data ? ([data.t, data.amplitude] as [Float64Array, Float64Array]) : null),
[data],
);
return (
<div
style={{
padding: "1rem",
background: "#1a1a2e",
color: "#eee",
minHeight: "100vh",
}}
>
<h1>uneri: Mixed-Density Test (Worker)</h1>
<p data-testid="points-received">
Points received: {pointsReceived} | Chart points: {data?.t?.length ?? 0} | Buffer latestT:{" "}
{bufferRef.current.latestT.toFixed(1)}
</p>
<div style={{ marginBottom: "1rem" }}>
<button type="button" onClick={() => setTimeRange(null)}>
All
</button>
<button type="button" onClick={() => setTimeRange(30)}>
30s
</button>
<button type="button" onClick={() => setTimeRange(60)}>
60s
</button>
</div>
<TimeSeriesChart title="sine" yLabel="" data={sineData} color="#4af" />
<TimeSeriesChart title="cosine" yLabel="" data={cosineData} color="#f84" />
<TimeSeriesChart title="amplitude" yLabel="" data={amplitudeData} color="#8f4" />
</div>
);
}