From fd6437ac78fab006c35673fcfccca3fe306dc2c7 Mon Sep 17 00:00:00 2001 From: byun Date: Tue, 7 Oct 2025 17:18:00 +0000 Subject: [PATCH] =?UTF-8?q?=EA=B7=B8=EB=9E=98=ED=94=84=20=EB=B7=B0?= =?UTF-8?q?=EC=96=B4=20x=20=EC=B6=95=20=EC=84=A4=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graph_viewer.h | 152 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 129 insertions(+), 23 deletions(-) diff --git a/graph_viewer.h b/graph_viewer.h index f3db69f..adfaa4b 100644 --- a/graph_viewer.h +++ b/graph_viewer.h @@ -29,8 +29,18 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral( 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; @@ -42,7 +52,13 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral( } .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; @@ -110,9 +126,21 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral(
- - - +
+ + + +
+
+ X-Axis Scale: + + +
+
+ X-Axis Range: + + +
Connecting...
@@ -126,8 +154,10 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral( let startTime = 0; let selectedSignals = []; let dbcData = {}; - let lastTimestamps = {}; // 각 신호의 마지막 타임스탬프 추적 + let lastTimestamps = {}; const MAX_DATA_POINTS = 60; + let scaleMode = 'index'; // 'index' or 'time' + let rangeMode = '10s'; // '10s' or 'all' const COLORS = [ {line: '#FF6384', fill: 'rgba(255, 99, 132, 0.2)'}, {line: '#36A2EB', fill: 'rgba(54, 162, 235, 0.2)'}, @@ -143,6 +173,7 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral( this.ctx = canvas.getContext('2d'); this.signal = signal; this.data = []; + this.times = []; // 실제 시간(초) 저장 this.labels = []; this.colors = COLORS[colorIndex % COLORS.length]; this.currentValue = 0; @@ -161,13 +192,16 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral( this.draw(); } - addData(value, label) { + addData(value, time) { this.data.push(value); - this.labels.push(label); + this.times.push(parseFloat(time)); + this.labels.push(time); this.currentValue = value; - if (this.data.length > MAX_DATA_POINTS) { + // 인덱스 모드에서만 개수 제한 + if (scaleMode === 'index' && this.data.length > MAX_DATA_POINTS) { this.data.shift(); + this.times.shift(); this.labels.shift(); } @@ -184,10 +218,42 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral( ctx.clearRect(0, 0, this.width, this.height); - const minValue = Math.min(...this.data); - const maxValue = Math.max(...this.data); + // 표시할 데이터 필터링 + let displayData = []; + let displayTimes = []; + let displayLabels = []; + + if (rangeMode === '10s') { + // 최근 10초 데이터만 + 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; + // X축 범위 계산 + 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; @@ -208,13 +274,17 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral( ctx.textAlign = 'center'; ctx.fillStyle = '#aaa'; ctx.font = '10px Arial'; - if (this.labels.length > 0) { - ctx.fillText(this.labels[0] + 's', padding, this.height - padding + 15); - if (this.labels.length > 1) { - const lastIdx = this.labels.length - 1; - ctx.fillText(this.labels[lastIdx] + 's', - padding + (graphWidth / (MAX_DATA_POINTS - 1)) * lastIdx, - this.height - padding + 15); + 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); @@ -230,20 +300,30 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral( ctx.stroke(); } - if (this.data.length < 1) return; + if (displayData.length < 1) return; - // 점만 그리기 (선과 영역 채우기 제거) + // 점 그리기 ctx.fillStyle = this.colors.line; - for (let i = 0; i < this.data.length; i++) { - const x = padding + (graphWidth / (MAX_DATA_POINTS - 1)) * i; - const y = this.height - padding - ((this.data[i] - minValue) / range) * graphHeight; + for (let i = 0; i < displayData.length; i++) { + let x; - // 점 크기를 5로 증가 (더 잘 보이도록) + if (scaleMode === 'time') { + // 시간 기반: 실제 시간 간격을 X축에 반영 + 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(); @@ -359,6 +439,32 @@ const char graph_viewer_html[] PROGMEM = R"rawliteral( updateStatus('Stopped', false); } + 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);