博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
打造自己的图表控件3
阅读量:5089 次
发布时间:2019-06-13

本文共 8609 字,大约阅读时间需要 28 分钟。

上期实现了数据投影的功能,现在就可以来实现坐标轴了。

以前只是整个画板范围内进行绘制,现在如果要进行坐标轴绘制,就要给画板分不同区域。

const ChartArea = {    plot: 'plot',    xAxis: 'xAxis',    yAxis: 'yAxis',}

然后给 ChartElement 添加一个 area 属性 默认绘制到 plot 上

class ChartElement {    ...    get area() {        return ChartArea.plot    }    ...  }

然后实现一个 Axis ,area 默认到 xAxis,ticks 是刻度值的列表,labels 是刻度值字符串形式的列表。

range 获得现在视场内值的范围。

class Axis extends ChartElement {    constructor() {        super()        this._ticks = []        this._labels = []        this.ticksCount = 20        this.ticksLength = 10    }    get area() {        return ChartArea.xAxis    }    get ticks() {        return this._ticks    }    get labels() {        return this._labels    }    get range() {        var viewport = this.viewport        if (this.area == ChartArea.yAxis) {            return [viewport.visible[1], viewport.visible[3]]        } else {            return [viewport.visible[0], viewport.visible[2]]        }    }
      calcTicks() { }
}

由于坐标轴有不同的表示方法,有的用时间表示,有的用离散的点表示,有的用连续的值表示。

这里只实现连续的值画刻线的方法。 

创建一个FloatAxis 类,继承Axis,然后实现 calcTicks 方法

class FloatAxis extends Axis {    constructor() {        super()    }    calcTicks() {        let range = this.range        let delta = range[1] - range[0]        let log = Math.round(Math.log10(delta))        let min, max        if (log > 0) {            let pow = Math.pow(10, log - 1)            min = Math.round(range[0] / pow) * pow            max = Math.round(range[1] / pow) * pow        } else {            min = Math.round(range[0] * Math.pow(10, -log)) / Math.pow(10, -log)            max = Math.round(range[1] * Math.pow(10, -log)) / Math.pow(10, -log)        }        let calcStep = (max - min) / this.ticksCount        let step        if (log > 0) {            let pow = Math.pow(10, log - 1)            step = Math.round(calcStep / pow) * pow        } else {            step = Math.round(calcStep * Math.pow(10, -log)) / Math.pow(10, -log)        }        if (step == 0) step = calcStep        let ticks = []        let labels = []        let end = max + step        let x = min        while (x < end) {            ticks.push(x)            labels.push(this.formatLabel(x, log - 2))            x += step        }        this._ticks = ticks        this._labels = labels    }    formatLabel(value, log) {        if (log < 0) {            return value.toFixed(-log)        } else {            return ~~value + ""        }    }}

 到这里 刻度 已经可以被计算出来了。

 一共有两个坐标轴 X轴 和 Y 轴,于是 创建两个类,表示这两个轴

class FloatHorizontalAxis extends FloatAxis {    constructor() {        super()    }    get area() {        return ChartArea.xAxis    }}class FloatVerticalAxis extends FloatAxis {    constructor() {        super()    }    get area() {        return ChartArea.yAxis    }}

坐标转换在 Viewport 中实现,所以为Viewport 添加两个方法

class Viewport {    ...    transformX(x, [left, top, width, height]) {        let visibleLeft = this.visible[0]        let visibleWidth = this.visible[2] - visibleLeft        let screenLeft = left        let screenWidth = width        return screenLeft + (x - visibleLeft) / visibleWidth * screenWidth    }    transformY(y, [left, top, width, height]) {        let visibleBottom = this.visible[1]        let visibleHeight = this.visible[3] - visibleBottom        let screenTop = top        let screenHeight = height        return screenTop + screenHeight - (y - visibleBottom) / visibleHeight * screenHeight    }}

 这里基础已经构建完毕,开始实现画图的部分。

class VerticalAxisDrawing extends FloatVerticalAxis {    constructor() {        super()    }    render(context, [left, top, width, height]) {        context.beginPath()        context.moveTo(left + width, top)        context.lineTo(left + width, height)        context.stroke()        this.calcTicks()        let ticks = this.ticks        let labels = this.labels        let ticksLength = this.ticksLength        context.save()        context.font = '14px sans-serif'        let x = left + width - ticksLength        for (let i = 0, length = ticks.length; i < length; i++) {            let y = this.viewport.transformY(ticks[i], [left, top, width, height])            context.beginPath()            context.moveTo(x, y)            context.lineTo(x + ticksLength, y)            context.stroke()            context.fillText(labels[i], x - ticksLength - labels[i].length * 5, y + 7)        }        context.restore()    }}
class HorizontalAxisDrawing extends FloatHorizontalAxis {    constructor() {        super()    }    render(context, [left, top, width, height]) {        context.beginPath()        context.moveTo(left, top)        context.lineTo(left + width, top)        context.stroke()        this.calcTicks()        let ticks = this.ticks        let labels = this.labels        let ticksLength = this.ticksLength        context.save()        context.font = '14px sans-serif'        let y = top        for (let i = 0, length = ticks.length; i < length; i++) {            let x = this.viewport.transformX(ticks[i], [left, top, width, height])            context.beginPath()            context.moveTo(x, y)            context.lineTo(x, y + ticksLength)            context.stroke()            context.fillText(labels[i], x - labels[i].length * 4, y + ticksLength + 14)        }        context.restore()    }}

距离成功只有一步了,现在开始改造 CanvasDrawing

现在整个图像被分成3个部分,plot,xAxis,yAxis,所以给 CanvasDrawing 添加一个screens属性,表示不同的区域

class CanvasDrawing {    constructor(width, height) {        ...        this.screens = {            [ChartArea.plot]: [50, 0, width - 50, height - 50],            [ChartArea.xAxis]: [50, height - 50, width - 50, 50],            [ChartArea.yAxis]: [0, 0, 50, height - 50],        }    }    ...}

再添加获取要绘制的区域和尺寸的方法

class CanvasDrawing {    ...    getScreen(area) {        return this.screens[area]    }    * getArea() {        yield ChartArea.xAxis        yield ChartArea.yAxis        yield ChartArea.plot    }     ...  }

 然后实现一个过滤的方法获取在某区域内要绘制的元素

class CanvasDrawing {    ...    * getElements(chart, area) {        var elements = chart.elements || []        for (let element of elements.filter(e => e.area == area && this.isDrawElement(e))) {            yield element        }    }    isDrawElement(element) {        return [CanvasDrawingElement, HorizontalAxisDrawing, VerticalAxisDrawing]            .some(type => element instanceof type)    }}

为了能提高一点点性能,创建一个背景画布,先画到背景画布上,然后在画到要显示的画布上。

class CanvasDrawing {    constructor(width, height) {        var canvas = this.canvas = document.createElement("canvas")        var view = this.view = document.createElement("canvas")        canvas.width = width        canvas.height = height        view.width = width        view.height = height        this.width = width        this.height = height        this.context = canvas.getContext("2d")        this.viewContext = view.getContext("2d")        ...    }    init(dom) {        dom.appendChild(this.view)    }   }

最后 实现 renderChart 方法

class CanvasDrawing {    ...    renderChart(chart) {        let context = this.context        context.save()        context.fillStyle = "#ffffff"        context.fillRect(0, 0, this.width, this.height)        context.restore()        for (let area of this.getArea()) {            let screen = this.getScreen(area)            for (let element of this.getElements(chart, area)) {                context.save()                element.render(context, screen)                context.restore()            }            this.viewContext.drawImage(this.canvas, ...screen, ...screen)        }    }    ...}

调用

var width = 800var height = 600var dataCount = 1000var chart = new Chart()chart.viewport.setVisible(0, -2, dataCount, 2)chart.add(new VerticalAxisDrawing())chart.add(new HorizontalAxisDrawing())var chartDrawing = new CanvasDrawing(width, height)chartDrawing.init(document.body)var lines = [];for (let index = 0; index < 50; index++) {    var lineDrawing = new LineDrawing()    chart.add(lineDrawing)    lines.push(lineDrawing);}var step = 0var begintime = +new Date()var count = 0function run() {    requestAnimationFrame(run)    var now = +new Date()    count = ((count + 1) % 16)    if (count == 0) {       console.log( ~~(1000 / (now - begintime)))    }    begintime = now    step += 1    chart.viewport.setVisible(step, -1 * lines.length, dataCount + step, 1 * lines.length)    for (let j = 0; j < lines.length; j++) {        let lineDrawing = lines[j]        lineDrawing.data = []        for (let i = 0; i < dataCount; i++) {            lineDrawing.data.push(i + step)            lineDrawing.data.push((j+1) * Math.sin((step + i) * (360 * 4 / width) * Math.PI / 180))        }    }    chartDrawing.renderChart(chart)}run()

效果

 

转载于:https://www.cnblogs.com/cuifeipeng/p/7698760.html

你可能感兴趣的文章
iOS开发个人独立博客收集
查看>>
JSONArray和JSONObject的简单使用
查看>>
北京大片《全球热死》正在上映!
查看>>
多进程复习
查看>>
Centos 安装golang beego
查看>>
JavaScript内置对象 以及和 内置对象相关的语法
查看>>
framespacing="10"和border="10"在frameSet中有什么区别?
查看>>
JavaScript中的字符串连接
查看>>
函数定义的三种方式
查看>>
【Java设计模式】java单例模式
查看>>
[HNOI2007]分裂游戏
查看>>
iframe标签用法详解
查看>>
Ubuntu下第一个C程序的成功运行
查看>>
一、架构设计的内容
查看>>
转:Spring-session & redis 子域名共享session
查看>>
11.推送到远程仓库
查看>>
poj3614 Sunscreen(贪心+STL)
查看>>
webNav
查看>>
rand()函数的用法
查看>>
Tesseract-OCR4.0识别中文与训练字库实例
查看>>