JavaScript data visualization library for creating interactive charts, graphs, and scientific visualizations
81
Functions for efficiently appending or prepending data to existing traces, ideal for real-time data visualization and streaming applications.
Appends new data to the end of existing trace arrays. Perfect for real-time data feeds and growing datasets.
/**
* Extends existing trace data arrays by appending new values
* @param graphDiv - DOM element ID (string) or element reference
* @param update - Object with arrays of new data to append
* @param indices - Array of trace indices to extend
* @param maxPoints - Maximum number of points to keep per trace (optional windowing)
* @returns Promise that resolves to the graph div element
*/
function extendTraces(
graphDiv: string | HTMLElement,
update: {[key: string]: any[][]},
indices: number | number[],
maxPoints?: number | number[]
): Promise<HTMLElement>;Usage Examples:
import Plotly from 'plotly.js-dist';
// Basic data extension - add single point
await Plotly.extendTraces('chart', {
x: [[new Date()]], // New x value for first trace
y: [[Math.random()]] // New y value for first trace
}, [0]);
// Extend multiple traces with multiple points
await Plotly.extendTraces('chart', {
x: [[1, 2, 3], [4, 5, 6]], // 3 points for trace 0, 3 points for trace 1
y: [[10, 11, 12], [20, 21, 22]] // Corresponding y values
}, [0, 1]);
// Real-time data streaming with windowing
await Plotly.extendTraces('chart', {
x: [[Date.now()]],
y: [[sensorReading]]
}, [0], 100); // Keep only last 100 points
// Different window sizes per trace
await Plotly.extendTraces('chart', {
x: [[time], [time]],
y: [[value1], [value2]]
}, [0, 1], [50, 200]); // Trace 0: 50 points, Trace 1: 200 points
// Streaming with timestamps
const timestamp = new Date().toISOString();
await Plotly.extendTraces('chart', {
x: [[timestamp]],
y: [[temperature]],
text: [[`Temp: ${temperature}°C`]]
}, [0]);Adds new data to the beginning of existing trace arrays. Useful for reverse chronological data or when new data should appear at the start.
/**
* Extends existing trace data arrays by prepending new values
* @param graphDiv - DOM element ID (string) or element reference
* @param update - Object with arrays of new data to prepend
* @param indices - Array of trace indices to extend
* @param maxPoints - Maximum number of points to keep per trace (optional windowing)
* @returns Promise that resolves to the graph div element
*/
function prependTraces(
graphDiv: string | HTMLElement,
update: {[key: string]: any[][]},
indices: number | number[],
maxPoints?: number | number[]
): Promise<HTMLElement>;Usage Examples:
// Prepend single data point
await Plotly.prependTraces('chart', {
x: [[0]],
y: [[initialValue]]
}, [0]);
// Prepend multiple points to multiple traces
await Plotly.prependTraces('chart', {
x: [[-3, -2, -1], [-6, -5, -4]],
y: [[1, 2, 3], [4, 5, 6]]
}, [0, 1]);
// Prepend with windowing (remove from end)
await Plotly.prependTraces('chart', {
x: [[earlierTime]],
y: [[earlierValue]]
}, [0], 100); // Keep only first 100 points
// Historical data loading (newest first)
const historicalData = await fetchHistoricalData();
await Plotly.prependTraces('chart', {
x: [historicalData.timestamps],
y: [historicalData.values]
}, [0], 1000);class RealTimeChart {
constructor(chartId, maxPoints = 100) {
this.chartId = chartId;
this.maxPoints = maxPoints;
this.isStreaming = false;
}
async startStreaming(dataSource, interval = 1000) {
this.isStreaming = true;
while (this.isStreaming) {
try {
const newData = await dataSource.getNext();
await Plotly.extendTraces(this.chartId, {
x: [[new Date()]],
y: [[newData.value]]
}, [0], this.maxPoints);
await new Promise(resolve => setTimeout(resolve, interval));
} catch (error) {
console.error('Streaming error:', error);
this.stopStreaming();
}
}
}
stopStreaming() {
this.isStreaming = false;
}
}
// Usage
const chart = new RealTimeChart('live-chart', 200);
await chart.startStreaming(sensorDataSource, 500);// Stream data for multiple sensors
async function streamMultipleSensors(sensorData) {
const xData = [];
const yData = [];
const now = new Date();
sensorData.forEach(sensor => {
xData.push([now]);
yData.push([sensor.reading]);
});
await Plotly.extendTraces('multi-sensor-chart', {
x: xData,
y: yData
}, Array.from({length: sensorData.length}, (_, i) => i), 300);
}
// Stream every second
setInterval(async () => {
const readings = await Promise.all([
sensor1.read(),
sensor2.read(),
sensor3.read()
]);
await streamMultipleSensors(readings);
}, 1000);// Collect data in batches for better performance
class BatchStreamer {
constructor(chartId, batchSize = 10, maxPoints = 1000) {
this.chartId = chartId;
this.batchSize = batchSize;
this.maxPoints = maxPoints;
this.buffer = {x: [], y: []};
}
addDataPoint(x, y) {
this.buffer.x.push(x);
this.buffer.y.push(y);
if (this.buffer.x.length >= this.batchSize) {
this.flush();
}
}
async flush() {
if (this.buffer.x.length > 0) {
await Plotly.extendTraces(this.chartId, {
x: [this.buffer.x],
y: [this.buffer.y]
}, [0], this.maxPoints);
this.buffer = {x: [], y: []};
}
}
}// Maintain a fixed time window (e.g., last 5 minutes)
class TimeWindowChart {
constructor(chartId, windowMs = 300000) { // 5 minutes
this.chartId = chartId;
this.windowMs = windowMs;
}
async addDataPoint(value) {
const now = Date.now();
// Add new point
await Plotly.extendTraces(this.chartId, {
x: [[now]],
y: [[value]]
}, [0]);
// Remove old points outside window
const chartDiv = document.getElementById(this.chartId);
const trace = chartDiv.data[0];
const cutoffTime = now - this.windowMs;
const validIndices = trace.x.findIndex(x => x >= cutoffTime);
if (validIndices > 0) {
await Plotly.restyle(this.chartId, {
x: [trace.x.slice(validIndices)],
y: [trace.y.slice(validIndices)]
}, [0]);
}
}
}// Only extend if data meets certain criteria
async function conditionalExtend(chartId, newData, threshold = 0) {
const filteredData = newData.filter(point => point.value > threshold);
if (filteredData.length > 0) {
await Plotly.extendTraces(chartId, {
x: [filteredData.map(p => p.timestamp)],
y: [filteredData.map(p => p.value)]
}, [0]);
}
}// Limit update frequency to prevent overwhelming the UI
class RateLimitedStream {
constructor(chartId, minInterval = 100) {
this.chartId = chartId;
this.minInterval = minInterval;
this.lastUpdate = 0;
this.pendingData = [];
}
async addData(x, y) {
this.pendingData.push({x, y});
const now = Date.now();
if (now - this.lastUpdate >= this.minInterval) {
await this.flushPending();
this.lastUpdate = now;
}
}
async flushPending() {
if (this.pendingData.length > 0) {
const xData = this.pendingData.map(d => d.x);
const yData = this.pendingData.map(d => d.y);
await Plotly.extendTraces(this.chartId, {
x: [xData],
y: [yData]
}, [0]);
this.pendingData = [];
}
}
}// Use typed arrays for better performance with large datasets
const bufferSize = 1000;
const xBuffer = new Float64Array(bufferSize);
const yBuffer = new Float32Array(bufferSize);
let bufferIndex = 0;
function addPoint(x, y) {
xBuffer[bufferIndex] = x;
yBuffer[bufferIndex] = y;
bufferIndex = (bufferIndex + 1) % bufferSize;
// Update plot every N points
if (bufferIndex % 10 === 0) {
const recentX = Array.from(xBuffer.slice(Math.max(0, bufferIndex - 10), bufferIndex));
const recentY = Array.from(yBuffer.slice(Math.max(0, bufferIndex - 10), bufferIndex));
Plotly.extendTraces('chart', {x: [recentX], y: [recentY]}, [0], 1000);
}
}// Use scattergl for high-frequency streaming
const initialData = [{
x: [],
y: [],
type: 'scattergl', // WebGL-accelerated rendering
mode: 'lines',
name: 'High Frequency Data'
}];
await Plotly.newPlot('high-freq-chart', initialData);
// Stream data at high frequency
setInterval(async () => {
await Plotly.extendTraces('high-freq-chart', {
x: [[Date.now()]],
y: [[Math.sin(Date.now() / 1000)]]
}, [0], 10000); // Keep 10,000 points
}, 16); // ~60 FPSinterface StreamingEvents {
'plotly_afterplot': () => void;
'plotly_autosize': () => void;
'plotly_beforeplot': () => void;
}Usage Examples:
const chartDiv = document.getElementById('streaming-chart');
// Detect when streaming updates complete
chartDiv.on('plotly_afterplot', () => {
console.log('Streaming update rendered');
});
// Handle plot resizing during streaming
chartDiv.on('plotly_autosize', () => {
console.log('Plot resized during streaming');
});// Handle streaming errors gracefully
async function safeExtendTraces(chartId, update, indices, maxPoints) {
try {
await Plotly.extendTraces(chartId, update, indices, maxPoints);
} catch (error) {
console.error('Failed to extend traces:', error);
// Fallback: clear and restart
if (error.message.includes('data')) {
console.warn('Clearing chart data and restarting');
await Plotly.react(chartId, [{x: [], y: [], type: 'scatter'}]);
}
}
}// Data update structure for streaming
interface StreamUpdate {
x?: any[][];
y?: any[][];
z?: any[][];
text?: string[][];
marker?: {
color?: any[][];
size?: number[][];
};
[key: string]: any[][];
}
// Windowing configuration
type MaxPoints = number | number[];
// Streaming configuration options
interface StreamingConfig {
maxPoints?: MaxPoints;
updateInterval?: number;
batchSize?: number;
enableWebGL?: boolean;
timeWindow?: number;
}Install with Tessl CLI
npx tessl i tessl/npm-plotly-js-distdocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10