打造自己的图表控件4

上一期已经完成了一个雏形。可以显示一个完整的折线图。本期给图表加上鼠标互动的功能。

首先新增一个专门处理鼠标事件的类。

class MouseNavigation extends ChartElement {
    constructor() {
        super()
    }
    get area() {
        return ChartArea.plot
    }
    hit(mouseEventArgs) {
        return false
    }
    onMouseDown(mouseEventArgs) {

    }
    onMouseMove(mouseEventArgs) {

    }
    onMouseUp(mouseEventArgs) {

    }
    onMouseWheel(mouseEventArgs) {

    }
}

hit 用来测试是否命中 

MouseEventArgs 定义如下,封装了鼠标相对于图表的位置 x,y. 触发事件时的 位置 和 屏幕尺寸,还有原始的事件对象。

class MouseEventArgs {
    constructor(x, y, area, screen, e) {
        this.x = x
        this.y = y
        this.area = area
        this._isPrevent = false
        this.event = e
        this.screen = screen
    }
    get isPrevent() {
        return this._isPrevent
    }
    prevent() {
        this._isPrevent = true
    }
}

 然后改造一下 Chart ,让它可以触发这些事件

class Chart {
    ...
    mouseDown(mouseEventArgs) {
        this._raiseMouseEvent('onMouseDown', mouseEventArgs)
    }
    mouseMove(mouseEventArgs) {
        this._raiseMouseEvent('onMouseMove', mouseEventArgs)
    }
    mouseUp(mouseEventArgs) {
        this._raiseMouseEvent('onMouseUp', mouseEventArgs)
    }
    mouseWheel(mouseEventArgs) {
        this._raiseMouseEvent('onMouseWheel', mouseEventArgs)
    }
    * _hits(mouseEventArgs) {
        let list = []
        for (let element of this.elements) {
            if (element instanceof MouseNavigation) {
                if (element.hit(mouseEventArgs)) {
                    list.push(element)
                }
            }
        }
        list = list.reverse()
        for (let element of list) {
            yield element
        }
    }
    _raiseMouseEvent(key, mouseEventArgs) {
        for (let element of this._hits(mouseEventArgs)) {          
            element[key](mouseEventArgs)
            if (mouseEventArgs.isPrevent) break
        }
    }
}

最后,在 CanvasDrawing 中注册事件

 由于在 init 中已经保存了 chart,所以 render 的时候就不用在传入 chart 了。

class CanvasDrawing {
    ...
    init(dom, chart) {
        this.chart = chart
        dom.appendChild(this.view)

        document.addEventListener("mousedown", e => {
            if (e.target == this.view) e.preventDefault() 
            var mouseEventArgs = this.createMouseEventArgs(e)
            chart.mouseDown(mouseEventArgs)
        })
        document.addEventListener("mouseup", e => {
            if (e.target == this.view) e.preventDefault()
            var mouseEventArgs = this.createMouseEventArgs(e)
            chart.mouseUp(mouseEventArgs)
        })
        document.addEventListener("mousemove", e => {
            if (e.target == this.view) e.preventDefault()
            var mouseEventArgs = this.createMouseEventArgs(e)
            chart.mouseMove(mouseEventArgs)
        })
        document.addEventListener("mousewheel", e => {
            if (e.target == this.view) e.preventDefault()
            var mouseEventArgs = this.createMouseEventArgs(e)
            chart.mouseWheel(mouseEventArgs)
        })
    }
    createMouseEventArgs(e) {
        let offsetX = this.view.offsetLeft
        let offsetY = this.view.offsetTop
        let x = e.pageX - offsetX
        let y = e.pageY - offsetY
        let area = this.findArea(x, y)
        let screen = this.getScreen(area)
        return new MouseEventArgs(x, y, area, screen, e)
    }
    renderChart() {
        let chart = this.chart
        ...
    }
    findArea(x, y) {
        for (let area of this.getArea()) {
            let [x2, y2, width, height] = this.getScreen(area)
            if (x >= x2 && x < x2 + width && y >= y2 && y < y2 + height) return area
        }
        return null
    }
    ...
}

最后实现一个 MouseNavigationDrawing 来支持拖拽和缩放。

class MouseNavigationDrawing extends MouseNavigation {
    constructor() {
        super()
        this.start = []
        this.draging = false
        this.screen = []
    }
    hit(mouseEventArgs) {
        if (this.draging) return true
        let e = mouseEventArgs
        if (e.area == this.area)
            return true
        return false
    }
    onMouseDown(mouseEventArgs) {
        let e = mouseEventArgs
        e.prevent()
        let ieLeftDown = this._isLeftDown(e)
        console.log(e)
        if (ieLeftDown) {
            this._startDrag(e)
        }
    }
    _startDrag(e) {
        this.start = [e.x, e.y]
        this.draging = true
        this.screen = e.screen
    }
    _stopDrag() {
        this.start = [0, 0]
        this.draging = false
    }
    _isLeftDown(e) {
        return e.event.button == 0
    }
    onMouseMove(mouseEventArgs) {
        let e = mouseEventArgs
        if (this.draging && this._isLeftDown(e)) {
            e.prevent()
            let startX = this.start[0]
            let startY = this.start[1]
            let width = this.screen[2]
            let height = this.screen[3]
            var deltaX = e.x - startX
            var deltaY = e.y - startY

            let visibleLeft = this.viewport.visible[0]
            let visibleBottom = this.viewport.visible[1]
            let visibleWidth = this.viewport.visible[2] - visibleLeft
            let visibleHeight = this.viewport.visible[3] - visibleBottom

            var offsetX = deltaX / width * visibleWidth
            var offsetY = deltaY / height * visibleHeight

            this.start = [e.x, e.y]
            this.viewport.offset(-offsetX, offsetY)
        }
    }
    onMouseUp(mouseEventArgs) {
        let e = mouseEventArgs
        if (this.draging) {
            e.prevent()
            this._stopDrag()
        }
    }
    onMouseWheel(mouseEventArgs) {
        let e = mouseEventArgs
        e.prevent()
        let delta = 0.1 * e.event.wheelDeltaY / 120 
this.viewport.zoom(-delta) } }

Viewport 添加 offset 和 zoom 来支持拖放 和 缩放


class Viewport {
    ...
offset(x, y) { let [fromX, fromY, toX, toY]
= this.visible this.visible = [fromX + x, fromY + y, toX + x, toY + y] } center() { let [fromX, fromY, toX, toY] = this.visible return [fromX + (toX - fromX) * 0.5, fromY + (toY - fromY) * 0.5] } zoom(delta) { let [fromX, fromY, toX, toY] = this.visible let [x, y] = this.center() let ratio = 1 + delta let left = x - (x - fromX) * ratio let right = left + (toX - fromX) * ratio let bottom = y - (y - fromY) * ratio let top = bottom + (toY - fromY) * ratio this.visible = [left, bottom, right, top] }
}

最后调用一下

    var width = 800
    var height = 600
    var dataCount = 1000
    var chart = new Chart()
   
  
    chart.add(new VerticalAxisDrawing())
    chart.add(new HorizontalAxisDrawing())
    chart.add(new MouseNavigationDrawing())
    var chartDrawing = new CanvasDrawing(width, height)
    chartDrawing.init(document.body, chart)
    var lines = []
    for (let index = 0; index < 50; index++) {
        var lineDrawing = new LineDrawing()
        chart.add(lineDrawing)
        lines.push(lineDrawing);
    }
    chart.viewport.setVisible(0, -1 * lines.length, dataCount , 1 * lines.length)
    var e = s => document.querySelector(s)

    var fps = e('#fps')
    var step = 0
    var begintime = +new Date()
    var count = 0
    function run() {
        requestAnimationFrame(run)
        var now = +new Date()
        count = ((count + 1) % 16)
        if (count == 0) {
            fps.innerHTML = ~~(1000 / (now - begintime))
        }
        begintime = now
        step += 1
        
        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)
                lineDrawing.data.push((j + 1) * Math.sin((step + i) * (360 * 4 / width) * Math.PI / 180))
            }
        }
        chartDrawing.renderChart(chart)
    }
    run()

效果

下载

posted @ 2017-10-25 12:29  长蘑菇星人  阅读(533)  评论(1编辑  收藏  举报