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

React中Nivo Rocks图表Y轴刻度异常问题求助

解决Nivo ResponsiveBar Y轴刻度未适配数据最大值的问题

问题描述

在React项目中使用Nivo Rocks的ResponsiveBar图表时,出现Y轴刻度未正确适配数据最大值的问题——比如当数据中“Latest offer”字段值为893时,Y轴仅显示到800,未能包含完整数值。该问题仅在部分场景出现,需修复以满足QA要求。

代码实现

const ValueAtStack = () => {
    const { callData, data, loading } = useNegotiationGlanceStackValue();
    const params = useParams<any>();
    const { data: dataNegotiation } = useNegotiation(params.id);

    useEffect(() => {
        if (params.id) callData({ negotiationId: params.id });
    }, [params.id]);

    const colors = [
        ThemeColors().blueDark1,
        ...(data?.data ? data?.data?.map(() => ThemeColors().blue) : []),
    ];

    return (
        <Item
            icon={<DollarOutlined />}
            title={t("Saving")}
            loading={loading}
            empty={data?.data?.length === 0 || !data?.data}
            emptyDescription={t("Please add some negotiation rounds in Result Tracker")}
            emptyExtra={
                <CustomButton
                    href={`/pipelines/fact-sheet/${params.id}/lead-negotiation/results-tracker`}>
                    {t("Results Tracker")}
                </CustomButton>
            }>
            <div className={classes.description}>
                <Currency replace={dataNegotiation?.currencyId} />
                {t(`'${data?.unit}`)}
            </div>
            <div
                className={classes.content}
                style={{
                    height: 300,
                    width: window.innerWidth - 300,
                }}>
                {data && (
                    <ResponsiveBar
                        data={[data.target, ...data?.data]}
                        keys={["Latest offer"]}
                        indexBy="date"
                        margin={{ top: 50, right: 20, bottom: 50, left: 60 }}
                        padding={0.3}
                        colors={colors}
                        colorBy="indexValue"
                        valueScale={{ type: "linear" }}
                        indexScale={{ type: "band", round: true }}
                        defs={[
                            {
                                id: "dots",
                                type: "patternDots",
                                background: "inherit",
                                color: "#38bcb2",
                                size: 4,
                                padding: 1,
                                stagger: true,
                            },
                            {
                                id: "lines",
                                type: "patternLines",
                                background: "inherit",
                                color: "#eed312",
                                rotation: -45,
                                lineWidth: 6,
                                spacing: 10,
                            },
                        ]}
                        borderColor={{
                            from: "color",
                            modifiers: [["darker", 1.6]],
                        }}
                        axisTop={null}
                        axisRight={null}
                        axisBottom={{
                            tickSize: 5,
                            tickPadding: 5,
                            tickRotation: data.length > 5 ? 45 : 0,
                            legendPosition: "middle",
                            legendOffset: 32,
                        }}
                        axisLeft={{
                            tickSize: 5,
                            tickPadding: 5,
                            tickRotation: 0,
                            legendPosition: "middle",
                            legendOffset: -40,
                        }}
                        labelSkipWidth={12}
                        labelSkipHeight={12}
                        labelTextColor={{
                            from: "color",
                            modifiers: [["darker", 1.6]],
                        }}
                        role="application"
                    />
                )}
            </div>
        </Item>
    );
};

export default ValueAtStack;

最小测试数据

{
    date: "2023-03-23T12:37:32.835",
    "Latest offer": 893,
}

解决方案

问题根源在于Nivo默认的线性刻度计算会自动取整到最近的“友好”数值,导致最大值未被包含。以下两种方式可彻底解决:

方法1:手动指定Y轴刻度范围

通过配置valueScale的domain属性,强制设置Y轴的最小值和最大值。先计算数据中的最大值,再将最大值(或加少量余量)作为domain的上限,确保刻度覆盖完整数据:

// 在组件内部计算数据最大值
const maxValue = data ? Math.max(
    data.target["Latest offer"],
    ...data.data.map(item => item["Latest offer"])
) : 0;

// 修改ResponsiveBar的valueScale配置
valueScale={{ 
    type: "linear", 
    domain: [0, maxValue * 1.1] // 乘以1.1给图表顶部留余量,也可直接用maxValue
}}

方法2:自定义Y轴刻度值

通过axisLeft的tickValues属性生成精确的刻度,确保最大值被包含在内:

// 计算数据最大值
const maxValue = data ? Math.max(
    data.target["Latest offer"],
    ...data.data.map(item => item["Latest offer"])
) : 0;

// 生成自定义刻度,按步长适配数据
const generateTicks = (max) => {
    const ticks = [];
    const step = 100; // 可根据数据量级调整步长
    for (let i = 0; i <= max; i += step) {
        ticks.push(i);
    }
    // 如果最大值不是步长的倍数,手动添加最大值作为最后一个刻度
    if (max % step !== 0) {
        ticks.push(max);
    }
    return ticks;
};

// 修改ResponsiveBar的axisLeft配置
axisLeft={{
    tickSize: 5,
    tickPadding: 5,
    tickRotation: 0,
    legendPosition: "middle",
    legendOffset: -40,
    tickValues: generateTicks(maxValue)
}}

完整修改后的组件片段

const ValueAtStack = () => {
    const { callData, data, loading } = useNegotiationGlanceStackValue();
    const params = useParams<any>();
    const { data: dataNegotiation } = useNegotiation(params.id);

    useEffect(() => {
        if (params.id) callData({ negotiationId: params.id });
    }, [params.id]);

    const colors = [
        ThemeColors().blueDark1,
        ...(data?.data ? data?.data?.map(() => ThemeColors().blue) : []),
    ];

    // 计算数据中的最大值
    const maxValue = data ? Math.max(
        data.target["Latest offer"],
        ...data.data.map(item => item["Latest offer"])
    ) : 0;

    return (
        <Item
            icon={<DollarOutlined />}
            title={t("Saving")}
            loading={loading}
            empty={data?.data?.length === 0 || !data?.data}
            emptyDescription={t("Please add some negotiation rounds in Result Tracker")}
            emptyExtra={
                <CustomButton
                    href={`/pipelines/fact-sheet/${params.id}/lead-negotiation/results-tracker`}>
                    {t("Results Tracker")}
                </CustomButton>
            }>
            <div className={classes.description}>
                <Currency replace={dataNegotiation?.currencyId} />
                {t(`'${data?.unit}`)}
            </div>
            <div
                className={classes.content}
                style={{
                    height: 300,
                    width: window.innerWidth - 300,
                }}>
                {data && (
                    <ResponsiveBar
                        data={[data.target, ...data?.data]}
                        keys={["Latest offer"]}
                        indexBy="date"
                        margin={{ top: 50, right: 20, bottom: 50, left: 60 }}
                        padding={0.3}
                        colors={colors}
                        colorBy="indexValue"
                        valueScale={{ 
                            type: "linear", 
                            domain: [0, maxValue * 1.1]
                        }}
                        indexScale={{ type: "band", round: true }}
                        defs={[
                            {
                                id: "dots",
                                type: "patternDots",
                                background: "inherit",
                                color: "#38bcb2",
                                size: 4,
                                padding: 1,
                                stagger: true,
                            },
                            {
                                id: "lines",
                                type: "patternLines",
                                background: "inherit",
                                color: "#eed312",
                                rotation: -45,
                                lineWidth: 6,
                                spacing: 10,
                            },
                        ]}
                        borderColor={{
                            from: "color",
                            modifiers: [["darker", 1.6]],
                        }}
                        axisTop={null}
                        axisRight={null}
                        axisBottom={{
                            tickSize: 5,
                            tickPadding: 5,
                            tickRotation: data.length > 5 ? 45 : 0,
                            legendPosition: "middle",
                            legendOffset: 32,
                        }}
                        axisLeft={{
                            tickSize: 5,
                            tickPadding: 5,
                            tickRotation: 0,
                            legendPosition: "middle",
                            legendOffset: -40,
                        }}
                        labelSkipWidth={12}
                        labelSkipHeight={12}
                        labelTextColor={{
                            from: "color",
                            modifiers: [["darker", 1.6]],
                        }}
                        role="application"
                    />
                )}
            </div>
        </Item>
    );
};

export default ValueAtStack;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 09:37:04