I have code like:
d3.json("topo-census-regions.json", function(error, topoJ) {
g.append("path")
.datum(topojson.mesh(topoJ, topoJ.objects.divis, function(a, b) { return a !== b; }))
.attr("class", "division-borders")
.attr("d", path);
});
It works, basically. (This is pretty much straight from Bostock's zoom demo, just using a different source file.)
My problem is that the boundaries between census regions render out as a single SVG path. TopoJson's mesh method collapses all shared internal boundaries into a single compound path. But I need to render different parts of the path with different styles.
For a visual reference, see this.
The boundary between "Pacific" and "Mountain" within the "Western" division should be one path element, the boundary between "West North Central" and "East North Central" should be another, etc. I could manufacture these paths manually in Illustrator fairly easily but need to be able to do this programmatically across a large data set. I want the efficient de-duplication that mesh performs, but with continuous segments as separate elements.
Thanks in advance for any help.
Related
I come back for a problem. I'm still learning d3js for 5 days and I have a problem about the border rendering.
Why I have that ?
The problem occurs when I add region borders.
svgMap.append("path")
.attr("class", "reg_contour")
.datum(topojson.mesh(fr[0], fr[0].objects.reg_GEN_WGS84_UTF8, function(a, b) { return a !== b; }))
.attr("d", path);
Here is my code : https://plnkr.co/edit/mD1PzxtedWGrZd5ave28?p=preview
Just for the record, the json file combines two layers (departments and regions) created with the same shp and compiled by QGIS for geojson output. After I convert this to topojson by mapshapper.
The error lies in your topojson - the two different feature types, departments and regions do not share the same coordinates along their common boundaries.
First, in this sort of situation it is desirable to check to make sure there is no layering issue (often other features are drawn ontop of others, hiding them or portions of them), we can do this by just showing the regional boundaries:
(plunkr)
So the problem can't be layering, if we look at a particular feature in the topojson, say the department of Creuse:
{"arcs":[[-29,-28,-27,-26,202,-297,-296,205,-295,-410,419]],"type":"Polygon","properties":{"codgeo":"23","pop":120581,"libgeo":"Creuse","libgeo_m":"CREUSE","codreg":"75","libreg":"Nouvelle Aquitaine"}}
We see that the department is drawn using 11 arcs representing each portion of the boundary based on shared boundaries between multiple features so that shared boundaries are only represented once in the data.
If we zoom in on Creuse we can see those 11 arc segments shared between either other departments, regions, or with nothing at all:
The thick portions of the boundary correspond to the thick white boundaries in the image in the question, I've only changed the styling and zoom from the original plunkr
This looks problematic, the department should only have 6 arcs:
Why are there additional arcs? Because the boundaries of the departments are not aligned properly - the shared boundaries between departments do not always share the same arcs in your topojson. Chances are the departments might use a different scale than the regions, a different precision or projection, or were made somehow differently. This has resulted in minute nearly imperceptible differences that have resulted in boundaries that share coordinates in reality not sharing the same coordinates in the data, and thus the shared arcs are going unrecognized.
Since you are generating the mesh like this:
topojson.mesh(fr[0], fr[0].objects.reg_GEN_WGS84_UTF8, function(a, b) { return a !== b; })
Only shared boundaries are drawn, which explains the gaps.
We can rectify this a few ways, the easiest way would be to remove the regions altogether. The departments record which region they are in, we can check to see if a boundary should be drawn if the departments on each side of that boundary are in different regions:
.datum(topojson.mesh(fr[0], fr[0].objects.dep_GEN_WGS84_UTF8, function(a, b) { return a.properties.libreg !== b.properties.libreg; }))
Which gives us:
(plunkr)
Alternatively, we can re-habilitate the regional boundaries by importing them into a GIS platform such as ArcGIS and repairing the geometry. We could also import the departments and make a new layer based on region properties in a dissolve. Using the repair geometry tool in Arc, I get a nice boundary (when shown with the same code as the first image here):
There are other methods, such as including a tolerance in aligning arcs, but these might be more difficult than the above.
A couple of months ago, I tried combining Hierarchical Edge Bundling and Radial Reingold–Tilford Tree using d3.js
I started from the HEB and tried to make it into a tree.
Things have not worked out the way I wanted, and I realized it might be better to start from a collapsible radial tree (not Reingold Tilford), with a different angle.
Here is a JSFiddle of the radial tree
The data model has also changed, as elements now have a name, children and imports (links).
var flare =
{
"name": "root",
"children": [
{
"name": "test1.parent1","children": [
{"name": "test1.child11","children": [
{"name": "test1.child111"},
{"name": "test1.child112"}
]}
],"imports": ["test2.parent2","test3.parent3","test4.parent4"]
},
{
"name": "test2.parent2","children": [
{"name": "test2.child21"},
{"name": "test2.child22"},
{"name": "test2.child28","children":[
{"name": "test2.child281"},
{"name": "test2.child282"}
]}
],"imports": ["test3.parent3"]
},
{"name": "test3.parent3","imports": ["test4.parent4"]},
{
"name": "test4.parent4","children": [
{"name": "test4.child41"},
{"name": "test4.child42"}
]
}
]
};
To start slowly, I would like to combine the non-interactive Hierarchical edge bundling from Mike Bostock with the current JSFiddle, but keeping in mind that the interactions will be part of it later on.
Also, only the first level has to have links (parent-parent link) as shown below (the result that I want):
My current biggest issue is that the HEB has no "root", but the tree starts with a single item. So everything I have tried so far has led to a big mess at the center of the tree.
Note that there is a circle at the center of the tree to cover the root to level 1 links, so the tree starts at level 1 (parents).
var circle = svg.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", diameter - 725.3)
.style("fill", "#F3F5F6")
.style("stroke-width", 0.2)
.style("stroke", "black");
Ideally, the links between parents have to update when a level is (un)collapsed, like it does for the nodes and the links between levels, but that can come later and might not be that difficult after initially getting the first level links to show. Also, the data template might change if necessary, but all 3 pieces of information are important (name, children and imports).
Another way to do this would be to be able to change the data to not include the root part, and that it behaves exactly as it does now
Partial answers are also welcome.
I have managed to add links between elements in this JSFiddle using parts of the code found in the original hierarchical edge bundling by Mike Bostock and added them to radial version of the collapsible tree. However, they do not update when collapsing or expanding children!
var bundle = d3.layout.bundle();
var line = d3.svg.line.radial()
.interpolate("bundle")
.tension(.85)
.radius(function(d) { return d.y; })
.angle(function(d) { return d.x / 180 * Math.PI; });
then in update(source):
var middleLinks = packageImports(root);
svg.selectAll("path.middleLink")
.data(bundle(middleLinks))
.enter().append("path")
.attr("class", "middleLink")
.attr("d", line);
The "packageImport" function can be found at the bottom.
function packageImports(json) {
var map = {},
imports = [];
console.log(json.children);
// Compute a map from name to node.
json.children.forEach(function(d) {
console.log(d.name)
map[d.name] = d;
});
// For each import, construct a link from the source to target node.
json.children.forEach(function(d) {
if (d.imports) d.imports.forEach(function(i) {
imports.push({source: map[d.name], target: map[i]});
});
});
console.log(imports);
return imports;
}
My current biggest issue is that the HEB has no "root", but the tree starts with a single item. So everything I have tried so far has led to a big mess at the center of the tree.
Given that you have a root, why not set that as the center "node" and make it a radial tree, like this? Here is another example, but with multiple roots.
That said, you did not ask for a radial tree, even if it sounds like that would answer your question. If you are determined to keep things arranged in circles of varying radium, maybe the problem to be solved is that of the criss-crossing of the lines looking so "messy". If that is the case, then a different sorting algorithm might do the trick. Here is an article that expands on using different sorting algorithms in a similar scenario with D3.js. D3 layout algorithms are also open source online.
The best resource I was able to find for you was this Stack Mathematics Answer. A few points especially stand out for me in it:
You can have a total mess of connections/branches, but if it's interactive and the branches to and from a specific node light up when you hover over that node, then it can still be a readable/usable graph.
You can try different sorting algorithms until you find one that works well with your data.
You can have one or more nodes inside the circle, and in this case putting the root node in the middle might make a lot of sense.
It is not a practically usable answer, since it's in Mathematica, but I think its explanation of the underlying theories and techniques is useful.
A perhaps more artistic approach, if you do not want to put the root of the tree in the center, would be to make all branches from parents to the root parent be semi-transparent, and possibly a different color.
Hope this helps!
I am trying to optimize a little bit the SVG I am generating using the amazing D3js geo module.
I am using d3.geo.path as the d attribute generator for SVG paths:
path = d3.geo.path().projection(config.projection); // projection is initialized somewhere else
and then using it to render paths like this:
svg.selectAll(".country")
.data(countries)
.enter()
.insert("path", ".graticule")
.attr({
class: "country",
d: path
})
and the kind of path strings I am getting are like this one:
M713.601085,459.8780053259876L714.7443994399441,460.08170562468473L715.0310281028103,460.5903728431771L715.0310281028103...
As can be seen, some numbers are extremely long with too many decimals and not really useful at my current resolution, which clogs a little bit the DOM and makes it sluggish to debug these paths (there are also many, since it's a world map with lots of countries drawn on it).
So my first approach was creating this pathSimplified wrapper for path:
// Path simplified: take some decimals out of the 'd' string
pathSimplified = function (d) {
return path(d).replace(/(\.\d{4})\d+/g, '$1');
};
and then use it instead of path:
//...
.attr({
class: "country",
d: pathSimplified
})
That works and now I get only 4 decimals for each value on the path string. Like this:
M713.6010,459.8780L714.7443,460.0817L715.0310,460.5903L715.0310...
My question is: can it be done in a better, less hackish way? It doesn't feel right to tweak the string after it's spit out by D3js's path function and it would be nice to specify the rounding at some point on the path itself or the projection...
Your idea is good, simplifying a polyline helps, but you can certainly do it better than just truncating coordinates: algorithms for polyline semplification exist and might give you nicer results.
For example, take a look at the Ramer-Douglas-Peucker algorithm. It is implemented in libraries like Simplify.js(check the demo on that page out), which also allows you to adjust it with tolerance.
You'll have to pass the line as an array of x/y coords, but I trust that isn't too complicated to do.
I also noticed that d3 geo projections allow for a precision parameter (like here), maybe there's something like that that you overlooked? In any case, simplifying coordinates on your own with simplify.js is usually always possible.
I'm a newcomer to D3 and I'm trying to make a world globe with some points ("pins") on it. Demo here: http://bl.ocks.org/nltesown/66eee134d6fd3babb716
Quite commonly, the projection is defined as:
var proj = d3.geo.orthographic()
.center([0, 0])
.rotate([50, -20, 0])
.scale(250)
.clipAngle(90)
.translate([(width / 2), (height / 2)]);
the clipAngle works well for the svg paths, but not the pins (which are svg circles). As you can see on the demo, the pin that sits between Iceland and Greenland should be hidden (it's Taiwan).
So I suppose the problem comes from these lines, but I can't understand why:
.attr("transform", function(d) {
return "translate(" + proj([ d.lng, d.lat ]) + ")";
});
It is not sufficient to just set the clipping radius via clipAngle() to get the desired behavior. The projection alone will not do the clipping, but just calculate the projected coordinates without taking into account any clipping. That is the reason, why Taiwan gets rendered, although you expected it to be hidden.
But, thanks to D3, salvation is near. You just need to re-think the way you are inserting your circles representing places. D3 has the mighty concept of geo path generators which will take care of the majority of the work needed. When fed a projection having a clipping angle set, the path generator will take this into account when calculating which features to actually render. In fact, you have already set up a proper path generator as your variable path. You are even correctly applying it for the globe, the land and the arcs.
The path generator will operate on GeoJSON data, so all you need to do is convert your places to valid GeoJSON features of type Point. This could be done with a little helper function similar to that used for the arcs:
function geoPlaces(places) {
return places.map(function(d) {
return {
type: "Point",
coordinates: [d.lng, d.lat]
};
});
}
With only minor changes you are then able to bind these GeoJSON data objects to make them available for the path generator which in turn takes care of the clipping:
svg.selectAll(".pin") // Places
.data(geoPlaces(places))
.enter().append("path")
.attr("class", "pin")
.attr("d", path);
Have a look at my fork of your example for a working demo.
I'm learning D3 and my toy application is a visualizer for a 2-dimensional gravity simulation using Symplectic Velocity Verlet Integration.
I have had quite some success animating and drawing a path using bound structured data, but it seems to me that D3 isn't really designed for attaching data to more than one element. It sort of assumes that for any given data that there is a clear and simple SVG element that should own it, and this is evident in the direct storage of data within the __data__ property inside the DOM.
It's not clear, though, the proper way to represent a datum with more than one SVG element. For instance, I'd really prefer to draw a path and a circle for each planet, the path traces its past position (and can have a bunch of clever line-length and color interpolation applied), and the circle plots its current position.
I can even come up with a few more elements I might want to draw: A vector-arrow for velocity... A vector-arrow for acceleration...
In my case, my master data structure is constructed like this, and is dynamically maintained in this structure:
var data = [];
function makeParticle(x, y, vx, vy) {
// state vector plus goodies
return [
x, y,
vx, vy,
0, 0,
[] // path
];
}
data.push(makeParticle(400, 100, -0.5, 1));
data.push(makeParticle(300, -100, 0.5, 2)); // and so on
Each element of data is a planet, which contains its current state vector (position, velocity, and cached acceleration (needed for the integrator)) as well as its path history which is an array of positions which is kept truncated to some reasonably large length.
Currently I am updating the path history like this:
var paths = d3.select('svg').selectAll("path").data(data);
paths.enter().append('path'); // put cool transitions here
paths.exit().remove();
paths.attr("stroke", "red")
.attr("d", function(d){
return lineDrawer(d[6]);
})
This works fine, each path tracks its own copy of its own planet's path.
It's not immediately clear to me how to extend this elegantly to include my circle at the head of each path. I certainly do not want to duplicate the entire datum into the circle's DOM element (as the path data is simply not necessary for it).
Edit in light of my self-answer:
I am hoping that someone can help to elucidate a way to use "groups" to make data-driven the set of things to draw for each datum. e.g. [position, velocity, force, path] as data where they are visualized using, respectively, a circle, an arrow closed path, an arrow closed path, and an open path. It is also possible that this is completely overthinking it because these properties are sort of fixed.
I guess in the process of thinking the problem through, it's become clear to me that all I have to do is filter out the data, so that I only attach the position state data to selectAll('circle.planet') rather than the full datum value which includes the massive path history array. This way it gets exactly the data it is responsible for displaying.
It seems like I have to do some more reading about subselections (I'm mostly puzzled by why (or if) the subselections are limited to two dimensions of hierarchy), but this seems completely reasonable. It seems logical, if I want to draw 4 items for each datum, I just have to somehow "assign" the correct subsets of my datum's structure to each SVG visualizer element.