index.js 5.8 KB

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