block by curran 6f97aa94357cae4611c54a80c11f6128

Responsive Axes with ReactiveModel

Full Screen

This example shows one way to make responsive density-based linear axes using D3 4.0 and ReactiveModel. Combines Margin Convention II with ReactiveModel with Density-based Ticks. To experience the resize behavior, run this example full-screen and resize the browser.

web counter

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Margin Convention II 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: 16pt sans-serif;
      }

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

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

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

    <script>
      
      // Resizes the SVG container.
      function SVG(my){
        my("svg")
          ("width", 100)
          ("height", 100)
          (function (svg, width, height){
            svg
              .attr("width", width)
              .attr("height", height);
          }, "svg, width, 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")

          (function (g, marginLeft, marginTop){
            g.attr("transform", "translate(" + marginLeft + "," + marginTop + ")");
          }, "g, marginLeft, marginTop");
      }
      
      // Adds a gray rectangle inside the margin.
      function GrayRectangle(my){

        my("rect", function (g){
            return g.append("rect")
              .attr("fill", "lightgray")
              .attr("stroke", "gray");
          }, "g")

          (function (rect, innerWidth, innerHeight){
            rect
              .attr("width", innerWidth)
              .attr("height", innerHeight);
          }, "rect, innerWidth, innerHeight");
      }
      
      // Tracks the X scale on the model.
      function XScale(my){
        var scale = d3.scaleLinear();
        
        my("xScaleDomain")
        
          ("xScaleRange", function (innerWidth){
            return [0, innerWidth];
          }, "innerWidth")
          
          ("xScale", function(xScaleDomain, xScaleRange){
            return scale
              .domain(xScaleDomain)
              .range(xScaleRange);
          }, "xScaleDomain, xScaleRange");
      }
      
      // Tracks the Y scale on the model.
      function YScale(my){
        var scale = d3.scaleLinear();
        
        my("yScaleDomain")
        
          ("yScaleRange", function (innerHeight){
            return [innerHeight, 0];
          }, "innerHeight")

          ("yScale", function(yScaleDomain, yScaleRange){
            return scale
              .domain(yScaleDomain)
              .range(yScaleRange);
          }, "yScaleDomain, yScaleRange");
      }
      
      // Creates the X axis.
      function XAxis(my){
        
        var axis = d3.axisBottom();
        
        // Approximate number of pixels between ticks.
        my("xAxisTickSpacing", 70)
        
          ("xAxisG", function (g){
            return g.append("g")
              .attr("class", "x axis");
          }, "g")
        
          (function(xAxisG, innerHeight){
            xAxisG.attr("transform", "translate(0," + innerHeight + ")");
          }, "xAxisG, innerHeight")

          ("xAxisTicks", function (xAxisTickSpacing, innerWidth){
            return innerWidth / xAxisTickSpacing;
          }, "xAxisTickSpacing, innerWidth")

          ("xAxis", function(xAxisTicks, xScale){
            return axis
              .scale(xScale)
              .ticks(xAxisTicks);
          }, "xAxisTicks, xScale")

          (function(xAxisG, xAxis){
            xAxis(xAxisG);
          }, "xAxisG, xAxis");
      }
      
      // Creates the X axis.
      function YAxis(my){
        
        var axis = d3.axisLeft();
        
        // Approximate number of pixels between ticks.
        my("yAxisTickSpacing", 40)
        
          ("yAxisG", function (g){
            return g.append("g")
              .attr("class", "y axis");
          }, "g")

          ("yAxisTicks", function (yAxisTickSpacing, innerHeight){
            return innerHeight / yAxisTickSpacing;
          }, "yAxisTickSpacing, innerHeight")

          ("yAxis", function(yAxisTicks, yScale){
            return axis
              .scale(yScale)
              .ticks(yAxisTicks);
          }, "yAxisTicks, yScale")

          (function(yAxisG, yAxis){
            yAxis(yAxisG);
          }, "yAxisG, yAxis")
        
      }
      
      // The constructor for an "axis visualization" component.
      // Renders a gray rectangle with responsive axes.
      function AxesVis(){
        
        return ReactiveModel()
          .call(SVG)
          .call(Margin)
          .call(XScale)
          .call(XAxis)
          .call(YScale)
          .call(YAxis)
          .call(GrayRectangle)
        ;
        
      }
      
      // 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 MarginVis component.
      function main(){
        
        // Set up the MarginVis instance.
        var container = d3.select("#chart-container"),
            axesVis = AxesVis()
              .svg(container.append("svg"))
     			    .call(Resize, container.node())
              .xScaleDomain([0, 1000])
              .yScaleDomain([0, 1000]);
        
        // Change the margins around in a funky way.
        var time = 0;
        requestAnimationFrame(function updateMargin(){
          time += 0.01;
          
          axesVis
            .marginLeft(  (Math.sin(time     ) + 1.4) * 200)
            .marginRight( (Math.sin(time * 2 ) + 1.2) * 200)
            .marginTop(   (Math.sin(time * 3 ) + 1.5) * 100)
            .marginBottom((Math.sin(time * 4 ) + 1.5) * 100);
          
          requestAnimationFrame(updateMargin);
        });
      }
      main();
      
    </script>
  </body>
</html>