FPGA_WebLab/src/components/LogicAnalyzer/LogicalWaveFormDisplay.vue

284 lines
7.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div
class="w-full"
:class="{
'h-48': !analyzer.logicData.value,
'h-150': analyzer.logicData.value,
}"
>
<v-chart
v-if="analyzer.logicData.value"
class="w-full h-full"
:option="option"
autoresize
:update-options="updateOptions"
/>
<div
v-else
class="w-full h-full flex flex-col gap-6 items-center justify-center"
>
<div class="text-center">
<h3 class="text-xl font-semibold text-slate-600 mb-2">
暂无逻辑分析数据
</h3>
<p class="text-sm text-slate-500">点击下方按钮生成测试数据用于观察</p>
</div>
<!-- <button
class="group relative px-8 py-3 bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white font-medium rounded-lg shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200 ease-in-out focus:outline-none focus:ring-4 focus:ring-blue-300 active:scale-95"
@click="analyzer.generateTestData"
>
<span class="flex items-center gap-2">
<RefreshCcw
class="w-5 h-5 group-hover:rotate-180 transition-transform duration-300"
/>
生成测试数据
</span>
<div
class="absolute inset-0 bg-white opacity-0 group-hover:opacity-20 rounded-lg transition-opacity duration-200"
></div>
</button> -->
<button
class="group relative px-8 py-3 bg-gradient-to-r text-white font-medium rounded-lg shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200 ease-in-out focus:outline-none focus:ring-4 active:scale-95"
:class="{
'from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 focus:ring-blue-300':
!analyzer.isCapturing.value,
'from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 focus:ring-red-300':
analyzer.isCapturing.value,
}"
:disabled="
analyzer.isOperationInProgress.value && !analyzer.isCapturing.value
"
@click="
analyzer.isCapturing.value
? analyzer.stopCapture()
: analyzer.startCapture()
"
>
<span class="flex items-center gap-2">
<template v-if="analyzer.isCapturing.value">
<Square class="w-5 h-5" />
停止捕获
</template>
<template v-else>
<Play class="w-5 h-5" />
开始捕获
</template>
</span>
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, shallowRef } from "vue";
import VChart from "vue-echarts";
import { RefreshCcw, Play, Square } from "lucide-vue-next";
// Echarts
import { use } from "echarts/core";
import { LineChart } from "echarts/charts";
import {
TooltipComponent,
GridComponent,
DataZoomComponent,
AxisPointerComponent,
ToolboxComponent,
} from "echarts/components";
import { CanvasRenderer } from "echarts/renderers";
import type { ComposeOption } from "echarts/core";
import type { LineSeriesOption } from "echarts/charts";
import type {
AxisPointerComponentOption,
TooltipComponentOption,
GridComponentOption,
DataZoomComponentOption,
} from "echarts/components";
import type {
ToolboxComponentOption,
XAXisOption,
YAXisOption,
} from "echarts/types/dist/shared";
import { useLogicAnalyzerState } from "./LogicAnalyzerManager";
import { useRequiredInjection } from "@/utils/Common";
import { isUndefined } from "lodash";
use([
TooltipComponent,
ToolboxComponent,
GridComponent,
AxisPointerComponent,
DataZoomComponent,
LineChart,
CanvasRenderer,
]);
type EChartsOption = ComposeOption<
| AxisPointerComponentOption
| TooltipComponentOption
| ToolboxComponentOption
| GridComponentOption
| DataZoomComponentOption
| LineSeriesOption
>;
const analyzer = useRequiredInjection(useLogicAnalyzerState);
// 添加更新选项来减少重绘
const updateOptions = shallowRef({
notMerge: false,
lazyUpdate: true,
silent: false,
});
const option = computed((): EChartsOption => {
if (isUndefined(analyzer.logicData.value)) return {};
// 只获取启用的通道
const enabledChannels = analyzer.enabledChannels.value;
const enabledChannelIndices = analyzer.channels
.map((channel, index) => (channel.enabled ? index : -1))
.filter((index) => index !== -1);
const channelCount = enabledChannels.length;
const channelSpacing = 2; // 每个通道之间的间距
// 如果没有启用的通道,返回空配置
if (channelCount === 0) {
return {};
}
// 使用单个网格
const grids: GridComponentOption[] = [
{
left: "5%",
right: "5%",
top: "5%",
bottom: "15%",
},
];
// 单个X轴
const xAxis: XAXisOption[] = [
{
type: "category",
boundaryGap: false,
data: analyzer.logicData.value.x.map((x) => x.toFixed(3)),
axisLabel: {
formatter: (value: string) =>
analyzer.logicData.value
? `${value}${analyzer.logicData.value.xUnit}`
: `${value}`,
},
},
];
// 单个Y轴范围根据启用通道数量调整
const yAxis: YAXisOption[] = [
{
type: "value",
min: -0.5,
max: channelCount * channelSpacing - 0.5,
interval: channelSpacing,
axisLabel: {
formatter: (value: number) => {
const channelIndex = Math.round(value / channelSpacing);
return channelIndex < channelCount
? enabledChannels[channelIndex].label
: "";
},
},
splitLine: { show: false },
},
];
// 创建系列数据,只包含启用的通道
const series: LineSeriesOption[] = enabledChannelIndices.map(
(originalIndex: number, displayIndex: number) => ({
name: enabledChannels[displayIndex].label,
type: "line",
data: analyzer.logicData.value!.y[originalIndex].map(
(value: number) => value + displayIndex * channelSpacing + 0.2,
),
step: "end",
lineStyle: {
width: 2,
color: enabledChannels[displayIndex].color,
},
areaStyle: {
opacity: 0.3,
origin: displayIndex * channelSpacing,
color: enabledChannels[displayIndex].color,
},
symbol: "none",
// 优化性能配置
sampling: "lttb",
// 减少动画以避免闪烁
animation: false,
}),
);
return {
// 全局动画配置
animation: false,
tooltip: {
trigger: "axis",
axisPointer: {
type: "line",
label: {
backgroundColor: "#6a7985",
},
// 减少axisPointer的动画
animation: false,
},
formatter: (params: any) => {
if (Array.isArray(params) && params.length > 0) {
const timeValue = analyzer.logicData.value!.x[params[0].dataIndex];
const dataIndex = params[0].dataIndex;
let tooltip = `Time: ${timeValue.toFixed(3)}${analyzer.logicData.value!.xUnit}<br/>`;
// 只显示启用通道在当前时间点的原始数值0或1
enabledChannelIndices.forEach(
(originalIndex: number, displayIndex: number) => {
const channelName = enabledChannels[displayIndex].label;
const originalValue =
analyzer.logicData.value!.y[originalIndex][dataIndex];
tooltip += `${channelName}: ${originalValue}<br/>`;
},
);
return tooltip;
}
return "";
},
// 优化tooltip性能
hideDelay: 100,
},
toolbox: {
feature: {
restore: {},
},
},
grid: grids,
xAxis: xAxis,
yAxis: yAxis,
dataZoom: [
{
show: true,
realtime: true,
start: 0,
end: 100,
},
{
type: "inside",
realtime: true,
start: 0,
end: 100,
},
],
series: series,
};
});
</script>