Accessing data in D3 from a click event - d3.js

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] )

Related

D3.js v5 making clones of selection.clones

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>

d3.js add a label in the center of a path

how to add a label in the center of path programmatically without using the BBOX method because it does not work with banana shapes
d3.json("mapgeo.json", function(json) {
//Bind data and create one path per GeoJSON feature
paths = g.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr('name', function(d) {
return d.properties.name.toLowerCase();
})
.attr("d", path)
.attr("id", function(d, i) { return 'polygon'+i;})
.style("fill", "steelblue");
for(var i=0;i<paths[0].length;i++){
var pp = paths[0][i].__data__.properties;
svg
.append('text')
.attr("x", 145)
.attr("dy", 105)
.append("textPath")
.attr("xlink:href","#polygon"+i)
.text(paths[0][i].__data__.properties.temperature+' C°');
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="400" height="300">
<g>
<path name="cf40" d="M590.3383838385344,295.20151514932513 C 756 327,756 327, 878.5818181820214,279.5361111164093L822.186363636516,527.0494949556887L728.1939393933862,555.2472222223878Z" id="polygon2" style="fill: steelblue;" transform="translate(-500,-260)"></path>
</g>
<text x="145" dy="105"><textPath xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#polygon2">CF40</textPath></text>
</svg>
(I confess that I quite didn't understand what you want to achieve with your code, so, I'm going to address specifically your question's title: "how to add a label in the center of a path").
D3 have a handy function for locating the center of the path, called path.centroid:
Returns the projected planar centroid (typically in pixels) for the specified GeoJSON object. This is handy for, say, labeling state or county boundaries, or displaying a symbol map.
You can use it to position your labels:
.attr("x", function(d) {
return path.centroid(d)[0];
})
.attr("y", function(d) {
return path.centroid(d)[1];
})
Here is a demo with a USA map (just found the code online). I'm locating the center of each path using centroid and labelling it with "foo":
var width = 500,
height = 400;
var projection = d3.geoAlbersUsa()
.scale(700)
.translate([width / 2, height / 2]);
var path = d3.geoPath()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("https://dl.dropboxusercontent.com/u/232969/cnn/us.json", function(error, us) {
svg.selectAll(".state")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.attr('class', 'state');
svg.selectAll(".stateText")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("text")
.attr("x", function(d) {
return path.centroid(d)[0];
})
.attr("y", function(d) {
return path.centroid(d)[1];
})
.attr("text-anchor", "middle")
.attr("font-size", "12px")
.text("foo")
});
.state {
fill: none;
stroke: black;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>

Integrating d3 with meteor? Trying to draw pie charts

I'm trying to draw pie charts in Meteor, but I'm very new to both d3 and Meteor and am not really understanding what is going on.
The following d3 code to draw pie charts from a csv file works for me outside of Meteor:
<!DOCTYPE html>
<meta charset="utf-8">
<link href='http://fonts.googleapis.com/css?family=Montserrat:400,700' rel='stylesheet' type='text/css'>
<style>
body {
font: 30px "Montserrat";
text-transform:uppercase;
}
svg {
padding: 10px 0 0 10px;
}
.arc {
stroke: #fff;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var radius = 150,
padding = 10;
var color = d3.scale.ordinal()
.range(["#f65c55","#c8e7ec"]);
var arc = d3.svg.arc()
.outerRadius(radius)
.innerRadius(radius - 40);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.population; });
d3.csv("data.csv", function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "Criteria"; }));
data.forEach(function(d) {
d.ages = color.domain().map(function(name) {
return {name: name, population: +d[name]};
});
});
var legend = d3.select("body").append("svg")
.attr("class", "legend")
.attr("width", radius * 2)
.attr("height", radius * 2)
.selectAll("g")
.data(color.domain().slice().reverse())
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 50 + ")"; });
legend.append("rect")
.attr("width", 40)
.attr("height", 40)
.style("fill", color);
legend.append("text")
.attr("x", 50)
.attr("y", 20)
.attr("dy", ".35em")
.attr("font-size","20px")
.text(function(d) { return d; });
var svg = d3.select("body").selectAll(".pie")
.data(data)
.enter().append("svg")
.attr("class", "pie")
.attr("width", radius * 2)
.attr("height", radius * 2)
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
svg.selectAll(".arc")
.data(function(d) { return pie(d.ages); })
.enter().append("path")
.attr("class", "arc")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.name); });
svg.append("text")
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.Criteria; });
});
</script>
I also have a Meteor template as follows that I want to draw these charts in:
<div class="tab-pane active" id="playback">
{{> playback}}
</div>
However, when I try and follow web tutorials to integrate the two, the graphs don't get drawn. Can anyone help me understand why? Thanks in advance!
EDIT: forgot to mention, data.csv looks like this:
Criteria,Disapproval, Approval
Too Fast,1,2
Too Slow,5,6
Clarity,2,3
Legibility,202070,343207
The first line is for the legend, and the rest are for 4 separate graphs.
You have to make sure that the template is rendered before you access the DOM elements by code. So put your D3 code inside a template rendered method, like this:
Template.playback.rendered = function() {
// your D3 code
}
or on the body tag e.g.:
UI.body.rendered = function() {
// your D3 code
}
Template.chart.rendered = function(){
Deps.autorun(function () {
//d3 codeing here!!
}
}
It's working for me. If you're coding without Deps.autorun() it's will not render.
Oh!! one morething at html page in you case maybe .
However for my case I using nvd3.org http://nvd3.org/livecode/index.html#codemirrorNav. And this I hope you will clearify.

d3 force layout nodes are pinning on dblick but appearance does not modify

Using and playing around with the Les Miserables Json data, I coded from modifications in d3. I was able to code everything except highlighting/marking a node if it is in the "fixed" state (vs the force graph layout). When you double click a node, it will freeze, a drag and move will pin it somewhere while all the other nodes (by default) in the force layout. When you double click the node again, the node is unpinned, and floating in the force layout as usual. The second thing I wanted to do is that if the node is in a “pinned” state, the node should be a different color, or highlighted somehow. I tried a few ways within the method chaining, checking with conditional statements for the node state to modify the node features, but they are not working. I also separated out the dblclick handler as an outside function (which is my current version.
I put my code here of the original attempt within the method chains:
http://pastebin.com/SqrqgVET
I also tried another way, using a variable “pinned” to determine the state of the node, within the double-click, to modify the nodes border, and change the state accordingly but that is not changing the node’s appearance either. There is a console.log() statement, and the function is going into the if else portions accordingly.
http://pastebin.com/dzEw42mQ
Below is the current version.
Any feedback would be great. Thanks!
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
cursor: move;
stroke: #fff;
stroke-width: 1.5px;
}
.node.fixed {
fill: #f00;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
</style>
<head>
<title>Victor Hugo Had No Internet</title>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="http://d3js.org/colorbrewer.v1.min.js"></script>
</head>
<body>
<script type="text/javascript">
//Size of region to render on
var width = 960,
height = 500;
var color = d3.scale.ordinal()
.domain([1, 10])
.range(colorbrewer.BrBG[9]);
//D3 force directed layout
//Try playing with the charge and link distance
var force = d3.layout.force()
.charge(-100)
.linkDistance(40)
//.on("tick", tick) //event ADDED
.size([width, height]);
//Add our canvas
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
//Select the miserables data ;)
d3.json("miserables.json", function(error, graph) {
force
.nodes(graph.nodes)
.links(graph.links)
.start();
//Add the links
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
//Add the nodes
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", function(d){ return Math.sqrt(d.coolness);})
.attr("stroke", "#ffffff")
.on("dblclick", dblclick)
.call(force.drag)
.style("fill", function(d) {return color(d.group);}); //});
//.style("border", 5);//function(d) {
//if (d.fixed==false) {return 4}; });
// node.append("text") //label
// .attr("dx", 6)
// .attr("dy", ".10em")
// .text(function(d) { return d.name; });
//add labels
var labels = svg.selectAll("text")
.data(graph.nodes)
.enter()
.append("text")
.attr({"x":function(d){return d.x;},
"y":function(d){return d.y;}})
.text(function(d) {
if (d.coolness>25 && d.name.length > 6) {return d.name.substring(0,6)+'...'}
if (d.coolness>25 && d.name.length < 6) {return d.name}
else { return null } ;})
.call(force.drag);
//Update stuff for animation:
// This takes the physics simulation for the force directed graph and
// sets the location of the nodes and edges to the new positions
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
labels.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
});
// action to take on mouse double click
function dblclick(d) { //color, stroke not working.
//var pinned = d3.select(this).attr("stroke");
console.log("dblclick")
if (d.fixed == true) { //pinned state
console.log("pinned")
d3.select(this)
.attr("stroke", "#ffffff")
.attr("stroke-width", 1.5)
.classed("fixed", d.fixed = false);//now unpin
} else { //else not pinned state
console.log("not pinned")
d3.select(this)
.attr("stroke", "#000000")
.attr("stroke-width", 4)
.classed("fixed", d.fixed = true);
}
}//end dbl click
});
</script>`
Here is the output of the console log (where I added console.log(this) to check the right element is being passed to the dblclick function and if parameters are being set accordingly:
[Log] dblclick (miserables_graph.html, line 131)
[Log] <circle class=​"node fixed" r=​"9.1104335791443" stroke=​"#000000" style=​"fill:​ #f6e8c3;​" cx=​"630.38114584665" cy=​"98.39845698676822" stroke-width=​"4">​</circle>​ (miserables_graph.html, line 132)
[Log] not pinned (miserables_graph.html, line 140)
[Log] dblclick (miserables_graph.html, line 131)
[Log] <circle class=​"node" r=​"9.1104335791443" stroke=​"#ffffff" style=​"fill:​ #f6e8c3;​" cx=​"630.38114584665" cy=​"98.39845698676822" stroke-width=​"1.5">​</circle>​ (miserables_graph.html, line 132)
[Log] pinned (miserables_graph.html, line 134)

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