打造自己的图表控件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坐标轴

点击下载

 

posted @ 2017-10-16 15:51  长蘑菇星人  阅读(185)  评论(0编辑  收藏  举报