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/2d9a4b90
class 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 (
<div
className="waterWave"
ref={(n) => (this.root = n)}
style={{ transform: `scale(${radio})` }}
>
<div style={{ width: height, height: height }}>
<canvas
className="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;