D3 for Beginners

I've longed for learning D3. It's partially because I love graphes. One graph triumphs thousands of words, I think. Now I got the opportunity to learn it while working on a gig. Sweet!

The first couple of graphs I drew were just basic column charts, pie/donut chart and line chart. Now I am implementing hover over event and I would like to share my learning process.

Column chart

I start with column chart. Requirement: when mouse over a column bar, a tooltip shows up and display the number that bar represents.

Simple enough, we start from creating a hover-box div.

div = d3.select(@$()[0]).selectAll(".hover-box").data(data)

div
  .enter()
    .append("div")
    .attr("class", "hover-box")
div.exit().remove()

Now the hover-box is created. We just need to position it right. At beginning, I used current mouse position d3.event.pageX and d3 graph offset @$().offset() to calculate hover-box position. Soon I realize it's not a good idea. Because the position is calculated based on mouse points, if the mouse is moving up and down along one bar, the tooltip moves as well. That's quite distracting. So I decided to fix the tooltip pointing to the top of the bar. Then we have div style like this:

div.style("top", (d) -> return y(d.value) + offset.top )

Basically it calcuates top position based on the bar's current y axis value and then use graph's offset to balance it.
A little complicated for the horizontal position because if you have multiple columns for one x point, you need to adjust each of them. Otherwise, these tooltips will all end up at the same spot. For this, we include each bar's offset into our right margin.

div.style("right", (d)->
			  offset = 0
          	if d.type == 'firstType'
            	offset = barFirstOffset
          	else
            	offset = barSecondOffset
          	right = width + margin.right 
            		- offset - x(d.date) 
                    - (barWidth / 2) - 12
          	return right + 'px'
        )

Once it's done, it looks like this:

Ring chart

Next is a ring chart. Requirement: when hover over a slice of a ring chart, that slice needs to be highlighted and center label changed accordingly.

Highlighted means either the color darken/lighten or size changes. I like the hover effect to make the slice bigger. To make ring chart slice size change, a simple way is to replace original defined arc with a new arc, which has a different outerRadius but same innerRadius

arc = d3.svg.arc()
      .innerRadius(42)
      .outerRadius(75)

arc2 = d3.svg.arc()
      .innerRadius(42)
      .outerRadius(85)

Add mouse over event:

arcsEnter = arcs.enter()
      .append("path")
      .attr("fill", (d, i) -> d.color)
      .attr("d", arc)

arcsEnter.on("mouseover", (d) ->
      d3.select(this)
        .transition()
        .duration(200)
        .attr("d", arc2)
)

Initially arcs has attribute d as arc. It displays the ring chart. When mouse over, attribute d changed to arc2, which apparently has a bigger outerRadius. The effect is having the same slice but bigger size popping out.

The label in the ring center displays the total amount when there's no hover over. When mouse hovers over to one slice, the center label text changes to that slice's corresponding amount and color.

It looks pretty straighforward. We just need to select the label text and then do the change.

selected = d3.selectAll('.chartLabel').data(data)
selected
  .style('fill', (d, i) -> d.color)
  .text (d) => d.value

Wait, but that doesn't work properly. Because we select all the chartLabel classes. If there are more than 1 ring chart displayed, all chart labels will be changed when mouse over one slice. To fix it, we specify the current seleted element. That way, only current hovered slice's chartLabel will be selected.

selected = 
d3.select(@$([0])
  .selectAll('.chartLabel')
  .data([d])