I am new to using d3 and JavaScript and would appreciate some constructive feedback. I'm mocking up a practice example to learn d3 that involves plotting climate data (temperature anomaly) from 1880-2010 from two sources (GISS and HAD). So far I have generated a multiple line chart in d3 using this data. Code and data are here https://gist.github.com/natemiller/0c3659e0e6a0b77dabb0
In this example the data are originally plotted grey, but each line colored a different color on mouseover.
I would like to add two additional features.
I would like, on mouseover, to reorder the lines so that the moused-over line is on top, essentially reorder the lines. I've read that this requires essentially replotting the SVG and I have tried code along the lines of this
source.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", "lightgrey")
.on("mouseover", function() {
if (active) active.classed("highlight", false);
active = d3.select(this.parentNode.appendChild(this))
.classed("highlight", true);
})
.style("stroke",function(d) {return color(d.name);})
.on("mouseout", function(d) {
d3.select('#path-' + d.name)
.transition()
.duration(750)
.style("stroke", "lightgrey")
})
.attr("id", function(d, i) { return "path-" + d.name; });
where the .on("mouseover"... code is meant to highlight the current "moused-over" line. It doesn't seem to work in my example. All the lines are highlighted initially and then turn grey with the mouseover/mouseout. If someone could help me identify how to update my code so that I can reorder the lines on mouseover that would be great!
I have been playing around with labeling the lines such that when either the line or its label is moused-over the line and label colors update. I've played around a bit using id's but so far I can't get both the text and the line to change color. I've managed to 1. mouseover the line and change the color of the text, 2. mouseover the text and change the color of the line, 2. mouseover the line and change the line, but not have both the line and the text change color when either of them are moused-over. Here is a section of code that serves as a start (using ids), but doesn't quite work as it only specifies the path, but not the text and the path. I've tried adding them both to d3.select('#path-','#text-'..., and variations on this, but it doesn't seem to work.
source.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", "lightgrey")
.on("mouseover", function(d){
d3.select(this)
.style("stroke",function(d) {return color(d.name);});
})
.on("mouseout", function(d) {
d3.select('#path-' + d.name)
.transition()
.duration(750)
.style("stroke", "lightgrey")
})
.attr("id", function(d, i) { return "path-" + d.name; });
source.append("text")
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 15]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.temperature) + ")"; })
.attr("x", 5)
.attr("dy", ".35em")
.style("stroke", "lightgrey")
.on("mouseover", function(d){
d3.select('#path-' + d.name)
.style("stroke",function(d) {return color(d.name);});
})
.on("mouseout", function(d) {
d3.select('#path-' + d.name)
.transition()
.duration(750)
.style("stroke", "lightgrey")
})
.text(function(d) { return d.name; })
.attr("font-family","sans-serif")
.attr("font-size","11px")
.attr("id", function(d, i) { return "text-" + d.name; });
I greatly appreciate your help. I am new to d3 and this help-serve. Its a steep learning curve at the moment, but I hope this example and the code is reasonably clear. If its not let me know how I can make it better and I can repost the question.
Thanks so much,
Nate
Chris Viau provided a good answer to this question over on the d3 Google group.
https://groups.google.com/forum/?fromgroups=#!topic/d3-js/-Ra66rqHGk4
The trick is to select the path's g parent to reorder it with the others:
this.parentNode.parentNode.appendChild(this.parentNode);
This appends the current selection's container "g" on top of all the other "g".
I've found this useful in lots of other instances as well.
Thanks Chris!
Related
i've stuck for this code. Im trying to add text behind the circle
and sample code like this
for the text:
g.selectAll(".my-text")
.data(marks)
.enter().append("text")
.attr("class", "text-desc")
.attr("x", function(d, i) {
return projection([d.long, d.lat])[0];
})
.attr("y", function(d, i) {
return projection([d.long, d.lat])[1];
})
.attr('dy', ".3em")
.text(function() { return location})
.attr("text-anchor", "middle")
.attr('color', 'white')
.attr('font-size', 15)
and for circle like this
g.selectAll(".circle")
.data(marktests)
.enter().append("circle")
.attr("class", "bubble")
.attr("cx", function(d, i) {
return projection([d.long, d.lat])[0];
})
.attr("cy", function(d, i) {
return projection([d.long, d.lat])[1];
})
.attr("r", function() {
return myRadius(locationGroup + 20);
})
.on('mouseover', tipBranch.show)
.on('mouseout', tipBranch.hide)
.on('click', function(d){
window.open('http://localhost:8000/detail/'+d.branch);
});
}
but i got result just like this
and the elements if using inspect element
Thank you if you can help to help me and explain how to solve the problem code
First of all I noticed the following issue:
g.selectAll(".my-text")
.data(marks)
.enter().append("text")
.attr("class", "text-desc")
Also the following line: .text(function() { return location}) is faulty because you are missing the data object that you iterate with. This might be changed to: .text(function(d) { return d.location})
you are selecting all elements with class .my-text but then you are attaching text-desc as class to the text elements. The correct change for this would be:
g.selectAll(".text-desc")
.data(marks)
.enter().append("text")
.attr("class", "text-desc")
considering that you want to use text-desc as a class. the same problem is with the circle as well: Either do: g.selectAll("circle") to select the circle tag elements or g.selectAll(".bubble") to select the bubbles.
You are also using different iterating objects for text and circles - usually you should iterate over a single collection.
Another issue with the sample is that location and locationGroup are not part of the collection items. I would expect that the values to be taken from the data object as such .text( d => d.location) and .attr("r", d => myRadius(d.locationGroup)). Before proceeding make sure that you populate iterating items with this properties.
Another approach would be to do the following:
const group =
g.selectAll('.mark')
.data(marks)
.enter()
.append('g')
.attr('class', 'mark')
.attr('transform', d => {
const proj = projection([d.long, d.lat])
return `translate(${proj[0]}, ${proj[1]})`;
})
group.append('text').text(d => return d.location) //apply other props to text
group.append('circle').text(d => return d.location) //apply other props to circle
Using this approach will allow you to iterate the collection with a group element and use translation property in order to move the group to the location (small improvement, projection will be executed once) and use the group to populate with other elements: text, circle.
Hope it helps.
I have built a multi series line chart using the D3 library. I have been trying to work on animating the line chart so I can give out one line at a time and not all 5 of them together. Can anybody give me some ideas as to how I can go about with this? I have tried using transition() but I seem to be using it wrong. This is what I have done so far -
var city = svg.selectAll(".city")
.data(years)
.enter().append("g")
.attr("class", "city");
city.append("path")
.attr("class", "line");`
d3.transition().selectAll(".line")
.duration(500)
.ease("linear")
.attr("d", function(d){return line(d.values); })
.style("stroke", function(d) {return color(d.name); })
.attrTween("d",pathTween);
function pathTween() {
var interpolate = d3.scale.quantile()
.domain([0,1])
.range(d3.range(1,years.length+1));
return function(t){
return line(data.slice(0,interpolate(t)));
};
D3 transitions have a .delay() method that allows you to set a delay before each transition starts. In your case (having each line start separately) it would look like this:
d3.transition().selectAll(".line")
.duration(500)
.delay(function(d, i) { return i * 500; })
.ease("linear")
.attr("d", function(d){return line(d.values); })
.style("stroke", function(d) {return color(d.name); });
This takes the index of each line (i) and offsets the start of the transition depending on that.
I've been playing around with mbostock's D3 hive plot from here, and I would like to upgrade it to have similar functionalities as this example. That is, when I mouseover a node, all its links should be highlighted, as well as text that shows from where and to where these links come from/go to.
I manage highlighting the individual nodes and links without a problem using for example
svg.selectAll(".link")
.data(lLinks)
.enter().append("path")
.on("mouseover", function() {
d3.select(this)
.transition()
.duration(150)
.style("stroke-width", 3)
})
but I am having trouble figuring out how to highlight all the links belonging to a particular node.
My current data structure is practically the same as mbostock's, which I assume is ill suited for what I am trying to do.
How should I go about upgrading this code to highlight all the links and show their source/destination?
here's a working fiddle: http://jsfiddle.net/boatrokr/rk2s5/
Below is the code for the node selection. As you can see, all I did was select the links and if the link source or destination matched the selected node, the stroke width of the link gets set to 5. I'm not sure if this is the best way to do it, but it works. - edit: I forgot to remove a console.log in there - needed it for dev :)
svg.selectAll(".node")
.data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("transform", function(d) { return "rotate(" + degrees(angle(d.x)) + ")"; })
.attr("cx", function(d) { return radius(d.y); })
.attr("r", 5)
.style("fill", function(d) { return color(d.x); })
.on("mouseover", function(d) {
d3.select(this)
.transition()
.duration(150)
.style("stroke-width", 3)
d3.selectAll(".link")
.data(links)
.style("stroke-width", function (dl) {
if(dl.source == d){
console.log(dl);
return 5;
}else if(dl.target == d){
return 5;
}
});
})
.on("mouseout", function(){
d3.select(this)
.style("stroke-width", 1.5)
d3.selectAll(".link")
.style("stroke-width", 1.5)
});
I have created a force directed graph but I'm unable to add text to the links created.
How can I do so?
Following is my code link
I have used the following line to append the titles on the link's, but its not coming.
link.append("title")
.text(function (d) {
return d.value;
});
What am I doing wrong with this ?
This link contains the solution that you need.
The key point here is that "title" adds tooltip. For label, you must provide slightly more complex (but not overly complicated) code, like this one from the example from the link above:
// Append text to Link edges
var linkText = svgCanvas.selectAll(".gLink")
.data(force.links())
.append("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("x", function(d) {
if (d.target.x > d.source.x) {
return (d.source.x + (d.target.x - d.source.x)/2); }
else {
return (d.target.x + (d.source.x - d.target.x)/2); }
})
.attr("y", function(d) {
if (d.target.y > d.source.y) {
return (d.source.y + (d.target.y - d.source.y)/2); }
else {
return (d.target.y + (d.source.y - d.target.y)/2); }
})
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("dy", ".35em")
.text(function(d) { return d.linkName; });
The idea of the code is simple: It calculates the midpoint of the link, and displays some text at that place (you can decide what that text actually is). There are some additional calculations and conditions, you can figure it out from the code, however you'll anyway want to change them depending on your needs and aesthetics.
EDIT: Important note here is that "gLink" is the name of the class of links, previously defined with this code:
// Draw lines for Links between Nodes
var link = svgCanvas.selectAll(".gLink")
.data(force.links())
In your example, it may be different, you need to adjust the code.
Here is a guide how to incorporate solution from example above to another example of force layout that doesn't have link labels:
SVG Object Organization and Data Binding
In D3 force-directed layouts, layout must be supplied with array of nodes and links, and force.start() must be called. After that, visual elements may be created as requirements and desing say. In our case, following code initializes SVG "g" element for each link. This "g" element is supposed to contain a line that visually represent link, and the text that corresponds to that link as well.
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter()
.append("g")
.attr("class", "link")
.append("line")
.attr("class", "link-line")
.style("stroke-width", function (d) {
return Math.sqrt(d.value);
});
var linkText = svg.selectAll(".link")
.append("text")
.attr("class", "link-label")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.value;
});
"g" elements have class "link", lines have class "link-line", ad labels have class "link-label". This is done so that "g" elements may be easily selected, and lines and labels can be styled in CSS file conveninetly via classes "link-line" and "link-label" (though such styling is not used in this example).
Initialization of positions of lines and text is not done here, since they will be updated duting animation anyway.
Force-directed Animation
In order for animation to be visible, "tick" function must contain code that determine position of lines and text:
link.attr("x1", function (d) { return d.source.x; })
.attr("y1", function (d) { return d.source.y; })
.attr("x2", function (d) { return d.target.x; })
.attr("y2", function (d) { return d.target.y; });
linkText
.attr("x", function(d) {
return ((d.source.x + d.target.x)/2);
})
.attr("y", function(d) {
return ((d.source.y + d.target.y)/2);
});
Here is the resulting example: plunker
I have the multi series line chart code with slight modifications to support my data set. This is what I wish to do, and no solution I have looked at seems to function properly for me. I wish to overlay some element (circle, rectange, hidden, whichever) over each point on the line such that I could then attach a mouseover element on that point to display a box with data containing the d.time, d.jobID and how much that differs from an average. If possible, I would like the solution to only do this to the main line (the varying line) rather than the two lines drawn to represent the average. Here, I have a picture of the graph as-is for visual inspection. If that doesn't work, I have also attached it.
I have posted a bit the code below:
d3.tsv("values.tsv", function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "time" && key !== "jobID"; }));
data.forEach(function(d) {
d.time = parseDate(d.time);
d.jobID = parseInt(d.jobID);
});
var points = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {time: d.time, jobID: d.jobID, value: parseFloat(d[name],10)};
})
};
});
....
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 7)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("mbps");
var point = svg.selectAll(".point")
.data(points)
.enter().append("g")
.attr("class", "point");
point.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
point.append("text")
.datum(function(d) { return {name: d.name, jobID: d.jobID, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.time) + "," + y(d.value.value) + ")"; })
.attr("x", 6)
.attr("dy", ".7em")
.text(function(d) { return d.name; });
});
I have already tried the following code just to see if it worked with my implementation:
point.append("svg:circle")
.attr("stroke", "black")
.attr("fill", function(d, i) { return "black" })
.attr("cx", function(d, i) { return x(d.time) })
.attr("cy", function(d, i) { return y(d.value) })
.attr("r", function(d, i) { return 3 });
D3.JS seems like a pretty awesome piece of work, and I'm fortunate to have it.
EDIT: jsfiddle
The trick is to pass the data again to a selection and then operate on the result of that. Have a look at Mike's tutorial for some background and examples.
I've changed your jsfiddle to add circles here. Attaching svg:title elements or doing something else to show more information should be straightforward. Note that I modified your code to create the data points slightly to include the name with each element. This way, only one additional level of selections is necessary (treat all the points the same and add them in a single pass). The cleaner way to solve this from a code design point of view would be to have 2 additional levels -- first have a selection for the points for an individual line (and add an svg:g element to group them) and then add the points within this group. This would make the code quite a bit more complex and difficult to understand though.