Have a topoJSON file that I am importing - seems like this should be easy to flip, but I have no idea. Should I be transforming the object after it's created or adjusting the JSON? I tried using some projections, which flipped the object, but distorted it all over the place.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.counties {
fill: blue;
}
.states {
fill: none;
stroke: #fff;
stroke-linejoin: round;
}
</style>
<svg width="960" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var path = d3.geoPath()
//first make projection
//var projection = d3.geoMercator();
//var path = d3.geoPath()
// .projection(projection);
d3.json("data.topojson", function(error, us) {
if (error) throw error;
var counties = topojson.feature(us, us.objects.counties),
counties = counties.features.filter(function(d) { return d.properties.AWATER === 0; });
svg.append("g")
.attr("class", "counties")
.selectAll("path")
.data(counties)
.enter().append("path")
.attr("d", path)
.style("fill", 'blue')
.append('g')
attr('transform', 'rotate(180 0 0)');
});
</script>
You aren't using a projection - so the coordinates in the file are translated to pixels with no transformation. If your data has typical geographic coordinates, or lat longs that are both positive, high values are at the north end (the top in most maps), and low values are at the south end (the bottom in most maps).
In svg coordinates, low values are located at the top and increase as one moves towards the bottom - the opposite of most geographic coordinate conventions. You could use a geoIdentity as your projection to flip the json's y coordinates:
var projection = d3.geoIdentity()
.reflectY(true)
.fitSize([width,height],geojson)
Where geojson is your feature collection: topojson.feature(us, us.objects.counties)
And then use it with your path:
var path = d3.geoPath().projection(projection);
Related
I'm trying to draw choropleth map of Seoul but it just gives me only a black rectangle.
I thought there might be a problem with projection.scale but I couldn't figure it out.
I used scale.fitSize() function to handle it. but my d3 is older version that not available of fitSize().
my code is here:
var width = 600, height = 700;
var svg = d3.select('#chart').append('svg')
.attr('width',width)
.attr('height',height);
var projection = d3.geo.mercator()
.center([128,36])
.scale(5000)
.translate([width/2, height/2]);
var path = d3.geo.path()
.projection(projection);
d3.json('Seoulmap.json',function(error,data) {
var features = topojson.feature(data, data.objects['Seoulmap']).features;
svg.selectAll('path')
.data(features)
.enter().append('path')
.attr('class','name')
.attr('d',path)
.attr('id',function(d) { return d.properties.ADM_DR_NM; });
});
How my code is rendering black rectangle:
I have some d3 svg elements in which I pass as attribute a list of coordinates x,y and I plot them on a svg map. Like the example below:
var svg = iniMarker().append("g")
.attr("data-lineData", JSON.stringify(lineData))
//Function to draw line
var lineFunction = d3.svg.line()
.x(function (d) { return d.x; })
.y(function (d) { return d.y; });
The resulting path structure is like this:
<path d="M1100,850L1180,800" class="pPath" stroke-dasharray="94.33981323242188 94.33981323242188" stroke-dashoffset="0"></path>
<path d="M1180,800L1280,800" class="pPath" stroke-dasharray="100 100" stroke-dashoffset="0"></path>
<path d="M1280,800L1380,800" class="pPath" stroke-dasharray="100 100" stroke-dashoffset="0"></path>
On the top of this line path there is also a circle which takes as input the line data coordinates values:
var circle = svg.append("circle")
.attr("r", 10)
.attr("fill", "#fff")
.attr("class", "marker")
.attr("transform", function () {
return "translate(" + lineData[0].x + "," + lineData[0].y + ")";
});
The data where I take the coordinates information are store in this format:
var DataSet = [{
"id": "",
"isActive": true,
"personId": "p1",
"name": "test",
"lineData": [
{ "timestamp": "2018/09/15 10:00:05 AM", "x": 1100, "y": 950 }]
Now I need to represent each of these points taken from this coordinates list on a canvas map exactly superimposed on the svg map. And in that case the problem is that the svg coordinates of the list don't match the same position of the canvas coordinates.
In fact if I try to generate some canvas coordinate points by using the function below:
document.getElementById("canv").onclick = function (e) {
var localX = e.clientX - e.target.offsetLeft;
var localY = e.clientY - e.target.offsetTop;
}
Then the coordinates at the onclick event are pretty much different from the svg coordinates that I have from DataSet obj as position.
Actually in order to match them, I have just tried to pass the DataSet obj on a function in order to get the svg coordinates and plot them on the canvas map, like this:
DataSet.map(function (m) { if(m.isActive) return m.lineData; else return []; }).forEach(f => data = data.concat(f));
data.forEach(e => {
//here I call another function for plot the svg coordinates as points on the canvas map
});
But it's obvious that I need to convert them first in order to match correctly.
Following the suggestion of #enxaneta I need to create a canvas from these data objects. But the problem is that the coordinates that I already have are for svg, not for canvas format.
I need to find a way to convert the list of svg coordinates that I have in order to match or be more similar as possibile to the canvas coordinates system.
I'm open to suggestions.
Your question is not very clear but assuming that you have an svg path, you can transform it to a canvas path using the Path2D object: In the next example the SVG paths are red, the canvas paths are left black.
var svgPathApple = Apple.getAttribute("d");
var svgPathLeaf = Leaf.getAttribute("d");
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cw = c.width = 500;
var ch = c.height = 500;
var apple = new Path2D(svgPathApple);
var leaf = new Path2D(svgPathLeaf);
ctx.stroke(apple);
ctx.stroke(leaf);
canvas,svg{border:1px solid #d9d9d9; width:45vw;}
path{fill: none; stroke:red;}
<svg viewBox="0 0 500 500">
<path id="Apple" d="M376.349,171.58
c0,0-37.276,22.484-36.094,60.946s31.438,61.577,42.012,63.313c4.127,0.678-24.314,57.988-42.039,72.189
s-36.067,17.159-64.47,5.917c-28.403-11.242-48.521,0.724-65.089,6.871s-36.687-0.361-63.905-39.415
s-57.396-129.585-15.976-173.964s87.574-26.627,100-20.71s34.32,5.325,59.172-5.917S363.922,153.237,376.349,171.58z"/>
<path id="Leaf" d="M311.852,68.621c0,0,2.367,14.793-3.55,27.219
s-28.189,55.061-60.473,47.337c-0.809-0.193-5.529-14.482,1.398-29.002C259.004,93.682,284.49,70.699,311.852,68.621z"/>
</svg>
<canvas id="c"></canvas>
UPDATE:
SVG stands for Scalable Vector Graphics. Although your svg size is the same as your canvas size, in facr your svg is four time as big. In order to know the real size you have to look at the value of the viewBox attribute: "52 0 2104 1200". Translated to plain english the width of your svg is 2104, and the height is 1200. And this is not all. Your svg coordinates start at x=52 and y = 0. Given a point p={x:100,y:100} on the SVG, your canvas coordinates are: x = (100 - 52)/2 and y = 100/2;
Next comes a simple example where I'm drawing a circle on canvas and on the svg element:
const canvas = document.getElementById("canv");
const ctx = canvas.getContext("2d");
let x = (100 - 52)/2,
y = 100/2,
r = 10/2;
ctx.beginPath();
ctx.arc(x,y,5,0,2*Math.PI);
ctx.fill();
canvas,svg{border:1px solid;}
<svg version="1.1" id="svgMap" width="1052px" height="600px" viewBox="52 0 2104 1200">
<circle id="testCircle" cx="100" cy ="100" r="10" />
</svg>
<canvas width="1052" height="600" id="canv"></canvas>
I hope this helps.
I am drawing a map of the United States,Costa Rica and Canada. And I would like the maps to adapt to the size of the div#statesvg
<div id="statesvg" style="width:100%; height:100%"></div>
the size of div#statesvg is dynamic.
I only want by default that the maps fits exactly to the div that contains it.
I'm trying to center each map, but it's not centered. I would like to know if there is any mathematical formula or something to scale the map until it fully occupies the svg container.
if(json=="usa.json"){
// D3 Projection
var projection = d3.geo.albersUsa()
.translate([width/2, height/2])
.scale((height*1.25));
}
if(json=="canada.json"){
//canada lat long 54.6965251,-113.7266353
var projection = d3.geo.mercator()
.center([-113.7266353,54.6965251 ])
.translate([width/2, height/2])
.scale((height*1.25));
}
if(json=="costarica.json"){
//costa rica lat long
var projection = d3.geo.mercator()
.center([-87.0531006,8.351569 ])
.translate([width/2, height/2])
.scale((height*1.25));
}
// Define path generator
var path = d3.geo.path() // path generator that will convert GeoJSON to SVG paths
.projection(projection); // tell path generator to use albersUsa projection
this is my actual problem for each map
thanks!
this is my code:
http://plnkr.co/edit/lJgx0fbLcEh7e4W3j6XD?p=preview
There is this nice gist from nrabinowitz, which provides a function which scales and translate a projection to fit a given box.
It goes through each of the geodata points (data parameter), projects it (projection parameter), and incrementally update the necessary scale and translation to fit all points in the container (box parameter) while maximizing the scale:
function fitProjection(projection, data, box, center) {
...
return projection.scale(scale).translate([transX, transY])
}
I've adapted part of your code (the Canada map) to use it:
d3.json("canada.json", function(data) {
var projection =
fitProjection(d3.geo.mercator(), data, [[0, 0], [width, height]], true)
var path = d3.geo.path().projection(projection);
d3.select("#statesvg svg").remove();
var svg = d3.select("#statesvg").append("svg")
.attr("width", width+"px")
.attr("height", height+"px");
svg.selectAll("path")
.data(data.features)
.enter()
.append("path")
.attr("d", path)
.style("stroke", "#fff")
.style("stroke-width", "1")
.style("fill", function(d) { return "rgb(213,222,217)"; });
});
As commented by the author, it seems to only work for Mercator projections.
i want to create map of Africa using d3.js and topoJSON. I have that datasource https://gist.githubusercontent.com/bricedev/3905007f1794b0cb0bcd/raw/ad5c995f6990f7c3c7fad5c6206bc6fd5462f1fb/africa.json
That is my code. How i can get properties and create correct map? Please help me where is the error?
<!DOCTYPE html>
<html>
<head>
<meta name="description" content="D3byEX 12.15" />
<meta charset="utf-8">
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var width = 1000, height = 728;
var svg = d3.select("body").append("svg")
.attr({ width: width, height: height });
var mainGroup = svg.append("g");
mainGroup.style({ stroke: "white", "stroke-width": "2px", "stroke-opacity": 0.0 });
var projection = d3.geo.mercator();
var path = d3.geo.path().projection(projection);
var url = 'https://gist.githubusercontent.com/bricedev/3905007f1794b0cb0bcd/raw/ad5c995f6990f7c3c7fad5c6206bc6fd5462f1fb/africa.json';
d3.json(url, function (error, africa) {
var countries = topojson.feature(africa, africa.objects.countries).features;
var neighbors = topojson.neighbors(africa.objects.countries.geometries);
var color = d3.scale.category20();
mainGroup.selectAll("path", "countries")
.data(countries)
.enter().append("path")
.attr("d", path)
.style("fill", function (d, i) {
return color(d.color = d3.max(neighbors[i],
function (n) { return countries[n].color; }) + 1 | 0);
});
mainGroup.selectAll("path")
.on("mouseover", function () {
console.log("mouseover");
d3.select(this).style("stroke-opacity", 1.0);
});
mainGroup.selectAll("path")
.on("mouseout", function () {
d3.select(this).style("stroke-opacity", 0.0);
});
});
</script>
Your topojson is not in WGS84, that is to say lat/long coordinate space or unprojected data. D3.projection() requires WGS84.
Your topojson is already projected in, what I assume is, the Africa Lambert Conformal Conic projection. You do not need to use a projection to display this in d3.js. In order to display this data without a projection you can define the projection of your geoPath as:
path = d3.geoPath().projection(null);
This is how the topojson was projected in the block in which that data came from.
If you need to scale or translate the projection, then d3.geoTransform can help you, see this block.
Alternatively, you can reproject your topojson so that it uses lat/long pairs and will properly project using d3.projection().
I'm doing this in JavaScript ( SVG or Canvas ) but I'm really just looking for pseudocode for how somebody could accomplish this:
If given a rectangle, how can you fill it with various sized, non-overlapping triangles similar to this picture:
http://imgur.com/5XOxpjB
UPDATE
Here is what I came up with for those that are interested. I used D3.js which has a great delaunay function to do this.
var width = 360;
var height = 220;
var vertices = d3.range(100).map(function (d) {
return [Math.random() * width, Math.random() * height];
});
// place coordinates in the corners
[
[0, 0],
[width, 0],
[0, height],
[width, height]
].forEach(function (d) {
vertices.push(d);
});
// add the temporary coordinates that will follow mousemove
vertices.unshift([0, 0]);
var svg = d3.select("#main").append("svg")
.style("width", width + "px")
.style("height", height + "px")
.on("mousemove", function () {
vertices[0] = d3.mouse(this);
draw();
})
.on("click", function () {
vertices.push(d3.mouse(this));
});
var path = svg.append("g").selectAll("path");
draw();
function draw() {
path = path.data(d3.geom.delaunay(vertices).map(function (d) {
return "M" + d.join("L") + "Z";
}), String);
path.exit().remove();
path.enter().append("path").attr("class", function (d, i) {
return "q" + (i % 9) + "-9";
}).attr("d", String);
}
#main {
position: relative;
width: 360px;
height: 220px;
border: 1px solid #ddd;
}
path {
fill: #fff;
stroke: steelblue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="main"></div>
I would do the following:
Define a 2D array for your points, 1D for each line where each entry contains a point for a vertice/corner.
Create points based on a grid
Offset the same points using some form of "jitter". The jitter will only offset the point's position but you will still have the same order in the array
Have a method looping over the arrays drawing a quadrant for each two points on two lines
Split that quadrant into two triangles using the two opposite corner vertices.
Varying jitter should create a similar pattern as shown in the image. To control the jitter you could randomize an angle and radius rather than just the position. Then convert the result of that to a new position.
An ASCII representation (perhaps not useful..):
line 0: xy xy xy xy xy .... vertices for upper part of quadrant
line 1: xy xy xy xy xy .... vertices for lower part and shared
upper part of next quadrant
line 2: xy xy xy xy xy ....
line 3: xy xy xy xy xy ....
...
Apply jitter
Draw in ordered fashion whatever the point position is:
line 0: xy_xy_xy xy xy ....
| /| /| ...
|/_|/_|
line 1: xy xy xy xy xy ....
...
You could also look into Voronoi diagram but be aware of that you'll end up with quadratic polygons and n-gons, the latter can be a bit of a challenge but check Delaunay triangulation for that.
Someone has already written a library to do exactly this. http://qrohlf.com/trianglify/