그래프 뷰어 x 축 설정 추가

This commit is contained in:
2025-10-07 17:18:00 +00:00
parent da51fafd5a
commit fd6437ac78

View File

@@ -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(
</div>
<div class="controls">
<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 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>
<div class="status" id="status">Connecting...</div>
@@ -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);