简体   繁体   English

与 d3 可重用模式的交互性

[英]Interactivity with the d3 reusable pattern

In D3:在 D3 中:
I'm trying to implement a chart using the reusable charts pattern which allows me to add events which are handled outside the actual chart.我正在尝试使用可重用图表模式来实现图表,该模式允许我添加在实际图表之外处理的事件。 For instance I have a simple barchart where I can click on each individual bar.例如,我有一个简单的条形图,我可以在其中单击每个条形图。 Now instead of just handling the click event inside the chart component I want to be able to handle it from where I create a chart instance.现在,我希望能够从创建图表实例的位置处理它,而不是仅仅处理图表组件内的点击事件。 For example I want to edit the data which corresponds to the clicked bar element or I want to update the position of some other element depending on the position of the bar I clicked on.例如,我想编辑与单击的条形元素相对应的数据,或者我想根据我单击的条形的位置更新其他元素的位置。 To keep the chart component as modular and independent as possible I don't want certain tasks to be done inside it.为了使图表组件尽可能模块化和独立,我不希望在其中完成某些任务。

I came up with a pretty straight forward solution but I feel like there must be a better solution to this.我想出了一个非常直接的解决方案,但我觉得必须有更好的解决方案。 This way I have to account for every possible event that the outside world might be interested in. JSFiddle这样我就必须考虑到外界可能感兴趣的每一个可能的事件。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;
}

Is there any kind of standard way/best practice to handle this type of situation?是否有任何标准方法/最佳实践来处理这种情况? Or is this just not really a common situation?或者这只是不是一个普遍的情况?

Any help appreciated!任何帮助表示赞赏!

What you're looking for is to create a separation between your core application code and code that can be reused.您正在寻找的是在您的核心应用程序代码和可以重用的代码之间创建分离。 This is a good idea because it keeps your application's code small, making it easier for you to change it and move it forward.这是一个好主意,因为它使您的应用程序的代码保持较小,使您更容易对其进行更改并向前推进。 In a reusable chart you want to send a very generic events, eg clicked , while in your application you need the events to be very specific to your domain, eg addSugar .在可重用图表中,您希望发送非常通用的事件,例如clicked ,而在您的应用程序中,您需要非常特定于您的域的事件,例如addSugar

There are two ingredients you need: d3.dispatch and d3.rebind .您需要两种成分: d3.dispatchd3.rebind The former helps you create a clear internal API, while the latter helps you expose the dispatched events to the outside world.前者帮助您创建清晰的内部 API,而后者帮助您将调度的事件暴露给外部世界。

Go here to find an example of this.去这里找到一个例子 What you want to look for is three things:你要寻找的是三件事:

  1. Inside the reusable chart you create a dispatcher with events you want to publish to the outside world: var myDispatch = d3.dispatch('myclick', 'mydrag')在可重用图表中,您创建一个调度程序,其中包含要发布到外部世界的事件: var myDispatch = d3.dispatch('myclick', 'mydrag')
  2. Then in your reusable chart's event handlers you publish these events like so: myDispatch.myclick(argumentsyouwanttopass)然后在可重用图表的事件处理程序中发布这些事件,如下所示: myDispatch.myclick(argumentsyouwanttopass)
  3. As a last step you make these events available to the outside: return d3.rebind(_chart, myDispatch, "on");作为最后一步,您将这些事件提供给外部: return d3.rebind(_chart, myDispatch, "on");
  4. Then you can bind to these events on the outside: myChart.on('myclick', function(){})然后就可以在外面绑定这些事件了: myChart.on('myclick', function(){})

So your example, rewritten could look like this:所以你的例子,重写可能是这样的:

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');
}

Additional note: to register multiple event handlers, you need to namespace them (like you have to for regular d3 events), eg chart.on('click.foo', handlerFoo) , chart.on('click.bar', handlerBar) etc.附加说明:要注册多个事件处理程序,您需要为它们命名(就像常规 d3 事件一样),例如chart.on('click.foo', handlerFoo) , chart.on('click.bar', handlerBar)等。

One idea to make it more reusable and applicable to any possible event is to define a handler array.使其更具可重用性和适用于任何可能的事件的一个想法是定义一个handler数组。 This is similar to d3 itself.这类似于 d3 本身。 Here is an 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;
}

You can use your chart the same way you use HTML elements.您可以像使用 HTML 元素一样使用chart

chart.on("click", clickHandler);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM