打造自己的图表控件5
上一次加入了鼠标拖拽功能,这回加入我来加入一个区域框,并且框内数据的阴影,效果如下
具体实现思路就是,可以响应鼠标事件来移动和调整大小,并且获取覆盖的数据,画出阴影.
响应鼠标事件可以改造上次写的鼠标移动的功能
区域框的设计为2部分,一部分是上下的区域,用来移动整个区域框,一部分为左右的线,用来改变区域框的大小.
鼠标在这两个部分上点击时才会捕获鼠标事件.
具体实现如下
class VerticalRangeNavigation extends MouseNavigation { constructor() { super() this.start = [] this.draging = false this.screen = [] this.range = [] this._area = { "topBar":"topBar", "bottomBar":"bottomBar", "left":"left", "right":"right", "none":"none" } this.barSize = 20 this.hitTarget = this._area.none this.color = "#FF00FF" } setRange(from, to){ this.range = [from , to] } hit(mouseEventArgs) { if (this.draging) return true let e = mouseEventArgs if (e.area != this.area) return false if(this._getHitArea(mouseEventArgs) != this._area.none) return true return false } onMouseDown(mouseEventArgs) { let e = mouseEventArgs e.prevent() let ieLeftDown = this._isLeftDown(e) if (ieLeftDown) { this._startDrag(e) this.hitTarget = this._getHitArea(mouseEventArgs) } } _getHitArea(mouseEventArgs){ let e = mouseEventArgs let x1 = this.viewport.transformX(this.range[0],e.screen) let x2 = this.viewport.transformX(this.range[1],e.screen) let offset = 5 if(e.x > Math.min(x1 ,x2) && e.x < Math.max( x1 ,x2) && e.y > e.screen[1] && e.y < e.screen[1] + this.barSize) return this._area.topBar if(e.x > Math.min(x1 ,x2) && e.x < Math.max( x1 ,x2) && e.y > e.screen[1] + e.screen[3] - this.barSize && e.y < e.screen[1] + e.screen[3] ) return this._area.bottomBar if (Math.abs(e.x - x1) < offset) return this._area.left if (Math.abs(e.x - x2) < offset) return this._area.right return this._area.none } _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 width = this.screen[2] let height = this.screen[3] var deltaX = e.x - startX let visibleLeft = this.viewport.visible[0] let visibleWidth = this.viewport.visible[2] - visibleLeft var offsetX = deltaX / width * visibleWidth switch (this.hitTarget){ case this._area.left: this.range[0] += offsetX break; case this._area.right: this.range[1] += offsetX break; case this._area.topBar: case this._area.bottomBar: this.range[0] += offsetX this.range[1] += offsetX break; default: break; } this.start = [e.x, e.y] } let target = this.hitTarget if (target == this._area.none){ target = this._getHitArea(mouseEventArgs) } switch (target){ case this._area.left: mouseEventArgs.cursor = "w-resize" break; case this._area.right: mouseEventArgs.cursor = "e-resize" break; case this._area.topBar: case this._area.bottomBar: mouseEventArgs.cursor = "move" break; default: break; } } onMouseUp(mouseEventArgs) { let e = mouseEventArgs if (this.draging) { e.prevent() this._stopDrag() this.hitTarget = this._area.none } } }
基本上就是完全拷贝了之前的 MouseNavigationDrawing .加入了鼠标捕获区域的判定.鼠标移动时改变区域的位置.
但是现在的区域框是看不到的.所以还要实现显示的功能,
之前我有 CanvasDrawingElement 类型,所以继承它的类都有 render 方法进行绘图,可是js并不支持多继承,也没有接口.
不过并不妨碍我们使用 render , 我们稍稍改造一下绘制的逻辑,在里面判断有没有 render 方法,只要有 render 都进行绘制就可以了.
一下是实现绘制的方法,这样区域框就可以被显示出来了.
class VerticalRangeNavigation extends MouseNavigation { ... render(context, [left, top, width, height]) { context.strokeStyle = this.color let x1 = this.viewport.transformX( this.range[0],[left, top, width, height]) let x2 = this.viewport.transformX( this.range[1],[left, top, width, height]) context.beginPath() context.moveTo(x1, top) context.lineTo(x1 , top + height) context.stroke() context.beginPath() context.moveTo(x2, top) context.lineTo(x2 , top + height) context.stroke() context.fillStyle = this.color context.fillRect(x1, top, x2 - x1,this.barSize) context.fillRect(x1, top + height - this.barSize , x2 - x1, this.barSize) } }
鸭子万岁.
现在已经可以实现移动的功能了,再加入数据阴影的功能.
class VerticalRangeNavigation extends MouseNavigation { ... _getAreaData(){ let elements = [...this.chart.getElements()] || [] let result = [] for (let element of elements.filter(e => Array.isArray(e.data) )) { let rangeData = [] for (let i = 0; i < element.data.length / 2; i++) { let x = element.data[2 * i] let y = element.data[2 * i + 1] let x1 = this.range[0] let x2 = this.range[1] if(x > Math.min(x1,x2) && x < Math.max(x1,x2) ){ rangeData.push(x) rangeData.push(y) } } result.push({data:rangeData,color:element.color}) } return result } render(context, [left, top, width, height]) { let rangeDatas = this._getAreaData() for (let index = 0; index < rangeDatas.length; index++) { const rangeData = rangeDatas[index] let points = this.viewport.transform(rangeData.data, [left, top, width, height]) let centerY = this.viewport.transformY(0, [left, top, width, height]) for (let j = 0; j < points.length / 2; j++) { const x = points[j * 2]; const y = points[j * 2 + 1]; context.strokeStyle = rangeData.color || "#FF0000" context.beginPath() context.moveTo(x, y) context.lineTo(x , centerY) context.stroke() } } ... } }
最后的效果
收工