Append new shapes to existing shapes within SVG document D3 - d3.js

In D3 I'm used to creating visuals from a blank div. However I'm trying to get my head around the following.
I have an svg document (https://raw.githubusercontent.com/gangrel11/samplefiles/main/d3%20task1.svg) which is just 3 rectangles.
What I'm trying to do is append 3 new shapes (circles) to each one of the existing rectangles so that they appear in the centre of each one.
This is where I got to:
const svgUrl = "https://raw.githubusercontent.com/gangrel11/samplefiles/main/d3%20task1.svg"
d3.xml(svgUrl).then(render);
function render(svg) {
// add svg
d3.select("body").node().append(svg.documentElement)
var svg = d3.select("body").append("svg");
svg.selectAll("rect")
.append("rect")
.attr("width", 20)
.attr("height", 20)
.attr("x", 20)
.attr("y", 10)
.attr("fill", "red")
}
Here is my jsfiddle

You have a few problems:
You are adding another svg element instead of using the existing one.
My suggestion is changing:
d3.select("body").append("svg");
to
d3.select("body").select("svg #layer1");
notice that I also targeted the g element #layer1 that get transformed.
you try to append rect element to rect element but svg doesn't know how to draw rect inside rect - this syntax is invalid.
instead, you can target each element and use his position using the .each method and append them after all the existing rects.
code:
function render(svg) {
// add svg
d3.select("body").node().append(svg.documentElement)
var svg = d3.select("body").select("svg #layer1");
svg.selectAll("rect")
.each(function (rect){
const {x, y} = this.getBoundingClientRect();
svg.append("rect")
.attr("width", 20)
.attr("height", 20)
.attr("x", this.getAttribute('x'))
.attr("y", this.getAttribute('y'))
.attr("fill", "red");
});
}

Related

tspan is always hidden on FF

I have a d3 code jsfiddle. Upper text here is visible in all browser except FF.
Here is the complete code:
var data = [2000, 400];
var chart = d3.select("#container").append("svg")
.attr("class", "chart")
.attr("width", 800) // bar has a fixed width
.attr("height", 300)
.style("padding-top", "20px")
var x = d3.scale.linear()
.domain([0, d3.max(data)])
.range([10, 100]);
chart.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("y", 18)
.attr("x", 10)
.attr("width", function(d){
return x(d) - 10
})
.attr("height", 10)
var text = [0];
var data = ['Upper text here']
chart.selectAll("text")
.data(text)
.enter()
.append("text")
.attr("x",x)
.attr("dy", ".25em")
.append('tspan')
.text('Upper text here')
.attr('x', x)
.attr("y", function(_, i) {
return i === 1 ? 42 : -9;
})
.attr('dy', '.35em')
Not sure, whats the mistake here.
In SVG 1.1 the overflow property on <svg> elements defaults to hidden.
In SVG 2 it is proposed that it default to visible.
It seems like Chrome has implemented this proposed SVG 2 change but Firefox probably won't.
You really should draw inside the SVG canvas and not depend on overflow visible as it has a pretty big performance hit since the browser can no longer assume that the maximum SVG canvas bounds are the outer SVG element's height and width.
If you really must draw outside the bounds then simply set overflow explicitly to get consistent cross-browser behaviour e.g.
var chart = d3.select("#container").append("svg")
.attr("class", "chart")
.attr("width", 800) // bar has a fixed width
.attr("height", 300)
.attr("overflow", "visible")
.style("padding-top", "20px")

How to change Horizontal bar chart to vertical bar chart in D3 v4

I have a Horizontal bar chart in D3, but I'd like to have a vertical bar chart instead of a horizontal. Here's what it looks like now. I tried fiddling with the X and Y axes and can't seem to get it right. Any help would be greatly appreciated.
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<!DOCTYPE html>
<html>
<head>
<title>Layered Bar Chart</title>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<script>
var dataArray = [10, 20, 40, 60];
var canvas = d3.select("body")
.append("svg")
.attr("width", 500).attr("height", 500);
var bars = canvas.selectAll("rect")
.data(dataArray)
.enter()
.append("rect")
.attr('class', 'base')
.attr("width", function(d){
return d * 10;
})
.attr("height", 30)
.attr("y", function(d, i){
return i * 35;
})
.attr("fill", "blue");
//.attr("fill-width", "green");
/*
canvas.append("text")
.text(function(d){
return d;
}).attr;
*/
var dataArray1 = [5, 10, 20, 30];
var bars1 = canvas.selectAll("rect.addon")
.data(dataArray1)
.enter()
.append("rect")
.attr('class', 'addon')
.attr("width", function(d){
return d * 7;
})
.attr("height", 20)
.attr("y", function(d, i){
return (i*35) + 5;
})
.attr("fill", "red");
//.attr("fill-width", "black");
</script>
</body>
</html>
For this type of conversion you need to change more than x and y.
Using your example code, the chart's shape will be dictated by the four properties of each rectangle: x, y, width, and height. Afterall, (generally) a vertical bar chart does not have the same height for each bar, while a horizontal bar chart does.
Secondly, most vertical bar charts have the rects anchored on the bottom while most horizontal bar charts have the rects anchored to the left. In svg coordinate space the top of the svg is y = 0, while the left of the svg is x = 0. Consequently, simply swapping x and y , width and height in your example will result in a bar chart that has bars reaching down from the top of the chart.
To solve this, you need to modify the y attribute of each rectangle. As the y attribute sets the top of the rectangle, you need to figure out the top of each rectangle. For your code:
.attr("y", function(d,i) { return heightOfSVG - heightOfRectangle; }) // bottom of rectangle - rectangle height = rectangle top
This line is new as you didn't set the x attribute in your original code, instead using the default zero. This won't do in the vertical graph, as the top of the rectangle can't be at y = 0 (in most cases).
The use of scales of course will complicate this, as will labelled axes, or multiple-series bar charts etc. I've used your code, as it was provided. I've stripped your example to one chart as opposed to two to show what those changes might look like applied to a minimal example of a scale-less graph.
var canvas = d3.select("body")
.append("svg")
.attr("width", 500).attr("height", 500);
var bars = canvas.selectAll("rect")
.data([10, 20, 40, 60])
.enter()
.append("rect")
.attr("height", function(d){
return d * 10;
})
.attr("width", 30)
.attr("x", function(d, i){
return i * 35;
})
.attr("y", function(d,i) { return 500 - d * 10; })
.attr("fill", "blue");
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>

d3.js trying to call data method twice on same selector produces strange results

I'm new to D3 and am trying to build a table like structure out of rectangles. I would like the header to be a different color than the rest of the rectangles. I've written the following code:
table = svgContainer.selectAll('rect')
.data([managedObj])
.enter()
.append('rect')
.attr("width", 120)
.attr("height", 20)
.attr("fill", "blue")
.text(function(d) {
return d.name;
});
// create table body
table.selectAll('rect')
.data(managedObj.data)
.enter()
.append('rect')
.attr("y", function() {
shift += 20;
return shift;
})
.attr("width", 120)
.attr("height", 20)
.attr("fill", "red")
.text(function(d) {
return d.name;
});
This is producing the following results:
This is almost what I intended except it is nesting the second group of rectangles inside the first rectangle. This causes only the first blue rectangle to be visible. I'm assuming this has something to do with calling the data method twice. How can I fix this issue?
I think I understand the intended result, so I'll give it a go:
This line :
table.selectAll('rect')
is selecting the rectangle just created here:
table = svgContainer.selectAll('rect')....append('rect')....
You don't want to append rectangles to that rectangle (or any rectangle for that matter) because this won't work, but you do want to append them to the SVG itself.
So instead of table.selectAll you should be using svgContainer.selectAll, but there are two other issues:
if you use svgContainer.selectAll('rect') you will be selecting the rect you have already appended, when you actually want an empty selection. See the answer here.
you cannot place text in a rect (See answer here), instead you could append g elements and then append text and rect elements to those. And, for ease of positioning, you could translate the g elements so that positioning the rectangles and text is more straight forward.
So, your code could look like:
var data = ["test1","test2","test3","test4"];
var svgContainer = d3.select('body').append('svg').attr('width',900).attr('height',400);
var header = svgContainer.selectAll('g')
.data([data])
.enter()
.append('g')
.attr('transform','translate(0,0)');
header.append('rect')
.attr("width", 120)
.attr("height", 20)
.attr("fill", "blue");
header.append('text')
.attr('y',15)
.attr('x',5)
.text(function(d) {
return "header";
});
// create table body
var boxes = svgContainer.selectAll('.box')
.data(data)
.enter()
.append('g')
.attr('class','box')
.attr('transform',function(d,i) { return 'translate(0,'+((i+1)*20)+')'; });
boxes.append('rect').attr("width", 120)
.attr("height", 20)
.attr("fill", "red");
boxes.append('text')
.attr('y',15)
.attr('x',5)
.text(function(d) {
return d;
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

d3.js adding polygon inside rectangle

How to add polygon inside rectangle?, below is the code i have but it is not showing polygon inside rectangle. could you please help me.
var svgContainer = d3.select("body").append("svg")
var rectangle = svgContainer.append("rect")
.style("stroke", "black")
.style("fill", "none")
.attr("x", 50)
.attr("y", 50)
.attr("width", 100)
.attr("height", 100);
var cir = rectangle.append("polygon") // attach a polygon
.style("stroke", "black") // colour the line
.style("fill", "none") // remove any fill colour
.attr("points", "30,50,100,150,100,150"); // x,y points
You are making the polygon with in the rect DOM which is incorrect
You should be attaching the polygon to the svg
So it should be
svgContainer.append("polygon")
corrected code below:
var svgContainer = d3.select("body").append("svg")
var rectangle = svgContainer.append("rect")
.style("stroke", "black")
.style("fill", "none")
.attr("x", 50)
.attr("y", 50)
.attr("width", 100)
.attr("height", 100);
var cir = svgContainer.append("polygon") // attach a polygon to the svg
.style("stroke", "black") // colour the line
.style("fill", "none") // remove any fill colour
.attr("points", "30,50,100,150,100,150"); // x,y points
Working fiddle here
To make the polygon appear within the rectangle you will need to provide the polygon points/coordinates accordingly.
Just by making the polygon within the rect DOM element will not make it show withing the rectangle.
Hope this clears your concern.

D3.js: circle color change on title mouseover doesn't work

I'm appending some text to D3.js circles and want the circles to change color mouseover, also on mouseover on the text.
Currently the circles do change color on mouseover, but when hovering over the text, the circle mouseover doesn't work anymore (logical: I'm hovering over the text). How do I get the circles to also change color when hovering over the text?
My code (gnode is a earlier defined circle):
var label = gnode.append("text")
.text(function(d) { return d.key ; })
.attr("font-size", function(d) {return 12 + d.value[0]/4})
.attr("text-anchor", "middle")
.call(force.drag)
.on("mouseover", function(d){
this.style.cursor='pointer';
d3.select( "#" + d.key.toLowerCase().replace(/ /g, '_'))
.attr("id", "none")
.classed("mouse_over",true)
.classed("mouse_out",false);
thanks
You can achieve this by simply putting all the elements belonging together inside a group. Then attach the mouse events to the group instead of the elements themselves.
First create svg element and append data:
var svg = d3.select("#main")
.append("svg")
.attr("id", "svgElement")
.attr("width", width)
.attr("height", height);
var svgg = svg.selectAll("g.myGroup")
.data(myData)
.enter()
.append("g");
Then add your elements via the each function:
svgg.each(function (d, i) {
selection = d3.select(this);
// ... append to this selection
});
Now attach mouse events to the group:
svgg.on("mouseover", function(d) {
d3.select(this) // Select and do something
});
You can find the working fiddle here:
http://jsfiddle.net/77XLD/1/
Note that the event fires when either moving over the line of the circle and also when hovering over the text.

Resources