Reputation: 3891
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
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