zoukankan      html  css  js  c++  java
  • 打造自己的图表控件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()

    效果

    下载

  • 相关阅读:
    网页设计~老生常谈~浏览器兼容2个主要问题的解决
    谈谈网页功能测试
    从PMP学习中浅谈公司行政工作
    肉肉谈对需求设计的想法到底是功能驱动界面?还是界面驱动功能?
    jndi和rmi学习
    mysql赋值变量:=的使用
    用Cookies和HashTable制作购物车
    nginx实现简单的反向代理
    .net Form认证扩展保存 Object 类型
    基于Docker搭建私有镜像仓库
  • 原文地址:https://www.cnblogs.com/cuifeipeng/p/7728222.html
Copyright © 2011-2022 走看看