6

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

qwerty
  • 205
  • 2
  • 10

2 Answers2

2

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>
  );
}
Jayesh Naghera
  • 319
  • 3
  • 13
0

There isn't much of a reference in the docs but after looking through some examples, it seems like the problem in your code may be that the fillOpacity property of Recharts expects a numerical value:,

Example code for AreaChart: enter image description here

Your valuePercent() returns a string with the percentage. Changing the return type of valuePercent() to a numerical value should solve your issue.

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;

    // Ensure that the returned value is a numerical value between 0 and 1
    // by multiplying the ratio with 100 and dividing it by 100
    return ratio * 100 / 100;
  };
Aib Syed
  • 3,118
  • 2
  • 19
  • 30
  • It doesn't work. If I specify a value of '30%' the opacity works. – qwerty Dec 08 '22 at 06:07
  • If it is working as a string, can you ensure your value is a string by doing something like this? `fillOpacity: valuePercent(item).toString()` – Aib Syed Dec 08 '22 at 15:08
  • dont work.I posted the full code in the question. – qwerty Dec 09 '22 at 10:54
  • What do you get when you `console.log` the output value of the valuePercent function? – Aib Syed Dec 09 '22 at 15:53
  • for some reason the function is not outputting anything. I do not understand why. Can you see my code in the question. I added a link to codeandbox – qwerty Dec 09 '22 at 19:04
  • That's your issue then. I don't know what's in your payload but you should `console.log` the payload and then try to see how you can properly output the `count`. If you can also provide an example of the payload, it would help to solve the problem. – Aib Syed Dec 09 '22 at 19:29