This is the Feature
var singaporeJSON = {"type":"FeatureCollection","geocoding":{"creation_date":"2016-01-09","generator":{"author":{"name":"Mapzen"},"package":"fences-builder","version":"0.1.2"},"license":"ODbL (see http://www.openstreetmap.org/copyright)"},"features":[{"properties":{"name:display":"Singapore","name":"singapore"},"geometry":{"type":"MultiPolygon","coordinates":[[[[104.5706735,1.4419380999999996],[104.55258289999996,1.4106225999999995],[104.3891305,1.3172011999999995],[104.3488465,1.3331733],[104.35531379999998,1.3563739000000001],[104.37827119999996,1.4102812999999996],[104.4633395,1.4991501],[104.48455620000003,1.5130449],[104.486389,1.5123502],[104.4986883,1.5064215999999995],[104.52192920000003,1.4921171000000004],[104.5327622,1.4838079999999998],[104.5430018,1.4747782999999997],[104.5526002,1.4650701999999998],[104.5706735,1.4419380999999996]]],[[[104.12611109999997,1.28925],[104.12517559999996,1.2758195999999995],[104.11490400000002,1.2765354],[104.0927257,1.2733869],[104.0333333,1.2694999999999996],[103.9184908,1.2226474],[103.88074999999998,1.20725],[103.85983329999998,1.1959722],[103.80499999999999,1.1714444],[103.74069440000004,1.1303611],[103.67072219999996,1.1794444],[103.66069440000003,1.1881667],[103.57233329999997,1.1987500000000004],[103.56666670000003,1.1955],[103.60286109999997,1.2641666999999996],[103.6170833,1.3154166999999997],[103.6178333,1.3216111],[103.6300556,1.3410556],[103.64919440000003,1.3799166999999997],[103.65297219999995,1.3870833],[103.6535,1.3912778000000001],[103.6571667,1.4004166999999996],[103.6639167,1.4104721999999998],[103.6694444,1.4157499999999996],[103.67388889999995,1.4281389],[103.68333329999997,1.43725],[103.69405560000003,1.4398889],[103.69886109999997,1.4433055999999995],[103.70375,1.4507778],[103.71411110000003,1.4574721999999998],[103.72833329999997,1.4601389],[103.74677779999998,1.4503889],[103.76069440000003,1.4483889],[103.7711111,1.4527778],[103.7904722,1.4651389],[103.80366670000002,1.4765],[103.81266670000002,1.4784722],[103.8221667,1.4766943999999993],[103.83416670000003,1.4729999999999996],[103.85199999999999,1.4661666999999996],[103.8586667,1.4629443999999996],[103.8649722,1.4588610999999996],[103.8681667,1.4565278],[103.88613889999996,1.4351388999999997],[103.8978611,1.4279443999999992],[103.91275,1.4275277999999996],[103.91683330000002,1.4266666999999993],[103.93341670000002,1.4304166999999997],[103.93769440000005,1.4304721999999999],[103.94252780000002,1.4278332999999992],[103.96066670000003,1.42475],[103.96680560000003,1.4221666999999996],[103.9835833,1.4242778],[103.98616670000003,1.4249166999999996],[103.99525000000003,1.4236943999999991],[104.00286109999998,1.4206111],[104.0233056,1.4391388999999997],[104.0408333,1.4438889],[104.05747219999996,1.4398610999999992],[104.07119440000002,1.4346389],[104.07647219999996,1.4309166999999996],[104.08847219999996,1.4175832999999995],[104.09075,1.4124443999999996],[104.09266670000002,1.4059443999999992],[104.0930278,1.3998055999999999],[104.09247219999996,1.3942500000000004],[104.0835833,1.3687500000000001],[104.07972219999996,1.3575],[104.08527779999997,1.3466666999999997],[104.12611109999997,1.28925]]]]},"type":"Feature"}]}
This is the Code
var w = $('#map').parent().width();
var h = $(document).height() - 100;
var projection = d3.geoEquirectangular()
.scale(w)//scale it down to h / (2*Math.PI)
.translate([-w , h]);//translate as per your choice
//Create SVG element
var svg = d3.select("#map")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("path")
.data(singaporeJSON.features)
.enter()
.append("path")
.attr("d", d3.geoPath().projection(projection))
.style("fill", "steelblue");
And the result is, (ref attachment, highlighted by red circle)
How do I scale this without any trial and error, so that it fits the SVG height and width
The answer is in the link
Center a map in d3 given a geoJSON object
referred by Gerado Futado.
This would be the final code
// Create a unit projection.
var projection = d3.geo.albers()
.scale(1)
.translate([0, 0]);
// Create a path generator.
var path = d3.geo.path()
.projection(projection);
// Compute the bounds of a feature of interest, then derive scale & translate.
var b = path.bounds(feature), // if u have features, use features[0]
s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];
// Update the projection to use computed scale & translate.
projection
.scale(s)
.translate(t);
svg.selectAll("path")
.data(singaporeJSON.features)
.enter()
.append("path")
.attr("d", d3.geoPath().projection(projection)) .style("fill", "steelblue");
I need to add multiple arcs to one svg elements (every one got different animation). I need to fill them with radial gradients, but now, the center of radial gradient is not in the centre of whole svg element, but in the centre of specific arc. How it looks now. First i make all of the gradients i need in defs.
var tmpgrad=null;
for( var k = 0; k<data.length;k++){
tmpgrad = grads
.append("radialGradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("r", "50%")
.attr("id", function(d, i) { return "grad" + k; });
tmpgrad
.append("stop")
.attr("offset", "0%")
.style("stop-color", data[k].endColor)
.style("stop-opasity", 0);
tmpgrad
.append("stop")
.attr("offset", "100%")
.style("stop-color",data[k].startColor)
.style("stop-opasity", 1);
}
Then I make my arcs with deferent options:
var width= 1200;
var height=360;
var oArc = d3.svg.arc()
.innerRadius(iRadius)
.outerRadius(oRadius);
var oPie = d3.layout.pie()
.startAngle( sA )
.endAngle( eA )
.sort(null);
var group = svgDrawer.append("g");
var oPath = group.selectAll("g")
.data(oPie([0,200]))
.enter()
.append("path");
oPath
.attr("fill",function(d, i){ if(i==1){ i=0; } else { i = id; } return "url(#grad" + i +")"; })
.attr("d",oArc)
.each(function(d){
this._current = d;
});
But in the result as i said before i got the separated arcs with gradient starting in the middle of every arc element (path actually). How can i solve that.
I am the beginner in svg and javascript subject.
Needing some help... i was able to find an example of a rotating globe, that works great, i even found a way to put red circles at a point. Even better to setup a timer and everything rotates with the globe great. But if i put text on the map at the same point as the red circles it shows up at the starting point that i placed it, but as the world turns the red circle moves with the globe, but the text is frozen at the points that it was written. i am trying to get the text to rotate with the world and the red circles. think in the country of united states i want to put a number, brazil would have number when the globe rotates to china the values would still be on the countries i put it and when it rotates US and Brazil back to the front the numbers are there showing. This is what i have in code, bear with me I am still a noob when working with D3. thanks for any input...
// Initialize some variables:
var element = '#home1',
width = $("#home1").width(),
height = $("#home1").height();
var diameter = 460,
radius = diameter/2,
velocity = .001,
then = Date.now();
var features, circles;
var projection = d3.geo.orthographic()
.scale(radius - 2)
.translate([radius, radius])
.clipAngle(90);
// Save the path generator for the current projection:
var path = d3.geo.path()
.projection(projection)
.pointRadius( function(d,i) {
return radius;
});
// Define the longitude and latitude scales, which allow us to map lon/lat coordinates to pixel values:
var lambda = d3.scale.linear()
.domain([0, width])
.range([-180, 180]);
var phi = d3.scale.linear()
.domain([0, height])
.range([90, -90]);
// Create the drawing canvas:
var svg = d3.select("#home1").append("svg:svg")
.attr("width", diameter)
.attr("height", diameter);
//Create a base circle: (could use this to color oceans)
var backgroundCircle = svg.append("svg:circle")
.attr('cx', diameter / 2)
.attr('cy', diameter / 2)
.attr('r', 0)
.attr('class', 'geo-globe');
// Make a tag to group all our countries, which is useful for zoom purposes. (child elements belong to a 'group', which we can zoom all-at-once)
var world = svg.append('svg:g');
var zoomScale = 1; // default
// Create the element group to mark individual locations:
var locations = svg.append('svg:g').attr('id', 'locations');
// Having defined the projection, update the backgroundCircle radius:
backgroundCircle.attr('r', projection.scale() );
// Construct our world map based on the projection:
d3.json('world-countries.json', function(collection) {
features = world.selectAll('path')
.data(collection.features)
.enter()
.append('svg:path')
.attr('class', 'geo-path')
.attr('d', path);
// features.append('svg:title')
// .text( function(d) { return d.properties.name; });
}); // end FUNCTION d3.json()
d3.json("data.geojson", function(collection) {
console.log("2");
cs = locations.selectAll('path')
.data(collection.features)
.enter().append('svg:path')
.datum(function(d) {return {type: "Point", coordinates: [d.geometry.coordinates[0], d.geometry.coordinates[1]]}; })
.attr('class', 'geo-node')
.attr("d", path.pointRadius(5))
.attr('d', path);
cs1 = locations.selectAll('text')
.data(collection.features)
.enter().append('svg:text')
.attr("transform", function(d) {return "translate(" + projection(d.geometry.coordinates) + ")"; })
.attr("dy", ".35em")
.attr('d', path)
.text(function(d) { return d.properties.name; });
}); // end FUNCTION d3.json()
d3.timer(function() {
if(offpage === 0)
{
var angle = velocity * (Date.now() - then);
projection.rotate([angle,0,0])
svg.selectAll("path").attr("d", path.projection(projection));
}
});
d3.select(window)
.on("touchmove", mousemove)
.on("touchstart", mousedown);
function mousemove() {
offpage = 0;
}
function mousedown() {
offpage=1
}
In your code, features(the world map) is a path, and cs(the city points) is a path, but cs1(the city names) is a text. In your timer you rotate the paths, which doesn't rotate the text.
My solution uses rotation degrees, instead of angle, so you'll have to adapt the formula.
d3.timer(function() {
tcounter++
rotation++
if (rotation>=360) rotation = 0
projection.rotate([rotation,0,0])
www.attr("d", path.projection(projection));
citydot.attr("d", path.projection(projection));
ctext.attr("transform", function(d) {
return "translate(" + projection(d.geometry.coordinates) + ")"; })
.text(function(d) {
if (((rotation + d.geometry.coordinates[0] > -90) && (rotation + d.geometry.coordinates[0] <90)) ||
((rotation + d.geometry.coordinates[0] > 270) && (rotation + d.geometry.coordinates[0] <450)))
return d.properties.city;
else return "" });
if (tcounter > 360) return true
else return false
})
I am using the blur effect on the d3 map as given here: http://geoexamples.blogspot.in/2014/01/d3-map-styling-tutorial-ii-giving-style.html?
But after using this method (because of how the data is loaded..using datum) my zoom functionality behaves randomly. Irrespective of where I click it zooms to the same point. Also, the animations have become very slow after using the filter.
Is there any other way to achieve blur? Or a solution to this problem?
Any help?
Thanks.
This is the code for the world creation in case when filtering is required (use of datum as per the code on the above site).
d3.json("world-110m2.json", function(error, world) {
g.insert("path")
.datum(topojson.feature(world, world.objects.land))
.attr("d", path);
g.insert("path")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("d", path)
.append("path");
g.selectAll("path")
.on("click", click);})
This is the code used in case filtering is not required (No use of datum - maybe the datum is causing the issue)
d3.json("world-110m2.json", function(error,topology) {
g.selectAll("path")
.data(topojson.object(topology, topology.objects.countries)
.geometries)
.enter()
.append("path")
.attr("d",path)
.on("click", click);)}
This is the zoom function: got the code from here: http://bl.ocks.org/mbostock/2206590
function click(d) {
var x, y, k;
var centered;
if (d && centered !== d) {
var centroid = path.centroid(d);
x = centroid[0];
y = centroid[1];
k = 4;
centered = d;
} else {
x = width / 2;
y = height / 2;
k = 1;
centered = null;
}
if (active === d) return reset();
g.selectAll(".active").classed("active", false);
d3.select(this).classed("active", active = d);
var b = path.bounds(d);
g.selectAll("path")
.classed("active", centered && function(d) { return d === centered; });
g.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
}
The blur filter consumes lots of resources, as indicated in the post. Speciallly if you combine it with other filters.
One solution would be using Canvas instead of SVG. Here you have some filters using the Canvas element. It should be possible to achieve the same result.
I can't find why the zoom stops working, but the performance is slower because you use all the data, so you are applying the filter to all the data instead of using only the part of the word you are showing, so you are using a much bigger image when you zoom.
I'm upgrading my map to v3 of D3 and using the click to zoom transformation outlined in the D3 example code found here.
My code is nearly identical except that my map has slightly smaller dimensions (564 x 300 instead of 960 x 500). In addition, I have my map nested within a div and off to the top left of my page (though I don't think this matters)
The initial load of my map loads is fine (using black background for distinction currently)
// Clear existing map in case there is remnant data.
$("#map").html(null);
var mapWidth = 564;
var mapHeight = 300;
var projection = d3.geo.albersUsa()
.scale(mapWidth)
.translate([0, 0]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("#map")
.append("svg")
.attr("id", "map-svg")
.attr("width", mapWidth)
.attr("height", mapHeight);
svg.append("rect")
.attr("id", "map-background")
.attr("class", "background")
.attr("width", mapWidth)
.attr("height", mapHeight)
.on("click", click);
// Create placeholders for shapes and labels
var states = svg.append("g")
.attr("transform", "translate(" + mapWidth / 2 + "," + mapHeight / 2 + ")")
.attr("id", "states");
However, when a state is clicked, and the click function runs, my transform seems to be off. In my case, the state of Arkansas was clicked (indicated with blue shading)
function click(d)
{
var x = 0,
y = 0,
k = 1;
if (d && centered !== d)
{
var centroid = path.centroid(d);
x = -centroid[0];
y = -centroid[1];
k = 4;
centered = d;
}
else
{
centered = null;
}
d3.select("#states").selectAll("path")
.classed("active", centered && function (d) { return d === centered; });
d3.select("#states").transition()
.duration(1000)
.attr("transform", "scale(" + k + ")translate(" + x + "," + y + ")")
.style("stroke-width", 1.5 / k + "px");
}
My only thought is that my centroid calculations need to be adjusted slightly for the smaller size or different position of the map, but this doesn't seem right either.
How do I make the proper adjustments?
EDIT: I found that if I add a "negative translation" at the end of the transform (translate(" + -x + "," + -y + ")) that it gets closer to properly centering on the zoom, but not perfectly
You're missing one of two transforms from the example; but read on for a simpler solution.
The example you're using has two nested transforms. First, a static transform on the outer G element:
var g = svg.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.append("g")
.attr("id", "states");
This transform serves the same purpose as projection.translate normally does, but the example is using the translate([0, 0]). (As I said, there will be a simpler solution…)
The second transform is set dynamically on the inner G (with id "states") to zoom in:
g.transition()
.attr("transform", "scale(" + k + ")translate(" + x + "," + y + ")");
Note that the var g here refers to the inner G element, because when g was defined, there was an append("g") that was chained with a second append("g"); the variable is thus defined as the second, inner G, rather than the first, outer G.
The resulting SVG looks like this:
<g transform="translate(480,250)">
<g id="states" transform="translate(75.746,-439.514)scale(4,4)">
…
</g>
</g>
Your derivation is missing the nested, inner G. So when you set the "transform" attribute on your #states G element, you're overwriting the outer transform, giving you this:
<g id="states" transform="scale(4)translate(18.936679862557288,-109.8787070159044)">
…
</g>
So, you're missing the static transform, "translate(480,250)".
I'd recommend combining these transforms together. Then you don't need the outer G, and you can :
g.transition()
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"
+ "scale(" + k + ")"
+ "translate(" + x + "," + y + ")");
This also eliminates the need to set the projection’s translate to [0, 0], so you can use the standard translate [width / 2, height / 2] instead. I've updated the example to do just that!