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>
Related
I've got a legend, with colored rectangles...
I'd like to replace the rectangles with symbols (i.e., circle, cross, diamond, square). I can't figure out how to do that.
I've been using variations of .attr("d", d3.svg.symbol().type('circle'). For instance, I tried:
legendRect
.attr("d", d3.svg.symbol().type(function (d) { return d[2] })
and I tried:
legendRect.append("svg:path")
.attr("d", d3.svg.symbol().type((d: any) => { return d[2] }))
d[2] is "supposed to be" pulling from legendData, as shown in the below code example...like it does with d[1] for the fill.
But I don't ever see anything change.
Here's the code I'm using for the legend, without the symbol stuff, below. What am I doing wrong and how can I change the rectangles to symbols? Where do I need to add what?
var legendData = [["OA", "yellow", "circle"], ["OI", "blue", "cross"], ["RARC", "green", "diamond"], ["CAPE", "red", "square"], ["Other", "black", "triangleDown"]];
var legend = this.svg.append("g")
.attr("class", "legend")
.attr("height", 0)
.attr("width", 0)
.attr('transform', 'translate(-20,250)');
var legendRect = legend.selectAll('rect').data(legendData);
legendRect.enter()
.append("rect")
.attr("x", width - 65)
.attr("width", 10)
.attr("height", 10)
;
legendRect
.attr("y", function (d, i) {
return i * 20;
})
.style("fill", function (d) {
return d[1];
})
var legendText = legend.selectAll('text').data(legendData);
legendText.enter()
.append("text")
.attr("x", width - 52);
legendText
.attr("y", function (d, i) {
return i * 20 + 9;
})
.text(function (d) {
return d[0];
});
Here's how I would code it. Notice, that I data-bind to a wrapper g element and then place the symbol and text into it for each legend item. You can then position the g instead of positioning the text and "symbol" separately. This also removes the need for double-binding the data.
var legendData = [["OA", "yellow", "circle"], ["OI", "blue", "cross"], ["RARC", "green", "diamond"], ["CAPE", "red", "square"], ["Other", "black", "triangleDown"]];
var svg = d3.select('body').append('svg').attr('width', 500).attr('height', 500);
var legend = svg.append('g')
.attr("class", "legend")
.attr("height", 0)
.attr("width", 0)
.attr('transform', 'translate(20,20)');
var legendRect = legend
.selectAll('g')
.data(legendData);
var legendRectE = legendRect.enter()
.append("g")
.attr("transform", function(d,i){
return 'translate(0, ' + (i * 20) + ')';
});
legendRectE
.append('path')
.attr("d", d3.svg.symbol().type((d) => { return d[2] }))
.style("fill", function (d) {
return d[1];
});
legendRectE
.append("text")
.attr("x", 10)
.attr("y", 5)
.text(function (d) {
return d[0];
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
This is a implementation which uses symbols for your legend. You can use the symbols like the following:
svg.selectAll('.symbol')
.data(legendData)
.enter()
.append('path')
.attr('transform', function(d, i) {
return 'translate(' + (20) + ',' + ((i * 20) + 10) + ')';
})
.attr('d', d3.symbol().type(function(d, i) {
if (d[2] === "circle") {
return d3.symbolCircle;
} else if (d[2] === "cross") {
return d3.symbolCross;
} else if (d[2] === "diamond") {
return d3.symbolDiamond;
} else if (d[2] === "square") {
return d3.symbolSquare;
} else {
return d3.symbolTriangle;
}
})
.size(100))
.style("fill", function(d) {
return d[1];
});
Then you can set your legend labels like the following:
svg.selectAll('.label')
.data(legendData)
.enter()
.append('text')
.attr("x", "40")
.attr("y", function(d, i){ return ((i * 20)+15);})
.text(function(d) {
return d[0];
});
Check fiddle here - https://jsfiddle.net/zoxckLe3/
P.S. - Above solution uses d3 v4. To achieve the same in v3, use the following line .attr('d', d3.svg.symbol().type(function(d){return d[2];})) instead of the part where I match d[2] to the symbol name.
For adding image icons, you can use below code.
legend.append("**image**")
.attr("x", 890)
.attr("y", 70)
.attr("width", 20)
.attr("height", 18)
.attr("xlink:href",function (d) {
**return "../assets/images/dev/"+d+".png";**
})
This works for me..
I created d3 pack layout.Text is added in nodes. Requirement is text should be fit in circular nodes in such way that Text should be at bottom, proportional to node size and should be fit inside node.but text is either overlapping or hides behind another node or not proportional to nodes as in JSFiddle examples
http://jsfiddle.net/oo66o0q0/11/,
http://jsfiddle.net/oo66o0q0/12/,
http://jsfiddle.net/oo66o0q0/13/
.So how to resolve all issue with one code?
function treeLayout(nodes){
var node = diagramLayout.selectAll(".node");
node = node.data(nodes.descendants(), function(d) {
return d.uid;
});
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("height", nodeHeight)
.attr("width", nodeWidth)
nodeEnter.append("circle")
.attr("id", function(d) { return "node-" + d.data.name; })
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return color(d.depth); });
nodeEnter.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
var nodeText = nodeEnter.append("text")
.text(function(d) { return d.data.name; })
.attr("clip-path", function(d) { return "url(#clip-" + d.data.name + ")"; })
.style("text-anchor", "middle")
.style("stroke", "#000")
.style("stroke-width", "0.1px")
.attr("y", function(d) {
return d.r-(d.r/d.data.name.length);
})
.style("font-family", "sans-serif")
.style("font-size", function(d) {
return d.r/(d.data.name.length)+ "px";
})
}
}
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; });
Fiddle: http://jsfiddle.net/jzaun/SCb7T/
Code:
var width = 500,
height = 500;
var dotSize = 50;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-10020)
.linkDistance(dotSize)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var graph = {
"nodes":[
{"name":"Name","group":1},
{"name":"Name","group":2},
{"name":"Name","group":3},
{"name":"Name","group":4}
],
"links":[
{"source":1,"target":0,"value":1},
{"source":2,"target":0,"value":1},
{"source":3,"target":0,"value":1}
]
};
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", dotSize)
.style("fill", function(d) { return color(d.group); });
node.append("image")
.attr("xlink:href", "http://lorempixel.com/64/64/cats")
.attr("x", -32)
.attr("y", -32)
.attr("width", 64)
.attr("height", 64);
node.append("title")
.text(function(d) { return d.name; });
force.on("tick", function() {
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; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
You can't append image to circle elements -- the SVG spec doesn't allow this. Instead, append the images directly and set transform on them in the tick function instead of cx/cy.
Complete example here.
I'm relatively new to D3 and trying to add labels to a bar chart.. I keep running into the problem of labels applying all values to each label. Precedding this code is normal data load etc.
// Controls Bar Layout and Offset (x(d.weekOf)+20)
var property = svg.selectAll(".property")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + (x(d.weekOf)+20) + ",0)"; });
// Theese are the bars, width is the width of the bars
property.selectAll("rect")
.data(function(d) { return d.emissions; })
.enter().append("rect")
.attr("width", "80")
.attr("y", function(d) { return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.attr("opacity", "0.7")
.style("fill", function(d) { return color(d.name); });
// Add Capacity Labels
property.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d, i) {return d.Internal; })
.attr("x", 41)
.attr("y", 210)
.attr("text-anchor", "middle");
Clearly I'm missing something simple..?
Your labels are a subselection of the property selection, so instead of using data() to join each property label to the entire dataset as you are, you should instead use data to join to the corresponding parent datum, like so:
property.selectAll("text")
.data(function(d) {return [d];})
.enter()
.append("text")
.text(function(d, i) {return d.Internal; })
.attr("x", 41)
.attr("y", 210)
.attr("text-anchor", "middle");