How to add data-cy testing ids to d3 page - cypress

I'm testing a page developed with d3 (in javascript) and need to add data-cy attributes to the artifacts on the page, as per recommended best practices.
But all the examples I've found show how to add the ids to HTML, but in my app the HTML is generated.
For example,
var node = svg.append("g")
.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r", 25)
.attr("cx", width / 2)
.attr("cy", height / 2)
.style("fill", "#19d3a2")
.style("fill-opacity", 0.3)
.attr("stroke", "#b3a2c8")
.style("stroke-width", 4)
.call(d3.drag() // call specific function when circle is dragged
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
so how do I do it in javascript?

The d3 .attr() method is not limited to keys that are known to the d3 framework. You can just add the data-cy attribute this way.
However, you will need a unique id, and since the node is generated it will have to be part of the data that generates the circles
var node = svg.append("g")
.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("data-cy", (d, i) => d.name)
generates
<circle data-cy="123" r="..." cx="..." cy="..." stroke="..." style="..."></circle>

Related

D3v4: add text to the center of d3.symbolCircle

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)

Trouble with storing enter selections containing transitions

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!

D3: use transition .attr('x') to horizontally slide <text> elements

I want to
display some text one after another
after each of the text is displayed, I would like to slide them horizontally from the left side of the page to the right side of the page: so I would like to change the texts' x= 10 to x= 500.
I am able to make step 1) but not 2).
Here is my script:
var textall =["one", "two", "three"]
var number = -1
d3.select('svg')
.transition()
.duration(0)
.delay(0)
.on("start", function addtext() {
number+=1;
if (number < textall.length){
d3.select('svg').append('g').append("text")
.attr("class", "one")
.attr('x', 10)
.attr('y', function(d,i) {return (number+1)*30; })
.text(textall[number])
.attr("font-family", "sans-serif")
.attr("font-size", "20px")
.attr("fill", "black")
.transition()
.duration(0)
.delay(2000)
.on("start", addtext);
}
<!-- }; -->
});
d3.selectAll(".one")
.transition()
.duration(1000)
.delay(6000)
.attr('x', 90)
On jsfiddle
Updated Fiddle I have updated your fiddle, not with the end result but with enough that you can fill in the exact values. Basically I added and .on("end") function that moves the text after they have been added. Further tweaking can happen to improve the aesthetic.

d3 rotating text labels

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

D3.v4 graph freezes when re-rendered

I am porting my D3.v3 code to the latest D3.v4. I used to invoke my rendering simulation function additional times after my data changes (additional nodes, additional links, etc).
Now, if i invoke again my render function the graph freezes, and it does not adjust new nodes or links and dragging won't work either.
Following is the code in jsfiddle
https://jsfiddle.net/za0xpdc5/4/
Graph.prototype.render = function() {
this.simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(200, 200));
link = this.linksG
.selectAll("line")
.data(this.links)
.enter()
.append("line")
.attr("id", function(d) {
return d.id
});
node = this.nodesG
.selectAll("g")
.data(this.nodes)
.enter()
.append("g")
.attr("id", function(d) {return d.id;})
.append("circle")
.attr("id", function(d) {return "node_" + d.id})
.attr("r", 5)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
# ====================================
# <---- It appears the freeze occurs at this point
# during the 2nd execution of graph.render()
# ====================================
this.simulation.nodes(this.nodes).on("tick", this.tick);
this.simulation.force("link").links(this.links);
}

Resources