Display svg image as node of d3.js in phonejs - d3.js

Making application with phonejs, using d3.js with force layout. I want to display images as node of d3.
Here is how node is created:
node = container.append("g").selectAll("image.node")
.data(nodes_edges_json.nodes)
.append("svg:image")
.attr("class", "node")
.attr("xlink:href", function(nodeObj) { return nodeObj.image; })
.attr("width", function(nodeObj) { return setHeightWidth(nodeObj); })
.attr("height", function(nodeObj) { return setHeightWidth(nodeObj); })
.attr("x", function(nodeObj) { return setXYCoordinates("x", nodeObj); })
.attr("y", function(nodeObj) { return setXYCoordinates("y", nodeObj); })
.attr("title",function(nodeObj) { return nodeObj.name;})
.call(drag);
Currently it does not display images in place of nodes.

You may need to add an enter()
node = container.append("g").selectAll("image.node")
.data(nodes_edges_json.nodes).enter() // <-----------------HERE
.append("svg:image")
.attr("class", "node")
.attr("xlink:href", function(nodeObj) { return nodeObj.image; })
.attr("width", function(nodeObj) { return setHeightWidth(nodeObj); })
.attr("height", function(nodeObj) { return setHeightWidth(nodeObj); })
.attr("x", function(nodeObj) { return setXYCoordinates("x", nodeObj); })
.attr("y", function(nodeObj) { return setXYCoordinates("y", nodeObj); })
.attr("title",function(nodeObj) { return nodeObj.name;})
.call(drag);

Related

How to do use getbbox to generate multiple rects

i can only generate one rect for the text i have. I dont know how to apply getBBox to multiple text elements so they can all have their own backgrounds. Does anyone know how i can accomplish this?
function jobsCreation() {
let enteringText = dataJobs
.enter()
.append("text")
.attr("x", function (d) { return d.posX })
.attr("y", function (d) { return d.posY })
.attr("id", "jobText")
.text(function (d) { return d.name });
let texts = d3.selectAll("#jobText");
let bbox = enteringText.node().getBBox();
console.log(bbox);
let rect = svg.append("rect")
.attr("x", bbox.x)
.attr("y", bbox.y)
.attr("width", bbox.width)
.attr("height", bbox.height)
.attr("id", "nodeBox")
.style("fill", "white")
.style("stroke", "black")
.style("stroke-width", "1.5px");
enteringText.raise();
}
svg.append() will create a single element and selection.node() will only return a single node.
While selection.nodes() will return an array all the nodes in a selection, we still need to append one rectangle for each node. To do this, we can use the array returned by selection.nodes() as a data array for a enter cycle:
let enteringRects = svg.selectAll("rect")
.data(enteringText.nodes())
.enter()
.append("rect")
.attr("x", function(node) { return node.getBBox().x })
.attr("y", function(node) { return node.getBBox().y })
.attr("width", function(node) { return node.getBBox().width })
.attr("height", function(node) { return node.getBBox().height })
.style("fill", "white")
.style("stroke", "black")
.style("stroke-width", "1.5px")
.lower();
Now we create one rectangle for every text element we create. As the bound datum in this selection is a DOM element we can access getBBox() easily as it is now a property of the datum itself.
let data = d3.range(15).map(function(d) { return Math.floor(Math.random() * 150) });
let width = 500;
let height = 300;
let svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
let enteringText = svg.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("x", function (d) { return Math.random() * width })
.attr("y", function (d) { return Math.random() * height })
.text(function (d) { return d });
let enteringRects = svg.selectAll("rect")
.data(enteringText.nodes())
.enter()
.append("rect")
.attr("x", function(node) { return node.getBBox().x })
.attr("y", function(node) { return node.getBBox().y })
.attr("width", function(node) { return node.getBBox().width })
.attr("height", function(node) { return node.getBBox().height })
.style("fill", "white")
.style("stroke", "black")
.style("stroke-width", "1.5px")
.lower();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

D3 js Mouseover/Mouseout

Can i use style fill and class with fill also ?
bar.selectAll("rect")
.data(function(d) { return d.valores; })
.enter().append("rect")
.attr("class", function (d) { return ((d.star == true) ? "bar_star" : "bar"); }) //added line
.attr("x", function (d) { return x1(d.name); })
.attr("width", x1.rangeBand())
.attr("y", height)
.attr("height", 0)
.transition()
.duration(500)
.attr("y", function (d) { return y(d.value); })
.attr("height", function (d) { return height - y(d.value); })
.attr("value", function(d) { return d.name; })
.style("fill", function(d) { return color(d.name); }); //before
bar.on("mouseover", tip.show);
bar.on("mouseout", tip.hide);
My classes is :
.bar {
fill: orange;
}
.bar:hover {
fill: orangered ;
}
If I try directly in the
.on(mouseover)
it does not work.My fill is invisible.When I remove the line
.style("fill", function(d) { return color(d.name); });
Nothing works neither the tooltip nor the hover. What should I do ?
Thank you for answring,

How to draw data properties aside a graph node?

I'm drawing a graph with D3 in which the node is a picture with name above. I would now like display some properties on the right side of the node.
var node = svg.select(".nodes").selectAll(".node")
.data(nodes, function(d) {return d.id});
var nodeEnter = node.enter()
.append("g")
.attr("class", function(d) { return "node "+d.type})
.attr("id", function(d) { return d.label })
.call(force.drag);
nodeEnter.filter(function(d) {return (d.properties)}).append("rect")
.attr("x", -40)
.attr("y", -50)
.attr("width", 130)
.attr("height", 90);
nodeEnter.filter(function(d) {return (d.imgB64)}).append("image")
.attr("xlink:href", function(d) { return 'data:image/png;base64, ' + d.imgB64; })
.attr("x", -40)
.attr("y", -40)
.attr("width", 80)
.attr("height", 80);
d3.selectAll(node);
var textNode = nodeEnter.filter(function(d) { return (d.imgB64) && (d.name));
textNode.append("text")
.attr("class", function(d) { return "text "+d.type; })
.attr("text-anchor", function(d) { return (d.properties) ? "left" : "middle"})
.attr("dx", -38)
.attr("dy", function(d) { return (d.properties) ? -38 : 42})
.text(function(d) {
return d.name;
});
var textNode = nodeEnter.filter(function(d) { return (d.properties)});
textNode.append("text")
.attr("text-anchor", "left")
.attr("dx", 40)
.attr("dy", -30)
.style("font-size",9)
.text(function(d) { return "MW: "+d.properties.MW.toPrecision(3); });
The above code properly draw one property on the right side of the image. How could I loop through all available properties of each node and display them as a list.
Each node data is formatted as follow:
{imgB64: "...", name: "A", properties: {MW: 354, logP: 4.6, pKd: -6.2, logPapp: -5.99}}
I tried the following:
var textNode = nodeEnter.filter(function(d) { return (d.properties)});
textNode.append("text")
.data(function(d) { return d.properties })
.attr("text-anchor", "left")
.attr("dx", 40)
.attr("dy", function(d,i) { return (i*15-30) })
.style("font-size",9)
.text(function(d) { return "foo" });
I get the an error message "TypeError: undefined is not an object (evaluating 'd.properties')".
How should I create new data to handle the properties?
Code is shared on fiddle
Thanks
As suggested by echonax, d.properties was not an array. This can be addressed by
.data(function(d) { return jsonToArray(d.data) })
with
function jsonToArray(data) {
var a = [];
for (k in data) {
a.push({key: k, value: data[k]});
}
return a;
}
Thanks to echonax suggestion, I saved a lot if redundant code.

Different shape/image for leaf nodes in pack layout

I am using a pack layout in d3.js (https://github.com/mbostock/d3/wiki/Pack-Layout) and load in a json file with a parent-child structure.
My question is probably a very trivial one: I would like to append a circle or an image(or maybe a rectangle) depending on whether d.children returns parent or child (where child in basically a leaf node).
This is a bit of the code that appends circles to all nodes:
vis.selectAll("circle")
.data(nodes)
.enter().append("svg:circle")
.attr("class", function(d) { return d.children ? "parent" : "child"; })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return d.r; });
Does anyone have any suggestions?
Thank you,
Use a filter function:
// if you have children, append a circle
node.filter(function(d){
return d.children;
})
.append("circle")
.attr("r", function(d) { return d.r; });
// if you don't have children, append a rect
node.filter(function(d){
return !d.children;
})
.append("rect")
.attr("width", function(d) { return d.r; })
.attr("height", function(d) { return d.r; })
.attr("x", function(d) { return -d.r/2; })
.attr("y", function(d) { return -d.r/2; });
Example here.

Can't add text without append("g"), append("g") gives error from d3.js

I am new to D3 and I am working with code from here. I changed the code so I can add new nodes (neighbors) and edges to them with data from MySql upon click on a node, which is why part of the node code is in start(). I want to append text labels to the nodes, and from some googling I understand that both the circle element and the text element needs to be within a g. However, when I do this, I get an error from d3.js on line 742:
Uncaught NotFoundError: Failed to execute 'insertBefore' on 'Node':
The node before which the new node is to be inserted is not a child of
this node
Why is this and how do I fix it to get what I want, while preserving the addNode functionality?
Here is my code:
var width = 960,
height = 500;
var color = d3.scale.category10();
var nodes = [],
links = [];
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.charge(-400)
.linkDistance(120)
.size([width, height])
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var node = svg.selectAll(".node")/*.append("g")*/,
link = svg.selectAll(".link");
function start() {
link = link.data(force.links(), function(d) { return d.source.id + "-" + d.target.id; });
link.enter().insert("line", ".node").attr("class", "link");
link.exit().remove();
node = node.data(force.nodes(), function(d) { return d.id;});
node.enter()/*.append("g")*/
.append("circle")
.attr("class", function(d) { return "node " + d.id; })
.attr("id", function(d) { return d.id; })
.attr("r", 8)
.on("click", nodeClick)
.append("text")
.text(function(d) {return d.id; });
node.exit().remove();
force.start();
}
function nodeClick() {
var node_id = event.target.id;
handleClick(node_id, "node");
}
function tick() {
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
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; });
}
The commented-out append("g") indicates where I tried to place it (separate attempts).
You want to cache the d3 selector before appending the text.
node.enter().append("g")
.append("circle")
.attr("class", function(d) { return "node " + d.id; })
.attr("id", function(d) { return d.id; })
.attr("r", 8)
.on("click", nodeClick)
.append("text")
.text(function(d) {return d.id; });
That'll work but create a xml structure like this:
<g>
<circle>
<text>...</text>
</circle>
</g>
What you want is:
<g>
<circle>...</circle>
<text>...</text>
</g>
To achieve that, you must insert one step:
var g = node.enter().append("g");
g.append("circle")
.attr("class", function(d) { return "node " + d.id; })
.attr("id", function(d) { return d.id; })
.attr("r", 8)
.on("click", nodeClick);
g.append("text")
.text(function(d) {return d.id; });

Resources