/** * Example Usage: Complete examples of the new architecture * * This file demonstrates how to use the separated data/visualization architecture: * - TimeSeriesPlot: Pure visualization * - DataSource: Data generation/provision * - Connections: Links between them */ import { Application } from 'pixi.js'; import { TimeSeriesPlot } from './timeseries-plot.js'; import { SyntheticDataSource, FunctionDataSource, StreamingDataSource, WebSocketDataSource, } from './data-sources.js'; import { DirectConnection, BufferedConnection, ConnectionManager, connectSyntheticData, connectFunction, createConnectedPlot, } from './plot-connections.js'; import { TestDataFactory, SineWaveGenerator, PerlinNoiseGenerator, ChirpGenerator, } from './test-data-generators.js'; // ============================================================================ // Example 1: Simple Setup - One plot, one data source // ============================================================================ export async function example1_SimpleSetup() { console.log('=== Example 1: Simple Setup ==='); // Create PixiJS app const app = new Application(); await app.init({ width: 800, height: 600, backgroundColor: 0x1a1a26, }); document.body.appendChild(app.canvas); // Create plot (visualization only) const plot = new TimeSeriesPlot({ x: 0, y: 0, width: 800, height: 600, title: 'Simple Sine Wave', showGrid: true, }); app.stage.addChild(plot.container); // Create data source const generator = TestDataFactory.createSimpleSine(30); const source = new SyntheticDataSource({ generator: generator, pointsPerLine: 100, width: 800, lineInterval: 100, // New line every 100ms }); // Connect source to plot const connection = new DirectConnection(source, plot); connection.connect(); // Update plot every frame app.ticker.add(() => { plot.update(); }); return { app, plot, source, connection }; } // ============================================================================ // Example 2: Quick Setup Using Helper Functions // ============================================================================ export async function example2_QuickSetup() { console.log('=== Example 2: Quick Setup ==='); const app = new Application(); await app.init({ width: 800, height: 600, backgroundColor: 0x1a1a26, }); document.body.appendChild(app.canvas); // One-liner setup! const { plot, source, connection } = createConnectedPlot( app, { x: 0, y: 0, width: 800, height: 600, title: 'Quick Setup', }, { generator: TestDataFactory.createComplexPattern(30), lineInterval: 100, } ); app.ticker.add(() => plot.update()); return { app, plot, source, connection }; } // ============================================================================ // Example 3: Multiple Plots with Different Data Sources // ============================================================================ export async function example3_MultiplePlots() { console.log('=== Example 3: Multiple Plots ==='); const app = new Application(); await app.init({ width: 1600, height: 600, backgroundColor: 0x1a1a26, }); document.body.appendChild(app.canvas); const width = 800; const height = 600; // Left plot: Sine wave const plot1 = new TimeSeriesPlot({ x: 0, y: 0, width: width, height: height, title: 'Sine Wave', color: 0xff6666, }); // Right plot: Perlin noise const plot2 = new TimeSeriesPlot({ x: width, y: 0, width: width, height: height, title: 'Perlin Noise', color: 0x66ff66, }); app.stage.addChild(plot1.container); app.stage.addChild(plot2.container); // Connect different data sources const conn1 = connectSyntheticData( TestDataFactory.createSimpleSine(30), plot1, { lineInterval: 100 } ); const conn2 = connectSyntheticData( TestDataFactory.createSmoothNoise(30), plot2, { lineInterval: 100 } ); app.ticker.add(() => { plot1.update(); plot2.update(); }); return { app, plots: [plot1, plot2], connections: [conn1, conn2] }; } // ============================================================================ // Example 4: Using Function-Based Data Source // ============================================================================ export async function example4_FunctionSource() { console.log('=== Example 4: Function Source ==='); const app = new Application(); await app.init({ width: 800, height: 600, backgroundColor: 0x1a1a26 }); document.body.appendChild(app.canvas); const plot = new TimeSeriesPlot({ x: 0, y: 0, width: 800, height: 600, title: 'Custom Function', }); app.stage.addChild(plot.container); // Define a custom function: (x, t) => y // x is normalized 0-1 across the width // t is time in seconds const customFunc = (x, t) => { // Create an interference pattern const wave1 = Math.sin(x * 10 + t * 2); const wave2 = Math.sin(x * 15 - t * 3); const wave3 = Math.cos(x * 8 + t * 1.5); return (wave1 + wave2 + wave3) / 3; }; const connection = connectFunction(customFunc, plot, { lineInterval: 100, amplitude: 30, }); app.ticker.add(() => plot.update()); return { app, plot, connection }; } // ============================================================================ // Example 5: Swapping Data Sources at Runtime // ============================================================================ export async function example5_SwappingSources() { console.log('=== Example 5: Swapping Sources ==='); const app = new Application(); await app.init({ width: 800, height: 600, backgroundColor: 0x1a1a26 }); document.body.appendChild(app.canvas); const plot = new TimeSeriesPlot({ x: 0, y: 0, width: 800, height: 600, title: 'Dynamic Source Switching', }); app.stage.addChild(plot.container); // Start with sine wave let currentConnection = connectSyntheticData( TestDataFactory.createSimpleSine(30), plot, { lineInterval: 100 } ); app.ticker.add(() => plot.update()); // Function to switch to a different data source const switchToSource = (generator, title) => { // Disconnect current source currentConnection.disconnect(); // Connect new source currentConnection = connectSyntheticData(generator, plot, { lineInterval: 100, }); plot.setTitle(title); console.log(`Switched to: ${title}`); }; // Example: Switch sources every 5 seconds let sourceIndex = 0; const sources = [ { gen: TestDataFactory.createSimpleSine(30), title: 'Sine Wave' }, { gen: TestDataFactory.createComplexPattern(30), title: 'Complex Pattern' }, { gen: TestDataFactory.createSmoothNoise(30), title: 'Perlin Noise' }, { gen: TestDataFactory.createFrequencySweep(30), title: 'Frequency Sweep' }, ]; setInterval(() => { sourceIndex = (sourceIndex + 1) % sources.length; const source = sources[sourceIndex]; switchToSource(source.gen, source.title); }, 5000); return { app, plot, switchToSource }; } // ============================================================================ // Example 6: Streaming Data with Buffering // ============================================================================ export async function example6_StreamingData() { console.log('=== Example 6: Streaming Data ==='); const app = new Application(); await app.init({ width: 800, height: 600, backgroundColor: 0x1a1a26 }); document.body.appendChild(app.canvas); const plot = new TimeSeriesPlot({ x: 0, y: 0, width: 800, height: 600, title: 'Streaming Data (Buffered)', }); app.stage.addChild(plot.container); // Create streaming source (emits individual points) const generator = new SineWaveGenerator({ frequency: 2.0, amplitude: 1.0, sampleRate: 60, }); const source = new StreamingDataSource({ generator: generator, sampleRate: 60, // 60 points per second }); // Use buffered connection to assemble points into lines const connection = new BufferedConnection(source, plot, { bufferSize: 100, // Buffer 100 points before creating a line bufferTimeout: 1000, // Or timeout after 1 second }); connection.connect(); app.ticker.add(() => plot.update()); return { app, plot, source, connection }; } // ============================================================================ // Example 7: Connection Manager (Managing Multiple Connections) // ============================================================================ export async function example7_ConnectionManager() { console.log('=== Example 7: Connection Manager ==='); const app = new Application(); await app.init({ width: 800, height: 600, backgroundColor: 0x1a1a26 }); document.body.appendChild(app.canvas); const plot = new TimeSeriesPlot({ x: 0, y: 0, width: 800, height: 600, title: 'Managed Connections', }); app.stage.addChild(plot.container); // Create connection manager const manager = new ConnectionManager(); // Add first connection const source1 = new SyntheticDataSource({ generator: TestDataFactory.createSimpleSine(30), pointsPerLine: 100, width: 800, lineInterval: 100, }); const connId1 = manager.connect(source1, plot, { type: 'direct' }); console.log('Connection ID:', connId1); app.ticker.add(() => plot.update()); // Later: disconnect and switch to different source setTimeout(() => { manager.disconnect(connId1); const source2 = new SyntheticDataSource({ generator: TestDataFactory.createFrequencySweep(30), pointsPerLine: 100, width: 800, lineInterval: 100, }); const connId2 = manager.connect(source2, plot, { type: 'direct' }); plot.setTitle('Frequency Sweep'); console.log('Switched to connection:', connId2); }, 5000); return { app, plot, manager }; } // ============================================================================ // Example 8: Complete Interactive Demo // ============================================================================ export async function example8_InteractiveDemo() { console.log('=== Example 8: Interactive Demo ==='); const app = new Application(); await app.init({ width: 1600, height: 800, backgroundColor: 0x1a1a26, }); document.body.appendChild(app.canvas); // Create two plots const plot1 = new TimeSeriesPlot({ x: 0, y: 0, width: 800, height: 800, title: 'Plot 1 - Press 1-5 to change', color: 0xff6666, }); const plot2 = new TimeSeriesPlot({ x: 800, y: 0, width: 800, height: 800, title: 'Plot 2 - Press 6-0 to change', color: 0x66ff66, }); app.stage.addChild(plot1.container); app.stage.addChild(plot2.container); // Connection manager const manager = new ConnectionManager(); // Available data sources const dataSources = { sine: () => TestDataFactory.createSimpleSine(30), complex: () => TestDataFactory.createComplexPattern(30), noise: () => TestDataFactory.createSmoothNoise(30), sweep: () => TestDataFactory.createFrequencySweep(30), burst: () => TestDataFactory.createBurstySignal(30), }; // Track current connections let conn1Id = null; let conn2Id = null; // Helper to switch source const switchSource = (plot, generatorFunc, title) => { // Disconnect old connection const connId = plot === plot1 ? conn1Id : conn2Id; if (connId !== null) { manager.disconnect(connId); } // Create new connection const source = new SyntheticDataSource({ generator: generatorFunc(), pointsPerLine: 100, width: plot.width, lineInterval: 100, }); const newConnId = manager.connect(source, plot, { type: 'direct' }); plot.setTitle(title); // Store connection ID if (plot === plot1) { conn1Id = newConnId; } else { conn2Id = newConnId; } }; // Initialize with default sources switchSource(plot1, dataSources.sine, 'Plot 1 - Sine Wave'); switchSource(plot2, dataSources.complex, 'Plot 2 - Complex Pattern'); // Keyboard controls window.addEventListener('keydown', (e) => { switch (e.key) { case '1': switchSource(plot1, dataSources.sine, 'Plot 1 - Sine Wave'); break; case '2': switchSource(plot1, dataSources.complex, 'Plot 1 - Complex Pattern'); break; case '3': switchSource(plot1, dataSources.noise, 'Plot 1 - Perlin Noise'); break; case '4': switchSource(plot1, dataSources.sweep, 'Plot 1 - Frequency Sweep'); break; case '5': switchSource(plot1, dataSources.burst, 'Plot 1 - Burst Signal'); break; case '6': switchSource(plot2, dataSources.sine, 'Plot 2 - Sine Wave'); break; case '7': switchSource(plot2, dataSources.complex, 'Plot 2 - Complex Pattern'); break; case '8': switchSource(plot2, dataSources.noise, 'Plot 2 - Perlin Noise'); break; case '9': switchSource(plot2, dataSources.sweep, 'Plot 2 - Frequency Sweep'); break; case '0': switchSource(plot2, dataSources.burst, 'Plot 2 - Burst Signal'); break; case 'g': plot1.setGridVisible(!plot1.showGrid); plot2.setGridVisible(!plot2.showGrid); break; case 'c': plot1.clearData(); plot2.clearData(); break; } }); // Update loop app.ticker.add(() => { plot1.update(); plot2.update(); }); console.log('Controls:'); console.log(' 1-5: Change Plot 1 source'); console.log(' 6-0: Change Plot 2 source'); console.log(' G: Toggle grid'); console.log(' C: Clear data'); return { app, plot1, plot2, manager }; } // ============================================================================ // Quick Test: Run one of the examples // ============================================================================ // Uncomment to run an example: // example1_SimpleSetup(); // example2_QuickSetup(); // example3_MultiplePlots(); // example4_FunctionSource(); // example5_SwappingSources(); // example6_StreamingData(); // example7_ConnectionManager(); //example8_InteractiveDemo();