index.js 6.19 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';
9 10 11
import styles from './index.less';

/* eslint react/no-danger:0 */
afc163's avatar
afc163 committed
12
class Pie extends Component {
13
  state = {
ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
14
    height: 0,
15
    legendData: [],
niko's avatar
niko committed
16
    legendBlock: false,
偏右's avatar
偏右 committed
17
  };
18

jim's avatar
jim committed
19
  componentDidUpdate(preProps) {
ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
20 21
    const { data } = this.props;
    if (data !== preProps.data) {
22 23
      // because of charts data create when rendered
      // so there is a trick for get rendered time
jim's avatar
jim committed
24
      this.getLegendData();
nikogu's avatar
nikogu committed
25
    }
26
  }
ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
27

jim's avatar
jim committed
28
  getG2Instance = chart => {
niko's avatar
niko committed
29
    this.chart = chart;
jim's avatar
jim committed
30 31 32
    requestAnimationFrame(() => {
      this.getLegendData();
    });
niko's avatar
niko committed
33 34 35
  };

  // for custom lengend view
Rupeshiya's avatar
Rupeshiya committed
36
  getLegendData = () => {
niko's avatar
niko committed
37 38
    if (!this.chart) return;
    const geom = this.chart.getAllGeoms()[0]; // θŽ·ε–ζ‰€ζœ‰ηš„ε›Ύε½’
jim's avatar
jim committed
39
    if (!geom) return;
niko's avatar
niko committed
40 41
    const items = geom.get('dataArray') || []; // θŽ·ε–ε›Ύε½’ε―ΉεΊ”ηš„

jim's avatar
jim committed
42
    const legendData = items.map(item => {
niko's avatar
niko committed
43 44 45 46 47 48 49 50 51 52 53 54
      /* eslint no-underscore-dangle:0 */
      const origin = item[0]._origin;
      origin.color = item[0].color;
      origin.checked = true;
      return origin;
    });

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

jim's avatar
jim committed
55
  handleRoot = n => {
56
    this.root = n;
niko's avatar
niko committed
57
  };
58

59 60 61 62
  handleLegendClick = (item, i) => {
    const newItem = item;
    newItem.checked = !newItem.checked;

afc163's avatar
afc163 committed
63
    const { legendData } = this.state;
64 65
    legendData[i] = newItem;

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

68
    if (this.chart) {
niko's avatar
niko committed
69
      this.chart.filter('x', val => filteredLegendData.indexOf(val) > -1);
70 71 72 73 74
    }

    this.setState({
      legendData,
    });
niko's avatar
niko committed
75
  };
76

ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
  // for window resize auto responsive legend
  @Bind()
  @Debounce(300)
  resize() {
    const { hasLegend } = this.props;
    const { legendBlock } = this.state;
    if (!hasLegend || !this.root) {
      window.removeEventListener('resize', this.resize);
      return;
    }
    if (this.root.parentNode.clientWidth <= 380) {
      if (!legendBlock) {
        this.setState({
          legendBlock: true,
        });
      }
    } else if (legendBlock) {
      this.setState({
        legendBlock: false,
      });
    }
  }

niko's avatar
niko committed
100
  render() {
101
    const {
niko's avatar
niko committed
102 103 104 105 106 107 108
      valueFormat,
      subTitle,
      total,
      hasLegend = false,
      className,
      style,
      height,
109
      percent,
niko's avatar
niko committed
110
      color,
111 112
      inner = 0.75,
      animate = true,
niko's avatar
niko committed
113
      colors,
niko's avatar
niko committed
114
      lineWidth = 1,
nikogu's avatar
nikogu committed
115
    } = this.props;
116

ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
117
    const { legendData, height: stateHeight, legendBlock } = this.state;
niko's avatar
niko committed
118 119 120 121
    const pieClassName = classNames(styles.pie, className, {
      [styles.hasLegend]: !!hasLegend,
      [styles.legendBlock]: legendBlock,
    });
niko's avatar
niko committed
122

yoyo837's avatar
yoyo837 committed
123 124 125 126 127
    const {
      data: propsData,
      selected: propsSelected = true,
      tooltip: propsTooltip = true,
    } = this.props;
128

yoyo837's avatar
yoyo837 committed
129
    let data = propsData || [];
yoyo837's avatar
yoyo837 committed
130 131
    let selected = propsSelected;
    let tooltip = propsTooltip;
132

niko's avatar
niko committed
133
    const defaultColors = colors;
ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
134 135
    selected = selected || true;
    tooltip = tooltip || true;
136
    let formatColor;
niko's avatar
niko committed
137 138 139 140 141 142 143 144 145 146 147

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

148
    if (percent || percent === 0) {
149 150
      selected = false;
      tooltip = false;
jim's avatar
jim committed
151
      formatColor = value => {
152
        if (value === '占比') {
afc163's avatar
Fix pie  
afc163 committed
153
          return color || 'rgba(24, 144, 255, 0.85)';
154
        }
155
        return '#F0F2F5';
156 157 158 159 160 161 162 163 164 165 166 167 168 169
      };

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

niko's avatar
niko committed
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
    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
186
    });
187 188

    return (
189
      <div ref={this.handleRoot} className={pieClassName} style={style}>
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
        <ReactFitText maxFontSize={25}>
          <div className={styles.chart}>
            <Chart
              scale={scale}
              height={height || stateHeight}
              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 || percent === 0 ? 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">{typeof total === 'function' ? total() : total}</div>
                )}
              </div>
            )}
          </div>
        </ReactFitText>
niko's avatar
niko committed
223 224 225 226 227 228
        {hasLegend && (
          <ul className={styles.legend}>
            {legendData.map((item, i) => (
              <li key={item.x} onClick={() => this.handleLegendClick(item, i)}>
                <span
                  className={styles.dot}
jim's avatar
jim committed
229 230 231
                  style={{
                    backgroundColor: !item.checked ? '#aaa' : item.color,
                  }}
niko's avatar
niko committed
232 233 234
                />
                <span className={styles.legendTitle}>{item.x}</span>
                <Divider type="vertical" />
nikogu's avatar
nikogu committed
235
                <span className={styles.percent}>
236
                  {`${(Number.isNaN(item.percent) ? 0 : item.percent * 100).toFixed(2)}%`}
nikogu's avatar
nikogu committed
237
                </span>
niko's avatar
niko committed
238
                <span className={styles.value}>{valueFormat ? valueFormat(item.y) : item.y}</span>
niko's avatar
niko committed
239 240 241 242
              </li>
            ))}
          </ul>
        )}
243 244 245 246
      </div>
    );
  }
}
Rayron Victor's avatar
Rayron Victor committed
247 248

export default Pie;