index.js 6.0 KB

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