I'm trying to create a rounded picture using D3JS in a graph.
Here is my fiddle: http://jsfiddle.net/vsvuyusg/1/
As you can see, on line 99 we have this code (that supposed to work):
var defs = svg.append('svg:defs');
node.each(function(d) {
var _node = d3.select(this);
defs.append("svg:pattern")
.attr("id", "circlePicture_" + d.name)
.append("svg:image")
.attr("xlink:href", function(d) {
var rnd = Math.floor(Math.random() * 64 + 1);
var imagePath =
"http://www.bigbiz.com/bigbiz/icons/ultimate/Comic/Comic"
+ rnd.toString() + ".gif";
console.log(imagePath);
return imagePath;
})
.attr("width", 12)
.attr("height", 12)
.attr("x", 0)
.attr("y", 0);
_node.append("circle")
.attr("cx", 12)
.attr("cy", 12)
.attr("r", 12)
.style("fill", "url(#circlePicture_" + d.name + ")")
});
I create a pattern for each image, setting the node name on it and use it to fill a circle appended to the node (using the same node name as ref).
I've noticed that after the code executes, I cannot find any def or pattern tags inside the svg tag. Although, the console.log that we have when creating the patterns execute entirely.
What's wrong?
You need to set a height/width attribute on the pattern node:
defs.append("svg:pattern")
.attr("id", "circlePicture_" + d.name)
.attr("width", 12)
.attr("height", 12)
.append("svg:image")
...
Updated fiddle.
Related
I was trying to retrieve nodes and links from a python code that already has them unique and in the form of a JSON but while the console.log shows up the data on the browser am unable to view them as a forcelayout graph on the screen. Presumably i dont understand the sequence of calling here in javascript so bear with my lack of javascript knowledge. Any help here is much appreciated.
This is a borrowed example from the gallery of course with me tampering the d3.json piece.
<script>
var width = 960,
height = 500;
//force = d3.layout.force().nodes(d3.values(ailments)).links(rels).size([width, height]).linkDistance(60).charge(-300).on("tick", tick).start();
var nodes = {};
var ailments = {} ;
var rels = [];
var force = d3.layout.force().size([width, height]).linkDistance(60).charge(-300).on("tick",tick);
console.log('initiated force!! be with you !!');
d3.json("/getA", function(error, dataset){
console.log('getA the function gets called now ... ');
ailments = dataset.nodes;
//ailments = dataset.nodes['nodes'];
//rels = dataset.links['links'] ;
rels = dataset.links;
console.log(ailments);
//console.log(nodes);
console.log('relationships coming up..');
console.log(rels);
force.start();
});
function tick() {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
}
force = d3.layout.force().nodes(d3.values(ailments)).links(rels).size([width, height]).linkDistance(60).charge(-300).on("tick", tick).start();
console.log("now i threw the graph out there");
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.attr("stroke", "black")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
// Per-type markers, as they don't inherit styles.
// Use elliptical arc path segments to doubly-encode directionality.
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 6)
.call(force.drag);
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.name; });
function linkArc(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 + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
</script>
as i said i am still unclear on the sequence here as this is my first ever javascript attempt!
Noticed that python and flask tags were removed - which i think is fine because i dont think there is an issue with the below python code that's sending this data. just FYI
#app.route("/getA")
def getA():
print('get A gets called ...')
db = get_db()
nodes = []
rels = []
ailment = []
cure = []
jlinks = {"links":[]}
uniqueNodes = {"nodes":[]}
sql = "MATCH (a:Ailment) -[:SOLVED_BY]->(theCURE) return a as ailment, theCURE limit 5"
with db as graphDB_Session:
nodes = graphDB_Session.run(sql)
print("output:")
for node in nodes:
#print(node)
prepare_links(node, ailment, cure)
for idx, value in enumerate(ailment):
#print (ailment[idx], cure[idx])
source = ailment[idx]['title']
target = cure[idx]['title']
y = {"source":source, "target":target, "type":"SOLVED_BY"}
jlinks["links"].append(y)
if (source not in (uniqueNodes["nodes"])):
uniqueNodes["nodes"].append(source)
if (target not in (uniqueNodes["nodes"])):
uniqueNodes["nodes"].append(target)
print(jlinks)
print(uniqueNodes)
return Response(dumps({"nodes": uniqueNodes, "links": jlinks}),mimetype="application/json")
def serialize_ailment(ailment):
return {
"title":ailment["title"]
}
def serialize_cure(cure):
return {
"title":cure["title"]
}
def prepare_links(node, ailment, cure):
#ailment.append(serialize_ailment(node.value("ailment")))
ailment.append(node.value("ailment"))
#cure.append(serialize_cure(node.value("theCURE")))
cure.append(node.value("theCURE"))
Bear with some unwanted code, comments as i've basically tried out a few variants.
Two mistakes ... i figured ...
The sequence of calling - as i mentioned above had to be fixed by rolling the forcelayout calls from within the .json function
The approach to getting an array from the API call into another array object here in Javascript was also messed up. Specifically this line -
rels = dataset.links["links"];
looks very obvious but somehow i'd messed this up as well. The updated script that works on my own data finally goes like this -
d3.json("/getA", function(error, dataset){
console.log('getA the function gets called now ... ');
ailments = dataset.nodes;
rels = dataset.links["links"];
console.log(ailments);
//console.log(nodes);
console.log('relationships coming up..');
console.log(rels);
//links = [dataset.links];
rels.forEach(function(dink) {
dink.source = nodes[dink.source] || (nodes[dink.source] = {name: dink.source});
dink.target = nodes[dink.target] || (nodes[dink.target] = {name: dink.target});
console.log('creating unique nodes gets called again');
});
console.log('relationships again ..');
console.log(rels);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.attr("stroke", "black")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
var force = d3.layout.force().nodes(d3.values(nodes)).links(rels).size([width, height]).linkDistance(60).charge(-300).on("tick", tick).start();
console.log('graph goes up only now');
force.on("tick", function(e) {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
});
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.name; });
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("r", 6)
.call(force.drag);
circle.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
And the python code that "serves up" this data goes like this ...
#app.route("/getA")
def getA():
print('get A gets called ...')
db = get_db()
nodes = []
ailment = []
cure = []
jlinks = {"links":[]}
uniqueNodes = {"nodes":[]}
sql = "MATCH (a:Ailment) -[:SOLVED_BY]->(theCURE) return a as ailment, theCURE limit 5"
with db as graphDB_Session:
nodes = graphDB_Session.run(sql)
print("output:")
for node in nodes:
#print(node)
prepare_links(node, ailment, cure)
counter = 0
for idx, value in enumerate(ailment):
source = ailment[idx]['title']
target = cure[idx]['title']
y = {"source":str(source), "target":str(target), "type":"SOLVED_BY"}
jlinks["links"].append(y)
if source not in uniqueNodes["nodes"]:
uniqueNodes["nodes"].append(source)
if target not in uniqueNodes["nodes"]:
uniqueNodes["nodes"].append(target)
print(jlinks)
print(uniqueNodes)
return Response(dumps({"nodes": uniqueNodes, "links": jlinks}),mimetype="application/json")
Technicalily i haven't yet used up the unique nodes coming from the python function but that's for another day :) thanks for reading.
I am using CodeFlower built upon D3.js. I want to show an image as a background instead of arbitrary colors, and i successfully did that using SVG Patterns.
DEMO FIDDLE
// Enter any new nodes
this.node.enter().append('defs')
.append('pattern')
.attr('id', function(d) { return (d.id+"-icon-img");}) // just create a unique id (id comes from the json)
.attr('patternUnits', 'userSpaceOnUse')
.attr('width', 80)
.attr('height', 80)
.append("svg:image")
.attr("xlink:xlink:href", function(d) { return (d.icon);}) // "icon" is my image url. It comes from json too. The double xlink:xlink is a necessary hack (first "xlink:" is lost...).
.attr("x", 0)
.attr("y", 0)
.attr("height", 80)
.attr("width", 80)
.attr("preserveAspectRatio", "xMinYMin slice");
this.node.enter().append('svg:circle')
.attr("class", "node CodeFlowerNode")
.classed('directory', function(d) { return (d._children || d.children) ? 1 : 0; })
.attr("r", function(d) { return d.children ? 3.5 : Math.pow(d.size, 2/5) || 1; })
.style("fill", function(d) { return ("url(#"+d.id+"-icon-img)");})
/* .style("fill", function color(d) {
return "hsl(" + parseInt(360 / total * d.id, 10) + ",90%,70%)";
})*/
.call(this.force.drag)
.on("click", this.click.bind(this))
.on("mouseover", this.mouseover.bind(this))
.on("mouseout", this.mouseout.bind(this));
The problem i am seeing is the image is not centrally aligned in the circle it is kind of tile layout composed of 4 images.
How can i make its position center and covering the circle nicely.
DEMO FIDDLE
You need to change the way you define your pattern. You should define it with respect to the element it is being applied to. So leave patternUnits at the default of objectBoundingBox, and set the width and height to 1.
Then you need to also set the patternContentUnits to objectBoundingBox also, and give the <image> the same size (width and height of 1).
this.node.enter().append('defs')
.append('pattern')
.attr('id', function(d) { return (d.id+"-icon");})
.attr('width', 1)
.attr('height', 1)
.attr('patternContentUnits', 'objectBoundingBox')
.append("svg:image")
.attr("xlink:xlink:href", function(d) { return (d.icon);})
.attr("height", 1)
.attr("width", 1)
.attr("preserveAspectRatio", "xMinYMin slice");
Demo fiddle here
I have created a SVG element with some nodes:
gnodes = svg.selectAll("g.node")
.data(_nodes);
var newNodes = gnodes.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.call(drag)
.on('mouseover', onMouseOver)
.on('mouseout', onMouseOut);
newNodes.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", radius);
newNodes.append("image")
.attr("xlink:href", getImage)
.attr("x", -radius/2)
.attr("y", -radius/2)
.attr("width", radius + "px")
.attr("height", radius + "px");
In the onMouseOver I want to change the colour of highlighted circle, but I can not get this item from the data I receive:
function onMouseOver(d, i) {
var c1 = d.select("circle"); // error
var c2 = i.select("circle"); // error
var c3 = d.selectAll("circle"); // error
var c4 = i.selectAll("circle"); // error
}
What is a way to get child node with d3?
d is the data object and i the index. Both are not d3 instances that provide access to any of the d3 select functions.
Try this:
myelement.on('mouseenter', function(d,i) {
d3.select(this).select('circle');
});
i have donut chart with legend specification. I have 2 values in dataset. But here with this code i'm getting only the first value, "Unresolved".
var dataset = {
Unresolved: [3],
Resolved:[7]
};
var keyValue=[];
for(key in dataset){
keyValue.push(key);
}
var width = 260,
height = 300,
radius = Math.min(width, height) / 2;
var color = ["#9F134C", "#ccc"];
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 90)
.outerRadius(radius - 80);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var gs = svg.selectAll("g").data(d3.values(dataset)).enter().append("g");
var path = gs.selectAll("path")
.data(function(d,i) { return pie(d); })
.enter().append("path")
.attr("fill", function(d, i) { console.log("log", keyValue[i]);return color[i]; }) //Here i'm getting only the 1st value "unresolved".
.attr("d", arc);
var legendCircle = d3.select("body").append("svg").selectAll("g").data(keyValue).enter().append("g")
.attr("class","legend")
.attr("width", radius)
.attr("height", radius * 2)
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legendCircle.append("rect")
.attr("width", 18)
.attr("height", 10)
.style("fill", function(d, i) { return color[i];});
legendCircle.append("text")
.attr("x", 24)
.attr("y", 5)
.attr("dy", ".35em")
.text(function(d) { return d; });
The output i'm getting is,
Can anyone help on this? Thanks.
It looks like you're doing a nested selection in your code, which you would usually only need for nested data. Your data is not nested however -- there's a single level with 2 values. What's happening is that, by using a nested selection, you're descending into the value arrays, each of which contains only a single value.
It works fine if you do away with the nested selection and pass your original data to the pie layout.
var gs = svg.selectAll("g").data(pie(d3.values(dataset))).enter().append("g");
var path = gs.append("path")
.attr("fill", function(d, i) { return color[i]; })
.attr("d", arc);
Complete example here.
While trying to understand d3 I saw the line .text(String);. I could not understand what String is suppose to be. I thought maybe its an empty string (nope), a method (i didnt see that in the api reference) and pondered what else it could be.
I commented it out below and got expected results. What I don't understand is what is String and why does it work. With this line my 3 squared boxes has text (its a internal value of the data it will represent later) while commented out it does not.
Demo
Html
<div class='chart' id='chart-10'/>
<script src="http://d3js.org/d3.v3.min.js"></script>
JS:
var w = 360;
var h = 180;
var svg = d3.select("#chart-10").append("svg")
.attr("width", w)
.attr("height", h);
var g = svg.selectAll(".data")
.data([50,150,250])
.enter().append("g")
.attr("class", "data")
.attr("transform", function(d, i) { return "translate(" + 20 * (i + 1) + ",20)"; });
g.append("circle")
.attr("class", "little")
.attr("r", 1e-6);
g.append("rect")
.attr("x", -10)
.attr("y", -10)
.attr("width", 20)
.attr("height", 20)
.style("fill", "lightgreen")
.style("stroke", "green");
g.append("text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
;// .text(String);
g.attr("transform", function(d, i) { return "translate(" + 20 * (i + 1) + ",20)"; });
g.select("rect").style("opacity", 1);
g.select("circle").attr("r", 1e-6);
var t = g.transition().duration(750);
t.attr("transform", function(d, i) { return "translate(" + d + ",90)"; });
t.select("circle").attr("r", Math.sqrt);
t.select("rect").style("opacity", 1e-6);
It looks like the String constructor. According to d3 documentation, as pointed out by Matt:
if value is a function, then the function is evaluated for each selected element (in order), being passed the current datum d and the current index i, with the this context as the current DOM element. The function's return value is then used to set each element's text content.
So, you set g.data to [50,150,250] a few lines before. Each number is converted to a String object by the String constructor, returned and used as the text values of your DOM nodes.