I am trying to get the new position of the node after it has been transformed. In this case I am moving a rectangle across the screen. I was hoping to be able to access the transform on the rectangle in another context but this only shows the starting position of the node. How do I get the node's final position?
The problem seems to be related to the use of transition() in combination with a transform function ("myTransform" in the example below). I just can't figure out what is going on.
var moveButton = d3.selectAll("#move");
moveButton.on("click", moveBox);
var myTransform = function(d) {
return "translate(" + d.a + "," + d.b + ")";
};
var data = [{
"a": 40,
"b": 40
}];
var box = d3.selectAll("svg")
.append("g")
.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("id", "myBox")
.attr("height", "50")
.attr("width", "100")
.attr("fill", "green")
function moveBox(d) {
box = d3.select("#myBox")
.transition()
.attr("transform", myTransform);
// How do I retrieve the transform?
//console.log(box.attr("transform"));
}
<html>
<head>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<form action="move">
<input id="move" name="move" type="button" value="Move" />
</form>
<svg>
</svg>
</body>
</html>
Sure, so you're looking to access the transform after the transition has finished. You can do so by using the transition's .on method, as documented in d3-transition and the life of a transition. In this case:
box = d3.select("#myBox")
.transition()
.attr("transform", myTransform)
.on('end', function() {
console.log(box.attr("transform"));
});
Should do it for you.
Related
I have a simple D3 simulation that looks like this
When I click the 'remove 1 and 4' button I want to remove nodes 1 and 4 from the simulation. The result, however, is the following:
Visually it seems to have removed 3 and 4 (not 1 and 4).
My code is below. Any ideas what I need to do to make this work correctly?
I'm assuming I've fundamentally misunderstood how updating nodes works in d3. Any help appreciated.
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<!DOCTYPE html>
<meta charset="utf-8">
<script type="text/javascript" src="http://d3js.org/d3.v3.js"></script>
<body>
<button>remove 1 and 4</button>
<script>
var width = 400,
height = 400;
var nodes = [1, 2, 3, 4].map(function(x) { return { name: x}});
var force = d3.layout.force()
.size([width, height])
.nodes(nodes)
.linkDistance(30)
.charge(-500)
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var content = svg.append("g");
var nodesData = force.nodes(),
nodeElement = content.selectAll(".node");
function tick() {
nodeElement.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
d3.selectAll('button').on('click', function() {
//remove 1 and 4
nodesData = [
nodesData[1],
nodesData[2]
]
refresh();
});
var WIDTH = 100;
//
// this logic is slightly modified from http://bl.ocks.org/tgk/6068367
//
function refresh() {
force.nodes(nodesData)
nodeElement = nodeElement.data(nodesData);
var nodeGroup = nodeElement.enter()
.append('g')
.attr("class", "node");
// node text
nodeGroup.append("text")
.attr("class", "nodetext")
.attr("dx", WIDTH/2)
.attr("dy", "14px")
.text(function(n) { return 'node '+n.name })
nodeElement.exit().remove();
force.start();
}
refresh();
</script>
You can solve your problem by adding a "key" function to the .data call inside the refresh function: nodeElement = nodeElement.data(nodesData, function(d){ return d.name });.
The problem you saw is not specific to updating nodes. Ordinarily, selections work based off of index of the data array. So if first D3 had [a,b,c,d] and now it has [a,d], it's going to take the first two elements ([a,b]) unless you tell it the key that defines each item in the array. That's what the function above does.
For more see https://github.com/d3/d3-selection/blob/master/README.md#selection_data
Today I am learning about D3.js
I started by studying this content:
https://bost.ocks.org/mike/circles/
Most of it seems easy to understand and follow.
But I have a problem getting exit() to work.
I do understand the idea that I can use exit() to force elements in my DOM to synch with values in a JS array.
So I wrote some code to demonstrate this idea and my code fails.
I want to know how I can rewrite my JS so that exit() will force elements in my DOM to synch with values in a JS array.
<html>
<body>
Ref:
<a href='https://bost.ocks.org/mike/circles/' target='x'>
https://bost.ocks.org/mike/circles/
</a>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>
// I should create 3 circles
var svg1 = d3.select('body')
.append('svg')
.attr('id','svg1')
.attr('width',800).selectAll("circle")
.data([0, 1, 2])
.enter()
.append("circle")
.attr("cy", 60)
.attr("cx", function(d, i) { return i * 100 + 30 })
.attr("r", function(d) { return 5+5*d })
// So far so good. I see 3 circles
// Now I should remove some.
var mycircles_a = svg1.selectAll("circle")
.data([99, 88])
// I should ask D3 to make the data-array sync with the circle-elements:
mycircles_a.exit().remove()
// Above call fails for some reason; I still see 3 circles!
// I should see 2 circles because mycircles_a is bound to an array with only 2 values.
'bye'
</script>
</body>
</html>
In your example svg1 is, itself, an "enter" selection.
Your code works just fine if you break the chain, making svg1 just a selection that creates the SVG:
var svg1 = d3.select('body')
.append('svg')
.attr('id','svg1')
.attr('width',800);
svg1.selectAll("circle")
.data([0, 1, 2])
.enter()
.append("circle")
.attr("cy", 60)
.attr("cx", function(d, i) { return i * 100 + 30 })
.attr("r", function(d) { return 5+5*d })
var mycircles_a = svg1.selectAll("circle")
.data([99, 88])
mycircles_a.exit().remove()
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
You're not saving a reference to svg1 correctly.
https://jsfiddle.net/y008c61L/
var svg1 = d3.select('body')
.append('svg')
.attr('id','svg1')
.attr('width',800);
svg1.selectAll("circle")
//...
I'm attempting to nest an arc generated using d3.arc perfectly inside another arc.
I can do this by drawing "on my own":
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
function arc_position(x, y, radius, angle) {
return {
x: x + (radius * Math.cos(angle)),
y: y + (radius * Math.sin(angle))
};
}
function describe_arc(x, y, radius, startAngle, endAngle) {
var s = arc_position(x, y, radius, endAngle);
var e = arc_position(x, y, radius, startAngle);
var sweep = e - s <= 180 ? '0' : '1';
var d = [
'M', s.x, s.y,
'A', radius, radius, 0, sweep, 0, e.x, e.y
].join(' ');
return d;
}
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500)
.append("g");
var s = arc_position(250, 250, 200, Math.PI/2);
var e = arc_position(250, 250, 200, (Math.PI * 3)/2);
svg.append("path")
.style("fill", "none")
.style("stroke", "orange")
.style("stroke-width", "20px")
.attr("d", describe_arc(250,250,180,(Math.PI * 3)/2, Math.PI/2));
svg.append("path")
.style("fill", "none")
.style("stroke", "orange")
.style("stroke-width", "20px")
.attr("d", "M" + (s.x + 30) + "," + s.y + "L" + (e.x + 30) + "," + e.y);
svg.append("path")
.style("fill", "none")
.style("stroke", "steelblue")
.style("stroke-width", "20px")
.attr("d", describe_arc(250,250,200,(Math.PI * 3)/2, Math.PI/2));
svg.append("path")
.style("fill", "none")
.style("stroke", "steelblue")
.style("stroke-width", "20px")
.attr("d", "M" + (s.x + 10) + "," + s.y + "L" + (e.x + 10) + "," + e.y);
</script>
</body>
</html>
But I can't figure out a methodology using d3.arc:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500)
.append("g")
.attr("transform","translate(250,250)");
var arc = d3.arc()
.innerRadius(0)
.outerRadius(200)
.startAngle(0)
.endAngle(Math.PI);
svg.append("path")
.style("fill", "none")
.style("stroke", "steelblue")
.style("stroke-width", "20px")
.attr("d", arc());
arc.outerRadius(200 - 40);
svg.append("path")
.style("fill", "none")
.style("stroke", "orange")
.style("stroke-width", "20px")
.attr("d", arc())
.attr("transform", "translate(20,0)")
</script>
</body>
</html>
Any ideas?
I don't think that there is a good way to do this just using d3.arc because that is meant for drawing sections of circles and you're trying to draw a partial ellipse.
You can get close by generating the angle offset using the stroke width and the radius of the inner arc as offsetAngle = sin(stroke width / inner radius). The arc's startAngle is the offsetAngle and the endAngle is Math.PI - offsetAngle.
Unfortunately, that will generate a path which includes the center point of the circle. You can hack together something that works by just removing the L0,0 from the generated path (innerArc().replace("L0,0","")) and this will give you what you want, albeit in an ugly fashion.
Because it is a fairly simple path, it is probably best to use your own path generator to do this instead.
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500)
.append("g")
.attr("transform","translate(250,250)");
var outerRadius = 200;
var stroke = 20;
var innerRadius = outerRadius - stroke;
var innerAngle = Math.sin(stroke/innerRadius);
var outerArc = d3.arc()
.innerRadius(0)
.outerRadius(outerRadius)
.startAngle(0)
.endAngle(Math.PI);
var innerArc = d3.arc()
.innerRadius(0)
.outerRadius(innerRadius)
.startAngle(innerAngle)
.endAngle(Math.PI - innerAngle);
svg.append("path")
.style("fill", "none")
.style("stroke", "steelblue")
.style("stroke-width", stroke)
.attr("d", outerArc());
svg.append("path")
.style("fill", "none")
.style("stroke", "orange")
.style("stroke-width", stroke)
.attr("d", innerArc().replace("L0,0",""));
</script>
</body>
</html>
(This is not an answer, but just a comment, which I chose to disguise as an answer because I need the S.O. snippet)
I believe that Paul S is right, and he deserves the big prize of the green tick: what you're trying to paint in the inner (orange) path is not an arc of a circumference, but an ellipse instead. And, as the docs say,
The arc generator produces a circular or annular sector
, which will not work for creating an elliptic sector.
You can easily see that in the following demo: using a for loop, we draw arcs with decreasing outer radius. The only way to perfectly fill the internal space of each arc is if the next, smaller arch has the same center of the precedent, bigger one:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500)
.append("g")
.attr("transform","translate(250,250)");
var arc = d3.arc()
.innerRadius(0)
.outerRadius(200)
.startAngle(0)
.endAngle(Math.PI);
var color = d3.scaleOrdinal(d3.schemeCategory10);
for(var i = 0; i<11; i++){
svg.append("path")
.style("stroke", color(i))
.style("fill", "none")
.style("stroke-width", "20px")
.attr("d", arc());
arc.outerRadius(200 - 20*i);
};
</script>
</body>
</html>
There is an even easier way to visualize that. Let's create a single arc, with a huge stroke:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500)
.append("g")
.attr("transform","translate(250,250)");
var arc = d3.arc()
.innerRadius(0)
.outerRadius(150)
.startAngle(0)
.endAngle(Math.PI);
svg.append("path")
.style("fill", "none")
.style("stroke", "steelblue")
.style("stroke-width", "100px")
.attr("d", arc());
</script>
</body>
</html>
Look at the blue arc: it is a semicircle. Now look at the white space inside it: it's clearly not a semicircle. The blue thing is a semicircle, but the space inside it is not.
So, as d3.arc() right now doesn't have the option to set two different focal points, the options are creating your own function (as you did) or hacking the path (as Paul S did).
You draw second arc with outer radius R - 2 * strokewidth and center shifted by strokewidth.
But smaller arc should have the same center as larger one and outer radius R - strokewidth
I am a D3 newer and wrote a program to draw a pie ring.
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>pie ring</title>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script type="text/javascript">
var dataset=[{name:A,value:5},{name:C,value:10},{name:F,value:13}];
var pie=d3.layout.pie(dataset);
var h=600;
var w=600;
var outerRadius=w/2;
var innerRadius=w/3;
var arc=d3.svg.arc()
.outerRadius(outerRadius)
.innerRadius(innerRadius);
var svg=d3.select("body")
.append("svg")
.attr("width",w)
.attr("height",h);
var color=d3.scale.category10();
var arcs=svg.selectAll("g.arc")
.data(pie(dataset))
.enter()
.append("g")
.attr("class","arc")
.attr("transform","translate("+outerRadius+","+outerRadius+")");//translate(a,b)
arcs.append("path")
.attr("fill",function(d,i){
return color(i);
})
.attr("d",arc);
arcs.append("text")
.attr("transform",function(d){
return "translate("+arc.centroid(d)+")";
})
.attr("text-anchor","middle")
.text(function(d){
return d.name + ":" + d.value;
});
</script>
</body>
</html>
But failed, I don't know that how to define data structure and don't know how to pie dataset. pie(dataset) or pie(function(d){return d.value} ); could you help me to correct it
the javascript console told me that A is not defined. This A is the value of the name of the dataset. I don't know if the only request is digital value or digital array for pie ring.
Few problems here. It's telling you A isn't defined because it's not. You're using plain text instead of a string, so it thinks A is a variable, not a label. Even if you resolved that, there would be some other issues with the structure.
Copied almost straight from the d3 pie chart example here, I put in your data and it works a charm.
Here's the code, but I also strongly recommend going over the tutorial so you can see how it's supposed to work.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>pie ring</title>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script type="text/javascript">
var w = 600;
var h = 600;
var outerRadius=w/2;
var innerRadius=w/3;
color = d3.scale.category20c();
data = [{"label":"A", "value":5},
{"label":"C", "value":10},
{"label":"F", "value":13}];
var vis = d3.select("body")
.append("svg:svg")
.data([data])
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("transform", "translate(" + outerRadius + "," + outerRadius + ")")
var arc = d3.svg.arc()
.outerRadius(outerRadius)
.innerRadius(innerRadius);
var pie = d3.layout.pie()
.value(function(d) { return d.value; });
var arcs = vis.selectAll("g.slice")
.data(pie)
.enter()
.append("svg:g")
.attr("class", "slice");
arcs.append("svg:path")
.attr("fill", function(d, i) { return color(i); } )
.attr("d", arc);
arcs.append("svg:text")
.attr("transform", function(d) {
d.innerRadius = innerRadius;
d.outerRadius = outerRadius;
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.text(function(d, i) { return data[i].label; });
</script>
</body>
</html>
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.