【问题标题】:Use RxJS for D3 selection events使用 RxJS 处理 D3 选择事件
【发布时间】:2016-06-17 07:37:56
【问题描述】:

我正在尝试从 d3“输入”选择中获取 Observable。我找不到正确执行此操作的方法。

例如,对于以下选择:

selection
    .attr("class", "node")
    .attr('id', (d) => d.id)

如果选择是一个 enter() 选择,我希望将点击事件作为一个 Observable。我该怎么做?

我尝试使用 fromEvent

const clickStream = Rx.Observable.fromEvent(selection[0], 'click')

这似乎应该起作用,因为 selection[0] 是一个 DOM 节点数组(对吗?)。

那么我怎样才能让这玩得很好呢?

【问题讨论】:

  • selection[0] 不只是一个 单个 DOM 节点(列表的第一个),而不是一个 DOM 节点数组吗?
  • 啊不,我的错,用 d3 试过了,你是对的。相反,您能否发布selection 的选择方式?

标签: javascript d3.js rxjs


【解决方案1】:

来自Rx.Observable.fromEvent 的文档:

用于附加监听器的 DOMElement、NodeList、jQuery 元素、Zepto 元素、Angular 元素、Ember.js 元素或 EventEmitter。

问题是我们从 d3 选择中得到的selector[0] 不是这些。它只是一个包含节点的常规数组,这让 RxJS 感到困惑。

解决方案 1

除了将 d3 选择对象传递给 Rx.Observable.fromEvent 之外,您还可以使用返回 RxJS 可以处理的内容(例如使用 jQuery 或 document.querySelectorAll('.node'))重新选择节点。

解决方案 2

或者,您可以遍历数组中的节点并将它们一一传递给 RxJS。

function d3EventObservable(selection, event) {
    //Start with an observable that will never emit
    var obs = Rx.Observable.never();
    selection.each(function() {
        //Create observables from each of the elements
        var events = Rx.Observable.fromEvent(this, event);
        //Merge the observables into one
        obs = obs.merge(events);
    });
    return obs;
}

const clickStream = d3EventObservable(selector, 'click');

【讨论】:

  • 解决方案 1 总是重新选择所有节点,对吗?所以这样我就不能使用d3的进入/退出流程。解决方案 2 确实是我想要的,但是您知道 d3 是否为每个实体使用一个侦听器进行输入选择,因为这似乎无法扩展到数千个节点。 (也为我迟到的反应感到抱歉)
  • @bryanph 我也发现解决方案 2 更好。不确定 d3 的内部结构,老实说,我什至没有太多经验。不过,如果您担心性能问题,我建议您继续创建一个包含数千个节点的测试,看看它的效果如何。
【解决方案2】:

我的用例:我是 d3/rxjs 的新手,想要这个功能。 仅当我需要处理来自事件的数据时才想使用 d3.on,但对于整个控制流/ajax 请求和其他东西,希望 Rxjs 挺身而出,并希望经常这样做,尽可能少的样板。

猴子补丁: 正如 noppa 已经提到的,Rx.Observable.fromEvent 需要一个 NodeList,它是任何 getElementBy*/querySelector* 的标准输出。

但是 d3.select/selectAll 返回 Array[Array]

确切的期望在this Rxjs code section。这只需要一个 [object NodeList],并且由于 Arrays 和 NodeList 已经有 .length 的共同点,这将适用于 Rxjs fromEvent。

这里需要一个猴子补丁,因为 gecko/v8 没有公开方法来制作自定义 NodeList 对象。

添加到 noppa 的解决方案中:

解决方案 3

function returnNodeListFrom(selected){
  if(Array.isArray(selected)){
    newArray=selected;
    newArray.toString=function(){return '[object NodeList]'};
     return newArray; 
  }else return selected;
}

那么,你就可以了

Rx.Observable.fromEvent(returnNodeListFrom(selection[0]),'click').subscribe();

解决方案 4

//Slightly better version of 3 using lodash
function returnNodeListFrom(selected){
  if(Array.isArray(selected)){
    newArray=_.flatten(selected);
    newArray.toString=function(){return '[object NodeList]'};
     return newArray; 
  }else return selected;
}

那么,你就可以了

Rx.Observable.fromEvent(returnNodeListFrom(selection),'click').subscribe();

【讨论】:

  • Monkeypatch 实际上是不需要的。如果您觉得最好的方法是首先将数组转换为 NodeList,您可以使用document fragment。 IMO 它只是打开了不必要的性能问题和错误的可能性,但它仍然是一种方法。
  • 嘿.. 是的,也调查过了.. 文档片段的问题是它从选择中分离了节点 - 例如检查该链接的选定答案上的第 3 和第 4 个 cmets - 他们建议此方法有副作用,并且这些节点上的所有未来访问都将失败。这意味着,您的退出/删除和进入/追加不会像您想要的那样干净。同样,我的用例是能够在 Rx 和 D3 之间切换 - Rx 用于油门/去抖动等,d3 仅用于数据操作。
【解决方案3】:

我最终采纳了 noppa 的部分建议。 在输入选择开始时,我将输入选择标志设置为类

selection
.attr("class", "node")
.classed('enter-selection', true) // for rxjs..

然后我使用 querySelectorAll 选择对应的回车选择:

const domNodes = document.querySelectorAll('.node.enter-selection')

然后我订阅我需要的事件,例如:

const domNodes = document.querySelectorAll('.node.enter-selection')

最后在输入选择结束时,我删除了类标志:

selection.classed('enter-selection', false)

【讨论】:

    猜你喜欢
    • 2022-01-13
    • 2018-01-11
    • 1970-01-01
    • 2019-04-07
    • 1970-01-01
    • 2015-04-13
    • 1970-01-01
    • 1970-01-01
    • 2021-09-25
    相关资源
    最近更新 更多