block by Rnhatch 3039755

Comparative Filter

Full Screen

This Gist uses Mike Bostock’s Crossfilter against a Dataset of mulitple entries. See in action at http://bl.ocks.org/Rnhatch/raw/3039755/

Data here has been scrubbed and anonymized

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<title>Comparative Filtering</title>
<style>


body {
  font-family: "Verdana";
  margin: 40px auto;
  width: 960px;
  min-height: 2000px;
  font-weight: 300;
  font-size: 12px;
}

#body {
  position: relative;
}

footer {
  padding: 2em 0 1em 0;
  font-size: 12px;
}


h1 + h2 {
  margin-top: 0;
}

h2 {
  font-weight: 400;
  font-size: 28px;
}

h3 {
  font-weight: 300;
  font-size: 18px;
}
#body > p {
  line-height: 1.5em;
  width: 640px;
  text-rendering: optimizeLegibility;
}

#charts {
  padding: 10px 0;
}

.chart {
  display: inline-block;
  height: 151px;
  margin-bottom: 20px;
}

.reset {
  padding-left: 1em;
  font-size: smaller;
  color: #ccc;
}

.background.bar {
  fill: #ccc;
}

.foreground.bar {
  fill: steelblue;
}

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

.axis text {
  font: 10px sans-serif;
}

.brush rect.extent {
  fill: steelblue;
  fill-opacity: .125;
}

.brush .resize path {
  fill: #eee;
  stroke: #666;
}


#Tenure-chart {
  width: 420px;
}

#Age-chart {
  width: 420px;
}

#date-chart {
  width: 900px;
}

#TotalPTD-chart {
  width: 420px;
}


#Division-chart {
  width: 420px;
}


#claim-list {
  min-height: 1024px;
}

#claim-list .date,
#claim-list .day {
  margin-bottom: .4em;
}

#claim-list .claim {
  line-height: 1.5em;
  background: #eee;
  width: 920px;
  margin-bottom: 1px;
}

#claim-list .time {
  color: #999;
}

#claim-list .claim div {
  display: inline-block;
  width: 100px;
}

#claim-list div.Diag_Cat {
  width: 240px;
  padding-right: 10px;
  text-align: left;
}

#claim-list div.Age {
  width: 50px;
  padding-right: 10px;
  text-align: right;
}

#claim-list div.State {
  width: 30px;
  padding-right: 10px;
  text-align: right;
}

#claim-list div.Division {
  width: 150px;
  padding-right: 10px;
  text-align: right;
}

#claim-list div.TotPTD,
#claim-list div.Tenure {
  width: 100px;
  padding-right: 10px;
  text-align: right;
}

#claim-list .early {
  color: green;
}

aside {
  position: absolute;
  left: 740px;
  font-size: smaller;
  width: 220px;
}

</style>

<div id="body">

<h2>Test of Rapid Filtering</h2>

<h3><span id="active">-</span> of <span id="total">-</span> objects selected.</h3>

<div id="charts">
 <div id="Tenure-chart" class="chart">
    <div class="title">Tenure (months)</div>
  </div>
  <div id="Age-chart" class="chart">
    <div class="title">Age</div>
  </div>
  <div id="date-chart" class="chart">
    <div class="title">Date</div>
  </div>
  <div id="TotalPTD-chart" class="chart">
    <div class="title">Total Paid to Date</div>
  </div>
  <div id="Division-chart" class="chart">
    <div class="title">Division</div>
  </div>
 
 </div>

<h3> List of representative objects </h3>
<div id="lists">
  <div id="claim-list" class="list"></div>
</div>


</div>

<script src="//square.github.com/crossfilter/crossfilter.v1.min.js"></script>
<script src="//square.github.com/crossfilter/d3.v2.min.js"></script>

<script>

    d3.csv("claims.csv", function(claims) {

        // Various formatters.
        var formatNumber = d3.format(",r"),
      formatChange = d3.format("+,d"),
      formatDate = d3.time.format("%B %d, %Y"),
      formatTime = d3.time.format("%I:%M %p");

        // A nest operator, for grouping the claim list.
        var nestByDate = d3.nest()
      .key(function(d) { return d3.time.day(d.date); });

        // A little coercion, since the CSV is untyped.
        claims.forEach(function(d, i) {
            d.index = i;
            d.date = parseDate(d.date);
            d.Tenure = +d.Tenure;
            d.Age = +d.Age;
            d.TotalPTD = +d.TotalPTD;
            d.Division = +d.Division;

         

        });

        // Create the crossfilter for the relevant dimensions and groups.
        var claim = crossfilter(claims),
       all = claim.groupAll(),
      date = claim.dimension(function(d) { return d3.time.week(d.date); }),
      dates = date.group(),

         //      hour = claim.dimension(function(d) { return d.date.getHours() + d.date.getMinutes() / 60; }),
         //        hours = hour.group(Math.floor),

      Tenure = claim.dimension(function(d) { return Math.max(0, Math.min(800, d.Tenure)); }),
      Tenures = Tenure.group(function(d) { return Math.floor(d / 20) * 20; }),

      Age = claim.dimension(function(d) { return Math.min(80, d.Age); }),
      Ages = Age.group(function(d) { return Math.floor(d / 3) * 3; });

      TotalPTD = claim.dimension(function(d) { return Math.min(100000, d.TotalPTD); }),
      TotalPTDs = TotalPTD.group(function(d) { return Math.floor(d / 5000) * 5000; });

      Division = claim.dimension(function(d) { return Math.min(125, d.Division); }),
      Divisions = Division.group(function(d) { return Math.floor(d / 5) * 5; });
        var charts = [

         //    barChart()
         //        .dimension(hour)
         //     .group(hours)
         //          .x(d3.scale.linear()
         //             .domain([0, 1])
         //           .rangeRound([0, 10])),

    barChart()
        .dimension(Tenure)
        .group(Tenures)
      .x(d3.scale.linear()
        .domain([0, 800])
        .rangeRound([0, 400])),

    barChart()
        .dimension(Age)
        .group(Ages)
      .x(d3.scale.linear()
        .domain([0, 80])
        .rangeRound([0, 300])),

     barChart()
        .dimension(date)
        .group(dates)
        .round(d3.time.week.round)
      .x(d3.time.scale()
        .domain([new Date(2011, 0, 1), new Date(2011, 12, 1)])
        .rangeRound([0, 800]))
        .filter([new Date(2011, 1, 1), new Date(2011, 2, 1)]),

    barChart()
        .dimension(TotalPTD)
        .group(TotalPTDs)
      .x(d3.scale.linear()
        .domain([0, 100000])
        .rangeRound([0, 400])),

    barChart()
        .dimension(Division)
        .group(Divisions)
      .x(d3.scale.linear()
        .domain([0, 125])
        .rangeRound([0, 300]))

  ];

        // Given our array of charts, which we assume are in the same order as the
        // .chart elements in the DOM, bind the charts to the DOM and render them.
        // We also listen to the chart's brush events to update the display.
        var chart = d3.selectAll(".chart")
      .data(charts)
      .each(function(chart) { chart.on("brush", renderAll).on("brushend", renderAll); });

        // Render the initial lists.
        var list = d3.selectAll(".list")
      .data([claimList]);

        // Render the total.
        d3.selectAll("#total")
      .text(formatNumber(claim.size()));

        renderAll();

        // Renders the specified chart or list.
        function render(method) {
            d3.select(this).call(method);
        }

        // Whenever the brush moves, re-rendering everything.
        function renderAll() {
            chart.each(render);
            list.each(render);
            d3.select("#active").text(formatNumber(all.value()));
        }

        // Like d3.time.format, but faster.
        function parseDate(d) {
            return new Date(2011,
        d.substring(0, 2) - 1,
        d.substring(2, 4),
        d.substring(4, 6),
        d.substring(6, 8));
        }

        window.filter = function(filters) {
            filters.forEach(function(d, i) { charts[i].filter(d); });
            renderAll();
        };

        window.reset = function(i) {
            charts[i].filter(null);
            renderAll();
        };

        function claimList(div) {
            var claimsByDate = nestByDate.entries(date.top(40));

            div.each(function() {
                var date = d3.select(this).selectAll(".date")
          .data(claimsByDate, function(d) { return d.key; });

                date.enter().append("div")
          .attr("class", "date")
        .append("div")
          .attr("class", "day")
          .text(function(d) { return formatDate(d.values[0].date); });

                date.exit().remove();

                var claim = date.order().selectAll(".claim")
          .data(function(d) { return d.values; }, function(d) { return d.index; });

                var claimEnter = claim.enter().append("div")
          .attr("class", "claim");

                claimEnter.append("div")
          .attr("class", "CL_Number")
          .text(function(d) { return d.CL_Number; });


                claimEnter.append("div")
          .attr("class", "Diag_Cat")
          .text(function(d) { return d.Diag_Cat; });

                claimEnter.append("div")
          .attr("class", "Age")
          .text(function(d) { return formatNumber(d.Age) + " yrs."; });

                claimEnter.append("div")
          .attr("class", "Tenure")
          .text(function(d) { return formatNumber(d.Tenure) + " mos."; });

                claimEnter.append("div")
          .attr("class", "State")
          .text(function(d) { return d.State; });

                claimEnter.append("div")
          .attr("class", "Division")
          .text(function(d) { return "Division "  + formatNumber(d.Division); });

                claimEnter.append("div")
          .attr("class", "TotalPTD")
          .text(function(d)  { return "$ "  + formatNumber(d.TotalPTD); });


                claim.exit().remove();

                claim.order();
            });
        }

        function barChart() {
            if (!barChart.id) barChart.id = 0;

            var margin = { top: 10, right: 10, bottom: 20, left: 10 },
        x,
        y = d3.scale.linear().range([100, 0]),
        id = barChart.id++,
        axis = d3.svg.axis().orient("bottom"),
        brush = d3.svg.brush(),
        brushDirty,
        dimension,
        group,
        round;

            function chart(div) {
                var width = x.range()[1],
          height = y.range()[0];

                y.domain([0, group.top(1)[0].value]);

                div.each(function() {
                    var div = d3.select(this),
            g = div.select("g");

                    // Create the skeletal chart.
                    if (g.empty()) {
                        div.select(".title").append("a")
              .attr("href", "javascript:reset(" + id + ")")
              .attr("class", "reset")
              .text("reset")
              .style("display", "none");

                        g = div.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 + ")");

                        g.append("clipPath")
              .attr("id", "clip-" + id)
            .append("rect")
              .attr("width", width)
              .attr("height", height);

                        g.selectAll(".bar")
              .data(["background", "foreground"])
            .enter().append("path")
              .attr("class", function(d) { return d + " bar"; })
              .datum(group.all());

                        g.selectAll(".foreground.bar")
              .attr("clip-path", "url(#clip-" + id + ")");

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

                        // Initialize the brush component with pretty resize handles.
                        var gBrush = g.append("g").attr("class", "brush").call(brush);
                        gBrush.selectAll("rect").attr("height", height);
                        gBrush.selectAll(".resize").append("path").attr("d", resizePath);
                    }

                    // Only redraw the brush if set externally.
                    if (brushDirty) {
                        brushDirty = false;
                        g.selectAll(".brush").call(brush);
                        div.select(".title a").style("display", brush.empty() ? "none" : null);
                        if (brush.empty()) {
                            g.selectAll("#clip-" + id + " rect")
                .attr("x", 0)
                .attr("width", width);
                        } else {
                            var extent = brush.extent();
                            g.selectAll("#clip-" + id + " rect")
                .attr("x", x(extent[0]))
                .attr("width", x(extent[1]) - x(extent[0]));
                        }
                    }

                    g.selectAll(".bar").attr("d", barPath);
                });

                function barPath(groups) {
                    var path = [],
            i = -1,
            n = groups.length,
            d;
                    while (++i < n) {
                        d = groups[i];
                        path.push("M", x(d.key), ",", height, "V", y(d.value), "h9V", height);
                    }
                    return path.join("");
                }

                function resizePath(d) {
                    var e = +(d == "e"),
            x = e ? 1 : -1,
            y = height / 3;
                    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);
                }
            }

            brush.on("brushstart.chart", function() {
                var div = d3.select(this.parentNode.parentNode.parentNode);
                div.select(".title a").style("display", null);
            });

            brush.on("brush.chart", function() {
                var g = d3.select(this.parentNode),
          extent = brush.extent();
                if (round) g.select(".brush")
          .call(brush.extent(extent = extent.map(round)))
        .selectAll(".resize")
          .style("display", null);
                g.select("#clip-" + id + " rect")
          .attr("x", x(extent[0]))
          .attr("width", x(extent[1]) - x(extent[0]));
                dimension.filterRange(extent);
            });

            brush.on("brushend.chart", function() {
                if (brush.empty()) {
                    var div = d3.select(this.parentNode.parentNode.parentNode);
                    div.select(".title a").style("display", "none");
                    div.select("#clip-" + id + " rect").attr("x", null).attr("width", "100%");
                    dimension.filterAll();
                }
            });

            chart.margin = function(_) {
                if (!arguments.length) return margin;
                margin = _;
                return chart;
            };

            chart.x = function(_) {
                if (!arguments.length) return x;
                x = _;
                axis.scale(x);
                brush.x(x);
                return chart;
            };

            chart.y = function(_) {
                if (!arguments.length) return y;
                y = _;
                return chart;
            };

            chart.dimension = function(_) {
                if (!arguments.length) return dimension;
                dimension = _;
                return chart;
            };

            chart.filter = function(_) {
                if (_) {
                    brush.extent(_);
                    dimension.filterRange(_);
                } else {
                    brush.clear();
                    dimension.filterAll();
                }
                brushDirty = true;
                return chart;
            };

            chart.group = function(_) {
                if (!arguments.length) return group;
                group = _;
                return chart;
            };

            chart.round = function(_) {
                if (!arguments.length) return round;
                round = _;
                return chart;
            };

            return d3.rebind(chart, brush, "on");
        }
    });

</script>