D3 v5:如何为多折线图中的多条线设置动画?

时间:2020-05-06 05:32:33

标签: d3.js

非常感谢您,我是D3的新手。我正在尝试制作一个具有动画效果的多折线图,基本上就像从左到右“画线”一样。我希望一次性画出所有线条。

以下是指向Codepen项目的链接:https://codepen.io/amandaraen/project/editor/ZLLWBe

基本上,根据我正在阅读的Scott Murray的D3书,这是我认为应该做的事情:

    d3.transition().selectAll(".line")
    .delay(function(d, i) { return i * 500; })
    .attr("d", function(d) { return line(d.values); })
    .style("stroke", function(d, i){ 
        return colorScale(i); 
  }); 

但是,所有这些操作是将行全部完整地一一添加,相反,我希望它们展开。我在这里查看了很多帖子,并尝试了很多事情,但这是我最近的一次。另外,(很烦人)我在右侧有这行的标签,但是在添加过渡代码后,标签消失了。

编辑,好吧,我做了一些更改,现在,至少重新出现了标签。因此,现在仅是如何使线条看起来像被绘制一样。更新:

 var country = g.selectAll(".country")
    .data(countries)
    .enter().append("g")
    .attr("class", "country"); 

// draw the lines
country.append("path")
    .transition()
    .duration(2000)
    .delay(function(d, i) { return i * 500; } )
    .attr("class", "line")
    .attr("d", function(d) { return line(d.values); })
    .style("stroke", function(d, i){ 
        return colorScale(i); 
  }); 

1 个答案:

答案 0 :(得分:0)

编辑: 实际上,我会忽略我的解决方案,而是尝试本文所述的笔划虚线属性 http://duspviz.mit.edu/d3-workshop/transitions-animation/ 我通过这个问题链接到的。 Animate line chart in d3


要使线条看起来像绘制的线条,请使用attrTween和内置的插值器d3.interpolateNumber来设置x2,y2值。

<!doctype html>
<html lang='en'>
<head>
<!-- Required meta tags -->
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'>
<!-- Bootstrap CSS -->
<link rel='stylesheet' href='https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css' integrity='sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh' crossorigin='anonymous'>

<title>Hello, world!</title>
</head>
<body>

<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src='https://code.jquery.com/jquery-3.4.1.min.js' integrity='sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=' crossorigin='anonymous'></script>
<script src='https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js' integrity='sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo' crossorigin='anonymous'></script>
<script src='https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js' integrity='sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6' crossorigin='anonymous'></script>
<!-- d3 v5 -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/5.15.0/d3.js'></script>
<script>

let width = 500
let height = 300 
//make up some lines
let lines = [
    {
        "x1": 0, 
        "y1": 0, 
        "x2": 400, 
        "y2": 250
    },
    {
        "x1": 0, 
        "y1": 250, 
        "x2": 400, 
        "y2": 0
    }
]

//add lines 
let svg = d3.select( "body" ).style( "padding", "30px" ).append( "svg" ).attr( "height", height).attr( "width", width )


// Here you set the x1, y1, x2, y2 values of line to all be equal to x1, y1, 
// Then in your transition, use interpolate the number between x1 and x2 and y1 
// and y2 respectively. 
svg.selectAll( ".line").data( lines )
    .enter().append( "line" )
        .attr( "class", "line" )
        .attr( "stroke-width", 2 )
        .attr( "stroke", "black" )
        .attr( "x1", d => d.x1 )
        .attr( "y1", d => d.y1 )
        .attr( "x2", d => d.x1 )
        .attr( "y2", d => d.y1 )
        .transition().duration( 750 )
            .attrTween( "x2", d => {
                return d3.interpolateNumber( d.x1, d.x2 )
            })
            .attrTween( "y2", d => {
                return d3.interpolateNumber( d.y1, d.y2 )
            })



</script>
</body>
</html>

由于这是一行,因此可以轻松使用内插器中的构建,但是如果它是路径或其他可以定义自己的路径。我发现这是一个很好的参考。 https://www.andyshora.com/tweening-shapes-paths-d3-js.html

编辑:我意识到您正在建立路径。我敢肯定有一个更优雅的解决方案,但是您可以在每个节点处分解路径以创建细分列表。例如,如果路径为ABCD,您将获得 一种 AB 美国广播公司 ABCD

然后您可以延迟每行,以便依次绘制A,AB,ABC等。

let width = 500
let height = 300 

//get some paths 
let paths = [
    getPath( .5, 0 ), 
    getPath( .25, 0 )
]

// for each path, take a length from 0-i 
// So for path [(0,0), (1, 1), (2,2)] => 
// [ [(0,0)], [(0,0),(1,1)], [(0,0), (1,1), (2,2)] ]
let pathSegments = paths.map( p => segmentPath( p ))


//make the line generator
var lineGenerator = d3.line()
.x(function(d, i) {
    return d.x;
})
.y(function(d) {
    return d.y;
});

//add the svg
let svg = d3.select("body").style("padding", "30px").append( "svg" ).attr( "height", height).attr( "width", width)


//add the group elements which will contain the segments for each line - merge with update selection - 
let lines = svg.selectAll( ".line" ).data( pathSegments )
lines = lines.enter().append( "g" )
    .attr( "class", "line" )
    .merge( lines )

//To add line to g,  selectAll to add the segments - data bound to lines will be passed 
// down to children if data is called - in this case .data( d => d ) - d is the segmented 
// path [ [(0,0)], [(0,0),(1,1)], [(0,0), (1,1), (2,2)] ] for a single line
// add a delay on stroke-width to give the appearance of being drawn
lines.selectAll( ".segment" ).data( d => d )
    .enter().append( "path" )
        .attr( "class", "segment" )
        .attr( "stroke", "black" )
        .attr( "stroke-width", 0 )
        .attr( "fill", "none" )
        .attr( "d", lineGenerator )
        .transition().duration(750)
        .delay( (d,i) => i * 100 )
            .attr( "stroke-width", 2 )


//helper functions
function getPath( m, b ) {
    let line = [...Array(5).keys()].map( d => {
        return {
            "x": d * 100, 
            "y": m * d * 100 + b,
        }
    })
    return line
}
function segmentPath( path ) {
    let segments = []
    for( let i=1; i < path.length; i++ ) {
        segments.push( path.slice(0, i ))
    }
    return segments 

}

当然,您可能想在添加后删除多余的细分。同样,它仅在路径段足够小以进行平滑过渡时才有效。如果不是,则将线进一步分段,或将路径分成一组线,然后从上方使用x1,y1,x2,y2插值。