d2bjs / d2b

Reusable d3-based chart library.
http://d2bjs.org
BSD 3-Clause "New" or "Revised" License
97 stars 17 forks source link

events on charts in Vue #6

Closed timelyportfolio closed 7 years ago

timelyportfolio commented 7 years ago

Are there any examples of event handling, such as click or mouseover, with vuejs charts? I looked for chart.on but it is not defined.

kevinwarne commented 7 years ago

Good question. I didn't realize this as an issue until now.

When using d2b charts without vuejs it's pretty easy to dynamically add your own event binding. Because the chart is rendered synchronously you can just programmatically setup the events after you have rendered the chart. E.g.

var sunburst = d2b.chartSunburst();
var chart = d3.select('.chart');

chart.call(sunburst);

chart.selectAll('.d2b-sunburst-arc').on('mouseover.custom', function (d) {
  // do something when a sunburst arc is moused over..
  console.log(d);
});

The vue.js components have watchers to check when the data or config have changed and then automatically call the chart updater. Probably the best way to implement this would be to have a beforeRender and rendered event hook on the vue components. So that would allow you to do something like..

<div id = 'app'>
  <sunburst-chart 
    :data = 'sunburstChartData' 
    @rendered = 'sunburstChartRendered'>
  </sunburst-chart>
</div>
var app = new Vue({
  el: '#app',
  components: {
    'sunburst-chart': d2b.vueChartSunburst
  },
  data: {
    sunburstChartData: {
      // some sunburst data
    }
  },
  methods: {
    sunburstChartRendered: function (el, chart) {
      d3.select(el).selectAll('.d2b-sunburst-arc')
        .on('mouseover.custom', function (d) {
          // do something when a sunburst arc is moused over..
          console.log(d);
        });
    }
  }
});

I'll think some more about this and work on getting something implemented tomorrow.

kevinwarne commented 7 years ago

One additional note per to your original question. I've found it difficult to add events to the d2b generators themselves. E.g. chart.on(...). This is because a generator might be used for rendering many charts and then the events could have been invoked from anyone of them.

kevinwarne commented 7 years ago

I published a d2b update this morning on v0.0.28. You should be able to bind events whenever the vueChartSunburst component emits a rendered event. I updated my previous answer to show how this can be used. The beforeRender and rendered events are passed the element node el and chart generator chart.

Another thing to note is that most d2b internal events are bound to the default event namespace. This means that if you are working with the d2b-sunburst-arc elements and you bind an event like d3.select('.d2b-sunburst-arc').on('click', ...) this will likely override the internal click event and you won't be able to click to zoom anymore. Name-spacing the event like d3.select('.d2b-sunburst-arc').on('click.custom', ...) should do the trick though.

Let me know if this is what you were looking for. Thanks!

timelyportfolio commented 7 years ago

Working through it now. Will report back. Thanks!!!

timelyportfolio commented 7 years ago

@kevinwarne, here is what I ended up doing even though the result is still not perfect.

    sunburstChartRendered: function (el, chart) {
      var that = this;
      d3.select(el).selectAll('.d2b-sunburst-chart')
        .on('mouseover', function (d) {
          if(d3.event.target.classList[0] === 'd2b-sunburst-arc'){
            that.filtered_tree = d3.select(d3.event.target).datum().data;
          }
        });

When selecting .d2b-sunburst-arc, I would lose the click.custom handler after a couple of clicks, so I chose to move the handler to the .d2b-sunburst-chart level. Here is the live example https://bl.ocks.org/timelyportfolio/8c9a77da3e9ca57827a8801fb8a16ba9.

Thanks again!

kevinwarne commented 7 years ago

@timelyportfolio This is a great example of extending the sunburst to a treemap on mouseover.

I can see why the click event is failing to work after a couple tries. It's because the internal chart zooming is actually 'exiting' nodes and then they arn't being rebound upon entering again. I'll think more about how best to achieve something like this as it is probably a common use case.

In the meantime this is a solution that would work, however it would require disabling the sunburst zooming so that nodes don't get removed..

// disable zooming on the chart
sunburstChartConfig: function () {
    chart.label(function(d){return d.name});
    chart.color(function(d){return color(d.name);})
    chart.sunburst().size(function(d){return d.x}).zoomable(false);

    // // another option if you wanted to remove the breadcrumbs  for this particular visualization
    // chart.chartFrame().breadcrumbsEnabled(false);
    // // yet another option for horizontal breadcrumbs across the top
    // chart.chartFrame().breadcrumbsOrient('top');
    // chart.breadcrumbs().vertical(false);    
}

// apply custom events
sunburstChartRendered: function (el, chart) {
  var that = this;
  d3.select(el).selectAll('.d2b-sunburst-arc')
    .on('click.custom', function (d) { // click or mouseover can be used here
      that.filtered_tree = d.data;
    });
}

Cheers!

kevinwarne commented 7 years ago

@timelyportfolio I think I've come up with a solution to this d2b application.

Any d2b generators that render content to the page will fire an event when they are applied either externally or internally. This event is bubbled up through the DOM. For example:

var sunburst = d2b.chartSunburst();
var chart = d3.select('.chart').on('breadcrumbs-updated', customizeBreadcrumbs);

chart.call(sunburst);

function customizeBreadcrumbs() {
  // This function will be called whenever the breadcrumbs are updated.
};

So for your purposes we want to rebind the custom events whenever the arcs are updated (entered or exited). The d2b.svgSunburst is in charge of updating the sunburst arcs so we can expect a 'svg-sunburst-updated' event to bubble up through the DOM whenever this d2b component is updated. Something like this "should" do the trick as of v0.0.29.

<div id = 'app'>
  <sunburst-chart 
    :data = 'sunburstChartData' 
    ref = 'sunburst'
    @svg-sunburst-updated.native = 'bindArcEvents'
  >
  </sunburst-chart>
</div>
var app = new Vue({
  el: '#app',
  components: {
    'sunburst-chart': d2b.vueChartSunburst
  },
  data: {
    sunburstChartData: {
      // some sunburst data
    }
  },
  methods: {
    bindArcEvents: function () {
      var that = this,
            el = this.$refs.sunburst.$el;
      d3.select(el).selectAll('.d2b-sunburst-arc')
        .on('mouseover.custom', function (d) { // click or mouseover can be used here
          that.filtered_tree = d.data;
        });
    }
  }
});
timelyportfolio commented 7 years ago

Very interesting, so d3 events bubble up as native? I really appreciate you looking at this!

kevinwarne commented 7 years ago

So the native identifier used when binding an event for a vue component means that the event should be bound to the component's root node $el rather than the vue instance itself. So if you were listening for a click event on the entire sunburst chart component you could just use @click.native = 'someFunction'.

The vue documentation briefly touches on it here

kevinwarne commented 7 years ago

@timelyportfolio I'll go ahead and close this issue for now. Feel free to reopen if you have further question or issues on the matter.

kjhatis commented 6 years ago

I want get the Data(the object) on which bar/point(in case of line chart) the user have clicked. is there any way to get the exact object?

cmburns commented 6 years ago

I am also interested in an answer to kjhatis question. So how can I get the event if a user clicks on a certain bar of a barchart?

Thanks...

kevinwarne commented 6 years ago

Sorry, I must have missed @kjhatis message. This can be done with plain d3 after the chart has been rendered. Just bind a click event and listen for the datum in the handler.

note: the datum will actually be a wrapper of the original user supplied datum with some other d2b specific attributes available. If you want the original datum just console.log d.data in the example below instead of d

d3.selectAll('.d2b-bar-group').on('click', function (d) { console.log(d) })