添加了数组数据监控的图表绘制支持

This commit is contained in:
jinchao 2025-06-10 16:09:27 +08:00
parent 6f46cff40e
commit b5f41c40b8
3 changed files with 147 additions and 56 deletions

Binary file not shown.

View File

@ -198,10 +198,6 @@ class FloatingChartWindow extends HTMLElement {
flex-direction: column;
}
.window-content.minimized {
display: none;
}
.chart-container {
flex: 1;
position: relative;
@ -240,7 +236,6 @@ class FloatingChartWindow extends HTMLElement {
<div class="window-header">
<span class="window-title">${this.getAttribute('title') || '图表窗口'}</span>
<div class="window-controls">
<button class="window-control-button minimize-button"></button>
<button class="window-control-button close-button">×</button>
</div>
</div>
@ -364,6 +359,16 @@ class FloatingChartWindow extends HTMLElement {
}
};
// 确保数据集配置正确
if (chartData.datasets && chartData.datasets.length > 0) {
chartData.datasets = chartData.datasets.map(dataset => ({
...dataset,
borderWidth: 2,
pointRadius: 0,
tension: 0
}));
}
this.chartInstance = new Chart(ctx, {
type: 'line',
data: chartData,
@ -373,12 +378,10 @@ class FloatingChartWindow extends HTMLElement {
addEventListeners() {
const header = this.shadowRoot.querySelector('.window-header');
const minimizeButton = this.shadowRoot.querySelector('.minimize-button');
const closeButton = this.shadowRoot.querySelector('.close-button');
const resizeHandle = this.shadowRoot.querySelector('.resize-handle');
header.addEventListener('mousedown', this.handleMouseDown);
minimizeButton.addEventListener('click', this.handleMinimize);
closeButton.addEventListener('click', this.handleClose);
resizeHandle.addEventListener('mousedown', this.handleResizeStart);
@ -394,7 +397,7 @@ class FloatingChartWindow extends HTMLElement {
handleMouseDown(e) {
// 检查点击是否在标题栏或其子元素上
const header = this.shadowRoot.querySelector('.window-header');
if (header && (e.target === header || header.contains(e.target)) && !this.isMinimized) {
if (header && (e.target === header || header.contains(e.target))) {
this.isDragging = true;
this.dragOffset = {
x: e.clientX - this.position.x,
@ -420,7 +423,7 @@ class FloatingChartWindow extends HTMLElement {
y: Math.max(minY, e.clientY - this.dragOffset.y)
};
this.updatePosition();
} else if (this.isResizing && !this.isMinimized) {
} else if (this.isResizing) {
const deltaX = e.clientX - this.resizeStart.x;
const deltaY = e.clientY - this.resizeStart.y;
@ -452,7 +455,7 @@ class FloatingChartWindow extends HTMLElement {
}
handleResize(e) {
if (this.isResizing && !this.isMinimized) {
if (this.isResizing) {
const deltaX = e.clientX - this.resizeStart.x;
const deltaY = e.clientY - this.resizeStart.y;
@ -546,6 +549,38 @@ class FloatingChartWindow extends HTMLElement {
// 始终使用普通数字格式,保留一位小数
return `${context.dataset.label}: ${value.toFixed(1)}`;
};
// 计算所有数据点的范围
let min = Infinity;
let max = -Infinity;
data.datasets.forEach(dataset => {
if (dataset.data && dataset.data.length > 0) {
const datasetMin = Math.min(...dataset.data);
const datasetMax = Math.max(...dataset.data);
min = Math.min(min, datasetMin);
max = Math.max(max, datasetMax);
}
});
if (min !== Infinity && max !== -Infinity) {
const range = max - min;
// 如果所有数据都是0使用固定范围
if (min === 0 && max === 0) {
options.scales.y.min = -1;
options.scales.y.max = 1;
}
// 如果范围很小,使用固定比例
else if (range < 1) {
options.scales.y.min = min - 0.5;
options.scales.y.max = max + 0.5;
} else {
// 如果范围较大,使用百分比
const margin = range * 0.2; // 使用20%的边距
options.scales.y.min = min - margin;
options.scales.y.max = max + margin;
}
}
// 更新图表
this.chartInstance.update('none');
@ -565,6 +600,70 @@ class FloatingChartWindow extends HTMLElement {
window.style.zIndex = zIndex;
}
}
// 添加新方法:处理数据更新
handleDataUpdate(values, interfaceName) {
if (!this.chartInstance) return;
const chartData = this.chartInstance.data;
// 如果是第一次收到数据,创建数据集
if (chartData.datasets.length === 0) {
if (values.length > 1) {
// 创建多个数据集
chartData.datasets = values.map((_, index) => ({
label: `${interfaceName} (${index + 1})`,
data: [],
borderColor: this.getRandomColor(),
fill: false,
borderWidth: 2,
pointRadius: 0,
tension: 0
}));
} else {
// 创建单个数据集
chartData.datasets = [{
label: interfaceName,
data: [],
borderColor: this.getRandomColor(),
fill: false,
borderWidth: 2,
pointRadius: 0,
tension: 0
}];
}
}
// 添加新的数据点
chartData.labels.push(this.dataPointIndex.toString());
values.forEach((value, index) => {
if (index < chartData.datasets.length) {
chartData.datasets[index].data.push(value);
}
});
this.dataPointIndex++;
// 保持最近100个数据点
if (chartData.labels.length > 100) {
chartData.labels.shift();
chartData.datasets.forEach(dataset => {
dataset.data.shift();
});
}
// 更新图表
this.updateChartData(chartData);
}
// 添加新方法:获取随机颜色
getRandomColor() {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
}
customElements.define('floating-chart-window', FloatingChartWindow);

View File

@ -366,6 +366,7 @@ class DataMonitor extends HTMLElement {
}
const groupedInterfaces = this.getGroupedInterfaces();
const allData = {}; // 存储所有结构体的数据
// 对每个结构体启动监控并获取数据
for (const [structName, interfaceNames] of Object.entries(groupedInterfaces)) {
@ -403,13 +404,18 @@ class DataMonitor extends HTMLElement {
throw new Error(`获取监控数据失败: 返回数据为空`);
}
// 更新表格数据
this.updateTableData(responseData.data);
// 合并数据
Object.assign(allData, responseData.data);
} catch (structError) {
console.error(`处理结构体 ${structName} 时出错:`, structError);
continue;
}
}
// 一次性更新所有数据
if (Object.keys(allData).length > 0) {
this.updateTableData(allData);
}
} catch (error) {
console.error('数据更新失败:', error);
this.stopDataUpdateTimer();
@ -488,7 +494,12 @@ class DataMonitor extends HTMLElement {
* @param {string} modelStructName - 结构体名称
*/
async handlePlot(interfaceName, modelStructName) {
// 检查监控状态
const statusIndicator = this.shadowRoot.getElementById('statusIndicator');
if (!statusIndicator || !statusIndicator.classList.contains('active')) {
return; // 如果不在监控状态,直接返回
}
// 检查是否已经存在该接口的图表窗口
const windowId = `${interfaceName}_${modelStructName}`;
if (this.chartWindows.has(windowId)) {
@ -506,12 +517,7 @@ class DataMonitor extends HTMLElement {
// 创建图表数据
const chartData = {
labels: [],
datasets: [{
label: interfaceName,
data: [],
borderColor: this.getRandomColor(),
fill: false
}]
datasets: []
};
// 创建图表配置
@ -530,6 +536,7 @@ class DataMonitor extends HTMLElement {
scales: {
y: {
beginAtZero: false,
display: true,
ticks: {
callback: function(value) {
// 如果数值的绝对值大于1000或小于0.1,使用科学计数法
@ -567,11 +574,7 @@ class DataMonitor extends HTMLElement {
callbacks: {
label: function(context) {
const value = context.raw;
// 如果数值的绝对值大于1000或小于0.1,使用科学计数法
if (Math.abs(value) > 1000 || (Math.abs(value) < 0.1 && value !== 0)) {
return `${context.dataset.label}: ${value.toExponential(1)}`;
}
// 否则使用普通数字,保留一位小数
// 始终使用普通数字格式,保留一位小数
return `${context.dataset.label}: ${value.toFixed(1)}`;
}
}
@ -594,7 +597,7 @@ class DataMonitor extends HTMLElement {
// 再设置属性
floatingWindow.setAttribute('title', interfaceName);
floatingWindow.setAttribute('initial-position', JSON.stringify({ x: 400, y: 200 })); // 调整初始位置
floatingWindow.setAttribute('initial-position', JSON.stringify({ x: 400, y: 200 }));
floatingWindow.setAttribute('initial-size', JSON.stringify({ width: 400, height: 300 }));
floatingWindow.setAttribute('chart-data', JSON.stringify(chartData));
floatingWindow.setAttribute('chart-options', JSON.stringify(chartOptions));
@ -608,42 +611,26 @@ class DataMonitor extends HTMLElement {
if (row && row.monitorData) {
try {
const data = JSON.parse(row.monitorData);
let value;
// 处理数值类型
if (typeof data === 'number') {
value = data;
} else if (typeof data === 'object') {
value = JSON.stringify(data);
const data = row.monitorData;
let values = [];
// 尝试解析数据
if (typeof data === 'string' && data.includes(',')) {
// 如果是逗号分隔的字符串,分割并转换为数字
values = data.split(',').map(v => parseFloat(v.trim()));
} else if (typeof data === 'number') {
// 如果是单个数字
values = [data];
} else {
// 尝试将字符串转换为数字
const numValue = parseFloat(data);
value = isNaN(numValue) ? data : numValue;
}
// 使用独立的数据点计数器
chartData.labels.push(floatingWindow.dataPointIndex.toString());
chartData.datasets[0].data.push(value);
floatingWindow.dataPointIndex++; // 增加计数器
// 保持最近100个数据点
if (chartData.labels.length > 100) {
chartData.labels.shift();
chartData.datasets[0].data.shift();
values = isNaN(numValue) ? [] : [numValue];
}
// 如果是第一个数据点设置y轴范围
if (chartData.datasets[0].data.length === 1 && typeof value === 'number') {
// 计算合适的y轴范围
const range = Math.abs(value) * 0.2; // 使用20%的范围
chartOptions.scales.y.min = value - range;
chartOptions.scales.y.max = value + range;
// 更新图表配置
floatingWindow.setAttribute('chart-options', JSON.stringify(chartOptions));
// 如果数据有效,更新图表
if (values.length > 0) {
floatingWindow.handleDataUpdate(values, interfaceName);
}
// 更新图表数据
floatingWindow.setAttribute('chart-data', JSON.stringify(chartData));
} catch (e) {
console.error('解析数据失败:', e);
}
@ -671,6 +658,10 @@ class DataMonitor extends HTMLElement {
}
render() {
// 获取当前监控状态
const statusIndicator = this.shadowRoot.getElementById('statusIndicator');
const isMonitoring = statusIndicator && statusIndicator.classList.contains('active');
// 按ModelStructName分组
const groupedInterfaces = this.filteredInterfaces.reduce((groups, item) => {
const group = groups[item.ModelStructName] || [];
@ -1043,7 +1034,8 @@ class DataMonitor extends HTMLElement {
</tr>
</thead>
<tbody>
${this.tableData.map(row => ` <tr>
${this.tableData.map(row => `
<tr>
<td title="${row.InterfaceName}">${row.InterfaceName}</td>
<td title="${row.ModelStructName}">${row.ModelStructName}</td>
<td class="data-cell" title="${this.formatMonitorData(row.monitorData)}">${this.formatMonitorData(row.monitorData)}</td>