简介
Element 的交互可以分为:
- 状态变换的交互,例如: active, selected
- 显示隐藏
- 拖拽、框选
- 编辑
-
Element Action
基本的 Action
基本的 Action 有:
ElementVisible
- ElementActive
- ElementSelected
- ElementMove
- ElementDelegation
- ElementResize
- ElementFilter
- ElementCombine
- ElementDelete
Action 的扩展
每个 Action 可以扩展出更多的 Action,适应不同类型的 Element
class ElementAction extends Action {
show() {}
hide() {}
remove() {}
}
class ElementResize extends Action {
start() {}
resize() {}
end() {}
reset() {}
}
// 扩展新的 Action
class BubbleResize extends ElementResize {
}
class ElementSelected extends Action {
selected() {}
clear() {}
}
// 扩展饼图选中
class PieSelected extends ElementSelected {
selected() {}
clear() {}
}
状态变化
active
Element 的 active 有三种形态:
- 单个 Element active
- 多个 Element 一起 active
- 部分高亮,部分变暗
class ElementActiveAction extends Action {
getActiveElements() {}
active() {
// context
const activeElements = this.getActiveElements()
activeElements.forEach(el => {
el.setState('active', true);
});
}
reset() {
const activeElements = context.view.get('activeElements');
activeElements.forEach(el => {
el.setState('active', false);
});
}
}
class ElementHiglightAction extends ElementActiveAction {
highlight() {
const activeElements = this.getActiveElements()
activeElements.forEach(el => {
el.setState('active', true);
});
}
reset() {}
}
G2.RegisterAction('A', ElementActiveAction);
hover 到 element ,单个 element active
G2.RegisterInteraction('element-active', {
start: [
{trigger: 'element:mouseenter', action: 'ElementActive:active'}
],
end: [
{trigger: 'element:mouseleave', action: 'element-active:reset'}
]
});
hover 到 axis label 上,对应的 element 高亮
G2.RegisterInteraction('element-axis-active', {
start: [
{trigger: 'axis-label:mouseenter', action: 'element-active:active'}
],
end: [
{trigger: 'axis-label:mouseleave', action: 'element-active:reset'}
]
});
hover 到 legend 上,对应的 element 高亮
G2.RegisterInteraction('element-legend-active', {
start: [
{trigger: 'legend-item:mouseenter', action: 'element-active:active'}
],
end: [
{trigger: 'legend-item:mouseleave', action: 'element-active:reset'}
]
});
hover 到 element 上 highlight
G2.RegisterInteraction('element-higlight', {
start: [
{trigger: 'element:mouseenter', action: 'element-highlight:highlight'}
],
end: [
{trigger: 'element:mouseleave', action: 'element-highlight:reset'}
]
});
hover 到 legend 上
G2.RegisterInteraction('element-legend-higlight', {
start: [
{trigger: 'legend-item:mouseenter', action: 'element-higlight:active'}
],
end: [
{trigger: 'legend-item:mouseleave', action: 'element-higlight:reset'}
]
});
鼠标附近 x 轴上的多个 elmement active
鼠标附近多个 elmement 高亮,需要定义一个新的 Action
class IntervalActiveByX extends Action {
active() {}
reset() {}
}
// 可以随便命名
class PointActiveByXY extends Action {
active() {}
reset() {}
}
G2.RegisterAction('interval-active-by-x', IntervalActiveByX);
G2.RegisterAction('point-active-by-xy', IntervalActiveByX);
G2.RegisterInteraction('element-active-by-x', {
start: [
{trigger: 'view:plotenter', action: 'interval-active-by-x:active'}
],
processing: [
{trigger: 'view:plotmove', action: 'interval-active-by-x:active'}
],
end: [
{trigger: 'view:plotenter', action: 'interval-active-by-x:reset'}
]
});
x,y 最近的 Element 高亮
G2.RegisterInteraction('element-active-by-x', {
start: [
{trigger: 'view:plotenter', action: 'point-active-by-xy:active'}
],
processing: [
{trigger: 'view:plotmove', action: 'point-active-by-xy:active'}
],
end: [
{trigger: 'view:plotenter', action: 'point-active-by-xy:reset'}
]
});
interval 相同颜色的高亮
class IntervalActiveByColor extends Action {
active() {}
reset() {}
}
G2.RegisterInteraction('element-active-by-x', {
start: [
{trigger: 'element:mouseenter', action: 'interval-active-by-color:active'}
],
end: [
{trigger: 'element:mouseleave', action: 'interval-active-by-color:reset'}
]
});
selected
元素的选中包括:
- 单选
- 多选
- 框选
class ElementSinlgeSelectedAction extends Action {
selected() {}
cancel() {}
}
class ElementMultipleSelectedAction extends Action {
selected() {}
unselected() {}
reset() {}
}
class ElementRangeSelectedAction extends Action {
start() {}
selected() {}
reset() {}
}
class ElementMaskAction extends Action {
start() {}
resize() {}
hide() {}
}
class ElementDelegationAction extends Action {
show() {}
move() {}
hide() {}
}
G2.registerAction('element-single-selected', ElementSinlgeSelectedAction);
G2.registerAction('element-multiple-selected', ElementMultipleSelectedAction);
G2.registerAction('element-mask', ElementRangeSelectedAction);
点击单选 selected,可取消
G2.RegisterInteraction('element-selected',{
start: [
{trigger: 'element:click', action: 'element-single-selected:selected'}
],
end: [
{trigger: 'element:click', action: 'element-single-selected:reset'}
]
});
点击单选,点击空白取消选中
G2.RegisterInteraction('element-selected',{
start: [
{trigger: 'element:click', action: 'element-single-selected:selected'}
],
end: [
{trigger: 'view:click', isEnable(context) {
return !context.event.element; // 未点击到时取消
}, action: 'element-single-selected:reset'}
]
});
多选,再次点击取消
G2.RegisterInteraction('element-multiple-selected', {
start: [
{trigger: 'element:click', action: 'element-multiple-selected:selected'}
],
end: [
{trigger: 'element:click', action: 'element-multiple-selected:unselected'}
]
});
框选,无 mask,中间过程无选中
G2.RegisterInteraction('element-multiple-selected', {
start: [
{trigger: 'view:mousedowm', action: 'element-range-selected:start'}
],
end: [
{trigger: 'view:mouseup', action: 'element-range-selected:selected'}
]
});
框选,无 mask,中间过程可选中
G2.RegisterInteraction('element-multiple-selected', {
start: [
{trigger: 'view:mousedowm', action: 'element-range-selected:start'}
],
processing: [
{trigger: 'view:mousemove', action: 'element-multiple-selected:selected'}
],
end: [
{trigger: 'view:mouseup', action: 'element-range-selected:selected'}
],
rollback: [
{trigger: 'view:click', action: 'element-range-selected:reset'}
]
});
框选,有 mask,中间过程可选中
G2.RegisterInteraction('element-multiple-selected', {
start: [
{trigger: 'view:mousedowm', action: 'element-range-selected:start'}
{trigger: 'view:mousedowm', action: 'element-mask:start'}
],
processing: [
{trigger: 'view:mousemove', action: 'element-multiple-selected:selected'},
{trigger: 'view:mousemove', action: 'element-mask:resize'}
],
end: [
{trigger: 'view:mouseup', action: 'element-range-selected:selected'},
{trigger: 'view:mouseup', action: 'element-mask:hide'}
],
rollback: [
{trigger: 'view:click', action: 'element-range-selected:reset'}
]
});
显示隐藏
Element 的显示隐藏一般发生在过滤时,如果发生数据过滤,则会整体更新,所以这里并不是数据过滤,而是组件导致的图形过滤。
class ElementFilterAction extends Action {
filter() {}
reset() {}
}
class ElementFilterByXXXAction extends Action {
filter() {}
reset() {}
}
class ElementRangeFilterAction extends Action {
filter() {}
reset() {}
}
通过图例过滤元素
G2.RegisterInteraction('element-legend-filter', {
showEnable: [
{trigger: 'legend-item:mouseenter', action: 'cursor:pointer'},
{trigger: 'legend-item:mouseleave', action: 'cursor:default'}
],
start: [
{trigger: 'legend-item:checkedchanged', action: 'element-filter:filter'}
],
rollback: [
{trigger: 'legend-item:dblclick', action: 'element-filter:reset'}
]
});
通过连续图例过滤
G2.RegisterInteraction('element-legend-slider-filter', {
start: [
{trigger: 'legend:valuechanged', action: 'element-filter:filter'}
]
});
通过 axis 上的 label 过滤
G2.RegisterInteraction('element-axis-filter', {
showEnable: [
{trigger: 'axis-label:mouseenter', action: 'cursor:pointer'},
{trigger: 'axis-label:mouseleave', action: 'cursor:default'}
],
start: [
{trigger: 'axis-label:checkedchanged', action: 'element-filter:filter'}
],
rollback: [
{trigger: 'axis-label:dblclick', action: 'element-filter:reset'}
]
});
通过框选过滤
G2.RegisterInteraction('element-axis-filter', {
showEnable: [
{trigger: 'view:mouseenter', action: 'cursor:cross'},
{trigger: 'view:mouseleave', action: 'cursor:default'}
],
start: [
{trigger: 'view:mousedown', action: 'element-range-filter:start'},
{trigger: 'view:mousedown', action: 'element-delegation:start'}
],
processing: [
{trigger: 'view:mousemove', action: 'element-delegation:resize'}
],
end: [
{trigger: 'view:mouseup', action: 'element-range-filter:filter'},
{trigger: 'view:mouseup', action: 'element-delegation:hide'}
],
rollback: [
{trigger: 'view:dblclick', action: 'element-range-filter:reset'}
]
});
拖拽&框选
class ElementMove extends Action {
start() {}
move() {}
end() {}
cancel() {}
}
拖拽
拖拽移动
G2.RegisterInteraction('element-drag-move', {
showEnable: [
{trigger: 'element:mouseenter', action: 'cursor:move'},
{trigger: 'element:mouseleave', action: 'cursor:default'},
],
start: [
{trigger: 'element:dragstart', action: 'element-move:start'},
],
processing: [
{trigger: 'element:drag', action: 'element-move:move'},
],
end: [
{trigger: 'element:dragend', action: 'element-move:end'},
],
rollback: [
{trigger: 'element:dblclick', action: 'element-move:reset'},
]
});
拖拽合并
class ElementCombineAction extends Action {
from() {}
to() {}
combine() {}
cancel() {}
reset() {}
}
G2.RegisterAction('element-combine', ElementCombineAction);
G2.RegisterInteraction('element-drag-move', {
showEnable: [
{trigger: 'element:mouseenter', action: 'cursor:move'},
{trigger: 'element:mouseleave', action: 'cursor:default'},
],
start: [
{trigger: 'element:dragstart', action: 'element-combine:from'},
{trigger: 'element:dragstart', action: 'element-delegation:show'},
],
processing: [
{trigger: 'element:dragenter', action: 'element-combine:to'},
{trigger: 'element:drag', action: 'element-delegation:move'},
],
end: [
{trigger: 'element:drop', action: 'element-combine:combine'},
{trigger: 'element:dragend', action: 'element-delegation:hide'},
{trigger: 'element:dragend', isEnable(context) {
return !context.event.element; // 没有drop 到其他 element 时取消
}, action: 'element-combine:cancel'},
],
rollback: [
{trigger: 'element:dblclick', action: 'element-move:reset'},
]
});
拖拽删除
class ElementDeleteAction extends Action {
start() {}
delete() {}
reset() {}
}
G2.RegisterInteraction('element-drag-move', {
showEnable: [
{trigger: 'element:mouseenter', action: 'cursor:move'},
{trigger: 'element:mouseleave', action: 'cursor:default'},
],
start: [
{trigger: 'element:dragstart', action: 'element-delegation:show'},
{trigger: 'element:dragstart', action: 'element-delete:start'},
],
processing: [
{trigger: 'element:drag', action: 'element-delegation:move'},
],
end: [
{trigger: 'element:dragend', action: 'element-delegation:hide'},
{trigger: 'recycle:drop', action: 'element-delete:delete'},
],
rollback: [
{trigger: 'recycle-icon:dblclick', action: 'element-delete:reset'},
]
});
拖拽排序
class ElementDragInsert extends Action {
start() {}
insert() {}
reset() {}
}
G2.RegisterInteraction('element-drag-move', {
showEnable: [
{trigger: 'element:mouseenter', action: 'cursor:move'},
{trigger: 'element:mouseleave', action: 'cursor:default'},
],
start: [
{trigger: 'element:dragstart', action: 'element-delegation:show'},
{trigger: 'element:dragstart', action: 'element-drag-insert:start'},
],
processing: [
{trigger: 'element:drag', action: 'element-delegation:move'},
],
end: [
{trigger: 'element:dragend', action: 'element-delegation:hide'},
{trigger: 'element:dragend', action: 'element-drag-insert:insert'},
],
rollback: [
{trigger: 'element:dblclick', action: 'element-delete:reset'},
]
});
拖拽变大、变小
不同 Geometry 的改变大小的方式并不一致,可以定义一个 Action ,也可以拆分成多个,这里我们按照多个来实现:
class LineResizeAction extends Action {
start() {}
resize() {}
end() {}
reset {}
}
class PointResizeAction extends Action {
start() {}
resize() {}
end() {}
reset {}
}
class IntervalResizeAction extends Action {
start() {}
resize() {}
end() {}
reset {}
}
class ElementReizeIcon extends Action {
show() {}
move() {}
hide() {}
}
G2.RegisterInteraction('bubble-drag-resize', {
showEnable: [
{trigger: 'point:mouseeneter', action: 'element-resize-icon:show'},
{trigger: 'point:mouseleave', action: 'element-resize-icon:hide'},
],
start: [
{trigger: 'element-resize-icon:dragstart', action: 'point-resize:start'}
],
processing: [
{trigger: 'element-resize-icon:drag', action: 'element-resize-icon::move'},
{trigger: 'element-resize-icon:drag', action: 'point-resize:resize'}
],
end: [
{trigger: 'element-resize-icon:dragend', action: 'point-resize:end'}
],
rollback: [
{trigger: 'point:dblclcik', action: 'point-resize:reset'}
]
});
按键
通过按键 active
编辑
输入
弹框
快捷键
复制、粘贴、剪切
总结
在写这些交互语法的时候一些感觉:
- 一些交互过程中辅助展示的的 Action 是非常容易复用的,例如: Mask, delegation 等
- 一些交互可以由多种触发源时,要么拆成多个Action,要么在 Action 内部判定不同的触发源
- 交互触发的条件,可以在多个位置判定,但是需要让使用者有清晰的概念
- 所有的交互都能转换成 trigger 和 Action,但是有一定的耦合,用户使用时往往需要查看 Action 的详细描述