Search code examples
javascriptd3.jschartsbrush

D3.v3 Brush Strange Behavior


I'm trying to draw a brush on my histogram. The brush controls only appear after a click event (not on the initial page load). Obviously, not the desired behavior.

How do I instantiate the chart AND the brushes on the initial page load before the first click event?

 // TEST Data //
 var caldata = [
 {"cal_start_yr": "1945"}, {"cal_start_yr": "1948"},
 {"cal_start_yr": "1945"}, {"cal_start_yr": "1950"},
 {"cal_start_yr": "1945"}, {"cal_start_yr": "1941"},
 {"cal_start_yr": "1944"}, {"cal_start_yr": "1949"}
 ];
// CROSSFILTER Aggregations //
 var cals = crossfilter(caldata);
 var total = cals.groupAll().reduceCount().value();
 var year = cals.dimension(function(d) {
   return d.cal_start_yr;
 });
 var countYear = year.group().reduceCount();
 var yearCount = countYear.all();

// Some helper AGGREGATION Values
 var keys = countYear.all().map(function(d) {return d.value;}),
   min = d3.min(countYear.all(), function(d) {return d.key;}),
   max = d3.max(countYear.all(), function(d) {return d.key;}),
   range = max - min;

 // Histogram dimensions
 var margin = {top: 10, right: 20, bottom: 10,left: 10 },
   height = 250 - margin.top - margin.bottom,
   width = 450 - margin.left - margin.right,
   barPadding = 5;
	 
// Histogram SCALES 
 var xScale = d3.scale.linear()
   .domain([min, max])
   .range([0, width]);
 var yScale = d3.scale.linear()
   .domain([0, d3.max(countYear.all(), function(d) {return d.value;})])
   .range([height / 2, 0]);

// D3 Tool Tip
 var tip = d3.tip()
   .attr('class', 'd3-tip')
   .html(function(d) {return d.key});

// CANVAS setup //
 var histogram1 = d3.select("#histogram1").append("svg:svg")
   .attr("width", width + margin.left + margin.right)
   .attr("height", height + margin.top + margin.bottom)
   .append("g");
// Initiate Tooltip //
 histogram1.call(tip);

// DRAW Histogram //
 histogram1.selectAll("rect")
   .data(yearCount)
   .enter().append("rect")
   .attr("x", function(d) {
     return xScale(d.key) + 0.5 * (width / range)
   })
   .attr("width", width / range)
   .attr("y", function(d) {
     return yScale(d.value);
   })
   .attr("height", function(d) {
     return (height / 2 - yScale(d.value));
   })
   .attr("fill", "green")
   .attr("fill-opacity", .25)
   .attr("stroke", "white")
   .on("mouseover", tip.show)
   .on("mouseout", tip.hide);

// X AXIS //
 var xAxis = d3.svg.axis()
   .scale(xScale)
   .ticks(5)
   .orient("bottom")
   .tickFormat(d3.format("d"));
 histogram1.append("g")
   .attr("class", "axis")
   .call(xAxis)
   .attr("transform", "translate(" + margin.left + "," + height / 2 + ")");

 var brush = d3.svg.brush()
   .x(xScale)
   .extent([xScale(+1945), xScale(+1946)])
   .on("brush", function(d) {console.log(d);});

 var brushg = histogram1.append("g")
   .attr("class", "brush")
   .call(brush)

 brushg.selectAll("rect")
   .attr("height", height / 2);

 brushg.selectAll(".resize")
   .append("path")
   .attr("d", resizePath);




 function resizePath(d) {
   // Style the brush resize handles. No idea what these vals do...
   var e = +(d == "e"),
     x = e ? 1 : -1,
     y = height / 4; // Relative positon if handles
   return "M" + (.5 * x) + "," + y + "A6,6 0 0 " + e + " " + (6.5 * x) + "," + (y + 6) + "V" + (2 * y - 6) + "A6,6 0 0 " + e + " " + (.5 * x) + "," + (2 * y) + "Z" + "M" + (2.5 * x) + "," + (y + 8) + "V" + (2 * y - 8) + "M" + (4.5 * x) + "," + (y + 8) + "V" + (2 * y - 8);
 }
/*** d3-tip styles */
.as-console-wrapper { max-height: 20% !important;}

.d3-tip {
  line-height: 1.5;
  padding: 8px;
  background: rgba(0, 0, 0, 0.8);
  color: #fff;
  border-radius: 0px;
  text-align: center;
}

.d3-tip:after {
  box-sizing: border-box;
  display: inline;
  font-size: 10px;
  width: 100%;
  line-height: 1;
  color: rgba(0, 0, 0, 0.8);
  content: "\25BC";
  position: absolute;
  text-align: center;
}

.d3-tip.n:after {
  top: 100%;
  left: 0;
  margin: -1px 0 0;
}


/*** D3 brush */

.brush .extent {
  stroke: #222;
  fill-opacity: .125;
  shape-rendering: crispEdges;
}
<script src="https://d3js.org/d3.v3.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.12/crossfilter.min.js"></script>


<div id="histogram1"></div>


Solution

  • The brush's extent is set to a value from the scale's domain, not its range! From the docs:

    The scale is typically defined as a quantitative scale, in which case the extent is in data space from the scale's domain

    To set the initial extent you have to use

    .extent([1945, 1946])
    

    instead of

    .extent([xScale(+1945), xScale(+1946)])
    

    For a working demo have a look at the updated snippet:

    // TEST Data //
     var caldata = [
     {"cal_start_yr": "1945"}, {"cal_start_yr": "1948"},
     {"cal_start_yr": "1945"}, {"cal_start_yr": "1950"},
     {"cal_start_yr": "1945"}, {"cal_start_yr": "1941"},
     {"cal_start_yr": "1944"}, {"cal_start_yr": "1949"}
     ];
    // CROSSFILTER Aggregations //
     var cals = crossfilter(caldata);
     var total = cals.groupAll().reduceCount().value();
     var year = cals.dimension(function(d) {
       return d.cal_start_yr;
     });
     var countYear = year.group().reduceCount();
     var yearCount = countYear.all();
    
    // Some helper AGGREGATION Values
     var keys = countYear.all().map(function(d) {return d.value;}),
       min = d3.min(countYear.all(), function(d) {return d.key;}),
       max = d3.max(countYear.all(), function(d) {return d.key;}),
       range = max - min;
    
     // Histogram dimensions
     var margin = {top: 10, right: 20, bottom: 10,left: 10 },
       height = 250 - margin.top - margin.bottom,
       width = 450 - margin.left - margin.right,
       barPadding = 5;
    	 
    // Histogram SCALES 
     var xScale = d3.scale.linear()
       .domain([min, max])
       .range([0, width]);
     var yScale = d3.scale.linear()
       .domain([0, d3.max(countYear.all(), function(d) {return d.value;})])
       .range([height / 2, 0]);
    
    // D3 Tool Tip
     var tip = d3.tip()
       .attr('class', 'd3-tip')
       .html(function(d) {return d.key});
    
    // CANVAS setup //
     var histogram1 = d3.select("#histogram1").append("svg:svg")
       .attr("width", width + margin.left + margin.right)
       .attr("height", height + margin.top + margin.bottom)
       .append("g");
    // Initiate Tooltip //
     histogram1.call(tip);
    
    // DRAW Histogram //
     histogram1.selectAll("rect")
       .data(yearCount)
       .enter().append("rect")
       .attr("x", function(d) {
         return xScale(d.key) + 0.5 * (width / range)
       })
       .attr("width", width / range)
       .attr("y", function(d) {
         return yScale(d.value);
       })
       .attr("height", function(d) {
         return (height / 2 - yScale(d.value));
       })
       .attr("fill", "green")
       .attr("fill-opacity", .25)
       .attr("stroke", "white")
       .on("mouseover", tip.show)
       .on("mouseout", tip.hide);
    
    // X AXIS //
     var xAxis = d3.svg.axis()
       .scale(xScale)
       .ticks(5)
       .orient("bottom")
       .tickFormat(d3.format("d"));
     histogram1.append("g")
       .attr("class", "axis")
       .call(xAxis)
       .attr("transform", "translate(" + margin.left + "," + height / 2 + ")");
    
     var brush = d3.svg.brush()
       .x(xScale)
       .extent([1945, 1946])
       .on("brush", function(d) {console.log(brush.extent());});
    
     var brushg = histogram1.append("g")
       .attr("class", "brush")
       .call(brush)
    
     brushg.selectAll("rect")
       .attr("height", height / 2);
    
     brushg.selectAll(".resize")
       .append("path")
       .attr("d", resizePath);
    
    
    
    
     function resizePath(d) {
       // Style the brush resize handles. No idea what these vals do...
       var e = +(d == "e"),
         x = e ? 1 : -1,
         y = height / 4; // Relative positon if handles
       return "M" + (.5 * x) + "," + y + "A6,6 0 0 " + e + " " + (6.5 * x) + "," + (y + 6) + "V" + (2 * y - 6) + "A6,6 0 0 " + e + " " + (.5 * x) + "," + (2 * y) + "Z" + "M" + (2.5 * x) + "," + (y + 8) + "V" + (2 * y - 8) + "M" + (4.5 * x) + "," + (y + 8) + "V" + (2 * y - 8);
     }
    /*** d3-tip styles */
    
    .d3-tip {
      line-height: 1.5;
      padding: 8px;
      background: rgba(0, 0, 0, 0.8);
      color: #fff;
      border-radius: 0px;
      text-align: center;
    }
    
    .d3-tip:after {
      box-sizing: border-box;
      display: inline;
      font-size: 10px;
      width: 100%;
      line-height: 1;
      color: rgba(0, 0, 0, 0.8);
      content: "\25BC";
      position: absolute;
      text-align: center;
    }
    
    .d3-tip.n:after {
      top: 100%;
      left: 0;
      margin: -1px 0 0;
    }
    
    
    /*** D3 brush */
    
    .brush .extent {
      stroke: #222;
      fill-opacity: .125;
      shape-rendering: crispEdges;
    }
    
    .as-console-wrapper { max-height: 30% !important;}
    <script src="https://d3js.org/d3.v3.min.js"></script>
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.min.js"></script>
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.12/crossfilter.min.js"></script>
    
    
    <div id="histogram1"></div>