index.js 7.3 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';
ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
9
import ResizeObserver from 'resize-observer-polyfill';
10 11 12
import styles from './index.less';

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

  componentDidMount() {
ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
21 22 23
    window.addEventListener(
      'resize',
      () => {
ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
24
        this.requestRef = requestAnimationFrame(() => this.resize());
ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
25 26 27
      },
      { passive: true }
    );
ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
28
    this.resizeObserver();
29 30
  }

jim's avatar
jim committed
31
  componentDidUpdate(preProps) {
ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
32 33
    const { data } = this.props;
    if (data !== preProps.data) {
34 35
      // because of charts data create when rendered
      // so there is a trick for get rendered time
jim's avatar
jim committed
36
      this.getLegendData();
nikogu's avatar
nikogu committed
37
    }
38
  }
ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
39

40
  componentWillUnmount() {
ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
41
    window.cancelAnimationFrame(this.requestRef);
42
    window.removeEventListener('resize', this.resize);
afc163's avatar
afc163 committed
43
    this.resize.cancel();
afc163's avatar
afc163 committed
44 45 46
    if (this.chartDom) {
      this.resizeObserverInstance.unobserve(this.chartDom);
    }
47 48
  }

jim's avatar
jim committed
49
  getG2Instance = chart => {
niko's avatar
niko committed
50
    this.chart = chart;
jim's avatar
jim committed
51 52 53 54
    requestAnimationFrame(() => {
      this.getLegendData();
      this.resize();
    });
niko's avatar
niko committed
55 56 57
  };

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

jim's avatar
jim committed
64
    const legendData = items.map(item => {
niko's avatar
niko committed
65 66 67 68 69 70 71 72 73 74 75 76
      /* 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
77
  handleRoot = n => {
78
    this.root = n;
niko's avatar
niko committed
79
  };
80

81 82 83 84
  handleLegendClick = (item, i) => {
    const newItem = item;
    newItem.checked = !newItem.checked;

afc163's avatar
afc163 committed
85
    const { legendData } = this.state;
86 87
    legendData[i] = newItem;

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

90
    if (this.chart) {
niko's avatar
niko committed
91
      this.chart.filter('x', val => filteredLegendData.indexOf(val) > -1);
92 93 94 95 96
    }

    this.setState({
      legendData,
    });
niko's avatar
niko committed
97
  };
98

ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
99
  resizeObserver() {
afc163's avatar
afc163 committed
100
    this.resizeObserverInstance = new ResizeObserver(entries => {
ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
101 102 103
      const { height } = entries[0].contentRect;
      this.setState(preState => {
        if (preState.height !== height) {
ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
104 105 106 107 108 109 110
          return {
            height,
          };
        }
        return null;
      });
    });
ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
111
    if (this.chartDom) {
afc163's avatar
afc163 committed
112
      this.resizeObserverInstance.observe(this.chartDom);
ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
113 114 115
    }
  }

ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
  // 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
139
  render() {
140
    const {
niko's avatar
niko committed
141 142 143 144 145 146 147
      valueFormat,
      subTitle,
      total,
      hasLegend = false,
      className,
      style,
      height,
148
      percent,
niko's avatar
niko committed
149
      color,
150 151
      inner = 0.75,
      animate = true,
niko's avatar
niko committed
152
      colors,
niko's avatar
niko committed
153
      lineWidth = 1,
nikogu's avatar
nikogu committed
154
    } = this.props;
155

ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
156
    const { legendData, height: stateHeight, legendBlock } = this.state;
niko's avatar
niko committed
157 158 159 160
    const pieClassName = classNames(styles.pie, className, {
      [styles.hasLegend]: !!hasLegend,
      [styles.legendBlock]: legendBlock,
    });
niko's avatar
niko committed
161

yoyo837's avatar
yoyo837 committed
162 163 164 165 166
    const {
      data: propsData,
      selected: propsSelected = true,
      tooltip: propsTooltip = true,
    } = this.props;
167

yoyo837's avatar
yoyo837 committed
168
    let data = propsData || [];
yoyo837's avatar
yoyo837 committed
169 170
    let selected = propsSelected;
    let tooltip = propsTooltip;
171

niko's avatar
niko committed
172
    const defaultColors = colors;
ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
173 174
    selected = selected || true;
    tooltip = tooltip || true;
175
    let formatColor;
niko's avatar
niko committed
176 177 178 179 180 181 182 183 184 185 186

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

187
    if (percent || percent === 0) {
188 189
      selected = false;
      tooltip = false;
jim's avatar
jim committed
190
      formatColor = value => {
191
        if (value === '占比') {
afc163's avatar
Fix pie  
afc163 committed
192
          return color || 'rgba(24, 144, 255, 0.85)';
193
        }
194
        return '#F0F2F5';
195 196 197 198 199 200 201 202 203 204 205 206 207 208
      };

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

niko's avatar
niko committed
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
    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
225
    });
226 227

    return (
228
      <div ref={this.handleRoot} className={pieClassName} style={style}>
ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
        <div
          ref={ref => {
            this.chartDom = ref;
          }}
        >
          <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>
niko's avatar
niko committed
255

ι™ˆεΈ…'s avatar
ι™ˆεΈ… committed
256 257 258 259 260 261 262 263 264 265 266 267
              {(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>
        </div>
niko's avatar
niko committed
268 269 270 271 272 273
        {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
274 275 276
                  style={{
                    backgroundColor: !item.checked ? '#aaa' : item.color,
                  }}
niko's avatar
niko committed
277 278 279
                />
                <span className={styles.legendTitle}>{item.x}</span>
                <Divider type="vertical" />
nikogu's avatar
nikogu committed
280
                <span className={styles.percent}>
281
                  {`${(Number.isNaN(item.percent) ? 0 : item.percent * 100).toFixed(2)}%`}
nikogu's avatar
nikogu committed
282
                </span>
niko's avatar
niko committed
283
                <span className={styles.value}>{valueFormat ? valueFormat(item.y) : item.y}</span>
niko's avatar
niko committed
284 285 286 287
              </li>
            ))}
          </ul>
        )}
288 289 290 291
      </div>
    );
  }
}
Rayron Victor's avatar
Rayron Victor committed
292 293

export default Pie;