index.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. import React, { Component } from 'react';
  2. import { Chart, Tooltip, Geom, Coord } from 'bizcharts';
  3. import { DataView } from '@antv/data-set';
  4. import { Divider } from 'antd';
  5. import classNames from 'classnames';
  6. import ReactFitText from 'react-fittext';
  7. import Debounce from 'lodash-decorators/debounce';
  8. import Bind from 'lodash-decorators/bind';
  9. import autoHeight from '../autoHeight';
  10. import styles from './index.less';
  11. /* eslint react/no-danger:0 */
  12. @autoHeight()
  13. export default class Pie extends Component {
  14. state = {
  15. legendData: [],
  16. legendBlock: false,
  17. };
  18. componentDidMount() {
  19. this.getLegendData();
  20. this.resize();
  21. window.addEventListener('resize', this.resize);
  22. }
  23. componentWillReceiveProps(nextProps) {
  24. const { data } = this.props;
  25. if (data !== nextProps.data) {
  26. // because of charts data create when rendered
  27. // so there is a trick for get rendered time
  28. const { legendData } = this.state;
  29. this.setState(
  30. {
  31. legendData: [...legendData],
  32. },
  33. () => {
  34. this.getLegendData();
  35. }
  36. );
  37. }
  38. }
  39. componentWillUnmount() {
  40. window.removeEventListener('resize', this.resize);
  41. this.resize.cancel();
  42. }
  43. getG2Instance = chart => {
  44. this.chart = chart;
  45. };
  46. // for custom lengend view
  47. getLegendData = () => {
  48. if (!this.chart) return;
  49. const geom = this.chart.getAllGeoms()[0]; // 获取所有的图形
  50. const items = geom.get('dataArray') || []; // 获取图形对应的
  51. const legendData = items.map(item => {
  52. /* eslint no-underscore-dangle:0 */
  53. const origin = item[0]._origin;
  54. origin.color = item[0].color;
  55. origin.checked = true;
  56. return origin;
  57. });
  58. this.setState({
  59. legendData,
  60. });
  61. };
  62. handleRoot = n => {
  63. this.root = n;
  64. };
  65. handleLegendClick = (item, i) => {
  66. const newItem = item;
  67. newItem.checked = !newItem.checked;
  68. const { legendData } = this.state;
  69. legendData[i] = newItem;
  70. const filteredLegendData = legendData.filter(l => l.checked).map(l => l.x);
  71. if (this.chart) {
  72. this.chart.filter('x', val => filteredLegendData.indexOf(val) > -1);
  73. }
  74. this.setState({
  75. legendData,
  76. });
  77. };
  78. // for window resize auto responsive legend
  79. @Bind()
  80. @Debounce(300)
  81. resize() {
  82. const { hasLegend } = this.props;
  83. if (!hasLegend || !this.root) {
  84. window.removeEventListener('resize', this.resize);
  85. return;
  86. }
  87. const { legendBlock } = this.state;
  88. if (this.root.parentNode.clientWidth <= 380) {
  89. if (!legendBlock) {
  90. this.setState({
  91. legendBlock: true,
  92. });
  93. }
  94. } else if (legendBlock) {
  95. this.setState({
  96. legendBlock: false,
  97. });
  98. }
  99. }
  100. render() {
  101. const {
  102. valueFormat,
  103. subTitle,
  104. total,
  105. hasLegend = false,
  106. className,
  107. style,
  108. height,
  109. forceFit = true,
  110. percent = 0,
  111. color,
  112. inner = 0.75,
  113. animate = true,
  114. colors,
  115. lineWidth = 1,
  116. } = this.props;
  117. const { legendData, legendBlock } = this.state;
  118. const pieClassName = classNames(styles.pie, className, {
  119. [styles.hasLegend]: !!hasLegend,
  120. [styles.legendBlock]: legendBlock,
  121. });
  122. const { data: d, selected: s, tooltip: t } = this.props;
  123. let data = d || [];
  124. let selected = s || true;
  125. let tooltip = t || true;
  126. const defaultColors = colors;
  127. // let data = this.props.data || [];
  128. // let selected = this.props.selected || true;
  129. // let tooltip = this.props.tooltip || true;
  130. let formatColor;
  131. const scale = {
  132. x: {
  133. type: 'cat',
  134. range: [0, 1],
  135. },
  136. y: {
  137. min: 0,
  138. },
  139. };
  140. if (percent) {
  141. selected = false;
  142. tooltip = false;
  143. formatColor = value => {
  144. if (value === '占比') {
  145. return color || 'rgba(24, 144, 255, 0.85)';
  146. } else {
  147. return '#F0F2F5';
  148. }
  149. };
  150. data = [
  151. {
  152. x: '占比',
  153. y: parseFloat(percent),
  154. },
  155. {
  156. x: '反比',
  157. y: 100 - parseFloat(percent),
  158. },
  159. ];
  160. }
  161. const tooltipFormat = [
  162. 'x*percent',
  163. (x, p) => ({
  164. name: x,
  165. value: `${(p * 100).toFixed(2)}%`,
  166. }),
  167. ];
  168. const padding = [12, 0, 12, 0];
  169. const dv = new DataView();
  170. dv.source(data).transform({
  171. type: 'percent',
  172. field: 'y',
  173. dimension: 'x',
  174. as: 'percent',
  175. });
  176. return (
  177. <div ref={this.handleRoot} className={pieClassName} style={style}>
  178. <ReactFitText maxFontSize={25}>
  179. <div className={styles.chart}>
  180. <Chart
  181. scale={scale}
  182. height={height}
  183. forceFit={forceFit}
  184. data={dv}
  185. padding={padding}
  186. animate={animate}
  187. onGetG2Instance={this.getG2Instance}
  188. >
  189. {!!tooltip && <Tooltip showTitle={false} />}
  190. <Coord type="theta" innerRadius={inner} />
  191. <Geom
  192. style={{ lineWidth, stroke: '#fff' }}
  193. tooltip={tooltip && tooltipFormat}
  194. type="intervalStack"
  195. position="percent"
  196. color={['x', percent ? formatColor : defaultColors]}
  197. selected={selected}
  198. />
  199. </Chart>
  200. {(subTitle || total) && (
  201. <div className={styles.total}>
  202. {subTitle && <h4 className="pie-sub-title">{subTitle}</h4>}
  203. {/* eslint-disable-next-line */}
  204. {total && (
  205. <div className="pie-stat">{typeof total === 'function' ? total() : total}</div>
  206. )}
  207. </div>
  208. )}
  209. </div>
  210. </ReactFitText>
  211. {hasLegend && (
  212. <ul className={styles.legend}>
  213. {legendData.map((item, i) => (
  214. <li key={item.x} onClick={() => this.handleLegendClick(item, i)}>
  215. <span
  216. className={styles.dot}
  217. style={{
  218. backgroundColor: !item.checked ? '#aaa' : item.color,
  219. }}
  220. />
  221. <span className={styles.legendTitle}>{item.x}</span>
  222. <Divider type="vertical" />
  223. <span className={styles.percent}>
  224. {`${(isNaN(item.percent) ? 0 : item.percent * 100).toFixed(2)}%`}
  225. </span>
  226. <span className={styles.value}>{valueFormat ? valueFormat(item.y) : item.y}</span>
  227. </li>
  228. ))}
  229. </ul>
  230. )}
  231. </div>
  232. );
  233. }
  234. }