Search code examples
d3.jsrangescale

D3 v4 Scaling Data to Range


Data is not correctly scaling to range.

I am appending an svg to a div,

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

and then a group to that svg.

 // create group
    var group = svg.append("g")
      .attr("class", "main_group")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

Then scaling for the x axis,

var x = d3.scaleLinear()
      .domain(0, d3.max(function(d) {
        return d.x_pos
      })).range([0, width]);

The width is set with,

  var margin = {
            top: 50,
            right: 50,
            bottom: 50,
            left: 50
          },
          width = 600 - margin.left - margin.right, // width=500
          height = 600 - margin.top - margin.bottom; // height=500

When I change the x_pos value to greater than 800 the rendered rect is outside the svg. At the moment at x_pos=525 the top left of the rect is visible.

  { 
        // "x_pos": "800",
        "x_pos": "525",
        "y_pos": "300",
        "name": "Dd"
      },

I expected the x_pos value in the data to always scale to the range [0, width].

Why is the data not scaling to the range?

github and gh-pages demo

    svg {
      border: 2px solid gray;
    }
<!DOCTYPE html>
<meta charset="utf-8">

<body>
  <div id="box">
  </div>
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script>
    data = [{
        "x_pos": "100",
        "y_pos": "400",
        "name": "Aa"
      },
      {
        "x_pos": "200",
        "y_pos": "200",
        "name": "Bb"
      },
      {
        "x_pos": "300",
        "y_pos": "100",
        "name": "Cc"
      },
      {
        // "x_pos": "300",
        "x_pos": "525",
        "y_pos": "300",
        "name": "Dd"
      },
      {
        "x_pos": "0",
        "y_pos": "0",
        "name": "Ee"
      }
    ]

    data.forEach(function(d) {
      d.x_pos = +d.x_pos;
      d.y_pos = +d.y_pos;
    });

    var margin = {
        top: 50,
        right: 50,
        bottom: 50,
        left: 50
      },
      width = 600 - margin.left - margin.right, // width=500
      height = 600 - margin.top - margin.bottom; // height=500

    var x = d3.scaleLinear()
      .domain(0, d3.max(function(d) {
        return d.x_pos
      })).range([0, width]);

    var y = d3.scaleLinear()
      .domain([0, d3.max(data, function(d) {
        return d.y_pos
      })]).range([height, 0]);

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

    // create group
    var group = svg.append("g")
      .attr("class", "main_group")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // bind the data
    var boxes = group.selectAll("boxes")
      .data(data)
      .enter();

    // add rects
    boxes.append("rect")
      .attr("class", "boxes")
      .attr("x", function(d) {
        return d.x_pos
      })
      .attr("y", function(d) {
        return d.y_pos
      })
      .attr("rx", "5px")
      .attr("width", 60)
      .attr("height", 20)
      .attr("stroke", "darkgray")
      .attr("fill", "lightblue");

    // add text
    boxes.append("text")
      .attr("class", "legend_text")
      .attr("x", function(d) {
        return d.x_pos
      })
      .attr("y", function(d) {
        return d.y_pos
      })
      .attr("dx", "0.5em")
      .attr("dy", "1.0em")
      .style("font-weight", "bold")
      .text(function(d) {
        return d.name;
      });

    // add text coordinates
    boxes.append("text")
      .attr("class", "legend_text")
      .attr("x", function(d) {
        return d.x_pos
      })
      .attr("y", function(d) {
        return d.y_pos
      })
      .attr("dx", "-2.5em")
      .attr("dy", "-.5em")
      .style("font-weight", "bold")
      .text(function(d) {
        return "(" + d.x_pos + ", " + d.y_pos + ")";
      });
  </script>


Solution

  • The issue here is very simple: you are not using any scale to position the rectangles:

    .attr("x", function(d) {
        return d.x_pos
    })
    

    Solution: Use your scale.

    .attr("x", function(d) {
        return x(d.x_pos)
        //     ^--- scale here
    })
    

    Also, you're not setting your domain correctly. It should be:

    var x = d3.scaleLinear()
        .domain([0, d3.max(data, function(d) {
            return d.x_pos
        })]).range([0, width]);
    

    Here is your code with those changes:

    svg {
          border: 2px solid gray;
        }
    <!DOCTYPE html>
    <meta charset="utf-8">
    
    <body>
      <div id="box">
      </div>
      <script src="https://d3js.org/d3.v4.min.js"></script>
      <script>
        data = [{
            "x_pos": "100",
            "y_pos": "400",
            "name": "Aa"
          },
          {
            "x_pos": "200",
            "y_pos": "200",
            "name": "Bb"
          },
          {
            "x_pos": "300",
            "y_pos": "100",
            "name": "Cc"
          },
          {
            // "x_pos": "300",
            "x_pos": "525",
            "y_pos": "300",
            "name": "Dd"
          },
          {
            "x_pos": "0",
            "y_pos": "0",
            "name": "Ee"
          }
        ]
    
        data.forEach(function(d) {
          d.x_pos = +d.x_pos;
          d.y_pos = +d.y_pos;
        });
    
        var margin = {
            top: 50,
            right: 50,
            bottom: 50,
            left: 50
          },
          width = 600 - margin.left - margin.right, // width=500
          height = 600 - margin.top - margin.bottom; // height=500
    
        var x = d3.scaleLinear()
          .domain([0, d3.max(data, function(d) {
            return d.x_pos
          })]).range([0, width]);
    
        var y = d3.scaleLinear()
          .domain([0, d3.max(data, function(d) {
            return d.y_pos
          })]).range([height, 0]);
    
        var svg = d3.select("#box")
          .append("svg")
          .attr("width", width + margin.left + margin.right)
          .attr("height", height + margin.top + margin.bottom);
    
        // create group
        var group = svg.append("g")
          .attr("class", "main_group")
          .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    
        // bind the data
        var boxes = group.selectAll("boxes")
          .data(data)
          .enter();
    
        // add rects
        boxes.append("rect")
          .attr("class", "boxes")
          .attr("x", function(d) {
            return x(d.x_pos)
          })
          .attr("y", function(d) {
            return y(d.y_pos)
          })
          .attr("rx", "5px")
          .attr("width", 60)
          .attr("height", 20)
          .attr("stroke", "darkgray")
          .attr("fill", "lightblue");
    
        // add text
        boxes.append("text")
          .attr("class", "legend_text")
          .attr("x", function(d) {
            return x(d.x_pos)
          })
          .attr("y", function(d) {
            return y(d.y_pos)
          })
          .attr("dx", "0.5em")
          .attr("dy", "1.0em")
          .style("font-weight", "bold")
          .text(function(d) {
            return d.name;
          });
    
        // add text coordinates
        boxes.append("text")
          .attr("class", "legend_text")
          .attr("x", function(d) {
            return x(d.x_pos)
          })
          .attr("y", function(d) {
            return y(d.y_pos)
          })
          .attr("dx", "-2.5em")
          .attr("dy", "-.5em")
          .style("font-weight", "bold")
          .text(function(d) {
            return "(" + d.x_pos + ", " + d.y_pos + ")";
          });
      </script>