antv 水球图 http://antv-2018.alipay.com/zh-cn/g2/3.x/demo/other/liquid-fill-gauge.html

    antd-pro 首页水球图
    https://github.com/ant-design/ant-design-pro/blob/all-blocks/src/pages/dashboard/monitor/index.tsx

    react水球图 - 图1

    1. import React, { Component } from "react";
    2. import "./styles.css";
    3. /* eslint no-return-assign: 0 */
    4. /* eslint no-mixed-operators: 0 */
    5. // riddle: https://riddle.alibaba-inc.com/riddles/2d9a4b90
    6. class WaterWave extends Component {
    7. state = {
    8. radio: 1
    9. };
    10. timer = 0;
    11. root = null;
    12. node = null;
    13. componentDidMount() {
    14. this.renderChart();
    15. this.resize();
    16. window.addEventListener(
    17. "resize",
    18. () => {
    19. requestAnimationFrame(() => this.resize());
    20. },
    21. { passive: true }
    22. );
    23. }
    24. componentDidUpdate(props) {
    25. const { percent } = this.props;
    26. if (props.percent !== percent) {
    27. // 不加这个会造成绘制缓慢
    28. this.renderChart("update");
    29. }
    30. }
    31. componentWillUnmount() {
    32. cancelAnimationFrame(this.timer);
    33. if (this.node) {
    34. this.node.innerHTML = "";
    35. }
    36. window.removeEventListener("resize", this.resize);
    37. }
    38. resize = () => {
    39. if (this.root) {
    40. const { height = 1 } = this.props;
    41. const { offsetWidth } = this.root.parentNode;
    42. this.setState({
    43. radio: offsetWidth < height ? offsetWidth / height : 1
    44. });
    45. }
    46. };
    47. renderChart(type) {
    48. const { percent, color = "#1890FF" } = this.props;
    49. const data = percent / 100;
    50. const self = this;
    51. cancelAnimationFrame(this.timer);
    52. if (!this.node || (data !== 0 && !data)) {
    53. return;
    54. }
    55. const canvas = this.node;
    56. const ctx = canvas.getContext("2d");
    57. if (!ctx) {
    58. return;
    59. }
    60. const canvasWidth = canvas.width;
    61. const canvasHeight = canvas.height;
    62. const radius = canvasWidth / 2;
    63. const lineWidth = 2;
    64. const cR = radius - lineWidth;
    65. ctx.beginPath();
    66. ctx.lineWidth = lineWidth * 2;
    67. const axisLength = canvasWidth - lineWidth;
    68. const unit = axisLength / 8;
    69. const range = 0.2; // 振幅
    70. let currRange = range;
    71. const xOffset = lineWidth;
    72. let sp = 0; // 周期偏移量
    73. let currData = 0;
    74. const waveupsp = 0.005; // 水波上涨速度
    75. let arcStack = [];
    76. const bR = radius - lineWidth;
    77. const circleOffset = -(Math.PI / 2);
    78. let circleLock = true;
    79. for (
    80. let i = circleOffset;
    81. i < circleOffset + 2 * Math.PI;
    82. i += 1 / (8 * Math.PI)
    83. ) {
    84. arcStack.push([radius + bR * Math.cos(i), radius + bR * Math.sin(i)]);
    85. }
    86. const cStartPoint = arcStack.shift();
    87. ctx.strokeStyle = color;
    88. ctx.moveTo(cStartPoint[0], cStartPoint[1]);
    89. function drawSin() {
    90. if (!ctx) {
    91. return;
    92. }
    93. ctx.beginPath();
    94. ctx.save();
    95. const sinStack = [];
    96. for (let i = xOffset; i <= xOffset + axisLength; i += 20 / axisLength) {
    97. const x = sp + (xOffset + i) / unit;
    98. const y = Math.sin(x) * currRange;
    99. const dx = i;
    100. const dy = 2 * cR * (1 - currData) + (radius - cR) - unit * y;
    101. ctx.lineTo(dx, dy);
    102. sinStack.push([dx, dy]);
    103. }
    104. const startPoint = sinStack.shift();
    105. ctx.lineTo(xOffset + axisLength, canvasHeight);
    106. ctx.lineTo(xOffset, canvasHeight);
    107. ctx.lineTo(startPoint[0], startPoint[1]);
    108. const gradient = ctx.createLinearGradient(0, 0, 0, canvasHeight);
    109. gradient.addColorStop(0, "#ffffff");
    110. gradient.addColorStop(1, color);
    111. ctx.fillStyle = gradient;
    112. ctx.fill();
    113. ctx.restore();
    114. }
    115. function render() {
    116. if (!ctx) {
    117. return;
    118. }
    119. ctx.clearRect(0, 0, canvasWidth, canvasHeight);
    120. if (circleLock && type !== "update") {
    121. if (arcStack.length) {
    122. const temp = arcStack.shift();
    123. ctx.lineTo(temp[0], temp[1]);
    124. ctx.stroke();
    125. } else {
    126. circleLock = false;
    127. ctx.lineTo(cStartPoint[0], cStartPoint[1]);
    128. ctx.stroke();
    129. arcStack = [];
    130. ctx.globalCompositeOperation = "destination-over";
    131. ctx.beginPath();
    132. ctx.lineWidth = lineWidth;
    133. ctx.arc(radius, radius, bR, 0, 2 * Math.PI, true);
    134. ctx.beginPath();
    135. ctx.save();
    136. ctx.arc(radius, radius, radius - 3 * lineWidth, 0, 2 * Math.PI, true);
    137. ctx.restore();
    138. ctx.clip();
    139. ctx.fillStyle = color;
    140. }
    141. } else {
    142. if (data >= 0.85) {
    143. if (currRange > range / 4) {
    144. const t = range * 0.01;
    145. currRange -= t;
    146. }
    147. } else if (data <= 0.1) {
    148. if (currRange < range * 1.5) {
    149. const t = range * 0.01;
    150. currRange += t;
    151. }
    152. } else {
    153. if (currRange <= range) {
    154. const t = range * 0.01;
    155. currRange += t;
    156. }
    157. if (currRange >= range) {
    158. const t = range * 0.01;
    159. currRange -= t;
    160. }
    161. }
    162. if (data - currData > 0) {
    163. currData += waveupsp;
    164. }
    165. if (data - currData < 0) {
    166. currData -= waveupsp;
    167. }
    168. sp += 0.07;
    169. drawSin();
    170. }
    171. self.timer = requestAnimationFrame(render);
    172. }
    173. render();
    174. }
    175. render() {
    176. const { radio } = this.state;
    177. const { percent, title, height = 1 } = this.props;
    178. return (
    179. <div
    180. className="waterWave"
    181. ref={(n) => (this.root = n)}
    182. style={{ transform: `scale(${radio})` }}
    183. >
    184. <div style={{ width: height, height: height }}>
    185. <canvas
    186. className="waterWaveCanvasWrapper"
    187. ref={(n) => (this.node = n)}
    188. width={height * 2}
    189. height={height * 2}
    190. />
    191. </div>
    192. <div className="text" style={{ width: height }}>
    193. {title && <span>{title}</span>}
    194. <h4>{percent}%</h4>
    195. </div>
    196. </div>
    197. );
    198. }
    199. }
    200. export default WaterWave;