canvas作图系列——折(曲)线图

mac2024-07-01  59

折(曲)线图

折线图和曲线图最主要的地方就是获取到当前点以及下一个点的坐标值,只要有了这两个值,其他的都不是问题。canvas绘制曲线主要用的的是二次贝塞尔曲线和三次贝塞尔曲线 二次贝塞尔曲线 ctx.moveTo(20,20) //开始点坐标 ctx.quadraticCurveTo(20,100,200,20); //控制点坐标的xy和结束点坐标的xy 三次贝塞尔曲线 ctx.moveTo(20,20); //开始点坐标 ctx.bezierCurveTo(20,100,200,100,200,20); //控制点1的xy坐标,控制点2的xy坐标,结束点坐标

图一

用三次贝塞尔曲线制作的柱图,左曲线和右曲线的计算公式在代码的注释里有

function zjChart(data, index) { var dataMax = 0; for (let i = 0; i < data.length; i++) { data[i].riskNumber = parseFloat(data[i].riskNumber) if (data[i].riskNumber >= dataMax) { dataMax = data[i].riskNumber } } var canvas = document.querySelector("#cav8"); var zeroPoint = [0, canvas.clientHeight - 10]; //坐标轴0点坐标 var cav = canvas.getContext("2d"); cav.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); //绘制前先清空画布 var canvasBGHeight = canvas.clientHeight - 20; //背景高度 var barWidth = canvas.clientWidth / data.length; //柱体宽度 var middlePointX = canvas.clientWidth / data.length/2; //柱体中点坐标 var startPointX = 0; //柱体起始坐标 for (let i = 0; i < data.length; i++) { //绘制柱体的贝塞尔三次曲线 var startY = canvasBGHeight; var startX = startPointX + 5; var endY = (canvasBGHeight) - (data[i].riskNumber / dataMax) * (canvasBGHeight - 100); var endX = middlePointX; var bezierCurveNor = cav.createLinearGradient( middlePointX, startY, middlePointX, endY ); bezierCurveNor.addColorStop(0, "#2A1AA5"); bezierCurveNor.addColorStop(1, "#9A73F8"); var bezierCurveHigh = cav.createLinearGradient( middlePointX, startY, middlePointX, endY ); bezierCurveHigh.addColorStop(0, "#5828A5"); bezierCurveHigh.addColorStop(1, "#FF4D4D"); cav.beginPath(); cav.moveTo(startX, canvasBGHeight); //(endX-startX)*2/3+startX,startY,(endX-startX)*2/3+startX,endY,endX,endY 左曲线计算公式 cav.bezierCurveTo( ((endX - startX) * 2) / 3 + startX, startY, ((endX - startX) * 2) / 3 + startX, endY, endX, endY ); startY = endY; startX = endX; endY = canvasBGHeight; endX = middlePointX + barWidth / 2 - 5; cav.moveTo(startX, startY); //(endX-startX)*1/3+startX,startY,(endX-startX)*1/3+startX,endY,endX,endY 右曲线计算公式 cav.bezierCurveTo( ((endX - startX) * 1) / 3 + startX, startY, ((endX - startX) * 1) / 3 + startX, endY, endX, endY ); cav.setLineDash([1, 0]); cav.fillStyle = i == index ? bezierCurveHigh : bezierCurveNor; cav.lineTo(startPointX + 5, canvasBGHeight); cav.closePath(); cav.fill(); // 绘制中隔线 cav.beginPath(); cav.moveTo(middlePointX, canvasBGHeight); cav.lineTo(middlePointX, 0); cav.lineWidth = 1; cav.setLineDash([2, 1]); cav.strokeStyle = "rgba(255,255,255,0.6)"; cav.stroke(); //绘制顶端圆球 cav.drawImage(chartBall, startX - 7, startY - 7, 14, 14); //绘制顶端连线 if (i < data.length-1) { cav.beginPath(); cav.moveTo(middlePointX, startY); cav.lineTo( middlePointX + barWidth, canvasBGHeight - (data[i + 1].riskNumber / dataMax) * (canvasBGHeight - 100) ); cav.lineWidth = 2; cav.setLineDash([2, 2]); cav.strokeStyle = "#fff"; cav.stroke(); } if (i == index) { // 绘制高亮框 var ractHigh = cav.createLinearGradient( middlePointX, canvasBGHeight, middlePointX, 0 ); ractHigh.addColorStop(0, "rgba(181, 68, 250,0.5)"); ractHigh.addColorStop(1, "rgba(181, 68, 250,0.2)"); cav.beginPath(); cav.rect(startPointX, 0, barWidth, canvasBGHeight); cav.fillStyle = ractHigh; cav.fill(); //绘制弹出框 if ( startY + floatBG.height >= canvasBGHeight && !(startY - floatBG.height <= 0) ) { var floatBGPositionY = startY - floatBG.height; } else { var floatBGPositionY = startY; } var floatBGWidth = (floatBG.width * data[i].riskName.length) / 10 + 20; if (index < 3) { var floatBGPositionX = middlePointX + 10; } else { var floatBGPositionX = middlePointX - floatBGWidth - 10; } var floatLineColor = cav.createLinearGradient( floatBGPositionX + 10, floatBGPositionY + floatBG.height / 2 - 10, floatBGPositionX + floatBGWidth + 5, floatBGPositionY + floatBG.height / 2 - 10 ); floatLineColor.addColorStop(0, "rgba(255, 255, 255,0)"); floatLineColor.addColorStop(1, "rgba(255, 255, 255,1)"); } startPointX += barWidth; middlePointX += barWidth; } //弹出框 cav.drawImage( floatBG, floatBGPositionX, floatBGPositionY, floatBGWidth, 80 ); //弹框分割线 cav.beginPath(); cav.moveTo( floatBGPositionX + 10, floatBGPositionY + floatBG.height / 2 - 10 ); cav.lineTo( floatBGPositionX + floatBGWidth - 10, floatBGPositionY + floatBG.height / 2 - 10 ); cav.setLineDash([2, 1]); cav.strokeStyle = floatLineColor; cav.stroke(); //弹框文字 cav.textAlign = "start"; cav.textBaseline = "bottom"; cav.font = "16px MeicrosoftYahei"; cav.fillStyle = "#fff"; cav.fillText( data[index].riskName, floatBGPositionX + 10, floatBGPositionY + floatBG.height / 2 - 15 ); cav.textAlign = "start"; cav.textBaseline = "top"; cav.font = "bold 36px Akrobat-Black"; cav.fillStyle = "#fff"; cav.fillText( data[index].riskNumber + "%", floatBGPositionX + 10, floatBGPositionY + floatBG.height / 2 - 5 ); //绘制底边 var bottomLine = cav.createLinearGradient( 0, canvasBGHeight, canvas.clientWidth, canvasBGHeight ); bottomLine.addColorStop(0, "rgba(255, 255, 255,0)"); bottomLine.addColorStop(0.4, "rgba(255, 255, 255,1)"); bottomLine.addColorStop(0.6, "rgba(255, 255, 255,1)"); bottomLine.addColorStop(1, "rgba(255, 255, 255,0)"); cav.beginPath(); cav.moveTo(0, canvasBGHeight); cav.lineTo(canvas.clientWidth, canvasBGHeight); cav.lineWidth = 1; cav.strokeStyle = bottomLine; cav.setLineDash([1, 0]); cav.stroke(); }

图二

两柱图之间的连线是用三次贝塞尔曲线做的波浪线,控制点取的分别是连线的三分之一处和三分之二处的坐标,一个向上偏移一个向下偏移

function zlChart(data, index) { var dataMax = 0; for (let i = 0; i < data.length; i++) { data[i].riskNumber = parseFloat(data[i].riskNumber) if (data[i].riskNumber >= dataMax) { dataMax = data[i].riskNumber } } var canvas = document.querySelector("#cav9"); var zeroPoint = [0, canvas.clientHeight - 10]; //坐标轴0点坐标 var cav = canvas.getContext("2d"); cav.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); //绘制前先清空画布 var canvasBGHeight = canvas.clientHeight - 20; //背景高度 var barWidth = canvas.clientWidth / data.length/2; //柱体宽度 var startPointX = canvas.clientWidth / data.length/2 - barWidth / 2; //柱体起始坐标 var circlePointX = canvas.clientWidth / data.length/2; //圆点起始坐标 for (let i = 0; i < data.length; i++) { //绘制柱形 var barColorNor = cav.createLinearGradient( startPointX, 0, startPointX, canvasBGHeight ); barColorNor.addColorStop(0, "rgba(78, 36, 255,0)"); barColorNor.addColorStop(1, "rgba(78, 36, 255,0.7)"); var barColorHigh = cav.createLinearGradient( startPointX, 0, startPointX, canvasBGHeight ); barColorHigh.addColorStop(0, "rgba(78, 36, 255,0)"); barColorHigh.addColorStop(1, "rgba(250, 75, 79,0.7)"); cav.beginPath() cav.rect(startPointX, 0, barWidth, canvasBGHeight) cav.fillStyle = i == index ? barColorHigh : barColorNor cav.fill() //绘制柱形中线 var lineColor = cav.createLinearGradient( circlePointX, canvasBGHeight, circlePointX, 0 ); lineColor.addColorStop(0, "rgba(255, 255, 255,0)"); lineColor.addColorStop(1, "rgba(255, 255, 255,0.7)"); cav.beginPath() cav.moveTo(circlePointX, 0) cav.lineTo(circlePointX, canvasBGHeight) cav.setLineDash([1, 0]) cav.lineWidth = i == index ? 2 : 1 cav.strokeStyle = i == index ? lineColor : 'rgba(255,255,255,0.5)' cav.stroke() var circlePointY = canvasBGHeight - (data[i].riskNumber / dataMax) * (canvasBGHeight - 100); if (i == index) { //绘制圆点 cav.drawImage(chartBall, circlePointX - 7, circlePointY - 7, 14, 14) } else { cav.beginPath() cav.arc(circlePointX, circlePointY, 5, 0, Math.PI * 2, false) cav.fillStyle = "#fff" cav.fill() } if (i < data.length-1) { var nextX = canvas.clientWidth / data.length var nextY = canvasBGHeight - (data[i + 1].riskNumber / dataMax) * (canvasBGHeight - 100) cav.beginPath() cav.moveTo(circlePointX, circlePointY) cav.bezierCurveTo( nextX / 3 + circlePointX + 20, circlePointY - (circlePointY - nextY) / 3 - 20, nextX * 2 / 3 + circlePointX - 20, circlePointY - (circlePointY - nextY) * 2 / 3 + 20, circlePointX + nextX, nextY ); cav.setLineDash([2, 1]) cav.lineWidth = 2 cav.strokeStyle = '#fff' cav.stroke() } if (i == index) { //绘制弹出框 if ( circlePointY + floatBG.height >= canvasBGHeight && !(circlePointY - floatBG.height <= 0) ) { var floatBGPositionY = circlePointY - floatBG.height; } else { var floatBGPositionY = circlePointY; } var floatBGWidth = (floatBG.width * data[i].riskName.length) / 10 + 20; if (index < 3) { var floatBGPositionX = circlePointX + 10; } else { var floatBGPositionX = circlePointX - floatBGWidth - 10; } var floatLineColor = cav.createLinearGradient( floatBGPositionX + 10, floatBGPositionY + floatBG.height / 2 - 10, floatBGPositionX + floatBGWidth + 5, floatBGPositionY + floatBG.height / 2 - 10 ); floatLineColor.addColorStop(0, "rgba(255, 255, 255,0)"); floatLineColor.addColorStop(1, "rgba(255, 255, 255,1)"); } circlePointX += canvas.clientWidth / data.length startPointX += canvas.clientWidth / data.length } //弹出框 cav.drawImage( floatBG, floatBGPositionX, floatBGPositionY, floatBGWidth, 80 ); //弹框分割线 cav.beginPath(); cav.moveTo( floatBGPositionX + 10, floatBGPositionY + floatBG.height / 2 - 10 ); cav.lineTo( floatBGPositionX + floatBGWidth - 10, floatBGPositionY + floatBG.height / 2 - 10 ); cav.setLineDash([2, 1]); cav.strokeStyle = floatLineColor; cav.stroke(); //弹框文字 cav.textAlign = "start"; cav.textBaseline = "bottom"; cav.font = "16px MeicrosoftYahei"; cav.fillStyle = "#fff"; cav.fillText( data[index].riskName, floatBGPositionX + 10, floatBGPositionY + floatBG.height / 2 - 15 ); cav.textAlign = "start"; cav.textBaseline = "top"; cav.font = "bold 36px Akrobat-Black"; cav.fillStyle = "#fff"; cav.fillText( data[index].riskNumber + "%", floatBGPositionX + 10, floatBGPositionY + floatBG.height / 2 - 5 ); //绘制底边 var bottomLine = cav.createLinearGradient( 0, canvasBGHeight, canvas.clientWidth, canvasBGHeight ); bottomLine.addColorStop(0, "rgba(255, 255, 255,0)"); bottomLine.addColorStop(0.4, "rgba(255, 255, 255,1)"); bottomLine.addColorStop(0.6, "rgba(255, 255, 255,1)"); bottomLine.addColorStop(1, "rgba(255, 255, 255,0)"); cav.beginPath(); cav.moveTo(0, canvasBGHeight); cav.lineTo(canvas.clientWidth, canvasBGHeight); cav.lineWidth = 1; cav.strokeStyle = bottomLine; cav.setLineDash([1, 0]); cav.stroke(); }
最新回复(0)