用d3js中的(动画)路径连接点

connecting dots with an (animated) path in d3js

本文关键字:路径 连接点 动画 d3js 中的      更新时间:2023-09-26

我有下面的d3js代码,我正试图用它做两件事——这个问题只是关于第一个:

1) 我想在绿点之间画一条绿色的虚线。

2) 最后,我想有一个动画,只从红色路径开始,当你点击文本时,它会将红色路径转换为绿色路径(有点"向下下沉")

建议任何一个都很棒!

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>D3: Labels removed</title>
        <script type="text/javascript" src="../d3/d3.v3.js"></script>
        <style type="text/css">
            .axis path,
            .axis line {
                fill: none;
                stroke: black;
                shape-rendering: crispEdges;
            }
            .axis text {
                font-family: sans-serif;
                font-size: 11px;
            }
        </style>
    </head>
    <body>
        <p>Click me to move the line[not working yet]</p>
        <script type="text/javascript">
            //Width and height
            var w = 500;
            var h = 300;
            var padding = 30;

            //Static dataset
            var dataset = [
                            [2,13],[5, 21], [7,22],[8,28],[10, 30], [12, 34], [13,36],[16,42],[17, 44]
                          ];
            var dataset2 = [
                            [12, 28], [13,26],[16,19],[17, 13]
                          ];
            //Create scale functions
            var xScale = d3.scale.linear()
                                 .domain([0, d3.max(dataset, function(d) { return d[0]; })])
                                 .range([padding, w - padding * 2]);
            var yScale = d3.scale.linear()
                                 .domain([0, d3.max(dataset, function(d) { return d[1]; })])
                                 .range([h - padding, padding]);
            // var rScale = d3.scale.linear()
            //                   .domain([0, d3.max(dataset, function(d) { return d[1]; })])
            //                   .range([2, 5]);
            //Define X axis
            var xAxis = d3.svg.axis()
                              .scale(xScale)
                              .orient("bottom")
                              .ticks(5);
            //Define Y axis
            var yAxis = d3.svg.axis()
                              .scale(yScale)
                              .orient("left")
                              .ticks(5);

            //Create SVG element
            var svg = d3.select("body")
                        .append("svg")
                        .attr("width", w)
                        .attr("height", h);
            //Create circles

            svg.append("g")
               .selectAll("circle")
               .data(dataset)
               .enter()
               .append("circle")
               .attr("cx", function(d) {
                    return xScale(d[0]);
               })
               .attr("cy", function(d) {
                    return yScale(d[1]);
               })
               .attr("r", function(d) {
                    return 5;})
               .attr("fill", function(d){
                    if (d[0] > 11) {return "red";}
                    else return "black";
               });
            svg.append("g")
               .selectAll("circle")
               .data(dataset2)
               .enter()
               .append("circle")
               .attr("cx", function(d) {
                    return xScale(d[0]);
               })
               .attr("cy", function(d) {
                    return yScale(d[1]);
               })
               .attr("r", function(d) {
                    return 5;
               })
               .attr("fill", "green");
            //Create X axis
            svg.append("g")
                .attr("class", "axis")
                .attr("transform", "translate(0," + (h - padding) + ")")
                .call(xAxis);
            //Create Y axis
            svg.append("g")
                .attr("class", "axis")
                .attr("transform", "translate(" + padding + ",0)")
                .call(yAxis);
            //Create dividing line
            svg.append("line")
                .attr("x1", xScale(dataset[0][0]))
                .attr("y1", yScale(dataset[0][1]))
                .attr("x2", xScale(dataset[4][0]))
                .attr("y2", yScale(dataset[4][1]))
                .attr("stroke", "black")
                .attr("stroke-width", 2)
                .attr("stroke-dasharray", "5,5");
            svg.append("line")
                .attr("x1", xScale(dataset[4][0]))
                .attr("y1", yScale(dataset[4][1]))
                .attr("x2", xScale(dataset[8][0]))
                .attr("y2", yScale(dataset[8][1]))
                .attr("stroke", "red")
                .attr("stroke-width", 2)
                .attr("stroke-dasharray", "5,5");
            svg.append("line")
                .attr("x1", xScale(11))
                .attr("y1", 0)
                .attr("x2", xScale(11))
                .attr("y2", h-padding)
                .attr("stroke", "black")
                .attr("stroke-width", 2)
                .attr("stroke-dasharray", "5,5");
            d3.select("p")
                .on("click", function() {
                    //code for moving line goes here    
                });
        </script>
    </body>
</html>

连接绿点的最简单方法是路径生成器:

var line = d3.svg.line()
  .x(function(d) { return xScale(d[0]); })
  .y(function(d) { return yScale(d[1]); });
greenG
  .append("path")
  .attr("d", line(dataset2))
  .style("fill", "none")
  .style("stroke", "green")
  .style("stroke-dasharray", "5,5");

你的底线有点复杂。这里有一个实现:

// create an array of "interpolaters" for each point to be moved
// from the greenSet to the redSet
var interpolaters = [];
greenSet.forEach(function(d,i){
  interpolaters.push({
    x: d3.interpolate(xScale(redSet[i][0]), xScale(d[0])),
    y: d3.interpolate(yScale(redSet[i][1]), yScale(d[1]))
  })
});
// set up a path generator line function to be used
var fallLine = d3.svg.line()
  .x(function(d) { return d[0]; })
  .y(function(d) { return d[1]; });
// g to hold our falling line and points
var fallingG = svg.append("g");
// this path is our line
fallingPath = fallingG
  .append("path")
  .style("fill", "none")
  .style("stroke", "steelblue")
  .style("stroke-dasharray", "5,5")
  // set up the transition
  .transition()
  // delay it 1s
  .delay(1000)
  // it'll last 3s
  .duration(3000)
  .ease('linear')
  // custom tween, that'll redraw the line on each animation
  .tween("fallLine", function(d) {
    var path = d3.select(this);
    return function(t) {
      var lineData = [];
      // loop our interpolaters to get current line positions
      interpolaters.forEach(function(i){
        lineData.push([i.x(t),i.y(t)]);
      });
      path.attr("d", fallLine(lineData));
    };
  });
// similar code for our circles
fallingG
  .selectAll("circle")
  .data(interpolaters)
  .enter()
  .append("circle")
  .style("fill","steelblue")
  .attr("r", 5)
  .transition()
  .delay(1000)
  .duration(3000)
  .ease('linear')
  // here we can use attrTween instead of a custom tween
  .attrTween("cx", function(d){
    return d.x;
  })
  .attrTween("cy", function(d){
    return d.y;
  });

完整工作代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>D3: Labels removed</title>
  <script data-require="d3@3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
  <style type="text/css">
    .axis path,
    .axis line {
      fill: none;
      stroke: black;
      shape-rendering: crispEdges;
    }
    
    .axis text {
      font-family: sans-serif;
      font-size: 11px;
    }
  </style>
</head>
<body>
  <p>Click me to move the line[not working yet]</p>
  <script type="text/javascript">
    //Width and height
    var w = 500;
    var h = 300;
    var padding = 30;
    //Static dataset
    var blackSet = [
      [2, 13],
      [5, 21],
      [7, 22],
      [8, 28],
      [10, 30]
    ];
    
    var redSet = [
      [12, 34],
      [13, 36],
      [16, 42],
      [17, 44]
    ];
    var greenSet = [
      [12, 28],
      [13, 26],
      [16, 19],
      [17, 13]
    ];
    //Create scale functions
    var xScale = d3.scale.linear()
      .domain([0, d3.max(redSet, function(d) {
        return d[0];
      })])
      .range([padding, w - padding * 2]);
    var yScale = d3.scale.linear()
      .domain([0, d3.max(redSet, function(d) {
        return d[1];
      })])
      .range([h - padding, padding]);
    // var rScale = d3.scale.linear()
    //                   .domain([0, d3.max(dataset, function(d) { return d[1]; })])
    //                   .range([2, 5]);
    //Define X axis
    var xAxis = d3.svg.axis()
      .scale(xScale)
      .orient("bottom")
      .ticks(5);
    //Define Y axis
    var yAxis = d3.svg.axis()
      .scale(yScale)
      .orient("left")
      .ticks(5);
    //Create SVG element
    var svg = d3.select("body")
      .append("svg")
      .attr("width", w)
      .attr("height", h);
    //Create circles
    svg.append("g")
      .selectAll("circle")
      .data(blackSet)
      .enter()
      .append("circle")
      .attr("cx", function(d) {
        return xScale(d[0]);
      })
      .attr("cy", function(d) {
        return yScale(d[1]);
      })
      .attr("r", function(d) {
        return 5;
      })
      .attr("fill", "black");
    svg.append("g")
      .selectAll("circle")
      .data(redSet)
      .enter()
      .append("circle")
      .attr("cx", function(d) {
        return xScale(d[0]);
      })
      .attr("cy", function(d) {
        return yScale(d[1]);
      })
      .attr("r", function(d) {
        return 5;
      })
      .attr("fill", "red");
    
    var greenG = svg.append("g");
    
    greenG
      .selectAll("circle")
      .data(greenSet)
      .enter()
      .append("circle")
      .attr("cx", function(d) {
        return xScale(d[0]);
      })
      .attr("cy", function(d) {
        return yScale(d[1]);
      })
      .attr("r", function(d) {
        return 5;
      })
      .attr("fill", "green");
      
    var line = d3.svg.line()
      .x(function(d) { return xScale(d[0]); })
      .y(function(d) { return yScale(d[1]); });
      
    greenG
      .append("path")
      .attr("d", line(greenSet))
      .style("fill", "none")
      .style("stroke", "green")
      .style("stroke-dasharray", "5,5");
  
    //Create X axis
    svg.append("g")
      .attr("class", "axis")
      .attr("transform", "translate(0," + (h - padding) + ")")
      .call(xAxis);
    //Create Y axis
    svg.append("g")
      .attr("class", "axis")
      .attr("transform", "translate(" + padding + ",0)")
      .call(yAxis);
    //Create dividing line
    svg.append("line")
      .attr("x1", xScale(blackSet[0][0]))
      .attr("y1", yScale(blackSet[0][1]))
      .attr("x2", xScale(blackSet[4][0]))
      .attr("y2", yScale(blackSet[4][1]))
      .attr("stroke", "black")
      .attr("stroke-width", 2)
      .attr("stroke-dasharray", "5,5");
      
    svg.append("line")
      .attr("x1", xScale(redSet[0][0]))
      .attr("y1", yScale(redSet[0][1]))
      .attr("x2", xScale(redSet[3][0]))
      .attr("y2", yScale(redSet[3][1]))
      .attr("stroke", "red")
      .attr("stroke-width", 2)
      .attr("stroke-dasharray", "5,5");
    svg.append("line")
      .attr("x1", xScale(11))
      .attr("y1", 0)
      .attr("x2", xScale(11))
      .attr("y2", h - padding)
      .attr("stroke", "black")
      .attr("stroke-width", 2)
      .attr("stroke-dasharray", "5,5");
    d3.select("p")
      .on("click", function() {
        //code for moving line goes here    
      });
      
    var interpolaters = [];
     greenSet.forEach(function(d,i){
      interpolaters.push({
        x: d3.interpolate(xScale(redSet[i][0]), xScale(d[0])),
        y: d3.interpolate(yScale(redSet[i][1]), yScale(d[1]))
      })
    });
    
    var fallLine = d3.svg.line()
      .x(function(d) { return d[0]; })
      .y(function(d) { return d[1]; });
    
    var fallingG = svg.append("g");
    
    fallingPath = fallingG
      .append("path")
      .style("fill", "none")
      .style("stroke", "steelblue")
      .style("stroke-dasharray", "5,5")
      .transition()
      .delay(1000)
      .duration(3000)
      .ease('linear')
      .tween("fallLine", function(d) {
        var path = d3.select(this);
        return function(t) {
          var lineData = [];
          interpolaters.forEach(function(i){
            lineData.push([i.x(t),i.y(t)]);
          });
          path.attr("d", fallLine(lineData));
        };
      });
      
    fallingG
      .selectAll("circle")
      .data(interpolaters)
      .enter()
      .append("circle")
      .style("fill","steelblue")
      .attr("r", 5)
      .transition()
      .delay(1000)
      .duration(3000)
      .ease('linear')
      .attrTween("cx", function(d){
        return d.x;
      })
      .attrTween("cy", function(d){
        return d.y;
      });
  </script>
</body>
</html>