d3 v5 在缩放轴时使用嵌套值

d3 v5 use nested values when scaling axis

我有以下代码根据区域和日期嵌套我的数据。我遇到的问题是我不知道如何定义 yScale 来动态绘制轴,以便返回嵌套数据的最大总和(嵌套数据的最大 val 高于数据集 bc 中的最大 val它是聚合的)。因此我的 yAxis 是 t运行cated 并且图表没有显示所有数据。

在代码中,如果我将域硬编码为 .domain([0, 3500]) 则轴是正确的,否则就不正确。我不想对域进行硬编码。如何引用嵌套值?

已编辑以显示评论中提供的代码,当脚本在整个数据集上 运行 时,这有助于但不能完全修复。请看底部的图片。

      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>


更新:

yAxis 的最大值小于 nest 数据的实际最大值,因此被截断。

您必须在 yScale 计算中使用您的 nest 数据,而不是使用原始 dataset 数据。

实现步骤:

  1. 先定义nest
  2. nest两次得到totalMax
  3. 使用totalMax计算yScale
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])

我根据你的代码写了一个Demo :

   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>


我注意到你的 d.count 的数据类型是 string 所以最大值不正确。

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

尝试将值转换为 return 之前的 number :

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

要在嵌套数组中获取最大值,您可以嵌套 d3.max 函数,如“

var maxCountSum = d3.max(nest, function(d) {
    return d3.max(d.values, function (f) {
        return f.value
    });
});

然后申请 yScale 域:

var yScale = d3.scaleLinear()
    .domain([0, maxCountSum]).range([height, 0]);