【问题标题】:d3js multi-line chart is being drawn off the plot area - is update pattern to the issue?d3js 多折线图正在绘制区域 - 问题的更新模式是什么?
【发布时间】:2020-03-21 02:35:52
【问题描述】:

我创建了一个多折线图,它绘制了多组线对 - 尽管我不确定我是否正确地更新了线条或以最 d3 的方式。用新数据更新现有路径的正确方法是什么?我使用 setinterval 来模拟新数据到达。

我遇到的另一个问题是,这些线都不是在实际的绘图区域上绘制的——它们似乎从图表的左侧开始。我检查了 x 刻度,它从数据中获得了正确的日期,所以不确定到底发生了什么,并且会重视一些输入。我已经厌倦了手动将 x 域设置为一些硬编码的日期,它会改变线条,但这肯定是不对的。我不确定这是否与更新内容有关,因为 x 和 y 比例得到更新,但路径没有。

所以我的问题是因为我不确定它们是否相互关联:

1) 如何修复线条的绘制?

2) 我是否对两条线路都进行了正确的更新?

<!DOCTYPE html>
<meta charset="utf-8">

<head>
  <title>my plots!</title>
  <script src="https://d3js.org/d3.v5.min.js"></script>
  <!-- load the d3.js library -->
  <style>
    /* set the CSS */
    
    body {
      font: 12px Arial Bold;
    }
    
    .graph-svg-component {
      background-color: white;
    }
    
    .line {
      fill: none;
      stroke-width: 3px;
    }
    
    .rawline {
      fill: none;
      stroke-width: 3px;
    }
    
    .raw2line {
      fill: none;
      stroke-width: 10px;
      opacity: 0.4;
    }
  </style>
</head>

<body>
  <h1> plot</h1>
  <div id="graphDiv"></div>
  <!-- css FIll none is important so that its just a line and not an area -->

  <script>
    var data_set = [{
        'Asset_Date': '2009-03-23',
        'Raw': 25,
        'Raw2': 25,
        'Asset': 'A'
      },
      {
        'Asset_Date': '2009-03-24',
        'Raw': 28,
        'Raw2': 25.4,
        'Asset': 'A'
      },
      {
        'Asset_Date': '2009-03-25',
        'Raw': 26,
        'Raw2': 25.37,
        'Asset': 'A'
      },
      {
        'Asset_Date': '2009-03-26',
        'Raw': 22,
        'Raw2': 25.03,
        'Asset': 'A'
      },
      {
        'Asset_Date': '2009-03-27',
        'Raw': 19,
        'Raw2': 24.42,
        'Asset': 'A'
      },
      {
        'Asset_Date': '2009-03-28',
        'Raw': 23,
        'Raw2': 24.28,
        'Asset': 'A'
      },
      {
        'Asset_Date': '2009-03-23',
        'Raw': 30,
        'Raw2': 30,
        'Asset': 'B'
      },
      {
        'Asset_Date': '2009-03-24',
        'Raw': 31,
        'Raw2': 33,
        'Asset': 'B'
      },
      {
        'Asset_Date': '2009-03-25',
        'Raw': 32,
        'Raw2': 34,
        'Asset': 'B'
      },
      {
        'Asset_Date': '2009-03-26',
        'Raw': 33,
        'Raw2': 35,
        'Asset': 'B'
      },
      {
        'Asset_Date': '2009-03-27',
        'Raw': 34,
        'Raw2': 36,
        'Asset': 'B'
      },
      {
        'Asset_Date': '2009-03-28',
        'Raw': 37,
        'Raw2': 39,
        'Asset': 'B'
      }
    ]

    var margin = {
      top: 30,
      right: 50,
      bottom: 30,
      left: 50
    };

    var svgWidth = 800;
    var svgHeight = 800;
    var graphWidth = svgWidth - margin.left - margin.right;
    var graphHeight = svgHeight - margin.top - margin.bottom;
    var parseDate = d3.timeParse("%Y-%m-%d");

    var x = d3.scaleTime().range([0, graphWidth]);
    var y = d3.scaleLinear().range([graphHeight, 0]);

    var z = d3.scaleOrdinal(d3.schemeCategory10); // for colours

    var xAxis = d3.axisBottom().scale(x).ticks(10);
    var yAxis = d3.axisLeft().scale(y).ticks(20);

    // Need to create the lines manually for each bit of data
    // Line for raw value
    var line = d3.line()
      .x(function(d) {
        return x(d.Asset_Date);
      })
      .y(function(d) {
        return y(d.Raw);
      });

    // Line for raw2 value
    var line2 = d3.line()
      .x(function(d) {
        return x(d.Asset_Date);
      })
      .y(function(d) {
        return y(d.Raw2);
      });

    // Creates the SVG area within the div on the dom 
    // Just doing this once 
    var svg = d3.select("#graphDiv")
      .append("svg")
      .attr("width", svgWidth)
      .attr("height", svgHeight)
      .attr("class", "graph-svg-component");
    var g = svg.append("g")
      .attr("transform",
        "translate(" + margin.left + "," + margin.top + ")")
      .call(d3.zoom().on("zoom", function() {
        svg.attr("transform", d3.event.transform)
      }));


    // // Add the X Axis
    g.append("g").attr("class", "x axis")
      .attr("transform", "translate(0," + graphHeight + ")")
      .call(xAxis);
    // Text label for x axis 
    g.append("text")
      .style("text-anchor", "middle")
      .text("timeseries dates")
      .attr("transform", "translate(" + (graphWidth / 2) + " ," + (graphHeight + margin.top) + ")");

    // // Add the Y Axis
    g.append("g")
      .attr("class", "y axis")
      .call(yAxis);
    // text label for the y axis
    g.append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 0 - margin.left)
      .attr("x", 0 - (graphHeight / 2))
      .attr("dy", "1em")
      .style("text-anchor", "middle")
      .text("price points");

    function drawGraph(data_set) {
      console.log('Dataset contains', data_set.length, 'item(s)')

      data_set = data_set.map(function(d) {
        return {
          Asset: d.Asset,
          Asset_Date: parseDate(d.Asset_Date),
          Raw: +d.Raw,
          Raw2: +d.Raw2
        };
      });

      // Keying data on Asset for easy grouping
      var nested = d3.nest()
        .key(function(d) {
          return d.Asset;
        })
        .entries(data_set);

      // Set X domain to min and max of date from data set
      min1 = d3.min(nested, function(d) {
        return d3.min(d.values, function(d) {
          return d.Asset_Date;
        });
      });
      max1 = d3.max(nested, function(d) {
        return d3.max(d.values, function(d) {
          return d.Asset_Date;
        });
      });
      console.log(min1)
      console.log(max1)
      console.log(d3.extent([min1, max1]))
      x.domain(d3.extent([min1, max1]));

      y.domain([
        d3.min(nested, function(d) {
          return d3.min(d.values, function(d) {
            return Math.min(d.Raw, d.Raw2);
          });
        }),
        d3.max(nested, function(d) {
          return d3.max(d.values, function(d) {
            return Math.max(d.Raw, d.Raw2);
          });
        })
      ]);

      // for colours - by asset as they are grouped 
      var color = d3.scaleOrdinal(d3.schemeCategory10)
        .domain(d3.keys(nested[0])
          .filter(function(key) {
            return key === "Asset";
          }));


      // Update the axis
      svg.selectAll('.x.axis').call(xAxis);
      svg.selectAll('.y.axis').call(yAxis);

      // Join data 
      var city = svg.selectAll(".city").data(nested, function(d) {
        console.log(d);
        return d.key;
      })
      console.log(city)
      // Remove old elements as needed
      city.exit().transition().duration(750).remove()

      // Add a new group per category we have - aka new elements   
      var cityEnter = city.enter().append("g").attr("class", "city");

      // Add line for raw value 
      cityEnter.append("path")
        .attr("class", "rawline")
        .attr("fill", "none")
        .transition()
        .duration(500)
        .style("stroke", function(d) {
          return color(d.key);
        })
        .attr("d", function(d) {
          console.log("update rawline path");
          return line(d.values);
        });

      // Add line for raw2 value 
      cityEnter.append("path")
        .attr("class", "raw2line")
        .attr("fill", "none")
        .transition()
        .duration(500)
        .style("stroke", function(d) {
          return color(d.key);
        })
        .attr("d", function(d) {
          console.log("update raw2line path");
          return line2(d.values);
        });

      var t1 = city.transition();
      // Doing the update the lines with new data 

      t1.select(".rawline").attr("d", function(d) {
        return line(d.values);
      });
      t1.select(".raw2line").attr("d", function(d) {
        return line2(d.values);
      });
    }


    // display initial chart
    window.onload = drawGraph(data_set)
    //Push new data every 5 seconds for a specific date
    var h = setInterval(function() {
      data_set.push({
        'Asset_Date': '2009-03-29',
        'Raw': Math.floor(Math.random() * 50),
        'Raw2': Math.floor(Math.random() * 25),
        'Asset': 'A'
      }, {
        'Asset_Date': '2009-03-30',
        'Raw': Math.floor(Math.random() * 50),
        'Raw2': Math.floor(Math.random() * 25),
        'Asset': 'A'
      }, {
        'Asset_Date': '2009-03-31',
        'Raw': Math.floor(Math.random() * 50),
        'Raw2': Math.floor(Math.random() * 25),
        'Asset': 'A'
      }, {
        'Asset_Date': '2009-04-01',
        'Raw': Math.floor(Math.random() * 50),
        'Raw2': Math.floor(Math.random() * 25),
        'Asset': 'A'
      }, {
        'Asset_Date': '2009-04-02',
        'Raw': Math.floor(Math.random() * 50),
        'Raw2': Math.floor(Math.random() * 25),
        'Asset': 'A'
      }, {
        'Asset_Date': '2009-04-03',
        'Raw': Math.floor(Math.random() * 50),
        'Raw2': Math.floor(Math.random() * 25),
        'Asset': 'A'
      });
      console.log('Redrawing')
      drawGraph(data_set);
      clearInterval(h); //doing this so that it doesnt spam - if i uncomment this, it will keep spamming new lines
    }, 5000);
  </script>
</body>

</html>

非常感谢您对上述问题的帮助 - 如果您需要更多详细信息 - 请与我们联系!非常感谢!

【问题讨论】:

    标签: javascript html css d3.js svg


    【解决方案1】:
    1. 您正在使用select 更新行,select 只会选择第一个匹配项,将其替换为selectAll 将解决您的问题。

    2. 当您使用轴的边距时,您可以将translate 设置为您的.city 组。因此,当您附加 g 时,它看起来像这样:

      var cityEnter = city.enter().append("g")
         .attr("transform", "translate(" + margin.left + ", 0)")
         .attr("class", "city");
      

    这是一个 jsfiddle: https://jsfiddle.net/wt6vkj82/

    【讨论】:

    • 感谢您的回复,但现在看起来新传入的数据没有应用于路径? @Guillermo Garcia
    • @Lakhvir 这是关于边距问题的正确答案。但是,selectAll 更改不正确:您的代码与select 一起正常工作,因为对于每个组,每个类只有一个路径。最重要的是,selectAll 不会传播数据。
    • 非常感谢@GerardoFurtado!这样可行。最后一个问题是,如果我为新密钥推送新数据片段,它会在颜色域中再次使用第一种颜色而不是新颜色 - 你能帮忙吗?我想可能为我拥有的每个组附加一个硬编码的十六进制颜色,但可能有 100 个,所以不确定这是正确的方法。在此先感谢:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-26
    相关资源
    最近更新 更多