smcs
smcs

Reputation: 2004

D3: How to conditionally bind SVG objects to data?

I have here an array of objects that I'm visualising using D3. I bind each object to a group element and append to that an SVG graphic that depends on some object property, roughly like this:

var iconGroups = zoomArea.selectAll("g.icons")
        .data(resources)
        .enter()
        .append("g")

var icons = iconGroups.append(function(d){
         if(d.type == "Apple"){
             return appleIcon;
         }else if(d.type == "Orange"){
             return orangeIcon;
         })

etc. Now I'd like to extend some of those icons with an additional line. I could add a line element for each data point and set them visible only where applicable, but since I want to add them only for say one out of a hundred data points, that seems inefficient. Is there a way to bind SVG lines to only those objects where d.type == "Apple"?

Upvotes: 0

Views: 507

Answers (3)

licancabur
licancabur

Reputation: 645

I would create separate selections for icons and lines, this way:

var iconGroups = zoomArea.selectAll('g.icons')
  .data(resources);
iconGroups
  .enter()
  .append('g')
  .classed('icons', true);
iconGroups.exit().remove();

var icons = iconGroups.selectAll('.icon').data(function(d) {return [d];});
icons
  .enter()
  .append(function(d) {
    if(d.type === 'Apple'){
      return appleIcon;
    }else if(d.type === 'Orange'){
      return orangeIcon;
    }        
  }).classed('icon', true);
icons.exit().remove();

var lines = iconGroups.selectAll('.line').data(function(d) {
  return d.type === 'Apple' ? [d] : [];
});
lines
  .enter()
  .append('line')
  .classed('line', true);
lines.exit().remove();

.exit().remove() is added just because I add it always to be sure that updates work better. :)

Maybe the code is longer than .filter() but I use the following structure all the time and it's easier to scale it.

edit: apropos comment - If you need to pass indexes, you should pass them in binded data:

var iconGroups = zoomArea.selectAll('g.icons')
  .data(resources.map(function(resource, index) {
    return Object.create(resource, {index: index})
  }));

(Object.create() was used just to not mutate the data, you can use _.clone, Object.assign() or just mutate it if it does not bother you)

then you can access it like:

lines.attr("x1", function(d){ console.log(d.index);})

Upvotes: 1

Gilsha
Gilsha

Reputation: 14591

Use d3 filter.

selection.filter(selector)

Filters the selection, returning a new selection that contains only the elements for which the specified selector is true.

Reference: https://github.com/mbostock/d3/wiki/Selections#filter

Demo: http://bl.ocks.org/d3noob/8dc93bce7e7200ab487d

Upvotes: 1

Balint Domokos
Balint Domokos

Reputation: 1021

You could add a class to the icons to be selected (e.g. appleIcon), and use that class in a selector to add the lines.

Upvotes: 1

Related Questions