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 use Mike Bostock's code to Center a map in d3 given a geoJSON object.
The important part of the code is this:
var width = 960,
height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("/d/4090846/us.json", function(error, us) {
var states = topojson.feature(us, us.objects.states),
state = states.features.filter(function(d) { return d.id === 34; })[0];
/* ******************* AUTOCENTERING ************************* */
// 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(state),
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);
/* ******************* END *********************************** */
// Landmass
svg.append("path")
.datum(states)
.attr("class", "feature")
.attr("d", path);
// Focus
svg.append("path")
.datum(state)
.attr("class", "outline")
.attr("d", path);
});
For example, bl.ocks.org/4707858 zoom in such:
How to center and zoom on the target topo/geo.json AND adjust the svg frame dimensions so it fit a 5% margin on each size ?
Mike's explained
Basically, Mike's code states the frame dimensions via
var width = 960, height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
Once the frame is hardly set, then you check out the largest limiting ratio so your geojson shape fill your svg frame on its largest dimension relative to the svg frame dimensions widht & height. Aka, if the shape's width VS frame width or shape height VS frame height is the highest. This, in turn, help to recalculate the scale via 1/highest ratio so the shape is as small as required. It's all done via:
var b = path.bounds(state),
s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height);
// b as [[left, bottom], [right, top]]
// (b[1][0] - b[0][0]) = b.left - b.right = shape's width
// (b[1][3] - b[0][4]) = b.top - b.bottom = shape's height
Then, refreshing your scale and transition you get Mike Bostock's zoom:
New framing
To frame up around the geojson shape is actually a simplification of Mike's code. First, set temporary svg dimensions:
var width = 200;
var svg = d3.select("body").append("svg")
.attr("width", width);
Then, get the dimensions of the shapes and compute around it :
var b = path.bounds(state);
// b.s = b[0][1]; b.n = b[1][1]; b.w = b[0][0]; b.e = b[1][0];
b.height = Math.abs(b[1][1] - b[0][1]); b.width = Math.abs(b[1][0] - b[0][0]);
var r = ( b.height / b.width );
var s = 0.9 / (b.width / width); // dimension of reference: `width` (constant)
//var s = 1 / Math.max(b.width / width, b.height / height ); // dimension of reference: largest side.
var t = [(width - s * (b[1][0] + b[0][0])) / 2, (width*r - s * (b[1][1] + b[0][1])) / 2]; //translation
Refresh projection and svg's height:
var proj = projection
.scale(s)
.translate(t);
svg.attr("height", width*r);
It's done and fit the pre-allocated width=150px, find the needed height, and zoom properly. See http://bl.ocks.org/hugolpz/9643738d5f79c7b594d0
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 am learning D3, and I want to learn how to add text to an svg in different locations following a formula rather than adding each text element to a location manually. Adding the letters A, B, C works below, but then the first three numbers are hidden because the first three text elements are already occupied by A, B, C.
What can I change to be able to place my letters and numbers separately? Thanks for your advice.
var w = 600;
var h = 500;
var letters = ["A","B","C"];
var numbers = ["1","2","3","4","5"];
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height",h);
svg.selectAll("text")
.data(letters)
.enter()
.append("text")
.attr("x", function(d, i) {return (i+1)*100})
.attr("y", function(d, i) {return (i+1)*100})
.style("text.anchor", "left")
.style("font-size", "40px")
.style("fill", "gray")
.attr("font-family","monospace")
.text(function(d){return d});
svg.selectAll("text")
.data(numbers)
.enter()
.append("text")
.attr("x", function(d, i) {return (i+1)*100})
.attr("y", 40)
.style("text.anchor", "left")
.style("font-size", "40px")
.style("fill", "gray")
.attr("font-family","monospace")
.text(function(d){return d});
You just need to make the text elements for the numbers and letters different. One easy way to do that is using classes.
svg.selectAll('text.letter')
.date(letters)
.enter()
.append('text')
.classed('letter', true)
// ...
svg.selectAll('text.number')
.date(numbers)
.enter()
.append('text')
.classed('number', true)
// ...
Working Demo
I'm trying to make the background-position follow the cursor within the relative dimensions of the 'figure'. JSFiddle here: http://jsfiddle.net/LJCkj/
It's off by a few pixels and I'm not sure how to take the scale into account.
The figure has an initial 180% background size and then on hover a 115% background size.
jQuery(function($) {
toScale = 1.15; // The 115% on hover
$('figure').on('mousemove', function(e) {
el = $(this);
w = el.width() * toScale;
h = el.height() * toScale;
x = e.pageX - el.offset().left - w / 2;
y = e.pageY - el.offset().top - h / 2;
if ((x >= toScale && x <= w) && (y >= toScale && y <= h))
el.css({
backgroundPosition: x+'px '+y+'px'
});
});
});
is what I've figured so far. But it's off by a good amount. Any ideas?
I think you are doing the multiplication by toScale at the wrong time.
Also, you are checking if x and y are greater than toScale, which is 1.15, so you can never move the picture back into the corner again.
Thirdly, because you are checking if both x and y are valid, it is very hard to move it back to the corner, because as soon as any of the values if out of bounds, you stop moving.
Your adjusted javascript could look like this:
function Between(a, min, max)
{
// return a, but bound by min and max.
return a<min?min:a>max?max:a;
}
jQuery(function($) {
toScale = 1.15; // The 115% on hover
$('figure').on('mousemove', function(e) {
el = $(this);
w = el.width();
h = el.height();
x = (e.pageX - el.offset().left - w / 2) * toScale;
y = (e.pageY - el.offset().top - h / 2) * toScale;
x = Between(x, 0, w);
y = Between(y, 0, h);
el.css({
backgroundPosition: x+'px '+y+'px'
});
$('span').text(x + ',' + y);
});
});
Your fiddle. Note I've added a span to view the coordinates. It might help you as well in the further development of your code.
http://jsfiddle.net/LJCkj/2