sunny
sunny

Reputation: 3891

How to replace d3.js path element rather than draw another one?

I am trying to make a line graph widget where there is a pre-existing data to which the user can add by clicking on a point on the graph to add info. The working example is here.

Right now the points all show up where I want, but each click results in a new line being added to the graph rather than an existing line being replaced. Here is how the graph is originally created (built on an example from D3 in Action):

function lineChart(data) {

   //...code setting up x and y axis...//

    d3.select("svg").selectAll("circle.tweets")
        .data(data)
        .enter()
        .append("circle")
        .attr("class", "tweets")
        .attr("r", 10)
        .attr("cx", function(d) {return xScale(d.day)})
        .attr("cy", function(d) {return yScale(d.tweets)})
        .style("fill", "black")

    // Here is the mousedown click function as it's set up to add new elements to the data
    d3.select("svg")
        .on('mousedown', function() {
            x = d3.mouse(this)[0];
            y = d3.mouse(this)[1];

            x_day = ((x-20)/(480-20)*9.5 + 1);
            y_day = 35 - ((y - 20)/(480-20)*35)

            tweets.push(new newNode(x_day, y_day, 11, 11));              
            updateData();
        });

    tweetLine = d3.svg.line()
        .interpolate("cardinal")
        .x(function(d) {
            return xScale(d.day)
        })
        .y(function(d) {
            return yScale(d.tweets)
        })

    d3.select("svg")
        .append("path")
        .attr("d", tweetLine(data))
        .attr("fill", "none")
        .attr("stroke", "darkred")
        .attr("stroke-width", 2)

}

And here is the mousedown function for the graph that ideally would add the new point and redraw the graph accordingly:

function updateData(){
    d3.select("svg").selectAll("circle.tweets")
        .data(tweets)
        .enter()
        .append("circle")
        .attr("class", "tweets")
        .attr("r", function(d) { console.log("doing data for " + d.day); return 10;})
        .attr("cx", function(d) {return xScale(d.day)})
        .attr("cy", function(d) {return yScale(d.tweets)})
        .style("fill", "black")
        .on('mousedown', function(d) {console.log(d);});

    tweets.sort(function(a, b){
        return a.day - b.day;
    })

    d3.select("path.line").remove();
    tweetLine = d3.svg.line()
        .interpolate("cardinal")
        .x(function(d) {
            return xScale(d.day)
        })
        .y(function(d) {
            return yScale(d.tweets)
        })

    d3.select("svg")
        .append("path")
        .attr("d", tweetLine(tweets))
        .attr("fill", "none")
        .attr("stroke", "darkred")
        .attr("stroke-width", 2)

}

I would like to have only 1 path drawn through all the elements of tweets, but right now my update function adds a line with each click event without removing the old line. What am I doing wrong here? Why does the .remove() call have no effect?

Also, I've tried rewriting my line to add it in the way recommended by this post: d3 update data and update graph using this code:

tweetLine = d3.svg.line()
    .interpolate("cardinal")
    .x(function(d) {
        return xScale(d.day)
    })
    .y(function(d) {
        return yScale(d.tweets)
    })

d3.select("svg")
    .selectAll("path.line")
    .data(data)
    .enter()
    .append("svg:path")
    .attr("d", tweetLine)
    .attr("fill", "none")
    .attr("stroke", "darkred")
    .attr("stroke-width", 2)

But then the line isn't drawn at all. What's wrong with the above?

Upvotes: 0

Views: 2211

Answers (1)

Mark
Mark

Reputation: 108512

No need to remove the line and redraw, just update it. First, give it a unique class name on initial draw to make it easier to re-select. Then simply do:

d3.select(".myLine")
  .attr("d", tweetLine(tweets));

Full working code:

<!DOCTYPE html>
<html>

<head>
  <script data-require="[email protected]" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
  <style>
  svg {
    height: 500px;
    width: 500px;
    border: 1px solid gray;
  }
line {
  shape-rendering: crispEdges;
  stroke: #000000;
  }

line.minor  {
  stroke: #777777;
  stroke-dasharray: 2,2;
}

path.domain {
  fill: none;
  stroke: black;
}

</style>
</head>

<body>
  <svg width="480" height="480"></svg>
  <script>
    var tweets, tweetLine;
    assign();

    function lineChart(data) {

      xScale = d3.scale.linear().domain([1, 10.5]).range([20, 480]);
      yScale = d3.scale.linear().domain([0, 35]).range([480, 20]);

      xAxis = d3.svg.axis()
        .scale(xScale)
        .orient("bottom")
        .tickSize(480)
        .tickValues([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);

      d3.select("svg").append("g").attr("id", "xAxisG").call(xAxis);

      yAxis = d3.svg.axis()
        .scale(yScale)
        .orient("right")
        .ticks(10)
        .tickSize(480)
        .tickSubdivide(true);

      d3.select("svg").append("g").attr("id", "yAxisG").call(yAxis);

      d3.select("svg").selectAll("circle.tweets")
        .data(data)
        .enter()
        .append("circle")
        .attr("class", "tweets")
        .attr("r", function(d) {
          console.log("doing data for " + d.day);
          return 10;
        })
        .attr("cx", function(d) {
          return xScale(d.day)
        })
        .attr("cy", function(d) {
          return yScale(d.tweets)
        })
        .style("fill", "black")


      d3.select("svg")
        .on('mousedown', function() {
          x = d3.mouse(this)[0];
          y = d3.mouse(this)[1];

          x_day = ((x - 20) / (480 - 20) * 9.5 + 1);
          y_day = 35 - ((y - 20) / (480 - 20) * 35)
          console.log("y day is " + y_day);
          console.log("x is " + x_day);
          tweets.push(new newNode(x_day, y_day, 11, 11));

          updateData();
        });

      tweetLine = d3.svg.line()
        .interpolate("cardinal")
        .x(function(d) {
          return xScale(d.day)
        })
        .y(function(d) {
          return yScale(d.tweets)
        })

      d3.select("svg")
        .append("path")
        .attr("class", "myLine")
        .attr("d", tweetLine(data))
        .attr("fill", "none")
        .attr("stroke", "darkred")
        .attr("stroke-width", 2)

    }

    function newNode(day, tweets, retweets, favorites) {
      this.day = day;
      this.tweets = tweets;
      this.retweets = retweets;
      this.favorites = favorites;
    }


    function assign() {
      tweets = [{"day":"1","tweets":"1","retweets":"2","favorites":"5"},{"day":"2","tweets":"6","retweets":"11","favorites":"3"},{"day":"3","tweets":"3","retweets":"8","favorites":"1"},{"day":"4","tweets":"5","retweets":"14","favorites":"6"}];
      lineChart(tweets);
    }

    function updateData() {
      
      d3.select("svg").selectAll("circle.tweets")
        .data(tweets)
        .enter()
        .append("circle")
        .attr("class", "tweets")
        .attr("r", function(d) {
          console.log("doing data for " + d.day);
          return 10;
        })
        .attr("cx", function(d) {
          return xScale(d.day)
        })
        .attr("cy", function(d) {
          return yScale(d.tweets)
        })
        .style("fill", "black")
        .on('mousedown', function(d) {
          console.log(d);
        });
        
      tweets.sort(function(a, b) {
        return a.day - b.day;
      });

      d3.select(".myLine")
        .attr("d", tweetLine(tweets));

    }
  </script>
</body>

</html>

Upvotes: 2

Related Questions