E B
E B

Reputation: 71

How to display multiple lines in Recharts X-Axis tick label?

I am being asked to put long string values as my X axis tick labels.

I tried other charting libraries, but this one overall has worked the best for my situation. I just cannot figure out what to do with the x axis labels.

I tried to create a separate div and map through the values underneath the chart, but I can't maintain the alignment between the chart and the values when there are more than 7 or 8 x axis values.

I had a tickFormatter function that cut the string at a certain number of characters/hid the overflow and was set at an angle on the x axis, but they still aren't happy with that.

This is my code currently and I feel like it is so close to being workable and I just wish that I could get the text to wrap onto more lines.

Working with SVGs is extremely confusing to me and I've tried to write functions that return SVG text elements as a custom tick, but I couldn't get it to work. Or when I pass something and it returns [object Object] as the label.

 const data = React.useMemo(() => {
 return orderOfItems?.map((claim, i) => ({
  Item: i + 1,
  Claim: claim.split(" ").join("\n"),
  Reach: (
    incrementalReachSummary[claim]?.Summary_Metrics.Reach * 100
  ).toFixed(1),
}));
}, [orderOfItems, incrementalReachSummary]);

const tickFormatter = (value: string) => {
const limit = 10; // put your maximum character

if (value.length < limit) return value;
return `${value.split(" ").join("\n")}...`;
};

return (
 <>
  <ResponsiveContainer
    align={"end"}
    w={"60%"}
    height={200}
    aspect={2}
    minWidth={"undefined"}
    maxHeight={"undefined"}
  >
    <LineChart
      data={data}
      margin={{
        top: 50,
        right: 250,
        left: 250,
        bottom: 500,
      }}
    >
      <CartesianGrid
        strokeDasharray="3"
        opacity={colorMode === "dark" ? 0.3 : 0.9}
      />
      <XAxis
        // hide={true}
        dataKey={"Claim"}
        interval="preserveStartEnd"
        angle={-35}
        tickFormatter={tickFormatter}
        textAnchor={"end"}
        axisLine={false}
        offset={5}
        tickMargin={10}
        style={{
          fill: colorMode === "dark" ? "#FFFFFF" : "#1A202C",
          textAlign: "center",
        }}
      >
        {/*<Label value="Claim" offset={100} position="bottom" />*/}
      </XAxis>
      <YAxis
        type={"number"}
        domain={[0, 100]}
        axisLine={false}
        tickLine={false}
        tickCount={7}
        tickMargin={10}
        tickFormatter={(Reach) => `${Reach}%`}
        style={{ fill: colorMode === "dark" ? "#FFFFFF" : "#1A202C" }}
      />
      <Tooltip content={<CustomToolTip />} />
      <Line
        type="monotone"
        strokeWidth={3}
        dataKey="Reach"
        stroke="#3182CE"
        activeDot={{ r: 9 }}
      >
        <LabelList
          dataKey="Reach"
          offset={14}
          position="insideTopLeft"
          formatter={(Reach) => `${Reach}%`}
          fill={colorMode === "dark" ? "#FFFFFF" : "#1A202C"}
        />
      </Line>
    </LineChart>
  </ResponsiveContainer>

enter image description here

Upvotes: 6

Views: 13194

Answers (3)

Super Hero
Super Hero

Reputation: 11

I wrote a component that outputs multiline string in more accurately way. Just add \n (ex. June\n2024) in you label string and it would output label as multiline string.

You can use it this way

<XAxis tick={<CustomizedAxisTick />} />

And this is custom tick component

function CustomizedAxisTick(props: TickProps) {
  const { fill, height, orientation, payload, stroke, textAnchor, type, width, x, y } = props;

  return (
    <text
      {...{ fill, height, orientation, stroke, textAnchor, type, width, x, y }}
      className="recharts-text recharts-cartesian-axis-tick-value"
    >
      {payload.value.split('\n').map((value: string, index: number) => {
        const dy = 0.71 * (index + 1) + 'em';
        return (
          <tspan dy={dy} key={index} x={payload.tickCoord}>
            {value}
          </tspan>
        );
      })}
    </text>
  );
}

Upvotes: 1

Drew Deal
Drew Deal

Reputation: 61

Actually it is easier and cleaner to just use the <Text> component and set a width in order to force a wrap. see https://recharts.org/en-US/api/Text

In my case, I also needed to alternate the y position in order to keep text from overlapping.

<Text x={x} y={y+offset} style={{fontSize: "12px"}} fill="#000000" textAnchor="middle" width="170" verticalAnchor="start">
   {payload.value}
</Text>

Upvotes: 1

A G
A G

Reputation: 22617

You need to create a custom tick react component. Line breaks will not work. You will need to use multiple <tspan> components with correct dy or vertical position. Also increase the chart bottom so lines are not hidden.

Use payload.value to access the original value & create as many tspans required.

<XAxis tick={<CustomizedTick />} />

Example

function CustomizedTick(props) {
    const { x, y, stroke, payload } = props;
    return (
        <g transform={`translate(${x},${y})`}>
        <text x={0} y={0} dy={16} fill="#666">
          <tspan textAnchor="middle" x="0">
            Line 1
          </tspan>
          <tspan textAnchor="middle" x="0" dy="20">
            Line 2
          </tspan>
          <tspan textAnchor="middle" x="0" dy="40">
            Line 3
          </tspan>
        </text>
      </g>
    );
  }

Working codesandbox.

Upvotes: 4

Related Questions