【问题标题】:How do I draw a zoomable d3 line chart over time?如何随时间绘制可缩放的 d3 折线图?
【发布时间】:2021-04-26 09:20:19
【问题描述】:

我正在尝试在 d3.js 中绘制一个可缩放的相对简单的折线图。我一直在看这些例子。

简单的线条示例

https://observablehq.com/@d3/learn-d3-shapes?collection=@d3/learn-d3

https://www.d3-graph-gallery.com/graph/line_basic.html

缩放示例

https://observablehq.com/@d3/zoomable-area-chart

这是我的 jsfiddle。我的线路没有出现。

https://jsfiddle.net/tw5u2po9/

编辑:我的线现在正在工作,只是在进行缩放

https://jsfiddle.net/dc3g2uqm/

HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8"/>
</head>
<body>
  <h1>My Chart</h1>
  <div style="width:75%">
    <canvas id="my_canvas"></canvas>
  </div>
  <div id="my_chart"></div>
</body>
</html>

CSS

.line {
    fill: none;
    stroke: #ffab00;
    stroke-width: 3;
}
  
.overlay {
  fill: none;
  pointer-events: all;
}

/* Style the dots by assigning a fill and stroke */
.dot {
    fill: #ffab00;
    stroke: #fff;
}
  
.focus circle {
  fill: none;
  stroke: steelblue;
}

Javascript

var x = [new Date(2020, 10, 10), new Date(2020, 10, 11), new Date(2020, 10, 12), new Date(2020, 10, 13)]
var y = [5,6,8,4]
var data = {}
data.x = x;
data.y = y;


// 2. Use the margin convention practice 
var margin = {top: 50, right: 50, bottom: 50, left: 50}
var width = window.innerWidth - margin.left - margin.right // Use the window's width 
var height = window.innerHeight - margin.top - margin.bottom; // Use the window's height


// 5. X scale will use the index of our data
var xScale = d3.scaleUtc()
    .domain([0 , d3.max(x)]) // input
    .range([0, width]); // output

// 6. Y scale will use the randomly generate number 
var yScale = d3.scaleLinear()
    .domain([0, d3.max(y)]) // input 
    .range([height, 0]); // output 

// 7. d3's line generator
var line = d3.line()
    .x(function(d) { return xScale(d.x); }) // set the x values for the line generator
    .y(function(d) { return yScale(d.y); }) // set the y values for the line generator 

// 1. Add the SVG to the page and employ #2
var svg = d3.select("#my_chart").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// 3. Call the x axis in a group tag
svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom

// 4. Call the y axis in a group tag
svg.append("g")
    .attr("class", "y axis")
    .call(d3.axisLeft(yScale)); // Create an axis component with d3.axisLeft

// 9. Append the path, bind the data, and call the line generator 
svg.append("path")
    .datum(data) // 10. Binds data to the line 
    .attr("class", "line") // Assign a class for styling 
    .attr("d", line); // 11. Calls the line generator 

【问题讨论】:

    标签: javascript d3.js


    【解决方案1】:

    对于直线和x轴问题:

    • data 格式应为 {"x": date obj, "y": number}
    • x 轴domain 应该是日期的d3.mind3.max

    对于缩放,d3 v6 has an update 以不同的方式使用event

    我认为折线图缩放的 this d3 v5 example 比您提到的 observable 更容易适应 - 请参阅下面的 zoom 函数并与原始块进行比较。

    注意 svg defs 添加一个 clippath 成为 x 轴和线的属性。这将阻止图表的缩放部分向左右“溢出”。

    // Prep data
    /* var x = [new Date(2020, 10, 10), new Date(2020, 10, 11), new Date(2020, 10, 12), new Date(2020, 10, 13)]
    var y = [5,6,8,4] */
    var x = d3.timeDays(new Date(2020, 06, 01), new Date(2020, 10, 30));
    var y = Array.from({length: x.length}, Math.random).map(n => Math.floor(n * 10) + 5);
    var data = x.map((v, i) => {
      return {
        "x": v,
        "y": y[i]
      }
    });
    
      
    // Use the margin convention practice 
    var margin = {top: 50, right: 50, bottom: 50, left: 50}
    var width = 600 - margin.left - margin.right // Use the window's width 
    var height = 400 - margin.top - margin.bottom; // Use the window's height
    
    // zoom function for d3v6
    // adapt to v6 from this v5 block https://bl.ocks.org/LemoNode/7ac1d41fe75fe7d2d9cb85e78aad6303
    var zoom = d3.zoom()
      .on('zoom', (event) => {
        xScale
          .domain(event.transform.rescaleX(xScale2).domain())
          .range([0, width].map(d => event.transform.applyX(d))); //
          
        svg.select(".line")
            .attr("d", line); // zooms line
    
          svg.select(".x-axis")
            .call(d3.axisBottom(xScale)
          .tickSizeOuter(0)); // zooms axis
      })
      .scaleExtent([1, 32]); // adjust 32 to less zoom less
    
    
    // X scale - use min and max of scale
    var xScale = d3.scaleUtc()
      .domain([d3.min(x), d3.max(x)]) // input
      .range([0, width]); // output
    
    // constant reference point whilst zooming
    var xScale2 = d3.scaleUtc()
      .domain([d3.min(x), d3.max(x)]) // input
      .range([0, width]); // output  
      
    // Y scale - add 5 for bit of whitespace at top of graph
    var yScale = d3.scaleLinear()
      .domain([0, d3.max(y) + 5]) // input 
      .range([height, 0]); // output 
    
    // d3's line generator
    var line = d3.line()
      .x(function(d) { return xScale(d.x); }) // set the x values for the line generator
      .y(function(d) { return yScale(d.y); }) // set the y values for the line generator 
    
    // Add the SVG to the page and employ #2
    var svg = d3.select("#my_chart").append("svg")
      .attr("width", width + margin.left + margin.right)
      .attr("height", height + margin.top + margin.bottom)
      .call(zoom) // call the zoom
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    
    // clippath to stop line and x-axis 'spilling over'
    svg.append("defs").append("clipPath")
      .attr("id", "clip")
      .append("rect")
      .attr("x", 0)
      .attr("width", width)
      .attr("height", height);
        
    // call x-axis and apply the clip from the defs
    svg.append("g")
      .attr("class", "x-axis")    
      .attr("clip-path", "url(#clip)") // add the clip path!
      .attr("transform", "translate(0," + height + ")")
      .call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom
    
    // Call the y-axis 
    svg.append("g")
      .attr("class", "y-axis")
      .call(d3.axisLeft(yScale)); // Create an axis component with d3.axisLeft
        
    // Append the path, bind the data, and call the line generator 
    svg.append("path")
      .datum(data) // 10. Binds data to the line 
      .attr("class", "line") // Assign a class for styling 
      .attr("clip-path", "url(#clip)") // add the clip path!
      .attr("d", line); // 11. Calls the line generator
    .line {
      fill: none;
      stroke: #ffab00;
      stroke-width: 3;
    }
      
    .overlay {
      fill: none;
      pointer-events: all;
    }
    
    /* Style the dots by assigning a fill and stroke */
    .dot {
      fill: #ffab00;
      stroke: #fff;
    }
      
    .focus circle {
      fill: none;
      stroke: steelblue;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.0.0/d3.min.js"></script>
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="utf-8"/>
    </head>
    <body>
      <div id="my_chart"></div>
    </body>
    </html>

    编辑

    您的修改解决了一个能够缩小的问题,即将zoom 函数中xScale 中的range 与原始函数对齐。

    对于 x 轴上的可缩放日期,请参阅使用 xScale2 来保存 xScaledomain 的原始定义,这可以在 zoom 函数中引用:

    xScale
          .domain(event.transform.rescaleX(xScale2).domain())
    

    一些 D3 魔法正在处理刻度标签 - 肯定是可配置的。

    HTH

    【讨论】:

    • 这个代码和例子是一个巨大的帮助,谢谢。我还有两个问题。 1)您可以缩小太多。我认为这是由于 .translateExtent(extent).extent(extent) 未附加到缩放功能和 2)当我放大得太远时,x 轴不会改变以显示单个日期。
    • @user1071182 - 查看我的编辑(批准你的)。 HTH
    • 谢谢。希望只是一个问题。我有大约 10 年的数据。缩小时显示每周数据,但放大时需要查看每日数据。我可以通过使用 d3.axisBottom(xScale).tickSizeOuter(0).ticks(20) 将 ticks() 函数添加到 x 轴来添加更多刻度,但是如何在缩放时强制实际数据更加精细到每日级别?
    • 我认为数据正在放大,我只需要更多的刻度。我将此答案标记为完整并在此处创建了一个新问题stackoverflow.com/questions/65863587/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-10
    • 1970-01-01
    相关资源
    最近更新 更多