Search code examples
javascriptreactjsrecharts

Fill opacity don't work with calculate value Recharts


For each Stacked Bar element, I calculate its percentage

const valuePercent = attribute => ({payload}) => {
    const keys  = getKeys(chartData);
    const total = keys.reduce((acc, curr) => {
        return acc + payload[curr].count;
    }, 0);
    const ratio = total > 0 ? payload[attribute].count / total : 0;
    return `${(ratio * 100).toFixed(0)}%`;
  };

But when I substitute this value in the style, it doesn't work. What could be the problem?

return keys.map((item, index) => ( <
  Bar key = {
    index
  }
  dataKey = {
    `${item}.count`
  }
  stackId = 'a'
  style = {
    {
      fill: '#0452D7',
      fillOpacity: valuePercent(item),
    }
  }
  />
));
if you just put some value in fill opacity, everything is fine. At the same time, I see in the console that the function is triggered, and the percentages are calculated

All my code


Solution

  • May shape properties of Bar not work with hooks, so you need to customize using shape. so as checked your codesandbox code and update with solve code as below:

    import "./styles.css";
    import React from "react";
    import { BarChart, Bar, XAxis, YAxis, LabelList } from "recharts";
    
    const chartData = [
      {
        name: "data 1",
        city1: { count: 1000, label: "data 1", price: 9999 },
        city2: { count: 2400, label: "data 2", price: 9999 },
        city3: { count: 2400, label: "data 3", price: 9999 }
      },
      {
        name: "data 2",
        city1: { count: 4000, label: "data 1", price: 9999 },
        city2: { count: 2400, label: "data 2", price: 9999 },
        city3: { count: 2400, label: "data 3", price: 9999 }
      },
      {
        name: "data 3",
        city1: { count: 4000, label: "data 1", price: 9999 },
        city2: { count: 2400, label: "data 2", price: 9999 },
        city3: { count: 2400, label: "data 3", price: 9999 }
      },
      {
        name: "data 4",
        city1: { count: 4000, label: "data 1", price: 9999 },
        city2: { count: 2400, label: "data 2", price: 9999 },
        city3: { count: 2400, label: "data 3", price: 9999 }
      },
      {
        name: "data 5",
        city1: { count: 4000, label: "data 1", price: 9999 },
        city2: { count: 2400, label: "data 2", price: 9999 },
        city3: { count: 2400, label: "data 3", price: 9999 }
      },
      {
        name: "data 6",
        city1: { count: 4000, label: "data 1", price: 9999 },
        city4: { count: 2400, label: "data 3", price: 9999 }
      }
    ];
    
    export default function App() {
      const getKeys = (data) => {
        const keys = [...new Set(data.flatMap(Object.keys))];
        return keys.filter((key) => key !== "name");
      };
    
      const valueAccessor = (attribute) => ({ payload }) => {
        const keys = getKeys(chartData);
        const total = keys.reduce((acc, curr) => {
          if (payload.hasOwnProperty(curr)) {
            return acc + payload[curr].count;
          }
          return acc;
        }, 0);
        if (payload.hasOwnProperty(attribute)) {
          const ratio = total > 0 ? payload[attribute].count / total : 0;
          return `${(ratio * 100).toFixed(0)}%`;
        }
      };
    
      const getBarShape = (x, y, width, height, radius, newFillOpacity) => {
        const [tl, tr, bl, br] = radius;
        const d = `M${x},${y + tl}
          a${tl},${tl} 0 0 1 ${tl},${-tl}
          h${width - tl - tr}
          a${tr},${tr} 0 0 1 ${tr},${tr}
          v${height - tr - br}
          a${br},${br} 0 0 1 ${-br},${br}
          h${bl + (br - width)}
          a${bl},${bl} 0 0 1 ${-bl},${-bl}
          z`;
        return ({ fill, fillOpacity, stroke }) => (
          <path
            d={d}
            fill={fill}
            fillOpacity={newFillOpacity ? newFillOpacity : fillOpacity}
            stroke={stroke}
          />
        );
      };
    
      const CustomleBar = ({ x, y, width, height, item, ...rest }) => {
        const fillOpacity = valueAccessor(item)(rest);
        const Bar = getBarShape(x, y, width, height, [0, 0, 0, 0], fillOpacity);
        return (
          <g>
            <Bar stackId="a" fill="#0452D7" />
          </g>
        );
      };
    
      const renderBars = () => {
        const keys = getKeys(chartData);
        return keys.map((item, index) => {
          return (
            <Bar
              key={index}
              dataKey={`${item}.count`}
              stackId="a"
              shape={<CustomleBar item={item} />}
            >
              <LabelList
                valueAccessor={(props) => valueAccessor(item)(props)}
                fill="#fff"
              />
            </Bar>
          );
        });
      };
    
      return (
        <BarChart
          width={500}
          height={300}
          data={chartData}
          stackOffset="expand"
          margin={{
            top: 20,
            right: 30,
            left: 20,
            bottom: 5
          }}
        >
          <XAxis hide dataKey="name" />
          <YAxis hide />
          {renderBars()}
        </BarChart>
      );
    }