如何使用canvas画玫瑰图

该代码示例展示了如何使用HTML5的CanvasAPI绘制两个带有缺口的圆,并根据外部数据在圆上画出刻度。当鼠标悬停在刻度线上时,会显示内容并绘制指向下方的指示线以及一个信息框。代码涉及角度计算、线条绘制以及事件监听等交互功能。
摘要由CSDN通过智能技术生成

 类似于这种样式。鼠标放上去还要有内容显示。

代码如下

<!DOCTYPE html>
<html>

<head>
    <title>Canvas Line Loop</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        canvas {
            background-color: #333;
            margin: 200px 0 0 200px;
        }
    </style>
</head>

<body>
    <canvas id="myCanvas" width="800" height="800"></canvas>
    <script>
        // 获取 Canvas 元素
        const canvas = document.getElementById('myCanvas');

        // 获取画布上下文
        const ctx = canvas.getContext('2d');
        //有多少数据分成几份
        let outDataArr = [10, 20, 50, 10, 20, 10, 20, 50, 10, 20, 50, 20, 50, 10, 20, 20, 50, 10, 20, 10, 20, 50, 60, 8,
            20, 3, 9, 50, 20, 50, 10, 20, 20, 50, 10, 20, 10, 20, 50, 60, 8, 20, 3, 9
        ]
        let insideDataarr = [10, 20, 50, 60, 8, 10, 10, 20, 50, 10, 20, 50, 20, 50, 10, 20, 20, 50, 10, 20, 10, 20, 50,
            60, 8, 20, 3, 9, 20, 50, 10, 20, 50, 20, 3, 20, 20, 50, 10, 20, 50, 10, 20, 9
        ]

        /**
         * 画两个由缺口的圆
         */

        //圆的起始角度和结尾角度
        let beginAngle = 270
        let EndAngle = 220

        //内,外圈半径
        let radius = 100
        let inradius = 90
        //内外圈颜色
        let outside = '#02aba3'
        let inside = '#d95524'
        //是否画出内外圈
        let isShowArc = false

        if (isShowArc) {
            //画外圈圆
            ctx.beginPath()
            // ctx.arc(350, 300, 100, Math.PI * 1.5, (Math.PI) * 1.2)
            ctx.arc(350, 300, 100, [(Math.PI) / 180] * beginAngle, [(Math.PI) / 180] * EndAngle)
            ctx.lineWidth = 6
            ctx.stroke()

            ctx.moveTo(0, 0)
            ctx.lineTo(350, 0)
            ctx.lineTo(350, 300)
            ctx.stroke()
            //画内圈圆
            ctx.beginPath()
            // ctx.arc(350, 300, 80, Math.PI * 1.5, (Math.PI) * 1.2)
            ctx.arc(350, 300, 80, [(Math.PI) / 180] * beginAngle, [(Math.PI) / 180] * EndAngle)
            ctx.lineWidth = 1
            ctx.stroke()
        }

        /**
         * 画圆上的数据展示
         */

        //刻度长度占比:获取数据中的最大值然后通过和最大值的比较算出每个数据的长度占比
        let outLengthPercentage = []
        let inLengthPercentage = []
        let sumNumOut = Math.max.apply(null, outDataArr);
        let sumNumIn = Math.max.apply(null, insideDataarr);
        outDataArr.map((res, index) => {
            outLengthPercentage.push((res / sumNumOut).toFixed(2) - 0)
            inLengthPercentage.push((insideDataarr[index] / sumNumIn).toFixed(2) - 0)
        })

        //外,内圈刻度最长为
        let outMaXLength = 60
        let inMaXLength = 20

        //有多少刻度:通过数组的长度判断刻度的数量
        let dataNum = outDataArr.length

        //由于canvas中角度顺序是 上 270° ,右 0°,下 90°,左 180°
        //所以设置圆最上端顶点的角度为
        let tops = 270


        /**
         * 保存所有画出来的点的坐标用以判断鼠标是否在刻度线上
         */
        //获得外圈所有的尾线的坐标
        let outLastData = []
        //外圈x,y坐标的范围
        let outLineData = []
        //选中的那个刻度的index值
        let checkLindexIndex = null
        let oldCheckLindexIndex = null


        //开始启动
        drawLine()


        /**
         * 函数
         */

        //在圆上画刻度的函数
        function drawLine() {
            console.log('开始画刻度');
            //清除
            //获得外圈所有的尾线的坐标
            outLastData = []
            //外圈x,y坐标的范围
            outLineData = []

            //用以设置内外圈刻度长度的标识
            let index = 0
            //每个刻度对应的角度
            let addbeishu = ((360 - (beginAngle - EndAngle)) / (dataNum - 1)).toFixed(2) - 0
            for (let i = 0; i < 310;) {
                i = i.toFixed(2) - 0
                //度数
                let angle = tops + i
                angle = angle >= 360 ? (angle - 360).toFixed(2) - 0 : angle

                //画刻度的函数
                //要传的参数有:内外圈种类 , 圆的横坐标 ,圆的纵坐标 ,圆的度数 ,外圈刻度颜色 ,内圈刻度颜色 ,每个刻度长度 ,内外圈的刻度的基础半径
                drawScale('out', 350, 300, angle, outside, inside, outMaXLength * outLengthPercentage[index], radius)
                drawScale('in', 350, 300, angle, outside, inside, inMaXLength * inLengthPercentage[index], inradius)

                i += addbeishu

                //给圆的最后一个加数据 其中 EndAngle 是圆结尾的度数 
                if (i >= 310) {
                    drawScale('out', 350, 300, EndAngle, outside, inside, outMaXLength * outLengthPercentage[index],
                        radius)
                    drawScale('in', 350, 300, EndAngle, outside, inside, inMaXLength * inLengthPercentage[index],
                        inradius)
                }

                index++
            }
        }

        //画刻度
        function drawScale(type, x, y, angle, outsideColor, insideColor, length, radius) {

            //外圈
            if (type == 'out') {
                //通过sin和cos来计算刻度 开始点和结尾点 坐标 
                let oldY = Math.sin((Math.PI / 180) * (angle)).toFixed(2) * radius
                let oldX = Math.cos((Math.PI / 180) * (angle)).toFixed(2) * radius
                let newY = Math.sin((Math.PI / 180) * (angle)).toFixed(2) * (radius + length)
                let newX = Math.cos((Math.PI / 180) * (angle)).toFixed(2) * (radius + length)
                ctx.beginPath()
                ctx.moveTo(x + oldX, y + oldY)
                ctx.lineTo(x + newX, y + newY)
                ctx.lineWidth = 3
                ctx.strokeStyle = outsideColor
                ctx.stroke()
                outLastData.push([x + newX, y + newY])
                //如果在右上角那存的数据就是 x<刻度结尾x && x>刻度开始x ,y>刻度结尾y && y<刻度开始y
                //按照 小的下标为0 ,大的下标为 1
                if (angle >= 270 && angle <= 360) {
                    outLineData.push({
                        x: [x + oldX - 1, x + newX + 1],
                        y: [y + newY - 1, y + oldY + 1]
                    })
                }
                if (angle >= 0 && angle < 90) {
                    outLineData.push({
                        x: [x + oldX - 1, x + newX + 1],
                        y: [y + oldY - 1, y + newY + 1]
                    })
                }
                if (angle >= 90 && angle < 180) {
                    outLineData.push({
                        x: [x + newX - 1, x + oldX + 1],
                        y: [y + oldY - 1, y + newY + 1]
                    })

                }
                if (angle >= 180 && angle < 270) {
                    outLineData.push({
                        x: [x + newX - 1, x + oldX + 1],
                        y: [y + newY - 1, y + oldY + 1]
                    })
                }


            } else {
                //内圈
                oldY = Math.sin((Math.PI / 180) * (angle)).toFixed(2) * radius
                oldX = Math.cos((Math.PI / 180) * (angle)).toFixed(2) * radius
                newY = Math.sin((Math.PI / 180) * (angle)).toFixed(2) * (radius - length)
                newX = Math.cos((Math.PI / 180) * (angle)).toFixed(2) * (radius - length)
                ctx.beginPath()
                ctx.moveTo(x + oldX, y + oldY)
                ctx.lineTo(x + newX, y + newY)
                ctx.lineWidth = 3
                ctx.strokeStyle = insideColor
                ctx.stroke()
            }

        }



        //重画页面
        let times = null

        function drawUp(e) {
            // console.log(outLineData);
            var rect = canvas.getBoundingClientRect();
            //获得在canvas中点击的数据
            let checkCoordinate = [e.clientX - rect.x, e.clientY - rect.y]


            for (let index = 0; index < outLineData.length; index++) {
                let res = outLineData[index]
                if (res.x[1] >= checkCoordinate[0] && res.x[0] <= checkCoordinate[0] && res.y[1] >=
                    checkCoordinate[1] && res.y[0] <= checkCoordinate[1]) {
                    console.log('开始画延伸线');
                    checkLindexIndex = index
                    let x = outLastData[index][0]
                    let y = outLastData[index][1]
                    ctx.beginPath()
                    ctx.moveTo(x, y)
                    //前一半线在右端,后一半在左端
                    if (Math.floor(outLineData.length / 2) < index) {
                        ctx.lineTo(600, y + 10)
                    } else {
                        ctx.lineTo(200, y + 10)
                    }

                    ctx.stroke()
                    break;
                } else {
                    checkLindexIndex = null
                }

            }

            if (checkLindexIndex != oldCheckLindexIndex && typeof (checkLindexIndex) == 'number') {
                console.log('重画延伸线');
                ctx.clearRect(0, 0, 800, 800)
                drawLine()
                let x = outLastData[checkLindexIndex][0]
                let y = outLastData[checkLindexIndex][1]
                console.log(x, y);
                ctx.beginPath()
                ctx.moveTo(x, y)
                //由于不是满圆,所以外线段的数组数量来判断详情指示框在左还是右。如果是在左边则
                if (Math.floor(outLineData.length * 0.55) > checkLindexIndex) {
                    ctx.lineTo(600, y + 10)
                    ctx.lineTo(600, 500)
                    ctx.lineTo(450, 500)
                    ctx.lineTo(450, 788)
                    ctx.lineTo(750, 788)
                    ctx.lineTo(750, 500)
                    ctx.lineTo(600, 500)
                    drawText(500, 600)
                } else {
                    ctx.lineTo(150, y + 10)
                    ctx.lineTo(150, 500)
                    ctx.lineTo(10, 500)
                    ctx.lineTo(10, 788)
                    ctx.lineTo(300, 788)
                    ctx.lineTo(300, 500)
                    ctx.lineTo(150, 500)
                    drawText(50, 600)
                }
                ctx.strokeStyle = '#c8c9cc'
                ctx.stroke()
                checkLindexIndex = oldCheckLindexIndex
            } else if (checkLindexIndex == oldCheckLindexIndex && checkLindexIndex) {

            } else {
                console.log('清除延伸线');
                ctx.clearRect(0, 0, 800, 800)
                drawLine()
            }

        }

        /**
         * 画下面的文字
         */
        function drawText(x, y) {
            ctx.font = '50px Verdana'
            ctx.strokeText('Hello Canvas!', x, y, 200)
            // ctx.beginPath()
            ctx.moveTo(x, y + 8)
            ctx.lineTo(x + 200, y + 8)

            ctx.moveTo(x - 50, y + 88)
            ctx.lineTo(x + 250, y + 88)
            ctx.stroke()
        }



        //canvas.onclick = drawUp

        canvas.onmousemove = drawUp
    </script>
</body>

</html>

当然,我可以帮你实现一个使用 canvas 绘制玫瑰花的方法。以下是一个简单的示例代码: ```javascript function drawRose(ctx, centerX, centerY, numPetals, petalLength, petalWidth) { ctx.beginPath(); ctx.translate(centerX, centerY); ctx.moveTo(0, 0); for (let i = 0; i < numPetals * 2; i++) { ctx.rotate(Math.PI / numPetals); if (i % 2 === 0) { ctx.bezierCurveTo( petalLength / 2, petalWidth / 2, petalLength, -petalWidth / 2, petalLength, 0 ); } else { ctx.bezierCurveTo( petalLength / 2, -petalWidth / 2, petalLength, petalWidth / 2, petalLength, 0 ); } } ctx.closePath(); ctx.fillStyle = 'red'; ctx.fill(); } // 使用示例 const canvas = document.getElementById('myCanvas'); const ctx = canvas.getContext('2d'); drawRose(ctx, canvas.width / 2, canvas.height / 2, 5, 100, 20); ``` 在这个示例中,我们定义了一个名为 `drawRose` 的方法,它接受以下参数: - `ctx`:canvas 的上下文对象。 - `centerX` 和 `centerY`:玫瑰花的中心点坐标。 - `numPetals`:玫瑰花的花瓣数量。 - `petalLength` 和 `petalWidth`:花瓣的长度和宽度。 在方法内部,我们使用 `ctx.beginPath()` 开始绘制路径,并使用 `ctx.moveTo(0, 0)` 将起始点设置为原点。 然后,我们使用一个循环来绘制花瓣。在每次迭代中,我们使用 `ctx.rotate()` 方法旋转布,然后根据迭代次数的奇偶性使用 `ctx.bezierCurveTo()` 方法绘制花瓣的曲线路径。 最后,我们使用 `ctx.closePath()` 闭合路径,并使用 `ctx.fillStyle` 设置填充颜色为红色。你可以根据需要修改填充颜色或其他样式属性。 在示例的最后,我们获取 canvas 元素和上下文对象,并调用 `drawRose` 方法来绘制玫瑰花。 希望这个示例能满足你的需求!如果有任何问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值