Sunday, February 4, 2018

Change jQuery slider to SVG Slider

Leave a Comment

This is a follow-up question to Put title above slider in D3 svg element. I've decided that the best option is to use a SVG slider and put the title on top of it.

Here is my current implementation, using a jQuery slider (note that the title and slider need their places switched):

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`;        var margin = {      top: 50,      right: 20,      bottom: 50,      left: 80    },    width = 1400 - 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 sessions_desktop_col = "#CE1126",    sessions_mobile_col = "#00B6D0";    var x = d3.scaleTime()    .range([0, width - margin.left - margin.right]);    var y = d3.scaleLinear().range([height, 0]);  var z = d3.scaleOrdinal()    .range([sessions_desktop_col, sessions_mobile_col]); // 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')).tickSize(-width + margin.left + margin.right);    var formatNum = d3.format(",");    var barPad = 8;    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);    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);    // x-axis  var monthAxis = g.append("g")    .attr("class", "x axis")    .attr("transform", "translate(0," + height + ")")    .call(xMonthAxis);    var defaultSliderValue = 25;    // calculate offset date based on initial slider value  var offsetDate = defaultSliderValue ? d3.timeDay.offset(d3.timeMonth.offset((new Date(datePlusOneMonth.getFullYear(), datePlusOneMonth.getMonth() - defaultSliderValue)), 1), -1) : min  // set x domain and re-render xAxis  x.domain([offsetDate, datePlusOneMonth]);  g.select('.x.axis').call(xMonthAxis);    var filteredData = data.filter(function(d) {    return d.date >= offsetDate;  });  barWidth = (width - margin.right - margin.left) / (filteredData.length + barPad);    // the bars   g.append("g").classed('bars', true)    .selectAll("g")    .data(d3.stack().keys(keys)(filteredData))    .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]) + 50;      var value = d.data[d3.select(this.parentNode).attr('class')]; // differentiating between col1 and col2 values      var totalCount = d.data["total"];      var valuePercent = value / totalCount;      var valueClass = d3.select(this.parentNode).attr('class').replace("col_1", "Column 1").replace("col_2", "Column 2");        //Update the tooltip position and value      d3.select("#tooltip")        .style("left", xPosition + "px")        .style("top", yPosition + "px")        d3.select("#tooltip")        .select("#count")        .text(formatNum(value)); // return the value         d3.select("#tooltip")        .select("#totalCount")        .text(formatNum(totalCount)); // return the value         d3.select("#tooltip")        .select("#percent")        .text((valuePercent * 100).toFixed(1) + "%"); // return the value         d3.select("#tooltip")        .select("#month")        .text(d3.timeFormat("%B %Y")(d.data.date)); // return the value         d3.select("#tooltip")        .select("#sessionLabel")        .text(valueClass); // return the class         //Show the tooltip      d3.select("#tooltip").classed("hidden", false);      })    .on("mouseout", function() {      //Hide the tooltip      d3.select("#tooltip").classed("hidden", true);      });    g.selectAll("bars")    .attr("transform", "translate(0,300)");    const firstDataYear = x.domain()[0];  if (firstDataYear.getMonth() == 11) { // When .getmonth() == 11, this shows two years overlapping with each other, making the label look ugly    const firstDataYearOffset = d3.timeDay.offset(firstDataYear, 1);    var tickValues = x.ticks().filter(function(d) {      return !d.getMonth()    });    if (tickValues.length && firstDataYearOffset.getFullYear() !== tickValues[1].getFullYear()) {      tickValues = [firstDataYearOffset].concat(tickValues);    } else {      tickValues = [firstDataYearOffset];    }  } else {    var tickValues = x.ticks().filter(function(d) {      return !d.getMonth()    });    if (tickValues.length && firstDataYear.getFullYear() !== tickValues[0].getFullYear()) {      tickValues = [firstDataYear].concat(tickValues);    } else {      tickValues = [firstDataYear];    }  }    xYearAxis.tickValues(tickValues);    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 grid")    .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)    .attr('class', function(d) {      return d;    })    .style("fill", z)    .on("mouseover", function(d) {      if (d == 'col_2') {        d3.selectAll(".col_2").style("fill", '#008394');      }      if (d == 'col_1') {        d3.selectAll(".col_1").style("fill", '#80061b');      };      })    .on("mouseout", function() {      d3.selectAll(".col_2").style("fill", col_2_col);      d3.selectAll(".col_1").style("fill", col_1_col);    });    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.replace("col_1", "Column 1").replace("col_2", "Column 2"));    })    .on("mouseover", function(d) {      if (d == 'col_2') {        d3.selectAll(".col_2").style("fill", '#008394');      }      if (d == 'col_1') {        d3.selectAll(".col_1").style("fill", '#80061b');      };      })    .on("mouseout", function() {      d3.selectAll(".col_2").style("fill", col_2_col);      d3.selectAll(".col_1").style("fill", col_1_col);    });;    // Initialize jQuery slider  $('div#month-slider').slider({    min: 1,    max: 25,    value: defaultSliderValue,    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	      $(e.target).find('.ui-slider-handle').html(ui.value);        // calculate offset date based on slider value      var offsetDate = ui.value ? d3.timeDay.offset(d3.timeMonth.offset((new Date(datePlusOneMonth.getFullYear(), datePlusOneMonth.getMonth() - ui.value)), 1), -1) : min      // set x domain and re-render xAxis      x.domain([offsetDate, datePlusOneMonth]);      g.select('.x.axis').call(xMonthAxis);        const firstDataYear = x.domain()[0];      if (firstDataYear.getMonth() == 11) { // When .getmonth() == 11, this shows two years overlapping with each other, making the label look ugly        const firstDataYearOffset = d3.timeDay.offset(firstDataYear, 1);        var tickValues = x.ticks().filter(function(d) {          return !d.getMonth()        });        if (tickValues.length == 2 && firstDataYearOffset.getFullYear() !== tickValues[1].getFullYear()) {          tickValues = [firstDataYearOffset].concat(tickValues);        } else {          tickValues = [firstDataYearOffset];        }      } else {        var tickValues = x.ticks().filter(function(d) {          return !d.getMonth()        });        if (tickValues.length && firstDataYear.getFullYear() !== tickValues[0].getFullYear()) {          tickValues = [firstDataYear].concat(tickValues);        } else {          tickValues = [firstDataYear];        }      }      xYearAxis.tickValues(tickValues);      g.select('.yearaxis.axis').call(xYearAxis);          // calculate 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 + barPad);        monthAxis.selectAll("g").select("text")        .attr("transform", "translate(" + barWidth / 2 + ",0)");        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]) + 50;          var value = d.data[d3.select(this.parentNode).attr('class')]; // differentiating between col1 and col2 values          var totalCount = d.data["total"];          var valuePercent = value / totalCount;          var valueClass = d3.select(this.parentNode).attr('class').replace("col_1", "Column 1").replace("col_2", "Column 2");            //Update the tooltip position and value          d3.select("#tooltip")            .style("left", xPosition + "px")            .style("top", yPosition + "px")            d3.select("#tooltip")            .select("#count")            .text(formatNum(value)); // return the value             d3.select("#tooltip")            .select("#totalCount")            .text(formatNum(totalCount)); // return the value             d3.select("#tooltip")            .select("#percent")            .text((valuePercent * 100).toFixed(1) + "%"); // return the value             d3.select("#tooltip")            .select("#month")            .text(d3.timeFormat("%B %Y")(d.data.date)); // return the value             d3.select("#tooltip")            .select("#sessionLabel")            .text(valueClass); // return the class             //Show the tooltip          d3.select("#tooltip").classed("hidden", false);          })        .on("mouseout", function() {          //Hide the tooltip          d3.select("#tooltip").classed("hidden", true);        });      }  });    g.append("text")    .attr("class", "title")    .attr("x", (width / 2))    .attr("y", 0 - (margin.top / 2))    .attr("text-anchor", "middle")    .text("Title");
#tooltip {    position: absolute;    width: 290px;    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 20px 20px 80px;  }    div#month-slider {    width: 50%;    margin: 0px 0px 0px 330px;    background: #e3eff4;  }    div#month-slider .ui-slider .ui-slider-handle {    width: 25%;  }    div#month-slider .ui-slider-handle {    text-align: center;  }    .title {    font-size: 30px;    font-weight: bold;  }    .grid line {    stroke: lightgrey;    stroke-opacity: 0.7;    shape-rendering: crispEdges;  }    .grid path {    stroke-width: 0;    pointer-events: none;  }
<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>    <body>      <div id="tooltip" class="hidden">      <p><strong>Month: </strong><span id="month"></span>        <p>          <p><strong><span id="sessionLabel"></span>: </strong><span id="count"></span> (<span id="percent"></span>)</p>          <p><strong>Total: </strong><span id="totalCount"></span></p>    </div>      <div class="slider-container" style='inline-block;'>      <span style='margin-left:6em;display:inline-block;position:relative;top:15px;'><strong>Number of Months Displayed:</strong></span>      <div id="month-slider" style='inline-block;'></div>    </div>

I've seen https://bl.ocks.org/mbostock/6452972, but most of it is gibberish to me. In particular, I get lost as soon as I get to the track-inset and track-overlay class assignments - I don't understand what the purposes of these are.

I also started by commenting out the jQuery slider and inserting the code

var slider = g.append("g")               .attr("id", "month-slider")               .attr("transform", "translate(" + (margin.left + width/2) + ",0)");    

above the monthAxis variable declaration, but beyond this - i.e., the bar filtering based on the value of the slider, as well as the label attached to the slider - I'm lost on how to do. Any help is appreciated.

0 Answers

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment