zoukankan      html  css  js  c++  java
  • 打造自己的图表控件2

    上次写了一个简单的折线图示例,但是显示的点的个数最多等于像素点的个数,这肯定是不满足需求的。

    今天就来实现将无限的数据点投影到有限的像素上。

    首先创建一个 Viewport 类,用来描述数据的范围,及数据点到像素的转换。

    class Viewport {
        constructor() {
            this.visible = [0, 0, 0, 0]//fromX, fromY, toX, toY
        }
        setVisible(fromX, fromY, toX, toY) {
            this.visible = [fromX, fromY, toX, toY]
        }
        transform(data, [left, top, width, height]) {
            let result = []
            let length = data.length / 2
            let visibleLeft = this.visible[0]
            let visibleBottom = this.visible[1]
            let visibleWidth = this.visible[2] - visibleLeft
            let visibleHeight = this.visible[3] - visibleBottom
    
            let screenLeft = left
            let screenTop = top
            let screenWidth = width
            let screenHeight = height
    
            for (let i = 1; i < length; i++) {
                let x = screenLeft + (data[2 * i] - visibleLeft) / visibleWidth * screenWidth
                let y = screenTop + screenHeight - (data[2 * i + 1] - visibleBottom) / visibleHeight * screenHeight
                result.push(x)
                result.push(y)
            }
            return result
        }
    }

    setVisible 设置窗口上显示的数据的范围

    transform 用来将数据转换成像素点

    然后给 Chart 添加一个 viewport 属性 

    class Chart {
        constructor() {
            this.elements = []
            this._viewport = new Viewport()
        }
        get viewport() {
            return this._viewport
        }
        add(element) {
            element.attach(this)
            this.elements.push(element)
        }
        remove(element) {
            element.detach(this)
            this.elements.remove(element)
        }
    }

    给ChartElement也添加上viewport属性

    class ChartElement {
        constructor() {
            this._viewport = null
        }
        get viewport() {
            return this._viewport || this.chart && this.chart.viewport || null
        }
        set viewport(v) {
            this._viewport = v
        }
        attach(chart) {
            this.chart = chart
        }
        detach(chart) {
            this.chart = null
        }
    }

    这里,就可以使用viewport进行转换了。

    由于viewport需要知道窗口的大小和位置,所以以前 render 时只传入width和height 就不够了,这里改造一下 CanvasDrawingElement 的 render 函数来适配 viewport,别忘了要把 SampleLineDrawing 的 render 也修改一下

    class CanvasDrawingElement extends ChartElement {
        constructor() {
            super()
        }
        render(context, [left, top, width, height]) {
    
        }
    }
    class SampleLineDrawing extends CanvasDrawingElement {
    constructor() {
    super() this.data = null //[ x1,y1,x2,y2 ]
    } render(context, [left, top, width, height]) { super.render(context, width, height) if (this.data != null) { context.strokeStyle = "#FF0000" context.beginPath() context.moveTo(this.data[0], this.data[1]) let length = this.data.length / 2
                for (let i = 1; i < length; i++) { let x = this.data[2 * i] let y = this.data[2 * i + 1] context.lineTo(x, y) } context.stroke()
    } } }

    同样  CanvasDrawing 也进行修改

    class CanvasDrawing {
        constructor(width, height) {
            var canvas = this.canvas = document.createElement("canvas")
            canvas.width = width
            canvas.height = height
            this.width = width
            this.height = height
            this.context = canvas.getContext("2d")
        }
        init(dom) {
            dom.appendChild(this.canvas);
        }
        renderChart(chart) {
            let context = this.context
            context.clearRect(0, 0, this.width, this.height)
            for (let element of chart.elements) {
                if (element instanceof CanvasDrawingElement) {
                    context.save()
                    element.render(context, [0, 0, this.width, this.height])
                    context.restore()
                }
            }
        }
    }

     然后实现一个 LineDrawing 用来绘制折线图

    class LineDrawing extends CanvasDrawingElement {
        constructor() {
            super()
            this.data = null //[ x1,y1,x2,y2 ]
        }
        render(context, screen) {
            super.render(context, screen)
            let data = this.data
            let viewport = this.viewport
            if (data != null) {
                let points = viewport.transform(data, screen)
                context.strokeStyle = "#FF0000"
                context.beginPath()
                context.moveTo(points[0], points[1])
                let length = points.length / 2
                for (let i = 1; i < length; i++) {
                    let x = points[2 * i]
                    let y = points[2 * i + 1]
                    context.lineTo(x, y)
                }
                context.stroke()
            }
        }
    }

    这样就可以指定显示的范围了。

    最后测试一下

    var width = 800
    var height = 600
    var dataCount = width * 2
    var chart = new Chart()
    
    chart.viewport.setVisible(0, -2, dataCount, 2)
    
    var lineDrawing = new LineDrawing()
    chart.add(lineDrawing)
    
    var chartDrawing = new CanvasDrawing(width, height)
    chartDrawing.init(document.body)
    var step = 0
    function run() {
        requestAnimationFrame(run)
        step += 1 / 60
        lineDrawing.data = []
        for (var i = 0; i < dataCount; i++) {
            lineDrawing.data.push(i)
            lineDrawing.data.push(Math.sin(step + i * (360 * 4 / width) * Math.PI / 180))
        }
        chartDrawing.renderChart(chart)
    }
    run()

    到此结束,下期实现简单的X,Y坐标轴

    点击下载

  • 相关阅读:
    Git 远程仓库分支管理
    Git 本地仓库管理
    Git 本地仓库管理
    SQLAlchemy_定义(一对一/一对多/多对多)关系
    SQLAlchemy_定义(一对一/一对多/多对多)关系
    自动化生成 Openstack 新项目开发框架
    自动化生成 Openstack 新项目开发框架
    Python 数据结构_队列
    Python 数据结构_队列
    Python 数据结构_堆栈
  • 原文地址:https://www.cnblogs.com/cuifeipeng/p/7677286.html
Copyright © 2011-2022 走看看