Why does my US Map crash the browser in Firefox? D3.js - javascript

This is an image of my d3 graph.
On Firefox, unlike Chrome and IE, the browser seemed to buck when pressed to paint the SVG and would crash. When the graph was removed, it was no problem and the page didn't break. Again, on every other browser it was no problem. There were no errors logged. Only browser death.
Works in IE 10 and Chrome 32 >> latest browsers. Is crashing in Firefox 26.0.
(function(){
var width = 900,
height = 500;
var projection = d3.geo.albersUsa()
.scale(1070)
.translate([460, height / 2]);
var path = d3.geo.path()
.projection(projection)
.pointRadius(2);
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height);
queue()
.defer(d3.json, "../us.json")
.defer(d3.tsv, "../long_lat.tsv")
.await(ready);
function ready(error, us, long_lat) {
if (error){
console.log(error);
}
svg.append("path")
.datum(topojson.feature(us, us.objects.land))
.attr("class", "land")
.attr("d", path);
svg.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("class", "states")
.attr("d", path);
svg.append("path")
.datum({type: "MultiPoint", coordinates: long_lat})
.attr("class", "points")
.attr("d", path)
.style('fill', 'rgb(247, 150, 29)');
};
})();
my tsv data:
0 1
-73.427242 40.871873
-73.996700 40.750000
-73.992363 40.740134
-87.525537 37.942482
-89.564576 44.501590
-118.819400 34.192900
-73.897400 42.774700
-101.886595 35.149460
-71.769200 42.249000
-71.887880 41.807116
-73.991458 40.737710
-78.953169 43.053301
-72.484121 42.787935
-122.615410 45.332182
...

Related

update line when dragging point in grouped multiline chart

I'm attempting my first foray into D3.js - the aim is a grouped multiline chart with draggable points, in which dragging a point results in the connecting line being updated too. Eventually, the updated data should be passed back to r (via r2d3() ). So far I managed to get the base plot and to make the points draggable... but when it comes to updating the line (and passing back the data?) I have been hitting a wall for hours now. Hoping someone might help me out?
I'm pasting the full script because I don't trust myself to not have done something truly unexpected anywhere. The code to do with dragging is all positioned at the bottom. In it's current form, dragging a circle makes the first of the lines (the lightblue one) disappear - regardless of which of the circles is dragged. On draggend the lines are drawn again with the default (smaller) stroke, which is of course not how it should work in the end. Ideally the line moves with the drag movement, althoug I'd also be happy if an updated line was drawn back again only after the drag ended.
I think that what I need to know is how to get the identifying info from the dragged circle, use it to update the corresponding variable in data (data is in wide format, btw), and update the corresponding path.
bonus question: drag doesn't work when making x scaleOrdinal (as intended). Is there a workaround for this?
// !preview r2d3 data= data.frame(id = c(1,1,2,2,3,3,4,4,5,5), tt = c(1, 2, 1, 2, 1, 2, 1, 2, 1, 2), val = c(14.4, 19.3, 22.0, 27.0, 20.7, 25.74, 16.9, 21.9, 18.6, 23.6))
var dById = d3.nest()
.key(function(d) {
return d.id;
})
.entries(data);
var margin = {
top: 40,
right: 40,
bottom: 40,
left: 40
},
width = 450 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var color = d3.scaleOrdinal()
.range(["#a6cee3", "#1f78b4", "#b2df8a", "#33a02c", "#fb9a99"]);
var x = d3.scaleLinear()
.range([0.25 * width, 0.75 * width])
.domain([1, 2]);
var y = d3.scaleLinear()
.rangeRound([height, 0])
.domain([0, d3.max(data, function(d) {
return d.val * 1.1;
})]);
var xAxis = d3.axisBottom(x),
yAxis = d3.axisLeft(y);
// Define the line by data variables
var connectLine = d3.line()
.x(function(d) {
return x(d.tt);
})
.y(function(d) {
return y(d.val);
});
svg.append('rect')
.attr('class', 'zoom')
.attr('cursor', 'move')
.attr('fill', 'none')
.attr('pointer-events', 'all')
.attr('width', width)
.attr('height', height)
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var focus = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
focus.selectAll('lines')
.data(dById)
.enter().append("path")
.attr("class", "line")
.attr("d", function(d) {
return connectLine(d.values);
})
.attr("stroke", function(d) {
return color(d.key);
})
.attr('stroke-width', 4);
focus.selectAll('circles')
.data(dById)
.enter().append("g")
.attr("class", "dots")
.selectAll("circle")
.data(function(d) {
return d.values;
})
.enter().append("circle")
.attr("cx", function(d) {
return x(d.tt);
})
.attr("cy", function(d) {
return y(d.val);
})
.attr("r", 6)
.style('cursor', 'pointer')
.attr("fill", function(d) {
return color(d.id);
})
.attr("stroke", function(d) {
return color(d.id);
});
focus.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
focus.append('g')
.attr('class', 'axis axis--y')
.call(yAxis);
/// drag stuff:
let drag = d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended);
focus.selectAll('circle')
.call(drag);
// focus.selectAll('line')
// .call(drag);
function dragstarted(d) {
d3.select(this).raise().classed('active', true);
dragID = Math.round(x.invert(d3.event.x));
// get x at start in order to force the dragged circle to stay at this x-value (i.e. allow it to vertically only)
}
function dragged(d) {
dragNewY = y.invert(d3.event.y);
d3.select(this)
.attr('cx', x(dragID))
.attr('cy', y(dragNewY));
// focus.selectAll('path')
// .attr("d", function(d) { return connectLine(d); }); // removes all lines (to be redrawn at dragended with a smaller stroke)
focus.select('path').attr("d", function(d) {
return connectLine(d);
}); // removes first lines (to be redrawn at dragended with a smaller stroke)
// How do I select only the line associated with the dragged circle?
}
function dragended(d) {
d3.select(this).classed('active', false);
focus.selectAll('lines')
.data(dById)
.enter().append("path")
.attr("class", "line")
.attr("d", function(d) {
return connectLine(d.values);
})
.attr("stroke", function(d) {
return color(d.key);
});
}
Update the data point associated with the circle and then update the circle and all the lines.
Do not add new lines in the dragend()
function dragged(d) {
dragNewY = y.invert(d3.event.y);
d.val = dragNewY;
d3.select(this)
.attr('cx', d => x(d.tt))
.attr('cy', d => y(d.val));
// focus.selectAll('path')
// .attr("d", function(d) { return connectLine(d); }); // removes all lines (to be redrawn at dragended with a smaller stroke)
focus.selectAll('path').attr("d", function(d) {
return connectLine(d.values);
}); // removes first lines (to be redrawn at dragended with a smaller stroke)
// How do I select only the line associated with the dragged circle?
}
function dragended(d) {
d3.select(this).classed('active', false);
// focus.selectAll('lines')
// .data(dById)
// .enter().append("path")
// .attr("class", "line")
// .attr("d", function(d) { return connectLine(d.values); })
// .attr("stroke", function(d) { return color(d.key); });
}

Why mouserover returning all data in callback

I'm new to d3 and currently trying to make a simple line chart using the example provided by Mike Bostock, I have arrieved to the following code.
<!DOCTYPE html>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var parseTime = d3.timeParse("%d-%b-%y");
var x = d3.scaleTime()
.rangeRound([0, width]);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
d3.tsv("data.tsv", function(d) {
d.date = parseTime(d.date);
d.close = +d.close;
return d;
}, function(error, data) {
if (error) throw error;
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain(d3.extent(data, function(d) { return d.close; }));
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.select(".domain");
g.append("g")
.call(d3.axisLeft(y))
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Weight (lbs)");
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line)
.on("mouseover", handleMouseOver);
});
function handleMouseOver(d,i) {
console.log(d);
console.log(i);
}
</script>
taken from the following link, I append the link if you want to test with the sample data https://bl.ocks.org/mbostock/3883245
The thing is that I want to add a new feature where the user can hover over a part of the line and see what is the value of the data at that moment, what I understand is that I append a new path for each entry in the data, the problem is that when I add a callback to the mouseover event that is suppose to receive as a parameter the data being hover like this:
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line)
.on("mouseover", handleMouseOver);
function handleMouseOver(d,i) {
console.log(d);
console.log(i);
}
The console.log(d) shows all the data in the data array and not the specific entry in the array that is being hovered, also the index i always gives 0. I want to know what I'm doing wrong or how can I achieve this. Thanks in advance.
Take the following code for the last append (all else is unchanged; and in that block, I also only changed the lines ending with //!!:
g.append("g").selectAll("path").data(data.slice(0, data.length-1)).enter().append("path") //!!
//.datum(data) //!!
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", function(d,i){return line([data[i], data[i+1]])}) //!!
.on("mouseover", handleMouseOver);
This gives you the correct data and index on mouse over depending on the segment.
Let me dive into the background a little bit:
datum sets the whole data as input for the only instance of path (when checking the DOM in your code above or bl.ocks.org, you'll only see one <path> with an insanely long d. Which is nice, as the line() function can handle this perfectly well. However, you only have ONE element for mouseover which doesn't help
hence, I chose another approach with one path for each line segment: My code has an insane number of <path>s with a very simple d each. However, each path can have a separate mouseover
to not get overwhelmed, I enclosed all the paths in a g, which doesn't hurt anyway
I did use the data() function. You can read up here for details: https://github.com/d3/d3-selection#selection_data
in brief, I tell it to take a selection of all path elements currently under g (none), and append as many new paths as necessary to satisfy the data at hand. Then, for each path, apply the next lines
(this doesn't yet update from a new data, but I want to keep it short)
and finally, to make it sound, I had to slice the data for each input
(I didn't use ES6 syntax for simplicity now, though ES6 would look nicer and is shorter. Doesn't matter for the result, however)

D3 script failing to create points in the canvas after a certain limit

I am trying to draw check in information in a d3 canvas. I'm using this popular script to create the map and draw the points.
I can draw roughly 12000 points, after that the script refuses to draw anything on the canvas. Could someone point out what I might be doing wrong?
<script>
var width = 960,
height = 480;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var projection = d3.geo.equirectangular()
.scale(153)
.translate([width/2,height/2])
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule();
geo_data = [];
d3.csv("data/2008.csv", function(x){
console.log(x.length)
for (i = 12000; i < 24000; i++) {
geo_data.push([x[i].lat, x[i].long])
}
});
d3.json("https://gist.githubusercontent.com/abenrob/787723ca91772591b47e/raw/8a7f176072d508218e120773943b595c998991be/world-50m.json", function(error, world) {
svg.append("g")
.attr("class", "land")
.selectAll("path")
.data([topojson.object(world, world.objects.land)])
.enter().append("path")
.attr("d", path);
svg.append("g")
.attr("class", "boundary")
.selectAll("boundary")
.data([topojson.object(world, world.objects.countries)])
.enter().append("path")
.attr("d", path);
svg.append("g")
.attr("class", "graticule")
.selectAll("path")
.data(graticule.lines)
.enter().append("path")
.attr("d", path);
svg.selectAll("circle")
.data(geo_data)
.enter()
.append("circle")
.attr("cx", function (d) { return projection(d)[0]; })
.attr("cy", function (d) { return projection(d)[1]; })
.attr("r", "2px")
.attr("fill", "red")
});
</script>
The csv file contains information in this format
lat,long
-104.934812, 39.711152
-104.984703, 39.739154
-105.09543, 39.802002

Mapping an arc in D3 using d3.geo.path()

I'm trying to plot an arc connecting two points on a map of the USA.
The code I have used to make the map of the usa is
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule()
.extent([[-98 - 45, 38 - 45], [-98 + 45, 38 + 45]])
.step([5, 5]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
queue()
.defer(d3.json,'us.json')
.await(makeMyMap);
function makeMyMap(error, us) {
if (error) throw error;
svg.insert("path", ".graticule")
.datum(topojson.feature(us, us.objects.land))
.attr("class", "land")
.attr("d", path);
svg.insert("path", ".graticule")
.datum(topojson.mesh(us, us.objects.counties, function(a, b) { return a !== b && !(a.id / 1000 ^ b.id / 1000); }))
.attr("class", "county-boundary")
.attr("d", path);
svg.insert("path", ".graticule")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("class", "state-boundary")
.attr("d", path);
drawPath()
}
function drawPath() {
var route = svg.insert("path", ".graticule")
.datum({type: "LineString", coordinates: [[33,-118], [38.6,-78]]})
.attr("class", "route")
.attr("d", path);
}
Currently the path that is drawn by the drawPath() function is being made somewhere, but I can't view it on the map. If I don't set fill: none in the CSS then the screen will be blacked out, but setting it to a colour just makes the canvas be covered by that color.
The us.json file is used to make the map and is a topojson object.
You screwed up the positions for your LineString. According to the spec positions are specified as [longitude,latitude]. Since values for latitude cannot exceed 90 degrees, it's obvious that you need to switch the order of your coordinate values:
.datum({type: "LineString", coordinates: [[-118,33], [-78,38.6]]})
Thanks to the comment by Mark who took the time and made the effort this is also available in his working demo.

Placing Lat/Long on a azimuthalEqualArea map with D3

I'm trying to plot some lat/long points onto a map, but I can't get them to appear in the correct place.
The dots should be in San Francisco. I have a JSfiddle of the code.
var width = 400,
height = 400;
var projection = d3.geo.azimuthalEqualArea()
.clipAngle(180 - 1e-3)
.scale(100)
.translate([width / 2, height / 2])
.precision(.1);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("defs").append("path")
.datum({type: "Sphere"})
.attr("id", "sphere")
.attr("d", path);
svg.append("use")
.attr("class", "stroke")
.attr("xlink:href", "#sphere");
svg.append("use")
.attr("class", "fill")
.attr("xlink:href", "#sphere");
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
d3.json("http://bl.ocks.org/mbostock/raw/4090846/world-50m.json", function(error, world) {
svg.insert("path", ".graticule")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
svg.insert("path", ".graticule")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
});
var latlong = {"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[37.3838312,-122.0423922]},"properties":{"timestampMs":1415894666875}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.3838474,-122.0423972]},"properties":{"timestampMs":1415894601718}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.3838474,-122.0423972]},"properties":{"timestampMs":1415894536288}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.3838411,-122.0424015]},"properties":{"timestampMs":1415894471356}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.383878,-122.0423925]},"properties":{"timestampMs":1415894406257}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.3838317,-122.0423856]},"properties":{"timestampMs":1415894326769}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.3838287,-122.0423933]},"properties":{"timestampMs":1415894261810}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.383829,-122.0423847]},"properties":{"timestampMs":1415894196224}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.3838765,-122.0424141]},"properties":{"timestampMs":1415894131768}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.3838177,-122.0423668]},"properties":{"timestampMs":1415894066809}}]};
// THESE ARE THE POINTS THAT ARE NOT BEING PLACED CORRECTLY
svg.selectAll("circle")
.data(latlong.features).enter()
.append("circle")
.attr("cx", function (d) { return projection(d.geometry.coordinates)[1]; })
.attr("cy", function (d) { return projection(d.geometry.coordinates)[0]; })
.attr("r", "2px")
.attr("fill", "red");
You've specified the latitude and longitude the wrong way round for d3.geo and you're also taking the output the wrong way round. It is counter to the way that they are displayed by convention (N/S then E/W) but it is more consistent with a drawing convention of across then up/down.
From path.projection in the D3 API Geo reference:
A projection function takes a two-element array of numbers
representing the coordinates of a location, [longitude, latitude], and
returns a similar two-element array of numbers representing the
projected pixel position [x, y].
To fix this, I've reversed the coordinates of your FeatureCollection:
var latlong = {"type":"FeatureCollection","features":[
{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.0423922,37.3838312]},"properties":{"timestampMs":1415894666875}},
etc...
and reversed the coordinates of your plot.
.attr("cx", function (d) { return projection(d.geometry.coordinates)[0]; })
.attr("cy", function (d) { return projection(d.geometry.coordinates)[1]; })
Everything else was fine. Amended JSFiddle here. So often it's the little things!

Categories