Display only line intersections of graticule - d3.js

I'm trying to only display the line intersections within graticule and not the whole lines within a D3 map.
Is there any support to do that with d3.geo.graticule?
Thanks very much for your help!
Thierry

The short answer to your question is: no, there is no way to only render the intersections with d3.geo.graticule.
But the desired result could be achieved with little effort by putting the intersections you want to be drawn into a helper array which is passed to a path generator for rendering. I forked one of my Plunks to demonstrate the solution:
Set up a helper array containing geometry objects to be passed to the path generator.
// Generate two-dimensional array containing the coordinates of all
// intersections we are interested in.
var lonLatIntersections = d3.range(-180, 180, 10).map(function(lat) {
return d3.range(0, 360, 10).map(function(lon) {
// Return geometry objects that can be handled by the path generator.
return {type: "Point", coordinates: [lon,lat]};
});
});
Use a nested selection to bind the two-dimensional (lat/lon) array and use the enter selections to append the intersection points. The actual drawing is done by supplying the geometry objects to the path generator which will take into account the projection.
// Do a nested selection to bind the two-dimensional data.
map.selectAll("g.lat")
.data(lonLatIntersections)
.enter().append("g") // Create groups per latitude.
.classed("lat", true)
.selectAll("circle")
.data(function(d) { return d; }) // Data binding for longitudes
.enter().append("path")
.attr({
"d": path, // Use the path generator to draw points
"class": "confluence"
});

Related

Multiple maps with d3.js: change values of scale and center

I’m building a (d3 v4) cartographic visualization which allows the user to switch between many datasets (json files) and two different regions (administrative units of a country and smaller administrative units into its capital city). Actually the switch from one to another dataset on the initial country level works well, through buttons and jquery.
Problem: it’s a bit less convincing when switching to a map/dataset about the capital city, as the projection is initially set for the whole country and consequently the user has to zoom many times to visualize properly the map of the capital city. I would like to change the values of .scale and .center when calling the projection but after several trials I haven’t found how to do it.
As I only have two different regions to show, my intuition was to set first values of scale and center and to change them to other values (I know the values of .scale and .center I would like to use in both cases) when the user switches to a map of the capital city through a function. Is there any possibility to switch easily these values? Do you have any suggestion to solve this problem?
As I load the json file path into a function when the user clicks on the button to switch to another dataset, I was trying to load the value of scale the same way but I’m probably doing wrong. It seems that the part of the code about the projection can't be put in a function?
Thanks for your help!
Small part of my code:
var width = 1100, height = 770;
var projection = d3.geoConicConformal()
.scale(19000) // value I would like to which when the region changes
.center([4.45, 50.53]) // value I would like to which when the region changes
.translate([width/2,height/2]);
var svg = d3.select( "#mapcontainer" )
.append( "svg" )
.attr("width", width)
.attr("height", height)
.style("border", "solid 1px black");
var path = d3.geoPath()
.projection(projection);
var color, jsonfile, legendtext;
function load (jsonfile, legendtext, color) {
d3.selectAll(".currentmap").remove() ;
d3.json(jsonfile, function(error, belgique) {
g.selectAll("path")
.data(belgique.features)
.enter()
.append("path")
.attr("d", path)
.style("stroke", "#fff")
.attr( "class", "currentmap")
.style("fill", function(d) {
var value = d.properties.DATA;
if (value) {return color(value);}
else {return "rgb(250,110,110)"}
});
})
};
//one of the following function for each map
function BGQprovinces() {
jsonfile = "ATLAS/NewGeoJson/bgq-data1-provinces.json";
legendText [= …];
color = d3.scaleOrdinal()
.domain( […])
.range([…]);
load(jsonfile, legendtext, color) ;
};
;
There area few approaches to accomplish this.
fitSize and fitExtent
One is to modify the projection scale and translate as opposed to scale and center. This is nearly the same operation, but translate pans the projected plane and center will pan the unprojected plane. To do so you need to use projection.fitSize([width,height],geojsonObject), or projection.fitExtent([[x0,y0],[x1,y1]],geojsonObject). The latter will allow margins of say, the first coordinate provided is the top left and the second coordinate provided is the bottom right of a bounding box in which the feature will be constrained.
d3.json(jsonfile, function(error, belgique) {
projection.fitSize([width,height], belgique);
// now draw as you would:
d3.selectAll(".currentmap").remove() ;
g.selectAll("path")
.data(belgique.features)
.enter()
.append("path")
.attr("d", path)
...
Note that for showing all of a country you need to have a feature that shows the whole country or a feature collection that shows all the parts of a country. You cannot use an array with fitSize or fitExtent, if you have an array of features, you can create a feature collection by using:
var featureCollection = {"type":"featureCollection","features":featureArray}
For your case, I'd suggest using fitSize or fitExtent.
centroid
If you really wanted to modify the center attribute as opposed to translate, or perhaps you want to change the rotation (a more likely outcome for conic conformals in many parts of the world, Belgium should be fine), then you need the geographic coordinates of the center. One way of a handful to do this is to get the centroid of a feature from path.geoCentroid:
var centroid = path.geoCentroid(geojsonObject);
Then use that to set the projection parameters to rotate:
projection.rotate([ -centroid[0],-centroid[1] ])
projection.center([0,0])
or to center:
projection.rotate([0,0])
projection.center(centroid)
Or a combination of both (depending on map projection type). Now you can apply fitSize or fitExtent, the feature is in the middle already, but now we can set the scale. The reason I suggest this as a potential answer is because not all projections, concic projections in particular, will give desired results by modifying only scale along with translate and/or center.
Of course for conic projections, you may need to find a way to set the parallels as well, but I'll leave that for another answer if it ever comes up.

in d3.geo MultiPoint how can I provide different shapes for different poins?

I have some geoJson data that I am charting using d3.geo.
When I write something like
d3.select("svg")
...
.attr("d", function(d) {
return path({
type:"MultiPoint",
coordinates: get_activity_coords_(d.activities)
});
})
I always get a circle for each coordinate. The coordinates represent locations of various stopping points of a journey. What I would prefer is a different shape for the first and the last coordinate.
Is it possible to do this using MultiPoint, is there an example that I can follow? I could draw the points one by one, but I recall reading that MultiPoint is far faster. Plus, the code would be much clearer to read.
Thanks a lot.
You can't do different shapes for MultiPoint geoJSON with d3.geo.path. You can change the radius based on a function, but it looks like you can only set it per feature and not per point, so you'd have to break your set of points into multiple features and lose any performance benefit from using the single element.
However, there are other ways to go about doing this.
One option, as you mentioned, is to create a nested selection with a separate <path> element for each point, and draw each path using a d3.svg.symbol() function. You can then customize the symbol function to be based on data or index.
var trips = d3.select("svg").selectAll("g.trips")
.data(/*The data you were currently using for each path,
now gets to a group of paths */)
.attr("class", "trips");
//also set any other properties for the each trip as a whole
var pointSymbol = d3.svg.symbol().type(function(d,i){
if (i === 0)
//this is the first point within its groups
return "cross";
if ( this === this.parentNode.querySelector("path:last-of-type") )
//this is the last point within its group
return "square";
//else:
return "circle";
});
var points = trips.selectAll("path")
.data(function(d) {
return get_activity_coords_(d.activities);
//return the array of point objects
})
.attr("transform", function(d){
/* calculate the position of the point using
your projection function directly */
})
.attr("d", pointSymbol);
Another option, which allows you to set custom shapes for the first and last point (but all intermediary points would be the same) is to connect the points as the vertices of a single, invisible <path> element and use line markers to draw the point symbols.
Your approach would be:
Create a <defs> element within your SVG (either hard-coded or dynamically with d3), and define the start, middle and end marker points within them. (You can use d3.svg.symbol() functions to draw the paths, or make your own, or use images, it's up to you.)
Use a d3.svg.line() function to create the path's "d" attribute based on your array of point coordinates; the x and y accessor functions for the line should use the projection function that you're using for the map to get the x/y position from the coordinates of that point. To avoid calculating the projection twice, you can save the projected coordinates in the data object:
var multipointLine = d3.svg.line()
.x(function(d,i) {
d.projectedCoords = projection(d);
return d.projectedCoords[0];
})
.y(function(d){ return d.projectedCoords[1];});
(You can't use your d3.geo.path() function to draw the lines as a map feature, because it will break the line into curves to match the curves of longitude and latitude lines in your map projection; to get the line markers to work, the path needs to be just a simple straight-line connection between points.)
Set the style on that path to be no stroke and no fill, so the line itself doesn't show up, but then set the marker-start, marker-mid and marker-end properties on the line to reference the id values of the correct marker element.
To get you started, here's an example using d3 to dynamically-generate line markers:
Is it possible to use d3.svg.symbol along with svg.marker

Venue/Indoor Map using D3.js and Geojson

I have created geojson file which contains all the features of 1st floor of a shopping mall. I got that venue map projected using d3.js with different colors but only some parts not the complete map. Below is the script code and link to the geojson file. Also please note that i have not converted this geojson into topojson and used Qgis to draw the maps and c#.net to convert the geometry data to geojson objects. Can anyone please check my json and my d3.js code? Do I need to use any other projections?
https://www.dropbox.com/s/8pu2s0yamfkd89p/JSONfromDB_8Feb2014.json
$(document).ready(function () {
parseResultShopDetails();
});
function parseResultShopDetails() {
var width = 600, height = 300;
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height);
var projection = d3.geo.mercator()
.scale(30)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
d3.json("http://localhost:1209/data/JSONfromDB_8Feb2014.json", function (error, jsonData) {
var color1 = d3.scale.category10();
svg.selectAll("path")
.data(jsonData.features)
.enter()
.append("path")
.attr("d", path)
.attr("text", function (d, i) { return "js"; })
.attr("fill", function (d, i) { return color1(i); });
});
}
It looks like the d3 mapping tools really fall apart if you try to use coordinates other than longitude and latitude.
I tried creating a "null" projection that just returns the input values, but the negative numbers and numbers greater than 360 were still getting wrapped by d3 before passing to the projection function. That avoids the trig errors from the Mercator projection, and it creates interesting art, but not the floor plan you were hoping for:
var projection = d3.geo.projection(function(λ, φ) {
return [ λ, φ ];
});
http://fiddle.jshell.net/rR2hG/1/
However, all is not lost. The second image in that example is created by just passing the array of coordinates as the points of <polygon> elements. I think that's closer to what you wanted. So you'll need to do a little more work to grab the points from the data file but you can definitely visualize them just as an array of coordinates.
svg2.selectAll("polygon")
.data(jsonData.features)
.enter()
.append("polygon")
.attr("points", function(d){ return d3.merge(d.geometry.coordinates);})
.attr("fill", function (d, i) {
return color1(i);
});
The only other suggestion is to write a script to convert your geoJSON file to geographic units. They don't have to be actual latitude and longitude of a particular place (you could still have the map centered on a reference point of your choice), but the scale has to be in degrees not feet or meters or whatever you are using.
D3's mapping projections are designed to transform 3D earth coordinates into 2D browser coordinates, so they are not that great at transforming local coordinates like the ones you've got. And as Amelia outlines your putting in coordinates that are outside of what's expected.
You'd be better off doing one of two things; creating a geometry stream based on 2 linear scales as outlined in this google groups discussion; or using d3's path generators.
To creating a 2D path generator is straightforward in d3 something like this will work:
var shops = d3.svg.line()
.interpolate("linear")
.x(function(d) {
return xScale(d.x);
})
.y(function(d) {
return yScale(d.y);
})
The real trick here is accessing the 'right' part of your json object. If you look into the geojson structure you see that there is a geometry part as well as an properties part. You need to dig through to pull out the coordinates and then pass them to the pavement generator. In this case it would be:
d.geometry.coordinates
which would obviously need to be referenced correctly.
Note that the method outlined here isn't going to work if you have complex geometries such as multi-polygons, you'll need to do quite a bit more work. If that's what you've got you'll want to create a custom geometry stream.
Now putting all of that together here's a working example of you're json.

Adding svg:circle elements to a d3.js line

I'm rendering a line chart via d3, which is bound to an array of objects of the following format:
{ name: "somename",
pointStart: 90210,
pointInterval: 187,
data: [1,2,3,4,5]
}
The Y values are the values in data, and the X values is a sequence of Date values, calculated by adding pointStart to the product of pointInterval and the index of data
In addition to plotting the line path, I'm trying to overlay "circles" at each x,y coordinate. The line renders properly, and all but the first circle shows up.
Check this plunkr for the live example.
Since the line path already has the x,y coordinates, I was hoping to use that, and draw the circle on each pair, however the first circle coordinate isn't found and I'm not sure why.
This is the code that selects the line array, gets the x,y pairs, and then draws the circle.
The data is bound to a 9 element array, but only 8 circles are added to the dom...
lines.selectAll('path')
.data(function(d) { console.log(getDataArray(d)); return getDataArray(d); })
.enter()
.append('circle')
.attr({
class:'dot',
cx:line.x(),
cy:line.y(),
r:circleR,
fill: function(d,i,e) { return colors(data[e].name);}
})
It's because you're selection for "path" but adding "circles". When you do lines.selectAll('path') it returns a selection that contains 1 element because there is already a <path> element under lines. So when you do the data bind with 9 elements, the first element get bound to the existing path leaving the remaining 8 elements for the enter selection.
If you change it to this it should work:
lines.selectAll('circle')
.data(function(d) { console.log(getDataArray(d)); return getDataArray(d); })
.enter()
.append('circle')

Is it possible to create pie charts with object consistency?

the pie chart update example on the bl.ocks site doesn't update the elements 'in place':
http://bl.ocks.org/j0hnsmith/5591116
function change() {
clearTimeout(timeout);
path = path.data(pie(dataset[this.value])); // update the data
// set the start and end angles to Math.PI * 2 so we can transition
// anticlockwise to the actual values later
path.enter().append("path")
.attr("fill", function (d, i) {
return color(i);
})
.attr("d", arc(enterAntiClockwise))
.each(function (d) {
this._current = {
data: d.data,
value: d.value,
startAngle: enterAntiClockwise.startAngle,
endAngle: enterAntiClockwise.endAngle
};
}); // store the initial values
path.exit()
.transition()
.duration(750)
.attrTween('d', arcTweenOut)
.remove() // now remove the exiting arcs
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}
Instead, it just treats the new array of value as brand new data and resizes the chart accordingly.
I've created a fiddle demonstrating the issue very simply:
http://jsfiddle.net/u9GBq/23/
If you press 'add', it add a random int to the array: this works as intended.
If you press 'remove', the only element getting transitioned out is always the last element to have entered the pie. In short, it behaves like a LIFO stack.
The expected behaviour is for the relevant pie arc to get transitioned out instead.
Is it possible to apply object consistency to pies? I've also tried adding a key function (not demonstrated on the fiddle) but that just breaks (oddly enough it works fine with my stacked graphs).
Thank you.
The easiest solution to this problem is to set missing values to zero, rather than removing them entirely, as in Part III of the Pie Chart Update series of examples. Then you get object constancy for free: you have the same number of elements, in the same order, across updates.
Alternatively, if you want a data join as in Part IV, you have to tell D3 where the entering arcs should enter from, and where the exiting arcs should exit to. A reasonable strategy is to find the closest neighboring arc from the opposite data: for a given entering arc, find the closest neighboring arc in the old data (pre-transition); likewise for a given exiting arc, find the closest neighboring arc in the new data (post-transition).
To continue the example, say you’re showing sales of apples in different regions, and want to switch to show oranges. You could use the following key function to maintain object constancy:
function key(d) {
return d.data.region;
}
(This assumes you’re using d3.layout.pie, which wraps your original data and exposes it as d.data.)
Now say when you transition to oranges, you have the following old data and new data:
var data0 = path.data(), // retrieve the old data
data1 = pie(region.values); // compute the new data
For each entering arc at index i (where d is data1[i]), you can step sequentially through preceding data in data1, and see if you can find a match in data0:
var m = data0.length;
while (--i >= 0) {
var k = key(data1[i]);
for (var j = 0; j < m; ++j) {
if (key(data0[j]) === k) return data0[j]; // a match!
}
}
If you find a match, your entering arcs can start from the matching arc’s end angle. If you don’t find a preceding match, you can then look for a following matching arc instead. If there are no matches, then there’s no overlap between the two datasets, so you might enter the arcs from angle 0°, or do a crossfade. You can likewise apply this technique to exiting arcs.
Putting it all together, here’s Part V:
Ok, found the solution.
The trick was to pass the key this way:
path = path.data(pie(dataset), function (d) {return d.data}); // this is good
as opposed to not passing it, or passing it the wrong way:
path = path.data(pie(dataset, function (d) {return d.data})); // this is bad
And here's an updated fiddle with a working transition on the right arc! :)
http://jsfiddle.net/StephanTual/PA7WD/1/

Resources