Skip to content

Commit

Permalink
timeseries plot - non-plotly
Browse files Browse the repository at this point in the history
  • Loading branch information
magland committed Feb 11, 2025
1 parent 71d3221 commit 79c2693
Show file tree
Hide file tree
Showing 6 changed files with 374 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { FunctionComponent, useMemo, useState } from "react";
import React, { FunctionComponent, useMemo, useState } from "react";
import { Props } from "./types";
import { Controls, CondensedControls } from "./Controls";
import { useNwbGroup } from "@nwbInterface";
import { useTimeseriesData } from "./hooks";
import TimeseriesPlot from "./TimeseriesPlot";
import TimeseriesPlotTSV2 from "./TimeseriesPlotTSV2";
import LabeledEventsPlot from "./LabeledEventsPlot";
import "../common/loadingState.css";

export const SimpleTimeseriesView: FunctionComponent<
Props & { condensed?: boolean }
> = ({ nwbUrl, path, width, condensed = false }) => {
> = ({ nwbUrl, path, width = 700, condensed = false }) => {
const [usePlotly, setUsePlotly] = useState(true);

const {
timeseriesClient,
error,
Expand Down Expand Up @@ -132,11 +135,26 @@ export const SimpleTimeseriesView: FunctionComponent<

return (
<div style={{ position: "relative" }}>
<div style={{ position: "absolute", top: 5, right: 5, zIndex: 1000 }}>
<button
onClick={() => setUsePlotly((p) => !p)}
style={{
padding: "4px 8px",
backgroundColor: usePlotly ? "#007bff" : "#6c757d",
color: "white",
border: "none",
borderRadius: "4px",
cursor: "pointer",
}}
>
{usePlotly ? "Using Plotly" : "Using TimeScrollView2"}
</button>
</div>
{isLoading && <div className="loadingIndicator">Loading data...</div>}
{condensed ? (
<CondensedControls
info={info}
visibleChannelsStart={visibleChannelsStart}
visibleChannelsStart={visibleChannelsStart || 0}
numVisibleChannels={numVisibleChannels}
visibleTimeStart={visibleTimeStart}
visibleDuration={visibleDuration}
Expand All @@ -152,7 +170,7 @@ export const SimpleTimeseriesView: FunctionComponent<
) : (
<Controls
info={info}
visibleChannelsStart={visibleChannelsStart}
visibleChannelsStart={visibleChannelsStart || 0}
numVisibleChannels={numVisibleChannels}
visibleTimeStart={visibleTimeStart}
visibleDuration={visibleDuration}
Expand All @@ -177,7 +195,7 @@ export const SimpleTimeseriesView: FunctionComponent<
visibleStartTime={visibleTimeStart || 0}
visibleEndTime={(visibleTimeStart || 0) + (visibleDuration || 1)}
/>
) : (
) : usePlotly ? (
<TimeseriesPlot
timestamps={loadedTimestamps}
data={loadedData}
Expand All @@ -188,6 +206,21 @@ export const SimpleTimeseriesView: FunctionComponent<
width={width}
height={condensed ? 200 : 350}
/>
) : (
<div
style={{ position: "relative", width, height: condensed ? 200 : 350 }}
>
<TimeseriesPlotTSV2
timestamps={loadedTimestamps}
data={loadedData}
visibleStartTime={visibleTimeStart || 0}
visibleEndTime={(visibleTimeStart || 0) + (visibleDuration || 1)}
channelNames={channelNames}
channelSeparation={channelSeparation}
width={width}
height={condensed ? 200 : 350}
/>
</div>
)}
</div>
);
Expand Down
25 changes: 8 additions & 17 deletions src/pages/NwbPage/plugins/simple-timeseries/TimeseriesPlot.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
import { FunctionComponent, useMemo } from "react";
import Plot from "react-plotly.js";

type Props = {
timestamps?: number[];
data?: number[][];
visibleStartTime: number;
visibleEndTime: number;
channelNames?: string[]; // Optional array of channel names
channelSeparation?: number; // Factor for channel separation (0 means no separation)
width?: number;
height?: number;
};
import { TimeseriesPlotProps as Props } from "./types";

const colors = [
"#1f77b4", // blue
Expand All @@ -23,15 +14,15 @@ const colors = [
"#7f7f7f", // gray
];

const TimeseriesPlot: FunctionComponent<Props> = ({
timestamps,
data,
visibleStartTime,
visibleEndTime,
const TimeseriesPlot: FunctionComponent<Partial<Props>> = ({
timestamps = [],
data = [],
visibleStartTime = 0,
visibleEndTime = 1,
channelSeparation = 0,
channelNames,
width,
height,
width = 700,
height = 300,
}) => {
// Memoize the transposed channel data
const channelData = useMemo(() => {
Expand Down
107 changes: 107 additions & 0 deletions src/pages/NwbPage/plugins/simple-timeseries/TimeseriesPlotTSV2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { FunctionComponent, useEffect, useState } from "react";
import TimeScrollView2 from "@shared/component-time-scroll-view-2/TimeScrollView2";
import { TimeseriesPlotProps as Props } from "./types";

const defaultMargins = {
left: 50,
right: 20,
top: 20,
bottom: 40,
};

const hideToolbar = true;
const gridlineOpts = { hideX: false, hideY: false };

const TimeseriesPlotTSV2: FunctionComponent<Props> = ({
width,
height,
timestamps,
data,
visibleStartTime,
visibleEndTime,
channelSeparation,
}) => {
const [canvasElement, setCanvasElement] = useState<
HTMLCanvasElement | undefined
>();
const [worker, setWorker] = useState<Worker | null>(null);

// Initialize worker
useEffect(() => {
if (!canvasElement) return;

const worker = new Worker(
new URL("./timeseriesPlotTSV2Worker", import.meta.url),
);
let offscreenCanvas: OffscreenCanvas;

try {
offscreenCanvas = canvasElement.transferControlToOffscreen();
} catch (err) {
console.warn(err);
console.warn(
"Unable to transfer control to offscreen canvas (expected during dev)",
);
return;
}

worker.postMessage(
{
canvas: offscreenCanvas,
},
[offscreenCanvas],
);

setWorker(worker);

return () => {
worker.terminate();
};
}, [canvasElement]);

// Update plot when data or view changes
useEffect(() => {
if (!worker) return;

const opts = {
canvasWidth: width,
canvasHeight: height,
margins: defaultMargins,
visibleStartTimeSec: visibleStartTime,
visibleEndTimeSec: visibleEndTime,
channelSeparation,
data,
timestamps,
};

worker.postMessage({ opts });
}, [
worker,
width,
height,
visibleStartTime,
visibleEndTime,
channelSeparation,
data,
timestamps,
]);

const yAxisInfo = {
showTicks: true,
yMin: undefined,
yMax: undefined,
};

return (
<TimeScrollView2
width={width}
height={height}
onCanvasElement={setCanvasElement}
gridlineOpts={gridlineOpts}
yAxisInfo={yAxisInfo}
hideToolbar={hideToolbar}
/>
);
};

export default TimeseriesPlotTSV2;
90 changes: 90 additions & 0 deletions src/pages/NwbPage/plugins/simple-timeseries/implementation-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# TimeseriesPlot TimeScrollView2 Implementation Plan

## Overview
Replace Plotly-based visualization with TimeScrollView2 in SimpleTimeseriesView while maintaining the same plotting behavior.

## Components to Create/Modify

### 1. TimeseriesPlotTSV2.tsx
- Props interface matching TimeseriesPlot:
```typescript
type Props = {
timestamps: number[]
data: number[][]
visibleStartTime: number
visibleEndTime: number
channelNames?: string[]
channelSeparation: number
width: number
height: number
}
```
- Key Features:
- Uses TimeScrollView2 for visualization
- Sets up worker communication
- Handles canvas setup and data updates
- Manages visible time range
- Supports channel separation
### 2. timeseriesPlotTSV2Worker.ts
- Responsibilities:
- Receives offscreen canvas
- Handles opts updates (dimensions, margins, time range)
- Processes and renders timeseries data
- Implements efficient drawing algorithm
- Supports channel separation visualization
### 3. SimpleTimeseriesView.tsx Changes
- Add usePlotly flag (initially false)
- Conditional rendering:
```typescript
{timeseriesClient.isLabeledEvents() ? (
<LabeledEventsPlot ... />
) : usePlotly ? (
<TimeseriesPlot ... />
) : (
<TimeseriesPlotTSV2 ... />
)}
```
## Implementation Steps
1. Create TimeseriesPlotTSV2.tsx:
- Use TimeScrollView2 component
- Set up canvas and worker initialization
- Handle prop updates and data changes
- Implement channel separation logic
2. Create timeseriesPlotTSV2Worker.ts:
- Handle canvas setup messages
- Process opts updates
- Implement drawing logic:
- Clear canvas
- Draw time series lines
- Apply channel separation
- Handle visible range updates
3. Update SimpleTimeseriesView.tsx:
- Add usePlotly flag
- Add conditional rendering
- Ensure props are passed correctly
## Testing Plan
1. Visual Verification:
- Compare rendering with Plotly version
- Verify channel separation works
- Check time range navigation
- Validate multi-channel display
2. Performance Testing:
- Compare rendering speed
- Check memory usage
- Verify worker communication
## Notes
- Worker-based rendering should improve performance
- TimeScrollView2 provides built-in time navigation
- Channel separation needs custom implementation in worker
- Maintain same visual style as Plotly version
Loading

0 comments on commit 79c2693

Please sign in to comment.