score:7

Accepted answer

This is a novel and neat concept for a visualization. If I understand correctly, we need to get complete alignment between rectangles and sparklines, mouseovers and tooltips, regardless of sorting. For my answer I'll be using the plunkr you link to first (not Mark's version). If I understand the question correctly, then are five issues that need to be addressed:

  1. Sorting vertically erases the datum used in the mouseover

First, in sorting your svg sparklines vertically you are assinging new datums - these are overwriting the child datums of the path, which is why once sorted vertically the mouseover doesn't work when hovering over the sparklines (the circle hugs the top of each svg). See this simplified example for example:

var div = d3.select("body").append("div");
div.append("p").datum("hello").text(function(d) { return d; });
div.datum("new datum").select("p").text(function(d) { return d; });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

This breaks the portion of the code where you derive information from the path's datum with get_data_on_line(), instead of an array of all the points you now have a index of the path.

This is a bit of a challenge, this could be alleviated by not using a forEach loop to append the svg's but instead using a proper enter/update/exit cylce for this and specifying more data than just an array for the line itself (specifically, if the index was included in the datum this would be much easier).

Having said that, the easiest way to do this without modifying the code much will be to replace this:

var sortedSVG = d3.selectAll(".data-svg")
   .datum(function(d){
      return sorted.indexOf(+this.dataset.r)})
   .sort(function(a,b){
     return d3.ascending(a,b)
});

With:

  sortedVertical.forEach(function(d) {
    d3.select("#data-svg-"+d).raise();
  })

This will order the SVG's in the desired order and does not alter the datum of the SVG's or the paths.

I've split up the vertical and horizontal orders into two separate variables, more on that in number two

  1. The mouseover for each rect uses a fixed column/x value

When setting the coordinates for the circle on on the sparkline in the rectangle mouseover function you use a fixed value:

 .attr('cx', xSpark(idc))

idc doesn't change, nor does the scale. So each rectangle will continue to highlight the same portion of the sparkgraph, regardless of reordering of the rectangles. To fix this we need to preserve the horizontal ordering of the indexes. I used the sorted variable, but gave it a scope so that both mouseover functions and sorting functions could access it:

var sorted = [0,1,2,3,4,5]; // base ordering

And then I could set the rectangle mouseover to get the correct column on the sparklines:

 .attr('cx', xSpark(sorted.indexOf(+idc))) 

Make sure you remove the definition of sorted in the sorting function. Consequently, this also requires using two variables, one for horizontal and another for vertical sorting orders, as we need the horizontal sorting order on demand for mouse overs, not just in the sorting function

  1. When sorting vertically you are moving nodes, but don't account for it

By sorting nodes vertically you break the ordering needed for the paths when sorting horizontally:

d3.selectAll('.sparkline-path')

This will select the element in the order they appear in the DOM - but as a user may have modified this order through sorting, the indexes no longer refer to the proper data when selecting rectangles when using the index of the above selection with:

d3.selectAll('.sparkline-path')
 .attr('d', function(d, k) {
    var arr = [];     
    var sel = d3.selectAll('rect[data-r=\'' + (index) + '\']') 
 ....

Instead, let's modify that to:

.attr('d', function(d, k) {
    var index = d3.select(this).attr("id").split("-")[2]; // get the original index    
    var arr = [];       
    var sel = d3.selectAll('rect[data-r=\'' + index + '\']')  

The index comes from the id property of the SVG, which lets us use the right data - if the index comes from the order of the SVG's after sorting we'll run into problems.

  1. The datum of the sparklines' paths remains the same even though the path changes.

The datum remains unchanged on updating the paths, but the datum is used in calculating what squares to highlight and how to place the circle. We need to update the datum of the spark line each time we modify it:

d3.select(this).datum(result);
  1. Need to update tooltip position based on sorted data

For the tooltip to align properly we need to consider the reordering we have done:

Instead of:

if(rect_r == r && rect_c == pos[1] ) {

Make sure you compare the relative position of each after considering ordering:

if(rect_r == r && sorted.indexOf(+rect_c) == pos[1] ) {  

Altogether, that changes code in about 11 places by my count (including several changes that just propagate the changes outlined above). I've created an updated plunkr: http://plnkr.co/edit/Bgbp7swTs98CMDFkSss3?p=preview

Each change is clearly marked with the old code commented out and the new code placed below.


I'll just re-emphasize though, the use of an enter cycle for entering the paths and SVGs (or alternatively, gs) with more descriptive data bound would allow for easier sorting, updating, and most likely easier de-bugging. The enter cycle seems to work well for the rectangles, but I'm unsure why the approaches for the paths differs. This answer comes to mind


Related Query