Displaying text after an onclick event - javascript

I'm using click events to log data to the console, but i'd like to display this data in a separate box (which i have created). Does anyone have any advice or suggestions for this? Or is there a decent library that can help me achieve this?
Cheers
var circles = svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r", 7)
.attr("cx", function(d) { return xScale(d[1]); })
.attr("cy", function(d) { return yScale(d[2]); })
.on('click', function(d, i) {
console.log("click", d[0]);
})
.attr("fill", function(d) {
var result = null;
if (data.indexOf(d) >= 0) {
result = colours(d);
} else {
result = "white";
}
return result;
});
var textBox = svg.append("rect")
.attr("x", 5)
.attr("y", 385)
.attr("height", 150)
.attr("width", 509)
.style("stroke", bordercolor)
.style("fill", "none")
.style("stroke-width", border);

In the "click" listener just select your box, or use the selection you already have:
circles.on("click", function(d) {
selection.append("text")
//etc...
})
Here is a simple demo, click the circle:
var svg = d3.select("svg");
var circle = svg.append("circle")
.datum({
name: "foo"
})
.attr("cx", 100)
.attr("cy", 100)
.attr("r", 60)
.style("fill", "teal");
var box = svg.append("g")
.attr("transform", "translate(300,50)");
box.append("rect")
.attr("height", 50)
.attr("width", 100)
.style("stroke", "black")
.style("fill", "none")
.style("stroke-width", "2px");
circle.on("click", function(d) {
box.append("text")
.attr("x", 10)
.attr("y", 20)
.text(d.name)
})
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="500" height="300"></svg>
Finally, two tips: make your selection a group or any other valid container for the text, not a rectangle, because you cannot append a text to a rectangle. Also, be prepared for all kinds of problems trying to fit your texts inside that rectangle: wrapping texts in an SVG is notoriously complicated.

Related

How to select a specific d3 node group element

I have a D3 v4 force simulation with several nodes. Each node has a group. When I mouse over one of the elements of that group(an invisible circle) I want one of the other elements (the red circle on that specific node only which I gave an id of "backcircle") to do something. Currently this is what I have, but it does it to all nodes not just the one I'm hovering over's element.
this.node = this.d3Graph.selectAll(null)
.data(this.props.nodes)
.enter()
.append("g")
.attr("class", "nodes");
this.node.append("circle")
.attr("id", "backCircle")
.attr("r", 60)
.attr("fill", "red")
this.node.append("svg:image")
.attr("xlink:href", function(d) { return d.img })
.attr("height", 60)
.attr("width", 60)
.attr("x", -30)
.attr("y", -30)
this.node.append("circle")
.attr("r", 60)
.attr("fill", "transparent")
.on( 'mouseenter', function(d) {
d.r = 65;
this.node.select("#backCircle")
.transition()
.attr("r", 80);
}.bind(this))
Before anything else, two important tips:
Do not use "transparent" in an SVG.
IDs are unique. So, use classes instead (or select by the tag name)
Back to your question:
There are several ways of selecting the circle element based on a sibling circle element. The first one is going up the DOM and down again, using this.parentNode. The second one, if you know exactly the sequence of the siblings, is using previousSibling.
In the following demos, I have 3 elements per group: a circle, a text and a rectangle. Hovering over the rectangle will select the circle.
First, the option with this.parentNode. in your case:
d3.select(this.parentNode).select(".backCircle")
Hover over the squares:
var svg = d3.select("svg");
var data = [50, 150, 250];
var g = svg.selectAll(null)
.data(data)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(" + d + ",75)"
});
g.append("circle")
.attr("class", "backCircle")
.attr("r", 40)
.attr("fill", "teal")
g.append("text")
.attr("font-size", 20)
.attr("text-anchor", "middle")
.text("FOO");
g.append("rect")
.attr("x", 20)
.attr("y", 20)
.attr("width", 20)
.attr("height", 20)
.style("fill", "firebrick")
.on("mouseenter", function() {
d3.select(this.parentNode).select(".backCircle")
.transition()
.attr("r", 50)
}).on("mouseleave", function() {
d3.select(this.parentNode).select(".backCircle")
.transition()
.attr("r", 40)
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Then, the option with previousSibling (here, you don't even need to set a class). In your case:
d3.select(this.previousSibling.previousSibling)
Hover over the squares:
var svg = d3.select("svg");
var data = [50, 150, 250];
var g = svg.selectAll(null)
.data(data)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(" + d + ",75)"
});
g.append("circle")
.attr("r", 40)
.attr("fill", "teal")
g.append("text")
.attr("font-size", 20)
.attr("text-anchor", "middle")
.text("FOO");
g.append("rect")
.attr("x", 20)
.attr("y", 20)
.attr("width", 20)
.attr("height", 20)
.style("fill", "firebrick")
.on("mouseenter", function() {
d3.select(this.previousSibling.previousSibling)
.transition()
.attr("r", 50)
}).on("mouseleave", function() {
d3.select(this.previousSibling.previousSibling)
.transition()
.attr("r", 40)
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
PS: Have in mind that, since I'm not using an object, there is no need for bind(this) in my snippets.
I think you need to select the node that is firing the mouseenter event from within its handler.
this.node.append("circle")
.attr("r", 60)
.attr("fill", "transparent")
.on( 'mouseenter', function(d) {
var mouseenterNode = d3.select(this)
mouseenterNode.attr("r", 65);
mouseenterNode.select("#backCircle")
.transition()
.attr("r", 80);
}.bind(this))

D3JS different image for each circle/node

So, I want to apply a different image in each circle in d3js.
One circle = one image different to others...
In my JSON data, i add a image path like this :
{"name":"Myriel","group":1,"icon": "images/01.png"},
{"name":"Napoleon","group":1,"icon": "images/10.png"}
the only solution I found for display images in circles in d3js is :
I declare refs and svg object.
for(var i=0;i<graph.nodes.length;i++){
var defs = svg.append('svg:defs');
defs.append('svg:pattern')
.data(graph.nodes)
.attr('id', "image"+i)
.attr('width', '1')
.attr('height', '1')
.append('svg:image')
.attr('xlink:href', function(d) { return(graph.nodes[i].icon); })
.attr('x', 0)
.attr('y', 0)
.attr('width', 120)
.attr('height', 120);
}
and I built my nodes/circles like that :
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 30)
.style("fill","url(#image1)")
.on("mouseover",function(){
var sel = d3.select(this)
sel.moveToFront();
})
.on("mouseout",function(){
var sel = d3.select(this);
sel.moveToBack();
});
If you want more explication, say me.
Sorry for my english...
Thank you in advance for helping!
This is the way I do it in my project (http://arda-maps.org/familytree/). I'm using png as image tough but it very similar if you just replace it with your ID from your svg path.
node
.append("svg:image")
.attr("class", "circle")
.attr("xlink:href", function (d) {
return "/pics/arda/creature/" + d.uniquename + "_familytree.png";
})
.attr("x", function (d) {
return familytree.posXY(d);
})
.attr("y", function (d) {
return familytree.posXY(d);
})
.attr("width", function (d) {
return familytree.sizeXY(d);
})
.attr("height", function (d) {
return familytree.sizeXY(d);
})
.on("error", function () {
d3.select(this).style("visibility", "hidden");
});
So you should test if this works for you.
.attr("xlink:href", function (d) {
return "url(#image1)";
})

Wrong positioning of D3 nodes

I have a simple force layout that calculates nodes and links when certain buttons are clicked. The first time the nodes are calculated and displayed everything is correctly positioned. However, when nodes are recalculated after another click, the position of the circles I have appended to the nodes are way off yet the text I added remains in the right place. Here's my JS:
//Compute Nodes and Links
var data = this.computePreviewNodes($(e.currentTarget).data("id"), $(e.currentTarget).data("type"));
var canvas = d3.select(".body").append("svg")
.attr("width", width)
.attr("height", screen.height/2)
.append("g");
canvas.append("text")
.text(compObj.name)
.attr("text-anchor", "middle")
.attr("font-size", "2em")
.attr("x", width/2)
.attr("y", 40);
var force = d3.layout.force()
.nodes(data.nodes)
.links(data.links)
.gravity(.05)
.distance(100)
.charge(-10)
.size([width, screen.height/2]);
var links = canvas.selectAll(".links")
.data(data.links)
.enter().append("line")
.attr("class", "links")
.attr("fill", "none")
.attr("stroke", "blue");
var nodes = canvas.selectAll(".nodes")
.data(data.nodes)
.enter()
.append("g")
.attr("class", "nodes")
.call(force.drag);
nodes.append("circle")
.attr("cx", function(d) {return d.x;})
.attr("cy", function(d) {return d.y;})
.attr("r", 10)
.attr("fill", "green");
nodes.append("text")
.text(function(d) {return d.name})
.attr("text-anchor", "right")
.attr("font-size", "1.8em")
.attr("y", 5);
force.on("tick", function(e) {
nodes
.attr("transform", function(d, i){
return "translate(" + d.x + ", " + d.y + ")";
});
links
.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;})
})
force.start();
My computePreviewNodes() function simply comes up with what nodes need to be displayed based on which button is clicked. My thoughts are that maybe I'm not updating my node positions correctly after the second rendering of my nodes. Any ideas?
Here's my canvas at the first click:
And here it is when I click/calculate my nodes once again:

D3: Attach text to circle such that it has same priority as circle object

I am able to add text to my sketch, but I would like it if I could make my text attached directly to the circle. This means that if a circle gets over-written by another circle, the text will as well. On a higher level not, I am finding the d3 model hard for constructing objects in a way that makes them composable with different shapes, etc. The code seems very procedural to mean so any tips would be greatly appeciated :)
JSFiddle link
var link = "https://api.github.com/orgs/csci-4830-002-2014/repos"
d3.json(link, function(error, data) {
var w = 10000;
var h = 1000;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("line")
.data(data)
.enter()
.append("line")
.attr("x1", 5)
.attr("y1", 5)
.attr("x2", function (d,i){
return 30*d.forks_count;
})
.attr("y2", function (d,i){
return 30*d.open_issues_count;
})
.attr("stroke-width", 2)
.attr("stroke", "black");
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r", 40)
.attr("cx", function(d){ return 30*d.forks_count; })
.attr("cy", function(d){ return 30*d.open_issues_count; })
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("fill", "white")
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("dx", function(d,i){ return 30*d.forks_count; })
.attr("dy", function(d,i){ return 30*d.open_issues_count; })
.text(function(d){
if (d.name.indexOf("challenge") != -1)
return "C";
else
return "H";
});
});
With the way your code written right now, all the lines will be added first, then all the circles, and finally the texts. SVG will always put elements added later on top. So to achieve what you want, you will need to group them up. To do this, you will need to add a g element for each element of your data
var element = svg.selectAll(".element")
.data(data)
.enter()
.append("g")
.attr("class","element");
Now you can add the line, circle, and text to it
element.append("line")
.attr("x1", 5)
.attr("y1", 5)
.attr("x2", function (d, i) {
return 30 * d.forks_count;
})
.attr("y2", function (d, i) {
return 30 * d.open_issues_count;
})
.attr("stroke-width", 2)
.attr("stroke", "black");
element.append("circle")
.attr("r", 30)
.attr("cx", function (d) {
return 30 * d.forks_count;
})
.attr("cy", function (d) {
return 30 * d.open_issues_count;
})
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("fill", "white")
element
.append("text")
.attr("dx", function (d, i) {
return 30 * d.forks_count;
})
.attr("dy", function (d, i) {
return 30 * d.open_issues_count+6;
})
.style("text-anchor", "middle")
.text(function (d) {
if (d.name.indexOf("challenge") != -1) return "C";
else return "H";
});
You can check the updated JSFiddle at http://jsfiddle.net/9tp1yun7/2/

How to access attributes of an element inside a group?

I'm not sure if I've grouped my elements properly, but my layout in d3 is like so:
var circleGroup = svg.selectAll("g")
.data(nodeList)
.enter()
.append("g")
This creates a bunch a groups, I need a circle in each group:
circleGroup.append("circle")
.attr("cx", function(d,i){
return coordinates[i][0];
})
.attr("cy", function(d,i){
return coordinates[i][1];
})
.attr("r", function(d){
return 10;
})
.attr("fill", "white");
The data itself doesn't actually have any coordinate data so I dynamically arrange them in a circle and just position them based on index. I also add some labels. I repeat coordinates[i][0] here but is there a way to access the "cx" and "cy" attributes of the circles? I tried a few forms of d3.select(this) but I'm getting nothing.
circleGroup.append("text")
.attr("x", function(d,i){
return coordinates[i][0];
})
.attr("y", function(d,i){
return coordinates[i][1];
})
.style("text-anchor","middle")
.text(function(d,i){
return d;
});
Don't mess with indices, this is hard to maintain and error prone. Instead of that, given your specific tree structure, use node.previousSibling:
circleGroup.append("text")
.attr("x", function() {
return d3.select(this.previousSibling).attr("cx");
})
.attr("y", function() {
return d3.select(this.previousSibling).attr("cy");
})
Here is a demo using (most of) your code:
var svg = d3.select("svg")
var circleGroup = svg.selectAll("g")
.data(d3.range(5))
.enter()
.append("g");
circleGroup.append("circle")
.attr("cx", function(d, i) {
return 20 + Math.random() * 280;
})
.attr("cy", function(d, i) {
return 20 + Math.random() * 130;
})
.attr("r", function(d) {
return 10;
})
.style("opacity", 0.2);
circleGroup.append("text")
.attr("x", function() {
return d3.select(this.previousSibling).attr("cx");
})
.attr("y", function() {
return d3.select(this.previousSibling).attr("cy");
})
.style("text-anchor", "middle")
.text("Foo");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>

Categories