block by curran aff67b589d0f775a6a7b75f94a05fb14

Dynamic Baseball Scatter Plot

Full Screen

A dynamic scatter plot of Baseball data that uses ReactiveModel.

Inspired by Baseball Scatterplot.

Forked from Responsive Axes with ReactiveModel.

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Scatter Plot with ReactiveModel</title>
    <script src="//d3js.org/d3.v4.0.0-alpha.49.min.js"></script>
    <script src="//datavis-tech.github.io/reactive-model/reactive-model-v0.11.0.min.js"></script>
    <style>

      /* Make the chart container fill the page using CSS. */
      #chart-container {
        position: fixed;
        left: 0px;
        right: 0px;
        top: 0px;
        bottom: 0px;
      }
    
      .axis text {
        font: 10pt sans-serif;
      }

      .axis-label {
        font: 14pt sans-serif;
      }

      .axis path {
        display: none;
      }
      .tick line {
        fill: none;
        stroke: #DDD;
        stroke-width: 1px;
        shape-rendering: crispEdges;
      }
      
    </style>
  </head>
  <body>

    <!-- The SVG graphics will be injected into this div. -->
    <div id="chart-container"></div>

    <script>

      var transitionDuration = 800;
      
      // Resizes the SVG container.
      function SVG(my){
        my("svg")
          ("width", 100)
          ("height", 100)
        
          ("svg-width", function (svg, width){
            svg.attr("width", width);
          }, "svg, width")
        
          ("svg-height", function (svg, height){
            svg.attr("height", height);
          }, "svg, height");
      }
      
      // Encapsulates the margin convention.
      function Margin(my){
        
        my("marginTop", 50)
          ("marginBottom", 50)
          ("marginLeft", 50)
          ("marginRight", 50)
        
          ("innerWidth", function (width, marginLeft, marginRight){
            return width - marginLeft - marginRight;
          }, "width, marginLeft, marginRight")

          ("innerHeight", function (height, marginTop, marginBottom){
            return height - marginTop - marginBottom;
          }, "height, marginTop, marginBottom")

          ("g", function (svg){
            return svg.append("g");
          }, "svg")

          ("g-transform", function (g, marginLeft, marginTop){
            g.attr("transform", "translate(" + marginLeft + "," + marginTop + ")");
          }, "g, marginLeft, marginTop");
        
      }

      // Adds the "data" property.
      function Data(my){
        my("data");
      }

      // Adds a column and accessor for the given column name.
      function Column(my, name){
        my(name + "Column")
          (name + "Accessor", function (column){
            return function (d){ return d[column]; };
          }, name + "Column");
      }
      
      // Sets up a linear scale with the given name.
      function ScaleLinear(my, name){
        var scale = d3.scaleLinear();
        
        my(name + "ScaleDomain", function (data, accessor){
          return d3.extent(data, accessor);
        }, "data, " + name + "Accessor");
        
        if(name === "x"){
          my("xScaleRange", function (innerWidth){
            return [0, innerWidth];
          }, "innerWidth");
        } else if(name === "y"){
          my("yScaleRange", function (innerHeight){
            return [innerHeight, 0];
          }, "innerHeight");
        }
          
        my(name + "Scale", function(domain, range){
            return scale
              .domain(domain)
              .range(range)
              .nice();
          }, name + "ScaleDomain, " + name + "ScaleRange")

          (name + "Scaled", function(scale, accessor){
            return function (d){
              return scale(accessor(d));
            };
          }, name + "Scale, " + name + "Accessor");
      }

      // Sets up an axis with the given name ("x" or "y")
      function Axis(my, name){

        var axisLengthProperty;
        var tickSizeProperty;
        var axis;

        // Approximate number of pixels between ticks.
        my(name + "AxisTickSpacing", 70)
        
          (name + "AxisG", function (g){
            return g.append("g").attr("class", name + " axis");
          }, "g");
        
        if(name === "x"){
          axisLengthProperty = "innerWidth";
          tickSizeProperty = "innerHeight";
          axis = d3.axisBottom();

          my(function(xAxisG, innerHeight){
            xAxisG.attr("transform", "translate(0," + innerHeight + ")");
          }, "xAxisG, innerHeight");

        } else if(name === "y"){
          axisLengthProperty = "innerHeight";
          tickSizeProperty = "innerWidth";
          axis = d3.axisLeft();
        }

        my(name + "AxisTicks", function (xAxisTickSpacing, axisLength){
            return axisLength / xAxisTickSpacing;
          }, name + "AxisTickSpacing," + axisLengthProperty)

          (name + "Axis", function(ticks, scale, tickSize){
            return axis
              .scale(scale)
              .tickSize(-tickSize)
              .ticks(ticks);
          }, name + "AxisTicks, " + name + "Scale," + tickSizeProperty)

          (function(axisG, axis){
            axis(axisG.transition().duration(transitionDuration));
          }, name + "AxisG, " + name + "Axis");
        
      }

      function AxisLabel(my, name){
        my(name + "AxisLabelOffset", 15)

          (name + "AxisLabel", function (svg){
            return svg.append("text")
              .attr("class", name + " axis-label")
              .style("text-anchor", "middle");
          }, "svg")

          my(function (axisLabel, column){
            axisLabel.text(column);
          }, name + "AxisLabel," + name + "Column");

        if(name === "x"){

          my(function (axisLabel, marginLeft, innerWidth){
              axisLabel.attr("x", marginLeft + innerWidth / 2);
            }, "xAxisLabel, marginLeft, innerWidth")

            (function (axisLabel, height, offset){
              axisLabel.attr("y", height - offset);
            }, "xAxisLabel, height, xAxisLabelOffset");

        } else if(name === "y"){
          my(function (label, offset, innerHeight, marginTop){
            label.attr("transform", [
              "translate(",
                offset,
              ",",
                marginTop + innerHeight / 2,
              ") rotate(-90)"
            ].join(""));
          }, "yAxisLabel, yAxisLabelOffset, innerHeight, marginTop");
        }
      }

      function ScatterPlotMarks(my){

        my("circleG", function (g){
            return g.append("g");
          }, "g")

          (function (circleG, data, xScaled, yScaled){
            var circle = circleG.selectAll("circle").data(data);
            circle.exit().remove();
            circle.enter().append("circle")
                .attr("r", 5)
                .attr("fill", "none")
                .attr("stroke", "rgba(0,0,0,0.7)")
              .merge(circle)
                .transition().duration(transitionDuration)
                .attr("cx", xScaled)
                .attr("cy", yScaled);
          }, "circleG, data, xScaled, yScaled");
      }
      
      // The constructor for an "axis visualization" component.
      // Renders a gray rectangle with responsive axes.
      function ScatterPlot(){
        
        return ReactiveModel()
          .call(SVG)
          .call(Margin)
          .call(Data)
          .call(Column, "x")
          .call(Column, "y")
          .call(ScaleLinear, "x")
          .call(ScaleLinear, "y")
          .call(ScatterPlotMarks)
          .call(Axis, "x")
          .call(Axis, "y")
          .call(AxisLabel, "x")
          .call(AxisLabel, "y");
      }
      
      // Respond to resize by setting width and height from DOM element.
      function Resize(my, el){
        function resize(){
          my.width(el.clientWidth)
            .height(el.clientHeight);
        }
        resize();
        window.addEventListener("resize", resize);
      }
      
      // The main program that uses the ScatterPlot component.
      function main(){
        
        // Set up the ScatterPlot instance.
        var container = d3.select("#chart-container"),
            scatterPlot = ScatterPlot()
              .svg(container.append("svg"))
              .call(Resize, container.node());

        // Tweak properties
        scatterPlot
          .marginTop(10)
          .marginBottom(40)
          .marginLeft(70)
          .marginRight(40)
          .xAxisLabelOffset(10)
          .yAxisLabelOffset(20)
          .yAxisTickSpacing(20);

        // Configure the visual encoding.
        scatterPlot
          .xColumn("atbat")
          .yColumn("hits");

        var interestingColumns = ["atbat", "hits", "homeruns", "rbi", "runs", "walks", "years"];

        // Load the data.
        d3.csv("Baseball.csv", function (d){
          interestingColumns.forEach(function (column){
            d[column] = +d[column];
          });
          return d;
        }, function(err, data){
          scatterPlot.data(data);
        });

        function randomColumn(){
          var i = Math.floor(Math.random() * interestingColumns.length);
          return interestingColumns[i];
        }

        // Update the X and Y columns randomly.
        setInterval(function (){
          scatterPlot.xColumn(randomColumn());
        }, 2345);
        setInterval(function (){
          scatterPlot.yColumn(randomColumn());
        }, 3456);

      }
      main();
      
    </script>
  </body>
</html>