I am working with force layout where some nodes are circle and some are arcs. I have used d3.symbolCircle to create my circles and now I am trying to add text to only circles. I have these circles as paths and I am trying to add text at the center of these circles.
I could get the text which follows the outline of the circle but what I want the text to be at the center of the circle laid out horizontally.
This is my code
let cir = d3.symbol().size(300).type(d3.symbolCircle);
node = svg.selectAll(".node")
.data(nodes.filter(d => d.name))
.enter().append("path")
.attr("class", "node")
.attr("id", d=> d.name)
.attr("d", d => {return d.type === 'Dish' ? cir(d) : arc(d);})
.on('mouseover', fade(0.1))
.on('mouseout', fade(1))
.on("click",stickNode)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
let text = svg.append("text")
.attr("dy", 15);
text.append("textPath")
.attr("xlink:href",function(d){return '#'+d.name;})
.attr('alignment-baseline', 'middle')
.attr('text-anchor', 'middle')
.attr('fill', 'white')
.attr("startOffset", '75%')
.text(d => d.name)
Related
If I try to store the following enter selection, I get an error when I try to access it. I don't have a problem if I remove the transition. Why? Are there other restrictions on storing selections? Here is an example:
// this works
var enterSel = d3.select("svg")
.selectAll("circle")
.data([100, 200, 300])
.enter()
.append("circle")
.attr("cx", d => d)
.attr("cy", "100")
.attr("fill", "red")
.attr("r", "0")
.transition()
.duration(2000)
.attr("r", "50");
The above appends and transitions three circles to red, as expected, but the enterSel variable cannot be used for further modifications:
// this doesn't work
enterSel.attr("fill", "green");
Uncaught Error: transition not found d3.v4.min.js:2
at zn (d3.v4.min.js:2)
at Cn (d3.v4.min.js:2)
at SVGCircleElement.<anonymous> (d3.v4.min.js:2)
at qn.each (d3.v4.min.js:2)
at qn.tween (d3.v4.min.js:2)
at qn.attrTween (d3.v4.min.js:2)
at qn.attr (d3.v4.min.js:2)
at <anonymous>:1:10
I can get around this by doing the transition separately, as follows, but I really want to understand why this is necessary.
// this works
var enterSel = d3.select("svg")
.selectAll("circle")
.data([100, 200, 300])
.enter()
.append("circle")
.attr("cx", d => d)
.attr("cy", "100")
.attr("fill", "red")
.attr("r", "0");
enterSel.transition()
.duration(2000)
.attr("r", "50");
enterSel.attr("fill", "green");
I'll post an answer for the future. A d3 selection returns a selection with the d3.selection.prototype. A transition on the other hand returns a transition with the d3.transition.prototype.
var enterSel = d3.select("svg")
.selectAll("circle")
.data([100, 200, 300])
.enter()
.append("circle")
.attr("cx", d => d)
.attr("cy", "100")
.attr("fill", "red")
.attr("r", "0")
.transition()
.duration(2000)
.attr("r", "50");
enterSel.attr("fill", "green");
Does not work because enterSel is now a transition and has different properties than a selection.
var enterSel = d3.select("svg")
.selectAll("circle")
.data([100, 200, 300])
.enter()
.append("circle")
.attr("cx", d => d)
.attr("cy", "100")
.attr("fill", "red")
.attr("r", "0");
enterSel.transition()
.duration(2000)
.attr("r", "50");
enterSel.attr("fill", "green");
This one works because enterSel is always a selection, which uses the selection prototype. The transition is sectioned away in the second call, but enterSel is always the selection of all the circles.
Hopefully this helps clear things up!
I have text labels that I would like to rotate.
in_years.selectAll(".xlabel")
.data(xTickLabels)
.enter()
.append("text")
.attr("class","xlabel")
.attr("text-anchor", "end")
//.attr("transform", "translate(0,0) rotate(-45)")
.attr("font-size", "14px")
.attr("x", (d,i) => xScale(xRange[i]))
.attr("y", (d,i) => height+15)
.text(function(d) { console.log(d);return d})
;
Wit "transform" line commented I obtain graph 1. Uncommenting the line I obtain graph 2 which does not make sense to me.
Any hint on why this happen and how to solve it? I am using d3 v3 for this
Why the rotation effect not as your expectation is due to the css property of rotate(). link
According to the rotate function definition in MDN doc:
''The axis of rotation passes through an origin''
So you have to translate each text element's (x,y) in order to let rotation axis is related to its site in the graphic.
// this code, which each text will rotate according to (0,0)
svg.selectAll('text.norotation')
.data(data)
.enter()
.append('text')
.text((d)=>d)
.classed('norotation', true)
.attr('fill', 'black')
.attr('x', (d,i)=> xScale(i))
.attr('y', 200)
Modified One
//each text element will rotate according to its position
svg.selectAll('text.rotation')
.data(data)
.enter()
.append('text')
.text((d)=>d)
.classed('rotation', true)
.attr('fill', 'black')
.attr('transform', (d,i)=>{
return 'translate( '+xScale(i)+' , '+220+'),'+ 'rotate(45)';})
.attr('x', 0)
.attr('y', 0)
Demo on Observalbe:https://beta.observablehq.com/#weitinglin/d3-rotating-text-labels
I have a radar chart with polygons. Now I want them to be filled with color on mouseover, but only when the mouse is on the path. When the mouse is inside the polygon it should have no fill.
So far I tried
svg.selectAll('.polygon')
.data(scaledData)
.enter()
.append('svg:path')
.attr('d', radialLine)
.attr('stroke', function(d, i) {return colors(i);})
.attr('stroke-width', '3')
.attr('fill', 'none')
.on("mouseover", function(d) {
d3.select(this).style("fill", d3.select(this).attr('stroke')).attr('opacity', 0.3);
})
.on("mouseout", function(d) {
d3.select(this).style("fill", "none").attr('opacity', 1);
});
Which fills when I'm over the whole polygon. Also I'd like to have the stroke stay the same and not change it's opacity.
Any help is appreciated
Set the attribute pointer-events="visibleStroke" to trigger the event over the stroke, and use fill-opacity instead of opacity.
svg.selectAll('.polygon')
.data(scaledData)
.enter()
.append('svg:path')
.attr('d', radialLine)
.attr('stroke', function(d, i) {return colors(i);})
.attr('stroke-width', '3')
.attr('fill', 'none')
.attr('pointer-events', 'visibleStroke')
.on("mouseover", function(d) {
d3.select(this).style("fill", d3.select(this).attr('stroke'))
.attr('fill-opacity', 0.3);
})
.on("mouseout", function(d) {
d3.select(this).style("fill", "none")
.attr('fill-opacity', 1);
});
I'm trying to draw a graph with d3 inserting images in node circles and drawing arrows on the target node. Nodes with images and edges are properly drawn, but the arrows are missing, although the marker is defined and used in links.
When I change the way nodes are attached to "g", arrows are drawn, but circle and images disappear. Can't figure out where the mistake stays.
D3 code is the following:
// define marker for the arrow
svg.append("defs").selectAll("marker")
.data(["arrow"])
.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")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
// add links
var link = svg.append("g").selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
.attr("marker-end", function(d) { return "url(#arrow)"; })
.style("stroke", "#FF3300");
// add nodes
var node = svg.append("g").selectAll(".node")
.data(nodes)
.enter().append("svg:g")
.attr("class", "node")
.on("dblclick", dblclick)
.call(force.drag);
node.append("svg:circle")
.attr("r", 50)
.style("fill", "#FFEBE6")
.style("stroke", "#FF3300")
.style("stroke-width", 3);
// add images - from base64
node.append("image")
.attr("xlink:href", function(d){
if (d.imgB64) {
return 'data:image/png;base64, ' + d.imgB64 ;
}
})
.attr("x", -40)
.attr("y", -40)
.attr("width", 80)
.attr("height", 80)
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0.0);
})
Nothing wrong with the code, arrows seems to be hidden/overlaid by the node circle and image. Changing the refX and refY attributes to 65 and 0 respectively addresses the issue.
Here is a plunker modified from mbostock
I want to make the text labels drag-able and attach a line to the circle when dragged.
.call(drag) works on the dots but not the labels
label = container.append("g")
.attr("class", "label")
.selectAll(".label")
.data(dots)
.enter().append("text")
.text(function(d) {return d.x + d.y; })
.attr("x", function(d) {return d.x; })
.attr("y", function(d) {return d.y; })
.attr("text-anchor", "middle")
.call(drag)
Here's a JSFiddle I made to demonstrate draggable text labels in D3.js
https://jsfiddle.net/h1n6fuwr/
Essentially you want to define the following variables/functions:
const drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended)
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
}
function dragged(d) {
const elem = d3.select(this)
elem.attr('x', +elem.attr('x') + d3.event.dx)
elem.attr('y', +elem.attr('y') + d3.event.dy)
}
function dragended(d) {}
And then call .call(drag) on your text labels.
const labels = ['Drag Me1', 'Drag Me2', 'Drag Me3']
d3.select('svg')
.selectAll('text')
.data(labels)
.enter()
.append('text')
.text(d => d)
.attr('fill', 'green')
.attr('x', (d, i) => 10 + i*30)
.attr('y', (d, i) => 15 + i*30)
.call(drag)
Append a rect behind the text, then .call(drag) on your rect. To get a suitable rect, you can use text.getBBox().