index.js 6.6 KB

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