index.js 6.31 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() {
22
    window.addEventListener('resize', this.resize);
23 24
  }

jim's avatar
jim committed
25 26
  componentDidUpdate(preProps) {
    if (this.props.data !== preProps.data) {
27 28
      // because of charts data create when rendered
      // so there is a trick for get rendered time
jim's avatar
jim committed
29
      this.getLegendData();
nikogu's avatar
nikogu committed
30
    }
31
  }
32
  componentWillUnmount() {
33
    window.removeEventListener('resize', this.resize);
afc163's avatar
afc163 committed
34
    this.resize.cancel();
35 36
  }

jim's avatar
jim committed
37
  getG2Instance = chart => {
niko's avatar
niko committed
38
    this.chart = chart;
jim's avatar
jim committed
39 40 41 42
    requestAnimationFrame(() => {
      this.getLegendData();
      this.resize();
    });
niko's avatar
niko committed
43 44 45
  };

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

jim's avatar
jim committed
52
    const legendData = items.map(item => {
niko's avatar
niko committed
53 54 55 56 57 58 59 60 61 62 63 64 65
      /* 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
66 67 68
  @Bind()
  @Debounce(300)
  resize() {
jim's avatar
jim committed
69 70 71 72 73 74 75 76 77 78 79 80 81
    requestAnimationFrame(() => {
      const { hasLegend } = this.props;
      if (!hasLegend || !this.root) {
        window.removeEventListener('resize', this.resize);
        return;
      }
      if (this.root.parentNode.clientWidth <= 380) {
        if (!this.state.legendBlock) {
          this.setState({
            legendBlock: true,
          });
        }
      } else if (this.state.legendBlock) {
82
        this.setState({
jim's avatar
jim committed
83
          legendBlock: false,
84 85
        });
      }
jim's avatar
jim committed
86
    });
87 88
  }

jim's avatar
jim committed
89
  handleRoot = n => {
90
    this.root = n;
niko's avatar
niko committed
91
  };
92

93 94 95 96
  handleLegendClick = (item, i) => {
    const newItem = item;
    newItem.checked = !newItem.checked;

afc163's avatar
afc163 committed
97
    const { legendData } = this.state;
98 99
    legendData[i] = newItem;

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

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

    this.setState({
      legendData,
    });
niko's avatar
niko committed
109
  };
110

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

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

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

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

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

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

niko's avatar
niko committed
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
    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
190
    });
191 192

    return (
193
      <div ref={this.handleRoot} className={pieClassName} style={style}>
afc163's avatar
afc163 committed
194
        <ReactFitText maxFontSize={25}>
偏右's avatar
偏右 committed
195
          <div className={styles.chart}>
niko's avatar
niko committed
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
            <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 */}
niko's avatar
niko committed
221 222 223
                {total && (
                  <div className="pie-stat">{typeof total === 'function' ? total() : total}</div>
                )}
niko's avatar
niko committed
224 225
              </div>
            )}
226
          </div>
偏右's avatar
偏右 committed
227 228
        </ReactFitText>

niko's avatar
niko committed
229 230 231 232 233 234
        {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
235 236 237
                  style={{
                    backgroundColor: !item.checked ? '#aaa' : item.color,
                  }}
niko's avatar
niko committed
238 239 240
                />
                <span className={styles.legendTitle}>{item.x}</span>
                <Divider type="vertical" />
nikogu's avatar
nikogu committed
241 242 243
                <span className={styles.percent}>
                  {`${(isNaN(item.percent) ? 0 : item.percent * 100).toFixed(2)}%`}
                </span>
niko's avatar
niko committed
244
                <span className={styles.value}>{valueFormat ? valueFormat(item.y) : item.y}</span>
niko's avatar
niko committed
245 246 247 248
              </li>
            ))}
          </ul>
        )}
249 250 251 252
      </div>
    );
  }
}