그래프 뷰어 x 축 설정 추가
This commit is contained in:
152
graph_viewer.h
152
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(
|
||||
</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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user