You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修改Amcharts5仪表盘图表使其填充至指定值

问题描述

当前使用Vue3组合式API + Nuxt3实现了带指针的Amcharts5仪表盘图表,现需要:

  1. 将其改为仅填充至指定值的仪表盘样式(参考Figma设计);
  2. 解决图表无法占满画布且不响应式的问题。

当前实现代码如下:

<template>
    <div class="gauge-chart" ref="gaugeRef"></div>
</template>

<script setup>
import * as am5 from "@amcharts/amcharts5";
import * as am5xy from "@amcharts/amcharts5/xy";
import * as am5radar from "@amcharts/amcharts5/radar";
import am5themes_Animated from "@amcharts/amcharts5/themes/Animated";

const props = defineProps({
    data: {
        required: true,
        type: Object,
    },
    gaugeBands: {
        required: true,
        type: Array,
    },
});

const gaugeRef = ref(null);
let root;
const axisDataItem = shallowRef(null);

onMounted(() => {
    root = am5.Root.new(gaugeRef.value);

    root.setThemes([am5themes_Animated.new(root)]);

    const chart = root.container.children.push(
        am5radar.RadarChart.new(root, {
            panX: false,
            panY: false,
            height: 200,
            width: 200,
            centerX: 29,
            centerY: 35,
            startAngle: -240,
            endAngle: 60,
        })
    );

    const axisRenderer = am5radar.AxisRendererCircular.new(root, {
        radius: am5.percent(100),
        innerRadius: -20,
        minGridDistance: 0,
    });

    axisRenderer.grid.template.setAll({
        stroke: root.interfaceColors.get("background"),
        visible: false,
        strokeOpacity: 1,
    });

    const axis = chart.xAxes.push(
        am5xy.ValueAxis.new(root, {
            maxDeviation: 0,
            min: 0,
            max: 60,
            strictMinMax: true,
            renderer: axisRenderer,
        })
    );

    axisDataItem.value = axis.makeDataItem({});
    const clockHand = am5radar.ClockHand.new(root, {
        pinRadius: am5.percent(50),
        radius: am5.percent(75),
        bottomWidth: 40,
        topWidth: 1,
    });

    const bullet = axisDataItem.value.set(
        "bullet",
        am5xy.AxisBullet.new(root, {
            sprite: clockHand,
        })
    );

    bullet.set("fill", am5.color(0x000000));

    axis.createAxisRange(axisDataItem.value);

    const label = chart.radarContainer.children.push(
        am5.Label.new(root, {
            fill: am5.color(0xffffff),
            centerX: am5.percent(50),
            textAlign: "center",
            centerY: am5.percent(50),
            fontSize: "2.5em",
            fontWeight: "bold",
        })
    );

    axisDataItem.value.set("value", Math.floor(props.data));
    bullet.get("sprite").on("rotation", () => {
        Math.round(axisDataItem.value.get("value")).toString();
        let fill = am5.color(0x000000);
        const value = axisDataItem.value.get("value");
        label.set("text", Math.round(value).toString());
        axis.axisRanges.each(function (axisRange) {
            if (value >= axisRange.get("value") && value <= axisRange.get("endValue")) {
                fill = axisRange.get("axisFill").get("fill");
            }
        });
        clockHand.pin.animate({
            key: "fill",
            to: fill,
            duration: 500,
            easing: am5.ease.out(am5.ease.cubic),
        });
        clockHand.hand.animate({
            key: "fill",
            to: fill,
            duration: 500,
            easing: am5.ease.out(am5.ease.cubic),
        });
    });

    chart.bulletsContainer.set("mask", undefined);

    am5.array.each(props.gaugeBands, (data) => {
        const axisRange = axis.createAxisRange(axis.makeDataItem({}));
        axisRange.setAll({
            value: data.lowScore,
            endValue: data.highScore,
        });

        axisRange.get("axisFill").setAll({
            visible: true,
            fill: am5.color(data.color),
            fillOpacity: 1,
        });
    });

    // Make stuff animate on load
    chart.appear(1000, 100);
});

onBeforeMount(() => {
    if (root) {
        root.dispose();
    }
});

watch(
    () => props.data,
    (newValue) =>
        axisDataItem.value.animate({
            key: "value",
            to: Math.floor(newValue),
            duration: 500,
            easing: am5.ease.out(am5.ease.cubic),
        })
);
</script>

<style>
.gauge-chart {
    left: 200px;
    width: 200px;
    height: 200px;
}
</style>
解决方案

1. 是否可以实现仅填充至指定值的仪表盘?

完全可以。通过Amcharts5的**轴范围(AxisRange)**功能,我们可以创建一个从最小值到目标值的填充段,替代原有的指针样式。

2. 具体实现方法

步骤1:改造仪表盘为填充式样式

  • 移除原有的ClockHand指针相关代码;
  • 创建一个动态轴范围,对应从0到目标值props.data的填充区域;
  • 保留中心数值标签,同步更新显示当前值。

步骤2:实现响应式占满画布

  • 移除图表的固定宽高,改为自适应容器尺寸;
  • 设置容器为100%宽高;
  • 开启Amcharts5的自动 resize 监听。

修改后的完整代码

<template>
    <div class="gauge-chart" ref="gaugeRef"></div>
</template>

<script setup>
import * as am5 from "@amcharts/amcharts5";
import * as am5xy from "@amcharts/amcharts5/xy";
import * as am5radar from "@amcharts/amcharts5/radar";
import am5themes_Animated from "@amcharts/amcharts5/themes/Animated";

const props = defineProps({
    data: {
        required: true,
        type: Number, // 修正类型:props.data应为数值而非Object
    },
    gaugeBands: {
        required: true,
        type: Array,
    },
});

const gaugeRef = ref(null);
let root;
let fillRange; // 用于存储动态填充的轴范围
const centerLabel = shallowRef(null);

onMounted(() => {
    root = am5.Root.new(gaugeRef.value);
    root._logo.dispose(); // 可选:移除Amcharts水印
    root.setThemes([am5themes_Animated.new(root)]);

    const chart = root.container.children.push(
        am5radar.RadarChart.new(root, {
            panX: false,
            panY: false,
            startAngle: -240,
            endAngle: 60,
            centerX: am5.percent(50), // 改为百分比居中
            centerY: am5.percent(50),
        })
    );

    const axisRenderer = am5radar.AxisRendererCircular.new(root, {
        radius: am5.percent(100),
        innerRadius: -20,
        minGridDistance: 0,
    });

    axisRenderer.grid.template.setAll({
        visible: false,
    });

    const axis = chart.xAxes.push(
        am5xy.ValueAxis.new(root, {
            maxDeviation: 0,
            min: 0,
            max: 60,
            strictMinMax: true,
            renderer: axisRenderer,
        })
    );

    // 添加背景分段(原gaugeBands)
    am5.array.each(props.gaugeBands, (data) => {
        const axisRange = axis.createAxisRange(axis.makeDataItem({}));
        axisRange.setAll({
            value: data.lowScore,
            endValue: data.highScore,
        });

        axisRange.get("axisFill").setAll({
            visible: true,
            fill: am5.color(data.color),
            fillOpacity: 0.3, // 背景分段设为半透明,突出填充段
        });
    });

    // 创建动态填充段:从0到目标值
    fillRange = axis.createAxisRange(axis.makeDataItem({}));
    fillRange.setAll({
        value: 0,
        endValue: Math.floor(props.data),
    });

    fillRange.get("axisFill").setAll({
        visible: true,
        fill: am5.color(0x000000), // 填充段颜色,可根据需求调整
        fillOpacity: 1,
    });

    // 添加中心数值标签
    centerLabel.value = chart.radarContainer.children.push(
        am5.Label.new(root, {
            fill: am5.color(0xffffff),
            centerX: am5.percent(50),
            textAlign: "center",
            centerY: am5.percent(50),
            fontSize: "2.5em",
            fontWeight: "bold",
            text: Math.floor(props.data).toString(),
        })
    );

    // 图表初始动画
    chart.appear(1000, 100);
});

onBeforeUnmount(() => {
    if (root) {
        root.dispose();
    }
});

// 监听数据变化,更新填充段和标签
watch(
    () => props.data,
    (newValue) => {
        const targetValue = Math.floor(newValue);
        // 动画更新填充段的结束值
        fillRange.animate({
            key: "endValue",
            to: targetValue,
            duration: 500,
            easing: am5.ease.out(am5.ease.cubic),
        });
        // 更新中心标签
        centerLabel.value.set("text", targetValue.toString());
    }
);
</script>

<style scoped>
.gauge-chart {
    width: 100%;
    height: 100%;
    min-height: 200px; /* 可选:设置最小高度防止容器过小 */
}
</style>

关键修改说明

  1. 填充式仪表盘改造:

    • 移除了ClockHand相关代码,改用axisRange创建动态填充段;
    • 背景分段设置为半透明,让填充段更突出;
    • 修正了props.data的类型定义(应为数值而非Object)。
  2. 响应式设置:

    • 移除了图表的固定width/height,改为百分比居中;
    • 容器样式改为width: 100%; height: 100%;,占满父容器;
    • 图表会自动监听容器尺寸变化并适配。

内容的提问来源于stack exchange,提问作者John

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 00:07:33