248 lines
6.3 KiB
Vue
248 lines
6.3 KiB
Vue
<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>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, shallowRef } from "vue";
|
||
import VChart from "vue-echarts";
|
||
import { RefreshCcw } 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 channelCount = analyzer.logicData.value.y.length;
|
||
const channelSpacing = 2; // 每个通道之间的间距
|
||
|
||
// 使用单个网格
|
||
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
|
||
? analyzer.channelNames.value[channelIndex]
|
||
: "";
|
||
},
|
||
},
|
||
splitLine: { show: false },
|
||
},
|
||
];
|
||
|
||
// 创建系列数据,每个通道有不同的Y偏移
|
||
const series: LineSeriesOption[] = analyzer.logicData.value.y.map(
|
||
(channelData: number[], index: number) => ({
|
||
name: analyzer.channelNames.value[index],
|
||
type: "line",
|
||
data: channelData.map(
|
||
(value: number) => value + index * channelSpacing + 0.2,
|
||
),
|
||
step: "end",
|
||
lineStyle: {
|
||
width: 2,
|
||
},
|
||
areaStyle: {
|
||
opacity: 0.3,
|
||
origin: index * channelSpacing,
|
||
},
|
||
symbol: "none",
|
||
// 优化性能配置
|
||
sampling: "lttb",
|
||
// large: true,
|
||
// largeThreshold: 2000,
|
||
// progressive: 2000,
|
||
// 减少动画以避免闪烁
|
||
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)
|
||
if (analyzer.logicData.value) {
|
||
analyzer.channelNames.value.forEach(
|
||
(channelName: string, index: number) => {
|
||
const originalValue =
|
||
analyzer.logicData.value!.y[index][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>
|