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

import React, { Component } from "react";import "./styles.css";/* eslint no-return-assign: 0 *//* eslint no-mixed-operators: 0 */// riddle: https://riddle.alibaba-inc.com/riddles/2d9a4b90class WaterWave extends Component {state = {radio: 1};timer = 0;root = null;node = null;componentDidMount() {this.renderChart();this.resize();window.addEventListener("resize",() => {requestAnimationFrame(() => this.resize());},{ passive: true });}componentDidUpdate(props) {const { percent } = this.props;if (props.percent !== percent) {// 不加这个会造成绘制缓慢this.renderChart("update");}}componentWillUnmount() {cancelAnimationFrame(this.timer);if (this.node) {this.node.innerHTML = "";}window.removeEventListener("resize", this.resize);}resize = () => {if (this.root) {const { height = 1 } = this.props;const { offsetWidth } = this.root.parentNode;this.setState({radio: offsetWidth < height ? offsetWidth / height : 1});}};renderChart(type) {const { percent, color = "#1890FF" } = this.props;const data = percent / 100;const self = this;cancelAnimationFrame(this.timer);if (!this.node || (data !== 0 && !data)) {return;}const canvas = this.node;const ctx = canvas.getContext("2d");if (!ctx) {return;}const canvasWidth = canvas.width;const canvasHeight = canvas.height;const radius = canvasWidth / 2;const lineWidth = 2;const cR = radius - lineWidth;ctx.beginPath();ctx.lineWidth = lineWidth * 2;const axisLength = canvasWidth - lineWidth;const unit = axisLength / 8;const range = 0.2; // 振幅let currRange = range;const xOffset = lineWidth;let sp = 0; // 周期偏移量let currData = 0;const waveupsp = 0.005; // 水波上涨速度let arcStack = [];const bR = radius - lineWidth;const circleOffset = -(Math.PI / 2);let circleLock = true;for (let i = circleOffset;i < circleOffset + 2 * Math.PI;i += 1 / (8 * Math.PI)) {arcStack.push([radius + bR * Math.cos(i), radius + bR * Math.sin(i)]);}const cStartPoint = arcStack.shift();ctx.strokeStyle = color;ctx.moveTo(cStartPoint[0], cStartPoint[1]);function drawSin() {if (!ctx) {return;}ctx.beginPath();ctx.save();const sinStack = [];for (let i = xOffset; i <= xOffset + axisLength; i += 20 / axisLength) {const x = sp + (xOffset + i) / unit;const y = Math.sin(x) * currRange;const dx = i;const dy = 2 * cR * (1 - currData) + (radius - cR) - unit * y;ctx.lineTo(dx, dy);sinStack.push([dx, dy]);}const startPoint = sinStack.shift();ctx.lineTo(xOffset + axisLength, canvasHeight);ctx.lineTo(xOffset, canvasHeight);ctx.lineTo(startPoint[0], startPoint[1]);const gradient = ctx.createLinearGradient(0, 0, 0, canvasHeight);gradient.addColorStop(0, "#ffffff");gradient.addColorStop(1, color);ctx.fillStyle = gradient;ctx.fill();ctx.restore();}function render() {if (!ctx) {return;}ctx.clearRect(0, 0, canvasWidth, canvasHeight);if (circleLock && type !== "update") {if (arcStack.length) {const temp = arcStack.shift();ctx.lineTo(temp[0], temp[1]);ctx.stroke();} else {circleLock = false;ctx.lineTo(cStartPoint[0], cStartPoint[1]);ctx.stroke();arcStack = [];ctx.globalCompositeOperation = "destination-over";ctx.beginPath();ctx.lineWidth = lineWidth;ctx.arc(radius, radius, bR, 0, 2 * Math.PI, true);ctx.beginPath();ctx.save();ctx.arc(radius, radius, radius - 3 * lineWidth, 0, 2 * Math.PI, true);ctx.restore();ctx.clip();ctx.fillStyle = color;}} else {if (data >= 0.85) {if (currRange > range / 4) {const t = range * 0.01;currRange -= t;}} else if (data <= 0.1) {if (currRange < range * 1.5) {const t = range * 0.01;currRange += t;}} else {if (currRange <= range) {const t = range * 0.01;currRange += t;}if (currRange >= range) {const t = range * 0.01;currRange -= t;}}if (data - currData > 0) {currData += waveupsp;}if (data - currData < 0) {currData -= waveupsp;}sp += 0.07;drawSin();}self.timer = requestAnimationFrame(render);}render();}render() {const { radio } = this.state;const { percent, title, height = 1 } = this.props;return (<divclassName="waterWave"ref={(n) => (this.root = n)}style={{ transform: `scale(${radio})` }}><div style={{ width: height, height: height }}><canvasclassName="waterWaveCanvasWrapper"ref={(n) => (this.node = n)}width={height * 2}height={height * 2}/></div><div className="text" style={{ width: height }}>{title && <span>{title}</span>}<h4>{percent}%</h4></div></div>);}}export default WaterWave;
