Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested selections #52

Open
samselikoff opened this issue Sep 30, 2013 · 12 comments
Open

Nested selections #52

samselikoff opened this issue Sep 30, 2013 · 12 comments

Comments

@samselikoff
Copy link

Hi,

I'm curious if there's an idiomatic way to work with nested selections with d3.chart. This is an example from a grouped barchart using vanilla d3.js:

  var state = svg.selectAll(".state")
      .data(data)
    .enter().append("g")
      .attr("class", "g")
      .attr("transform", function(d) { return "translate(" + x0(d.State) + ",0)"; });

  state.selectAll("rect")
      .data(function(d) { return d.ages; })
    .enter().append("rect")
      .attr("width", x1.rangeBand())
      .attr("x", function(d) { return x1(d.name); })
      .attr("y", function(d) { return y(d.value); })
      .attr("height", function(d) { return height - y(d.value); })
      .style("fill", function(d) { return color(d.name); });

Here we have two data bindings taking place. Initially this seems to suggest that they belong in two different layers, but I'm not sure that makes sense here.

I could "abuse" d3.chart by doing something like this

"enter": function() {
  var chart = this.chart();

  this
      .append("g")
      .attr("class", "g")
      .attr("transform", function(d) { return "translate(" + x0(d.State) + ",0)"; });
    .selectAll(".bar")
    .data(function(d) {return d.values;})
    .enter()
      .append("rect")
      .attr('class', 'bar')
      .attr("width", chart.x1Scale.rangeBand())
      ...;
},

though I'm almost certain this is not the best way to go. What's a good solution here?

Thanks!

P.S. I've been asking a lot of questions here - please let me know if there's somewhere else I should be asking (SO, google group, IRC, etc.).

@samselikoff
Copy link
Author

Perhaps someone could point me to an example that uses nested selections? I have to imagine there are some, this is such a common thing in D3.

@jugglinmike
Copy link
Member

You can find examples of nested selections in the "ticks" layer for this chord diagram and this bullet chart.

@samselikoff
Copy link
Author

Wonderful, thanks!

So basically, sometimes you'll need to do appending within the dataBind method, to get the selection right. This wasn't obvious to me, so maybe there's room for it in the docs somewhere.

PS these examples are nice, any reason they haven't made it to examples or charts yet? If there's anything I could do to help out, I think more examples like these would be really great.

@ccblaisdell
Copy link

Thanks for pointing to those examples, jugglinmike. I'm working on a grouped barchart similar to the one samselikoff linked to above, and I find that I need access to lifecycle events on both the individual bars, and the bar groups.

The method employed in the chord diagram only gives access to the most granular elements, and not the groups. ( I haven't figured out all the magic in the bullet chart yet, so hopefully someone can point out the way it's done there.)

Is there a way of getting lifecycle events on all levels of nested selections?

@headwinds
Copy link
Contributor

I thought the same thing as Sam after I downloaded the examples. I wanted the missing bar chart from the miso project site since I landed there first and got hooked ;-D

So I did some house cleaning on examples page and also included the 2 charts featured on the miso site. Along with a title, each example could also include a brief, 1-2 line description as I'm not sure the difference between all the chord examples - they're cool though.

If you like, I'll fork and commit this examples branch work.

@jugglinmike
Copy link
Member

@headwinds this has been on our agenda for a while now. A pull request would be welcome :)

@headwinds
Copy link
Contributor

done. I tried to create an "examples" branch without realizing you had already created one [love your last checkin comment on it btw ;-D ] so I just merged and committed it on my forked master.

@samselikoff
Copy link
Author

I've been running into issues lately related to this.

@jugglinmike, in the examples the parent selections are basically only containers for the nested selections. But sometimes, you need to perform data-driven transformations for both parent and nested selections, during various lifecycle events. Is there a way to do this currently without abusing d3.chart? (By abuse, I mean putting data joins and enter calls in many different places.)

@jugglinmike
Copy link
Member

@samselikoff Not really--let me know if you come up with anything nice!

@ialarmedalien
Copy link

@samselikoff @headwinds @ccblaisdell Has anyone come up with a solution to this issue? I'm putting together a set of d3.chart components, several of which use nested selections (tables, matrix charts). My current solution is abusive to d3.chart (as defined by @samselikoff two comments above ;-) ) but I'm looking for something that complies with the d3.chart paradigm. If someone has already invented this wheel, that would be great!

@svdwoude
Copy link

@samselikoff , @girlwithglasses, @jugglinmike I'm currently handling this as follows:

You create a layer for the groups (noting special here):

     this.layer("states",
      this.base.append('g').attr('class', 'states'),
      {
        dataBind: function(data) {
          return this.selectAll(".state").data(data);
        },

        insert: function() {
          var chart = this.chart();
          return this.append('g').classed('state',true);
        },

        events: {}

      }
    );

Afterwards you create a layer that holds all the bars from all the states. It's important to create the sub layer below your 'base' layer, it needs to be rendered first so you can use the created elements to attach new elements to:

    this.layer("ages",
      this.layer('states').selectAll('rect'),
      {
        dataBind: function(data) {
          var chart = this.chart();
          var sel = chart.layer('states').selectAll('state');
          return sel.selectAll("rect").data(data);
        },

        insert: function() {
          var chart = this.chart();
          return this.append('rect');
        },

        events: {}
      }
    );

This approach works and helps you attach life cycle events to each element but there are some things that feel counter intuitive. Below implementation of the sub-layer does not work but would feel somewhat more intuitive:

    this.layer("ages",
      this.layer('states'),
      {
        dataBind: function(data) {
          var sel = this.selectAll('state');
          return sel.selectAll("rect").data(data);
        },

        insert: function() {
          var chart = this.chart();
          return this.append('rect');
        },

        events: {}
      }
    );

I would expect to use this in the dataBind function as a reference to the states layer, ideally all the elements within that layer, omitting the need to do a selectAll('state') on that layer.

Couple of remarks:

  • I'm adding this.layer('states').selectAll('rect') as the second argument to my subLayer declaration but I'm pretty sure it's useless, as long as that second argument is a valid selection, it's more of a sanity-check type of deal to structure my code.
  • Would it be better to attach a d3.chart('barchart') to each state layer? Would that be possible? If so could someone provide some minimal example?
  • I've been using this approach with multiple levels of subLayer: subLayer having sub-subLayers... and this approach works without any problems, but I'd love to get some feedback from the creators as to how they feel about this approach. Or if they see any issues in implementing the 'more intuitive' approach I proposed above.

Any feedback is welcome!

@ialarmedalien
Copy link

@svdwoude I came up with the same strategy, although I migrated to kotojs, an ES6 fork of d3.chart, for various reasons, including being able to subclass layer to create specific classes -- e.g. I have a generic AddUpdateRemoveLayer, from which most other layers are derived, with methods for enter (add), merge:transition (update), and exit:transition (remove) events.

I dumped all my chart attachments and use layers instead, as I feel that attachments are a less flexible alternative. I think the d3.chart creators have a certain mental model of what a layer is (some of the other issues have an explanation of it), and I found it more useful to think of layers as containers for repeating or non-repeating presentation units, e.g. a chart axis; a table row; labels on a bar chart bar; etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants