0

Here is a simple yet inspiring topojson of a single state:

https://bl.ocks.org/mbostock/7061976

It is drawn by data from a json containing only that state as follows:

d3.json("va-counties.json", function(error, topo) {
  if (error) throw error;

What I want to do is dynamically project a county. Suppose there is a keyboard event or something that runs a function doing this: read into the parsed data, find the county id, and return a topojson feature of only that county. The difference between the above block and my case is that my json file would have all the counties in America, but I would only need 1 county at a time. Is there a way to achieve this in D3?

Just as a simple litmus test, for county id=1000, I tried:

  var current_county = topojson.feature(topo, topo.objects.counties).filter(function(d) { return d.id=1000;})),
      bounds = path.bounds(county);

Yet I kept getting persistent errors, no matter how much I toiled with it. Or it would stop throwing errors, but yet still not 'work'. Maybe .filter() is not the best tool for the job? What are other opinions?

Thank you for reading

Arash Howaida
  • 2,575
  • 2
  • 19
  • 50

1 Answers1

5

Well first of all your filter syntax is wrong, I think you meant a comparison and not an assignment:

d.id === 1000

Second, topojson.feature returns GeoJSON which in an object and it's just not going to filter like that. Your best bet is to filter it on the way in:

// filter the geometries of the topojson the structure you want
var geoCounty = topo.objects.counties.geometries.filter(function(d){
  return d.id === "51750";
});

// assign it back to the topojson object
topo.objects.counties.geometries = geoCounty;

// and off you go...
var county = topojson.feature(topo, topo.objects.counties),
    bounds = path.bounds(county);

Full running code:

<!DOCTYPE html>
<meta charset="utf-8">
<style>

.county {
  fill: #ccc;
}

</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/d3.geo.projection.v0.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script>

var width = 500,
    height = 300;

var projection = d3.geo.conicConformal()
    .parallels([38 + 02 / 60, 39 + 12 / 60])
    .rotate([78 + 30 / 60, 0])
    .scale(200000)
    .translate([0, 0]);

var path = d3.geo.path()
    .projection(projection);

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

d3.json("https://jsonblob.com/api/ce96ca06-e1ce-11e6-90ab-03e5986c4e20", function(error, topo) {
  if (error) throw error;

  var geoCounty = topo.objects.counties.geometries.filter(function(d){
    return d.id === "51750";
  });

  topo.objects.counties.geometries = geoCounty;

  var county = topojson.feature(topo, topo.objects.counties),
      bounds = path.bounds(county);

  projection
      .translate([width / 2 - (bounds[0][0] + bounds[1][0]) / 2, height / 2 - (bounds[0][1] + bounds[1][1]) / 2]);

  svg.append("path")
      .datum(county)
      .attr("class", "county")
      .attr("d", path);

});

</script>
Mark
  • 106,305
  • 20
  • 172
  • 230
  • That makes so much sense now. Thank you for showing me the right convention. And you are right, I should have used a comparison `===`. I'm trying to test out your example, but I'm having issues with pointing the libraries. I keep getting "Error: Invalid or unexpected token." from the d3.projection.js file. I know D3.js is utf-8, is that one something different? What should I do?? – Arash Howaida Jan 24 '17 at 06:40
  • @ArashHowaida, that error message should point you to a line of code. When you inspect it does something look off? Is the error really coming from `d3.projection.js` or your code? It'll be next to impossible for me to debug that for you without reproducing it. – Mark Jan 24 '17 at 12:50
  • it says `d3js.org/d3.geo.projection.v0.min.js` line 1, not from the main html. Let me run some tests to see what encoding it is. – Arash Howaida Jan 24 '17 at 12:57
  • I am really baffled. Would your approach work if we omitted `d3.geo.projection.v0.min.js`? In favor of the standard projection way? I guess the angles would be different, but wouldn't be all that noticeable. I tried just changing the projection function, but it seems there were some other calls to the geo.projection script aside from the projection variable. Until I figure out what's wrong with my referencing of the library, we could use that as a solution. – Arash Howaida Jan 24 '17 at 15:50
  • @ArashHowaida, sure. [Here is it](http://plnkr.co/edit/oWJMBdIMGBhyZFwOPj7X?p=preview) running with the standard `d3.geo.albersUsa`. – Mark Jan 24 '17 at 16:25
  • It works great! Thanks for the work-around. My last question is, why is it that when I change the json file it doesn't work? Dev tools didn't throw any errors, but nothing is projected. I'm just using the `us.json` here: https://bl.ocks.org/mbostock/raw/4090846/us.json. You can try substituting to this json to see what I mean. Perhaps I need to change a few headers? – Arash Howaida Jan 25 '17 at 04:18
  • @ArashHowaida, it still works for me with that file. I did notice, though, that the county ids in that file are **numeric** and **not strings** like in the previous file. Here's the [county where I grew up](http://plnkr.co/edit/w3yeCT0VTlx0IlUHLZG6?p=preview). – Mark Jan 25 '17 at 14:17
  • Oh cool, good example. Unfortunately, even after removing the quotes, it doesn't work. I must have missed something obvious, I will get back to you when I figure it out. -- Edit, I figured it out. Turns out to be an issue of different id conventions. – Arash Howaida Jan 25 '17 at 15:53
  • We will soon have a world record for # of comments! Actually in all seriousness, I'm having trouble using this 'dynamically' as in the post title. I put the relevant parts of your code in a function, as I had this in mind for multiple calls (as in projecting different counties as per different events, but still 1 at a time.) Yet it seems the line: `topo.objects.counties.geometries = geoCounty;` or some other line is interfering with subsequent calls to the project function. I tried deleting that line, but it made everything really slow and changed the scale. Can we make it more "dynamic"? – Arash Howaida Jan 27 '17 at 09:30
  • @ArashHowaida, my guess is that you have two problems. First, is the `topo` object getting modified and then can't be used to filter subsequent counties. You can [clone it](http://stackoverflow.com/questions/728360/how-do-i-correctly-clone-a-javascript-object) to keep a working copy around. Second, I'm not having any luck re-setting the projection's translation to use it over and over again. I've found I have to recreate it on each "draw". See updated plunker [here](http://plnkr.co/edit/w3yeCT0VTlx0IlUHLZG6?p=preview), which draws random counties in Maryland. – Mark Jan 27 '17 at 14:20
  • Wow that's great, almost exactly what I was trying to do. Hopefully the computational efficiency isn't affected tooo much by 'drawing' each time. Either way, I can finish my project now, thanks for your continued support. I learned a lot from this. – Arash Howaida Jan 27 '17 at 15:27