🐭

【React / Recharts】XAxis(横軸)のticksを三点リーダー(…)にする方法 | Offers Tech Blog

2023/07/03に公開

概要

こんにちは、Offers を運営している株式会社 overflow でフロントエンドのテックリードをしている Kazuya です。今回は、Reactのチャートライブラリ「Recharts」でXAxis(横軸)のticksを三点リーダー(…)にする方法を紹介します。少々マニアックな内容にはなりますが、最後までお付き合いいただけると幸いです。

XAxisのticksを三点リーダーにした棒グラフ
完成イメージ

[AD] Offers MGR(オファーズマネージャー)

本記事で紹介する方法は、筆者が担当しているプロダクトである「Offers MGR(オファーズマネージャー) 」で活用されています。「Four Keys分析」や「サイクルタイム分析」など開発組織の生産性を最大化するために必要となる指標を可視化させることができます。開発組織の健全性・生産性を中長期的に改善していきたい方はぜひお問い合わせください!

https://offers-mgr.com/lp/
https://zenn.dev/offersmgr

はじめに

本記事では、Reactのチャートライブラリ「Recharts」でXAxis(横軸)のticksを三点リーダー(…)にする方法を紹介します。基本的にReactの動作する環境であれば活用できますが、ライブラリのバージョンや環境依存によって正常に動作しない可能性はあります。実装の一例であることをご理解の上、参考にしていただけると幸いです。

実現したいこと

今回実現したいことは、RechartsでXAxis(横軸)のticksを三点リーダー(…)にするというものです。記事執筆した時点では、公式のドキュメントには 具体的なやり方は記載されていませんでした。そこで今回はXAxisのPropertiesにある「tick」をカスタマイズして実現していこうと思います。

https://recharts.org/en-US/api/XAxis#tick

使用するプラグイン

Recharts - v2.7.2

https://www.npmjs.com/package/recharts

実装方法

カスタム用のTickコンポーネントを作成する

最初にカスタマイズするTickコンポーネントを作成していきます。グラフコンポーネント毎に作るのではなく、1コンポーネントとして用意しておくと、再利用できることに加えメンテナンスもしやすくなります。

ChartEllipsisXAxisTick.tsx
import React from 'react';

type Props = {};

export const ChartEllipsisXAxisTick: React.FC<Props> = (props) => {
  const {} = props;
  return null
};

Propsを定義する

こちらの記事でも解説していますが、コンポーネント作成にあたって重要なのがPropsの定義です。今回はXAxisのtickをカスタマイズしていくため、ベースとなるPropsはXAxisから継承します。こちららは「Recharts」からインポートすることができます。

ChartEllipsisXAxisTick.tsx
import React from 'react';
import { XAxisProps } from 'recharts';

type Props = XAxisProps;

export const ChartEllipsisXAxisTick: React.FC<Props> = (props) => {
  const {} = props;
  return null
};

ただここで注意なのが、定義されている型が完全ではないことです。コンソールログ等で確認してもらうと分かるのですが、明らかに不足している型が存在しています。そこであまりスマートな実装ではありませんが、不足している型を追記していきます。今回はXAxis(横軸)で実際に表示される値が格納されている「payload」とTickの件数が格納されている「visibleTicksCount」を定義しています。

ChartEllipsisXAxisTick.tsx
import React from 'react';
import { XAxisProps } from 'recharts';

type Props = XAxisProps & {
  visibleTicksCount: number;
  payload: { value: number | string };
};

export const ChartEllipsisXAxisTick: React.FC<Props> = (props) => {
  const {} = props;
  // [MEMO]実際にPropsの中身を確認したい方は以下のコンソールログのコメントアウトを外してください
  // console.log(props);
  return null
};

表示する座標を指定する

ここからコンポーネントの本体部分を実装していきます。まず必要となるのが、Tickを表示させる座標の指定です。RechartsはDOMを見てもらえれば分かりますが、SVGで描写されています。そのため、要素を表示させるためには座標を定める必要があります。今回は表示位置は特に変更しないので、「XAxisProps」で定義されている「x」「y」をそのまま活用します。(表示位置もカスタマイズしたい方はここで調整してください)

ChartEllipsisXAxisTick.tsx
import React from 'react';
import { XAxisProps } from 'recharts';

type Props = XAxisProps & {
  visibleTicksCount: number;
  payload: { value: number | string };
};

export const ChartEllipsisXAxisTick: React.FC<Props> = (props) => {
  const { x, y } = props;
  return <g transform={`translate(${x},${y})`}></g>
};

上記のまま実装するとデフォルトと同じでグラフの真下に表示されます。

foreignObjectを活用してSVGにHTMLを埋め込む

今回の肝である実装箇所で、foreignObjectと呼ばれるタグを活用してSVG内にHTMLを埋め込めるようにしていきます。表示させる範囲と座標を定義してあげる必要があるので、まず範囲から定義していきます。

範囲を定義する

範囲は「width」と「height」それぞれに値を渡す必要があるため、まず「height」から定義していきます。heightは表示させる文字の高さ以上あれば問題ないため、任意設定してもらえれば大丈夫です。(今回の例では30としています)

ChartEllipsisXAxisTick.tsx
import React from 'react';
import { XAxisProps } from 'recharts';

type Props = XAxisProps & {
  visibleTicksCount: number;
  payload: { value: number | string };
};

export const ChartEllipsisXAxisTick: React.FC<Props> = (props) => {
  const { x, y } = props;
  return (
    <g transform={`translate(${x},${y})`}>
      <foreignObject height={30}></foreignObject>
    </g>
  );
};

次に「width」を定義していきますが、計算して出す必要があります。具体的な方法ですが、「XAxisProps」で定義されている「width(XAxisの横幅 ≒ グラフの横幅)」を追加で定義した「visibleTicksCount(tickの件数)」で割ることで算出することができます。

ChartEllipsisXAxisTick.tsx
import React from 'react';
import { XAxisProps } from 'recharts';

type Props = XAxisProps & {
  visibleTicksCount: number;
  payload: { value: number | string };
};

export const ChartEllipsisXAxisTick: React.FC<Props> = (props) => {
  const { x, y, width, visibleTicksCount } = props;
  const _width = width / visibleTicksCount;
  return (
    <g transform={`translate(${x},${y})`}>
      <foreignObject width={_width} height={30}></foreignObject>
    </g>
  );
};

座標を定義する

座標はx軸のみ求める必要があります。y軸は特に理由がなければ0で問題ありません。x軸は前述で算出した「width」を用いて計算することで算出できます。値の求め方ですが、widthを2で割るだけで算出できます。更にその値をマイナスにしてあげることで、要素の配置を中央にすることができます。(座標の計算が複雑化しているように見えますが、紐解いてみるとそこまで難しいことはしていません)

ChartEllipsisXAxisTick.tsx
import React from 'react';
import { XAxisProps } from 'recharts';

type Props = XAxisProps & {
  visibleTicksCount: number;
  payload: { value: number | string };
};

export const ChartEllipsisXAxisTick: React.FC<Props> = (props) => {
  const { x, y, width, visibleTicksCount } = props;
  const _width = width / visibleTicksCount;
  return (
    <g transform={`translate(${x},${y})`}>
      <foreignObject x={-(_width / 2)} y={0} width={_width} height={30}></foreignObject>
    </g>
  );
};

HTMLタグを用意する

ここまでくれば後はHTMLを埋め込むだけです。今回は特定の幅を超えたら三点リーダー(…)にするようにしたいため、以下のようなCSSを当ててあげます。(今回の例ではCSS modulesを使用しています)

style.module.scss
.text {
  display: block;
  width: 100%;
  height: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: center;
}
ChartEllipsisXAxisTick.tsx
import React from 'react';
import { XAxisProps } from 'recharts';
import styles from './style.module.scss';

type Props = XAxisProps & {
  visibleTicksCount: number;
  payload: { value: number | string };
};

export const ChartEllipsisXAxisTick: React.FC<Props> = (props) => {
  const { x, y, width, visibleTicksCount, payload } = props;
  const _width = width / visibleTicksCount;
  return (
    <g transform={`translate(${x},${y})`}>
      <foreignObject x={-(_width / 2)} y={0} width={_width} height={30}>
        <span className={styles.text}>{payload.value}</span>
      </foreignObject>
    </g>
  );
};

これでコンポーネント側は完成です。最後にこのコンポーネントをグラフに組み込んでいきます。

コンポーネントを組み込む

今回は公式のサンプルコードを少しカスタマイズしたものに組み込んでいきます。ポイントとしては、「XAxis」の「interval」を必ず0にする必要があります。これを0にしないと意図している挙動にならないため、注意しましょう。

ExampleBarChart.tsx
import React from 'react';
import { Bar, BarChart, CartesianGrid, Legend, Tooltip, XAxis, YAxis } from 'recharts';
import { ChartEllipsisXAxisTick } from '@/components/ChartEllipsisTick';

type Props = {};

const data = [
  {
    name: 'Page A',
    uv: 4000,
    pv: 2400,
    amt: 2400,
  },
  {
    name: 'Page B',
    uv: 3000,
    pv: 1398,
    amt: 2210,
  },
  {
    name: 'Page C',
    uv: 2000,
    pv: 9800,
    amt: 2290,
  },
  {
    name: 'Page D',
    uv: 2780,
    pv: 3908,
    amt: 2000,
  },
  {
    name: 'Page E',
    uv: 1890,
    pv: 4800,
    amt: 2181,
  },
  {
    name: 'Page F',
    uv: 2390,
    pv: 3800,
    amt: 2500,
  },
  {
    name: 'Page G',
    uv: 3490,
    pv: 4300,
    amt: 2100,
  },
];

export const ExampleBarChart: React.FC<Props> = () => (
  <BarChart width={400} height={300} data={data}>
    <CartesianGrid strokeDasharray="3 3" />
    <XAxis
      dataKey="name"
      interval={0}
      tick={(props) => <ChartEllipsisXAxisTick {...props} />}
    />
    <YAxis />
    <Tooltip />
    <Legend />
    <Bar dataKey="pv" fill="#8884d8" />
    <Bar dataKey="uv" fill="#82ca9d" />
  </BarChart>
);

ExampleBarChart
実際に表示させてみたコンポーネント

まとめ

今回は、Reactのチャートライブラリ「Recharts」でXAxis(横軸)のticksを三点リーダー(…)にする方法を紹介しました。Rechartsで実現するには少し工夫が必要でしたが、無事イメージ通りに実装することができました。これ以外の用途でも応用できると思いますので、記事を参考にぜひ色々お試しいただけると幸いです。

本記事を最後まで読んで頂き、ありがとうございました。いいねしていただけると記事執筆の励みになりますので、参考になったと思った方は是非よろしくお願いします!

おまけ

以前執筆した「【React/Vue.js】コンポーネント設計の(個人的)ベストプラクティス | Offers Tech Blog」ですが、ありがたいことに500いいねを突破しておりました!本当にありがとうございます。今後も多くの方々の参考になるような記事を執筆してまいりますので、気長にお待ちいただけると幸いです。

関連記事

https://zenn.dev/overflow_offers/articles/20220829-input-csv-parse-json
https://zenn.dev/overflow_offers/articles/20221024-css_modules_typescript
https://zenn.dev/offers/articles/20220418-what-is-bff-architecture

参考記事

https://gaurav5430.medium.com/exploring-recharts-using-foreignobject-to-render-custom-html-5c6b75d6207e

Offers Tech Blog

Discussion