rechartsのラベルをカスタマイズする方法

reactでChartを表示する際にはrechartsを使用するのが王道のようです。(chartjsもありますが、v2.0に対応しているライブラリがまだメジャーになってないなど色々あるようです。)

Pieチャートのラベルをカスタマイズしてみます。

http://recharts.org/#/en-US/examples/PieChartWithCustomizedLabel

チャートを描画する部分は以下のようになりますが、labelに独自の関数(以下の例だと「renderCustomizedLabel」)を定義することでカスタマイズができるようになります。

<PieChart width={800} height={500} onMouseEnter={this.onPieEnter}>
        <Pie
          data={data}
          cx={400}
          cy={200}
          innerRadius={40}
          outerRadius={180}
          fill="#8884d8"
          paddingAngle={0}
          labelLine={false}
          label={renderCustomizedLabel}
        >
          {data.map((entry, index) => (
            <Cell key={entry.name} fill={ChartColors[index % ChartColors.length]} />
          ))}
        </Pie>
      </PieChart>

ラベル部分はcanvasタグに従った書き方でいろいろとかけるようです。

グラフの内側に描画するラベル

const inner = this.calcLabelPosition(cx, cy, midAngle, innerRadius, outerRadius, 0.5);

グラフの外側に描画するラベル

const outer = this.calcLabelPosition(cx, cy, midAngle, innerRadius, outerRadius, 1.1);

のようにしてタグで囲むことで複数の描画ができました。また、style属性によってフォントサイズもそれぞれに指定することが可能です。

    const renderCustomizedLabel = ({ cx, cy, midAngle, innerRadius, outerRadius, percent, index }) => {
      const outer = this.calcLabelPosition(cx, cy, midAngle, innerRadius, outerRadius, 1.1);
      const inner = this.calcLabelPosition(cx, cy, midAngle, innerRadius, outerRadius, 0.5);
      const item = this.props.data[index];

      return (
        <g>
          <text
            x={outer.x}
            y={outer.y}
            fill={ChartColors[index % ChartColors.length]}
            textAnchor={outer.x > cx ? 'start' : 'end'}
            dominantBaseline="central"
          >
            {item.name}
          </text>
          <text
            x={inner.x}
            y={inner.y}
            fill="white"
            textAnchor="middle"
            dominantBaseline="central"
            style={{ fontWeight: 'bold', fontSize: `${FontSize[index % FontSize.length]}` }}
          >
            {`${(percent * 100).toFixed(0)}%`}
          </text>
        </g>
      );
    };

最終形は以下のようになります。

import React, { Component, PropTypes } from 'react';
// Redux
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
// Material UI
import * as colors from 'material-ui/styles/colors';
// chart
import { ResponsiveContainer, PieChart, Pie, Legend, Tooltip, Cell, Sector } from 'recharts';
// Other

const ChartColors = [colors.lightBlue500, colors.lime500, colors.orange500, colors.red500];
const FontSize = ['100%', '150%', '400%', '600%'];

const RADIAN = Math.PI / 180;

class BaseChart extends Component {
  calcLabelPosition(cx, cy, midAngle, innerRadius, outerRadius, param) {
    const diff = outerRadius - innerRadius;
    const diffPercent = diff * param;
    const radius = innerRadius + diffPercent;
    const cos = Math.cos((-midAngle) * RADIAN);
    const ax = radius * cos;
    const sin = Math.sin((-midAngle) * RADIAN);
    const ay = radius * sin;
    const x = cx + ax;
    const y = cy + ay;
    return { x, y };
  }
  render() {
    const renderCustomizedLabel = ({ cx, cy, midAngle, innerRadius, outerRadius, percent, index }) => {
      const outer = this.calcLabelPosition(cx, cy, midAngle, innerRadius, outerRadius, 1.1);
      const inner = this.calcLabelPosition(cx, cy, midAngle, innerRadius, outerRadius, 0.5);
      const item = this.props.data[index];

      return (
        <g>
          <text
            x={outer.x}
            y={outer.y}
            fill={ChartColors[index % ChartColors.length]}
            textAnchor={outer.x > cx ? 'start' : 'end'}
            dominantBaseline="central"
          >
            {item.name}
          </text>
          <text
            x={inner.x}
            y={inner.y}
            fill="white"
            textAnchor="middle"
            dominantBaseline="central"
            style={{ fontWeight: 'bold', fontSize: `${FontSize[index % FontSize.length]}` }}
          >
            {`${(percent * 100).toFixed(0)}%`}
          </text>
        </g>
      );
    };
    const { data } = this.props;

    return (
      <PieChart width={800} height={500} onMouseEnter={this.onPieEnter}>
        <Pie
          data={data}
          cx={400}
          cy={200}
          innerRadius={40}
          outerRadius={180}
          fill="#8884d8"
          paddingAngle={0}
          labelLine={false}
          label={renderCustomizedLabel}
        >
          {data.map((entry, index) => (
            <Cell key={entry.name} fill={ChartColors[index % ChartColors.length]} />
          ))}
        </Pie>
      </PieChart>
    );
  }
}

BaseChart.propTypes = {
  data: React.PropTypes.shape().isRequired,
};

export default BaseChart;