Antv G2 修改 Brush 默认行为为返回时间戳范围
G2 brush 时间轴,而不是筛选数据点
一、背景
目前 G2 使用 brush-x 筛选后的是对应的点,而不是 X 轴的时间范围。在实际使用过程中,我们需要场景如下
- 鼠标筛选一个区域
- 获取这个区域的开始时间和结束时间
- 以第2步获取到的时间范围作为结果来重新获取数据
二、核心代码一览
2.1 注册 Action
src/index.ts
,中枚举出了可以使用的 Action
1 2 3
| registerAction('brush', DataRangeFilter); registerAction('brush-x', DataRangeFilter, { dims: ['x'] }); registerAction('brush-y', DataRangeFilter, { dims: ['y'] });
|
2.2 filter 处理逻辑
- 看下 filter 流程
src/interaction/action/data/range-filter.ts
- 获取到用户当前选择的视觉点
- 转换视觉点,获取到实际选择的 min value 和 max value,并且生成 filter
- 根据 filter 进行数据筛选
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public filter() { let startPoint; let currentPoint; const view = this.context.view; const coord = view.getCoordinate(); const normalCurrent = coord.invert(currentPoint); const normalStart = coord.invert(startPoint); if (this.hasDim('x')) { const xScale = view.getXScale(); const filter = getFilter(xScale, 'x', normalCurrent, normalStart); this.filterView(view, xScale.field, filter); } this.reRender(view); }
|
- 看下如何获取到 min value 和 max value
src/interaction/action/data/range-filter.ts
- 获取到的 minValue 和 maxValue 取整后就是时间戳了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| function getFilter(scale: Scale, dim: string, point1: Point, point2: Point): FilterCondition { let min = Math.min(point1[dim], point2[dim]); let max = Math.max(point1[dim], point2[dim]); const [rangeMin, rangeMax] = scale.range; if (min < rangeMin) { min = rangeMin; } if (max > rangeMax) { max = rangeMax; } if (min === rangeMax && max === rangeMax) { return null; } const minValue = scale.invert(min); const maxValue = scale.invert(max);
if (scale.isCategory) { const minIndex = scale.values.indexOf(minValue); const maxIndex = scale.values.indexOf(maxValue); const arr = scale.values.slice(minIndex, maxIndex + 1); return (value) => { return arr.includes(value); }; } else { return (value) => { return value >= minValue && value <= maxValue; }; } }
|
我们可以在用户定义的 action 上下文里拿到对应的 rangefilter 实例
1
| ctx.actions.find(v => v.name === 'brush-x')
|
但同时我们也看到,我们需要的 minValue 和 maxValue 都是作为临时计算的产物,并没有挂在对象实例上,所以我们有以下三条路
- 获取到 ctx 后,自己重新计算
- 修改源码,把这个临时状态挂在对象上。不过需要重新发包,或者把代码纳入版本库?侵入性强,不便于后期升级,还是算了吧
- 再去看看其他方案吧
不需要在选择上浪费太多时间,干就完了。我们先选择方案一,要是后面有更好的,再更换嘛。
2.3 根据获取到的 ctx 来计算 Min 和 Max
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
export function getBrushedTimeRange(ctx) { const self = ctx.actions.find(v => v.name === 'brush-x') const view = self.context.view
const startPoint = self.startPoint const currentPoint = ctx.getCurrentPoint()
const paddingLeft = view.padding[3] const totalWith = view.width - view.padding[1] - view.padding[3] const startX = startPoint.x - paddingLeft const endX = currentPoint.x - paddingLeft
const timestampsCount = (view.getXScale().max - view.getXScale().min) / (view.getXScale().range[1] - view.getXScale().range[0]) const perTimestampWidth = timestampsCount / totalWith
const startXTimestamp = view.getXScale().min - view.getXScale().range[0] * totalWith * perTimestampWidth const startTime = startXTimestamp + startX * perTimestampWidth const endTime = startXTimestamp + endX * perTimestampWidth
return startTime < endTime ? [startTime, endTime] : [endTime, startTime] }
|
在brush回调的地方,执行下面的动作即可,着重关注 callback
地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| registerInteraction('brushX', { showEnable: [ { trigger: 'plot:mouseenter', action: 'cursor:crosshair' }, { trigger: 'plot:mouseleave', action: 'cursor:default' } ], start: [ { trigger: 'mousedown', action: ['brush-x:start', 'x-rect-mask:start', 'x-rect-mask:show'] } ], processing: [ { trigger: 'mousemove', action: ['x-rect-mask:resize'] } ], end: [ { trigger: 'mouseup', action: ['brush-x:end', 'x-rect-mask:end', 'x-rect-mask:hide'], callback: ctx => { const [startTime, endTime] = getBrushedTimeRange(ctx)
this.$emit('on-brushed', [startTime, endTime]) this.resetBrushAction = ctx.actions.find(v => v.name === 'brush-x') } } ], rollback: [ { trigger: 'dblclick', action: ['brush-x:reset', 'reset-button:hide'] }, { trigger: 'reset-button:click', action: ['brush-x:reset', 'reset-button:hide'] } ] })
|
三、踩坑方案
3.1 直接使用 brush-filter 导致的 scalex 上下文传递不一致问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
function hackGetFilterReturnMinMax(scale, dim, point1, point2) { let min = Math.min(point1[dim], point2[dim]) let max = Math.max(point1[dim], point2[dim]) const [rangeMin, rangeMax] = scale.range if (min < rangeMin) { min = rangeMin } if (max > rangeMax) { max = rangeMax } if (min === rangeMax && max === rangeMax) { return null }
const minValue = scale.invert(min) const maxValue = scale.invert(max)
return { minValue, maxValue } }
|
3.2 区分 view 的几个视角
四、一些链接
https://antv-g2.gitee.io/zh/examples/interaction/others#views-tooltip