最新消息:Welcome to the puzzle paradise for programmers! Here, a well-designed puzzle awaits you. From code logic puzzles to algorithmic challenges, each level is closely centered on the programmer's expertise and skills. Whether you're a novice programmer or an experienced tech guru, you'll find your own challenges on this site. In the process of solving puzzles, you can not only exercise your thinking skills, but also deepen your understanding and application of programming knowledge. Come to start this puzzle journey full of wisdom and challenges, with many programmers to compete with each other and show your programming wisdom! Translated with DeepL.com (free version)

d3.js - How to divide a map into zipcodes using d3, javascript, and a json file? - Stack Overflow

matteradmin4PV0评论

I'm trying to create a nyc map with zipcode areas I can color in based on census data (like color an area red if majority white or blue if majority nonwhite). I am simply using one of the shape files I found online from here ( ).

I converted the shp file to a geojson and then a topojson file.

I'd appreciate it if someone could look at my code below, and let me know how I can go about doing this.

Code:

<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>
<body>
<script src="//d3js/d3.v3.min.js" charset="utf-8"></script>
<script src="//d3js/topojson.v1.min.js"></script>

<script>

var width = 500,
    height = 500;

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

  var projection = d3.geo.albers()
   .center([0,40.7])
   .rotate([74,0])
   .translate([width/2,height/2])
   .scale(65000);

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

d3.json("zipcode.json", function(error, uk) {
    console.log(uk)
    console.log(uk.objects)
    console.log(uk.objects.zipcode)
  if (error) return console.error(error);
  var subunits = topojson.feature(uk, uk.objects.zipcode);

    svg.append("path")
        .datum(subunits)
        .attr("d", path);
});

Output:

The last part of my code (and the first part) is modeled after /. I understand I am trying to "Select All" of the some sort of feature array from the json file I am using in order to create paths. In my data, there's a coordinates array, which I am trying to access and use. My code doesn't throw any errors so I'm not sure where to look to debug.

Also, I'm I supposed to color the areas the paths create in this step or after I create the paths?

I'm trying to create a nyc map with zipcode areas I can color in based on census data (like color an area red if majority white or blue if majority nonwhite). I am simply using one of the shape files I found online from here ( https://data.cityofnewyork.us/Business/Zip-Code-Boundaries/i8iw-xf4u/data ).

I converted the shp file to a geojson and then a topojson file.

I'd appreciate it if someone could look at my code below, and let me know how I can go about doing this.

Code:

<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>
<body>
<script src="//d3js/d3.v3.min.js" charset="utf-8"></script>
<script src="//d3js/topojson.v1.min.js"></script>

<script>

var width = 500,
    height = 500;

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

  var projection = d3.geo.albers()
   .center([0,40.7])
   .rotate([74,0])
   .translate([width/2,height/2])
   .scale(65000);

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

d3.json("zipcode.json", function(error, uk) {
    console.log(uk)
    console.log(uk.objects)
    console.log(uk.objects.zipcode)
  if (error) return console.error(error);
  var subunits = topojson.feature(uk, uk.objects.zipcode);

    svg.append("path")
        .datum(subunits)
        .attr("d", path);
});

Output:

The last part of my code (and the first part) is modeled after https://bost.ocks/mike/map/. I understand I am trying to "Select All" of the some sort of feature array from the json file I am using in order to create paths. In my data, there's a coordinates array, which I am trying to access and use. My code doesn't throw any errors so I'm not sure where to look to debug.

Also, I'm I supposed to color the areas the paths create in this step or after I create the paths?

Share edited Mar 6, 2017 at 6:12 pr338 asked Mar 6, 2017 at 2:18 pr338pr338 9,19020 gold badges56 silver badges72 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 13

This answer uses d3 v3 and considers census tracts rather than zip codes (reflecting the original edit, but the principles remain the same)

The selection's role in adding features:

I understand I am trying to "Select All" of the some sort of feature array from the json file I am using in order to create paths.

Rather than selecting something from the json file, you are selecting elements in the DOM. D3 will bind the data in the json to the features where they exist, produce an enter() selection where they don't, and produce an exit() selection where there are excess DOM elements selected in relation to the json data.

This is why the initial appending of data with a selectAll(type).data(data) statement is followed with an .enter() statement generally. The enter returns the elements that must be added to the DOM:

svg.selectAll(".tract")
    // bind data to the selection
    .data(topojson.feature(uk, uk.objects.nyct2010).features)
  .enter()
    // set properties for the new elements:
    .append("path") 
    .attr("class", "tract")
    .attr("d", path);

If you were updating the data - say showing some year by year property in your maps, you wouldn't need the .enter() if the number of geographic features was constant (likely the case), you would just set the data and then modify the properties. If the number of elements in your new data array is the same as the old one, then the enter() selection will actually be empty.

The intial append with this method generally assumes the selectAll statement is empty, so that all items in the data are appended with the enter selection, this causes many people a lot of grief (a) (b) (c) (d) (e) (f).

When using the alternate approach:

svg.append('path')
  .datum(subunits)
  .attr('d',path')

You just append one path element enpassing all features, which makes styling individual areas impossible. In contrast, the top approach appends one path for each element in your json.

Setting map attributes:

You may have difficulty in setting the the class of each path to d.coordinates. Using topojson.feature(data, data.objects.features).features returns geojson from your topojson. The coordinates property of each feature is an array - which might not won't work with a class declaration.

But, you have the right approach. An inline function can set attributes easily:

var color = d3.scale.category20();

svg.selectAll("path")
  .data(subunits) // from the question code.
  .enter()
  .append('path')
  .attr('fill',function(d,i) { return color(i); })
  .attr("d", path);

Using this I get:

(block)

But, let's look at d in that inline function above ( .attr('fill',function(d,i) { console.log(d); return color(i); }) ). It is a geojson object:

Object { type: "Feature", properties: Object, geometry: Object }

If you don't see any properties (it'll always have a properties property, but it might be empty or contain only methods), you have some bad news, the properties are empty. Consequently, there are no properties containing data that can be displayed - eg coloring the map. There are also no identifiers in the data. This makes joining outside data to each feature impossible and there is no data in the feature to show. Topojson doesn't press properties so you should be able to see them if they are present in the text of the file:

..."Polygon","properties":{"CTLabel":"1223","BoroCode":"4","BoroName":"Queens","CT2010":"...

Showing properties of geographical features

You'll need to find a geographical dataset that has properties. Property-less features might be great for backgrounds, but less useful for everything else.

I found a source of the 2010 census tracts here. I downloaded the shapefile and converted it to topojson at mapshaper (be sure to copy all the files into the window - drag and drop - so that the data and the projection data is transfered). The data is already projected (to the New York State Plane), so you should unproject/'project' it to WGS84 by typing proj wgs84 in the console. This answer might help in terms of understanding projected/unprojected data and d3

The file I'm working with has the property BoroCode which I'll use to display in a choropleth type display:

svg.selectAll("path")
   .data(topojson.feature(data, data.objects.nyct2010).features)
   .enter()
   .append('path')
   .attr('fill',function(d) {return color(d.properties.BoroCode); })
   .attr("d", path);

This gives me:

(block)

Joining data to features

Many shapefiles, topojsons, geosjons, feature classes etc don't include many properties/attributes. These files containing geographic coordinates are often joined to files that contain properties/attributes (but no coordinates) in a data join based on an identifier shared in each data source.

There is an excellent example here on that in practice, though a better explanation might be here. I'll use the one of the few files I could find (relatively quickly and free) with census tract identifiers. Census information is generally great as it contains standardized identifiers. This file is a csv containing disposable ine data.

Now with the shared identifier, we can show the geographic shapes and assign colors to them based on the ine values in the csv.

Once both files are loaded, I'll make a dictionary:

var lookup = {};
ine.forEach(function(d) { lookup[d.tractID] = +d.disposable_ine; });

and then I'll show the features, almost the same as above:

svg.selectAll("path")
   .data(topojson.feature(data, data.objects.nyct2010).features)
   .enter()
   .append('path')
   .attr('fill',function(d) { return color(lookup[parseInt(d.properties.CT2010)] ); })
   .attr("d", path);

I used parseInt as I modified the csv's in Excel and lost the leading zeros in the csv, parseInt drops the leading zeros from my geojson identifiers.

The end result looks something like:

(block)

If you took a look at the block, you'll see I nested d3.csv inside the d3.json callback function. Each of these is asynchronous, so unless we use a library like queue.js we'll need to wait until the json is loaded before loading the csv. This nested approach solves the problem of potentially styling the features before the csv is loaded

This should cover how to color a map either based on increment, property within the geo/topo json and/or by joining the data from a non-spatial source to the spatial shapes. See the blocks for the implementation of a color scale, without a proper scale color(i) won't return anything.

Articles related to this article

Post a comment

comment list (0)

  1. No comments so far