d3 fitExtent function not mapping GeoJSON - javascript

I am trying to map the geojson linked below, but i am having trouble with the projection. When i try to pass the GeoJSON feature collection into fitExtent, no map appears (no paths are drawn).
PS;
I have used console log to break down each part of the code and everything seems fine i.e i have tested the file without the fitExtent function using regular scale and translate on the projection.
code snippet
let boroughProjection = d3.geoMercator().fitExtent(
[
[0, 0],
[width, height],
],
geojson
);
let boroughPath = d3.geoPath().projection(boroughProjection);
let boroughMap = this.mapCanvasB
.selectAll("path")
.data(geojson)
.enter()
.append("path")
.attr("class", "borough")
.attr("d", boroughPath)
.style("fill", "gray")
.style("stroke", "black")
.style("stroke-width", "0.4")
Link to GeoJSON https://raw.githubusercontent.com/blackmad/neighborhoods/master/london.geojson

Related

How to make a smooth transition in a pie chart when updating values in d3?

I am using j3.ds to deploy some data on a pie chart. It seems to work fine and it updates correctly when I introduce new data. The thing is, I wanted to do the transition when updating smoothly, like here:
https://www.d3-graph-gallery.com/graph/pie_changeData.html
For some reason it is not working when I introduce the merge and transition, can somebody help with the task? thanks in advance
update();
function update() {
var data = d3.selectAll('.values').nodes();
var pie = d3.pie() //we create this variable, for the values to be readeable in the console
.value(function(d) {return d.innerHTML; })(data);
console.log("pie = ",pie)
var u = svg.selectAll("path")
.data(pie)
console.log("u = ",u)
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function
u
.enter()
.append('path')
.merge(u)
.transition()
.duration(2000)
.attr('d', d3.arc()
.innerRadius(0)
.outerRadius(radius)
)
.attr('fill', function(d,i){ return color[i] })
.attr("stroke", "black")
.style("stroke-width", "2px")
.style("opacity", 1)
}
Merge combines one selection with another as follows:
selectionCombined = selection1.merge(selection2);
You aren't providing a second selection, so you aren't merging anything. The selection you do have you are calling .merge() on is the enter selection, returned by .enter() - unless you add new slices to the pie, this will be empty every update after the first. As you aren't merging anything with the enter selection, post merge the selection is still empty.
The enter selection is used to create elements so that for every item in the data array there is one corresponding element in the DOM - as you already have slices, only the update selection is not empty.
The update selection is that returned by .data(), it contains existing elements which correspond to items in the data array. You want to merge this selection with the one returned by .enter():
var update = svg.selectAll("path")
.data(pie)
var enter = update.enter()
.append('path')
var merged = update.merge(enter)
However, a transition needs a start value and an end value. In your case you are trasitioning the d attribute of a path. On update the start value is the path's current d and the end value is a d representing the new value for the slice. On initial entry, what is the value that the transition should start from? It may be more appropriate to only transition on update:
var arc = d3.arc().innerRadius(0).outerRadius(radius);
var update = svg.selectAll("path")
.data(pie)
var enter = update.enter()
.append('path')
// Set initial value:
.attr('d', arc)
// If these values don't change, set only on enter:
.attr('fill', function(d,i){ return color[i] })
.attr("stroke", "black")
.style("stroke-width", "2px")
update.transition()
// transition existing slices to reflect new data.
.attr('d',arc)
Note: transitioning paths can be difficult - you'll notice the deformity to the pie in the transition in your example. This is because of how the d attribute string is interpolated. If you want to preserve the radius a different approach is needed in applying the transition.

d3.js build connections by using loop function

I have build a connection by using d3. The codes show the data and method of connection:
var places = {
TYO: [139.76, 35.68],
BKK: [100.48, 13.75],
BER: [13.40, 52.52],
NYC: [-74.00, 40.71],
};
var connections = {
CONN1: [places.TYO, places.BKK],
CONN2: [places.BER, places.NYC],
};
...
svg.append("path")
.datum({type: "LineString", coordinates: connections.CONN1})
.attr("class", "route")
.attr("d", path);
svg.append("path")
.datum({type: "LineString", coordinates: connections.CONN2})
.attr("class", "route")
.attr("d", path);
You can see my codes, that I use the two identical methods to build two connections. That is not good to build more connections.
I am wondering, if there is a loop function to interpret the connections by using data "connections" directly? I mean, I could get information for data "connections" and use them directly to build connections.
I have thought some ways, such as .datum({type: "LineString", function(d,i) {
return coordinates: connections[i];});. But it does not work.
Could you please tell me some way to solve it? Thanks.
Generally when you want to append many features in d3, you want to use an array not an object. With an array you can use a d3 enter selection which will then allow you to build as many features as you need (if sticking to an object, note that connections[0] is not what you are looking for, connections["conn1"] is).
Instead, use a data structure like:
var connections = [
[places.TYO, places.NYC],
[places.BKK, places.BER],
...
]
If you must have identifying or other properties for each datapoint use something like:
var connections = [
{points:[places.TYO, places.NYC],id: 1,...},
{points:[places.BKK, places.BER],id: 2,...},
...
]
For these set ups you can build your lines as follows:
paths = svg.selectAll(".connection")
.data(connections)
.enter()
.append("path")
.attr("class","connection")
.attr('d', function(d) {
return path ({
type:"LineString",
coordinates: d
});
})
See here. Or:
paths = svg.selectAll(".connection")
.data(connections)
.enter()
.append("path")
.attr("class","connection")
.attr('d', function(d) {
return path ({
type:"LineString",
coordinates: d.points
});
})
Alternatively, you can use a data set up like:
var connections = [
{target:"TYO", source:"NYC"},
{target:"BKK", source: "BER"},
...
]
paths = svg.selectAll(".connection")
.data(connections)
.enter()
.append("path")
.attr("class","connection")
.attr('d', function(d) {
return path ({
type:"LineString",
coordinates: [ places[d.source],places[d.target] ]
});
})
See here.
If selecting elements that don't yet exist, using these lines
d3.select("...")
.data(data)
.enter()
.append("path")
will append a path for each item in the data array - this means that d3 generally avoids the use of for loops as the desired behavior is baked right into d3 itself.

d3.path plots only first feature of feature collection

I am trying to plot a map of the UK and plotting a few selected points on it.
I am following the first part of this tutorial https://bost.ocks.org/mike/map/
Here's what I have done.
var svg = d3.select('body').append('svg')
.attr('height', height)
.attr('width', width)
.call(zoom);
d3.json("/data/uk.json", function(error, uk) {
if (error) return console.error(error);
var subunits = topojson.feature(uk, uk.objects.subunits);
var projection = d3.geo.albers()
.center([0, 55.4])
.rotate([4.4, 0])
.parallels([50, 60])
.scale(6000)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
svg.append('path')
.datum(subunits)
.attr('d', path);
svg.selectAll('.subunit')
.data(topojson.feature(uk, uk.objects.subunits).features)
.enter().append('path')
.attr('d', path);
Here is the part where I try to plot the points
d3.json('/data/places.json', function (error , result) {
if(error)
console.error(error);
svg.append('path')
.data(result.features)
.style('stroke', 'green')
.attr('d' , d3.geo.path().projection(projection))
});
The above code plots only one point on the map, i.e the first one in the JSON file
You are not correctly binding your features' data when using
svg.append('path')
.data(result.features)
.style('stroke', 'green')
.attr('d' , d3.geo.path().projection(projection))
This will append one path, bind the first element of result.features to this path and set the style and attribute accordingly.
To work the way you want it, you need to make use of the data joining mechanism of D3.
svg.selectAll('path.features')
.data(result.features)
.enter().append('path')
.attr('class', 'feature')
.style('stroke', 'green')
.attr('d' , d3.geo.path().projection(projection))
This will compute a data join for the features in result.features putting the new elements in the enter selection which is accessible by calling enter() on the seletion. Using this enter selection you can now append paths for all your features.
A further side note not directly related to your issue:
Depending on the number of features you want to append to your map, the .attr("d") setter might be called quite often. You could improve performance by reusing one instance of the path generator:
var geoPath = d3.geo.path().projection(projection); // You need just one instance
svg.selectAll('path.features')
.data(result.features)
.enter().append('path')
.attr('class', 'feature')
.style('stroke', 'green')
.attr('d' , geoPath) // Re-use the path generator
This is considered best practice, which should be generally applied.

i can't change the colors on the lines in a multi line graph with d3

it seems like i am doing everything right except the fact that i can't change the colors to distinguish between the lines, this code should work:
colors = ["blue","red","yellow","green","black","blue","gray"];
linesGroup = svg.append("g").attr("class", "lines");
var linedata;
for (var i in chart_data) {
linedata = chart_data[i];
console.log(linedata);
linesGroup.append("path")
.attr("d", line(linedata.points))
.attr("class", "line")
.attr("fill", "none")
.attr("stroke", function(d, i) {
console.log(colors[Math.floor((Math.random()*6)+1)]);
return colors[colors[Math.floor((Math.random()*6)+1)]];
});;
};
i am also using jsfiddle for the full example
http://jsfiddle.net/yr2Nw/
Set the stroke as an inline style instead and access the color array properly:
.style("stroke", function(d, i) {
return colors[Math.floor((Math.random()*6)+1)];
});
Using a for in loop isn't a super idiomatic way of getting things done in d3 (you'll run into problems if you try to use i).

Basic D3.js: how to use joins within a function?

I'm getting to grips with D3.js. I would like to write a function that draws one set of dots with one set of data, then another set of dots with another set of data.
I have written this, but the second set of dots is over-writing the first set of dots! How can I rewrite it without the selectAll so that I correctly end up with two sets of dots?
function drawDots(mydata) {
focus.selectAll(".dot").data(mydata)
.enter().append("circle")
.attr("class", "dot")
.attr("cx", line.x())
.attr("cy", line.y())
.attr("r", 3.5);
}
drawDots(data[0]);
drawDots(data[1]);
(NB: This is a simplification. Basically I want to know how to use .enter() with a function call.)
you need to give two sets of data distinct class names. Right now both get tagged with the same class (".dot"), but if they represent different sets, you also need to be able to distinguish between them. E.g.:
function drawDots(mydata, name) {
focus.selectAll(".dot"+"."+name).data(mydata)
.enter().append("circle")
.attr("class", "dot" + " " + name)
.attr("cx", line.x())
.attr("cy", line.y())
.attr("r", 3.5);
}
drawDots(data[0], "set1");
drawDots(data[1], "set2");
I've only used d3js for building force graphs, but I think in your case you need to add the nodes first to the visualization, then invoke enter() and then fetch what is in the graph.
function drawDots(mydata)
{
myD3Object.nodes(myData).start();
focus.selectAll(".dot").data(myD3Object.nodes())
.enter().append("circle")
.attr("class", "dot")
.attr("cx", line.x())
.attr("cy", line.y())
.attr("r", 3.5);
}

Categories

Resources