summaryrefslogtreecommitdiff
path: root/web-timeplot/src/main.js
diff options
context:
space:
mode:
Diffstat (limited to 'web-timeplot/src/main.js')
-rw-r--r--web-timeplot/src/main.js220
1 files changed, 220 insertions, 0 deletions
diff --git a/web-timeplot/src/main.js b/web-timeplot/src/main.js
new file mode 100644
index 0000000..0d065f7
--- /dev/null
+++ b/web-timeplot/src/main.js
@@ -0,0 +1,220 @@
+import { Application } from 'pixi.js';
+import { WaterfallGraph } from './waterfall.js';
+import { PerformanceMetrics } from './metrics.js';
+
+class TimePlot {
+ constructor() {
+ this.app = null;
+ this.graphs = [];
+ this.metrics = new PerformanceMetrics(60, 10000);
+ this.showMetrics = true;
+ this.showGrid = true;
+ this.time = 0;
+ }
+
+ async init() {
+ const container = document.getElementById('canvas-container');
+
+ // Try WebGPU first, fallback to WebGL
+ let preferredRenderer = 'webgpu';
+ let preference = 'webgpu';
+
+ // Check WebGPU availability
+ if (!navigator.gpu) {
+ console.log('WebGPU not available, using WebGL');
+ preferredRenderer = 'webgl';
+ preference = 'webgl';
+ }
+
+ try {
+ this.app = new Application();
+
+ await this.app.init({
+ preference: preference,
+ width: window.innerWidth,
+ height: window.innerHeight - 60, // Account for controls
+ backgroundColor: 0x1a1a26,
+ antialias: true,
+ autoDensity: true,
+ resolution: window.devicePixelRatio || 1,
+ });
+
+ container.appendChild(this.app.canvas);
+
+ // Display actual renderer type
+ const rendererType = this.app.renderer.type;
+ document.getElementById('renderer-type').textContent = rendererType;
+ console.log('Using renderer:', rendererType);
+
+ // Create two waterfall graphs side by side
+ this.setupGraphs();
+ this.setupControls();
+ this.setupKeyboard();
+ this.handleResize();
+
+ // Start animation loop
+ this.app.ticker.add(() => this.update());
+
+ console.log('TimePlot initialized successfully');
+
+ } catch (error) {
+ console.error('Failed to initialize PixiJS:', error);
+
+ // Ultimate fallback
+ if (preference === 'webgpu') {
+ console.log('Retrying with WebGL...');
+ this.init(); // Retry will use WebGL
+ }
+ }
+ }
+
+ setupGraphs() {
+ const width = this.app.screen.width;
+ const height = this.app.screen.height;
+
+ // Left graph
+ const graph1 = new WaterfallGraph({
+ x: 0,
+ y: 0,
+ width: width / 2,
+ height: height,
+ title: 'Frequency vs Time',
+ color: 0xff6666,
+ });
+
+ // Right graph
+ const graph2 = new WaterfallGraph({
+ x: width / 2,
+ y: 0,
+ width: width / 2,
+ height: height,
+ title: 'Position vs Time',
+ color: 0x66ff66,
+ });
+
+ this.graphs.push(graph1, graph2);
+ this.graphs.forEach(graph => {
+ this.app.stage.addChild(graph.container);
+ });
+ }
+
+ setupControls() {
+ document.getElementById('toggle-grid').addEventListener('click', () => {
+ this.toggleGrid();
+ });
+
+ document.getElementById('toggle-metrics').addEventListener('click', () => {
+ this.toggleMetrics();
+ });
+
+ document.getElementById('export-metrics').addEventListener('click', () => {
+ this.exportMetrics();
+ });
+
+ this.updateControlButtons();
+ }
+
+ setupKeyboard() {
+ window.addEventListener('keydown', (e) => {
+ switch(e.key.toLowerCase()) {
+ case 'g':
+ this.toggleGrid();
+ break;
+ case 'm':
+ this.toggleMetrics();
+ break;
+ case 'e':
+ this.exportMetrics();
+ break;
+ }
+ });
+ }
+
+ handleResize() {
+ window.addEventListener('resize', () => {
+ const width = window.innerWidth;
+ const height = window.innerHeight - 60;
+
+ this.app.renderer.resize(width, height);
+
+ // Update graphs
+ this.graphs[0]?.resize(0, 0, width / 2, height);
+ this.graphs[1]?.resize(width / 2, 0, width / 2, height);
+ });
+ }
+
+ update() {
+ this.metrics.beginFrame();
+ this.metrics.beginUpdate();
+
+ this.time += 0.016; // ~60fps increment
+
+ // Update each graph
+ this.graphs.forEach((graph, idx) => {
+ graph.update(this.time, idx);
+ });
+
+ const updateMs = this.metrics.endUpdate();
+
+ this.metrics.beginRender();
+
+ // Rendering happens automatically via PixiJS
+
+ const renderMs = this.metrics.endRender();
+
+ // Calculate total vertices (estimate)
+ const vertexCount = this.graphs.reduce((sum, g) => sum + g.getVertexCount(), 0);
+ const lineCount = this.graphs.reduce((sum, g) => sum + g.getLineCount(), 0);
+
+ this.metrics.endFrame(updateMs, renderMs, vertexCount, lineCount);
+
+ // Update UI
+ if (this.showMetrics) {
+ this.updateMetricsDisplay();
+ }
+ }
+
+ toggleGrid() {
+ this.showGrid = !this.showGrid;
+ this.graphs.forEach(graph => graph.setGridVisible(this.showGrid));
+ this.updateControlButtons();
+ console.log('Grid:', this.showGrid ? 'ON' : 'OFF');
+ }
+
+ toggleMetrics() {
+ this.showMetrics = !this.showMetrics;
+ this.updateControlButtons();
+ console.log('Metrics:', this.showMetrics ? 'ON' : 'OFF');
+ }
+
+ exportMetrics() {
+ const csv = this.metrics.exportToCSV();
+ const blob = new Blob([csv], { type: 'text/csv' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `timeplot-metrics-${Date.now()}.csv`;
+ a.click();
+ URL.revokeObjectURL(url);
+ console.log('Metrics exported');
+ }
+
+ updateMetricsDisplay() {
+ const display = document.getElementById('metrics-display');
+ if (display) {
+ display.textContent = this.metrics.formatSummary();
+ }
+ }
+
+ updateControlButtons() {
+ const gridBtn = document.getElementById('toggle-grid');
+ const metricsBtn = document.getElementById('toggle-metrics');
+
+ gridBtn.classList.toggle('active', this.showGrid);
+ metricsBtn.classList.toggle('active', this.showMetrics);
+ }
+}
+
+// Initialize the application
+const app = new TimePlot();
+app.init().catch(console.error);