前端大杂烩

【canvas】曲线图绘制使用贝塞尔曲线

2023-10-29  本文已影响0人  写写而已
GIF 2023-10-30 18-46-11.gif
<div>
    <canvas id="c1" width="500" height="255"></canvas>
    <button onclick="download()">download</button>
    <img id="img" src="" />
</div>
<style>
    * {
        margin: 0;
        padding: 0;
    }
    div {
        padding: 20px;
    }
    body {
        background-color: #333;
    }
    canvas {
        border-radius: 6px;
        background-color: #fff;
    }
</style>
<script src="./my-canvas.js"></script>
<script>
function download() {
    line.download()
}
</script>

class Line {
    /** 边距 */
    padding = {
        left: 0,
        top: 0,
        bottom: 0,
        right: 0
    }
    /** Y轴标尺 */
    axias = { max: 100, show: true, number: 5 }
    /** 间隔 */
    gap = 0
    margin = 12
    shadowX = 7
    shadowWidth = 4
    shadowOpacity = 0.1
    constructor(dom, data, options) {
        let element = document.querySelector(dom)
        this.element = element
        this.ctx = this.element.getContext('2d')
        this.data = JSON.parse(JSON.stringify(data))

        let { padding, axias, labels } = options
        padding = { ...this.padding, ...padding }
        this.padding = padding
        let { width, height } = element
        this.width = width
        this.height = height
        this.contentHight = height - padding.top - padding.bottom
        this.contentWidth = width - padding.left - padding.right
        // this.drawLines()
        this.setGap()
        this.axias = { ...this.axias, ...axias }
        this.AniPoints = this.setPoints()
        this.AniShadows = this.setPoints(true)
        this.points = []
        this.shadows = []
        // console.log(this.points)


        this.labels = labels
        // this.drawXLine()
        // this.drawxLabels()
        // this.drawYLabels()
        this.animation()
        // this.drawSmoothLines()
        // this.drawSmoothLines(true)
    }
    animation() {
        let { AniPoints, AniShadows, ctx, width, height } = this
        this.points.push(AniPoints.shift())
        this.shadows.push(AniShadows.shift())
        ctx.clearRect(0, 0, width, height)
        this.drawXLine()
        this.drawxLabels()
        this.drawYLabels()
        this.drawSmoothLines()
        this.drawSmoothLines(true)

        window.requestAnimationFrame(() => {
            if (AniPoints.length) {
                this.animation()
            }
        })
    }
    /** 横向底部信息 */
    drawxLabels() {
        let { labels, padding: { left, bottom }, gap, ctx, height, margin } = this
        labels.forEach((item, index) => {
            ctx.fillStyle = '#555'
            ctx.font = '10px Arial'
            let x = gap * index + left - 10 + margin
            let y = height - bottom + 16
            ctx.fillText(item, x, y)
        })
    }
    /** 横轴线 */
    drawXLine() {
        let { axias: { max, show, number }, padding: { left, right, top }, width, contentHight, ctx } = this
        if (!show) { return }
        let g = max / (number - 1) * (contentHight / max)
        this.yGap = g
        ctx.save()

        for (let i = 0; i < number; i++) {
            let endPoint = Number((i * g + top).toFixed(0)) + 0.5
            ctx.beginPath()
            ctx.moveTo(left, endPoint)
            ctx.lineTo(width - right, endPoint)
            ctx.strokeStyle = '#ddd'
            ctx.lineWidth = 1
            ctx.stroke()
            ctx.closePath()
        }

        ctx.restore()
    }
    /** 竖轴线 */
    drawYLabels() {
        let { axias: { max, show, number }, padding: { left, top }, ctx } = this
        if (!show) { return }
        let g = max / (number - 1)
        let toLeft = {
            1: 0,
            2: 8,
            3: 16
        }
        ctx.save()
        for (let i = 0; i < number; i++) {
            let text = (max - i * g).toFixed(0)
            ctx.fillStyle = '#555'
            ctx.font = '14px Arial'
            // console.log(top + i * this.yGap)
            ctx.textAlign = 'end'
            ctx.textBaseline = 'middle'
            ctx.fillText(text, left - 8, top + i * this.yGap + 0.5)
        }
        ctx.restore()
    }
    /** 横向间隔 */
    setGap() {
        let { contentWidth, data: { length }, margin } = this
        this.gap = (contentWidth - margin * 2) / (length - 1)
    }
    /** 结合Y轴标尺,转化为px数值 */
    setPoints(isShadow) {
        let { gap, padding: { left, top }, axias: { max }, data, contentHight, margin, shadowX } = this
        return data.map((item, index) => {
            return [index * gap + left + margin, contentHight - (item * contentHight / max) + top + (isShadow ? shadowX : 0)]
        })
    }
    /** 画线 */
    drawLines(isShadow) {
        let { ctx, points, shadows } = this
        points = isShadow ? shadows : points
        ctx.save()
        ctx.beginPath()
        ctx.moveTo(points[0][0], points[0][1])
        if (points[1]) {
            ctx.lineTo(points[1][0], points[1][1])
        }

        ctx.stroke()
        ctx.closePath()
        ctx.restore()
    }
    /** 画平滑线 */
    drawSmoothLines(isShadow) {
        let { ctx, points, drawLines, width, height, shadows, shadowWidth } = this
        points = isShadow ? shadows : points
        // ctx.clearRect(0, 0, 400, 205)
        if (points.length < 3) {
            return drawLines.call(this)
        }

        let f = 0.3
        ctx.save()
        ctx.beginPath()
        ctx.moveTo(points[0][0], points[0][1])
        let dx1 = 0
        let dy1 = 0
        let dx2 = 0
        let dy2 = 0
        let prevPoint = points[0]
        let nextPoint = null
        for (let i = 1; i < points.length; i++) {
            let currtPoint = points[i]
            nextPoint = points[i + 1]
            if (nextPoint) {
                dx2 = -(nextPoint[0] - currtPoint[0]) * f
            } else {
                dx2 = 0
                dy2 = 0
            }
            ctx.bezierCurveTo(prevPoint[0] - dx1, prevPoint[1] - dy1, currtPoint[0] + dx2, currtPoint[1] + dy2, currtPoint[0], currtPoint[1])
            dx1 = dx2
            dy1 = dy2
            prevPoint = currtPoint
        }
        let grd = ctx.createLinearGradient(0, height / 2, width, height / 2)
        grd.addColorStop(0, `rgba(14, 185, 160, ${isShadow ? 0.1 : 1})`)
        grd.addColorStop(1, `rgba(160, 209, 118, ${isShadow ? 0.1 : 1})`)
        // 将渐变赋值给线的样式
        ctx.strokeStyle = grd
        ctx.lineWidth = isShadow ? shadowWidth : 2
        ctx.stroke()
        ctx.closePath()
        ctx.restore()
    }
    download() {
        this.element.toBlob(blob => {
            console.log(blob)
            let img = new Image()
            let url = URL.createObjectURL(blob)
            document.querySelector('#img').src = url
            console.log(url)
            img.onload = function () {
                URL.revokeObjectURL(url)
            }
            img.src = url
            console.log(img)
        }, "image/png", 1)
    }
}
let data = [70, 45, 120, 100, 110, 80, 70, 102, 120, 80, 80, 90]
let line = new Line('#c1', data, {
    padding: {
        left: 40,
        right: 30,
        top: 28,
        bottom: 38
    },
    axias: { max: 200, show: true, number: 6 },
    labels: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
})
class Bg {
    /** Y轴标尺 */
    axias = { max: 100, show: true }
    constructor(dom, options) {
        let element = document.querySelector(dom)
        this.element = element
        this.ctx = this.element.getContext('2d')
        let { axias } = options
        this.axias = { ...this.axias, ...axias }
        let { width, height } = element
        this.width = width
        this.height = height
    }
}
上一篇下一篇

猜你喜欢

热点阅读