antvis/G2

[WIP]: 交互设计 #4208

pearmini posted onGitHub

交互

WIP

交互已经根据按照 4.0 以及山大的交互语法实现了一版,但目前还存在一些问题,这里对交互语法进行修改,目的是解决这些问题。

存在问题

  • 声明交互的方式和传统声明交互的方式差别太大。
  • Action 的拆分没有在易用性和复用性找到合适的平衡。

开始

这里用高亮交互举例子。

const options = {
  interactions: [{ type: 'highlight', stroke: 'red' }],
}

有以下实现方式:

// 交互语法
// API 形式
chart.observe('plot:pointermove')
  .pipe({ type: 'selectElements' })
  .subscribe({ type: 'highlightSelectedElements' })

// Spec 形式
const options = {
  observe: [
    {
      event: 'plot:pointermove',
      pipe: [{ type: 'selectElements' }],
      subscribe: [{ type: 'highlightSelectedElements' }],
    },
  ],
};

设计

API 形式的设计参考了 RXJS,一种声明形式处理事件的方式。

Think of RxJS as Lodash for events.

let count = 0;
const rate = 1000;
let lastClick = Date.now() - rate;
document.addEventListener('click', (event) => {
  if (Date.now() - lastClick >= rate) {
    count += event.clientX;
    console.log(count);
    lastClick = Date.now();
  }
});
import { fromEvent, throttleTime, map, scan } from 'rxjs';

fromEvent(document, 'click')
  .pipe(
    throttleTime(1000),
    map((event) => event.clientX),
    scan((count, clientX) => count + clientX, 0)
  )
  .subscribe((count) => console.log(count));

我大概描述下之前结合山大交互语法的大概想法

<img width="731" alt="image" src="https://user-images.githubusercontent.com/15646325/196868879-bf0b87c8-f392-4214-88cd-bb96fad55b12.png">

交互发生触发反馈有两种路径:

  1. 路径1(蓝色)
    • 反馈1:根据事件获取交互要操作的一些对象,存储在 context 的共享变量中
    • 反馈2:拿到context 共享变量的信息操作这些交互对象
  2. 路径2(绿色)
    • 无需获取交互操作对象,事件本身就决定了要直接去执行一些变更(包括操作对象),如:filter、rerender 等

Action 反馈,本质上也是函数,有明确的通途、输入、输出(只不过输入从 context 获取、输出是操作 context)

  • Q: 如果就是想用函数的写法,怎么做?
  • A: 支持函数方式写 action 反馈,而不是一定要类似 4.0 继承 Action 类去写一个自定义反馈.
  • Q: 如果想要复用 Action 怎么办?
  • A: 方式1: 自己抽取为通用 util, 跨文件复用. 方式2: 将自定义的 Action 注册一个名字, 然后和之前一样复用.

弊端: Action 拆分没有标准,有的时候过于细化了

内置 ElementActive

// 伪代码

// GetElements action: 出参是 shared.elements
const GetElements = (options: { elements }) => {
  return (context) => {
    // 这里可以通过任何方式去获取
    context.shared.elements = elements || [...]
  }
}

// ActiveElement action: 入参是 shared.elements
const ActiveElement = (options: { fill }) => {
  return (context) => {
    const elements = context.shared.elements;
    elements.forEach(ele => ele.style.fill = fill)
  }
}
// 注册为 library, 可以通过指定 action type 获取.
const ElementActive = () => {
  start: [
    { trigger: 'mouseenter', action: [{ type: 'getElements' }, { type: 'activeElement' }] },
  ],
  end: [
    // 
    { trigger: 'mouseleave', action: [{ type: 'getElements', elements: [] }, { type: 'activeElement' }] },
  ]
}

复写 ElementActive

const CustomGetElements = () => {
  return (context) => {
    // 监听外部 div 块的激活情况, 获取 id
    context.shared.elements = plot.querySelectorAll(`.element#${id}`);
  }
}
const CustomElementActive = () => {
  start: [
    { trigger: 'mouseenter', action: [{ type: CustomGetElements }, { type: 'activeElement' }] },
  ],
  end: [
    // 
    { trigger: 'mouseleave', action: [{ type: CustomGetElements }, { type: 'activeElement' }] },
  ]
}
posted by visiky over 2 years ago

转入到 discussion 吧~

posted by hustcc over 2 years ago

posted by pearmini over 2 years ago
posted by pearmini over 2 years ago

Fund this Issue

$0.00
Funded

Pull requests