I have code to make a circle and I'd like to place text on top of it.
I'm using this for my example: https://bl.ocks.org/mbostock/raw/7341714/
infoHeight = 200
infoWidth = 200
var compareSVG = d3.select(".info-container")
.append("svg")
.attr("class","comparison-svg")
.attr("width", infoWidth)
.attr("height", infoHeight);
var circle = compareSVG.append("g")
circle.append("circle")
.attr("r", circleRadius(d.properties.contextvalue))
.attr("cy", infoHeight/2)
.attr("cx", infoWidth/2)
.style("fill","grey")
.style("stroke","black")
.style("stroke-width","3px")
circle.append("text")
.text(d.properties.contextvalue)
.style("display", "block")
.style("y", infoHeight/2)
.style("x", infoHeight/2)
.style("color","red")
.style("font-size","20px")
The circle works, but the text won't appear on top of it. Instead, it is in the top left corner of the SVG element. I've tried position: absolute along with top and left and it stays in the same corner.
In D3, the attr methods uses Element.setAttribute internally, while style uses CSSStyleDeclaration.setProperty().
In an SVG <text> element, x and y are attributes. Therefore, change those style() methods for attr(). Also, get rid of that .style("display", "block").
So, it should be:
circle.append("text")
.text(d.properties.contextvalue)
.attr("y", infoHeight/2)
.attr("x", infoHeight/2)
.style("color","red")
.style("font-size","20px")
Here is your code with that change:
infoHeight = 200
infoWidth = 200
var compareSVG = d3.select("body")
.append("svg")
.attr("width", infoWidth)
.attr("height", infoHeight);
var circle = compareSVG.append("g")
circle.append("circle")
.attr("r", 50)
.attr("cy", infoHeight / 2)
.attr("cx", infoWidth / 2)
.style("fill", "lightgrey")
.style("stroke", "black")
.style("stroke-width", "3px")
circle.append("text")
.text("Foo Bar Baz")
.attr("y", infoHeight / 2)
.attr("x", infoHeight / 2)
.style("color", "red")
.style("font-size", "20px")
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Finally, pay attention to the position of the text: it's not entered (regarding the circle). If you want to center it, use text-anchor and dominant-baseline:
infoHeight = 200
infoWidth = 200
var compareSVG = d3.select("body")
.append("svg")
.attr("width", infoWidth)
.attr("height", infoHeight);
var circle = compareSVG.append("g")
circle.append("circle")
.attr("r", 50)
.attr("cy", infoHeight / 2)
.attr("cx", infoWidth / 2)
.style("fill", "lightgrey")
.style("stroke", "black")
.style("stroke-width", "3px")
circle.append("text")
.text("Foo Bar Baz")
.attr("y", infoHeight / 2)
.attr("x", infoHeight / 2)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "central")
.style("color", "red")
.style("font-size", "20px")
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Related
How could I scale a rectangle in svg to fit the text using d3.js.
When you run it for the first time the yellowgreen rect covers the text nicely, but if you will resize the screen the text size and position will be changed, while the rectangle stays the same.
Below is the code, here is the fiddle:
debugger;
var svg = d3.select("#drawRegion")
.append("svg")
.attr("width", "100%")
.attr("height", "100%");
svg.append("rect")
.attr("x", "0")
.attr("y", "0")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "yellow");
const rectAroundText = svg
.append("rect");
const textMiddleX = 50;
const textMiddleY = 50;
const testText = svg
.append("text");
testText
.attr("x", textMiddleX + "%")
.attr("y", textMiddleY + "50%")
.attr("text-anchor", "middle")
.attr("alignment-baseline", "central")
.attr("x", "50%")
.attr("y", "50%")
.attr("fill", "#000")
.classed("scalingText", true)
.text("svdfv");
const textBox = testText.node().getBBox();
rectAroundText
.attr("x", textBox.x)
.attr("y", textBox.y)
.attr("width", textBox.width)
.attr("height", textBox.height)
.attr("fill", "yellowgreen");
<div id="drawRegion">
</div>
<script src="https://d3js.org/d3.v5.min.js"></script>
I would like the rectangle to scale up and move along with the text. Is it possible?
I have a D3 v4 force simulation with several nodes. Each node has a group. When I mouse over one of the elements of that group(an invisible circle) I want one of the other elements (the red circle on that specific node only which I gave an id of "backcircle") to do something. Currently this is what I have, but it does it to all nodes not just the one I'm hovering over's element.
this.node = this.d3Graph.selectAll(null)
.data(this.props.nodes)
.enter()
.append("g")
.attr("class", "nodes");
this.node.append("circle")
.attr("id", "backCircle")
.attr("r", 60)
.attr("fill", "red")
this.node.append("svg:image")
.attr("xlink:href", function(d) { return d.img })
.attr("height", 60)
.attr("width", 60)
.attr("x", -30)
.attr("y", -30)
this.node.append("circle")
.attr("r", 60)
.attr("fill", "transparent")
.on( 'mouseenter', function(d) {
d.r = 65;
this.node.select("#backCircle")
.transition()
.attr("r", 80);
}.bind(this))
Before anything else, two important tips:
Do not use "transparent" in an SVG.
IDs are unique. So, use classes instead (or select by the tag name)
Back to your question:
There are several ways of selecting the circle element based on a sibling circle element. The first one is going up the DOM and down again, using this.parentNode. The second one, if you know exactly the sequence of the siblings, is using previousSibling.
In the following demos, I have 3 elements per group: a circle, a text and a rectangle. Hovering over the rectangle will select the circle.
First, the option with this.parentNode. in your case:
d3.select(this.parentNode).select(".backCircle")
Hover over the squares:
var svg = d3.select("svg");
var data = [50, 150, 250];
var g = svg.selectAll(null)
.data(data)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(" + d + ",75)"
});
g.append("circle")
.attr("class", "backCircle")
.attr("r", 40)
.attr("fill", "teal")
g.append("text")
.attr("font-size", 20)
.attr("text-anchor", "middle")
.text("FOO");
g.append("rect")
.attr("x", 20)
.attr("y", 20)
.attr("width", 20)
.attr("height", 20)
.style("fill", "firebrick")
.on("mouseenter", function() {
d3.select(this.parentNode).select(".backCircle")
.transition()
.attr("r", 50)
}).on("mouseleave", function() {
d3.select(this.parentNode).select(".backCircle")
.transition()
.attr("r", 40)
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Then, the option with previousSibling (here, you don't even need to set a class). In your case:
d3.select(this.previousSibling.previousSibling)
Hover over the squares:
var svg = d3.select("svg");
var data = [50, 150, 250];
var g = svg.selectAll(null)
.data(data)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(" + d + ",75)"
});
g.append("circle")
.attr("r", 40)
.attr("fill", "teal")
g.append("text")
.attr("font-size", 20)
.attr("text-anchor", "middle")
.text("FOO");
g.append("rect")
.attr("x", 20)
.attr("y", 20)
.attr("width", 20)
.attr("height", 20)
.style("fill", "firebrick")
.on("mouseenter", function() {
d3.select(this.previousSibling.previousSibling)
.transition()
.attr("r", 50)
}).on("mouseleave", function() {
d3.select(this.previousSibling.previousSibling)
.transition()
.attr("r", 40)
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
PS: Have in mind that, since I'm not using an object, there is no need for bind(this) in my snippets.
I think you need to select the node that is firing the mouseenter event from within its handler.
this.node.append("circle")
.attr("r", 60)
.attr("fill", "transparent")
.on( 'mouseenter', function(d) {
var mouseenterNode = d3.select(this)
mouseenterNode.attr("r", 65);
mouseenterNode.select("#backCircle")
.transition()
.attr("r", 80);
}.bind(this))
In the following example, is there a way to for the zoomArea to detect a mouse wheel event that happens while pointing on one of the grey circles? The aim is to not interrupt the zoom behaviour when doing so. The circles should still be able to receive pointer events in order to e.g. display tooltips.
var dataset = [0, 2345786000, 10000000000];
var svg = d3.select("body").append("svg");
var w = 500, h = 200;
var padding = 50;
svg.attr("width", w)
.attr("height", h);
// Background pattern
var patternSize = 5;
svg.append("defs")
.append("pattern")
.attr("id", "dotPattern")
.attr("patternUnits", "userSpaceOnUse")
.attr("width", patternSize)
.attr("height", patternSize)
.append("circle")
.attr("cx", patternSize / 2)
.attr("cy", patternSize / 2)
.attr("r", 2)
.style("stroke", "none")
.style("fill", "lightgrey")
.style("opacity", 0.5);
var xScale = d3.time.scale()
.domain([0, 10000000000])
.range([padding, w-padding]);
var xAxis = d3.svg.axis()
.scale(xScale)
.ticks(5);
svg.append("g")
.attr("class","axis")
.attr("transform", "translate(0," + (h-padding) + ")")
.call(xAxis);
var zoom = d3.behavior.zoom()
.on("zoom", build)
.scaleExtent([1, 20]);
zoom.x(xScale);
var clipPath = svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("x", padding)
.attr("y", 0)
.attr("width",w-2*padding)
.attr("height", h-padding);
var zoomArea = svg.append("g")
.attr("class", "zoomArea")
.style("cursor","move")
.attr("clip-path", "url(#clip)");
var zoomRect = zoomArea.append("rect")
.attr("x", padding)
.attr("y", 0)
.attr("width", w-2*padding)
.attr("height", h-padding)
.style("fill", "url(#dotPattern)")
.style("pointer-events", "all")
.style("cursor","move")
.call(zoom);
zoomArea.selectAll("circles")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d){
return xScale(d);
})
.attr("cy", h/2)
.attr("r",10)
.attr("fill","grey")
function build(){
svg.select("g.axis").call(xAxis);
d3.selectAll("circle")
.attr("cx", function(d){
return xScale(d);
});
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Call zoom on circles as well.
zoomArea.selectAll("circles")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d){
return xScale(d);
})
.attr("cy", h/2)
.attr("r",10)
.attr("fill","grey")
.call(zoom);//call zoom on circle
Working code here
Hope this helps!
Another way of doing the same:
First make a rectangle with the fill background,don't attach the zoom listener to it.
var zoomRect = zoomArea.append("rect")
.attr("x", padding)
.attr("y", 0)
.attr("width", w-2*padding)
.attr("height", h-padding)
.style("fill", "url(#dotPattern)")
.style("cursor","move");//no zoom call
Not attach circles.
zoomArea.selectAll("circles")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d){
return xScale(d);
})
.attr("cy", h/2)
.attr("r",10)
.attr("fill","grey");
Now make another rectangle same as the first except it has zoom behavior and fill transparent..so that its above all elements to handle the zoom behavior.
zoomArea.append("rect")
.attr("x", padding)
.attr("y", 0)
.attr("width", w-2*padding)
.attr("height", h-padding)
.style("fill", "transparent")
.style("pointer-events", "all")
.style("cursor","move")
.call(zoom);
Working example here
Hope this helps too!
It appears I've followed the steps properly to fill a circle with an external image. I've tried numerous different image links, yet still can't resolve this pixelation issue. Any help is appreciated, here's a link to my fiddle:
http://jsfiddle.net/SdN2F/7/
Code issue snipet:
var photoCircle = d3.select('svg')
.append("circle")
.attr("cx", width-160)
.attr("cy", height-140)
.attr("r", radius-35)
.style("fill", "url(#photo)");
var image = d3.select('svg')
.append("pattern")
.attr("id", "photo")
.attr("x", 0)
.attr("y", 0)
.attr("width", width-160)
.attr("height", height-140)
.append("image")
.attr("x", 0)
.attr("y", 0)
.attr("width", width-160)
.attr("height", height-140)
.attr("xlink:href", "http://static-1.nexusmods.com/15/mods/110/images/50622-1-1391287636.jpeg");
You need to set the patternUnits attribute of pattern.
var image = d3.select('svg').append('defs')
.append("pattern")
.attr("id", "photo")
.attr("patternUnits","userSpaceOnUse")
.attr("x", 0)
.attr("y", 0)
.attr("width", 2*radius)
.attr("height", 2*radius)
.append("image")
.attr("x", 0)
.attr("y", 0)
.attr("width", 2*radius)
.attr("height", 2*radius)
.attr("xlink:href", "http://static-1.nexusmods.com/15/mods/110/images/50622-1-1391287636.jpeg");
jsFiddle: http://jsfiddle.net/chrisJamesC/SdN2F/11/
To start I'm sure there is a much simpler way to do this then what I'm trying to do. I'm trying to zoom in on specific circles using d3js and have text overlaying the circle. The problem is that since the text is ontop of the circle the text blocks the onclick even that is fired when you click on the circle. Here is my code so far:
js
var svg = d3.select("svg")
.attr("width", width)
.attr("height", height)
.append("g");
var node1 = svg.append("g")
.append("circle")
.data([offset[0]])
.text(function(d){return d.label})
.attr("r", 25)
.style("fill","white")
.on("click", clicked);
node1.attr("cx", 530)
.attr("cy", 310)
.transition()
.delay(500)
.duration(1000)
.attr("r", 55)
.attr("cx", 530)
.attr("cy", 205);
d3.select('g').append('text')
.attr("id","orient")
.attr("dx", 510)
.attr("dy", 210)
.attr("width", 90)
.attr("height", 90)
.text(function(d){return offset[0].label});
var node2 = svg.append("g")
.append("circle")
.data([offset[1]])
.attr("r", 25)
.style("fill","white")
.on("click", clicked);
node2.attr("cx", 530)
.attr("cy", 310)
.transition()
.delay(500)
.duration(1000)
.attr("r", 55)
.attr("cx", 620)
.attr("cy", 310);
d3.select('g').append('text')
.attr("id","seperate")
.attr("dx", 590)
.attr("dy", 315)
.attr("width", 90)
.attr("height", 90)
.text(function(d){return offset[1].label});
function
function clicked(d) {
var imageSelected = this;
console.log("clicked");
var cx, cy, k, offset;
var setClass = d.swipe_class;
cx = d3.select(this).attr("cx");
cy = d3.select(this).attr("cy");
k = 2;
cx= cx - d.xoff;
cy= cy - d.yoff;
console.log("cy="+d.yoff +"cx="+ d.xoff);
svg.transition()
.duration(750)
.attr("transform", "translate(" + width/2 + "," + height/2 + ")scale(" + k + ")translate(" + -cx + "," + -cy + ")");
}
Is there a way to trigger the circles click event when I click the text ontop of it? Or maybe just a better way of doing this that would allow it?
You can set the text to ignore pointer events:
...
.append("text")
.attr("pointer-events", "none")
...