与 d3 可重用模式的交互

Interactivity with the d3 reusable pattern

在 D3 中:
我正在尝试使用 reusable charts pattern 实现图表,它允许我添加在实际图表之外处理的事件。例如,我有一个简单的条形图,我可以在其中单击每个单独的条形图。现在,我希望能够从创建图表实例的地方处理它,而不是仅仅处理图表组件内的点击事件。例如,我想编辑与单击的栏元素相对应的数据,或者我想根据我单击的栏的位置更新其他元素的位置。为了尽可能保持图表组件的模块化和独立性,我不希望在其中完成某些任务。

我提出了一个非常直接的解决方案,但我觉得必须有更好的解决方案。这样我就必须考虑到外界可能感兴趣的每一个可能发生的事件。JSFiddle

function clickHandler(d, i) {
  // do something with the element/event/data
}

var svg = d3.select('#container')
  .append('svg');

var myChart = chart()
    .onClick(clickHandler);

svg.append('g')
  .datum(data)
  .call(chart);

function chart() {
  var onClick = function() {};

  function _chart(selection) {
    selection.each(function(d, i) {

      selection.selectAll('rect')
        .data(d)
        .enter()
        .append('rect')
        .attr('x', 0)
        .attr('y', function(d, i) {return i * 11;})
        .attr('width', function(d, i) {return d;})
        .attr('height', 10);
        .on('click', _onClick);

    });
  }

  function _onClick(d, i) {
    d3.select(this)
      .attr('fill', 'red');

    onClick(d, i);
  }

  _chart.onClick = function(_) {
    if(!arguments.length) return onClick;
    onClick = _;
    return _chart;
  }

  return _chart;
}

有没有什么标准的way/best做法可以处理这种情况?或者这真的不是很常见的情况?

感谢任何帮助!

使它更具可重用性和适用于任何可能事件的一个想法是定义一个 handler 数组。这类似于 d3 本身。这是一个updated fiddle

function chart() {
  var event_handlers = {}; //key value pairs

  function _chart(selection) {
    selection.each(function(d, i) {

      var rects = selection.selectAll('rect')
        .data(d)
        .enter()
        .append('rect')
        .attr('x', 0)
        .attr('y', function(d, i) {return i * 11;})
        .attr('width', function(d, i) {return d;})
        .attr('height', 10);

        for(var event in event_handlers){
          rects.on(event, event_handlers[event])
        }

    });
  }

  _chart.on = function(event, fun) {
    if(arguments.length==1) return event_handlers[event];
    event_handlers[event] = fun;
    return _chart;
  }

  return _chart;
}

您可以像使用 HTML 元素一样使用 chart

chart.on("click", clickHandler);

您正在寻找的是在核心应用程序代码和可重复使用的代码之间创建一个分离。这是一个好主意,因为它使您的应用程序代码保持较小,使您更容易更改它并向前推进。在可重复使用的图表中,您希望发送非常通用的事件,例如clicked,而在您的应用程序中,您需要事件非常特定于您的域,例如addSugar.

您需要两种原料:d3.dispatch and d3.rebind。前者帮助你创建一个清晰的内部API,而后者帮助你将派发的事件暴露给外界

到此处查找 example。你要找的是三样东西:

  1. 在可重复使用的图表中,您创建了一个调度程序,其中包含要发布到外部世界的事件:var myDispatch = d3.dispatch('myclick', 'mydrag')
  2. 然后在您的可重用图表的事件处理程序中,您可以像这样发布这些事件:myDispatch.myclick(argumentsyouwanttopass)
  3. 作为最后一步,您可以向外部公开这些事件:return d3.rebind(_chart, myDispatch, "on");
  4. 然后你可以在外面绑定这些事件:myChart.on('myclick', function(){})

因此,您的示例重写后可能如下所示:

function chart() {
  var dispatch = d3.dispatch('click');

  function _chart(selection) {
    selection.each(function(d, i) {

      selection.selectAll('rect')
        .data(d)
        .enter()
        .append('rect')
        .attr('x', 0)
        .attr('y', function(d, i) {return i * 11;})
        .attr('width', function(d, i) {return d;})
        .attr('height', 10);
        .on('click', dispatch.click);

    });
  }

  return d3.rebind(_chart, dispatch, 'on');
}

附加说明:要注册多个事件处理程序,您需要为它们命名空间(就像您必须为常规 d3 事件一样),例如chart.on('click.foo', handlerFoo)chart.on('click.bar', handlerBar)