index.js 6.4 KB

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