简介

在前面的章节里面大概介绍了新设计的思路,在这里展开进行讨论两种交互写法:

  • 触发和反馈写到一起
  • 触发和反馈分开写

实现

本章选择了几种常见的交互作为示例,来探讨两种实现的优劣

都写到一起

active

active 的场景非常简单,所以写到一起更加合适

  1. G2.registerInteraction('active', {
  2. init(chart) {
  3. this.chart = chart;
  4. this.initEvents(chart)
  5. },
  6. initEvents(chart) {
  7. chart.on('element:mouseenter', Util.bind(this, 'onEnter'));
  8. chart.on('element:mouseenter', Util.bind(this, 'onEnter'));
  9. },
  10. onEnter(ev) {
  11. const element = ev.element;
  12. element.setState('active', true);
  13. },
  14. onOut(ev) {
  15. const element = ev.element;
  16. element.setState('active', false);
  17. },
  18. destroy() {
  19. this.chart.off('element:mouseenter', Util.bind(this, 'onEnter'));
  20. this.chart.off('element:mouseenter', Util.bind(this, 'onEnter'));
  21. }
  22. });
  • 写到一起时,不需要全局的 stateManager 管理状态量,直接调用 element 的方法即可
  • 触发形式只有一种的情况下,这种方案非常合适也很好理解

    highlight

    highlight 比较复杂一些,需要高亮对应的节点,也需要将其他节点变暗,实现代码如下:
    1. G2.registerInteraction('highlight', {
    2. init(chart) {
    3. this.chart = chart;
    4. this.initEvents(chart)
    5. },
    6. initEvents(chart) {
    7. chart.on('element:mouseenter', Util.bind(this, 'onEnter'));
    8. chart.on('element:mouseleave', Util.bind(this, 'onOut'));
    9. },
    10. onEnter(ev) {
    11. const element = ev.element;
    12. element.setState('active', true);
    13. const allElements = this.chart.getAllElements();
    14. allElements.forEach(el => {
    15. if (el !== element) {
    16. el.setState('dark', true);
    17. }
    18. });
    19. },
    20. onOut(ev) {
    21. const element = ev.element;
    22. element.setState('active', false);
    23. const allElements = this.chart.getAllElements();
    24. allElements.forEach(el => {
    25. if (el !== element) {
    26. el.setState('dark', false);
    27. }
    28. });
    29. },
    30. destroy() {
    31. this.chart.off('element:mouseenter', Util.bind(this, 'onEnter'));
    32. this.chart.off('element:mouseleave', Util.bind(this, 'onOut'));
    33. }
    34. });

selected

selected 同 active 类似,但是需要考虑单选、多选、取消选中的场景:

  1. G2.registerInteraction('selected', {
  2. init(chart) {
  3. this.chart = chart;
  4. this.initEvents(chart)
  5. },
  6. initEvents(chart) {
  7. chart.on('element:click', Util.bind(this, 'onClick'));
  8. },
  9. onClick(ev) {
  10. const element = ev.element;
  11. // 清理状态
  12. if (element.hasState('selected')) {
  13. element.setState('selected', false);
  14. } else {
  15. // 清理原先选中的元素
  16. const elements = chart.getAllElements();
  17. elements.forEach(el => {
  18. if (el.hasState('selected')) {
  19. el.setState('selected', false);
  20. }
  21. });
  22. element.setState('selected', true);
  23. }
  24. },
  25. destroy() {
  26. this.chart.off('element:click', Util.bind(this, 'onClick'));
  27. }
  28. });

tooltip

G2 中的 tooltip 主要有两中方式:

  • 根据 x 轴的位置,批量显示数据
  • 鼠标移动到的元素上显示数据

首先看一下批量显示数据

  1. G2.registerInteraction('shared-tooltip', {
  2. init(chart) {
  3. this.chart = chart;
  4. this.initEvents(chart)
  5. },
  6. initEvents(chart) {
  7. chart.on('mousemove', Util.bind(this, 'onMove'));
  8. },
  9. onMove(ev) {
  10. const elements = chart.getSnapElements({x: ev.x, y: ev.y}); // 虚拟的方法,获取逼近的元素
  11. const items = [];
  12. elements.forEach(el => {
  13. const model = el.getModel(); // 获取 element 的数据
  14. const originData = model.origin;
  15. items.push({
  16. title: originData.x,
  17. name: 'xxx',
  18. value: 'xxx'
  19. });
  20. // element.setState('active', true);
  21. // 或者 element.getTooltipItems();
  22. // 也可以作为 interaction 的回调函数 this.itemFormatter(record,index);
  23. });
  24. // 这个接口怎么设计
  25. // tooltip 是否要
  26. chart.showTooltip({
  27. x: ev.x,
  28. y: ev.y,
  29. items: items
  30. });
  31. },
  32. destroy() {
  33. this.chart.off('mousemove', Util.bind(this, 'onMove'));
  34. }
  35. });
  36. // 外面使用 tooltip 时:
  37. chart.interaction('shared-tooltip', {
  38. itemFormatter(record, index) {
  39. return { name: 'xxx', value: 'xxx' }; // 这里面需要思考的是如何拿到这一项的 color
  40. }
  41. });
  • 直接显示对应图形的 tooltip 也类似,这里面有一些问题需要解决:

    • tooltip 信息在不同的图表上有不同的显示,可能要针对不同的图表分别写 interaction,line-tooltip,pie-tooltip 等,是否需要一个通用的 tooltip
    • tooltip 信息如何让用户自定义,之前是通过注册到 geometry 上的,而现在则是用户在图表的外层实现
    • tooltip 显示时同时需要高亮 element,而 shared-tooltip 的场景下默认的 active 无法高亮默认的多个 element。可以禁用 active 的 interaction,而在 tooltip 的 interaction 中进行 active 操作,但是这就导致一个交互做了多件事,同一个反馈在多个交互里面都需要实现,难免会出现冲突。

      legend

      我们这里考虑三个 legend 上的交互,
  • 一个是在legend 上过滤数据,

  • 第二个是在 legend hover 时高亮对应的图形
  • 第三个是图形 active 时高亮对应的 legend 选项
  1. 数据过滤:
    1. G2.registerInteraction('legend-category-filter', {
    2. init(chart) {
    3. this.chart = chart;
    4. this.initEvents(chart)
    5. },
    6. initEvents(chart) {
    7. chart.on('legend:chenge', Util.bind(this, 'onClick'));
    8. },
    9. onClick(ev) {
    10. const legendItem = ev.item;
    11. const legend = ev.legend;
    12. const field = legendItem.field;
    13. const value = legendItem.value;
    14. const checked = legendItem.checked;
    15. const elements = chart.getAllElements();
    16. elements.forEach(el => {
    17. const record = el.getModel().origin; // 还没设计好如何定义
    18. if (record[field] === value) {
    19. if (checked) {
    20. element.show();
    21. } else {
    22. element.hide();
    23. }
    24. }
    25. });
    26. },
    27. destroy() {
    28. this.chart.off('legend:chenge', Util.bind(this, 'onClick'));
    29. }
    30. });
  • 图例需要自己实现勾选功能,然后通过 change 事件同 interaction 中交互,并没有解开 legend 和 chart 的耦合,后面会提供解开耦合的版本,但是实现上更加复杂。
  • 如果图例要实现单选功能,也可以通过设置element 的 show,hide
  • 这种方式存在的很大一个问题:无法同其他的过滤方式整合,例如默认情况下已经有几个类别被过滤,过滤数据不仅仅是图形隐藏,还会牵扯到数据 scale 范围的变化。
  1. legend 的 hover 时高亮
    1. G2.registerInteraction('legend-category-active', {
    2. init(chart) {
    3. this.chart = chart;
    4. this.initEvents(chart)
    5. },
    6. initEvents(chart) {
    7. chart.on('legend-item:mouseenter', Util.bind(this, 'onEnter'));
    8. chart.on('legend-item:mouseout', Util.bind(this, 'onOut'));
    9. },
    10. onEnter(ev) {
    11. const legendItem = ev.item;
    12. this.setActive(legendItem, true);
    13. },
    14. onOut(ev) {
    15. const legendItem = ev.item;
    16. this.setActive(legendItem, false);
    17. },
    18. setActive(legendItem, active) {
    19. const chart = this.chart;
    20. const field = legendItem.field;
    21. const value = legendItem.value;
    22. const checked = legendItem.checked;
    23. if (checked) { // 只有 checked 的才会 active
    24. const elements = chart.getAllElements();
    25. elements.forEach(el => {
    26. const record = el.getModel().origin; // 还没设计好如何定义
    27. if (record[field] === value) {
    28. element.setState('active', active);
    29. }
    30. });
    31. }
    32. },
    33. destroy() {
    34. this.chart.off('legend-item:mouseenter', Util.bind(this, 'onEnter'));
    35. this.chart.off('legend-item:mouseout', Util.bind(this, 'onOut'));
    36. }
    37. });
  • legend item 上的 hover 效果需要 legend 组件自己维护,也需要自己监听 mouseenter,mouseleave 时间,同时在 chart 上 emit 出来。
  • element 的 active 再次出现,多个 interaction 都来操作 element 的 active 时肯定会发生冲突
  1. 图形 active 时高亮对应的 legend 选项
    1. G2.registerInteraction('active-legend', {
    2. init(chart) {
    3. this.chart = chart;
    4. this.initEvents(chart)
    5. },
    6. initEvents(chart) {
    7. chart.on('element:mouseenter', Util.bind(this, 'onEnter'));
    8. chart.on('element:mouseleave', Util.bind(this, 'onOut'));
    9. },
    10. onEnter(ev) {
    11. const element = ev.element;
    12. element.setState('active', true);
    13. this.setLegendActive(element, true);
    14. },
    15. onOut(ev) {
    16. const element = ev.element;
    17. element.setState('active', false);
    18. this.setLegendActive(element, false);
    19. },
    20. setLegendActive(element, active) {
    21. const chart = this.chart;
    22. const record = element.getModel().origin;
    23. const legends = chart.getLegends(); // 有多个图例
    24. legends.forEach(legend => {
    25. // 也应该判定 legend 的类型是否是分类,如果是连续,需要另一种实现
    26. const field = legend.get('field'); // 这里怎么存储 field 可以再设计一下
    27. const items = legend.getItems();
    28. items.forEach(item => {
    29. const value = item.value;
    30. if (record[field] === value) {
    31. item.active = true;
    32. legend.updateItem(item); // 更新图例项
    33. }
    34. });
    35. });
    36. },
    37. destroy() {
    38. this.chart.off('element:mouseenter', Util.bind(this, 'onEnter'));
    39. this.chart.off('element:mouseleave', Util.bind(this, 'onOut'));
    40. }
    41. });
  • element 的 active 设置再次出现,而且都是 mouseeneter 和 mouseleave 导致的
  • legend 的事件最好在 chart 上使用委托的机制绑定,图表的生命周期内会创建和销毁图例

    小结

    把事件的触发和图表的响应都写到一起的方案,通过上面的几个示例可以看到其优点:

  • 实现简单,仅需要处理绑定事件、响应事件即可

  • 易于理解

但是同时我们也发现了这种方案的弊端:

  • active、selected 等常见的反馈到处需要设置,逻辑重复,可能会出现冲突的的情况
  • 不同 interaction 之间不知道彼此做的事情,很容易出现同时操作单个图形、组件的情况,例如:给折线图写了一个 tooltip 的交互,也给柱状图写了一个,同时触发交互时,仅有一个 tooltip 这时候后出现者会覆盖前面的交互。

这个方案问题的本质其实是复用和耦合的冲突,要解决这个问题需要:

  • 相同的行为只有一个入口
  • 可以监听其他交互的行为

那就来看第二个方案

触发和反馈分开

这种方案的思路是一些反馈不仅仅在一个场景用得到,例如:

  • 图表元素的 active 有多种情况会发生,鼠标移动到一个图形上,显示 tooltip 时,hover 到 legend 项上时。
  • 图表元素的 selected 可能会是用户点击导致,也可能通过程序设置。
  • 过滤在多种情况下发生,而触发过滤和响应过滤本质上时一体的,例如点击图例项会导致图表发生过滤,而其他情况引起过滤后,图例项也需要做出响应。

将这些通用的交互单独实现,其他交互中可以调用的方案有多种,但是一种解耦最好的方案是使用一个通用的状态管理控制器: StateManager

  1. class StateManager extends EventEmiter {
  2. setState(name, value) { // 设置状态,同时触发 'name' + change 的事件
  3. const originValue = this.states[name];
  4. this.states[name] = value;
  5. // 这个地方其实可以做 orginValue 和 value 是否相等的判定
  6. // 可能两者是复杂的对象,判定比较复杂
  7. this.emit(name + 'change', { name: name, value: value, originValue: originValue));
  8. },
  9. getState(name) {
  10. return this.states[name];
  11. }
  12. }

通用的交互 active, selected 等不再自己触发,而是监听状态量的变化,进行相应。

active

  1. G2.registerInteraction('element-active', {
  2. init(chart) {
  3. this.stateManager = chart.getStateManager();
  4. this.initEvents()
  5. },
  6. initEvents(chart) {
  7. const stateManager = this.stateManager;
  8. stateManager.on('activeElementschange', Util.bind(this, 'onChange'));
  9. },
  10. onChange(ev) {
  11. const value = ev.value;
  12. const activedElements = originValue;
  13. // 如果存在之前激活的元素
  14. activedElements && activedElements.forEach(el => {
  15. el.setState('active', false);
  16. });
  17. const elements = value.elements;
  18. // 如果 elements = null 这时候也会清理掉原先 active 的元素,会实现取消所有 active 的效果
  19. // 这是一种互斥的方案,在很多编程中会变得更加简单
  20. elements && elements.forEach(el => {
  21. el.setState('active', true);
  22. });
  23. }
  24. destroy() {
  25. this.stateManager.off('activeElements', Util.bind(this, 'onChange'));
  26. }
  27. });
  28. // 外面需要调用
  29. stateManager.setState('activeElements', [element1, element2]);
  30. // 清理所有的 active
  31. stateManager.setState('activeElements', null);

如果这时候我们想实现 hover 的 active 效果,则需要定义一个新的 interaction

  1. G2.registerInteraction('active', {
  2. init(chart) {
  3. this.chart = chart;
  4. this.stateManager = chart.getStateManager();
  5. },
  6. initEvents(chart) {
  7. chart.on('element:mouseenter', Util.bind(this, 'onEnter'));
  8. chart.on('element:mouseenter', Util.bind(this, 'onEnter'));
  9. },
  10. onEnter(ev) {
  11. const element = ev.element;
  12. this.stateManager.setState('activeElments', [element]);
  13. },
  14. onOut(ev) {
  15. this.stateManager.setState('activeElements', null);
  16. },
  17. destroy() {
  18. this.chart.off('element:mouseenter', Util.bind(this, 'onEnter'));
  19. this.chart.off('element:mouseenter', Util.bind(this, 'onEnter'));
  20. }
  21. });
  • 跟原先的代码相比变化不大,但是增加了一个 stateManager 和 状态量的概念,易理解带来困难
  • 也可以在业务代码中监听 activeElementschange ,显示被激活元素的信息

highlight

highlight 由于还牵扯到另一种元素的状态 dark ,可以先定义 dark 的交互,其他交互也可以复用

  1. G2.registerInteraction('element-dark', {
  2. init(chart) {
  3. this.stateManager = chart.getStateManager();
  4. this.initEvents()
  5. },
  6. initEvents(chart) {
  7. const stateManager = this.stateManager;
  8. stateManager.on('darkElementschange', Util.bind(this, 'onChange'));
  9. },
  10. onChange(ev) {
  11. const value = ev.value;
  12. const activedElements = originValue;
  13. // 如果存在之前变暗的元素
  14. activedElements && activedElements.forEach(el => {
  15. el.setState('dark', false);
  16. });
  17. const elements = value.elements;
  18. // 如果 elements = null 这时候也会清理掉原先 dark 的元素,会实现取消所有 dark 的效果
  19. // 这是一种互斥的方案,在很多编程中会变得更加简单
  20. elements && elements.forEach(el => {
  21. el.setState('dark', true);
  22. });
  23. }
  24. destroy() {
  25. this.stateManager.off('darkElementschange', Util.bind(this, 'onChange'));
  26. }
  27. });
  28. // 外面需要调用
  29. stateManager.setState('darkElements', [element1, element2]);
  30. // 清理所有的 active
  31. stateManager.setState('darkElements', null);
  • 我们会发现 active, dark 的状态完全一样,可以通过一套代码来实现 ```javascript G2.createElementInteraction = function(stateName) { G2.G2.registerInteraction(‘element-‘ + stateName, {
    1. ....
    }); }

G2.createElementInteraction(‘active’); G2.createElementInteraction(‘dark’);

// and so on

  1. ```javascript
  2. G2.registerInteraction('highlight', {
  3. init(chart) {
  4. this.chart = chart;
  5. this.stateManager = chart.getStateManager();
  6. this.initEvents(chart)
  7. },
  8. initEvents(chart) {
  9. chart.on('element:mouseenter', Util.bind(this, 'onEnter'));
  10. chart.on('element:mouseleave', Util.bind(this, 'onOut'));
  11. },
  12. onEnter(ev) {
  13. const element = ev.element;
  14. this.stateManager.setState('activeElements', element);
  15. const allElements = this.chart.getAllElements();
  16. const darkElments = [];
  17. allElements.forEach(el => {
  18. if (el !== element) {
  19. darkElments.push(el);
  20. }
  21. });
  22. this.stateManager.setState('darkElements', darkElments);
  23. },
  24. onOut(ev) {
  25. this.stateManager.setState('darkElements', null);
  26. this.stateManager.setState('activeElements', null);
  27. },
  28. destroy() {
  29. this.chart.off('element:mouseenter', Util.bind(this, 'onEnter'));
  30. this.chart.off('element:mouseleave', Util.bind(this, 'onOut'));
  31. }
  32. });

selected

在前面我们已经定义了 active, dark 的元素状态,这里仅需要定义 selected 状态即可

  1. G2.createElementInteraction('selected'); // 定义新的元素状态量

监听 click 时间,选中元素

  1. G2.registerInteraction('click-selected', {
  2. init(chart) {
  3. this.chart = chart;
  4. this.stateManager = chart.getStateManager();
  5. this.initEvents(chart)
  6. },
  7. initEvents(chart) {
  8. chart.on('element:click', Util.bind(this, 'onClick'));
  9. },
  10. onClick(ev) {
  11. const element = ev.element;
  12. stateManager = this.stateManager;
  13. // 仅考虑单选,如果已经选中,则清理状态
  14. // 如果支持多选,这里复杂一些
  15. if (element.hasState('selected')) {
  16. stateManager.setState('selectedElements', null);
  17. } else {
  18. // 清理原先选中的元素
  19. stateManager.setState('selectedElements', [element]);
  20. }
  21. },
  22. destroy() {
  23. this.chart.off('element:click', Util.bind(this, 'onClick'));
  24. }
  25. });
  • 由于清理逻辑已经在之前的 interaction 中支持,这里的代码会精简很多

    tooltip

    我们可以将显示 tooltip 这个行为也实现成一个交互,批量调用个单个调用都可以复用这个交互

    1. G2.registerInteraction('show-tooltip', {
    2. init(chart) {
    3. this.stateManager = chart.getStateManager();
    4. this.initEvents()
    5. },
    6. initEvents(chart) {
    7. const stateManager = this.stateManager;
    8. stateManager.on('tooltipElementschange', Util.bind(this, 'onChange'));
    9. },
    10. onChange(ev) {
    11. const elements = ev.value;
    12. const items = [];
    13. elements.forEach(el => {
    14. const model = el.getModel(); // 获取 element 的数据
    15. const originData = model.origin;
    16. items.push({
    17. title: originData.x,
    18. name: 'xxx',
    19. value: 'xxx'
    20. });
    21. });
    22. // 这个接口怎么设计
    23. // tooltip 是否要
    24. this.chart.showTooltip({
    25. x: ev.x,
    26. y: ev.y,
    27. items: items
    28. });
    29. }
    30. destroy() {
    31. this.stateManager.off('tooltipElementschange', Util.bind(this, 'onChange'));
    32. }
    33. });
  • 这里仅仅显示 tooltip 的信息,而不要进行 active 设置

同时显示多个元素的信息

  1. G2.registerInteraction('shared-tooltip', {
  2. init(chart) {
  3. this.chart = chart;
  4. this.stateManager = chart.getStateManager();
  5. this.initEvents(chart)
  6. },
  7. initEvents(chart) {
  8. chart.on('mousemove', Util.bind(this, 'onMove'));
  9. // 移出画布是 tooltip 消失的逻辑也要加
  10. },
  11. onMove(ev) {
  12. const elements = chart.getSnapElements({x: ev.x, y: ev.y}); // 虚拟的方法,获取逼近的元素
  13. this.stateManager.setState('tooltipElements', elements);
  14. this.stateManager.setState('activeElements', elements); // 同时 active 元素
  15. },
  16. destroy() {
  17. this.chart.off('mousemove', Util.bind(this, 'onMove'));
  18. }
  19. });

移动到单个图形上显示 tooltip

  1. G2.registerInteraction('-tooltip', {
  2. init(chart) {
  3. this.chart = chart;
  4. this.stateManager = chart.getStateManager();
  5. this.initEvents(chart)
  6. },
  7. initEvents(chart) {
  8. chart.on('mouseenter', Util.bind(this, 'onEnter'));
  9. chart.on('mouseleave', Util.bind(this, 'onOut'));
  10. },
  11. onEnter(ev) {
  12. const element = ev.element;
  13. this.stateManager.setState('tooltipElements', [element]);
  14. this.stateManager.setState('activeElements', [element]); // 同时 active 元素
  15. },
  16. onOut(ev) {
  17. this.stateManager.setState('tooltipElements', null);
  18. this.stateManager.setState('activeElements', null);
  19. },
  20. destroy() {
  21. this.chart.off('mousemove', Util.bind(this, 'onMove'));
  22. this.chart.off('mouseleave', Util.bind(this, 'onOut'));
  23. }
  24. });

legend

我们在这里同样实现常见的三种交互:

  • 一个是在legend 上过滤数据,
  • 第二个是在 legend hover 时高亮对应的图形
  • 第三个是图形 active 时高亮对应的 legend 选项
  1. 数据过滤:

首先我们可以定义 filter-element 的交互和定义 filterElements 的状态量

  1. G2.createElementInteraction('filtered');

通过 legend 过滤图形

  1. G2.registerInteraction('legend-category-filter', {
  2. init(chart) {
  3. this.chart = chart;
  4. this.stateManager = chart.getStateManager();
  5. this.initEvents(chart)
  6. },
  7. initEvents(chart) {
  8. chart.on('legend:chenge', Util.bind(this, 'onClick'));
  9. },
  10. onClick(ev) {
  11. const legend = ev.legend;
  12. const field = legend.field;
  13. const items = legend.getItems();
  14. const elements = chart.getAllElements();
  15. const filetedElements = [];
  16. elements.forEach(el => {
  17. const record = el.getModel().origin; // 还没设计好如何定义
  18. items.forEach(item => {
  19. if (!item.checked && element[field] == item.value){
  20. filetedElements.push(element);
  21. }
  22. });
  23. });
  24. this.stateManager.setState('filetedElements', filetedElements);
  25. },
  26. destroy() {
  27. this.chart.off('legend:chenge', Util.bind(this, 'onClick'));
  28. }
  29. });
  • 这种写法性能会变差,因为需要遍历所有 Legend 的选项
  1. legend 的 hover 时高亮
    1. G2.registerInteraction('legend-category-active', {
    2. init(chart) {
    3. this.chart = chart;
    4. this.stateManager = chart.getStateManager();
    5. this.initEvents(chart)
    6. },
    7. initEvents(chart) {
    8. chart.on('legend-item:mouseenter', Util.bind(this, 'onEnter'));
    9. chart.on('legend-item:mouseout', Util.bind(this, 'onOut'));
    10. },
    11. onEnter(ev) {
    12. const legendItem = ev.item;
    13. this.setActive(legendItem, true);
    14. },
    15. onOut(ev) {
    16. const legendItem = ev.item;
    17. this.setActive(legendItem, false);
    18. },
    19. setActive(legendItem, active) {
    20. const chart = this.chart;
    21. const field = legendItem.field;
    22. const value = legendItem.value;
    23. const checked = legendItem.checked;
    24. const activeElements = [];
    25. if (checked) { // 只有 checked 的才会 active
    26. const elements = chart.getAllElements();
    27. elements.forEach(el => {
    28. const record = el.getModel().origin; // 还没设计好如何定义
    29. if (record[field] === value) {
    30. activeElements.push(el);
    31. }
    32. });
    33. }
    34. this.stateManager.setState('activeElements', activeElements);
    35. },
    36. destroy() {
    37. this.chart.off('legend-item:mouseenter', Util.bind(this, 'onEnter'));
    38. this.chart.off('legend-item:mouseout', Util.bind(this, 'onOut'));
    39. }
    40. });
  • 这个实现看上去同第一种写法没什么差别,仅仅是屏蔽了如何进行 active
  1. hover 图表的图形,legend 高亮

代码略,整体来看跟前面的写法差异也不大

小结

将触发和反馈分离可以得到一些好处:

  • 常用的反馈可以集中编写,提升代码复用率,反馈的实现方式对上层屏蔽,便于后面修改
  • 可以在业务代码中监听反馈,而不管是如何触发的
  • 也可以同一个反馈使用不同的交互实现,仅需要按需加载即可

同时也带了一些问题:

  • 用户需要更多的知识,需要理解每个状态量的定义(可以是复杂对象)
  • 有些交互编写起来更复杂一些

总结

本章把 G2 现在支持的一些交互,按照两种方式分别实现出来,进行了一些对比,但这两种设计仅仅将图表绘制和交互的耦合解开,并不能称得上是图形语法,真正的图形语法会在后面的章节中详细的讲解,让用户定制交互更加轻松简单。