huridocs/uwazi

View on GitHub
app/react/Charts/components/Pie.js

Summary

Maintainability
A
0 mins
Test Coverage
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { ResponsiveContainer, PieChart, Pie, Legend, Cell, Sector } from 'recharts';

import Immutable from 'immutable';

import colorScheme from '../utils/colorScheme';

function ellipsisString(string, maxLength) {
  if (string.length <= maxLength) {
    return string;
  }

  return `${string.substring(0, maxLength - 3)}...`;
}

export class RechartsPie extends Component {
  static getDerivedStateFromProps(props) {
    return RechartsPie.mountData(props);
  }

  static mountData(props) {
    let fullData = Immutable.fromJS([]);
    if (props.data) {
      fullData = Immutable.fromJS(
        props.data.map(item => ({ name: item.label, value: item.results, enabled: true }))
      );
    }
    return { activeIndex: 0, fullData };
  }

  renderActiveShape(props) {
    const RADIAN = Math.PI / 180;
    const {
      cx,
      cy,
      midAngle,
      innerRadius,
      outerRadius,
      startAngle,
      endAngle,
      fill,
      payload,
      percent,
      value,
    } = props;
    const sin = Math.sin(-RADIAN * midAngle);
    const cos = Math.cos(-RADIAN * midAngle);
    const sx = cx + (outerRadius + 10) * cos;
    const sy = cy + (outerRadius + 10) * sin;
    const mx = cx + (outerRadius + 22) * cos;
    const my = cy + (outerRadius + 22) * sin;
    const ex = mx + (cos >= 0 ? 1 : -1) * 22;
    const ey = my;
    const textAnchor = cos >= 0 ? 'start' : 'end';

    return (
      <g>
        <text x={cx} y={cy} dy={8} textAnchor="middle" fill={fill}>
          {ellipsisString(payload.name, 14)}
        </text>
        <Sector
          cx={cx}
          cy={cy}
          innerRadius={innerRadius}
          outerRadius={outerRadius}
          startAngle={startAngle}
          endAngle={endAngle}
          fill={fill}
          stroke="#fff"
        />
        <Sector
          cx={cx}
          cy={cy}
          startAngle={startAngle}
          endAngle={endAngle}
          innerRadius={outerRadius + 6}
          outerRadius={outerRadius + 10}
          fill={fill}
        />
        <path d={`M${sx},${sy}L${mx},${my}L${ex},${ey}`} stroke={fill} fill="none" />
        <circle cx={ex} cy={ey} r={2} fill={fill} stroke="none" />
        <text x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey} textAnchor={textAnchor} fill="#333">
          {`${payload.name}: ${value}`}
        </text>
        <text x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey} dy={18} textAnchor={textAnchor} fill="#999">
          {`(${(percent * 100).toFixed(2)}%)`}
        </text>
      </g>
    );
  }

  getFilteredIndex(data, index) {
    const filteredIndexMap = {};
    let enabledIndices = -1;
    data.forEach((item, iterator) => {
      if (item.get('enabled')) {
        enabledIndices += 1;
        filteredIndexMap[iterator] = enabledIndices;
        return;
      }
      filteredIndexMap[iterator] = null;
    });

    return filteredIndexMap[index];
  }

  onIndexEnter(_data, index) {
    this.setState({ activeIndex: index });
  }

  onFullIndexEnter(_data, index) {
    this.onIndexEnter(null, this.getFilteredIndex(this.state.fullData, index));
  }

  onIndexClick(_data, index) {
    const oldData = this.state.fullData;
    const enabled = !oldData.getIn([index, 'enabled']);
    let activeIndex = null;
    const fullData = oldData.setIn([index, 'enabled'], enabled);
    if (enabled) {
      activeIndex = this.getFilteredIndex(fullData, index);
    }

    this.setState({ activeIndex, fullData });
  }

  render() {
    const filteredColors = [];

    const fullData = this.state.fullData.toJS();

    const filteredData = fullData.reduce((results, item, index) => {
      if (item.enabled) {
        results.push(item);
        filteredColors.push(colorScheme[index % colorScheme.length]);
      }
      return results;
    }, []);

    const legendFormatter = (index, item) => (
      <span style={{ color: fullData[index].enabled ? '#333' : '#999' }}>{item.name}</span>
    );

    return (
      <ResponsiveContainer height={320}>
        <PieChart>
          <Pie
            data={filteredData}
            dataKey="value"
            cx="50%"
            cy="50%"
            innerRadius={50}
            outerRadius={80}
            activeIndex={this.state.activeIndex}
            activeShape={this.renderActiveShape}
            animationBegin={200}
            animationDuration={500}
            onMouseEnter={this.onIndexEnter.bind(this)}
            onClick={this.onIndexEnter.bind(this)}
            fill="#8884d8"
          >
            {filteredData.map((_entry, index) => (
              <Cell key={index} fill={filteredColors[index]} opacity={0.8} />
            ))}
          </Pie>
          <Legend
            onMouseEnter={this.onFullIndexEnter.bind(this)}
            onClick={this.onIndexClick.bind(this)}
            payload={fullData.map((item, index) => ({
              value: item.name,
              type: 'rect',
              color: fullData[index].enabled ? colorScheme[index % colorScheme.length] : '#aaa',
              formatter: legendFormatter(index, item),
            }))}
          />
        </PieChart>
      </ResponsiveContainer>
    );
  }
}

RechartsPie.propTypes = {
  data: PropTypes.array,
};

export default connect()(RechartsPie);