CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-plotly-js-dist

JavaScript data visualization library for creating interactive charts, graphs, and scientific visualizations

81

1.02x
Overview
Eval results
Files

data-streaming.mddocs/

Data Streaming

Functions for efficiently appending or prepending data to existing traces, ideal for real-time data visualization and streaming applications.

Capabilities

extendTraces

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]);

prependTraces

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);

Real-Time Streaming Patterns

Continuous Data Stream

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);

Multi-Series Streaming

// 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);

Batch Data Extension

// 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: []};
    }
  }
}

Time-Based Windowing

// 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]);
    }
  }
}

Advanced Streaming Features

Conditional Data Extension

// 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]);
  }
}

Data Rate Limiting

// 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 = [];
    }
  }
}

Performance Optimization

Efficient Data Structures

// 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);
  }
}

WebGL for High-Frequency Data

// 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 FPS

Event Handling

interface 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');
});

Error Handling

// 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'}]);
    }
  }
}

Types

// 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-dist

docs

animation.md

chart-types.md

core-plotting.md

data-streaming.md

data-updates.md

export-utilities.md

index.md

interactive-components.md

layout-system.md

trace-management.md

tile.json