Adding a Class

73 views
Skip to first unread message

Zac Rinehart

unread,
Jun 13, 2016, 2:22:57 AM6/13/16
to d3-js
Hey everyone,

I have been coding away happily for a month or so, got a pretty cool dashboard going. What I want to do now is create a mouse event such that when I hover over the bar chart, the corresponding data points on my scatter plot will automatically be highlighted too. I think I know how to accomplish that by using classes, d3.selectAll('.class'), then I add the fill effects across all charts.

The bump in the road I have now is when I tried to add a class to my bar chart, it overwrote it's previous class of 'bar'. It seems to need to retain being a 'bar' or else the css doesn't get applied to it. When I load the page, all the bars are black (whereas normally they are a different color and have highlighted color mouse events).

The only other context I'll add is that I'm adding classes from within a tsv file. I have a column named 'bin' that has values from 1-24. So I want all these bar elements in the bar chart to still be 'bar's, but I wish to assign an additional class based on the corresponding bin value from the bin column I loaded from the data.map. So to clarify, if the 3rd row of the bar chart's tsv has an x value of 1200 and a y value of 5 and a bin value of 3, then it'd have to have two clases: 'bar and '3' or 'bin-3', something to that effect.

d3.tsv("bar_exper.tsv", type, function(error, data) {
  x.domain(data.map(function(d) { return d.letter; }));
  y.domain([0, d3.max(data, function(d) { return +d.frequency; })+1]);
  var x_bin = (data.map(function(d) { return d.bin}));

svg.selectAll(".bar")
      .data(data)
    .enter().append("rect")
      .attr("class", "bar")
      .attr("x", function(d) { return x(d.letter); })
      .attr("width", x.rangeBand())
      .attr("y", function(d) { return y(d.frequency); })
      .attr("height", function(d) { return height - y(d.frequency); })
      .on('mouseover', tip.show)
      .on('mouseout', tip.hide)
      .attr("class", function(d) {
        var c = "bar";
        c += "(d.bin)";
        return c;
      })
});


Curran

unread,
Jun 13, 2016, 3:41:33 AM6/13/16
to d3-js
Hello,

I think what you need is selection.classed.

If you set the class with "attr", then it will always override the previous class that was there, because "attr" sets the value for the entire attribute. The "class" attribute is different from others because you can set multiple classes by separating them with spaces. If you use selection.classed, it will handle multiple classes property, using a space delimiter in the actual attribute. This would allow you to have two classes at once, e.g. "bar" and "bar-4", which would appear in the "class" attribute as "bar bar-4".

Good luck!

Best regards,
Curran

Curran

unread,
Jun 13, 2016, 3:43:59 AM6/13/16
to d3-js
On second though, selection.classed might not be the best here, because you are dynamically generating the class names.

Maybe try this:

      .attr("class", function(d) {
        return "bar bar-" + d.bin;
      })
Message has been deleted

Zac Rinehart

unread,
Jun 13, 2016, 11:39:43 AM6/13/16
to d3-js
Hey Curran,

Thanks for your help again. Your suggestion got me closer to my ideal dashboard. Let me share the aftermath. The following are snippets from the bar chart, scatter plot and the global selectAll('.class') event.

svg.selectAll(".bar")
     .data(data)
   .enter().append("rect")
     .attr("class", function(d) { return "bar bin-" + d.bin;})
     .attr("x", function(d) { return x(d.letter); })
     .attr("width", x.rangeBand())
     .attr("y", function(d) { return y(d.frequency); })
     .attr("height", function(d) { return height - y(d.frequency); })
     .on('mouseover', tip.show)
     .on('mouseout', tip.hide)
});
//scatterplot
graphGroup
.selectAll('circle')
.data(data)
.enter()
.append('circle)
.attr('
class', function(d) { return "bin-"+ d.bin})
.attr({
  cx: function(d) {return scale(d.X); },
  cy: function(d) {return yscale(d.Y); },
  r: radius,
  fill: '
none',
 stroke: '
#376c9b'
});

d3
.selectAll('.bin-')
 
.on('mouseover', function(){
   d3
.select(this).attr('fill','red')
});

 So it displays properly, all the css is working, unlike before. But the last part, where I try to call a fill of all elements matching the class 'bin-' doesn't work. Developer tools didn't have anything to add, I was thinking it would say something like search query is empty or something if it wasn't going to work. So I guess it's not working because of another reason. I'm also not sure if selectAll works for partial matching text as far as classes go. For example, in the bar chart the class is bar bin- + d.bin, whereas in the scatter plot it is just 'bin-'+d.bin. Do I have to arbitrarily assign my svg circles a bar class name? 

I also have a sinking feeling that d3.select(this).attr('fill','red') will only fill one element. I thought d3.selectAll would apply to everything matching the class though. So I'm not sure. That's the whole reason I tried using classes, I need something that the html can reference throughout the document, because although the graphs are on the same html file, they are appended to different divs, so if I want to have this fancy mouse event thing, it has to communicate that somehow.

Let me know your thoughts, I really, really appreciate it!

Zac
 
 

 

martinez.v...@gmail.com

unread,
Jun 14, 2016, 9:10:45 AM6/14/16
to d3-js
Hello Zac:

If you want to assign two classes to each element, for instance one being 'bin' and the other being 'bin- + d.bin', you have to give them as a string, separating each class name with a space.

As Curran said, for your example

.attr("class", function(d) {
        return "bin bin-" + d.bin;
      })

Notice that the 'bin' is written twice; the first one will give to the element the class 'bin' so your code d3.selectAll('.bin') will work

Regards,

Beatriz

Curran

unread,
Jun 14, 2016, 11:39:20 AM6/14/16
to d3-js
Hi Zac,

Congrats on getting it working this far!

Maybe it's time to take a step back. The overall desired feature is:

"When I hover over the bar chart, the corresponding data points on my scatter plot will automatically be highlighted too."

Based on this I'm wondering, why is it necessary to assign unique classes for each bin? It sounds like you wanted to query based on the class, then change the style of the resulting elements depending on whether or not they match the highlighted bin. This is, however, more natural to do at the level of the data itself. Particularly, if you have a property called "bin" on each of the data elements for the bar chart and the scatter plot, they can be easily compared.

Maybe it might work better to try assigning a class that fades elements into the background, maybe call it "hidden". With this approach, you can assign this class to all marks that correspond to bins other than the highlighted (hovered) bin. That might look something like this:

  // This function gets called on hover, passing the hovered bin value.
  function updateHighlightedBin(bin) {

    // Update the highlighting on the bars of the bar chart.
    svg.selectAll("rect").classed("hidden", function(d) {
      return d.bin !== bin;
    });

    // Update the highlighting on the circles of the scatter plot.
    svg.selectAll("circle").classed("hidden", function(d) {
      return d.bin !== bin;
    });
  }

This is the approach taken in Mike Bostock’s Block "Scatterplot Matrix Brushing". The "hidden" class can be defined as follows.

.hidden {
  fill: #ccc !important;
}

The above approach has the disadvantage that the highlighted marks may not appear in front of non-highlighted marks that overlap. To solve this case, another approach would be to have two SVG groups (<g> elements) on top of one another, one that renders all the marks with the "hidden" style (or faded to the background with a semi-transparent overlay rectangle), and another (on top) that only renders the highlighted mark(s). An example of this approach can be seen in a Block I made "Horizontal Stacked Bars" (hover over the color legend to see it in action).

Hope this is helpful. Good luck!

All the best,
Curran

Zac Rinehart

unread,
Jun 14, 2016, 1:16:22 PM6/14/16
to d3-js
Thanks to both of you for taking the time to walk through this with me. It's been really helpful.

That brush scatter plot matrix was actually the inspiration for my dashboard. But I just wanted a slightly simplified variant. Instead of calling a brush I just wanted a single mouse event over 1 bar of the bar chart to simultaneously select the corresponding data points on the scatter plots appended on the other divs (seems kind of tricky because one bar chart element might contain five or more circles on the scatter plot). So I'm still shooting for something like that. I will take a step back and try your method and post again after due experimenting.

Also, either way I will try to upload it on to bl.ocks for reference. 

Best,

Zac

Zac Rinehart

unread,
Jun 15, 2016, 8:01:32 AM6/15/16
to d3-js
What's up guys,

Still haven't quite got that .hidden mouse event figured out. But good news is I put it on github/bl.ocks. Here is my dashboard:


It didn't fit in the frame when I put it on github, so I have it set to load zoom at 65%. 

Curran, your tips helped a lot. I'm not sure why I still can't make it work. I want to show my progress after all this effort. Hopefully when you see it, it will be easier to tinker with. The code in index is super long because there are 3 graphs in it. The bar chart is towards the top and so is the update hidden bin function. I have comments in there too. By the way, if you like my dashboard, feel free to use it as a template for your data. It's not fully functional, but hopefully we can think it out soon.

Note the tool tips don't work anymore. I'm not sure why that happened. There used to be black tooltips with white text over each bar as you moused over them. I'm guessing it has something to do with class conflict or something.

Anyway, when time is allotting, have a look and see if you can find why: "When I hover over the bar chart, the corresponding data points on my scatter plot will automatically be highlighted too." isn't working.

Thanks for reading,

Zac


Curran

unread,
Jun 16, 2016, 5:30:49 AM6/16/16
to d3-js
Hi Zac,

Nice work! I looked into it a bit and here's what I found.

.on('mouseover', updateHighlightedBin)

The above line passes the data element into updateHighlightedBin, rather than the bin. This could be changed like this in order to pass in the bin:

.on('mouseover', function (d){
  updateHighlightedBin(d.bin);
});

The expression

svg.selectAll("rect").classed("hidden", function(d) {
  return d.bin !== bin;
});

is selecting rects other than the bars. To make it more specific we can change it to select only the "bar" class, like this:

svg.selectAll(".bar").classed("hidden", function(d) {
  return d.bin !== bin;
});

This works. 

The expression

svg.selectAll("circle").classed("hidden", function(d) {
  return d.bin !== bin;
});

is not selecting anything, because the circles are in a different SVG group. In order to be able to select the group, you can add a class to the group creation, like this:

var graphGroup = svg.append('g')
  .attr('class', 'circle-group')
  .attr('transform', 'translate(' + margins.left + ',' + margins.top + ')');

The next issue is that the bins are not exposed in the data elements for the circles. The bins need to be on the circle data elements in order to be compared with the highlighted bin. To do this, the data loading part can be changed to this:

var data = rawData.map(function (d) {
  return { X: +d.Theoretical, Y: +d.Series, bin: d.Bin }
});

Now the circle highlighting can be made to work:

d3.select(".circle-group").selectAll("circle").classed("hidden", function(d) {
  return d.bin !== bin;
});

For the second scatter plot "Symmetry", there is no Bin column in the data, which you'll need to add for the linked highlighting.

Here's my modified block with the bare essentials for getting linked highlighting to work. You can take it from here and iterate on the styles to make it look better.

All the best,
Curran

P. S. You can add a .block file to customize the height of the frame in bl.ocks.org. See this example Data based gradients - HR Diagram, and the corresponding .block file. This is documented at http://bl.ocks.org/-/about

Zac Rinehart

unread,
Jun 17, 2016, 3:14:44 AM6/17/16
to d3-js
Hey curran,

Your example helped so much, my dashboard finally has linked highlighting! It's everything I imagined it'd be like. Only thing I added was just changing the styling a bit.   Here is an updated version:


Anyways, your continued support made it all possible. I'm very grateful. 

As fate would have it, the 2nd symmetry plot doesn't work. I worked on it all last night, because I don't want to seem so pathetic, being unable to do even that small of a thing by myself. But after checking everything 20 times (literally), it has everything the 1st scatter plot has. I entered a Bin column in the tsv file then I loaded d.Din into the data map, I added the 'circle-group' into the class attribute. All that stuff. When the page loads, I can tell it's not considered as 'hidden.' Even when I highlight something, it remains unresponsive. I also tried using d3.selectAll('circle-group'), because I was afraid d3.select would stop after it found the first circle-group. Didn't work either though.

I also changed hidden to circle.hidden in the css, because I wanted to restore the function of the tooltip for the bar charts. Strangely, the tooltips still don't work. I'm not sure why. I'm not prioritizing that issue for now though, that's more of an aesthetic thing. I'm more focused on getting linked highlighting across all charts. 


Best,

Zac


Curran

unread,
Jun 17, 2016, 9:54:56 AM6/17/16
to d3-js
Awesome work!

I managed to get both charts highlighting by changing the following

d3.select(".circle-group").selectAll("circle").classed("hidden", function(d) {
  return d.bin !== bin;
});

to this

d3.selectAll(".circle-group").selectAll("circle").classed("hidden", function(d) {
  return d.bin !== bin;
});

Zac Rinehart

unread,
Jun 17, 2016, 10:51:35 AM6/17/16
to d3-js
Hey,

You did it! Odd though, I explicitly tried doing just that yesterday and it didn't work. I must have had a typo or something. 

Issue solved. Thank you so much for helping me understand how to use .class in this way.

Your rock!

Zac

Zac Rinehart

unread,
Jun 18, 2016, 7:19:53 AM6/18/16
to d3-js
Hello again,

Just for further closure. I did find why the tooltip function stopped working. If I want tool tips and linked highlighting via .class attributes all from the same mouse hover event. The html element aint big enough for both of them. One must include an event handle argument after mouseover. For example, mouseover.tooltip. The reason is that concurrent mouse events can clash, so it's best to choose a suitable argument suffix.

So I updated my block accordingly, just in case anyone was anxiously waiting.

Curran

unread,
Jun 18, 2016, 9:28:40 AM6/18/16
to d3-js
The tooltips look great! Glad I could help.

Best,
Curran
Reply all
Reply to author
Forward
0 new messages