|
@@ -136,7 +136,7 @@ export default {
|
|
|
-->
|
|
|
|
|
|
<template>
|
|
|
- <div ref="chartDiv" style="width: 100%; height: 100%"></div>
|
|
|
+ <div ref="chartDiv" style="width: 100%; height: 350px"></div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
@@ -147,18 +147,35 @@ import am5themes_Animated from "@amcharts/amcharts5/themes/Animated";
|
|
|
|
|
|
export default {
|
|
|
name: "AmAreaChart",
|
|
|
+ props: {
|
|
|
+ className: {
|
|
|
+ type: String,
|
|
|
+ default: 'chart'
|
|
|
+ },
|
|
|
+ width: {
|
|
|
+ type: String,
|
|
|
+ default: '100%'
|
|
|
+ },
|
|
|
+ height: {
|
|
|
+ type: String,
|
|
|
+ default: '350px'
|
|
|
+ },
|
|
|
+ autoResize: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true
|
|
|
+ }
|
|
|
+ },
|
|
|
data() {
|
|
|
return {
|
|
|
chart: null,
|
|
|
root: null,
|
|
|
- series: null,
|
|
|
+ series1: null,
|
|
|
+ series2: null,
|
|
|
xAxis: null,
|
|
|
- data: [],
|
|
|
+ data1: [],
|
|
|
+ data2: [],
|
|
|
timer: null,
|
|
|
- lastValue: 15,
|
|
|
- visits: 10,
|
|
|
- isDestroyed: false,
|
|
|
- isAnimating: false,
|
|
|
+ isDestroyed: false
|
|
|
};
|
|
|
},
|
|
|
mounted() {
|
|
@@ -171,7 +188,6 @@ export default {
|
|
|
methods: {
|
|
|
initChart() {
|
|
|
try {
|
|
|
- // 保存 root 引用
|
|
|
const root = (this.root = am5.Root.new(this.$refs.chartDiv));
|
|
|
root.setThemes([am5themes_Animated.new(root)]);
|
|
|
|
|
@@ -189,9 +205,11 @@ export default {
|
|
|
// 创建Y轴
|
|
|
let yAxis = this.chart.yAxes.push(
|
|
|
am5xy.ValueAxis.new(root, {
|
|
|
- renderer: am5xy.AxisRendererY.new(root, {}),
|
|
|
+ renderer: am5xy.AxisRendererY.new(root, {
|
|
|
+ labelFormat: "{value}%"
|
|
|
+ }),
|
|
|
min: 0,
|
|
|
- max: 100,
|
|
|
+ max: 100
|
|
|
})
|
|
|
);
|
|
|
|
|
@@ -199,248 +217,257 @@ export default {
|
|
|
this.xAxis = this.chart.xAxes.push(
|
|
|
am5xy.DateAxis.new(root, {
|
|
|
baseInterval: { timeUnit: "second", count: 1 },
|
|
|
- renderer: am5xy.AxisRendererX.new(root, {}),
|
|
|
+ renderer: am5xy.AxisRendererX.new(root, {
|
|
|
+ minGridDistance: 100, // 增加标签之间的最小距离
|
|
|
+ cellStartLocation: 0.2,
|
|
|
+ cellEndLocation: 0.8,
|
|
|
+ dateFormats: {
|
|
|
+ second: "mm:ss",
|
|
|
+ minute: "mm:ss"
|
|
|
+ },
|
|
|
+ periodChangeDateFormats: {
|
|
|
+ second: "mm:ss",
|
|
|
+ minute: "mm:ss"
|
|
|
+ },
|
|
|
+ // 减少标签数量
|
|
|
+ maxDeviation: 0.5,
|
|
|
+ // 只显示首尾和少量中间的标签
|
|
|
+ minLabelGap: 50
|
|
|
+ }),
|
|
|
+ // 控制要显示的标签数量
|
|
|
+ gridIntervals: [
|
|
|
+ { timeUnit: "second", count: 5 } // 每5秒显示一个标签
|
|
|
+ ],
|
|
|
+ // 确保时间轴平滑滚动
|
|
|
+ interpolationDuration: 500,
|
|
|
+ interpolationEasing: am5.ease.cubic
|
|
|
})
|
|
|
);
|
|
|
|
|
|
- // 修改主数据系列的配置
|
|
|
- this.series = this.chart.series.push(
|
|
|
+ // 创建第一条线(expected)
|
|
|
+ this.series1 = this.chart.series.push(
|
|
|
am5xy.SmoothedXLineSeries.new(root, {
|
|
|
- name: "Series",
|
|
|
+ name: "GPU",
|
|
|
xAxis: this.xAxis,
|
|
|
yAxis: yAxis,
|
|
|
valueYField: "value",
|
|
|
valueXField: "date",
|
|
|
- connect: true,
|
|
|
- tension: 0.8, // 对应原代码中的 tensionX
|
|
|
- stroke: am5.color("#FF425C"), // 使用相同的颜色
|
|
|
- strokeWidth: 10,
|
|
|
+ tension: 0.8,
|
|
|
+ stroke: am5.color("#FF005A"),
|
|
|
+ strokeWidth: 3,
|
|
|
tooltip: am5.Tooltip.new(root, {
|
|
|
- labelText: "{valueY}",
|
|
|
- }),
|
|
|
+ labelText: "{name}: {valueY}%"
|
|
|
+ })
|
|
|
})
|
|
|
);
|
|
|
|
|
|
- // 设置渐变填充
|
|
|
- this.series.fills.template.setAll({
|
|
|
- visible: true,
|
|
|
- fillOpacity: 1,
|
|
|
- fill: am5.LinearGradient.new(root, {
|
|
|
- stops: [
|
|
|
- {
|
|
|
- offset: 0,
|
|
|
- color: am5.color("#FF425C"),
|
|
|
- opacity: 0.2,
|
|
|
- },
|
|
|
- {
|
|
|
- offset: 1,
|
|
|
- color: am5.color("#FF425C"),
|
|
|
- opacity: 0.9,
|
|
|
- },
|
|
|
- ],
|
|
|
- rotation: 90,
|
|
|
- }),
|
|
|
- });
|
|
|
-
|
|
|
- // 添加前端的圆形标记
|
|
|
- this.bulletSeries = this.chart.series.push(
|
|
|
- am5xy.LineSeries.new(root, {
|
|
|
- name: "Bullet",
|
|
|
+ // 创建第二条线(actual)
|
|
|
+ this.series2 = this.chart.series.push(
|
|
|
+ am5xy.SmoothedXLineSeries.new(root, {
|
|
|
+ name: "CPU",
|
|
|
xAxis: this.xAxis,
|
|
|
yAxis: yAxis,
|
|
|
valueYField: "value",
|
|
|
valueXField: "date",
|
|
|
+ tension: 0.8,
|
|
|
+ stroke: am5.color("#3888fa"),
|
|
|
+ strokeWidth: 3,
|
|
|
+ fill: am5.color("#f3f8ff"),
|
|
|
+ tooltip: am5.Tooltip.new(root, {
|
|
|
+ labelText: "{name}: {valueY}%"
|
|
|
+ })
|
|
|
})
|
|
|
);
|
|
|
|
|
|
- this.bulletSeries.bullets.push((root) => {
|
|
|
- return am5.Bullet.new(root, {
|
|
|
- sprite: am5.Circle.new(root, {
|
|
|
- radius: 5,
|
|
|
- fill: am5.color("#FF425C"),
|
|
|
- fillOpacity: 1,
|
|
|
- }),
|
|
|
- });
|
|
|
- });
|
|
|
- // 确保填充区域在线条下方显示
|
|
|
- this.series.fills.template.setAll({
|
|
|
- visible: true,
|
|
|
- fillOpacity: 0.5,
|
|
|
- });
|
|
|
-
|
|
|
- /* // 修改bullet系列,添加平滑属性
|
|
|
- this.bulletSeries = this.chart.series.push(
|
|
|
- am5xy.LineSeries.new(root, {
|
|
|
- name: "Series",
|
|
|
- xAxis: this.xAxis,
|
|
|
- yAxis: yAxis,
|
|
|
- valueYField: "value",
|
|
|
- valueXField: "date",
|
|
|
- connect: true,
|
|
|
- tension: 0.5,
|
|
|
+ // 添加图例
|
|
|
+ const legend = this.chart.children.push(
|
|
|
+ am5.Legend.new(root, {
|
|
|
+ centerX: am5.percent(50),
|
|
|
+ x: am5.percent(50),
|
|
|
+ layout: root.horizontalLayout
|
|
|
})
|
|
|
);
|
|
|
+ legend.data.setAll(this.chart.series.values);
|
|
|
|
|
|
- // 修复: 使用箭头函数保持this上下文,并使用外部的root引用
|
|
|
- this.bulletSeries.bullets.push(() => {
|
|
|
- return am5.Bullet.new(root, {
|
|
|
- sprite: am5.Circle.new(root, {
|
|
|
- radius: 5,
|
|
|
- fill: am5.color(0xff0000),
|
|
|
- }),
|
|
|
+ // 启用动画
|
|
|
+ this.series1.appear(1000);
|
|
|
+ this.series2.appear(1000);
|
|
|
+ this.chart.appear(1000, 100);
|
|
|
+
|
|
|
+ // 为 series1 添加圆点
|
|
|
+ this.series1.bullets.push((root) => {
|
|
|
+ const container = am5.Container.new(root, {});
|
|
|
+
|
|
|
+ // 创建光晕效果(外圈)
|
|
|
+ const halo = am5.Circle.new(root, {
|
|
|
+ radius: 12,
|
|
|
+ fill: am5.color("#FF005A"),
|
|
|
+ fillOpacity: 0.3,
|
|
|
+ stroke: am5.color("#FF005A"),
|
|
|
+ strokeOpacity: 0.3,
|
|
|
+ strokeWidth: 2
|
|
|
});
|
|
|
- });
|
|
|
- */
|
|
|
- // 初始化数据
|
|
|
- this.series.data.setAll(this.data);
|
|
|
- this.bulletSeries.data.setAll([]);
|
|
|
|
|
|
- // 启用图表动画
|
|
|
- this.series.appear(1000);
|
|
|
- this.chart.appear(1000, 100);
|
|
|
- } catch (error) {
|
|
|
- console.error("初始化图表时出错:", error);
|
|
|
- }
|
|
|
- },
|
|
|
- // 新增资源清理方法
|
|
|
- async cleanupResources() {
|
|
|
- this.isDestroyed = true;
|
|
|
- this.isAnimating = false;
|
|
|
+ // 创建主圆点(内圈)
|
|
|
+ const circle = am5.Circle.new(root, {
|
|
|
+ radius: 6,
|
|
|
+ fill: am5.color("#FF005A"),
|
|
|
+ fillOpacity: 0.8,
|
|
|
+ stroke: am5.color("#fff"),
|
|
|
+ strokeWidth: 2
|
|
|
+ });
|
|
|
|
|
|
- // 清理定时器
|
|
|
- if (this.timer) {
|
|
|
- clearInterval(this.timer);
|
|
|
- this.timer = null;
|
|
|
- }
|
|
|
+ // 将两个圆形添加到容器
|
|
|
+ container.children.push(halo);
|
|
|
+ container.children.push(circle);
|
|
|
+
|
|
|
+ // 创建光晕的呼吸动画
|
|
|
+ halo.animate({
|
|
|
+ key: "radius",
|
|
|
+ from: 8,
|
|
|
+ to: 16,
|
|
|
+ duration: 1000,
|
|
|
+ loops: Infinity,
|
|
|
+ easing: am5.ease.cubic,
|
|
|
+ direction: "alternate"
|
|
|
+ });
|
|
|
|
|
|
- // 等待所有动画完成
|
|
|
- await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
+ halo.animate({
|
|
|
+ key: "fillOpacity",
|
|
|
+ from: 0.3,
|
|
|
+ to: 0.1,
|
|
|
+ duration: 1000,
|
|
|
+ loops: Infinity,
|
|
|
+ easing: am5.ease.cubic,
|
|
|
+ direction: "alternate"
|
|
|
+ });
|
|
|
|
|
|
- try {
|
|
|
- // 按特定顺序清理图表资源
|
|
|
- if (this.bullet) {
|
|
|
- this.bullet.dispose();
|
|
|
- this.bullet = null;
|
|
|
- }
|
|
|
+ return am5.Bullet.new(root, {
|
|
|
+ sprite: container
|
|
|
+ });
|
|
|
+ });
|
|
|
|
|
|
- if (this.bulletSeries && !this.bulletSeries.isDisposed()) {
|
|
|
- this.bulletSeries.dispose();
|
|
|
- this.bulletSeries = null;
|
|
|
- }
|
|
|
+ // 为 series2 添加圆点
|
|
|
+ this.series2.bullets.push((root) => {
|
|
|
+ const container = am5.Container.new(root, {});
|
|
|
+
|
|
|
+ // 创建光晕效果(外圈)
|
|
|
+ const halo = am5.Circle.new(root, {
|
|
|
+ radius: 12,
|
|
|
+ fill: am5.color("#3888fa"),
|
|
|
+ fillOpacity: 0.3,
|
|
|
+ stroke: am5.color("#3888fa"),
|
|
|
+ strokeOpacity: 0.3,
|
|
|
+ strokeWidth: 2
|
|
|
+ });
|
|
|
|
|
|
- if (this.series && !this.series.isDisposed()) {
|
|
|
- this.series.dispose();
|
|
|
- this.series = null;
|
|
|
- }
|
|
|
+ // 创建主圆点(内圈)
|
|
|
+ const circle = am5.Circle.new(root, {
|
|
|
+ radius: 6,
|
|
|
+ fill: am5.color("#3888fa"),
|
|
|
+ fillOpacity: 0.8,
|
|
|
+ stroke: am5.color("#fff"),
|
|
|
+ strokeWidth: 2
|
|
|
+ });
|
|
|
|
|
|
- if (this.xAxis && !this.xAxis.isDisposed()) {
|
|
|
- this.xAxis.dispose();
|
|
|
- this.xAxis = null;
|
|
|
- }
|
|
|
+ // 将两个圆形添加到容器
|
|
|
+ container.children.push(halo);
|
|
|
+ container.children.push(circle);
|
|
|
+
|
|
|
+ // 创建光晕的呼吸动画
|
|
|
+ halo.animate({
|
|
|
+ key: "radius",
|
|
|
+ from: 8,
|
|
|
+ to: 16,
|
|
|
+ duration: 1000,
|
|
|
+ loops: Infinity,
|
|
|
+ easing: am5.ease.cubic,
|
|
|
+ direction: "alternate"
|
|
|
+ });
|
|
|
|
|
|
- if (this.chart && !this.chart.isDisposed()) {
|
|
|
- this.chart.dispose();
|
|
|
- this.chart = null;
|
|
|
- }
|
|
|
+ halo.animate({
|
|
|
+ key: "fillOpacity",
|
|
|
+ from: 0.3,
|
|
|
+ to: 0.1,
|
|
|
+ duration: 1000,
|
|
|
+ loops: Infinity,
|
|
|
+ easing: am5.ease.cubic,
|
|
|
+ direction: "alternate"
|
|
|
+ });
|
|
|
|
|
|
- if (this.root && !this.root.isDisposed()) {
|
|
|
- this.root.dispose();
|
|
|
- this.root = null;
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- console.error("清理图表资源时出错:", e);
|
|
|
+ return am5.Bullet.new(root, {
|
|
|
+ sprite: container
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error("初始化图表时出错:", error);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
- // 修改数据更新方法
|
|
|
async startLiveData() {
|
|
|
if (this.timer) {
|
|
|
clearInterval(this.timer);
|
|
|
}
|
|
|
|
|
|
- let lastTimestamp = Date.now();
|
|
|
-
|
|
|
this.timer = setInterval(async () => {
|
|
|
- // 多重状态检查
|
|
|
- if (
|
|
|
- this.isDestroyed ||
|
|
|
- this.isAnimating ||
|
|
|
- !this.root ||
|
|
|
- this.root.isDisposed() ||
|
|
|
- !this.series ||
|
|
|
- this.series.isDisposed()
|
|
|
- ) {
|
|
|
- clearInterval(this.timer);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
try {
|
|
|
const response = await gpu_info();
|
|
|
- const gpuData = response.data.data[0];
|
|
|
const currentTime = Date.now();
|
|
|
|
|
|
- if (currentTime - lastTimestamp < 900) {
|
|
|
- return;
|
|
|
- }
|
|
|
+ // 模拟两组数据
|
|
|
+ const expectedValue = Math.random() * 30 + 50;
|
|
|
+ const actualValue = response.data.data[0].usage;
|
|
|
|
|
|
- // 安全地更新数据
|
|
|
- if (!this.isDestroyed && this.series && !this.series.isDisposed()) {
|
|
|
- const newValue = gpuData.temperature;
|
|
|
-
|
|
|
- // 数据平滑处理
|
|
|
- this.lastValue =
|
|
|
- Math.abs(newValue - this.lastValue) > 10
|
|
|
- ? this.lastValue + (newValue > this.lastValue ? 2 : -2)
|
|
|
- : newValue;
|
|
|
-
|
|
|
- this.data.push({
|
|
|
- date: currentTime,
|
|
|
- value: this.lastValue,
|
|
|
- });
|
|
|
+ // 更新数据
|
|
|
+ this.data1.push({
|
|
|
+ date: currentTime,
|
|
|
+ value: expectedValue
|
|
|
+ });
|
|
|
+ this.data2.push({
|
|
|
+ date: currentTime,
|
|
|
+ value: actualValue
|
|
|
+ });
|
|
|
|
|
|
- if (this.data.length > 100) {
|
|
|
- this.data.shift();
|
|
|
- }
|
|
|
+ // 保持固定数量的数据点
|
|
|
+ if (this.data1.length > 15) {
|
|
|
+ this.data1.shift();
|
|
|
+ this.data2.shift();
|
|
|
+ }
|
|
|
|
|
|
- this.series.data.setAll(this.data);
|
|
|
- this.updateBullet();
|
|
|
+ // 更新图表
|
|
|
+ this.series1.data.setAll(this.data1);
|
|
|
+ this.series2.data.setAll(this.data2);
|
|
|
|
|
|
- // 安全地更新x轴
|
|
|
- if (this.xAxis && !this.xAxis.isDisposed()) {
|
|
|
- this.xAxis.zoomToDates(
|
|
|
- new Date(this.data[0].date),
|
|
|
- new Date(this.data[this.data.length - 1].date)
|
|
|
- );
|
|
|
- }
|
|
|
+ // 更新X轴范围
|
|
|
+ if (this.data1.length > 1) {
|
|
|
+ this.xAxis.zoomToDates(
|
|
|
+ new Date(this.data1[0].date),
|
|
|
+ new Date(this.data1[this.data1.length - 1].date)
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
- lastTimestamp = currentTime;
|
|
|
} catch (error) {
|
|
|
- console.error("Failed to fetch GPU info:", error);
|
|
|
+ console.error("Failed to fetch data:", error);
|
|
|
}
|
|
|
}, 2000);
|
|
|
},
|
|
|
|
|
|
- // 修改bullet更新方法
|
|
|
- updateBullet() {
|
|
|
- if (
|
|
|
- this.isDestroyed ||
|
|
|
- !this.root ||
|
|
|
- this.root.isDisposed() ||
|
|
|
- !this.bulletSeries ||
|
|
|
- this.bulletSeries.isDisposed() ||
|
|
|
- !this.data.length
|
|
|
- ) {
|
|
|
- return;
|
|
|
+ async cleanupResources() {
|
|
|
+ this.isDestroyed = true;
|
|
|
+ if (this.timer) {
|
|
|
+ clearInterval(this.timer);
|
|
|
+ this.timer = null;
|
|
|
}
|
|
|
|
|
|
- try {
|
|
|
- const lastDataPoint = this.data[this.data.length - 1];
|
|
|
- this.bulletSeries.data.setAll([lastDataPoint]);
|
|
|
- } catch (e) {
|
|
|
- console.error("更新bullet时出错:", e);
|
|
|
+ if (this.root) {
|
|
|
+ this.root.dispose();
|
|
|
}
|
|
|
- },
|
|
|
- },
|
|
|
+ }
|
|
|
+ }
|
|
|
};
|
|
|
-</script>
|
|
|
+</script>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|