D3.js v5 making clones of selection.clones - d3.js

Hi is it possible to make a clone of a selection clone in D3 v5?
I am essentially using selection clone to make and animate circles for simulating bacteria division.
I can call d3.select.clone multiple times but that only makes duplicate clones of the first circle that I call clone on. I want to be able to make clones of the clones as well.
var svg = d3.select('svg')
.style("width", '800px')
.style("height", '600px');
setInterval(function() {
svg.append("circle")
.attr("cx", 30)
.attr("cy", 50)
.attr("r", 15)
.attr("id", "test_circle0")
.style("fill", "red")
d3.select("#test_circle0").clone(true).transition().attr("transform", "translate(25)").duration(1000);
d3.select("#test_circle0").clone(true).transition().attr("transform", "translate(-25)").duration(3000);
}, 1000);

You need to put the creation of the initial circle outside the interval. Then you can clone it inside.
To do things to the clones themselves, you can change select to selectAll
var svg = d3.select('svg')
.style("width", '800px')
.style("height", '600px');
svg.append("circle")
.attr("cx", 300)
.attr("cy", 50)
.attr("r", 15)
.attr("class", "test-circle")
setInterval(function() {
d3.select(".test-circle").clone(true).transition().attr("transform", "translate(25)").duration(1000);
d3.selectAll(".test-circle").clone(true).transition().attr("transform", "translate(-25)").duration(3000);
}, 1000);
.test-circle {
fill: red
}
<div class="chart"></div>
<script src="https://d3js.org/d3.v5.min.js" charset="utf-8"></script>
<svg></svg>

Related

Accessing data in D3 from a click event

I have this svg that has a circle inside. The circle has been created with the data object from "myData".
The circle has a click event, but when I try to console.log() the data appended to this circle, I get the following error message : Uncaught ReferenceError: d is not defined.
Any suggestions are very welcome.
The below code can be tested here : https://jsfiddle.net/zsv21byf/
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg id="cv">
</svg>
const myData = [
{id:"Zoox",type:"Tier1",tags:"AD_Driverless",technology:"",market:"Robotaxi",valuation:"",description:"Zoox is an AI robotics company that provides mobility as-a-service and self-driving car services.",country:"US",region:"North America",image:"https://res-4.cloudinary.com/crunchbase-production/image/upload/c_lpad,h_170,w_170,f_auto,b_white,q_auto:eco/kpc7mmk886nbbipbeqau"}
]
var svg = d3.select("#cv")
.attr("height",300)
.attr("width", 300)
.style("border", "1px solid red")
const node = svg.append("g")
.attr("stroke", "red")
.attr("stroke-width", 5)
.selectAll("circle")
.data(myData)
.join("circle")
.style("fill", "white")
.attr("r", 20)
.attr("cx", 100)
.attr("cy", 100)
.on("click", function() {
console.log(d.id)
});
#cv{
width: 100%;
}
I figured it out. I needed to access the data through d3.select(this).
In my case, this is what needed to be typed in the console.log :
console.log( d3.select(this).data()[0] )

Transition trigger to not work till another transition is complete

I have 2 circles that move right when clicked. Transition takes 10 sec.
What I want
Requirement 1: If circle 1 is in transition, clicking circle 2 should not trigger transition
Requirement 2: If circle 1 is in transition, clicking circle 2 should stop circle 1 at its current position and start transition for circle 2
here is my code
let svg = d3.select("body").append("svg").attr("width", 1800).attr("height", 1800)
svg.selectAll("circles")
.data([100,200])
.enter()
.append("circle")
.attr("class","zubi")
.attr("cx",50)
.attr("cy",d=>d)
.attr("r",30)
.on("click",function(){
d3.select(this)
.transition()
.duration(10000)
.attr("transform","translate(800)")
})
One way to block any transitions is to just remove the handler when the transition is started, and put it back when it's done:
let svg = d3.select("body").append("svg").attr("width", 600).attr("height", 300);
function myClickFunction(d, i) {
circles.on("click", null);
d3.select(this)
.transition()
.duration(5000)
.attr("transform", "translate(300)")
.on("end", () => circles.on("click", myClickFunction));
}
let circles = svg.selectAll("circles")
.data([100, 200])
.enter()
.append("circle")
.attr("class", "zubi")
.attr("cx", 50)
.attr("cy", d => d)
.attr("r", 30)
.on("click", myClickFunction)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Other possibilities are to add/remove classes, or to get the active transition using d3.active.
You can use d3.selection.interrupt to cancel any currently running transitions on a selection. I gave the transition a name so you can more fine-grained control. You can have multiple transitions run side by side if you want - if no name is given all transitions are interrupted.
let svg = d3.select("body").append("svg").attr("width", 600).attr("height", 300)
let circles = svg.selectAll("circles")
.data([100, 200])
.enter()
.append("circle")
.attr("class", "zubi")
.attr("cx", 50)
.attr("cy", d => d)
.attr("r", 30)
.on("click", function(d, i) {
// Cancel any running transitions
circles.interrupt("circle-transform");
d3.select(this)
.transition("circle-transform")
.duration(5000)
.attr("transform", "translate(300)")
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

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 click to center content at position of element or click

I have been trying to get the basics of how I make a pannable zoomable, and click to center zoom on element d3 work. This example is what I want to do but I am having trouble translating it outside of the geo context: https://bl.ocks.org/mbostock/2206340
What I have accomplished is the first two parts pan and zoom, see a basic fiddle here https://jsfiddle.net/e9fbn2xp/
How can I accomplish centering the the circle in the center of the viewable window, so it looks like the circle is zoomed to? Note that although this is a fixed position circle I will eventually have dynamic data, so ideally I could reference the circles position dynamically.
Here is my code:
HTML (note that this is React JSX syntax but that should be irrelevant to question)
<div style={{width: 800}}>
<svg style={{border: '1px solid black'}} id="viz" width="800" height="800">
</svg>
</div>
JAVASCRIPT
var svg = d3.select("#viz")
var width = svg.attr("width");
var height = svg.attr("height");
var testLayer = svg.append('g');
var aRect = testLayer.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("height", 800)
.attr("width", 800)
.attr("fill", 'green');
var aCircle = testLayer.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 40)
.attr("cx", 200)
.attr("cy", 200)
.on("mousedown", zoomToMe);
function zoomToMe(){
console.log("do the zoom")
}
var zoom = d3.zoom()
.scaleExtent([.5, 40])
.translateExtent([[0, 0], [width, height]])
.on("zoom", zoomed);
svg.call(zoom);
function zoomed() {
testLayer.attr("transform", d3.event.transform);
}
svg.on("click", function() {
var coords = d3.mouse(this);
})
I got a working solution and thought I would share the code in case others find it useful. It is a fairly different approach then my original but accomplishes the three goals, pan, mouse zoom, zoom to element. While these are three simple static circles the same concept should work with a dynamic dataset.
fiddle: https://jsfiddle.net/uc7oprx3/5/
HTML
<svg id="viz" width="400" height="400" />
JAVASCRIPT
var zoom = d3.zoom()
.scaleExtent([0.3,2])
.on("zoom", zoomed);
var svg = d3.select("#viz")
var width = svg.attr("width");
var height = svg.attr("height");
var zoomer = svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.call(zoom);
var g = svg.append("g");
var aCircle = g.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 40)
.attr("cx", 200)
.attr("cy", 200)
.on("mousedown", () => centerNode(200, 200));
var bCircle = g.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 40)
.attr("cx", 400)
.attr("cy", 400)
.on("mousedown", () => centerNode(400, 400));
var cCircle = g.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 40)
.attr("cx", 600)
.attr("cy", 600)
.on("mousedown", () => centerNode(600, 600));
function zoomed() {
g.attr("transform", d3.event.transform);
}
function centerNode(xx, yy){
g.transition()
.duration(500)
.attr("transform", "translate(" + (width/2 - xx) + "," + (height/2 - yy) + ")scale(" + 1 + ")")
.on("end", function(){ zoomer.call(zoom.transform, d3.zoomIdentity.translate((width/2 - xx),(height/2 - yy)).scale(1))});
}

d3.js: map with concentric circles emanating from dots

Been away from d3.js for a few months... and I've inherited a simple US map with features that someone else started.
The features are represented by simple dots of varying sizes.
I want to add emanating concentric circles to each dot, similar to the classic Onion example by Mike Bostock: http://bl.ocks.org/mbostock/4503672 (maybe not so ominous looking though)
I've got a block set up here: http://bl.ocks.org/mbostock/4503672
(Not sure why the states aren't rendering correctly in the block, but it probably doesn't matter for this.)
In Mike's example there is only one dot, so I'm have trouble understanding how to translate what he did to what I've got (many dots).
Here's my script:
/**
* Page initialization
*/
$(function() {
renderMap('#map-container');
});
function renderMap(container) {
var width = 960,
height = 500,
active;
var projection = d3.geo.albersUsa()
.scale(960)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var radius = d3.scale.sqrt()
.domain([0, 1e7])
.range([0, 10]);
var path2 = d3.geo.path()
.projection(projection);
// Remove svg, if already exist
d3.select(container).select('svg').remove();
var svg = d3.select(container).append("svg")
.attr("width", width)
.attr("height", height);
svg.append("rect")
.attr("width", width)
.attr("height", height);
//.on("click", reset);
var g = svg.append("g");
queue()
.defer(d3.json, "/mbostock/raw/4090846/us.json")
.defer(d3.json, "dots.json")
.await(function (error, us, centroid) {
g.append("g")
.attr("id", "states")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.attr("class", "state");
//.on('click', click);
g.append('path')
.attr("id", "state-borders")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("d", path)
.attr("class", "mesh");
var dots = g.append("g")
.attr("id", "dots")
.selectAll("path")
.data(centroid.data)
.enter().append("path")
.attr("class", "dot")
.attr("d", path2.pointRadius(function(d) { return radius(d.properties.pool); }));
}
);
}
and the key part of Mike's example for making the rings is:
setInterval(function() {
svg.append("circle")
.attr("class", "ring")
.attr("transform", "translate(" + projection([100, -8]) + ")")
.attr("r", 6)
.style("stroke-width", 3)
.style("stroke", "red")
.transition()
.ease("linear")
.duration(6000)
.style("stroke-opacity", 1e-6)
.style("stroke-width", 1)
.style("stroke", "brown")
.attr("r", 160)
.remove();
}, 750);
how do I get the rings positioned on the dots?
Review the differences between the two methods to learn a little bit more about how functional/declarative programming abstracts away the pain of iterative programming.
Approach with D3 idioms:
fiddle: http://jsfiddle.net/blakedietz/E66eT/1/
update: D3 Way
<!DOCTYPE html>
<html>
<head>
<title></title>
<style>
body {
background: #192887;
}
.graticule {
fill: none;
stroke: #fff;
stroke-width: .5px;
}
.land {
fill: #007421;
}
.dot {
fill: #c7141a;
}
.ring {
fill: none;
stroke: #c7141a;
}
</style>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<script>
var width = 960,
height = 500;
var projection = d3.geo.mercator()
.center([113, -3])
.scale(1275)
.translate([width / 2, height / 2])
.clipExtent([[0, 0], [width, height]])
.precision(.1);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule()
.step([5, 5]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
var data = [{x:-8,y:100},{x:-10,y:110},{x: -12,y:120}];
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("class","dot")
.attr("transform",translateCircle)
.attr("r",8);
function translateCircle(datum, index)
{
return "translate(" + projection([datum.y, datum.x]) + ")";
};
setInterval(function(){
svg
.selectAll("ring")
.data(data)
.enter()
.append("circle")
.attr("class", "ring")
.attr("transform", translateCircle)
.attr("r", 6)
.style("stroke-width", 3)
.style("stroke", "red")
.transition()
.ease("linear")
.duration(6000)
.style("stroke-opacity", 1e-6)
.style("stroke-width", 1)
.style("stroke", "brown")
.attr("r", 160)
.remove();
}, 750)
d3.select(self.frameElement).style("height", height + "px");
</script>
</body>
</html>
So I didn't create a fully d3 idiomatic approach for this solution, but it will work. If you can get this to work implicitly within a svg.selectAll("circle"/"unique selection"...), etc, that would be even more awesome. I'll work on that in the mean time. Until then there's this more explicitly iterative approach.
With Mike's example you're only appending a single element to the D.O.M. in the setInterval call. In order to expedite the binding process, I've created a projection method which operates on a set of coordinates: the translateCircle will operate on a datum within a collection of coordinates allowing access to the internal attributes of each collection element.
Within each setInterval call the forEach method iterates over the collection of coordinates and then calls the same internals within the setInterval method that was called by Mike originally.
Not So D3
<style>
body {
background: #192887;
}
.graticule {
fill: none;
stroke: #fff;
stroke-width: .5px;
}
.land {
fill: #007421;
}
.dot {
fill: #c7141a;
}
.ring {
fill: none;
stroke: #c7141a;
}
</style>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<script>
var width = 960,
height = 500;
var projection = d3.geo.mercator()
.center([113, -3])
.scale(1275)
.translate([width / 2, height / 2])
.clipExtent([[0, 0], [width, height]])
.precision(.1);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule()
.step([5, 5]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
var data = [{x:-8,y:100},{x:-10,y:110},{x: -12,y:120}];
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("class","dot")
.attr("transform",translateCircle)
.attr("r",8);
function translateCircle(datum, index)
{
return "translate(" + projection([datum.y, datum.x]) + ")";
};
setInterval(function(){
data.forEach(function(datum)
{
svg
.append("circle")
.attr("class", "ring")
.attr("transform", translateCircle(datum))
.attr("r", 6)
.style("stroke-width", 3)
.style("stroke", "red")
.transition()
.ease("linear")
.duration(6000)
.style("stroke-opacity", 1e-6)
.style("stroke-width", 1)
.style("stroke", "brown")
.attr("r", 160)
.remove();
})
}, 750)
d3.select(self.frameElement).style("height", height + "px");
</script>
</body>
</html>

Resources