D3JS different image for each circle/node - javascript

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)";
})

Related

Displaying text after an onclick event

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.

Uncaught TypeError: d3.pack(...).sort is not a function

I am new to Javascript and D3.js and I am currently trying to build a bubble chart in d3.js using an example provided here https://jrue.github.io/coding/2014/exercises/basicbubblepackchart/ and modifying it according to my assignment. I know that the example above was written in a previous version of d3, and I am using version v4 where syntax has slightly changed,however I am getting a following error when running a program:
Uncaught TypeError: d3.pack(...).sort is not a function
var diameter = 500, //max size of the bubbles
color = d3.scaleOrdinal(d3.schemeCategory10); //color category
var bubble = d3.pack()
.sort()
.size([diameter, diameter])
.padding(1.5);
var svg = d3.select("body")
.append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");
d3.csv("teams.csv", function(error, data){
data = data.map(function(d){ d.value = +d["Amount"]; return d; });
var nodes = bubble.nodes({children:data}).filter(function(d) { return !d.children; });
//setup the chart
var bubbles = svg.append("g")
.attr("transform", "translate(0,0)")
.selectAll(".bubble")
.data(nodes)
.enter();
bubbles.append("circle")
.attr("r", function(d){ return d.r; })
.attr("cx", function(d){ return d.x; })
.attr("cy", function(d){ return d.y; })
.style("fill", function(d) { return color(d.value); });
bubbles.append("text")
.attr("x", function(d){ return d.x; })
.attr("y", function(d){ return d.y + 5; })
.attr("text-anchor", "middle")
.text(function(d){ return d["Team"]; })
.style({
"fill":"black",
"font-family":"Helvetica Neue, Helvetica, Arial, san-serif",
"font-size": "12px"
});
What is the problem here?
Be sure to use the same version of d3 as the author in the example (it looks like you're good by the syntax). Also, it's 'd3.layout.pack().sort(null)'
:)

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/

Adding svg rectangles to checkboxes dynamically

I'm trying to add a coloured rectangle on the left of each checkbox of a list, which are created automatically by d3.js with this code:
d3.selectAll("#checkbox")
.selectAll("label")
.data(dataset, function (d) { return d.Grazie; })
.enter()
.append("div")
.attr("id", function (d) { return d.Grazie; })
.append("label")
.attr("class", "label")
.attr("id", function (d) { return d.Grazie; })
.text(function (d) { return d.Grazie; })
.append("input")
.attr({
type: "checkbox",
class: "Grazie",
name: "mode",
value: function (d, i) { return d.Grazie; }
})
.attr("id", function (d, i) { return d.Grazie; })
.attr("x", margin)
.attr("y", margin)
.attr("selected", null);
I tried appending an svg and then a rectangle after this process, but what I get is that the rectangle is placed after the text and the checkbox, with this code:
d3.select("#checkbox")
.select("#Sans")
.append("svg")
.attr("width", 8 + "px")
.attr("height", 8 + "px")
.append("rect")
.attr("fill", function (d) {
if (d.Grazie == "Sans") { return "#386cb0"; }
else { return "#f0027f"}})
.attr("x", 0)
.attr("y", 0)
.attr("width", 8 + "px")
.attr("height", 8 + "px");
The order I'd like to have is:
Rectangle - text - checkbox
I guess that the code for the rectangle could be integrated while creating the checkbox, but wasn't able to make it work. Any help is really appreciated, thanks!
You should break things up like this:
var entries = d3.select("#checkbox") // select is more appropriate than selectAll
.selectAll("div.list-entry")
.data(dataset, function (d) { return d.Grazie; })
var newEntries = entries.enter() // newEntries holds the result of .enter()
.append("div")
.attr("id", function (d) { return d.Grazie; })
.attr("class", "list-entry")
newEntries // now you can append various stuff into each div in newEntries
.append("label")
.attr("class", "label")
.attr("id", function (d) { return d.Grazie; })
.text(function (d) { return d.Grazie; })
newEntries
.append("input")
.attr({
type: "checkbox",
class: "Grazie",
name: "mode",
value: function (d, i) { return d.Grazie; }
})
.attr("id", function (d, i) { return d.Grazie; })
.attr("x", margin)
.attr("y", margin)
.attr("selected", null);
Then you can insert the rectangle creation code right before the label creation code (following the same pattern used for label and input creation).
But... is there a reason why you're using and <svg> and a <rect> to make a simple rectangle? That's complicated. Why not just use a plain old <div> with css width, height, background-color and display:inline-block or float:left?

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