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