Clarinetist
Clarinetist

Reputation: 1187

Add slider to bar graph for filtering

page.html:

<!DOCTYPE html>
<!-- https://bl.ocks.org/mbostock/3886208 -->
<style>
    #tooltip {
        position: absolute;
        width: 200px;
        height: auto;
        padding: 10px;
        background-color: white;
        -webkit-border-radius: 10px;
        -moz-border-radius: 10px;
        border-radius: 10px;
        -webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
        -moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
        box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
        pointer-events: none;
    }

    .legend {
        font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
        font-size: 60%;
    }

    #tooltip.hidden {
        display: none;
    }

    #tooltip p {
        margin: 0;
        font-family: sans-serif;
        font-size: 16px;
        line-height: 20px;
    }
    g[class="col_1"] rect:hover {
        fill:#80061b;
    }
    g[class="col_2"] rect:hover {
        fill:#008394;
    }
</style>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.7.0/d3-legend.min.js"></script>
<body>

<div id="tooltip" class="hidden">
    <p><strong>Month: </strong><span id="month"></span><p>
    <p><strong>Value: </strong><span id="count"></span></p>
</div>

<script>
var margin = {top: 20, right: 20, bottom: 50, left: 80},
    width = 1300 - margin.left - margin.right,
    height = 700 - margin.top - margin.bottom;

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

var g = svg.append("g")
           .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// parse the date / time
// look at the .csv in Notepad! DO NOT LOOK AT EXCEL!
var parseDate = d3.timeParse("%m/%d/%Y");


var x = d3.scaleTime()
          .range([0, width - margin.left - margin.right]);
var y = d3.scaleLinear().range([height, 0]);
var z = d3.scaleOrdinal()
          .range(["#CE1126", "#00B6D0"]); // red and blue 

var xMonthAxis = d3.axisBottom(x)
              .ticks(d3.timeMonth.every(1))
              .tickFormat(d3.timeFormat("%b")); // label every month

var xYearAxis = d3.axisBottom(x)
                  .ticks(d3.timeYear.every(1))
                  .tickFormat(d3.timeFormat("%Y")); // label every year

var yAxis = d3.axisLeft(y).tickFormat(d3.format('.2s'));

var formatNum = d3.format(",")

// load .csv file
d3.csv("test_data.csv", function(d, i, columns) {
  for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
  d.total = t;
  return d;
}, function(error, data){
    if (error) throw error;

    data.forEach(function(d) {
        d.date = parseDate(d.date);
    });

    var keys = data.columns.slice(1);
    var barWidth = (width - margin.right- margin.left)/(data.length+1);     

    data.sort(function(a, b) { return b.date - a.date; });


    x.domain(d3.extent( data, function(d){ return d.date }) );

    var max = x.domain()[1];
    var min = x.domain()[0];
    var datePlusOneMonth = d3.timeDay.offset(d3.timeMonth.offset(max, 1), -1); // last day of current month: move up one month, back one day 

    x.domain([min,datePlusOneMonth]);

    y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
    z.domain(keys);


    // the bars 
    g.append("g")
     .selectAll("g")
     .data(d3.stack().keys(keys)(data))
     .enter().append("g")
     .attr('class', function(d) { return d.key; })
     .attr("fill", function(d) { return z(d.key); })
     .selectAll("rect")
     .data(function(d) { return d; })
     .enter()
     .append("rect")
     .attr("x", function(d) { return x(d.data.date); })
     .attr("y", function(d) { return y(d[1]); })
     .attr("height", function(d) { return y(d[0]) - y(d[1]); })
     .attr("width", barWidth)
     .on("mousemove", function(d) {

        //Get this bar's x/y values, then augment for the tooltip
        var xPosition = parseFloat(d3.mouse(this)[0]) + 88;
        var yPosition = parseFloat(d3.mouse(this)[1]) - 29;
        var value = d.data[d3.select(this.parentNode).attr('class')]; // differentiating between col1 and col2 values

        //Update the tooltip position and value
        d3.select("#tooltip")
          .style("left", xPosition + "px")
          .style("top", yPosition + "px")                       
          .select("#count")
          .text(formatNum(value)); // return the value 

        d3.select("#tooltip")
          .style("left", xPosition + "px")
          .style("top", yPosition + "px")                       
          .select("#month")
          .text(d3.timeFormat("%B %Y")(d.data.date)); // return the value 

        //Show the tooltip
        d3.select("#tooltip").classed("hidden", false);

      })
     .on("mouseout", function() {
        //Hide the tooltip
        d3.select("#tooltip").classed("hidden", true);

     });


    // x-axis
    var monthAxis = g.append("g")
      .attr("class", "axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xMonthAxis);


    const firstDataYear = x.domain()[0];
    xYearAxis.tickValues([firstDataYear].concat(x.ticks()));

    var yearAxis = g.append("g")
                     .attr("class", "axis")
                     .attr("transform", "translate(0," + (height + 25) + ")")
                     .call(xYearAxis);

    var valueAxis = g.append("g")
                 .attr("class", "axis")
                 .call(yAxis);

    monthAxis.selectAll("g").select("text")
      .attr("transform","translate(" + barWidth/2 + ",0)");

    var options = d3.keys(data[0]).filter(function(key) { return key !== "date"; }).reverse();
    var legend = svg.selectAll(".legend")
                    .data(options.slice().filter(function(type){ return type != "total"}))
                    .enter().append("g")
                    .attr("class", "legend")
                    .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });

    legend.append("rect")
          .attr("x", width - 18)
          .attr("width", 18)
          .attr("height", 18)
          .style("fill", z);

    function capitalizeFirstLetter(string) {
        return string.charAt(0).toUpperCase() + string.slice(1);
    }

    legend.append("text")
          .attr("x", width - 24)
          .attr("y", 9)
          .attr("dy", ".35em")
          .style("text-anchor", "end")
          .text(function(d) { return capitalizeFirstLetter(d); });

});
</script>

</body>

test_data.csv:

date,col_1,col_2
11/1/2012,1977652,1802851
12/1/2012,1128739,948687
1/1/2013,1201944,1514667
2/1/2013,1863148,1834006
3/1/2013,1314851,1906060
4/1/2013,1283943,1978702
5/1/2013,1127964,1195606
6/1/2013,1773254,977214
7/1/2013,1929574,1127450
8/1/2013,1980411,1808161
9/1/2013,1405691,1182788
10/1/2013,1336790,937890
11/1/2013,1851053,1358400
12/1/2013,1472623,1214610
1/1/2014,1155116,1757052
2/1/2014,1571611,1935038
3/1/2014,1898348,1320348
4/1/2014,1444838,1934789
5/1/2014,1235087,950194
6/1/2014,1272040,1580656
7/1/2014,980781,1680164
8/1/2014,1391291,1115999
9/1/2014,1211125,1542148
10/1/2014,1020824,1782795
11/1/2014,1685081,926612
12/1/2014,1469254,1767071
1/1/2015,1168523,935897
2/1/2015,1602610,1450541
3/1/2015,1830278,1354876
4/1/2015,1275158,1412555
5/1/2015,1560961,1839718
6/1/2015,949948,1587130
7/1/2015,1413765,1494446
8/1/2015,1166141,1305105
9/1/2015,958975,1202219
10/1/2015,902696,1023987
11/1/2015,961441,1865628
12/1/2015,1363145,1954046
1/1/2016,1862878,1470741
2/1/2016,1723891,1042760
3/1/2016,1906747,1169012
4/1/2016,1963364,1927063
5/1/2016,1899735,1936915
6/1/2016,1300369,1430697
7/1/2016,1777108,1401210
8/1/2016,1597045,1566763
9/1/2016,1558287,1140057
10/1/2016,1965665,1953595
11/1/2016,1800438,937551
12/1/2016,1689152,1221895
1/1/2017,1607824,1963282
2/1/2017,1878431,1415658
3/1/2017,1730296,1947106
4/1/2017,1956756,1696780
5/1/2017,1746673,1662892
6/1/2017,989702,1537646
7/1/2017,1098812,1592064
8/1/2017,1861973,1892987
9/1/2017,1129596,1406514
10/1/2017,1528632,1725020
11/1/2017,925850,1795575

Output:

enter image description here

Desired Output:

I would like to insert a slider above this image outlining the number of months that will be shown in the graph above, something like so:

enter image description here

The user needs to be able to select an integer number of months between 1 and 24 inclusive using the slider. The latest month (in this case, Nov 2017) should always be shown. The slider will show only the number of months prior to the latest month as given by the value selected from the slider. (E.g., if 1 is selected from the slider, the graph will only consist of Oct 2017 and Nov 2017.)

I have already tried playing around with the code at https://bl.ocks.org/officeofjane/9b9e606e9876e34385cc4aeab188ed73, but this doesn't quite meet my needs for the following reason: the slider values and those in the data don't necessarily correspond for filtering (i.e., number of months vs. dates to be filtered). I do understand the basic idea of what I'll need to have the bar chart work with the slider, though - having to work with the exit and enter selections.

I don't need a complete solution, but any guidance on how to start on this would be appreciated.

Upvotes: 2

Views: 2217

Answers (1)

Shashank
Shashank

Reputation: 5660

Okay. So here's a code snippet with a simple jQuery slider (as I suggested in the comments) that filters out data for the last 24 months. To show the initial data i.e. for the original data, the slider has to be set to 0 (couldn't find any other way to do that). You can play around with the slider code.

var dataAsCsv = `date,col_1,col_2
11/1/2012,1977652,1802851
12/1/2012,1128739,948687
1/1/2013,1201944,1514667
2/1/2013,1863148,1834006
3/1/2013,1314851,1906060
4/1/2013,1283943,1978702
5/1/2013,1127964,1195606
6/1/2013,1773254,977214
7/1/2013,1929574,1127450
8/1/2013,1980411,1808161
9/1/2013,1405691,1182788
10/1/2013,1336790,937890
11/1/2013,1851053,1358400
12/1/2013,1472623,1214610
1/1/2014,1155116,1757052
2/1/2014,1571611,1935038
3/1/2014,1898348,1320348
4/1/2014,1444838,1934789
5/1/2014,1235087,950194
6/1/2014,1272040,1580656
7/1/2014,980781,1680164
8/1/2014,1391291,1115999
9/1/2014,1211125,1542148
10/1/2014,1020824,1782795
11/1/2014,1685081,926612
12/1/2014,1469254,1767071
1/1/2015,1168523,935897
2/1/2015,1602610,1450541
3/1/2015,1830278,1354876
4/1/2015,1275158,1412555
5/1/2015,1560961,1839718
6/1/2015,949948,1587130
7/1/2015,1413765,1494446
8/1/2015,1166141,1305105
9/1/2015,958975,1202219
10/1/2015,902696,1023987
11/1/2015,961441,1865628
12/1/2015,1363145,1954046
1/1/2016,1862878,1470741
2/1/2016,1723891,1042760
3/1/2016,1906747,1169012
4/1/2016,1963364,1927063
5/1/2016,1899735,1936915
6/1/2016,1300369,1430697
7/1/2016,1777108,1401210
8/1/2016,1597045,1566763
9/1/2016,1558287,1140057
10/1/2016,1965665,1953595
11/1/2016,1800438,937551
12/1/2016,1689152,1221895
1/1/2017,1607824,1963282
2/1/2017,1878431,1415658
3/1/2017,1730296,1947106
4/1/2017,1956756,1696780
5/1/2017,1746673,1662892
6/1/2017,989702,1537646
7/1/2017,1098812,1592064
8/1/2017,1861973,1892987
9/1/2017,1129596,1406514
10/1/2017,1528632,1725020
11/1/2017,925850,1795575`;

// Initialize jQuery slider
$('div#month-slider').slider({
	min: 0,
  max: 24,
  create: function() {
  	// add value to the handle on slider creation
  	$(this).find('.ui-slider-handle').html($( this ).slider( "value" ));
  },
  slide: function(e, ui) {
  	// change values on slider handle and label based on slider value
  	$('div.slider-container span.value').html(ui.value);
		$(e.target).find('.ui-slider-handle').html(ui.value);
    
    // calculate offset date based on slider value
  	var offsetDate = ui.value ? d3.timeMonth.offset(datePlusOneMonth, -ui.value) : min;
    // set x domain and re-render xAxis
		x.domain([offsetDate, datePlusOneMonth]);
    g.select('.x.axis').call(xMonthAxis);
    g.select('.yearaxis.axis').call(xYearAxis);


		// calcuate filtered data based on new offset date, set y axis domain and re-render y axis
    var filteredData = data.filter(function(d) { return d.date >= offsetDate; });
    y.domain([0, d3.max(filteredData, function(d) { return d.total; })]).nice();    
    g.select('.y.axis').transition().duration(200).call(yAxis);
    
    
    // re-render the bars based on new filtered data
    // the bars 
    var bars = g.select("g.bars")
     .selectAll("g")
     .data(d3.stack().keys(keys)(filteredData));

     var barRects = bars.enter().append("g").merge(bars)
     .attr('class', function(d) { return d.key; })
     .attr("fill", function(d) { return z(d.key); })
     .selectAll("rect")
     .data(function(d) { return d; });
     
     barRects.exit().remove();
     
     barRects.enter()
     .append("rect");
     
   barWidth = (width - margin.right- margin.left)/(filteredData.length+1);     

     g.select("g.bars").selectAll('g rect')
     .attr("x", function(d) { return x(d.data.date); })
     .attr("y", function(d) { return y(d[1]); })
     .attr("height", function(d) { return y(d[0]) - y(d[1]); })
     .attr("width", barWidth)
     .on("mousemove", function(d) {

        //Get this bar's x/y values, then augment for the tooltip
        var xPosition = parseFloat(d3.mouse(this)[0]) + 88;
        var yPosition = parseFloat(d3.mouse(this)[1]) - 29;
        var value = d.data[d3.select(this.parentNode).attr('class')]; // differentiating between col1 and col2 values

        //Update the tooltip position and value
        d3.select("#tooltip")
          .style("left", xPosition + "px")
          .style("top", yPosition + "px")                       
          .select("#count")
          .text(formatNum(value)); // return the value 

        d3.select("#tooltip")
          .style("left", xPosition + "px")
          .style("top", yPosition + "px")                       
          .select("#month")
          .text(d3.timeFormat("%B %Y")(d.data.date)); // return the value 

        //Show the tooltip
        d3.select("#tooltip").classed("hidden", false);

      })
     .on("mouseout", function() {
        //Hide the tooltip
        d3.select("#tooltip").classed("hidden", true);

     });
  }
})

var margin = {top: 20, right: 20, bottom: 50, left: 80},
    width = 1300 - margin.left - margin.right,
    height = 400 - margin.top - margin.bottom;

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

var g = svg.append("g")
           .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// parse the date / time
// look at the .csv in Notepad! DO NOT LOOK AT EXCEL!
var parseDate = d3.timeParse("%m/%d/%Y");


var x = d3.scaleTime()
          .range([0, width - margin.left - margin.right]);
var y = d3.scaleLinear().range([height, 0]);
var z = d3.scaleOrdinal()
          .range(["#CE1126", "#00B6D0"]); // red and blue 

var xMonthAxis = d3.axisBottom(x)
              .ticks(d3.timeMonth.every(1))
              .tickFormat(d3.timeFormat("%b")); // label every month

var xYearAxis = d3.axisBottom(x)
                  .ticks(d3.timeMonth.every(6))
                  .tickFormat(d3.timeFormat("%Y")); // label every year

var yAxis = d3.axisLeft(y).tickFormat(d3.format('.2s'));

var formatNum = d3.format(",")

var data = d3.csvParse(dataAsCsv, function(d, i, columns) {
	  for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
  d.total = t;
  return d;
});

data.forEach(function(d) {
  d.date = parseDate(d.date);
});

    var keys = data.columns.slice(1);
    var barWidth = (width - margin.right- margin.left)/(data.length+1);     

    data.sort(function(a, b) { return b.date - a.date; });


    x.domain(d3.extent( data, function(d){ return d.date }) );

    var max = x.domain()[1];
    var min = x.domain()[0];
    var datePlusOneMonth = d3.timeDay.offset(d3.timeMonth.offset(max, 1), -1); // last day of current month: move up one month, back one day 

    x.domain([min,datePlusOneMonth]);

    y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
    z.domain(keys);


    // the bars 
    g.append("g").classed('bars', true)
     .selectAll("g")
     .data(d3.stack().keys(keys)(data))
     .enter().append("g")
     .attr('class', function(d) { return d.key; })
     .attr("fill", function(d) { return z(d.key); })
     .selectAll("rect")
     .data(function(d) { return d; })
     .enter()
     .append("rect")
     .attr("x", function(d) { return x(d.data.date); })
     .attr("y", function(d) { return y(d[1]); })
     .attr("height", function(d) { return y(d[0]) - y(d[1]); })
     .attr("width", barWidth)
     .on("mousemove", function(d) {

        //Get this bar's x/y values, then augment for the tooltip
        var xPosition = parseFloat(d3.mouse(this)[0]) + 88;
        var yPosition = parseFloat(d3.mouse(this)[1]) - 29;
        var value = d.data[d3.select(this.parentNode).attr('class')]; // differentiating between col1 and col2 values

        //Update the tooltip position and value
        d3.select("#tooltip")
          .style("left", xPosition + "px")
          .style("top", yPosition + "px")                       
          .select("#count")
          .text(formatNum(value)); // return the value 

        d3.select("#tooltip")
          .style("left", xPosition + "px")
          .style("top", yPosition + "px")                       
          .select("#month")
          .text(d3.timeFormat("%B %Y")(d.data.date)); // return the value 

        //Show the tooltip
        d3.select("#tooltip").classed("hidden", false);

      })
     .on("mouseout", function() {
        //Hide the tooltip
        d3.select("#tooltip").classed("hidden", true);

     });


    // x-axis
    var monthAxis = g.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xMonthAxis);


    const firstDataYear = x.domain()[0];
  //  xYearAxis.tickValues([firstDataYear].concat(x.ticks()));

    var yearAxis = g.append("g")
                     .attr("class", "yearaxis axis")
                     .attr("transform", "translate(0," + (height + 25) + ")")
                     .call(xYearAxis);

    var valueAxis = g.append("g")
                 .attr("class", "y axis")
                 .call(yAxis);

    monthAxis.selectAll("g").select("text")
      .attr("transform","translate(" + barWidth/2 + ",0)");

    var options = d3.keys(data[0]).filter(function(key) { return key !== "date"; }).reverse();
    var legend = svg.selectAll(".legend")
                    .data(options.slice().filter(function(type){ return type != "total"}))
                    .enter().append("g")
                    .attr("class", "legend")
                    .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });

    legend.append("rect")
          .attr("x", width - 18)
          .attr("width", 18)
          .attr("height", 18)
          .style("fill", z);

    function capitalizeFirstLetter(string) {
        return string.charAt(0).toUpperCase() + string.slice(1);
    }

    legend.append("text")
          .attr("x", width - 24)
          .attr("y", 9)
          .attr("dy", ".35em")
          .style("text-anchor", "end")
          .text(function(d) { return capitalizeFirstLetter(d); });
    #tooltip {
        position: absolute;
        width: 200px;
        z-index: 2;
        height: auto;
        padding: 10px;
        background-color: white;
        -webkit-border-radius: 10px;
        -moz-border-radius: 10px;
        border-radius: 10px;
        -webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
        -moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
        box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
        pointer-events: none;
    }

    .legend {
        font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
        font-size: 60%;
    }

    #tooltip.hidden {
        display: none;
    }

    #tooltip p {
        margin: 0;
        font-family: sans-serif;
        font-size: 16px;
        line-height: 20px;
    }
    g[class="col_1"] rect:hover {
        fill:#80061b;
    }
    g[class="col_2"] rect:hover {
        fill:#008394;
    }
    div.slider-container {
      margin: 20px auto;
    }
    div#month-slider {
      width: 50%;
      margin: 0 auto;
    }
    div#month-slider .ui-slider-handle {
      text-align: center;
    }
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">


<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script
  src="https://code.jquery.com/ui/1.12.0/jquery-ui.min.js"
  integrity="sha256-eGE6blurk5sHj+rmkfsGYeKyZx3M4bG+ZlFyA7Kns7E="
  crossorigin="anonymous"></script>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.7.0/d3-legend.min.js"></script>

<div id="tooltip" class="hidden">
    <p><strong>Month: </strong><span id="month"></span><p>
    <p><strong>Value: </strong><span id="count"></span></p>
</div>

<div class="slider-container">
  <span>Number of months: <span class="value"></span></span>
  <div id="month-slider">

  </div>
</div>

Important code changes:

jQuery slider with a min value of 0 and max value of 24 (where 0 will show up the original data and 1-24 will be the data for the last number of months from the most recent date (Nov 17).

    // Initialize jQuery slider
    $('div#month-slider').slider({
        min: 0,
      max: 24,
      create: function() {
        // add value to the handle on slider creation
        $(this).find('.ui-slider-handle').html($( this ).slider( "value" ));
      },
      slide: function(e, ui) {
        // change values on slider handle and label based on slider value
        $('div.slider-container span.value').html(ui.value);
            $(e.target).find('.ui-slider-handle').html(ui.value);

    // calculate offset date based on slider value
    var offsetDate = ui.value ? d3.timeMonth.offset(datePlusOneMonth, -ui.value) : min;
    // set x domain and re-render xAxis
        x.domain([offsetDate, datePlusOneMonth]);
    g.select('.x.axis').call(xMonthAxis);
    g.select('.yearaxis.axis').call(xYearAxis);


        // calcuate filtered data based on new offset date, set y axis domain and re-render y axis
    var filteredData = data.filter(function(d) { return d.date >= offsetDate; });
    y.domain([0, d3.max(filteredData, function(d) { return d.total; })]).nice();    
    g.select('.y.axis').transition().duration(200).call(yAxis);


    // re-render the bars based on new filtered data
    // the bars 
    var bars = g.select("g.bars")
     .selectAll("g")
     .data(d3.stack().keys(keys)(filteredData));

     var barRects = bars.enter().append("g").merge(bars)
     .attr('class', function(d) { return d.key; })
     .attr("fill", function(d) { return z(d.key); })
     .selectAll("rect")
     .data(function(d) { return d; });

     barRects.exit().remove();

     barRects.enter()
     .append("rect");

     barWidth = (width - margin.right- margin.left)/(filteredData.length+1);     

     g.select("g.bars").selectAll('g rect')
     .attr("x", function(d) { return x(d.data.date); })
     .attr("y", function(d) { return y(d[1]); })
     .attr("height", function(d) { return y(d[0]) - y(d[1]); })
     .attr("width", barWidth)
     .on("mousemove", function(d) {

        //Get this bar's x/y values, then augment for the tooltip
        var xPosition = parseFloat(d3.mouse(this)[0]) + 88;
        var yPosition = parseFloat(d3.mouse(this)[1]) - 29;
        var value = d.data[d3.select(this.parentNode).attr('class')]; // differentiating between col1 and col2 values

        //Update the tooltip position and value
        d3.select("#tooltip")
          .style("left", xPosition + "px")
          .style("top", yPosition + "px")                       
          .select("#count")
          .text(formatNum(value)); // return the value 

        d3.select("#tooltip")
          .style("left", xPosition + "px")
          .style("top", yPosition + "px")                       
          .select("#month")
          .text(d3.timeFormat("%B %Y")(d.data.date)); // return the value 

        //Show the tooltip
        d3.select("#tooltip").classed("hidden", false);

      })
     .on("mouseout", function() {
        //Hide the tooltip
        d3.select("#tooltip").classed("hidden", true);

     });
     }
    })

Details:

  • Calculating the offset date using d3.timeMonth.offset based on the slider value.
  • Filtering out the data based on this offset date.
  • Re-rendering the X-Axis, Y-Axis and the bars using enter/update/exit pattern. I've added comments too.

Other changes:

  1. Added additional classes to the axes to differentiate between them. X-Axis:

    .attr("class", "x axis")
    

    X-year axis:

    .attr("class", "yearaxis axis")
    

    Y-Axis:

    .attr("class", "y axis")
    

    Added some CSS to the jQuery slider:

    div.slider-container {
      margin: 20px auto;
    }
    div#month-slider {
      width: 50%;
      margin: 0 auto;
    }
    div#month-slider .ui-slider-handle {
      text-align: center;
    }
    

    And a z-index to div#tooltip. Try removing that and you'll know why.

Let me know if you have any questions. Hope this helps. :)

Edit:

  1. Maintain the desired tick placement:

Added the following code within the slider once the barWidth is calcuated

    monthAxis.selectAll("g").select("text")
      .attr("transform","translate(" + barWidth/2 + ",0)");
  1. Maintain the year axis ticks on slide:

This took me a while to figure out but I came up with a different approach than the one answered here. With that approach, try taking off few rows from the data, you'll observe the mess of ticks.

So in the new approach, the tickValues for xYearAxis is computed as follows:

    const firstDataYear = x.domain()[0];
    var tickValues = x.ticks().filter(function(d) { return !d.getMonth()});
    if(firstDataYear.getFullYear() !== tickValues[0].getFullYear()) {
      tickValues = [firstDataYear].concat(tickValues); 
    }
    xYearAxis.tickValues(tickValues);

i.e. fetch all the years from the x domain and prepend the firstDataYear if absent. I find this to be a better approach.

Here's a demo link:

JS FIDDLE DEMO

Let me know if this solves the issues.

Upvotes: 2

Related Questions