2

I have a d3.v3.min.js histogram created using this as reference Histogram chart using d3 and I'd like to highlight in a separate plot (scatter plot) all the points that fall within one bar of this histogram. To this end I hook on the mouseover event of the rectangle to get the values within one bin. This works fine but I can't get their indices from the original input array:

var data = d3.layout.histogram().bins(xTicks)(values);

bar.append("rect")
   .attr("x", 1)
   .attr("width", (x(data[0].dx) - x(0)) - 1)
   .attr("height", function(d) { return height - y(d.y); })
   .attr("fill", function(d) { return colorScale(d.y) })
   .on("mouseover", function (d, i) { console.log(d); });

d is an array containing all the values within the bin, and i is the bin index. I need the indices of the original data values I passed to the histogram function so that I can look them up in the other plot by index (as opposed to a binary search needed on the value).

SkyWalker
  • 13,729
  • 18
  • 91
  • 187
  • Maybe I'm missing something, but your question doesn't make sense to me. In a histogram, the index of the data elements doesn't matter, only their frequencies. So, a data array like `[1,1,1,1,2,2]` produces the same histogram of a data array like `[1,1,2,1,1,2]`. Not only that, each rectangle represents the sum of all data points between two given thresholds. – Gerardo Furtado Jun 02 '17 at 08:36
  • @GerardoFurtado OP is talking about displaying *"in a separate plot (scatter plot)"*. If I got this right, on mouseover of a bin's rect the scatter plot should be refreshed with data represented by exactly that one bin. Maybe, *I* am missing something, but to me that makes sense. – altocumulus Jun 02 '17 at 08:41
  • @altocumulus but that has nothing to do with any index, only with the thresholds. – Gerardo Furtado Jun 02 '17 at 08:43
  • @GerardoFurtado I think, I know what OP is after. I am just about to write up an answer. – altocumulus Jun 02 '17 at 08:53

1 Answers1

3

Instead of just passing number values to the histogram generator you could create an array of objects carrying additional information:

// Generate a 1000 data points using normal distribution with mean=20, deviation=5
var f = d3.random.normal(20, 5);

// Create full-fledged objects instead of mere numbers.
var values = d3.range(1000).map(id => ({
                                  id: id,
                                  value: f()
}));

// Accessor function for the objects' value property.
var valFn = d => d.value;

// Generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
  .bins(x.ticks(20))
  .value(valFn)      // Provide accessor function for histogram generation
  (values);

By providing an accessor function to the histogram generator you are then able to create the bins from this array of objects. Calling the histogram generator will consequently result in bins filled with objects instead of just raw numbers. In an event handler you are then able to access your data objects by reference. The objects will carry all the initial information, be it the id property as in my example, an index or anything else you put in them in the first place.

Have a look at the following snippet for a working demo:

var color = "steelblue"; 
var f = d3.random.normal(20, 5);
// Generate a 1000 data points using normal distribution with mean=20, deviation=5
var values = d3.range(1000).map(id => ({
                                id: id,
                                value: f()
}));
var valFn = d => d.value;

// A formatter for counts.
var formatCount = d3.format(",.0f");

var margin = {top: 20, right: 30, bottom: 30, left: 30},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var max = d3.max(values, valFn);
var min = d3.min(values, valFn);
var x = d3.scale.linear()
      .domain([min, max])
      .range([0, width]); 

// Generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
    .bins(x.ticks(20))
    .value(valFn)
    (values);

var yMax = d3.max(data, function(d){return d.length});
var yMin = d3.min(data, function(d){return d.length});
var colorScale = d3.scale.linear()
            .domain([yMin, yMax])
            .range([d3.rgb(color).brighter(), d3.rgb(color).darker()]);

var y = d3.scale.linear()
    .domain([0, yMax])
    .range([height, 0]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var bar = svg.selectAll(".bar")
    .data(data)
  .enter().append("g")
    .attr("class", "bar")
    .attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; })
    .on("mouseover", d => { console.log(d)});

bar.append("rect")
    .attr("x", 1)
    .attr("width", (x(data[0].dx) - x(0)) - 1)
    .attr("height", function(d) { return height - y(d.y); })
    .attr("fill", function(d) { return colorScale(d.y) });

bar.append("text")
    .attr("dy", ".75em")
    .attr("y", -12)
    .attr("x", (x(data[0].dx) - x(0)) / 2)
    .attr("text-anchor", "middle")
    .text(function(d) { return formatCount(d.y); });

svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

/*
* Adding refresh method to reload new data
*/
function refresh(values){
  // var values = d3.range(1000).map(d3.random.normal(20, 5));
  var data = d3.layout.histogram()
    .value(valFn)
    .bins(x.ticks(20))
    (values);

  // Reset y domain using new data
  var yMax = d3.max(data, function(d){return d.length});
  var yMin = d3.min(data, function(d){return d.length});
  y.domain([0, yMax]);
  var colorScale = d3.scale.linear()
              .domain([yMin, yMax])
              .range([d3.rgb(color).brighter(), d3.rgb(color).darker()]);

  var bar = svg.selectAll(".bar").data(data);

  // Remove object with data
  bar.exit().remove();

  bar.transition()
    .duration(1000)
    .attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });

  bar.select("rect")
      .transition()
      .duration(1000)
      .attr("height", function(d) { return height - y(d.y); })
      .attr("fill", function(d) { return colorScale(d.y) });

  bar.select("text")
      .transition()
      .duration(1000)
      .text(function(d) { return formatCount(d.y); });

}

// Calling refresh repeatedly.
setInterval(function() {
  var values = d3.range(1000).map(id => ({
                                  id: id,
                                  value: f()
  }));
  refresh(values);
}, 2000);
body {
  font: 10px sans-serif;
}

.bar rect {
  shape-rendering: crispEdges;
}

.bar text {
  fill: #999999;
}

.axis path, .axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.as-console-wrapper {
  height: 20%;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
altocumulus
  • 21,179
  • 13
  • 61
  • 84
  • Running the code I get the following syntax error `Error: { "message": "Syntax error", "filename": "https://stacksnippets.net/js", "lineno": 36, "colno": 37}10:14:33.475` – SkyWalker Jun 07 '17 at 08:14
  • 1
    @GiovanniAzua I suppose, you are getting this error on IE, right? That's because that piece of programming art still [does not support](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions#Browser_compatibility) fat arrow functions, which are part of ES6. If you substitute those with standard functions, all works as expected: https://jsfiddle.net/1cs4gf31/. – altocumulus Jun 07 '17 at 08:38
  • Yeah corporate IE :( – SkyWalker Jun 07 '17 at 09:13