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

Chart.js分组柱状图(含正负值)单柱垂直渐变失效求助

问题描述

我正在使用Chart.js开发分组柱状图,想要为柱状条添加渐变颜色。当前实现的是基于整个图表区域的全局垂直渐变,希望改成包含正负值的垂直分组柱状图中,每个柱子从顶部到底部基于自身高度的垂直渐变。

当前代码如下:

import { Chart, Chart as ChartJS, registerables } from "chart.js";
import {
    getAxesTitle,
    getGridConfig,
    getLegendsConfig,
    getTooltipConfig,
    getZoomPanConfig,
    numDifferentiation
} from "./ChartJsConfig";
import React, { useEffect, useRef } from "react";

import annotationPlugin from "chartjs-plugin-annotation";
import { BarChartProps } from "types/Interfaces";
import { styleVariable } from "utils/UtilityFunc";
import zoomPlugin from "chartjs-plugin-zoom";

ChartJS.register(
    zoomPlugin,
    annotationPlugin,
    ...registerables
);

const BarChart = (props: BarChartProps) => {

    const { isShowGrid, isShowLegends, isShowTooltip, isZoomPan,
        isStacked, dataSet, zoomConfig, panConfig, annotation, titleInfo } = props;

    const chartContainer = useRef(null);

    function getScales() {
        return {
            x: {
                stacked: isStacked,
                ticks: {
                    color: styleVariable("--chart-scale"),
                    font: {
                        size: 11,
                        weight: "normal",
                        family: "NxtOption-Medium"
                    },
                    "callback": function (value: string | number) {
                        const newThis = this as any;
                        return newThis.getLabelForValue(value);
                    }
                },
                border: {
                    color: styleVariable("--chart-scale"),
                },
                title: getAxesTitle(titleInfo.x),
                grid: getGridConfig(isShowGrid)
            },
            y: {
                stacked: isStacked,
                ticks: {
                    color: styleVariable("--chart-scale"),
                    font: {
                        size: 11,
                        weight: "normal",
                        family: "NxtOption-Medium"
                    },
                    "callback": function (label: number) {
                        return numDifferentiation(label);
                    }
                },
                border: {
                    color: styleVariable("--chart-scale"),
                },
                title: getAxesTitle(titleInfo.y),
                grid: getGridConfig(isShowGrid)
            }
        };
    }

    function updateGradientColors(ctx: any, chartArea: any) {
        
        const redGradient = ctx && ctx.createLinearGradient(0, chartArea.top, 0, chartArea.bottom);
        if (redGradient) {
            redGradient.addColorStop(0.2, styleVariable("--bar-chart-red-top-color"));
            redGradient.addColorStop(1, styleVariable("--bar-chart-red-bottom-color"));
        }

        const greenGradient = ctx && ctx.createLinearGradient(0, chartArea.top, 0, chartArea.bottom);
        if (greenGradient) {
            greenGradient.addColorStop(0.2, styleVariable("--bar-chart-green-top-color"));
            greenGradient.addColorStop(1, styleVariable("--bar-chart-green-bottom-color"));
        }

        const gradientColors = [
            redGradient, greenGradient
        ];

        return gradientColors;
    }

    function getGradientColors() {
        if (chartContainer && chartContainer.current) {
            const normalColors = [
                styleVariable("--chart-call-oi-area-color"), 
                styleVariable("--chart-put-oi-area-color")
            ];
            
            return dataSet.datasets.map((dataset, idx) => {
                return {
                    ...dataset,
                    backgroundColor: function(context: any) {
                        const chart = context.chart;
                        const { ctx, chartArea } = chart;
                        if (!chartArea) return null;
                        const colors = updateGradientColors(ctx, chartArea);
                        if (colors.length)
                            return colors[ idx ];

                        return normalColors[ idx ];
                    }
                };
            });
        }

        return {};
    }

    useEffect(() => {
        if (chartContainer && chartContainer.current) {
            const configData = {
                type: "bar",
                "data": { ...dataSet, datasets: getGradientColors() },
                options: {
                    responsive: true,
                    animation: false,
                    maintainAspectRatio: false,
                    interaction: {
                        mode: "index",
                    },
                    scales: getScales(),
                    plugins: {
                        legend: getLegendsConfig(isShowLegends),
                        zoom: getZoomPanConfig(isZoomPan, zoomConfig, panConfig),
                        tooltip: getTooltipConfig(isShowTooltip),
                        annotation: annotation ?? ""
                    },
                    layout: {
                        autoPadding: false,
                        padding: {
                            top: 50, 
                            bottom: 10,
                            left: 20,
                            right: 20
                        },
                    }
                }
                
            };
              
            const newChartInstance = new Chart(
                chartContainer.current,
                configData as any
            );
            
            return () => {
                newChartInstance.destroy();
            };
        }

        return () => {
            return "";
        };
    }, [
        chartContainer, dataSet
    ]);

    return (
        <div className="chartjs-container">
            <canvas ref={chartContainer} className="canvas-tag" />
        </div>
    );
};

export default BarChart;
解决方案

要实现单个柱子的垂直渐变,需针对每个柱子的实际像素范围创建独立渐变,核心修改如下:

  1. 移除全局渐变生成函数updateGradientColors,改为在柱子的backgroundColor回调中单独生成渐变
  2. 通过context.element获取当前柱子的顶端(y)和底端(base)像素坐标,适配正负值的柱子方向
  3. 为每个柱子创建专属渐变,确保渐变范围完全匹配柱子自身高度

修改后的getGradientColors函数代码:

function getGradientColors() {
  if (chartContainer && chartContainer.current) {
    const normalColors = [
      styleVariable("--chart-call-oi-area-color"), 
      styleVariable("--chart-put-oi-area-color")
    ];
    
    return dataSet.datasets.map((dataset, idx) => {
      return {
        ...dataset,
        backgroundColor: function(context: any) {
          const chart = context.chart;
          const { ctx } = chart;
          const bar = context.element;
          
          // 获取当前柱子的顶端和底端像素坐标
          const barTop = bar.y;
          const barBottom = bar.base;
          
          // 创建匹配柱子高度的垂直渐变
          const gradient = ctx.createLinearGradient(0, barTop, 0, barBottom);
          
          // 根据数据集索引设置渐变颜色
          if (idx === 0) {
            gradient.addColorStop(0, styleVariable("--bar-chart-red-top-color"));
            gradient.addColorStop(1, styleVariable("--bar-chart-red-bottom-color"));
          } else {
            gradient.addColorStop(0, styleVariable("--bar-chart-green-top-color"));
            gradient.addColorStop(1, styleVariable("--bar-chart-green-bottom-color"));
          }
          
          return gradient;
        }
      };
    });
  }

  return {};
}

修改说明

  • 无需依赖chartArea,直接通过柱子元素的坐标计算渐变范围,自动适配正负值柱子的方向(负值柱子的base在y上方,渐变会自动反向)
  • 每个柱子生成独立的渐变对象,避免全局渐变导致的颜色错位问题
  • 保留了原有的CSS变量引用,维持样式的可配置性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 13:44:55