0
# Data Streaming
1
2
Functions for efficiently appending or prepending data to existing traces, ideal for real-time data visualization and streaming applications.
3
4
## Capabilities
5
6
### extendTraces
7
8
Appends new data to the end of existing trace arrays. Perfect for real-time data feeds and growing datasets.
9
10
```javascript { .api }
11
/**
12
* Extends existing trace data arrays by appending new values
13
* @param graphDiv - DOM element ID (string) or element reference
14
* @param update - Object with arrays of new data to append
15
* @param indices - Array of trace indices to extend
16
* @param maxPoints - Maximum number of points to keep per trace (optional windowing)
17
* @returns Promise that resolves to the graph div element
18
*/
19
function extendTraces(
20
graphDiv: string | HTMLElement,
21
update: {[key: string]: any[][]},
22
indices: number | number[],
23
maxPoints?: number | number[]
24
): Promise<HTMLElement>;
25
```
26
27
**Usage Examples:**
28
29
```javascript
30
import Plotly from 'plotly.js-dist';
31
32
// Basic data extension - add single point
33
await Plotly.extendTraces('chart', {
34
x: [[new Date()]], // New x value for first trace
35
y: [[Math.random()]] // New y value for first trace
36
}, [0]);
37
38
// Extend multiple traces with multiple points
39
await Plotly.extendTraces('chart', {
40
x: [[1, 2, 3], [4, 5, 6]], // 3 points for trace 0, 3 points for trace 1
41
y: [[10, 11, 12], [20, 21, 22]] // Corresponding y values
42
}, [0, 1]);
43
44
// Real-time data streaming with windowing
45
await Plotly.extendTraces('chart', {
46
x: [[Date.now()]],
47
y: [[sensorReading]]
48
}, [0], 100); // Keep only last 100 points
49
50
// Different window sizes per trace
51
await Plotly.extendTraces('chart', {
52
x: [[time], [time]],
53
y: [[value1], [value2]]
54
}, [0, 1], [50, 200]); // Trace 0: 50 points, Trace 1: 200 points
55
56
// Streaming with timestamps
57
const timestamp = new Date().toISOString();
58
await Plotly.extendTraces('chart', {
59
x: [[timestamp]],
60
y: [[temperature]],
61
text: [[`Temp: ${temperature}°C`]]
62
}, [0]);
63
```
64
65
### prependTraces
66
67
Adds new data to the beginning of existing trace arrays. Useful for reverse chronological data or when new data should appear at the start.
68
69
```javascript { .api }
70
/**
71
* Extends existing trace data arrays by prepending new values
72
* @param graphDiv - DOM element ID (string) or element reference
73
* @param update - Object with arrays of new data to prepend
74
* @param indices - Array of trace indices to extend
75
* @param maxPoints - Maximum number of points to keep per trace (optional windowing)
76
* @returns Promise that resolves to the graph div element
77
*/
78
function prependTraces(
79
graphDiv: string | HTMLElement,
80
update: {[key: string]: any[][]},
81
indices: number | number[],
82
maxPoints?: number | number[]
83
): Promise<HTMLElement>;
84
```
85
86
**Usage Examples:**
87
88
```javascript
89
// Prepend single data point
90
await Plotly.prependTraces('chart', {
91
x: [[0]],
92
y: [[initialValue]]
93
}, [0]);
94
95
// Prepend multiple points to multiple traces
96
await Plotly.prependTraces('chart', {
97
x: [[-3, -2, -1], [-6, -5, -4]],
98
y: [[1, 2, 3], [4, 5, 6]]
99
}, [0, 1]);
100
101
// Prepend with windowing (remove from end)
102
await Plotly.prependTraces('chart', {
103
x: [[earlierTime]],
104
y: [[earlierValue]]
105
}, [0], 100); // Keep only first 100 points
106
107
// Historical data loading (newest first)
108
const historicalData = await fetchHistoricalData();
109
await Plotly.prependTraces('chart', {
110
x: [historicalData.timestamps],
111
y: [historicalData.values]
112
}, [0], 1000);
113
```
114
115
## Real-Time Streaming Patterns
116
117
### Continuous Data Stream
118
119
```javascript
120
class RealTimeChart {
121
constructor(chartId, maxPoints = 100) {
122
this.chartId = chartId;
123
this.maxPoints = maxPoints;
124
this.isStreaming = false;
125
}
126
127
async startStreaming(dataSource, interval = 1000) {
128
this.isStreaming = true;
129
130
while (this.isStreaming) {
131
try {
132
const newData = await dataSource.getNext();
133
await Plotly.extendTraces(this.chartId, {
134
x: [[new Date()]],
135
y: [[newData.value]]
136
}, [0], this.maxPoints);
137
138
await new Promise(resolve => setTimeout(resolve, interval));
139
} catch (error) {
140
console.error('Streaming error:', error);
141
this.stopStreaming();
142
}
143
}
144
}
145
146
stopStreaming() {
147
this.isStreaming = false;
148
}
149
}
150
151
// Usage
152
const chart = new RealTimeChart('live-chart', 200);
153
await chart.startStreaming(sensorDataSource, 500);
154
```
155
156
### Multi-Series Streaming
157
158
```javascript
159
// Stream data for multiple sensors
160
async function streamMultipleSensors(sensorData) {
161
const xData = [];
162
const yData = [];
163
const now = new Date();
164
165
sensorData.forEach(sensor => {
166
xData.push([now]);
167
yData.push([sensor.reading]);
168
});
169
170
await Plotly.extendTraces('multi-sensor-chart', {
171
x: xData,
172
y: yData
173
}, Array.from({length: sensorData.length}, (_, i) => i), 300);
174
}
175
176
// Stream every second
177
setInterval(async () => {
178
const readings = await Promise.all([
179
sensor1.read(),
180
sensor2.read(),
181
sensor3.read()
182
]);
183
await streamMultipleSensors(readings);
184
}, 1000);
185
```
186
187
### Batch Data Extension
188
189
```javascript
190
// Collect data in batches for better performance
191
class BatchStreamer {
192
constructor(chartId, batchSize = 10, maxPoints = 1000) {
193
this.chartId = chartId;
194
this.batchSize = batchSize;
195
this.maxPoints = maxPoints;
196
this.buffer = {x: [], y: []};
197
}
198
199
addDataPoint(x, y) {
200
this.buffer.x.push(x);
201
this.buffer.y.push(y);
202
203
if (this.buffer.x.length >= this.batchSize) {
204
this.flush();
205
}
206
}
207
208
async flush() {
209
if (this.buffer.x.length > 0) {
210
await Plotly.extendTraces(this.chartId, {
211
x: [this.buffer.x],
212
y: [this.buffer.y]
213
}, [0], this.maxPoints);
214
215
this.buffer = {x: [], y: []};
216
}
217
}
218
}
219
```
220
221
### Time-Based Windowing
222
223
```javascript
224
// Maintain a fixed time window (e.g., last 5 minutes)
225
class TimeWindowChart {
226
constructor(chartId, windowMs = 300000) { // 5 minutes
227
this.chartId = chartId;
228
this.windowMs = windowMs;
229
}
230
231
async addDataPoint(value) {
232
const now = Date.now();
233
234
// Add new point
235
await Plotly.extendTraces(this.chartId, {
236
x: [[now]],
237
y: [[value]]
238
}, [0]);
239
240
// Remove old points outside window
241
const chartDiv = document.getElementById(this.chartId);
242
const trace = chartDiv.data[0];
243
const cutoffTime = now - this.windowMs;
244
245
const validIndices = trace.x.findIndex(x => x >= cutoffTime);
246
if (validIndices > 0) {
247
await Plotly.restyle(this.chartId, {
248
x: [trace.x.slice(validIndices)],
249
y: [trace.y.slice(validIndices)]
250
}, [0]);
251
}
252
}
253
}
254
```
255
256
## Advanced Streaming Features
257
258
### Conditional Data Extension
259
260
```javascript
261
// Only extend if data meets certain criteria
262
async function conditionalExtend(chartId, newData, threshold = 0) {
263
const filteredData = newData.filter(point => point.value > threshold);
264
265
if (filteredData.length > 0) {
266
await Plotly.extendTraces(chartId, {
267
x: [filteredData.map(p => p.timestamp)],
268
y: [filteredData.map(p => p.value)]
269
}, [0]);
270
}
271
}
272
```
273
274
### Data Rate Limiting
275
276
```javascript
277
// Limit update frequency to prevent overwhelming the UI
278
class RateLimitedStream {
279
constructor(chartId, minInterval = 100) {
280
this.chartId = chartId;
281
this.minInterval = minInterval;
282
this.lastUpdate = 0;
283
this.pendingData = [];
284
}
285
286
async addData(x, y) {
287
this.pendingData.push({x, y});
288
289
const now = Date.now();
290
if (now - this.lastUpdate >= this.minInterval) {
291
await this.flushPending();
292
this.lastUpdate = now;
293
}
294
}
295
296
async flushPending() {
297
if (this.pendingData.length > 0) {
298
const xData = this.pendingData.map(d => d.x);
299
const yData = this.pendingData.map(d => d.y);
300
301
await Plotly.extendTraces(this.chartId, {
302
x: [xData],
303
y: [yData]
304
}, [0]);
305
306
this.pendingData = [];
307
}
308
}
309
}
310
```
311
312
## Performance Optimization
313
314
### Efficient Data Structures
315
316
```javascript
317
// Use typed arrays for better performance with large datasets
318
const bufferSize = 1000;
319
const xBuffer = new Float64Array(bufferSize);
320
const yBuffer = new Float32Array(bufferSize);
321
let bufferIndex = 0;
322
323
function addPoint(x, y) {
324
xBuffer[bufferIndex] = x;
325
yBuffer[bufferIndex] = y;
326
bufferIndex = (bufferIndex + 1) % bufferSize;
327
328
// Update plot every N points
329
if (bufferIndex % 10 === 0) {
330
const recentX = Array.from(xBuffer.slice(Math.max(0, bufferIndex - 10), bufferIndex));
331
const recentY = Array.from(yBuffer.slice(Math.max(0, bufferIndex - 10), bufferIndex));
332
333
Plotly.extendTraces('chart', {x: [recentX], y: [recentY]}, [0], 1000);
334
}
335
}
336
```
337
338
### WebGL for High-Frequency Data
339
340
```javascript
341
// Use scattergl for high-frequency streaming
342
const initialData = [{
343
x: [],
344
y: [],
345
type: 'scattergl', // WebGL-accelerated rendering
346
mode: 'lines',
347
name: 'High Frequency Data'
348
}];
349
350
await Plotly.newPlot('high-freq-chart', initialData);
351
352
// Stream data at high frequency
353
setInterval(async () => {
354
await Plotly.extendTraces('high-freq-chart', {
355
x: [[Date.now()]],
356
y: [[Math.sin(Date.now() / 1000)]]
357
}, [0], 10000); // Keep 10,000 points
358
}, 16); // ~60 FPS
359
```
360
361
## Event Handling
362
363
```javascript { .api }
364
interface StreamingEvents {
365
'plotly_afterplot': () => void;
366
'plotly_autosize': () => void;
367
'plotly_beforeplot': () => void;
368
}
369
```
370
371
**Usage Examples:**
372
373
```javascript
374
const chartDiv = document.getElementById('streaming-chart');
375
376
// Detect when streaming updates complete
377
chartDiv.on('plotly_afterplot', () => {
378
console.log('Streaming update rendered');
379
});
380
381
// Handle plot resizing during streaming
382
chartDiv.on('plotly_autosize', () => {
383
console.log('Plot resized during streaming');
384
});
385
```
386
387
## Error Handling
388
389
```javascript
390
// Handle streaming errors gracefully
391
async function safeExtendTraces(chartId, update, indices, maxPoints) {
392
try {
393
await Plotly.extendTraces(chartId, update, indices, maxPoints);
394
} catch (error) {
395
console.error('Failed to extend traces:', error);
396
397
// Fallback: clear and restart
398
if (error.message.includes('data')) {
399
console.warn('Clearing chart data and restarting');
400
await Plotly.react(chartId, [{x: [], y: [], type: 'scatter'}]);
401
}
402
}
403
}
404
```
405
406
## Types
407
408
```javascript { .api }
409
// Data update structure for streaming
410
interface StreamUpdate {
411
x?: any[][];
412
y?: any[][];
413
z?: any[][];
414
text?: string[][];
415
marker?: {
416
color?: any[][];
417
size?: number[][];
418
};
419
[key: string]: any[][];
420
}
421
422
// Windowing configuration
423
type MaxPoints = number | number[];
424
425
// Streaming configuration options
426
interface StreamingConfig {
427
maxPoints?: MaxPoints;
428
updateInterval?: number;
429
batchSize?: number;
430
enableWebGL?: boolean;
431
timeWindow?: number;
432
}
433
```