Search code examples
javascriptreactjsrecharts

In RechartsJs, how to add margin on ReferenceLine label?


This is the result I want to achieve.

enter image description here

But I can't find a way to add margin between the ReferenceLine label and the bar components.

This is my chart component

    <BarChart
      width={500}
      height={300}
      data={data}
    >
      <XAxis hide dataKey="name" />
      {
        data.map((entry, index) => (
          <ReferenceLine key={`cell-${index}`} strokeWidth={0} x={entry.name} label={entry.name} />
        ))
      }
      <Bar dataKey="pv" fill="#8884d8">
        {
          data.map((entry, index) => (
            <Cell key={`cell-${index}`} fill={entry.pv <= 0 ? "#FF645C" : "#29DB92"} />
          ))
        }
      </Bar>
    </BarChart>

You can see the full example on my sandbox. https://codesandbox.io/s/bar-chart-with-positive-negative-forked-g3kvz5?file=/src/App.tsx:673-1176


Solution

  • Important: This solution is technically not "correct" if you care for the integrity of your data because it modifies the data so every entry does not start at zero, but because in this case we do not want to show an y-axis anyway I think this can be a simple solution:

    const transformData = (margin: number) => {
      return data.map((item) => {
        if (item.pv >= 0) {
          return {
            name: item.name,
            pv: [margin, item.pv + margin]
          };
        } else {
          return {
            name: item.name,
            pv: [-margin, item.pv - margin]
          };
        }
      });
    };
    

    This simple function maps over every entry of the data and depending of the original pv value returns a new object with the new position of the corresponding entry. The new pv value is an array where the first entry is the new starting position and the second one is the end position/length of the entry. The new starting position is ether the value of margin (When the original pv is positiv) or -margin (When the original pv is negativ). To keep the original length of the entries the second value of the new array is the original pv +/- the margin.

    The App component in this example than has to be modified like this:

    export default function App() {
      const barData = useMemo(() => transformData(1000), []);
    
      return (
        <BarChart width={500} height={300} data={barData}>
          <XAxis hide dataKey="name" />
          {barData.map((entry, index) => (
            <ReferenceLine
              key={`cell-${index}`}
              strokeWidth={0}
              x={entry.name}
              label={entry.name}
            />
          ))}
          <Bar dataKey="pv" fill="#8884d8">
            {barData.map((entry, index) => (
              <Cell
                key={`cell-${index}`}
                fill={entry.pv[0] <= 0 ? "#FF645C" : "#29DB92"}
              />
            ))}
          </Bar>
        </BarChart>
      );
    }
    

    Note: Since the new pv value of each entry is no longer a simple number but an array we need to change the fill property of each Cell component from entry.pv <= 0 ? "#FF645C" : "#29DB92" to entry.pv[0] <= 0 ? "#FF645C" : "#29DB92". You also can play with the margin value to fit your purpose. I choose 1000 here because I thought it looks nice :)

    Link to the full code: https://codesandbox.io/s/bar-chart-with-positive-negative-forked-cu6d7q?file=/src/App.tsx:971-1026