diff options
| author | grothedev <grothedev@gmail.com> | 2025-11-25 21:22:17 -0500 |
|---|---|---|
| committer | grothedev <grothedev@gmail.com> | 2025-11-25 21:22:17 -0500 |
| commit | 43420f2987b76aa7ede0012e1998ba8d61419bc9 (patch) | |
| tree | bbb7214c9105fbfeca4ba82056cdfc6fb107b5c2 /web-timeplot/ARCHITECTURE.md | |
| parent | 2c1b37a5b0c4962b405a85768b9b8cdd5c4f1097 (diff) | |
pushing claude changes before i take another route
Diffstat (limited to 'web-timeplot/ARCHITECTURE.md')
| -rw-r--r-- | web-timeplot/ARCHITECTURE.md | 434 |
1 files changed, 434 insertions, 0 deletions
diff --git a/web-timeplot/ARCHITECTURE.md b/web-timeplot/ARCHITECTURE.md new file mode 100644 index 0000000..b49c0b0 --- /dev/null +++ b/web-timeplot/ARCHITECTURE.md @@ -0,0 +1,434 @@ +# TimePlot Architecture + +## Overview + +This document describes the clean, modular architecture for TimePlot's data visualization system. + +## Design Principles + +**Separation of Concerns**: Data generation, data provision, and visualization are completely separate. + +- **Plots don't generate data** - They only display it +- **Data sources don't know about visualization** - They only produce data +- **Connections manage the flow** - They link sources to plots + +This architecture allows you to: +- Easily swap data sources without changing visualization +- Reuse plots with different data +- Test components independently +- Support multiple data types (real-time, synthetic, replay, etc.) + +## Architecture Layers + +``` +┌─────────────────────────────────────────────────┐ +│ Application Layer (main.js) │ +│ - Manages app lifecycle │ +│ - Creates plots and sources │ +│ - Sets up connections │ +└─────────────────────────────────────────────────┘ + │ + ├──────────────┬──────────────┐ + ↓ ↓ ↓ + ┌────────────────┐ ┌─────────────┐ ┌─────────────┐ + │ Connections │ │ Plots │ │ Sources │ + │ (Glue Layer) │ │ (Display) │ │ (Data) │ + └────────────────┘ └─────────────┘ └─────────────┘ +``` + +## Core Components + +### 1. Data Generators (`test-data-generators.js`) + +**Purpose**: Generate mathematical patterns for testing + +**Classes**: +- `DataGenerator` - Base class with common functionality +- `SineWaveGenerator` - Classic sine waves +- `SquareWaveGenerator` - Digital square waves +- `PerlinNoiseGenerator` - Smooth noise +- `ChirpGenerator` - Frequency sweeps +- `CompositeGenerator` - Combine multiple generators +- Many more... + +**Usage**: +```javascript +const generator = new SineWaveGenerator({ + frequency: 2.0, + amplitude: 30, + sampleRate: 100, +}); + +// Generate a line of points +const points = generator.generateLine(100, 800); +``` + +### 2. Data Sources (`data-sources.js`) + +**Purpose**: Provide data to plots via events + +**Key Concept**: Sources emit events when data is ready. They know *when* and *how* to provide data, but not *where* it goes. + +**Base Class**: +```javascript +class DataSource extends EventEmitter { + start() // Begin providing data + stop() // Stop providing data + emitLine() // Emit a complete line of data + emitPoint() // Emit a single data point +} +``` + +**Events**: +- `'line'` - Complete line of data ready: `{points, timestamp, metadata}` +- `'point'` - Single data point ready: `{value, timestamp}` +- `'error'` - Error occurred: `{error}` + +**Available Sources**: + +- **SyntheticDataSource** - Uses test generators, emits lines periodically +- **FunctionDataSource** - Evaluates a function (x,t) => y +- **StreamingDataSource** - Emits individual points at a sample rate +- **WebSocketDataSource** - Receives real-time data from WebSocket +- **CSVDataSource** - Replays data from CSV files +- **CompositeDataSource** - Combines multiple sources + +**Example**: +```javascript +// Create source +const source = new SyntheticDataSource({ + generator: new SineWaveGenerator({ frequency: 2.0 }), + pointsPerLine: 100, + width: 800, + lineInterval: 100, // ms between lines +}); + +// Listen to events +source.on('line', (data) => { + console.log('Received line:', data.points); +}); + +// Start generating +source.start(); +``` + +### 3. Plots (`timeseries-plot.js`) + +**Purpose**: Pure visualization - display data, nothing else + +**Key Concept**: Plots receive data via method calls. They know *how* to display data, but not *where* it comes from. + +**API**: +```javascript +class TimeSeriesPlot { + // Data input + addLine(points, metadata) // Add a line of data + addDataPoint(value, timestamp) // Add a single point + clearData() // Clear all data + + // Display control + setGridVisible(visible) + setScrollSpeed(speed) + setVerticalScale(scale) + setTitle(title) + + // Frame update + update() // Call every frame to scroll/render +} +``` + +**Example**: +```javascript +// Create plot +const plot = new TimeSeriesPlot({ + x: 0, + y: 0, + width: 800, + height: 600, + title: 'My Plot', + showGrid: true, +}); + +// Add to PixiJS stage +app.stage.addChild(plot.container); + +// Receive data +plot.addLine([ + {x: 0, y: 10}, + {x: 100, y: 20}, + {x: 200, y: 15}, +]); + +// Update every frame +app.ticker.add(() => plot.update()); +``` + +### 4. Connections (`plot-connections.js`) + +**Purpose**: Link data sources to plots + +**Key Concept**: Connections subscribe to source events and forward data to plots. They handle timing, buffering, and data transformation. + +**Connection Types**: + +**DirectConnection** - Lines go straight from source to plot +```javascript +const connection = new DirectConnection(source, plot); +connection.connect(); +``` + +**BufferedConnection** - Buffers individual points into lines +```javascript +const connection = new BufferedConnection(source, plot, { + bufferSize: 100, // Points per line + bufferTimeout: 1000, // Max time to wait (ms) +}); +connection.connect(); +``` + +**SynchronizedConnection** - Synchronizes multiple sources +```javascript +const connection = new SynchronizedConnection([source1, source2], plot, { + syncMode: 'wait-for-all', +}); +connection.connect(); +``` + +**Helper Functions**: +```javascript +// Quick setup for synthetic data +const conn = connectSyntheticData(generator, plot, { + lineInterval: 100, +}); + +// Quick setup for function-based +const conn = connectFunction((x, t) => Math.sin(x * 10 + t), plot); + +// All-in-one setup +const {plot, source, connection} = createConnectedPlot(app, plotConfig, sourceConfig); +``` + +## Data Flow + +### Scenario 1: Synthetic Data (Test Pattern) + +``` +DataGenerator SyntheticDataSource DirectConnection TimeSeriesPlot + │ │ │ │ + │ │ start() │ │ + │ ├───────────────────────>│ │ + │ │ │ │ + │ generateLine() │ │ │ + │<────────────────────────┤ │ │ + │ │ │ │ + │ returns points[] │ │ │ + ├────────────────────────>│ │ │ + │ │ │ │ + │ │ emit('line', data) │ │ + │ ├───────────────────────>│ │ + │ │ │ │ + │ │ │ addLine(points) │ + │ │ ├─────────────────────>│ + │ │ │ │ + │ │ │ │ update() + │ │ │ │ (scroll & render) +``` + +### Scenario 2: Real-Time Streaming + +``` +External System WebSocketDataSource BufferedConnection TimeSeriesPlot + │ │ │ │ + │ WebSocket message │ │ │ + ├────────────────────────>│ │ │ + │ │ │ │ + │ │ emit('point', value) │ │ + │ ├───────────────────────>│ │ + │ │ │ │ + │ │ │ buffer.push(value) │ + │ │ │ │ + │ │ emit('point') │ │ + │ ├───────────────────────>│ │ + │ │ │ │ + │ │ (continue...) │ │ + │ │ │ │ + │ │ │ buffer full! │ + │ │ │ │ + │ │ │ flush() │ + │ │ │ addLine(buffered) │ + │ │ ├─────────────────────>│ +``` + +## Usage Patterns + +### Pattern 1: Simple Test Visualization + +```javascript +import { TimeSeriesPlot } from './timeseries-plot.js'; +import { connectSyntheticData } from './plot-connections.js'; +import { TestDataFactory } from './test-data-generators.js'; + +// Create plot +const plot = new TimeSeriesPlot({...}); +app.stage.addChild(plot.container); + +// Connect data +const connection = connectSyntheticData( + TestDataFactory.createSimpleSine(30), + plot, + { lineInterval: 100 } +); + +// Update loop +app.ticker.add(() => plot.update()); +``` + +### Pattern 2: Swap Data Sources + +```javascript +// Start with one source +let connection = connectSyntheticData(generator1, plot); + +// Later, switch to different data +connection.disconnect(); +connection = connectSyntheticData(generator2, plot); +``` + +### Pattern 3: Multiple Plots, Different Data + +```javascript +const plot1 = new TimeSeriesPlot({x: 0, y: 0, ...}); +const plot2 = new TimeSeriesPlot({x: 800, y: 0, ...}); + +connectSyntheticData(sineGenerator, plot1); +connectSyntheticData(noiseGenerator, plot2); + +app.ticker.add(() => { + plot1.update(); + plot2.update(); +}); +``` + +### Pattern 4: Real-Time WebSocket Data + +```javascript +const plot = new TimeSeriesPlot({...}); + +const source = new WebSocketDataSource({ + url: 'ws://localhost:8080/data' +}); + +const connection = new BufferedConnection(source, plot, { + bufferSize: 100, +}); +connection.connect(); + +app.ticker.add(() => plot.update()); +``` + +### Pattern 5: Custom Function + +```javascript +const plot = new TimeSeriesPlot({...}); + +const connection = connectFunction( + (x, t) => Math.sin(x * 10 + t * 2) + Math.cos(x * 5 - t), + plot, + { lineInterval: 100, amplitude: 30 } +); + +app.ticker.add(() => plot.update()); +``` + +## File Organization + +``` +web-timeplot/src/ +├── test-data-generators.js # Math generators for test patterns +├── data-sources.js # Data provision (events) +├── timeseries-plot.js # Pure visualization +├── plot-connections.js # Glue layer +├── example-usage.js # Complete examples +├── main.js # Application entry point +├── state.js # App state management +└── waterfall.js # (Legacy - can be replaced) +``` + +## Migration Path + +### From Old WaterfallGraph to New Architecture + +**Old way** (tightly coupled): +```javascript +const graph = new WaterfallGraph({...}); +// Graph generates its own data +``` + +**New way** (separated): +```javascript +const plot = new TimeSeriesPlot({...}); +const source = new SyntheticDataSource({...}); +const connection = new DirectConnection(source, plot); +connection.connect(); +``` + +## Extension Points + +### Adding New Data Sources + +Extend `DataSource` and implement: +- `start()` - Begin providing data +- `stop()` - Stop providing data +- Call `emitLine()` or `emitPoint()` when data is ready + +Example: +```javascript +class MyCustomSource extends DataSource { + start() { + super.start(); + // Start your data provision mechanism + this.interval = setInterval(() => { + const points = [...]; // Generate points + this.emitLine(points); + }, 100); + } + + stop() { + super.stop(); + clearInterval(this.interval); + } +} +``` + +### Adding New Connection Types + +Extend `PlotConnection` and implement `setupSubscriptions()`: + +```javascript +class MyCustomConnection extends PlotConnection { + setupSubscriptions() { + const unsub = this.source.on('line', (data) => { + // Transform data as needed + const transformed = this.transformData(data); + this.plot.addLine(transformed.points); + }); + this.subscriptions.push(unsub); + } +} +``` + +## Benefits of This Architecture + +1. **Testability** - Each component can be tested independently +2. **Reusability** - Plots work with any data source +3. **Flexibility** - Easy to add new data sources or visualizations +4. **Maintainability** - Clear responsibilities, easy to understand +5. **Performance** - Can optimize each layer independently +6. **Real-world ready** - Supports actual data sources (WebSocket, files, etc.) + +## Next Steps + +- Replace old `waterfall.js` usage with new `TimeSeriesPlot` +- Create real data sources for your application +- Add more visualization types (heatmap, spectrogram, etc.) +- Add data processing layer (filtering, FFT, etc.) |
