d3 v5 use nested values when scaling axis

不问归期 提交于 2019-12-11 08:26:24

问题


I have the following code that nests my data based on region and date. The problem I am having is that I don't know how to define yScale to dynamically draw the axis so that the max sum from the nested data is returned (the max val of the nested data is higher than the max val in the dataset bc it is aggregated). Thus my yAxis is truncated and the chart doesn't show all the data.

In the code, if I hardcode the domain to .domain([0, 3500]) then the axis is correct, but otherwise it is not correct. I don't want to hardcode the domain. How do I reference the nested values?

EDITED to show code provided in comments, which helps but doesn't entirely fix when the script is run on the entire dataset. Please see pic at bottom.

      var yScale = d3.scaleLinear()
               .domain([0, d3.max(dataset, function(d) {
                  return parseInt(d.count,10); 
                })])
               .range([h - padding, padding])

      // var yScale = d3.scaleLinear()
      //   .domain([0, 3500])
      //   .range([h - padding, padding]) //not supposed to hard code the scale but it is not working 
<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="utf-8">
    <title>Nested Chart</title>
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <style type="text/css">
      .pagebreak {
        page-break-before: always;
      }

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

      .axis text {
        font-family: sans-serif;
        font-size: 11px;
      }

      .point {
        fill: none;
        size: 2px
      }

      .dot {
        fill: #ffab00;
        stroke: #fff;
        }



    </style>
  </head>

  <div style="width:800px; margin:0 auto;" class='body'></div>
  <div class="pagebreak"> </div>

  <body>

    <script type="text/javascript">
      var parseTime = d3.timeParse("%Y");


      var margin = {
          top: 20,
          right: 20,
          bottom: 30,
          left: 50
        },
        w = 960 - margin.left - margin.right,
        h = 500 - margin.top - margin.bottom;

      var padding = 20;


/////////////////get the data//////////////////               
      const csv = `state,region,year,count
        Alabama,South,2010,1
        Alabama,South,2011,1
        Alabama,South,2012,0
        Alabama,South,2013,0
        Alabama,South,2014,2
        Alabama,South,2015,6
        Alaska,West,2010,2245
        Alaska,West,2011,1409
        Alaska,West,2012,1166
        Alaska,West,2013,1329
        Alaska,West,2014,1296
        Alaska,West,2015,1575
        Connecticut,Northeast,2010,0
        Connecticut,Northeast,2011,0
        Connecticut,Northeast,2012,0
        Connecticut,Northeast,2013,0
        Connecticut,Northeast,2014,0
        Connecticut,Northeast,2015,1
        Missouri,Midwest,2010,2
        Missouri,Midwest,2011,3
        Missouri,Midwest,2012,2
        Missouri,Midwest,2013,0
        Missouri,Midwest,2014,1
        Missouri,Midwest,2015,5
        California,West,2010,546
        California,West,2012,243
        California,West,2013,240
        Wyoming,West,2015,198
        California,West,2011,195
        California,West,2014,191`;

      const dataset = d3.csvParse(csv);

      dataset.forEach(function(d) {
        d.date = parseTime(d.year);
        d.region = d['region'];
        d.state = d['state'];
        d.count = d['count'];
        //console.log(d)
      });

      /////////////////scales the data//////////////////
      var xScale = d3.scaleTime()
        .domain([d3.min(dataset, function(d) {
          return d.date
        }), d3.max(dataset, function(d) {
          return d.date
        })]).range([padding, w - padding * 2])

      var yScale = d3.scaleLinear()
        .domain([0, d3.max(dataset, function(d) {
          console.log(d.count)
          return d.count ///ERROR HERE
        })]).range([h - padding, padding])

      // var yScale = d3.scaleLinear()
      //   .domain([0, 3500])
      //   .range([h - padding, padding]) //not supposed to hard code the scale but it is not working otherwise...commented out above

      var xAxis = d3.axisBottom().scale(xScale);

      var yAxis = d3.axisLeft().scale(yScale);


      /////////////////charts start here//////////////////

      var svg = d3.select("body").append("svg")
        .attr("width", w + margin.left + margin.right)
        .attr("height", h + margin.top + margin.bottom)
        .append("g")
        .attr("transform",
          "translate(" + margin.left + "," + margin.top + ")");

      var svg1 = d3.select("body").append("svg")
        .attr("width", w + margin.left + margin.right)
        .attr("height", h + margin.top + margin.bottom)
        .append("g")
        .attr("transform",
          "translate(" + margin.left + "," + margin.top + ")");

      //Define the line
      var valueLine = d3.line()
        .x(function(d) {
          return xScale(new Date(d.key));
        })
        .y(function(d) {
          return yScale(d.value);
        })


      var nest = d3.nest()
        .key(function(d) {
          return d.region;
        })
        .key(function(d) {
          return d.date;
        })
        .rollup(function(leaves) {
          return d3.sum(leaves, function(d) {
            return (d.count)
          });
        })
        .entries(dataset)

        console.log(nest)


      // Set the color scheme
        var colors = d3.scaleOrdinal()
          .domain(["South", "West", "Northeast","Midwest"])
          .range(["#EF5285", "#88F284" , "#5965A3","#900C3F"]);


      var regYear = svg.selectAll(".regYear")
        .data(nest)
        .enter()
        .append("g")
        .attr("stroke", function(d){ return colors(d.key)}); // Adding color!

      // console.log(regYear)

      var paths = regYear.selectAll(".line")
        .data(function(d) {
          return [d.values]
        })
        .enter()
        .append("path");

      // Draw the line
      paths
        .attr("d", function(d) {
          return valueLine(d)
        })
        .attr("class", "line")
        .style("fill", "none");



    svg.selectAll(".dot")
        .data(dataset)
        .enter().append("circle") // Uses the enter().append() method
        .attr("class", "dot") // Assign a class for styling
        .attr("cx", function(d, i) { return xScale(i) })
        .attr("cy", function(d) { return yScale(d.count) })//this is not working
        .attr("r", 5);


      svg.append("g").attr("class", "axis").attr("transform", "translate(0," + (h - padding) + ")").call(xAxis);
      //draw Y axis
      svg.append("g").attr("class", "axis").attr("transform", "translate(" + padding + ",0)").call(yAxis);
      // add label
      svg.append("text").attr("x", (w / 2)).attr("y", h + 30).attr("text-anchor", "middle").text("Year");
      svg.append("text").attr("x", padding).attr("y", padding - 20).attr("text-anchor", "middle").text("# of Events");
      //add title
      svg.append("text").attr("x", (w / 2)).attr("y", padding).attr("text-anchor", "middle").text("Events per Year by Category");
      // add legend   
      var legend = svg.append("g")
        .attr("class", "legend")
        .attr("x", w - 65)
        .attr("y", 25)
        .attr("height", 100)
        .attr("width", 100);




      ////////////////////////////////////END///////////////////////////

    </script>

  </body>

</html>



回答1:


UPDATE :

The max value of yAxis is less than the actual max value of the nest data, so it's truncated.

You have to use your nest data inside yScale calculation rather than using the original dataset data.

Steps to achieve this:

  1. define nest first
  2. get the totalMax value by flatting nest twice
  3. use totalMax to calculate the yScale value
var totalMax = Object
                 .entries(nest)
                 .reduce(function(totalMax, [key, regionValue]){
                    const regionMax = Object
                                        .entries(regionValue.values)
                                        .reduce(function(regionMax, [key,yearValue]){
                                           return parseInt(yearValue.value,10) > regionMax ? parseInt(yearValue.value, 10) : regionMax;
                                        }, 0)
                    return parseInt(regionMax, 10) > totalMax ? parseInt(regionMax, 10) : totalMax;
                  }, 0)

var yScale = d3.scaleLinear().domain([0, totalMax]).range([h - padding, padding])

I write a Demo base on your code :

   var parseTime = d3.timeParse("%Y");


  var margin = {
      top: 20,
      right: 20,
      bottom: 30,
      left: 50
    },
    w = 960 - margin.left - margin.right,
    h = 500 - margin.top - margin.bottom;

  var padding = 20;


/////////////////get the data//////////////////               
  const csv = `state,region,year,count
    Alabama,South,2010,1
    Alabama,South,2011,1
    Alabama,South,2012,0
    Alabama,South,2013,0
    Alabama,South,2014,2
    Alabama,South,2015,6
    Alaska,West,2010,2245
    Alaska,West,2011,1409
    Alaska,West,2012,1166
    Alaska,West,2013,1329
    Alaska,West,2014,1296
    Alaska,West,2015,1575
    Connecticut,Northeast,2010,0
    Connecticut,Northeast,2011,0
    Connecticut,Northeast,2012,0
    Connecticut,Northeast,2013,0
    Connecticut,Northeast,2014,0
    Connecticut,Northeast,2015,1
    Missouri,Midwest,2010,2
    Missouri,Midwest,2011,3
    Missouri,Midwest,2012,2
    Missouri,Midwest,2013,0
    Missouri,Midwest,2014,1
    Missouri,Midwest,2015,5
    California,West,2010,546
    California,West,2012,243
    California,West,2013,240
    Wyoming,West,2015,198
    California,West,2011,195
    California,West,2014,191`;

  const dataset = d3.csvParse(csv);

  dataset.forEach(function(d) {
    d.date = parseTime(d.year);
    d.region = d['region'];
    d.state = d['state'];
    d.count = d['count'];
  });

  var nest = d3.nest()
    .key(function(d) {
      return d.region;
    })
    .key(function(d) {
      return d.date;
    })
    .rollup(function(leaves) {
      return d3.sum(leaves, function(d) {
        return parseInt(d.count, 10);
      });
    })
    .entries(dataset)

    
  /////////////////scales the data//////////////////
  var xScale = d3.scaleTime()
    .domain([d3.min(dataset, function(d) {
      return d.date
    }), d3.max(dataset, function(d) {
      return d.date
    })]).range([padding, w - padding * 2])

  var totalMax = Object
                .entries(nest)
                .reduce(function(totalMax, [key, regionValue]){
                  const regionMax = Object
                                    .entries(regionValue.values)
                                    .reduce(function(regionMax, [key,yearValue]){
                                       return parseInt(yearValue.value,10) > regionMax ? parseInt(yearValue.value, 10) : regionMax;
                                    }, 0)
                  return parseInt(regionMax, 10) > totalMax ? parseInt(regionMax, 10) : totalMax;
                }, 0)
  
  var yScale = d3.scaleLinear()
    .domain([0, totalMax]).range([h - padding, padding])

  // var yScale = d3.scaleLinear()
  //   .domain([0, 3500])
  //   .range([h - padding, padding]) //not supposed to hard code the scale but it is not working otherwise...commented out above

  var xAxis = d3.axisBottom().scale(xScale);

  var yAxis = d3.axisLeft().scale(yScale);


  /////////////////charts start here//////////////////

  var svg = d3.select("body").append("svg")
    .attr("width", w + margin.left + margin.right)
    .attr("height", h + margin.top + margin.bottom)
    .append("g")
    .attr("transform",
      "translate(" + margin.left + "," + margin.top + ")");

  var svg1 = d3.select("body").append("svg")
    .attr("width", w + margin.left + margin.right)
    .attr("height", h + margin.top + margin.bottom)
    .append("g")
    .attr("transform",
      "translate(" + margin.left + "," + margin.top + ")");

  //Define the line
  var valueLine = d3.line()
    .x(function(d) {
      return xScale(new Date(d.key));
    })
    .y(function(d) {
      return yScale(d.value);
    })


  


  // Set the color scheme
  var colors = d3.scaleOrdinal()
      .domain(["South", "West", "Northeast","Midwest"])
      .range(["#EF5285", "#88F284" , "#5965A3","#900C3F"]);


  var regYear = svg.selectAll(".regYear")
    .data(nest)
    .enter()
    .append("g")
    .attr("stroke", function(d){ return colors(d.key)}); // Adding color!

  var paths = regYear.selectAll(".line")
    .data(function(d) {
      return [d.values]
    })
    .enter()
    .append("path");

  // Draw the line
  paths
    .attr("d", function(d) {
      return valueLine(d)
    })
    .attr("class", "line")
    .style("fill", "none");
   


svg.selectAll(".dot")
    .data(dataset)
    .enter().append("circle") // Uses the enter().append() method
    .attr("class", "dot") // Assign a class for styling
    .attr("cx", function(d, i) { return xScale(i) })
    .attr("cy", function(d) { return yScale(d.count) })//this is not working
    .attr("r", 5);


  svg.append("g").attr("class", "axis").attr("transform", "translate(0," + (h - padding) + ")").call(xAxis);
  //draw Y axis
  svg.append("g").attr("class", "axis").attr("transform", "translate(" + padding + ",0)").call(yAxis);
  // add label
  svg.append("text").attr("x", (w / 2)).attr("y", h + 30).attr("text-anchor", "middle").text("Year");
  svg.append("text").attr("x", padding).attr("y", padding - 20).attr("text-anchor", "middle").text("# of Events");
  //add title
  svg.append("text").attr("x", (w / 2)).attr("y", padding).attr("text-anchor", "middle").text("Events per Year by Category");
  // add legend   
  var legend = svg.append("g")
    .attr("class", "legend")
    .attr("x", w - 65)
    .attr("y", 25)
    .attr("height", 100)
    .attr("width", 100);




  ////////////////////////////////////END///////////////////////////
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="utf-8">
    <title>Nested Chart</title>
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <style type="text/css">
      .pagebreak {
        page-break-before: always;
      }

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

      .axis text {
        font-family: sans-serif;
        font-size: 11px;
      }

      .point {
        fill: none;
        size: 2px
      }

      .dot {
        fill: #ffab00;
        stroke: #fff;
        }



    </style>
  </head>

  <div style="width:800px; margin:0 auto;" class='body'></div>
  <div class="pagebreak"> </div>

  <body>

   
  </body>

</html>

I noticed the data type of your d.count is string so that the max won't be correct.

console.log(d3.max(['6','2245'])) // it's 6!

Try converting the value to the number before return it :

var yScale = d3.scaleLinear()
               .domain([0, d3.max(dataset, function(d) {
                  return parseInt(d.count,10); 
                })])
               .range([h - padding, padding])



来源:https://stackoverflow.com/questions/58147794/d3-v5-use-nested-values-when-scaling-axis

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!