D3: Is there a simple way to enlarge a map statically? - d3.js

I am attempting to create a map of my city but the default map is too small. I don't want to do anything like zooming (yet) but I would like to double the size displayed.
Here is an extract of the script
var projection;
var w=1200;
var h=800;
var x=-14100;
var y=7300;
var scale=66700;
projection=d3.geo.albers()
.translate([x,y])
.scale([scale]);
var path=d3.geo.path()
.projection(projection);
var svg=d3.select("#data-div")
.append("svg")
.attr("width",w)
.attr("height",h);
var category;
// GROUPS
var paths = svg.append("g"),
circles = svg.append("g");
// TORONTO MAP JSON
d3.json("d3_files/json/new-toronto.json",function(error, data){
paths.selectAll("path")
.data(data.features)
.enter()
.append("path")
.attr("d",path)
.attr("name",function(data){
return"<strong>name</strong>"
}) // end name attr
.style("fill",function(data){
return"lightgrey";
}) // end fill style
.style("stroke","#000")
}); // end toronto json

Instead of:
var scale=66700;
do:
var scale= 33350;
This will 'zoom' the projection out. From the API:
The scale factor corresponds linearly to the distance between
projected points

Related

How to calculate SVG Linear Gradient

If I have an upward sloping straight line is it possible to set the gradient fill such that it is red when the curved line is below the straight line and blue when above. Below are the two line functions and the d3 area function generator and their associated arrays.
//global Variables:
var trendGrowthX =[0,1,2,3,4,5,6,7,8,9,10];
var trendGrowthY = [2,2.5,3,3.5,4,4.5,5,5.5,6,6.5,7];
var actualX = [0,1,2,3,4,5,6,7,8,9,10];
var actualY = [2,4.3,5.5,3.5,2,3,5.5,7.5,6.5,5.5,5];
//D3 path generator functions:
function addTrend(){
var trendLine = d3.line()
.x(function(d,i,a){return xScale(trendGrowthX[i]);})
.y(function(d,i,a){return xScale(trendGrowthY[i]);})
}
function addActual(){
var actualIncome = d3.line()
.x(function(d,i,a){return xScale(actualX[i])})
.y(function(d,i,a){return yScale(actualY[i])})
.curve(d3.curveCatmullRom.alpha(0.5))
g.append("path").attr("d",actualIncome(actualX))
.style("stroke","green")
.style("stroke-width",3)
}
function addArea(){
var area = d3.area()
.attr("x1",function(d,i,a){return xScale(actualX[i]); })
.attr("y1",function(d,i,a){return xScale(actualY[i]); })
.attr("y0",function(d,i,a){return xScale(trendGrwothY[i]); })
.attr("x0",function(d,i,a){return xScale(trendGrowthX[i]); })
g.append("path")
.attr("d",function(d,i,a){ area(actualX)})
.style("fill","url(#area-gradient)")
.attr("pointer-events","none")
}
SVG has a transformGradient property to allow you to manipulate the linear gradient using standard transform instructions, solution is below:
var gradient = d3.select("#area- gradient").attr("gradientTransform",`rotate(-18),${xScale(trendGrowthX[5]),${yScale(trendGrowthY[5])})`);

standard coordinates in d3.js

I've been keen on developing a choropleth map for Namibia.But found two interesting tools. leaflet and D3, though leaflet has clear instructions to implement which i did Its not so functionally in line with what i want to do. And that is where D3Geo came in. I've everything set but this function below to set my projection.
var projection = d3.geo.conicConformal()
.rotate([, 0])
.center([0, 0])
.parallels([ , ])
.scale(1000)
Is there just no function to just simply add the co-ordinates as how its done in the leaflet function below. for us who are not so geocentric.
var map = L.map('mapid').setView([-22.26,16.52], 5);
And if there isn't, can someone please guide me on how to convert the coordinates (-22.26,16.52 )to show Namibia using the d3.geo.conicConformal().
Correct me if it didn't address your issue (maybe you can provide a minimal example showing where you are stuck, using JSFiddle for example), but if I understand well you want to move/zoom/center the displayed image on the extend of your country. Here is an example doing this (I also added some code on how the layer was added for consistency):
// Define the projection you want to use,
// setting scale and translate to some starting values :
var projection = d3.geoConicConformal()
.translate([0, 0])
.scale(1)
var layer_name = "your_layer_name";
var geo_features = topojson.feature(topoObj, topoObj.objects[layer_name]).features;
// Define the path generator :
var path = d3.geoPath().projection(projection);
var width = 800,
height = 600;
// This is the main svg object on which you are drawing :
var map = d3.select("body").append("div")
.style("width", width + "px")
.style("height", height + "px")
.append("svg")
.attr("id", "svg_map")
.attr("width", width)
.attr("height", height);
// Add you layer to the map
map.append("g").attr("id", layer_name)
.attr("class", "layers")
.selectAll("path")
.data(geo_features)
.enter().append("path")
.attr("d", path)
.attr("id", (d,i)=> "feature_" + i)
.styles({"stroke": "rgb(0, 0, 0)", "fill": "beige")
// Where the job is done :
scale_to_layer(layer_name)
function scale_to_layer(name){
var bbox_layer = undefined;
// use all the paths of the layer (if there is many features)
// to compute the layer bbox :
map.select("#"+name).selectAll('path').each(function(d, i){
var bbox_path = path.bounds(d);
if(bbox_layer === undefined){
bbox_layer = bbox_path;
}
else {
bbox_layer[0][0] = bbox_path[0][0] < bbox_layer[0][0]
? bbox_path[0][0] : bbox_layer[0][0];
bbox_layer[0][1] = bbox_path[0][1] < bbox_layer[0][1]
? bbox_path[0][1] : bbox_layer[0][1];
bbox_layer[1][0] = bbox_path[1][0] > bbox_layer[1][0]
? bbox_path[1][0] : bbox_layer[1][0];
bbox_layer[1][1] = bbox_path[1][1] > bbox_layer[1][1]
? bbox_path[1][1] : bbox_layer[1][1];
}
});
// Compute the new scale param, with a little space (5%) around the outer border :
var s = 0.95 / Math.max((bbox_layer[1][0] - bbox_layer[0][0]) / width,
(bbox_layer[1][1] - bbox_layer[0][1]) / height);
// Compute the according translation :
var t = [(width - s * (bbox_layer[1][0] + bbox_layer[0][0])) / 2,
(height - s * (bbox_layer[1][1] + bbox_layer[0][1])) / 2];
// Apply the new projections parameters :
projection.scale(s)
.translate(t);
// And redraw your paths :
map.selectAll("g.layer").selectAll("path").attr("d", path);
};
Also, note that this example use d3 v4 (but in this case it doesn't change a lot apart from the naming of geoPath and geoConicConformal)

geoJSON projection with d3.js and dc.js for South Africa and provinces

I have been struggling for a few days trying to use dc.js with d3.js projection to draw a map of South Africa and the provinces. I have exhausted my search as most results incorporate the path used for SVG when not using dc.js and I can't seem to find a suitable example for correcting a projection in dc.js.
I can't seem to find the map thats being drawing and I don't know how to correct my projection.
I really really don't know what i'm missing, and anyone that can assist will be appreciated.
UPDATE: I have geoJson that ive tested in mapshaper and it works so the geojson is fine. I am just struggling with the projection.
zaMap = zaMapString
//new array
var zaMapData = [];
for(var p in zaMap["features"])
{
console.log("ndx2 province data " + zaMap["features"][p]["properties"]["name"]);
zaMapData.push({
province: zaMap["features"][p]["properties"]["name"],
donation: 1000
})
};
//crossfilter instance
var ndx2 = crossfilter(zaMapData);
//dimensions and group for dc/d3
var provinceDim = ndx2.dimension(function(d) {console.log("province d " + d["province"]); return d["province"];});
var donationsByProvince = provinceDim.group().reduceSum(function(d) {
return d["donation"];
});
//geoChoroplethChart
var zaChart = dc.geoChoroplethChart("#map");
//start of chart code using d3 and dc
zaChart.dimension(provinceDim)
.group(donationsByProvince)
.width(1000)
.height(330)
.colors(["#E2F2FF", "#C4E4FF", "#9ED2FF", "#81C5FF", "#6BBAFF", "#51AEFF", "#36A2FF", "#1E96FF", "#0089FF", "#0061B5"])
.projection(d3.geo.mercator()
.scale(26778)
.translate([8227, 3207]))
.overlayGeoJson(zaMap["features"], "name", function (d) {
return d.properties.name;
});
dc.renderAll();
$("#progress").css({"display": "none"});
})
UPDATE 2: I switched from fiddle to codepen so I could upload the geoJson file as a asset. The geoJson takes a while to load but using code from an existing stackoverflow question, I have gotten the map to draw and projection to correct itself automatically. The d3.js function is not wrapping the dc.js to tie in with crossfilter.js as yet but I am working on that. But this is progress :)
In http://jsfiddle.net/Jimmypoo/f67xo5ry/1/, you are trying to use JSON.parse to parse an zaMapString, which is already a JS object. You don't need to parse it, it's not a string.
Secondly, d3.json is meant for passing in a remote URL, which d3 grabs for you. You are trying to pass in a JS object, which already exists. So you can remove that function, and simply use .overlayGeoJson(zaMap["features"], "name", function (d) { inside.
You also forgot to include jQuery, yet you use it in $("#progress").css({"display": "none"});. You'll need to wrap the entire JS section in a $(document).ready as well.
Also, you are including the scripts multiple times, in both minified and unminified forms.You only need one instance of each library.
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/dc/1.7.0/dc.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/dc/1.7.0/dc.min.js"></script>
You are also trying to include dc's CSS as JavaScript.
<script src="http://cdnjs.cloudflare.com/ajax/libs/dc/1.7.0/dc.css"></script>
It should be added in JsFiddle's left-hand side resource panel instead.
I also don't think assigning #map directly to the body of your document is going to make things easier for you either..would recommend including something interior of that like <div id="map" style="width:100%;height:300px"></div>
These suggestions don't solve all your problems but get you most of the way along.You still have projection issues. Here is an forked fiddle to move from - http://jsfiddle.net/uggtjem6/
I have gotten the geoJson to work with d3.js, dc.js and crossfiler.
var width = 300;
var height = 400;
var zaMapData = [];
//geoChoroplethChart
var zaChart = dc.geoChoroplethChart("#map");
d3.json("https://s3-us-west-2.amazonaws.com/s.cdpn.io/384835/layer1.json", function(json) {
var zaMap = JSON.stringify(json);
console.log(zaMap);
for (var i = 0; i < json.features.length; i++) {
console.log("ndx2 province data " + json["features"][i]["properties"]["PROVINCE"]);
zaMapData.push({
province: json["features"][i]["properties"]["PROVINCE"],
donation: i*1000
})
};
//crossfilter instance
var ndx2 = crossfilter(zaMapData);
//dimensions and group for dc/d3
var provinceDim = ndx2.dimension(function(d) {console.log("province d " + d["province"]); return d["province"];});
var donationsByProvince = provinceDim.group().reduceSum(function(d) {
return d["donation"];
});
var max_province = donationsByProvince.top(1)[0].value;
// create a first guess for the projection
var center = d3.geo.centroid(json)
var scale = 150;
var offset = [width/2, height/2];
var projection = d3.geo.mercator().scale(scale).center(center)
.translate(offset);
// create the path
var path = d3.geo.path().projection(projection);
// using the path determine the bounds of the current map and use
// these to determine better values for the scale and translation
var bounds = path.bounds(json);
var hscale = scale*width / (bounds[1][0] - bounds[0][0]);
var vscale = scale*height / (bounds[1][1] - bounds[0][1]);
var scale = (hscale < vscale) ? hscale : vscale;
var offset = [width - (bounds[0][0] + bounds[1][0])/2,
height - (bounds[0][1] + bounds[1][1])/2];
// new projection
projection = d3.geo.mercator().center(center)
.scale(scale).translate(offset);
path = path.projection(projection);
//create dc.js chart
zaChart.dimension(provinceDim)
.group(donationsByProvince)
.width(width)
.height(height)
.colors(["#E2F2FF", "#C4E4FF", "#9ED2FF", "#81C5FF", "#6BBAFF", "#51AEFF", "#36A2FF", "#1E96FF", "#0089FF", "#0061B5"])
.colorDomain([0, max_province])
.projection(d3.geo.mercator()
.center(center)
.scale(scale)
.translate(offset))
.overlayGeoJson(json["features"], "PROVINCE", function (d) {
return d.properties.PROVINCE;
})
.title(function (p) {
return "Province: " + p["key"]
+ "\n"
+ "Total Donations: R " + Math.round(p["value"])
});
dc.renderAll();
});
My codepen here.

Create Unique Path Within Each SVG Group Element Using D3

I'm trying to create 50 SVG groups with each group containing a path that draws a particular US state. However, I can't figure out how to access the state data from the SVG group when creating the state path for that group. Can someone put me on the right track? Thanks!
var coords = [
{ 'id':'HI', 'class':'state hi', 'd': 'm 233.08751,519.30948 1.93993, ...' },
{ 'id':'AK', 'class':'state ak', 'd': 'm 158.07671,453.67502 -0.32332, ...'}
...
];
// Select the SVG
var svgContainer = d3.select('svg');
// Create groups with state data
var groups = svgContainer.selectAll('g')
.data(coords)
.enter()
.append('g');
// Create state path for each group
groups.each(function(g){
g.select('path')
.data(___NEED TO RETURN THE PATH DATA HERE___)
.enter()
.append('path');
});
Uncaught TypeError: g.select is not a function
Assuming your data is valid and you only want to handle new elements (no updates), this should work:
// Select the SVG
var svgContainer = d3.select('svg');
// Create groups with state data
var groups = svgContainer.selectAll('g')
.data(coords)
.enter()
.append('g')
.append('path')
.attr('d', function (d) { return d.d; });
I have created a pretty simplified fiddle with an example.
If you still want to use the .each function, you can proceed as follows:
groups.each(function (d, i){
// `this` (in this context) is bound to the current DOM element in the enter selection
d3.select(this).append('path').attr('d', d.d);
});
For further details, you can check the API documentation.
Hope it helps.

Create element "in memory", like jQuery does, in D3.js

Instead of doing
d3.select("body").append("svg")
, which most d3.js examples do, I'd like to create an element, and NOT attach it to body or anything right away.
Kind of like $('<div/>') in jQuery.
How can I do this?
Create the element using document.createElement() and pass it to d3 as usual.
In console:
> a = document.createElement("div")
<div>​</div>​
> d3.select(a).append("svg")
[Array[1]]
> a
<div>​
<svg>​</svg>​
</div>​
// create selection containing unattached div node
var sel = d3.create('svg');
and if you want just the node...
// retrieve unattached node
var node = d3.create('svg').node();
To create the svg element "in memory" use document.createElementNS.
Use of document.createElementNS:
// Create the svg elem
var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
// Create a g elem
var g = document.createElementNS("http://www.w3.org/2000/svg", "g");
// Create a d3 Selection with the elem
var d3Svg = d3.select(svg);
var d3g = d3.select(g);
Append a d3 selection to another d3 selection:
d3Svg.append(function(){ return d3g.node(); });
You should try something like:
var $svg = $('<svg/>');
var d3svg = d3.select($svg[0]);
// ... manipulate d3svg ...
$('body').append($svg)
Hope it helps.
I had to do this in order to support percentage widths on my custom SVG element:
var svgDom = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgDom.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
svg = d3.select(svgDom)
.attr("class", "chart")
.attr("width", "100%")
.attr("height", "100%");

Resources