index.js 6.36 KB
Newer Older
1
import React, { Component } from 'react';
niko's avatar
niko committed
2 3
import { Chart, Tooltip, Geom, Coord } from 'bizcharts';
import { DataView } from '@antv/data-set';
afc163's avatar
afc163 committed
4
import { Divider } from 'antd';
偏右's avatar
偏右 committed
5 6
import classNames from 'classnames';
import ReactFitText from 'react-fittext';
7
import Debounce from 'lodash-decorators/debounce';
afc163's avatar
afc163 committed
8
import Bind from 'lodash-decorators/bind';
niko's avatar
niko committed
9 10
import autoHeight from '../autoHeight';

11 12 13
import styles from './index.less';

/* eslint react/no-danger:0 */
niko's avatar
niko committed
14 15
@autoHeight()
export default class Pie extends Component {
16 17
  state = {
    legendData: [],
niko's avatar
niko committed
18
    legendBlock: false,
偏右's avatar
偏右 committed
19
  };
20 21

  componentDidMount() {
niko's avatar
niko committed
22
    this.getLengendData();
23 24
    this.resize();
    window.addEventListener('resize', this.resize);
25 26 27
  }

  componentWillReceiveProps(nextProps) {
niko's avatar
niko committed
28
    if (this.props.data !== nextProps.data) {
29 30 31 32 33 34 35 36 37 38
      // because of charts data create when rendered
      // so there is a trick for get rendered time
      this.setState(
        {
          legendData: [...this.state.legendData],
        },
        () => {
          this.getLengendData();
        }
      );
nikogu's avatar
nikogu committed
39
    }
40 41
  }

42
  componentWillUnmount() {
43
    window.removeEventListener('resize', this.resize);
afc163's avatar
afc163 committed
44
    this.resize.cancel();
45 46
  }

niko's avatar
niko committed
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
  getG2Instance = (chart) => {
    this.chart = chart;
  };

  // for custom lengend view
  getLengendData = () => {
    if (!this.chart) return;
    const geom = this.chart.getAllGeoms()[0]; // θŽ·ε–ζ‰€ζœ‰ηš„ε›Ύε½’
    const items = geom.get('dataArray') || []; // θŽ·ε–ε›Ύε½’ε―ΉεΊ”ηš„

    const legendData = items.map((item) => {
      /* eslint no-underscore-dangle:0 */
      const origin = item[0]._origin;
      origin.color = item[0].color;
      origin.checked = true;
      return origin;
    });

    this.setState({
      legendData,
    });
  };

  // for window resize auto responsive legend
afc163's avatar
afc163 committed
71 72 73
  @Bind()
  @Debounce(300)
  resize() {
74
    const { hasLegend } = this.props;
nikogu's avatar
nikogu committed
75 76
    if (!hasLegend || !this.root) {
      window.removeEventListener('resize', this.resize);
77 78
      return;
    }
79
    if (this.root.parentNode.clientWidth <= 380) {
80 81 82 83 84 85 86 87 88 89 90 91 92 93
      if (!this.state.legendBlock) {
        this.setState({
          legendBlock: true,
        });
      }
    } else if (this.state.legendBlock) {
      this.setState({
        legendBlock: false,
      });
    }
  }

  handleRoot = (n) => {
    this.root = n;
niko's avatar
niko committed
94
  };
95

96 97 98 99
  handleLegendClick = (item, i) => {
    const newItem = item;
    newItem.checked = !newItem.checked;

afc163's avatar
afc163 committed
100
    const { legendData } = this.state;
101 102
    legendData[i] = newItem;

niko's avatar
niko committed
103 104
    const filteredLegendData = legendData.filter(l => l.checked).map(l => l.x);

105
    if (this.chart) {
niko's avatar
niko committed
106
      this.chart.filter('x', val => filteredLegendData.indexOf(val) > -1);
107 108 109 110 111
    }

    this.setState({
      legendData,
    });
niko's avatar
niko committed
112
  };
113

niko's avatar
niko committed
114
  render() {
115
    const {
niko's avatar
niko committed
116 117 118 119 120 121 122 123 124 125
      valueFormat,
      subTitle,
      total,
      hasLegend = false,
      className,
      style,
      height,
      forceFit = true,
      percent = 0,
      color,
126 127
      inner = 0.75,
      animate = true,
niko's avatar
niko committed
128
      colors,
niko's avatar
niko committed
129
      lineWidth = 1,
nikogu's avatar
nikogu committed
130
    } = this.props;
131

niko's avatar
niko committed
132 133 134 135 136
    const { legendData, legendBlock } = this.state;
    const pieClassName = classNames(styles.pie, className, {
      [styles.hasLegend]: !!hasLegend,
      [styles.legendBlock]: legendBlock,
    });
niko's avatar
niko committed
137

niko's avatar
niko committed
138 139
    const defaultColors = colors;
    let data = this.props.data || [];
140
    let selected = this.props.selected || true;
niko's avatar
niko committed
141
    let tooltip = this.props.tooltip || true;
142
    let formatColor;
niko's avatar
niko committed
143 144 145 146 147 148 149 150 151 152 153

    const scale = {
      x: {
        type: 'cat',
        range: [0, 1],
      },
      y: {
        min: 0,
      },
    };

154 155 156 157 158
    if (percent) {
      selected = false;
      tooltip = false;
      formatColor = (value) => {
        if (value === '占比') {
afc163's avatar
Fix pie  
afc163 committed
159
          return color || 'rgba(24, 144, 255, 0.85)';
160
        } else {
afc163's avatar
Fix pie  
afc163 committed
161
          return '#F0F2F5';
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
        }
      };

      data = [
        {
          x: '占比',
          y: parseFloat(percent),
        },
        {
          x: '反比',
          y: 100 - parseFloat(percent),
        },
      ];
    }

niko's avatar
niko committed
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
    const tooltipFormat = [
      'x*percent',
      (x, p) => ({
        name: x,
        value: `${(p * 100).toFixed(2)}%`,
      }),
    ];

    const padding = [12, 0, 12, 0];

    const dv = new DataView();
    dv.source(data).transform({
      type: 'percent',
      field: 'y',
      dimension: 'x',
      as: 'percent',
偏右's avatar
偏右 committed
193
    });
194 195

    return (
196
      <div ref={this.handleRoot} className={pieClassName} style={style}>
afc163's avatar
afc163 committed
197
        <ReactFitText maxFontSize={25}>
偏右's avatar
偏右 committed
198
          <div className={styles.chart}>
niko's avatar
niko committed
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
            <Chart
              scale={scale}
              height={height}
              forceFit={forceFit}
              data={dv}
              padding={padding}
              animate={animate}
              onGetG2Instance={this.getG2Instance}
            >
              {!!tooltip && <Tooltip showTitle={false} />}
              <Coord type="theta" innerRadius={inner} />
              <Geom
                style={{ lineWidth, stroke: '#fff' }}
                tooltip={tooltip && tooltipFormat}
                type="intervalStack"
                position="percent"
                color={['x', percent ? formatColor : defaultColors]}
                selected={selected}
              />
            </Chart>

            {(subTitle || total) && (
              <div className={styles.total}>
                {subTitle && <h4 className="pie-sub-title">{subTitle}</h4>}
                {/* eslint-disable-next-line */}
                {total && <div className="pie-stat" dangerouslySetInnerHTML={{ __html: total }} />}
              </div>
            )}
227
          </div>
偏右's avatar
偏右 committed
228 229
        </ReactFitText>

niko's avatar
niko committed
230 231 232 233 234 235 236 237 238 239
        {hasLegend && (
          <ul className={styles.legend}>
            {legendData.map((item, i) => (
              <li key={item.x} onClick={() => this.handleLegendClick(item, i)}>
                <span
                  className={styles.dot}
                  style={{ backgroundColor: !item.checked ? '#aaa' : item.color }}
                />
                <span className={styles.legendTitle}>{item.x}</span>
                <Divider type="vertical" />
nikogu's avatar
nikogu committed
240 241 242
                <span className={styles.percent}>
                  {`${(isNaN(item.percent) ? 0 : item.percent * 100).toFixed(2)}%`}
                </span>
niko's avatar
niko committed
243 244 245 246 247 248 249 250 251 252
                <span
                  className={styles.value}
                  dangerouslySetInnerHTML={{
                    __html: valueFormat ? valueFormat(item.y) : item.y,
                  }}
                />
              </li>
            ))}
          </ul>
        )}
253 254 255 256
      </div>
    );
  }
}