index.js 6.6 KB

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