How to move a point from d3 arc start to end without wrapping? - animation

I am attempting to use d3.js to move a point along an arc from 0 to PI, say, without the point moving back along the innerRadius as seen here http://bl.ocks.org/mbostock/1705868.
I removed innerRadius hoping (unsuccessfully) that would work (http://jsfiddle.net/klin/23c5476v/). I had also tried setting the innerRadius with the same value as outerRadius.
Fragment I changed (changes marked with //) ...
var path = svg.append("svg:path")
.datum({endAngle: Math.PI}) //
.attr("d", d3.svg.arc()
// .innerRadius(h / 4) // Hoping removal would prevent inner transition
.outerRadius(h / 3)
.startAngle(0)
);//.endAngle(Math.PI));
Entire code ...
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
var path = svg.append("svg:path")
.datum({endAngle: Math.PI}) //
.attr("d", d3.svg.arc()
// .innerRadius(h / 4) // Hoping removal would prevent inner transition
.outerRadius(h / 3)
.startAngle(0)
);//.endAngle(Math.PI));
var circle = svg.append("svg:circle")
.attr("r", 6.5)
.attr("transform", "translate(0," + -h / 3 + ")");
function transition() {
circle.transition()
.duration(5000)
.attrTween("transform", translateAlong(path.node()))
.each("end", transition);
}
transition();
// Returns an attrTween for translating along the specified path element.
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
The problem I think is that the arc shape has area, so the path must be closed, while the line shape does not. Eventually I'd like to be able to separately animate object movement along a series of consecutive arcs similar to the answer to Interpolating along consecutive paths with D3.js, but first I need to avoid the loop back movement.
Is a simple solution maybe to not use d3's arc generator, but instead use another where the end point actually is the terminus of the path?

Paul is right.
You can do next
var arc = d3.svg.arc(); //plus params
$path.attr('d',function(){
var d = arc();
return d.split('L')[0]; //will return half of arc without lines
});

Related

D3.js: How to Center svg map after Drag, Zoom, or MouseMove Events

My map projection and display goes awry when users drag, move the map (see: https://realtimeceap.brc.tamus.edu).
For example (duplicate event):
1. Select a field condition from dropdownlist.
2. Select a State from dropdownlist.
3. Select a County from dropdownlist.
4. Drag the map or move the mousewheel.
5. Then, select another State from dropdownlist. The map is not centered at the middle of the svg element and the scale is off, instead of scale 1.
Appreciate any help.
I reset the map on selecting a state as follows:
function resetMap() {
svg = d3.select("#svgMap2");
var w = 728;
var h = 500;
var project = d3.geoAlbersUsa()
.scale(1000)
.translate([w / 2, h / 2]);
var t = project.translate(); // the projection's default translation
var scale = project.scale;
//reset all features to original scale
d3.select("#svgMap2").select("#counties").selectAll(".county")
.transition()
.duration(750)
.style("stroke-width", "0.5px")
.style("stroke", "#808080")
.attr("transform", "translate(" + t[0] + "," + t[1] + ")scale(" + scale + ")");
}
Been working on this for a while and found the solution is so simple. I needed to reset the g element's transform attribute to null to redefine it. The id of my svg = svgMap2 and the id of my g element = counties. BTW, I'm using D3.js version 4.
function resetMap() {
var k = 1;
var t = [0, 0];
svg = d3.select("#svgMap2")
d3.select("#svgMap2").select("#counties").selectAll(".county").classed("active", false);
d3.select("#counties").attr("transform", null);
svg.select("#counties").selectAll(".county") //must use svg or it disables pan, zoom
.attr("width", width)
.attr("height", height)
.style("stroke-width", "0.5px")
.style("stroke", "#808080")
.attr("transform", "translate(" + t[0] + "," + t[1] + ")scale(" + k + ")");
}

D3 placing nodes labels inside circles in Mobile Patent Suits

I'm new to D3 and I'm using this template (http://bl.ocks.org/mbostock/1153292) to visualize my graph. However, my nodes' labels are shore and I want to place them inside circles not on their right hand-side.
Can anybody help me with that?
Thanks a lot
Append circle and text inside a group element and use text-achor property to align the text to the middle.
var nodes = svg.selectAll(".node")
.data(force.nodes())
.enter()
.append("g")
.attr("class","node")
.call(force.drag);
var circles = nodes.append("circle")
.attr("r", 6);
var texts = nodes.append("text")
.attr("text-anchor", "middle")
.text(function(d) {
return d.name;
});
Change tick function as shown below.
function tick() {
path.attr("d", linkArc);
nodes.attr("transform", transform);
}
Update
JSFiddle 1 for aligning text to the center.
If you would like enclose the text label within the circle, increase the radius of circle using the length of name property. Note that, you will have to update the link target positions this time, to adjust the marker positions relative to the circle radius.
var circles = nodes.append("circle")
.attr("r", function(d){ d.radius = d.name.length*3; return d.radius; }
);
function linkArc(d) {
var tX = d.target.x-d.target.radius,
dx = tX - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + tX + "," + d.target.y;
}
JSFiddle 2

D3 Geo Tile Baselayer Offset

I'm using d3.geo.tile() and have used it successfully before but this time the tile layer doesn't seem to draw at the same scale and translate as the point layer. The below code creates a map that pans and draws just fine, but draws the circles, which should be in the Mediterranean, in Africa. If I zoom in, it scales the tiles and circles just fine, it's as if my xy coordinates are off, but they aren't.
I get the feeling that it's actually drawing the base layer without offsetting and scaling it properly because it should be centering on the coordinates 12,42, but it's a great big mystery to me since this exact same code works fine in a different application.
If someone can spot some problem, or just a hint, that would help.
function createNewMap(){
width = 1200, height = 800;
var tile = d3.geo.tile()
.size([1200, 800]);
var projection = d3.geo.mercator()
.scale((1 << 12) / 2 / Math.PI)
.translate([width / 2, height / 2]);
var center = projection([12, 42]);
var zoom = d3.behavior.zoom()
.scale(projection.scale() * 2 * Math.PI)
.scaleExtent([1 << 10, 1 << 17])
.translate([width - center[0], height - center[1]])
.on("zoom", zoomed);
projection
.scale(1 / 2 / Math.PI)
.translate([0, 0]);
var svg = d3.select("#newMapId").append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom);
var raster = svg.append("g");
var vector = svg.append("g");
vector.selectAll("g").data(dataModule.polisData).enter().append("g")
.attr("class", "sites")
.attr("transform", function(d) {return "translate(" + (projection([d.xcoord,d.ycoord])[0]) + "," + (projection([d.xcoord,d.ycoord])[1]) + ")scale("+(projection.scale())+")"})
.append("circle")
.attr("class", "sitecirc");
zoomed();
function zoomed() {
var tiles = tile
.scale(zoom.scale())
.translate(zoom.translate())
();
var image = raster
.attr("transform", "scale(" + tiles.scale + ")translate(" + tiles.translate + ")")
.selectAll("image")
.data(tiles, function(d) { return d; });
image.exit()
.remove();
image.enter().append("image")
.attr("xlink:href", function(d) { return "http://" + ["a", "b", "c", "d"][Math.random() * 4 | 0] + ".tiles.mapbox.com/v3/elijahmeeks.map-zm593ocx/" + d[2] + "/" + d[0] + "/" + d[1] + ".png"; })
.attr("width", 1)
.attr("height", 1)
.attr("x", function(d) { return d[0]; })
.attr("y", function(d) { return d[1]; });
vector
.attr("transform", "translate(" + zoom.translate() + ")scale(" + zoom.scale() + ")");
d3.selectAll(".sitecirc")
.attr("r", 10 / zoom.scale());
}
Your code appears to be based on my example that changes the SVG transform on zoom. Changing the transform is a nice technique when you have complex geometry that you just want to scale and translate when you pan or zoom — it’s typically faster than reprojecting everything — but it’s also more complex than changing the projection on zoom.
The code doesn’t change very much if you want to change the projection on zoom. In essence:
projection
.scale(zoom.scale() / 2 / Math.PI)
.translate(zoom.translate());
And then re-run your d3.geo.path to re-render. As shown in bl.ocks.org/9535021:
Also, fixing the projection and changing the transform can cause precision problems if you zoom in a lot. Another reason to only use that technique when it offers substantial performance gains by avoid reprojection. And here reprojecting is super-cheap because it’s just a handful of points.

make a circle progress bar, but animate arc.endAngle (angle >180 degree) stops working in d3.js

I am running D3.js to draw a progress bar in circle shape, which you will see the demo on jsfiddle , the progress bar has a transition animation.
The main code is
var width = 960,
height = 500,
twoPi = 2 * Math.PI,
progress = 0,
total = 1308573, // must be hard-coded if server doesn't report Content-Length
formatPercent = d3.format(".0%");
var arc = d3.svg.arc()
.startAngle(0)
.innerRadius(0)
.outerRadius(240);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var meter = svg.append("g")
.attr("class", "progress-meter");
meter.append("path")
.attr("class", "background")
.attr("d", arc.endAngle(twoPi));
var foreground = meter.append("path")
.attr("class", "foreground");
foreground.attr("d", arc.endAngle(twoPi * 0))
foreground.transition().duration(1500).attr("d", arc.endAngle( twoPi * 2/3 ));
var text = meter.append("text")
.attr("text-anchor", "middle")
.attr("dy", ".35em");
to make the progress bar move, we only need to change to the arc.endAngle(), which is on the line.
foreground.transition().duration(1500).attr("d", arc.endAngle( twoPi * 2/3 ));
if the angle is less than 180, ( endangle < twoPi*1/2), then the animation works fine, but when the angle is larger than 180, so means endangle >= twoPi*1/2. then the animation would not show, and if you look at the console, you will find many errors on d3.js
Error: Problem parsing d="M1.1633760361312584e-14,-190A190,190 0 1.481481481481482e-7,1 -0.000022772330200401806,-189.9999883969182L0,0Z" meeting.html:1
2
Error: Problem parsing d="M1.1633760361312584e-14,-190A190,190 0 2.56e-7,1 -0.00003935058659476369,-189.99997994987467L0,0Z"
so what is the exact problem for this, how to solve it
It doesn't work because you can't use the standard transition for radial paths. By default, it simply interpolates the numbers without knowing what they represent, so in your case, you end up with some really small numbers (e.g. 1.1633760361312584e-14) which Javascript represents in exponential notation which is not valid for SVG paths.
The solution is to use a custom tween function that knows how to interpolate arcs:
function arcTween() {
var i = d3.interpolate(0, twoPi * 2/3);
return function(t) {
return arc.endAngle(i(t))();
};
}
Complete example here. You may also be interested in this example, which shows how to do it with data bound to the paths.

Arc Based Text Alignment in D3

I'm trying to create a Radial Hub and Spoke diagram (http://bl.ocks.org/3169420) with D3 that uses arcs of a pie to represent relationships to and/or from a node of context. In other words, I'm trying to create a 360 degree view of a node's relationships.
In the example above, "Node 1" is in the center. I can't get the textual node names for related nodes #8 - #18 to align "outside" the wheel. Would anyone know what the best way is to do so?
The code that I'm using looks as follows...
// Add node names to the arcs, translated to the arc centroid and rotated.
arcs3.filter(function(d) {
return d.endAngle - d.startAngle > .2; }).append("svg:text")
.attr("dy", "5px")
.attr("angle", function(d) { return angle(d); })
.attr("text-anchor", function(d) { return angle(d) < 0 ? "start" : "end"; })
.attr("transform", function(d) { //set text'ss origin to the centroid
// Make sure to set these before calling arc.centroid
d.innerRadius = (height/2); // Set Inner Coordinate
d.outerRadius = (height/2); // Set Outer Coordinate
return "translate(" + arc3.centroid(d) + ")rotate(" + angle(d) + ")";
})
.style("fill", "Black")
.style("font", "normal 12px Arial")
.text(function(d) { return d.data.name; });
// Computes the angle of an arc, converting from radians to degrees.
function angle(d) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
return a > 90 ? a - 180 : a;
}
The issue seems to be that I need to evaluate the X and Y coordinates of where the Node labels are placed. However, I can't seem to correctly find/access those X and Y values.
Thanks, in advance, for any help you can offer.
My Best,
Frank
Thanks to Ger Hobbelt for this... In short, the angle function needs to be parameterized:
function angle(d, offset, threshold) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI + offset;
return a > threshold ? a - 180 : a;
}
The complete answer can be found d3.js Google Group: https://groups.google.com/forum/?fromgroups#!topic/d3-js/08KVfNmlhRo
My Best,
Frank

Resources