Display an SVG image at the middle of an SVG path - javascript

I have a force directed graph with different size nodes. I want to display a custom icon in the middle of each path connecting two nodes. From the d3 examples I found the way to display images within the nodes. However, when I try the same technique on the paths, the images are not shown.
var path = svg.append("svg:g").selectAll("path").data(force.links());
var pathEnter = path.enter().append("svg:path");
pathEnter.attr("class", function(d) {
return "link " + d.target.type;
})
pathEnter.append("svg:g").append("image")
.attr("xlink:href","http://127.0.0.1:8000/static/styles/images/add.png")
.attr("x",0).attr("y",0).attr("width",12).attr("height", 12)
.attr("class", "type-icon");

I guess I need a bit more patience before asking a question. The way I solved the problem is:
var icon = svg.append("svg:g").selectAll("g")
.data(force.links()).enter().append("svg:g");
icon.append("image").attr("xlink:href","imagePath")
.attr("x", -20)
.attr("y", -2)
.attr("width", 12).attr("height", 12)
.attr("class", "type-icon");
And then in the tick function:
icon.attr("transform", function(d) {
return "translate(" +((d.target.x+d.source.x)/2) + "," +
((d.target.y+d.source.y))/2 + ")";
});
to get the center point between the two nodes.

Related

How can i put label to my node in d3 directed graph [duplicate]

This question already has an answer here:
Creating force layout node labels in d3.js
(1 answer)
Closed 4 years ago.
I have a plnkr which creates node directed graph something like this:
http://plnkr.co/edit/Usru9pGp98ju2d0St6Wz?p=preview
Currently it displays the node title when we hover over it using this function:
.on('mouseover', function(d) {
textTarget = d
text.attr('transform', 'translate(' + d.x + ',' + d.y + ')')
.text(d.id)
.style('display', null)
d3.select(this)
.style('fill', colors.nodes.hover)
d3.selectAll(childNodes(d))
.style('fill', colors.nodes.hover)
.style('stroke', colors.nodes.method)
.style('stroke-width', 2)
d3.selectAll(parentNodes(d))
.style('fill', colors.nodes.dep)
.style('stroke', colors.nodes.method)
.style('stroke-width', 2)
})
But my idea is to show the label everytime.
I found some similar question where they have asked to append label to node, so I tried this:
node.append("text")
.attr('transform', function())
.attr('transform', 'translate(' + d.x + ',' + d.y + ')')
.text(d.id)
.style('display', null)
But doing this breaks my graph.
I am little new to d3 world, so not able to figure out what exactly is going wrong in this case.
Can any one help me to display label always for all the node (either inside the node circle or appended with it)
Approach:
So my approach to that would to to create group elements g with circle and text inside them, and then we can use force layout to position them with transform. This way we only care about group element position and circle with text should follow that position. So the node structure might look like that:
<g transform='translate(30,70)'>
<circle r='3'>/<circle>
<text>sample text</text>
</g>
Code:
You have quite a lot of code in your sample so I'll just focus on having persistent labels over circles. This might break some other things I'm not focusing on but should give you direction and hopefully you'll be able to fix the rest if not fell free to ask.
Using your plunker first thing you have to do it so associate data with g elements instead of circle :
node = vis.selectAll('g.node')
.data(nodes, function(d) { return d.filename })
.enter().append('g');
Then you can append circles to those g elements, and in this case there is no need to define cx and cy as this is taken care of by transform applied to g elements, so all you need to specify is r and any other styles you wan to apply:
node.append('circle')
.attr('class', 'node')
.attr('r', function(d) {
return scale * (d.root ? 8 : d.module && !d.native ? 5 : 3)
})
(...)
Now you can add your labels:
node.append('text')
.text(function(d){return d.id})
.style('text-anchor',middle); // this will center label horizontally
One last step is to update on 'tick' function as before it was updating circle position and now we want it to update g position, so instead of:
node.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; });
you should do:
node.attr('transform', function(d) {
return 'translate('+ d.x + ',' + d.y + ')';
});
And here you have your plunker with my changes, hope thats what you asked for.
http://plnkr.co/edit/Q69DkEH5Z9aOe92UwlOB?p=preview
Fell free to ask any more questions if you have any :)

d3: svg image in zoom circle packing

UPDATE: New JSFIDDLE Scaling now working, ditched the defs and rect altogether and just appended the image. But still stuck on translate.
The translating is still not working on zoom. I can set the translate to say -100 for both x and y to get the non-zoomed placement correct. But, when zooming, it's of course still translating it -100 and not the larger value it would need to be to keep it in place.
Appears to need something in the code in the zoom section toward the bottom. Been messing with the part currently commented out, but no luck so far.
// .attr("transform", function(d) { return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")"; })
// .attr("x", function(d) { return d.r * k; })
// .attr("y", function(d) { return d.r * k; })
.attr("width", function(d) { return d.r * k; })
.attr("height", function(d) { return d.r * k; })
Here's JSFIDDLE. I have a d3 circle packing with a raster image inside an svg rect within each node. How do you make the image scale when zooming? The container scales, but the image stays small and repeats when zoomed. Been trying to set the defs correctly, but no luck.
var defs = svg.append("defs")
// .data(nodes)
// .enter()
.append("pattern")
.attr("id", "bg")
.attr('patternUnits', 'userSpaceOnUse')
.attr('width', imageWidthHeight)
.attr('height', imageWidthHeight)
// .attr("transform", "translate(40,80)")
.append("image")
// .html("xlink:href", "img/" + function(d) { return d.image; })
.attr("xlink:href", "http://www.public-domain-photos.com/free-stock-photos-4/travel/yosemite/yosemite-meadows.jpg")
.attr('width', imageWidthHeight)
.attr('height', imageWidthHeight)
// .attr("transform", "translate(40,80)");
Also, can't get the container/image to translate into the center of the circle. I've commented those bits out for now because it screws everything up.
Have tried to apply info from these discussions, but still stuck. Thanks.
http://bl.ocks.org/mbostock/950642#graph.json
https://groups.google.com/forum/#!topic/d3-js/fL8_1BLrCyo
How to fill D3 SVG with image instead of colour with fill?
Adding elements to a D3 circle pack nodes
Answer JSFIDDLE
Got it. The trick was changing this bit of horrible:
(d.x - v[0]) * k
to this even worse bit of horrible:
(((d.x - v[0]) * (k)) - ((d.r / 2) * k))
Then the same for y.
Don't get me wrong, I'm grateful for the zoom circle pack template and the genius(es) who put it together. Thank you. It's just for someone at my noob level, the code above looks like a punishment of some kind. :)

Dynamically adding nodes to d3.js Force directed graph

I am having trouble with dynamically adding nodes on to a d3.js force directed graph and I was wondering if anyone here could shed some light on the subject.
The problem I am having is that I want the tick function to transform all nodes on the graph and not just the newly added ones.
Below are the functions I use for adding nodes and handling the transformation:
// Function to handle tick event
function tick() {
tick_path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" +
d.source.x + "," +
d.source.y + "L" +
d.target.x + "," +
d.target.y;
});
tick_node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
}
/**
* Append nodes to graph.
*/
function addNodes2Graph(){
// define the nodes
// selection will return none in the first insert
// enter() removes duplicates (assigning nodes = nodes.enter() returns only the non-duplicates)
var nodes = viz.selectAll(".node")
.data(g_nodes)
.enter().append("g")
.on("click", nodeClick)
.call(force.drag);
// From here on, only non-duplicates are left
nodes
.append("circle")
.attr("r", 12)
.attr("class", "node");
nodes.append("svg:image")
.attr("class", "circle")
.attr("xlink:href", "img/computer.svg")
.attr("x", "-8px")
.attr("y", "-8px")
.attr("width", "16px")
.attr("height", "16px");
// add the text
nodes.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) { return d.ip; });
return nodes;
}
// Execution (snippet)
// Add new nodes
tick_node = addNodes2Graph();
// Add new paths
tick_path = addPaths2Graph();
// Restart graph
force
.nodes(g_nodes) // g_nodes is an array which stores unique nodes
.links(g_edges) // g_edges stores unique edges
.size([width, height])
.gravity(0.05)
.charge(-700)
.friction(0.3)
.linkDistance(150)
.on("tick", tick)
.start();
I think the problem is that I only get non-duplicated results returned from addNodes2Graph which I then use in the tick function but I'm not sure how I could achieve this without adding duplicated nodes on to the graph.
At the moment, it's either adding duplicated elements on to the graph or transform only the new nodes on tick.
Thank you very much for your help in advance.
It looks like you're adding nodes only to the DOM, not to the force layout. To recap, here's what you need to do to add nodes to the force layout.
Add elements to the array that the force layout uses for its nodes. This needs to be the same array that you passed in initially, i.e. you can't create a new array and pass that it if you want smooth behaviour. Modifying force.nodes() should work fine.
Do the same for the links.
Add the new DOM elements using .data().enter() with the new data.
No change to the tick function should be required as adding the nodes and DOM elements is done elsewhere.
After adding the new nodes/links, you need to call force.start() again to have it take them into account.

Using arc.Centroid to put an svg:image in the middle of the arc - but not appearing

I am currently trying to place a svg:image in the centre of my arc:
var arcs = svg.selectAll("path");
arcs.append("svg:image")
.attr("xlink:href", "http://www.e-pint.com/epint.jpg ")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("width", "150px")
.attr("height", "200px");
I would appreciate it if someone could give me any advice on why it isn't appearing
thanks : http://jsfiddle.net/xwZjN/17/
Looking at the jsfiddle, you are creating the path elements after you try to append the svg:image elements to the them. It should be the other way around. You should first create the arcs and then append the images.
Second, as far as I know, the svg:path element should not contain any svg:image tags. It doesn't seem to display them if you place some inside. Instead what you should do is create svg:g tags with class arc and then use those to place the svg:images
Slightly modifying your jsfiddle could look something like this:
var colours = ['#909090','#A8A8A8','#B8B8B8','#D0D0D0','#E8E8E8'];
var arcs = svg.selectAll("path");
for (var z=0; z<30; z++){
arcs.data(donut(data1))
.enter()
//append the groups
.append("svg:g")
.attr("class", "arc")
.append("svg:path")
.attr("fill", function(d, i) { return colours[(Math.floor(z/6))]; })
.attr("d", arc[z])
.attr("stroke","black")
}
//here we append images into arc groups
var pics = svg.selectAll(".arc").append("svg:image")
.attr("xlink:href", "http://www.e-pint.com/epint.jpg ")
.attr("transform", function(d,i) {
//since you have an array of arc generators I used i to find the arc
return "translate(" + arc[i].centroid(d) + ")"; })
.attr("x",-5)
.attr("y",-10)
.attr("width", "10px")
.attr("height", "20px");
Where I also decreased the size of the images and offset them so that they fit into the arc.

import multiple svgs in one xml with d3.js

currently I'm fiddling with D3.js. It looks awesome!I was wondering if D3.js is suitable to draw multiple svg elements in a webpage. I found the d3.xml method and got it working.
Initially I was playing with Raphaeljs but, for me as a JS-newbie, useful tuts/docs are hard to find. I was converting SVG's to JSON-objects with webtool and then inculde them in 1 js-file it's a lot of work stripping tags and stuff. This saves HTTP-requests. Now I want to try the same with D3.js and see which one will suit me.
Any ideas for importing a multiple-SVG-file with D3?
There is no limit number of SVGs you can write using D3. All D3 does is access the DOM and modify it so that the browser knows to draw an SVG, or how to manipulate the SVG. D3 simply allows you to more easily access the elements and work with them.
If you are reading the SVGs from JSON objects, you just need to read in the multiple JSONS.
Take the first two examples from the D3js.org website as an example...
d3.csv("morley.csv", function(error, csv) {
var data = [];
csv.forEach(function(x) {
var e = Math.floor(x.Expt - 1),
r = Math.floor(x.Run - 1),
s = Math.floor(x.Speed),
d = data[e];
if (!d) d = data[e] = [s];
else d.push(s);
if (s > max) max = s;
if (s < min) min = s;
});
chart.domain([min, max]);
var svg = d3.select("body").selectAll("svg")
.data(data)
.enter().append("svg")
.attr("class", "box")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.bottom + margin.top)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(chart);
d3.select("button").on("click", function() {
svg.datum(randomize).call(chart.duration(1000)); // TODO automatic transitions
});
});
This, from the Box Plot example, will open up "morley.csv" and work with the data.
d3.json("flare.json", function(error, root) {
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(root))
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("title")
.text(function(d) { return d.className + ": " + format(d.value); });
node.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return color(d.packageName); });
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.className.substring(0, d.r / 3); });
});
This, from the Bubble Chart example, will open up "flare.json" and start doing stuff with that. There is nothing stopping you from doing both of these actions in the same file.
What you will have to look out for is variable references, as with any other program. Each SVG will need its own reference. The above two code blocks use the same variable names for several things, so they'd step on each other. If you wanted both these on one page, you would simply need to rename the variables so they are unique.
d3.xml("./svgs.xml", "application/xml", function(xml, error) {
var data = xml.documentElement,
sections = data.getElementsByTagName("section"),
svgWrapper = "center";
for (var i = 0, lenS = sections.length; i < lenS; i++) {
var name = sections[i].getElementsByTagName("name")[0].childNodes[0].nodeValue,
images = sections[i].getElementsByTagName("image");
for (var j = 0, lenI = images.length; j < lenI ; j++) {
//<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
var image = images[j].firstElementChild;
//assuming I only target 1 unique HTML5-element
if (document.getElementsByTagName(name)[0] !== undefined) {
//in search of wrapper-class and append data to it
document.getElementsByTagName(name)[0].getElementsByClassName(svgWrapper)[0].appendChild(image);
// If node "name" is not a element then it's an id
} else if (document.getElementById(name) !== undefined) {
document.getElementById(name).getElementsByClassName(svgWrapper)[0].appendChild(image);
} else {
console.alert("what the hell went wrong while inserting svgs?!");
alert("What the hell went wrong while inserting svgs?!");
}
}
}
});
If somebody got pointers to maybe improve this for like IE9 cause only test this for Chrome and Firefox (have to install Windows VM). How do I catch and handle the error-parameter in the XHR?

Categories