1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
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.)
|