Based on d3 (ver 1.4) documentation https://github.com/d3/d3-geo/blob/master/README.md#geoProjection , the d3 projection should be set to null to use the data's raw coordinates. How do I scale if the projection looks correct using null? Here is the code:
var path = d3.geoPath()
.projection(null)
.context(context);
bands.features.forEach(function(d, i) {
context.beginPath();
context.fillStyle = colors[i];
context.globalAlpha = .5;
path(d);
context.fill();
});
I have tried defining my own projection but the projection looks incorrect. Here is the code
var project = d3.geoProjection(function(x,y){
return [x,y]
});
var path = d3.geoPath()
.projection(project)
.context(context);
I would take a look at d3.geoTransform which should be better suited for showing already projected Cartesian data than d3.projection. From Mike Bostock:
But what if your geometry is already planar? That is, what if you just want to take projected geometry, but still translate or scale it to fit the viewport?
You can implement a custom geometry transform to gain complete control over the projection process.
To see a better example than what I can do here (and to read the rest of the quote), see this Bl.ock
For example, for your case you might use something like:
function scale (scaleFactor) {
return d3.geoTransform({
point: function(x, y) {
this.stream.point(x * scaleFactor, y * scaleFactor);
}
});
}
path = d3.geoPath().projection(scale(2));
As for why the custom projection shown
var project = d3.geoProjection(function(x,y){
return [x,y] });
changes/distorts the projection, I do not know (I had similar results testing this answer), but if this projection's output is useable, it can be scaled fairly easily:
var project = d3.geoProjection(function(x,y){
return [x,y] }).scale(1000);
Thanks for your suggestion. My problem turned out to not be a scale problem but a matrix transformation issue. I am using the d3marchingsquares.isobands software and I needed to transpose my data prior to sending it into marching squares.
My project is create a map similar to https://earth.nullschool.net/ but using Google Maps.
I am adapting Roger Veciana's "Isobands from a geotiff with d3" demo http://bl.ocks.org/rveciana/de0bd586eafd7fcdfe29227ccbdcd511. This is almost complete but having issues resizing the canvas layer. Next I am going to overlay Danny Cochran's windable canvas layer over the temperature contours. I updated Danny Cochran's code to work with the latest Google Map's version.
Related
Hi i am trying to create a map of the city of Birmingham, though i can see the paths have been generated and the data is being loaded. I do not see anything on the html page.
I have seen people use the projection function without setting center or translate and it would visual their maps but this has not worked for me.
I have looked into possible solutions and found centering your projection by the city you are interested in should help with getting the map to display properly but this did not helped. I also tried to play around with the scale but this also did not help.
Essentially my expected results was a map of Birmingham to be displayed in the middle of my svg object.
var w= 1400;
var h = 700;
var svg = d3.select("body").append("svg").attr("width",w).attr("height",h );
var projection = d3.geoMercator().translate([w/2, h/2]).scale(100).center([1.8904,52.4862]);
var path = d3.geoPath().projection(projection);
var ukmap = d3.json("https://martinjc.github.io/UK-GeoJSON/json/eng/wpc_by_lad/topo_E08000025.json");
// draw map
Promise.all([ukmap]).then(function(values){
var map = topojson.feature(values[0],values[0].objects.E08000025).features
console.log(map);
svg.selectAll("path")
.data(map)
.enter()
.append("path")
.attr("class","continent")
.attr("d", path)
.style("fill", "#f0e4dd") //steelblue
});
```
It looks like the path is in the middle of your svg:
It's just really small.
With a d3 Mercator projection scale of 100 you are displaying 360 degrees of longitude across 100 pixels. So, with an svg that is 1400 pixels across, you could be showing 14 earths. Not ideal for a city. If you up the scale value to 10000 you'll at least see your feature, but it's not quite centered and it's still pretty small, try values for center and scale like so:
.center([-1.9025,52.4862])
.scale(100000)
(keeping the translate the same)
Now we're getting somewhere:
But this is still tedium, we can simply use projection.fitExtent or projection.fitSize to do the scaling automagically:
Promise.all([ukmap]).then(function(values){
var map = topojson.feature(values[0],values[0].objects.E08000025)
projection.fitSize([w,h],map);
var features = map.features;
svg.selectAll("path")
.data(features)
.enter()
...
This stretches the feature to fill the specified dimensions (it takes a geojson object, not an array, hence my slight restructuring). We can also specify a margin like so:
projection.fitExtent([[100,100],[w-100,h-100]],map);
This provides a 100 pixel margin around the map so it doesn't touch the edge of the SVG.
Both of these methods, fitSize and fitExtent, automatically set the projeciton translate and scale, so we can actually skip setting the scale, center, and translate manually (translate and scale essentially do the same thing: one after projection and one before, respectively. It's usually easier to use both though when setting the projection parameters manually)
Below is an example of using Snap.svg's Matrix.x(x,y) and I'm trying to determine how it is used.
After creating the Matrix transforms below(see image), and requesting the Matrix.x(50,50), it returns a value of 315.47+. What is that value?
Thanks
var SNPsvg = Snap("#mySVG");
//---square, center(0,0)---
var rect = SNPsvg.rect(-30,-30,60,60).attr({fill: 'blue' });
var myMatrix = Snap.matrix();
myMatrix.translate(200,100)
myMatrix.scale(2,1.5)
myMatrix.skew(30,45)
myMatrix.rotate(30)
rect.transform(myMatrix)
var mX1=myMatrix.x(50,50)//--if add translate (50,50) ??---
Its the value of x when transformed by that matrix!
Typically you will use it alongside y,
var TransformedPt = {
x: Matrix.x(x,y),
y: Matrix.y(x,y),
}
However, I would first look into Snaps transform strings, as they are often easier. For example..
rect.transform('t200,200r30s2,3')
Would transform the rect, translating 200,200, rotating (around center) 30 degrees, and then scaling x,y 2,3
It's there to help avoid the need for dealing with matrices. See here also.
The documentation states that Matrix.x(x, y):
Returns x coordinate for given point after transformation described by the matrix.
From http://snapsvg.io/docs/#Matrix.x
I am working on a plugin to allow "natural looking" signatures to be drawn using mouse or touch. When confirmed by the user, the result will be a stored SVG that can then be displayed in place of the "Click to sign" button.
The attached JSFiddle http://jsfiddle.net/TrueBlueAussie/67haj4nt/3/ shows a testbed for what I am trying to do. The SVG generated image should look close to the original canvas paths.
The first div contains a canvas, in which I draw some multiple-segment lines (e.g. paths). Using quadraticCurveTo, and a midpoint for the control point, I draw the lines with smooth curves. This works just fine.
The key part of the curved line drawing is:
$.each(lines, function () {
if (this.length > 0) {
var lastPoint = this[0];
ctx.moveTo(lastPoint[0], lastPoint[1]);
for (var i = 1; i < this.length; i++) {
var point = this[i];
var midPoint = [(lastPoint[0] + point[0]) / 2, (lastPoint[1] + point[1]) / 2];
ctx.quadraticCurveTo(lastPoint[0], lastPoint[1], midPoint[0], midPoint[1]);
lastPoint = point;
}
// Draw the last line straight
ctx.lineTo(lastPoint[0], lastPoint[1]);
}
});
I have tried multiple options for SVG generation of the same output, but I am stumped on how to convert the same sets of points to equivalent curved lines. Quadratic Beziers require "proper" control points, but I would prefer to use the far simpler mid-points if possible.
Any ideas? Is this possible or will I have to convert both to use Beziers with calculated control point(s). Is there a simple way to calculate control points that will do the same job?
jQuery or raw JavaScript solutions are fine, but you need to demonstrate in the JSFiddle provided :)
It's just a bug in your code. You are not updating lastPoint in your SVG version.
http://jsfiddle.net/67haj4nt/4/
And if you update the SVG version to match the canvas version, you get identical curves.
http://jsfiddle.net/67haj4nt/5/
I am drawing an SVG map with D3 using the d3.geo.mercator() projection.
I also use a zoom behaviour with the map which applies a transform to the <g> object holding all paths of the map.
After looking at examples of dynamic simplification by Mike Bostock (http://bl.ocks.org/mbostock/6252418) I wonder whether I can apply such an algorithm in my case to redraw the geometry with fewer points when it's zoomed out?
In all examples I've seen, there is a simplify function which skips negligible points and plots the rest as it is, and that function is used in var path = d3.geo.path().projection(simplify). I can't use it like that since I need it to be applied on top of already existing projection = d3.geo.mercator().scale(*).translate([*,*]).
How should I use dynamic simplification with existing projection?
According to the example you quoted, Dynamic Simplification II
The simplify function would be something like
var simplify = d3.geo.transform({
point: function(x, y, z) {
if (z >= area) {
this.stream.point(x, y);
}
}
});
Where area is a treshold variable that you can set beforehand or modify dinamically according to zoom.
Then you would use it on the projection method of d3.geo.path() like
var path = d3.geo.path()
.projection(simplify);
That's more or less the situation you described in your answer. Now, according to Dynamic Simplification IV, the projection method could also be defined as
var path = d3.geo.path()
.projection({
stream: function(s) {
return simplify.stream(s);
}
});
This is exactly the same as before. It's just "expanding" the default methods. d3.geo.path always calls the projection stream method, so you can declare your own stream and forward it to simplify.stream.
Now, you say you need to reproject your path using d3.geo.mercator().
var mercatorProjection = d3.geo.mercator().scale(*).translate([*,*]);
No problem: the streams are chainable. You can do:
var path = d3.geo.path()
.projection({
stream: function(s) {
return simplify.stream(mercatorProjection.stream(s));
}
});
As well as:
var path = d3.geo.path()
.projection({
stream: function(s) {
return mercatorProjection.stream(simplify.stream(s));
}
});
The only difference being that the treshold area would have to be calculated differently if you're dealing with WGS84, pixels or another coordinate system.
Important Caveat, the z parameter in the simplify function isn't the altitude. It is the area of the triangle defined by each point, a precalculated value that's part of TopoJSON sweetness.
I'm afraid this means you can't rely on this example to simplify regular geoJSON. You'll have to add your own logic to calculate each point's related area (if you want to apply Visvalingam's algorithm) or the distance to the closest point (if you want to apply Douglas-Peucker algorithm) or implement your own algorithm.
Good luck.
I'm having a bit of trouble playing around with KineticJS.
As you can see from my fiddle, I am able to access the Shape object (box) inside of my drop event, to get the x,y coordinates, and I'm performing math on them to get the new coordinates I want to 'snap' the shape to, but I can't figure out how on earth to set the position and redraw the box.
The docs are sparse, at best:
http://www.kineticjs.com/api-docs.php (See Shape.setPosition( x, y))
Has anyone here messed with this library yet?
EDIT: My now working fiddle: http://jsfiddle.net/Programmer/m4MZk/
check out the "Animals on the Beach" lab which is an example of snapping shape objects into place based on their coordinates:
http://www.html5canvastutorials.com/labs/html5-canvas-animals-on-the-beach-game-with-kineticjs/
This worked for me and has now been extended into a full circuit diagram drawing app.
It's based on the Animals on the Beach code but a bit simpler.
http://reviseomatic.org/help/e-tools/Diagram%20Designer%20Circuits.php
wire1vImg.on('dragend', function() {
var point = wire1vImg.getPosition();
var newX = Math.round(point.x / 15) * 15;
var newY = Math.round(point.y / 15) * 15;
wire1vImg.setPosition(newX, newY);
stage.draw();
});