canvas作图系列——气泡图

本文详细介绍了使用JavaScript在Canvas上绘制两种不同类型的气泡图的方法。一种气泡图的半径大小代表数据值,另一种则通过调整圆心的Y坐标来反映数据比例。文中提供了完整的代码示例,包括如何创建渐变填充、绘制圆圈、添加交互元素如高亮和弹出框。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

气泡图

这里做的气泡图是柱形图的一种变体形式,但是基本思想其实差不多,只不过柱形的高矮换成的半径的大小,当然圆心的坐标也要随之改变,圆心的纵坐标要加上跟半径同比例的缩放系数才能保证圆的底部都在同一条水平线上

图一

在这里插入图片描述

function ybChart(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("#cav7");
		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 startRowLinePosition = canvas.clientWidth / data.length/2; //圆球底横X坐标
		var circlePointX = canvas.clientWidth / data.length/2; //圆球圆心标准坐标X
		var circlePointY = canvas.clientHeight * 3 / 5; //圆球圆心标准坐标Y
		var circleRadio = canvas.clientWidth/data.length/2; //圆球标准半径

		//画布背景
		cav.drawImage(
			ybBG,
			0,
			10,
			canvas.clientWidth,
			canvasBGHeight
		);
		for (let i = 0; i < data.length; i++) {
			//圆球底横
			cav.beginPath();
			cav.moveTo(startRowLinePosition - 10, zeroPoint[1] - 3);
			cav.lineTo(startRowLinePosition + 10, zeroPoint[1] - 3);
			cav.lineWidth = 3;
			cav.setLineDash([1, 0]);
			cav.strokeStyle = "#fff";
			cav.stroke();

			// 绘制圆球
			var normalBall = cav.createLinearGradient(
				circlePointX - (circleRadio * data[i].riskNumber) / dataMax,
				circlePointY + circleRadio - (circleRadio * data[i].riskNumber) / dataMax,
				circlePointX + (circleRadio * data[i].riskNumber) / dataMax,
				circlePointY + circleRadio - (circleRadio * data[i].riskNumber) / dataMax
			);
			normalBall.addColorStop(0, "#5128FF");
			normalBall.addColorStop(1, "#9F77FF");
			var lightHighBall = cav.createLinearGradient(
				circlePointX - (circleRadio * data[i].riskNumber) / dataMax,
				circlePointY + circleRadio - (circleRadio * data[i].riskNumber) / dataMax,
				circlePointX + (circleRadio * data[i].riskNumber) / dataMax,
				circlePointY + circleRadio - (circleRadio * data[i].riskNumber) / dataMax
			);
			lightHighBall.addColorStop(0, "#B444FC");
			lightHighBall.addColorStop(1, "#F14B6F");
			var circleRadioYPosition =
				circlePointY + circleRadio - (circleRadio * data[i].riskNumber) / dataMax;

			cav.beginPath();
			cav.arc(
				circlePointX,
				circleRadioYPosition,
				(circleRadio * data[i].riskNumber) / dataMax,
				0,
				Math.PI * 2,
				false
			);
			cav.fillStyle = i == index ? lightHighBall : normalBall;
			cav.fill();

			//圆球底竖
			if (i == index) {
				cav.beginPath();
				cav.moveTo(startRowLinePosition, zeroPoint[1] - 3);
				cav.lineTo(startRowLinePosition, 10);
				cav.lineWidth = 2;
				cav.setLineDash([3, 2]);
				cav.strokeStyle = "#fff";
				cav.stroke();

				//圆球中横线
				cav.beginPath();
				cav.moveTo(0, circleRadioYPosition);
				cav.lineTo(canvas.clientWidth, circleRadioYPosition);
				cav.lineWidth = 2;
				cav.setLineDash([3, 2]);
				cav.strokeStyle = "#fff";
				cav.stroke();

				//绘制交汇圆球
				cav.beginPath();
				cav.arc(circlePointX, circleRadioYPosition, 4, 0, Math.PI * 2, false);
				cav.fillStyle = "#fff";
				cav.fill();

				//圆球外环
				cav.beginPath();
				cav.arc(
					circlePointX,
					circleRadioYPosition,
					(circleRadio * data[i].riskNumber) / dataMax + 5,
					0,
					Math.PI * 2,
					false
				);
				cav.strokeStyle = "#fff";
				cav.lineWidth = 1;
				cav.setLineDash([1, 0]);
				cav.stroke();

				if (
					circleRadioYPosition + floatBG.height >=
					canvasBGHeight
				) {
					var floatBGPositionY =
						circleRadioYPosition - floatBG.height;
				} else {
					var floatBGPositionY =
						circleRadioYPosition - floatBG.height / 2;
				}

				var floatBGWidth =
					(floatBG.width * data[i].riskName.length) / 10 + 20;
				if (index < 3) {
					var floatBGPositionX = circlePointX + 5;
				} else {
					var floatBGPositionX = circlePointX - floatBGWidth - 5;
				}
				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)");
			} else {
				cav.beginPath();
				cav.moveTo(startRowLinePosition, zeroPoint[1] - 3);
				cav.lineTo(startRowLinePosition, zeroPoint[1] - 70);
				cav.lineWidth = 2;
				cav.setLineDash([1, 0]);
				cav.strokeStyle = "#fff";
				cav.stroke();
			}
			circlePointX += canvas.clientWidth / data.length;
			startRowLinePosition += 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
		);
	}

图二在这里插入图片描述

这个图标跟上面那个其实差不多,只不过这里的圆心Y坐标的计算公式不一样
画布高度+圆心半径*数据比值-数据比值*画布高度+顶部预留高度

JS

function floatBallChart(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("#cav10");
		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 startRowLinePosition = canvas.clientWidth / data.length*3/4+20; //圆球底横X坐标
		var circlePointX = canvas.clientWidth / data.length*3/4+20; //圆球圆心标准坐标X
		var circleRadio = canvas.clientWidth/data.length-10 //圆球标准半径

		//画布背景
		cav.drawImage(
			ybBG,
			0,
			10,
			canvas.clientWidth,
			canvasBGHeight
		);
		for (let i = 0; i < data.length; i++) {

			// 绘制圆球
			//圆球圆心纵坐标
			var circleRadioYPosition =
			canvasBGHeight+circleRadio*data[i].riskNumber / dataMax - data[i].riskNumber / dataMax*canvasBGHeight+20;
			var normalBall = cav.createLinearGradient(
				circlePointX - (circleRadio * data[i].riskNumber) / dataMax,
				circleRadioYPosition,
				circlePointX + (circleRadio * data[i].riskNumber) / dataMax,
				circleRadioYPosition
			);
			normalBall.addColorStop(0, "#9F77FF");
			normalBall.addColorStop(1, "#9F77FF");
			var lightHighBall = cav.createLinearGradient(
				circlePointX - (circleRadio * data[i].riskNumber) / dataMax,
				circleRadioYPosition,
				circlePointX + (circleRadio * data[i].riskNumber) / dataMax,
				circleRadioYPosition
			);
			lightHighBall.addColorStop(0, "#B444FC");
			lightHighBall.addColorStop(1, "#F14B6F");
			

			cav.beginPath();
			cav.arc(
				circlePointX,
				circleRadioYPosition,
				(circleRadio * data[i].riskNumber) / dataMax,
				0,
				Math.PI * 2,
				false
			);
			cav.fillStyle = i == index ? 'rgba(222, 72, 154,0.3)' : 'rgba(119, 78, 255,0.3)';
			cav.fill();

			//圆球底竖
			if (i == index) {
				cav.beginPath();
				cav.moveTo(startRowLinePosition, zeroPoint[1] - 3);
				cav.lineTo(startRowLinePosition, 10);
				cav.lineWidth = 2;
				cav.setLineDash([3, 2]);
				cav.strokeStyle = "#fff";
				cav.stroke();

				//圆球中横线
				cav.beginPath();
				cav.moveTo(0, circleRadioYPosition);
				cav.lineTo(canvas.clientWidth, circleRadioYPosition);
				cav.lineWidth = 2;
				cav.setLineDash([3, 2]);
				cav.strokeStyle = "#fff";
				cav.stroke();

				//绘制交汇圆球
				cav.beginPath();
				cav.arc(circlePointX, circleRadioYPosition, 4, 0, Math.PI * 2, false);
				cav.fillStyle = "#fff";
				cav.fill();

				//圆球外环
				cav.beginPath();
				cav.arc(
					circlePointX,
					circleRadioYPosition,
					(circleRadio * data[i].riskNumber) / dataMax + 5,
					0,
					Math.PI * 2,
					false
				);
				cav.strokeStyle = "#fff";
				cav.lineWidth = 1;
				cav.setLineDash([1, 0]);
				cav.stroke();

				if (
					circleRadioYPosition + floatBG.height >=
					canvasBGHeight
				) {
					var floatBGPositionY =
						circleRadioYPosition - floatBG.height;
				} else {
					var floatBGPositionY =
						circleRadioYPosition - floatBG.height / 2;
				}

				var floatBGWidth =
					(floatBG.width * data[i].riskName.length) / 10 + 20;
				if (index < 3) {
					var floatBGPositionX = circlePointX + 5;
				} else {
					var floatBGPositionX = circlePointX - floatBGWidth - 5;
				}
				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*3/4;
			startRowLinePosition += canvas.clientWidth /  data.length*3/4;
		}
		//弹出框
		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
		);
	}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IsQtion

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值