#ifndef GRAPH_VIEWER_H
#define GRAPH_VIEWER_H

const char graph_viewer_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CAN Signal Graph Viewer</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: #1a1a1a;
            color: white;
            overflow-x: hidden;
        }
        .header {
            background: linear-gradient(135deg, #43cea2 0%, #185a9d 100%);
            padding: 15px;
            text-align: center;
            box-shadow: 0 2px 10px rgba(0,0,0,0.3);
        }
        .header h1 { font-size: 1.5em; }
        .header p { font-size: 0.9em; opacity: 0.9; margin-top: 5px; }
        .controls {
            background: #2a2a2a;
            padding: 10px 15px;
            display: flex;
            gap: 10px;
            justify-content: center;
            flex-wrap: wrap;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
        }
        .control-group {
            display: flex;
            gap: 10px;
            align-items: center;
        }
        .control-label {
            font-size: 0.85em;
            color: #aaa;
        }
        .btn {
            padding: 8px 20px;
            border: none;
            border-radius: 5px;
            font-size: 0.9em;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s;
        }
        .btn-success { background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); color: white; }
        .btn-danger { background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%); color: white; }
        .btn-info { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; }
        .btn-warning { background: linear-gradient(135deg, #f2994a 0%, #f2c94c 100%); color: white; }
        .btn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0,0,0,0.3); }
        .btn.active { 
            box-shadow: inset 0 3px 5px rgba(0,0,0,0.3);
            transform: translateY(0);
        }
        
        .graphs {
            padding: 15px;
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(450px, 1fr));
            gap: 15px;
        }
        .graph-container {
            background: #2a2a2a;
            padding: 15px;
            border-radius: 10px;
            border: 2px solid #3a3a3a;
        }
        .graph-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 10px;
            padding-bottom: 8px;
            border-bottom: 2px solid #43cea2;
        }
        .graph-title {
            font-size: 1em;
            font-weight: 600;
            color: #43cea2;
        }
        .graph-value {
            font-size: 1.2em;
            font-weight: 700;
            color: #38ef7d;
            font-family: 'Courier New', monospace;
        }
        canvas {
            width: 100%;
            height: 250px;
            border: 1px solid #3a3a3a;
            border-radius: 5px;
            background: #1a1a1a;
        }
        .status {
            position: fixed;
            top: 10px;
            right: 10px;
            padding: 10px 15px;
            border-radius: 5px;
            background: #2a2a2a;
            border: 2px solid #43cea2;
            font-size: 0.9em;
            z-index: 1000;
        }
        .status.disconnected {
            border-color: #eb3349;
            background: #3a2a2a;
        }
        
        @media (max-width: 768px) {
            .graphs { grid-template-columns: 1fr; }
            canvas { height: 200px; }
        }
    </style>
</head>
<body>
    <div class="header">
        <h1>Real-time CAN Signal Graphs (Scatter Mode)</h1>
        <p>Viewing <span id="graph-count">0</span> signals</p>
    </div>
    
    <div class="controls">
        <div class="control-group">
            <button class="btn btn-success" onclick="startGraphing()">Start</button>
            <button class="btn btn-danger" onclick="stopGraphing()">Stop</button>
            <button class="btn btn-danger" onclick="window.close()">Close</button>
        </div>
        <div class="control-group">
            <span class="control-label">X-Axis Scale:</span>
            <button class="btn btn-info active" id="btn-index-mode" onclick="setScaleMode('index')">Index</button>
            <button class="btn btn-info" id="btn-time-mode" onclick="setScaleMode('time')">Time-Based</button>
        </div>
        <div class="control-group">
            <span class="control-label">X-Axis Range:</span>
            <button class="btn btn-warning active" id="btn-range-10s" onclick="setRangeMode('10s')">10s Window</button>
            <button class="btn btn-warning" id="btn-range-all" onclick="setRangeMode('all')">All Time</button>
        </div>
        <div class="control-group">
            <span class="control-label">Sort by:</span>
            <button class="btn btn-info active" id="btn-sort-selection" onclick="setSortMode('selection')">Selection Order</button>
            <button class="btn btn-info" id="btn-sort-name-asc" onclick="setSortMode('name-asc')">Name (A→Z)</button>
            <button class="btn btn-info" id="btn-sort-name-desc" onclick="setSortMode('name-desc')">Name (Z→A)</button>
        </div>
        <div class="control-group">
            <button class="btn btn-success" onclick="downloadCSV()" style="background: linear-gradient(135deg, #2ecc71 0%, #27ae60 100%);">
                📥 Download CSV
            </button>
            <button class="btn btn-danger" onclick="clearData()" style="background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);">
                🗑️ Clear Data
            </button>
        </div>
    </div>
    
    <div class="status" id="status">Connecting...</div>
    
    <div style="padding: 10px 15px; background: #2a2a2a; color: #aaa; font-size: 0.85em; display: flex; justify-content: space-between; align-items: center;">
        <span>Data Points: <strong id="data-point-count" style="color: #43cea2;">0</strong></span>
        <span>Recording Time: <strong id="recording-time" style="color: #43cea2;">0s</strong></span>
    </div>
    
    <div class="graphs" id="graphs"></div>
    
    <script>
        let ws;
        let charts = {};
        let graphing = false;
        let startTime = 0;
        let selectedSignals = [];
        let dbcData = {};
        let lastTimestamps = {};
        let sortMode = 'selection';
        const MAX_DATA_POINTS = 60;
        let scaleMode = 'index';
        let rangeMode = '10s';
        const COLORS = [
            {line: '#FF6384', fill: 'rgba(255, 99, 132, 0.2)'},
            {line: '#36A2EB', fill: 'rgba(54, 162, 235, 0.2)'},
            {line: '#FFCE56', fill: 'rgba(255, 206, 86, 0.2)'},
            {line: '#4BC0C0', fill: 'rgba(75, 192, 192, 0.2)'},
            {line: '#9966FF', fill: 'rgba(153, 102, 255, 0.2)'},
            {line: '#FF9F40', fill: 'rgba(255, 159, 64, 0.2)'},
            {line: '#FF6384', fill: 'rgba(255, 99, 132, 0.2)'},
            {line: '#4BC0C0', fill: 'rgba(75, 192, 192, 0.2)'},
            {line: '#FFCE56', fill: 'rgba(255, 206, 86, 0.2)'},
            {line: '#9966FF', fill: 'rgba(153, 102, 255, 0.2)'},
            {line: '#36A2EB', fill: 'rgba(54, 162, 235, 0.2)'},
            {line: '#FF9F40', fill: 'rgba(255, 159, 64, 0.2)'},
            {line: '#E74C3C', fill: 'rgba(231, 76, 60, 0.2)'},
            {line: '#3498DB', fill: 'rgba(52, 152, 219, 0.2)'},
            {line: '#2ECC71', fill: 'rgba(46, 204, 113, 0.2)'},
            {line: '#F39C12', fill: 'rgba(243, 156, 18, 0.2)'},
            {line: '#9B59B6', fill: 'rgba(155, 89, 182, 0.2)'},
            {line: '#1ABC9C', fill: 'rgba(26, 188, 156, 0.2)'},
            {line: '#E67E22', fill: 'rgba(230, 126, 34, 0.2)'},
            {line: '#95A5A6', fill: 'rgba(149, 165, 166, 0.2)'}
        ];
        
        class SimpleChart {
            constructor(canvas, signal, colorIndex) {
                this.canvas = canvas;
                this.ctx = canvas.getContext('2d');
                this.signal = signal;
                this.data = [];
                this.times = [];
                this.labels = [];
                this.rawValues = [];  // 원본 숫자값 저장
                this.colors = COLORS[colorIndex % COLORS.length];
                this.currentValue = 0;
                this.currentText = '';  // 현재 텍스트 값
                
                this.resizeCanvas();
                window.addEventListener('resize', () => this.resizeCanvas());
            }
            
            resizeCanvas() {
                const rect = this.canvas.getBoundingClientRect();
                this.canvas.width = rect.width * window.devicePixelRatio;
                this.canvas.height = rect.height * window.devicePixelRatio;
                this.ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
                this.width = rect.width;
                this.height = rect.height;
                this.draw();
            }
            
            addData(value, time) {
                this.data.push(value);
                this.times.push(parseFloat(time));
                this.labels.push(time);
                this.rawValues.push(value);
                this.currentValue = value;
                
                // Value Table이 있으면 텍스트 값 저장
                if (this.signal.valueTable && this.signal.valueTable[value] !== undefined) {
                    this.currentText = this.signal.valueTable[value];
                } else {
                    this.currentText = '';
                }
                
                if (scaleMode === 'index' && this.data.length > MAX_DATA_POINTS) {
                    this.data.shift();
                    this.times.shift();
                    this.labels.shift();
                    this.rawValues.shift();
                }
                
                this.draw();
            }
            
            getValueText(value) {
                if (this.signal.valueTable && this.signal.valueTable[value] !== undefined) {
                    return this.signal.valueTable[value];
                }
                return value.toFixed(2);
            }
            
            draw() {
                if (this.data.length === 0) return;
                
                const ctx = this.ctx;
                const padding = 40;
                const graphWidth = this.width - padding * 2;
                const graphHeight = this.height - padding * 2;
                
                ctx.clearRect(0, 0, this.width, this.height);
                
                let displayData = [];
                let displayTimes = [];
                let displayLabels = [];
                
                if (rangeMode === '10s') {
                    const currentTime = this.times[this.times.length - 1];
                    for (let i = 0; i < this.times.length; i++) {
                        if (currentTime - this.times[i] <= 10) {
                            displayData.push(this.data[i]);
                            displayTimes.push(this.times[i]);
                            displayLabels.push(this.labels[i]);
                        }
                    }
                } else {
                    displayData = [...this.data];
                    displayTimes = [...this.times];
                    displayLabels = [...this.labels];
                }
                
                if (displayData.length === 0) return;
                
                const minValue = Math.min(...displayData);
                const maxValue = Math.max(...displayData);
                const range = maxValue - minValue || 1;
                
                let minTime, maxTime, timeRange;
                if (scaleMode === 'time') {
                    minTime = displayTimes[0];
                    maxTime = displayTimes[displayTimes.length - 1];
                    timeRange = maxTime - minTime || 1;
                }
                
                // 축 그리기
                ctx.strokeStyle = '#444';
                ctx.lineWidth = 1;
                ctx.beginPath();
                ctx.moveTo(padding, padding);
                ctx.lineTo(padding, this.height - padding);
                ctx.lineTo(this.width - padding, this.height - padding);
                ctx.stroke();
                
                // Y축 레이블 (Value Table이 있으면 텍스트로 표시)
                ctx.fillStyle = '#aaa';
                ctx.font = '11px Arial';
                ctx.textAlign = 'right';
                
                if (this.signal.valueTable) {
                    // Value Table이 있는 경우
                    const uniqueValues = [...new Set(displayData)].sort((a, b) => a - b);
                    if (uniqueValues.length <= 5) {
                        uniqueValues.forEach((val, idx) => {
                            const y = this.height - padding - ((val - minValue) / range) * graphHeight;
                            const text = this.getValueText(val);
                            ctx.fillText(text, padding - 5, y + 5);
                        });
                    } else {
                        ctx.fillText(this.getValueText(maxValue), padding - 5, padding + 5);
                        ctx.fillText(this.getValueText(minValue), padding - 5, this.height - padding);
                    }
                } else {
                    // 일반 숫자 표시
                    ctx.fillText(maxValue.toFixed(2), padding - 5, padding + 5);
                    ctx.fillText(minValue.toFixed(2), padding - 5, this.height - padding);
                }
                
                // X축 레이블
                ctx.textAlign = 'center';
                ctx.fillStyle = '#aaa';
                ctx.font = '10px Arial';
                if (displayLabels.length > 0) {
                    ctx.fillText(displayLabels[0] + 's', padding, this.height - padding + 15);
                    if (displayLabels.length > 1) {
                        const lastIdx = displayLabels.length - 1;
                        let xPos;
                        if (scaleMode === 'time') {
                            xPos = this.width - padding;
                        } else {
                            xPos = padding + (graphWidth / (MAX_DATA_POINTS - 1)) * Math.min(lastIdx, MAX_DATA_POINTS - 1);
                        }
                        ctx.fillText(displayLabels[lastIdx] + 's', xPos, this.height - padding + 15);
                    }
                }
                ctx.fillText('Time (sec)', this.width / 2, this.height - 5);
                
                // 그리드 라인
                ctx.strokeStyle = '#333';
                ctx.lineWidth = 1;
                for (let i = 1; i < 5; i++) {
                    const y = padding + (graphHeight / 5) * i;
                    ctx.beginPath();
                    ctx.moveTo(padding, y);
                    ctx.lineTo(this.width - padding, y);
                    ctx.stroke();
                }
                
                // 데이터 포인트 그리기
                if (displayData.length < 1) return;
                
                ctx.fillStyle = this.colors.line;
                for (let i = 0; i < displayData.length; i++) {
                    let x;
                    
                    if (scaleMode === 'time') {
                        const timePos = (displayTimes[i] - minTime) / timeRange;
                        x = padding + graphWidth * timePos;
                    } else {
                        x = padding + (graphWidth / (MAX_DATA_POINTS - 1)) * i;
                    }
                    
                    const y = this.height - padding - ((displayData[i] - minValue) / range) * graphHeight;
                    
                    ctx.beginPath();
                    ctx.arc(x, y, 5, 0, Math.PI * 2);
                    ctx.fill();
                    
                    ctx.strokeStyle = '#fff';
                    ctx.lineWidth = 1;
                    ctx.stroke();
                }
            }
        }
        
        function initWebSocket() {
            const hostname = window.location.hostname;
            ws = new WebSocket('ws://' + hostname + ':81');
            
            ws.onopen = function() {
                console.log('WebSocket connected');
                updateStatus('Connected', false);
            };
            
            ws.onclose = function() {
                console.log('WebSocket disconnected');
                updateStatus('Disconnected', true);
                setTimeout(initWebSocket, 3000);
            };
            
            ws.onmessage = function(event) {
                try {
                    const data = JSON.parse(event.data);
                    
                    if (data.type === 'canBatch' && graphing) {
                        processCANData(data.messages);
                    }
                } catch(e) {
                    console.error('Error:', e);
                }
            };
        }
        
        function updateStatus(text, isError) {
            const status = document.getElementById('status');
            status.textContent = text;
            status.className = 'status' + (isError ? ' disconnected' : '');
        }
        
        function loadData() {
            try {
                const signals = localStorage.getItem('selected_signals');
                const dbc = localStorage.getItem('dbc_content');
                const savedSortMode = localStorage.getItem('sort_mode');
                
                if (!signals || !dbc) {
                    alert('No signals selected. Please select signals first.');
                    window.close();
                    return false;
                }
                
                if (savedSortMode) {
                    sortMode = savedSortMode;
                }
                
                selectedSignals = JSON.parse(signals);
                
                dbcData = {messages: {}};
                selectedSignals.forEach(sig => {
                    if (!dbcData.messages[sig.messageId]) {
                        dbcData.messages[sig.messageId] = {
                            id: sig.messageId,
                            signals: []
                        };
                    }
                    dbcData.messages[sig.messageId].signals.push(sig);
                });
                
                console.log('Loaded', selectedSignals.length, 'signals');
                
                // Value Table 정보 로깅
                selectedSignals.forEach(sig => {
                    if (sig.valueTable) {
                        console.log('Signal with Value Table:', sig.name, sig.valueTable);
                    }
                });
                
                return true;
            } catch(e) {
                console.error('Failed to load data:', e);
                alert('Failed to load signal data');
                window.close();
                return false;
            }
        }
        
        function sortSignalsForDisplay() {
            if (sortMode === 'name-asc') {
                return selectedSignals.sort((a, b) => a.name.localeCompare(b.name));
            } else if (sortMode === 'name-desc') {
                return selectedSignals.sort((a, b) => b.name.localeCompare(a.name));
            } else {
                return selectedSignals;
            }
        }
        
        function setSortMode(mode) {
            sortMode = mode;
            
            document.querySelectorAll('[id^="btn-sort-"]').forEach(btn => btn.classList.remove('active'));
            document.getElementById('btn-sort-' + mode).classList.add('active');
            
            createGraphs();
            
            console.log('Sort mode changed to:', mode);
        }
        
        function createGraphs() {
            const graphsDiv = document.getElementById('graphs');
            graphsDiv.innerHTML = '';
            charts = {};
            
            const sortedSignals = sortSignalsForDisplay();
            
            sortedSignals.forEach((signal, index) => {
                const container = document.createElement('div');
                container.className = 'graph-container';
                
                const canvas = document.createElement('canvas');
                canvas.id = 'chart-' + index;
                
                container.innerHTML = 
                    '<div class="graph-header">' +
                        '<div class="graph-title">' + signal.name + ' (0x' + signal.messageId.toString(16).toUpperCase() + ')' +
                        (signal.unit ? ' [' + signal.unit + ']' : '') + '</div>' +
                        '<div class="graph-value" id="value-' + index + '">-</div>' +
                    '</div>';
                
                container.appendChild(canvas);
                graphsDiv.appendChild(container);
                
                charts[index] = new SimpleChart(canvas, signal, index);
            });
            
            document.getElementById('graph-count').textContent = sortedSignals.length;
        }
        
        function startGraphing() {
            if (!graphing) {
                graphing = true;
                startTime = Date.now();
                lastTimestamps = {};
                updateStatus('Graphing...', false);
                console.log('Started graphing at', new Date().toISOString());
            }
        }
        
        function stopGraphing() {
            graphing = false;
            updateStatus('Stopped', false);
            console.log('Stopped graphing');
        }
        
        function setScaleMode(mode) {
            scaleMode = mode;
            
            document.getElementById('btn-index-mode').classList.toggle('active', mode === 'index');
            document.getElementById('btn-time-mode').classList.toggle('active', mode === 'time');
            
            Object.values(charts).forEach(chart => chart.draw());
            
            console.log('Scale mode changed to:', mode);
        }
        
        function setRangeMode(mode) {
            rangeMode = mode;
            
            document.getElementById('btn-range-10s').classList.toggle('active', mode === '10s');
            document.getElementById('btn-range-all').classList.toggle('active', mode === 'all');
            
            Object.values(charts).forEach(chart => chart.draw());
            
            console.log('Range mode changed to:', mode);
        }
        
        function processCANData(messages) {
            const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(1);
            const sortedSignals = sortSignalsForDisplay();
            
            console.log('Processing', messages.length, 'messages at time', elapsedTime + 's');
            
            let processedCount = 0;
            
            messages.forEach(canMsg => {
                const idStr = canMsg.id.replace(/\s/g, '').toUpperCase();
                let msgId = parseInt(idStr, 16);
                
                // Extended CAN ID 처리 (bit 31 제거)
                if (msgId & 0x80000000) {
                    msgId = msgId & 0x1FFFFFFF;
                }
                
                const timestamp = canMsg.timestamp;
                
                sortedSignals.forEach((signal, index) => {
                    if (signal.messageId === msgId && charts[index]) {
                        try {
                            const value = decodeSignal(signal, canMsg.data);
                            
                            // 항상 추가
                            charts[index].addData(value, elapsedTime);
                            
                            const valueDiv = document.getElementById('value-' + index);
                            if (valueDiv) {
                                // Value Table이 있으면 텍스트로 표시
                                if (signal.valueTable && signal.valueTable[value] !== undefined) {
                                    valueDiv.textContent = signal.valueTable[value];
                                } else {
                                    valueDiv.textContent = value.toFixed(2) + (signal.unit ? ' ' + signal.unit : '');
                                }
                            }
                            
                            processedCount++;
                        } catch(e) {
                            console.error('Error decoding signal ' + signal.name + ':', e);
                        }
                    }
                });
            });
            
            if (processedCount > 0) {
                console.log('Added', processedCount, 'new data points');
            }
            
            // 통계 업데이트
            updateStatistics();
        }
        
        function updateStatistics() {
            let totalDataPoints = 0;
            Object.values(charts).forEach(chart => {
                totalDataPoints += chart.data.length;
            });
            
            document.getElementById('data-point-count').textContent = totalDataPoints;
            
            const recordingTime = ((Date.now() - startTime) / 1000).toFixed(1);
            document.getElementById('recording-time').textContent = recordingTime + 's';
        }
        
        function decodeSignal(signal, hexData) {
            const bytes = [];
            
            if (typeof hexData === 'string') {
                const cleanHex = hexData.replace(/\s/g, '').toUpperCase();
                for (let i = 0; i < cleanHex.length && i < 16; i += 2) {
                    bytes.push(parseInt(cleanHex.substring(i, i + 2), 16));
                }
            }
            
            while (bytes.length < 8) bytes.push(0);
            
            let rawValue = 0;
            
            if (signal.byteOrder === 'intel') {
                for (let i = 0; i < signal.bitLength; i++) {
                    const bitPos = signal.startBit + i;
                    const byteIdx = Math.floor(bitPos / 8);
                    const bitIdx = bitPos % 8;
                    
                    if (byteIdx < bytes.length) {
                        const bit = (bytes[byteIdx] >> bitIdx) & 1;
                        rawValue |= (bit << i);
                    }
                }
            } else {
                for (let i = 0; i < signal.bitLength; i++) {
                    const bitPos = signal.startBit - i;
                    const byteIdx = Math.floor(bitPos / 8);
                    const bitIdx = 7 - (bitPos % 8);
                    
                    if (byteIdx < bytes.length && byteIdx >= 0) {
                        const bit = (bytes[byteIdx] >> bitIdx) & 1;
                        rawValue |= (bit << (signal.bitLength - 1 - i));
                    }
                }
            }
            
            if (signal.signed && (rawValue & (1 << (signal.bitLength - 1)))) {
                rawValue -= (1 << signal.bitLength);
            }
            
            return rawValue * signal.factor + signal.offset;
        }
        
        function downloadCSV() {
            if (Object.keys(charts).length === 0) {
                alert('No data to download!');
                return;
            }
            
            let csvContent = 'Time(s)';
            const sortedSignals = sortSignalsForDisplay();
            
            // 헤더 생성
            sortedSignals.forEach(signal => {
                const unit = signal.unit ? ' [' + signal.unit + ']' : '';
                csvContent += ',' + signal.name + unit;
                
                // Value Table이 있으면 텍스트 컬럼도 추가
                if (signal.valueTable) {
                    csvContent += ',' + signal.name + '_Text';
                }
            });
            csvContent += '\n';
            
            // 데이터 생성
            let maxLength = 0;
            Object.values(charts).forEach(chart => {
                if (chart.labels.length > maxLength) {
                    maxLength = chart.labels.length;
                }
            });
            
            for (let i = 0; i < maxLength; i++) {
                let row = '';
                let timeValue = '';
                
                sortedSignals.forEach((signal, index) => {
                    const chart = charts[index];
                    if (chart && chart.labels[i] !== undefined) {
                        if (timeValue === '') {
                            timeValue = chart.labels[i];
                        }
                        const value = chart.data[i];
                        row += ',' + value.toFixed(6);
                        
                        // Value Table이 있으면 텍스트도 추가
                        if (signal.valueTable) {
                            const text = signal.valueTable[value] !== undefined ? 
                                        signal.valueTable[value] : '';
                            row += ',"' + text + '"';
                        }
                    } else {
                        row += ',';
                        if (signal.valueTable) {
                            row += ',';
                        }
                    }
                });
                
                if (timeValue !== '') {
                    csvContent += timeValue + row + '\n';
                }
            }
            
            const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
            const link = document.createElement('a');
            const url = URL.createObjectURL(blob);
            
            const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
            link.setAttribute('href', url);
            link.setAttribute('download', 'can_signals_' + timestamp + '.csv');
            link.style.visibility = 'hidden';
            
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            
            console.log('CSV downloaded with ' + maxLength + ' data points');
        }
        
        function clearData() {
            if (!confirm('Clear all recorded data? This cannot be undone.')) {
                return;
            }
            
            Object.values(charts).forEach(chart => {
                chart.data = [];
                chart.times = [];
                chart.labels = [];
                chart.rawValues = [];
                chart.currentValue = 0;
                chart.currentText = '';
                chart.draw();
            });
            
            lastTimestamps = {};
            startTime = Date.now();
            
            updateStatistics();
            
            console.log('All data cleared');
        }
        
        if (loadData()) {
            createGraphs();
            initWebSocket();
            startGraphing();
        }
    </script>
</body>
</html>
)rawliteral";

#endif