Share of jobs in manufacturing by commuter area from 1990 to the 2016. Hover to isolate an individual commuter area’s series. Data are not seasonally-adjusted.
To keep the size of this graphic down I shrunk each faceted chart based on it’s max y-value while keeping the y-scale consistent across charts. At first I just let the browser float each chart in a container but this left weird gaps between charts. Looked into it and found that fitting irregular shapes into other shapes neatly is called the bin packing problem. I implemented a 2D rectangular bin packing algorithm in JavaScript based on Jukka Jylanki’s C++ implementation of the algorithm. This let me lay the charts out snugly.
This bin packing implementation can be found in this GitHub repository: BinPack. The API is still a bit rough at the moment.
Data come from the Bureau of Labor Statistics. The
download-data.sh
shell script in this gist repository shows how to download
the data in bulk. The clean-data.R
R script shows how the data was prepped
for the visualization. The little state icons are from ProPublica’s
StateFace project.
<html>
<head>
<style>
@font-face {
font-family: 'StateFaceRegular';
src: url('stateface-regular-webfont.eot');
src: url('stateface-regular-webfont.eot?#iefix') format('embedded-opentype'),
url('stateface-regular-webfont.woff') format('woff'),
url('stateface-regular-webfont.ttf') format('truetype'),
url('stateface-regular-webfont.svg#StateFaceRegular') format('svg');
font-weight: normal;
font-style: normal;
}
.stateface {
font-family: "StateFaceRegular";
}
.line {
fill: none;
stroke: #000;
stroke-opacity: 0.2;
}
.line.highlight {
stroke-width: 1.5px;
stroke-opacity: 1;
}
.chart-title {
fill: #000;
font-size: 16px;
text-shadow: -1px 0 0px #fff,
0 1px 0px #fff,
1px 0 0px #fff,
0 -1px 0px #fff;
}
.chart-title > .stateface {
text-anchor: end;
}
.chart-title > .name {
text-anchor: start;
}
.chart-title .metro {
font-size: 12px;
}
.data-label {
font-size: 12px;
text-anchor: middle;
text-shadow: -2px 0 0px #fff,
0 2px 0px #fff,
2px 0 0px #fff,
0 -2px 0px #fff;
}
.data-label circle {
fill: #fff;
stroke: #000;
}
.axis--x path {
stroke: none;
}
.voronoi-overlay {
fill-opacity: 0;
}
.hidden {
display: none;
}
</style>
</head>
<body>
<script src="bin-pack.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svgWidth = 960,
svgHeight = 1700;
var binPack = BinPack()
.binWidth(svgWidth)
.binHeight(svgHeight);
var margin = { top: 30, left: 40, bottom: 30, right: 10 },
width = svgWidth / 4 - margin.left - margin.right,
maxHeight = 200 - margin.top - margin.bottom;
var parseDate = d3.timeParse("%Y-%m-%d");
var formatShare = d3.format(".0%");
var x = function(d) { return d.date; },
xScale = d3.scaleTime().range([0, width]);
var y = function(d) { return d.share; },
yScale = d3.scaleLinear().range([maxHeight, 0]);
var localYScale = d3.local();
var localLine = d3.local();
var svg = d3.select("body").append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight);
var stateByCode = d3.map(),
areaByCode = d3.map(),
statefaceByCode = d3.map();
d3.queue()
.defer(d3.tsv, "state-names.tsv")
.defer(d3.tsv, "area-names.tsv")
.defer(d3.tsv, "manufacturing-share.tsv")
.await(ready);
function ready(error, stateNames, areaNames, data) {
if (error) throw error;
stateNames.forEach(function(d) {
stateByCode.set(d.state_code, d.state_name);
statefaceByCode.set(d.state_code, d.stateface_letter);
});
areaNames.forEach(function(d) {
areaByCode.set(d.area_code, d.area_name);
});
data = data
.map(type)
.filter(function(d) {
return d.date.getFullYear() > 1989 & d.share !== undefined;
});
xScale.domain(d3.extent(data, x));
yScale.domain([0, d3.max(data, y)]);
var nested = d3.nest()
.key(function(d) { return d.state_code; })
.key(function(d) { return d.area_code; })
.entries(data);
nested.sort(function(stateA, stateB) {
var a = stateByCode.get(stateA.key),
b = stateByCode.get(stateB.key);
return a > b ? 1 : -1;
});
//____________________________________________________________________________
// Line chart container (one for each state)
var chart = svg.selectAll(".line-chart").data(nested)
.enter().append("g")
.attr("class", "line-chart")
.each(function(state) {
var flattened = state.values
.map(function(d) { return d.values; })
.reduce(function(a, b) { return a.concat(b); });
var yMax = d3.max(flattened, y),
height = maxHeight - yScale(yMax);
var thisYScale = localYScale.set(this, d3.scaleLinear()
.domain([0, yMax])
.range([height, 0]));
localLine.set(this, d3.line()
.x(function(d) { return xScale(x(d)); })
.y(function(d) { return thisYScale(y(d)); }));
state.width = width + margin.left + margin.right;
state.height = height + margin.top + margin.bottom;
state.voronoi = d3.voronoi()
.x(function(d) { return xScale(x(d)); })
.y(function(d) { return thisYScale(y(d)); })
.size([width, height])
.polygons(flattened
.filter(function(d) { return d.date.getMonth() == 1; }));
binPack.add(state);
})
.attr("transform", function(state) {
var i = binPack.positioned
.map(function(d) { return d.datum; })
.indexOf(state);
var p = binPack.positioned[i];
return "translate(" + (p.x + margin.left) + "," +
(p.y + margin.top ) + ")";
});
//____________________________________________________________________________
// Axes
var xAxis = chart.append("g")
.call(d3.axisBottom(xScale).ticks(5))
.attr("class", "axis axis--x")
.attr("transform", function(d) {
var height = localYScale.get(this).range()[0];
return "translate(0," + height + ")";
});
var yAxis = chart.append("g")
.each(function(d) {
var yMax = localYScale.get(this).domain()[1],
yAxis = d3.axisLeft(localYScale.get(this))
.ticks(d3.tickStep(0, yMax, 0.05))
.tickFormat(formatShare);
d3.select(this)
.call(yAxis);
})
.attr("class", "axis axis--y");
//____________________________________________________________________________
// Chart title
var chartTitle = yAxis.append("g")
.attr("class", "chart-title")
.attr("transform", "translate(0,-10)");
chartTitle.append("text")
.attr("class", "stateface")
.attr("dx", -6)
.text(function(d) { return statefaceByCode.get(d.key); });
chartTitle.append("text")
.attr("class", "name")
.text(function(d) { return stateByCode.get(d.key); });
//____________________________________________________________________________
// Line
var line = chart.selectAll(".line").data(function(d) { return d.values; })
.enter().append("path")
.attr("class", function(d) { return "line a-" + d.key; })
.attr("d", function(d) {
return localLine.get(this)(d.values);
});
//____________________________________________________________________________
// Data label
var dataLabel = chart.append("g")
.attr("class", "data-label")
.classed("hidden", true);
dataLabel.append("text")
.attr("dy", "-.67em");
dataLabel.append("circle")
.attr("r", 3);
//____________________________________________________________________________
// Voronoi overlay for mouse interaction
chart.append("g")
.attr("class", "voronoi-overlay")
.selectAll(".polygon").data(function(d) { return d.voronoi; })
.enter().append("path")
.attr("class", "polygon")
.attr("d", renderCell)
.on("mouseenter", mouseenter)
.on("mouseleave", mouseleave);
function mouseenter(d) {
var area_code = d.data.area_code,
state_code = d.data.state_code,
p = [
xScale(x(d.data)),
localYScale.get(this)(y(d.data))
];
line
.classed("highlight", function(d) { return d.key == area_code; });
chartTitle.selectAll(".name")
.filter(function(d) { return d.key == state_code; })
.classed("metro", true)
.text(areaByCode.get(area_code));
var label = dataLabel
.filter(function(d) { return d.key == state_code; })
.classed("hidden", false);
label.select("text")
.attr("x", p[0])
.attr("y", p[1])
.text(formatShare(d.data.share));
label.select("circle")
.attr("cx", p[0])
.attr("cy", p[1]);
}
function mouseleave(d) {
var area_code = d.data.area_code,
state_code = d.data.state_code;
line
.classed("highlight", false);
chartTitle.selectAll(".name")
.filter(function(d) { return d.key == state_code; })
.classed("metro", false)
.text(stateByCode.get(state_code));
dataLabel
.classed("hidden", true);
};
}
function type(d) {
d.date = parseDate(d.date);
d.share = +d.share;
return d;
}
function renderCell(d) {
return d == null ? null : "M" + d.join("L") + "Z";
}
</script>
</body>
</html>
area_code area_name
00000 Statewide
10180 Abilene, TX
10380 Aguadilla-Isabela, PR
10420 Akron, OH
10500 Albany, GA
10540 Albany, OR
10580 Albany-Schenectady-Troy, NY
10740 Albuquerque, NM
10780 Alexandria, LA
10900 Allentown-Bethlehem-Easton, PA-NJ
11020 Altoona, PA
11100 Amarillo, TX
11180 Ames, IA
11244 Anaheim-Santa Ana-Irvine, CA Metropolitan Division
11260 Anchorage, AK
11460 Ann Arbor, MI
11500 Anniston-Oxford-Jacksonville, AL
11540 Appleton, WI
11640 Arecibo, PR
11700 Asheville, NC
12020 Athens-Clarke County, GA
12060 Atlanta-Sandy Springs-Roswell, GA
12100 Atlantic City-Hammonton, NJ
12220 Auburn-Opelika, AL
12260 Augusta-Richmond County, GA-SC
12420 Austin-Round Rock, TX
12540 Bakersfield, CA
12580 Baltimore-Columbia-Towson, MD
12940 Baton Rouge, LA
12980 Battle Creek, MI
13020 Bay City, MI
13140 Beaumont-Port Arthur, TX
13220 Beckley, WV
13380 Bellingham, WA
13460 Bend-Redmond, OR
13740 Billings, MT
13780 Binghamton, NY
13820 Birmingham-Hoover, AL
13900 Bismarck, ND
13980 Blacksburg-Christiansburg-Radford, VA
14010 Bloomington, IL
14020 Bloomington, IN
14100 Bloomsburg-Berwick, PA
14260 Boise City, ID
14500 Boulder, CO
14540 Bowling Green, KY
14740 Bremerton-Silverdale, WA
15180 Brownsville-Harlingen, TX
15260 Brunswick, GA
15380 Buffalo-Cheektowaga-Niagara Falls, NY
15500 Burlington, NC
15680 California-Lexington Park, MD
15804 Camden, NJ Metropolitan Division
15940 Canton-Massillon, OH
15980 Cape Coral-Fort Myers, FL
16020 Cape Girardeau, MO-IL
16060 Carbondale-Marion, IL
16180 Carson City, NV
16220 Casper, WY
16300 Cedar Rapids, IA
16540 Chambersburg-Waynesboro, PA
16580 Champaign-Urbana, IL
16620 Charleston, WV
16700 Charleston-North Charleston, SC
16740 Charlotte-Concord-Gastonia, NC-SC
16820 Charlottesville, VA
16860 Chattanooga, TN-GA
16940 Cheyenne, WY
16974 Chicago-Naperville-Arlington Heights, IL Metropolitan Division
16980 Chicago-Naperville-Elgin, IL-IN-WI
17020 Chico, CA
17140 Cincinnati, OH-KY-IN
17300 Clarksville, TN-KY
17420 Cleveland, TN
17460 Cleveland-Elyria, OH
17660 Coeur d'Alene, ID
17780 College Station-Bryan, TX
17820 Colorado Springs, CO
17860 Columbia, MO
17900 Columbia, SC
17980 Columbus, GA-AL
18020 Columbus, IN
18140 Columbus, OH
18580 Corpus Christi, TX
18700 Corvallis, OR
18880 Crestview-Fort Walton Beach-Destin, FL
19060 Cumberland, MD-WV
19100 Dallas-Fort Worth-Arlington, TX
19124 Dallas-Plano-Irving, TX Metropolitan Division
19140 Dalton, GA
19180 Danville, IL
19300 Daphne-Fairhope-Foley, AL
19340 Davenport-Moline-Rock Island, IA-IL
19380 Dayton, OH
19460 Decatur, AL
19500 Decatur, IL
19660 Deltona-Daytona Beach-Ormond Beach, FL
19740 Denver-Aurora-Lakewood, CO
19780 Des Moines-West Des Moines, IA
19804 Detroit-Dearborn-Livonia, MI Metropolitan Division
19820 Detroit-Warren-Dearborn, MI
20020 Dothan, AL
20100 Dover, DE
20220 Dubuque, IA
20260 Duluth, MN-WI
20500 Durham-Chapel Hill, NC
20524 Dutchess County-Putnam County, NY Metropolitan Division
20700 East Stroudsburg, PA
20740 Eau Claire, WI
20940 El Centro, CA
20994 Elgin, IL Metropolitan Division
21060 Elizabethtown-Fort Knox, KY
21140 Elkhart-Goshen, IN
21300 Elmira, NY
21340 El Paso, TX
21500 Erie, PA
21660 Eugene, OR
21780 Evansville, IN-KY
21820 Fairbanks, AK
22020 Fargo, ND-MN
22140 Farmington, NM
22180 Fayetteville, NC
22220 Fayetteville-Springdale-Rogers, AR-MO
22380 Flagstaff, AZ
22420 Flint, MI
22500 Florence, SC
22520 Florence-Muscle Shoals, AL
22540 Fond du Lac, WI
22660 Fort Collins, CO
22744 Fort Lauderdale-Pompano Beach-Deerfield Beach, FL Metropolitan Division
22900 Fort Smith, AR-OK
23060 Fort Wayne, IN
23104 Fort Worth-Arlington, TX Metropolitan Division
23420 Fresno, CA
23460 Gadsden, AL
23540 Gainesville, FL
23580 Gainesville, GA
23844 Gary, IN Metropolitan Division
23900 Gettysburg, PA
24020 Glens Falls, NY
24140 Goldsboro, NC
24220 Grand Forks, ND-MN
24260 Grand Island, NE
24300 Grand Junction, CO
24340 Grand Rapids-Wyoming, MI
24420 Grants Pass, OR
24500 Great Falls, MT
24540 Greeley, CO
24580 Green Bay, WI
24660 Greensboro-High Point, NC
24780 Greenville, NC
24860 Greenville-Anderson-Mauldin, SC
25020 Guayama, PR
25060 Gulfport-Biloxi-Pascagoula, MS
25180 Hagerstown-Martinsburg, MD-WV
25220 Hammond, LA
25260 Hanford-Corcoran, CA
25420 Harrisburg-Carlisle, PA
25500 Harrisonburg, VA
25620 Hattiesburg, MS
25860 Hickory-Lenoir-Morganton, NC
25940 Hilton Head Island-Bluffton-Beaufort, SC
25980 Hinesville, GA
26140 Homosassa Springs, FL
26300 Hot Springs, AR
26380 Houma-Thibodaux, LA
26420 Houston-The Woodlands-Sugar Land, TX
26580 Huntington-Ashland, WV-KY-OH
26620 Huntsville, AL
26820 Idaho Falls, ID
26900 Indianapolis-Carmel-Anderson, IN
26980 Iowa City, IA
27060 Ithaca, NY
27100 Jackson, MI
27140 Jackson, MS
27180 Jackson, TN
27260 Jacksonville, FL
27340 Jacksonville, NC
27500 Janesville-Beloit, WI
27620 Jefferson City, MO
27740 Johnson City, TN
27780 Johnstown, PA
27860 Jonesboro, AR
27900 Joplin, MO
27980 Kahului-Wailuku-Lahaina, HI
28020 Kalamazoo-Portage, MI
28100 Kankakee, IL
28140 Kansas City, MO-KS
28420 Kennewick-Richland, WA
28660 Killeen-Temple, TX
28700 Kingsport-Bristol-Bristol, TN-VA
28740 Kingston, NY
28940 Knoxville, TN
29020 Kokomo, IN
29100 La Crosse-Onalaska, WI-MN
29180 Lafayette, LA
29200 Lafayette-West Lafayette, IN
29340 Lake Charles, LA
29404 Lake County-Kenosha County, IL-WI Metropolitan Division
29420 Lake Havasu City-Kingman, AZ
29460 Lakeland-Winter Haven, FL
29540 Lancaster, PA
29620 Lansing-East Lansing, MI
29700 Laredo, TX
29740 Las Cruces, NM
29820 Las Vegas-Henderson-Paradise, NV
29940 Lawrence, KS
30020 Lawton, OK
30140 Lebanon, PA
30300 Lewiston, ID-WA
30460 Lexington-Fayette, KY
30620 Lima, OH
30700 Lincoln, NE
30780 Little Rock-North Little Rock-Conway, AR
30860 Logan, UT-ID
30980 Longview, TX
31020 Longview, WA
31080 Los Angeles-Long Beach-Anaheim, CA
31084 Los Angeles-Long Beach-Glendale, CA Metropolitan Division
31140 Louisville/Jefferson County, KY-IN
31180 Lubbock, TX
31340 Lynchburg, VA
31420 Macon, GA
31460 Madera, CA
31540 Madison, WI
31740 Manhattan, KS
31860 Mankato-North Mankato, MN
31900 Mansfield, OH
32420 Mayaguez, PR
32580 McAllen-Edinburg-Mission, TX
32780 Medford, OR
32820 Memphis, TN-MS-AR
32900 Merced, CA
33100 Miami-Fort Lauderdale-West Palm Beach, FL
33124 Miami-Miami Beach-Kendall, FL Metropolitan Division
33140 Michigan City-La Porte, IN
33220 Midland, MI
33260 Midland, TX
33340 Milwaukee-Waukesha-West Allis, WI
33460 Minneapolis-St. Paul-Bloomington, MN-WI
33540 Missoula, MT
33660 Mobile, AL
33700 Modesto, CA
33740 Monroe, LA
33780 Monroe, MI
33860 Montgomery, AL
33874 Montgomery County-Bucks County-Chester County, PA Metropolitan Division
34060 Morgantown, WV
34100 Morristown, TN
34580 Mount Vernon-Anacortes, WA
34620 Muncie, IN
34740 Muskegon, MI
34820 Myrtle Beach-Conway-North Myrtle Beach, SC-NC
34900 Napa, CA
34940 Naples-Immokalee-Marco Island, FL
34980 Nashville-Davidson--Murfreesboro--Franklin, TN
35004 Nassau County-Suffolk County, NY Metropolitan Division
35084 Newark, NJ-PA Metropolitan Division
35100 New Bern, NC
35380 New Orleans-Metairie, LA
35614 New York-Jersey City-White Plains, NY-NJ Metropolitan Division
35620 New York-Newark-Jersey City, NY-NJ-PA
35660 Niles-Benton Harbor, MI
35840 North Port-Sarasota-Bradenton, FL
36084 Oakland-Hayward-Berkeley, CA Metropolitan Division
36100 Ocala, FL
36140 Ocean City, NJ
36220 Odessa, TX
36260 Ogden-Clearfield, UT
36420 Oklahoma City, OK
36500 Olympia-Tumwater, WA
36540 Omaha-Council Bluffs, NE-IA
36740 Orlando-Kissimmee-Sanford, FL
36780 Oshkosh-Neenah, WI
36980 Owensboro, KY
37100 Oxnard-Thousand Oaks-Ventura, CA
37340 Palm Bay-Melbourne-Titusville, FL
37460 Panama City, FL
37620 Parkersburg-Vienna, WV
37860 Pensacola-Ferry Pass-Brent, FL
37900 Peoria, IL
37964 Philadelphia, PA Metropolitan Division
37980 Philadelphia-Camden-Wilmington, PA-NJ-DE-MD
38060 Phoenix-Mesa-Scottsdale, AZ
38220 Pine Bluff, AR
38300 Pittsburgh, PA
38540 Pocatello, ID
38660 Ponce, PR
38900 Portland-Vancouver-Hillsboro, OR-WA
38940 Port St. Lucie, FL
39140 Prescott, AZ
39340 Provo-Orem, UT
39380 Pueblo, CO
39460 Punta Gorda, FL
39540 Racine, WI
39580 Raleigh, NC
39660 Rapid City, SD
39740 Reading, PA
39820 Redding, CA
39900 Reno, NV
40060 Richmond, VA
40140 Riverside-San Bernardino-Ontario, CA
40220 Roanoke, VA
40340 Rochester, MN
40380 Rochester, NY
40420 Rockford, IL
40580 Rocky Mount, NC
40660 Rome, GA
40900 Sacramento--Roseville--Arden-Arcade, CA
40980 Saginaw, MI
41060 St. Cloud, MN
41100 St. George, UT
41140 St. Joseph, MO-KS
41180 St. Louis, MO-IL
41420 Salem, OR
41500 Salinas, CA
41540 Salisbury, MD-DE
41620 Salt Lake City, UT
41660 San Angelo, TX
41700 San Antonio-New Braunfels, TX
41740 San Diego-Carlsbad, CA
41860 San Francisco-Oakland-Hayward, CA
41884 San Francisco-Redwood City-South San Francisco, CA Metropolitan Division
41900 San German, PR
41940 San Jose-Sunnyvale-Santa Clara, CA
41980 San Juan-Carolina-Caguas, PR
42020 San Luis Obispo-Paso Robles-Arroyo Grande, CA
42034 San Rafael, CA Metropolitan Division
42100 Santa Cruz-Watsonville, CA
42140 Santa Fe, NM
42200 Santa Maria-Santa Barbara, CA
42220 Santa Rosa, CA
42340 Savannah, GA
42540 Scranton--Wilkes-Barre--Hazleton, PA
42644 Seattle-Bellevue-Everett, WA Metropolitan Division
42660 Seattle-Tacoma-Bellevue, WA
42680 Sebastian-Vero Beach, FL
42700 Sebring, FL
43100 Sheboygan, WI
43300 Sherman-Denison, TX
43340 Shreveport-Bossier City, LA
43420 Sierra Vista-Douglas, AZ
43524 Silver Spring-Frederick-Rockville, MD Metropolitan Division
43580 Sioux City, IA-NE-SD
43620 Sioux Falls, SD
43780 South Bend-Mishawaka, IN-MI
43900 Spartanburg, SC
44060 Spokane-Spokane Valley, WA
44100 Springfield, IL
44180 Springfield, MO
44220 Springfield, OH
44300 State College, PA
44420 Staunton-Waynesboro, VA
44700 Stockton-Lodi, CA
44940 Sumter, SC
45060 Syracuse, NY
45104 Tacoma-Lakewood, WA Metropolitan Division
45220 Tallahassee, FL
45300 Tampa-St. Petersburg-Clearwater, FL
45460 Terre Haute, IN
45500 Texarkana, TX-AR
45540 The Villages, FL
45780 Toledo, OH
45820 Topeka, KS
45940 Trenton, NJ
46060 Tucson, AZ
46140 Tulsa, OK
46220 Tuscaloosa, AL
46340 Tyler, TX
46520 Urban Honolulu, HI
46540 Utica-Rome, NY
46660 Valdosta, GA
46700 Vallejo-Fairfield, CA
47020 Victoria, TX
47220 Vineland-Bridgeton, NJ
47260 Virginia Beach-Norfolk-Newport News, VA-NC
47300 Visalia-Porterville, CA
47380 Waco, TX
47460 Walla Walla, WA
47580 Warner Robins, GA
47664 Warren-Troy-Farmington Hills, MI Metropolitan Division
47894 Washington-Arlington-Alexandria, DC-VA-MD-WV Metropolitan Division
47900 Washington-Arlington-Alexandria, DC-VA-MD-WV
47940 Waterloo-Cedar Falls, IA
48060 Watertown-Fort Drum, NY
48140 Wausau, WI
48260 Weirton-Steubenville, WV-OH
48300 Wenatchee, WA
48424 West Palm Beach-Boca Raton-Delray Beach, FL Metropolitan Division
48540 Wheeling, WV-OH
48620 Wichita, KS
48660 Wichita Falls, TX
48700 Williamsport, PA
48864 Wilmington, DE-MD-NJ Metropolitan Division
48900 Wilmington, NC
49020 Winchester, VA-WV
49180 Winston-Salem, NC
49420 Yakima, WA
49620 York-Hanover, PA
49660 Youngstown-Warren-Boardman, OH-PA
49700 Yuba City, CA
49740 Yuma, AZ
70750 Bangor, ME NECTA
70900 Barnstable Town, MA NECTA
71650 Boston-Cambridge-Nashua, MA-NH NECTA
71654 Boston-Cambridge-Newton, MA NECTA Division
71950 Bridgeport-Stamford-Norwalk, CT NECTA
72104 Brockton-Bridgewater-Easton, MA NECTA Division
72400 Burlington-South Burlington, VT NECTA
72850 Danbury, CT NECTA
73050 Dover-Durham, NH-ME NECTA
73104 Framingham, MA NECTA Division
73450 Hartford-West Hartford-East Hartford, CT NECTA
73604 Haverhill-Newburyport-Amesbury Town, MA-NH NECTA Division
74204 Lawrence-Methuen Town-Salem, MA-NH NECTA Division
74500 Leominster-Gardner, MA NECTA
74650 Lewiston-Auburn, ME NECTA
74804 Lowell-Billerica-Chelmsford, MA-NH NECTA Division
74854 Lynn-Saugus-Marblehead, MA NECTA Division
74950 Manchester, NH NECTA
75404 Nashua, NH-MA NECTA Division
75550 New Bedford, MA NECTA
75700 New Haven, CT NECTA
76450 Norwich-New London-Westerly, CT-RI NECTA
76524 Peabody-Salem-Beverly, MA NECTA Division
76600 Pittsfield, MA NECTA
76750 Portland-South Portland, ME NECTA
76900 Portsmouth, NH-ME NECTA
77200 Providence-Warwick, RI-MA NECTA
78100 Springfield, MA-CT NECTA
78254 Taunton-Middleborough-Norton, MA NECTA Division
78700 Waterbury, CT NECTA
79600 Worcester, MA-CT NECTA
92581 Baltimore City, MD
92811 Kansas City, MO
92812 Kansas City, KS
93561 New York City, NY
93562 Orange-Rockland-Westchester, NY
93563 Bergen-Hudson-Passaic, NJ
93565 Middlesex-Monmouth-Ocean, NJ
94781 Calvert-Charles-Prince George's, MD
94783 Northern Virginia, VA
97961 Philadelphia City, PA
97962 Delaware County, PA
//______________________________________________________________________________
// Rect class
function Rect(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
Rect.prototype.contains = function(r) {
// Does this rectangle contain the specified rectangle?
return this.x <= r.x &&
this.y <= r.y &&
this.x + this.width >= r.x + r.width &&
this.y + this.height >= r.y + r.height;
};
Rect.prototype.disjointFrom = function(r) {
// Is this rectangle disjoint from the specified rectangle?
return this.x + this.width <= r.x ||
this.y + this.height <= r.y ||
r.x + r.width <= this.x ||
r.y + r.height <= this.y;
};
Rect.prototype.intersects = function(r) {
// Does this rectangle intersect the specified rectangle?
return !this.disjointFrom(r);
};
Rect.prototype.copy = function() {
// Create a copy of this rectangle.
return new Rect(this.x, this.y, this.width, this.height);
};
//______________________________________________________________________________
// BinPacker class
// Uses MAXRECTS-BSSF-BNF bin packer algorithm from
// https://github.com/juj/RectangleBinPack
//
// MAXRECTS-BSSF-BNF stands for "Maximal Rectangles - Best Short Side Fit". It
// positions the rectangle against the short side of the free rectangle into
// which it fits most snugly.
function BinPacker(width, height) {
this.width = width;
this.height = height;
// TODO: Allow for flexible width or height. If a rectangle doesn't fit into
// the bin extend the width or height to accommodate it.
// Array of rectangles representing the free space in the bin
this.freeRectangles = [new Rect(0, 0, width, height)];
// Array of rectangles positioned in the bin
this.positionedRectangles = [];
// Array of rectangles that couldn't fit in the bin
this.unpositionedRectangles = [];
}
BinPacker.prototype.insert = function(width, height) {
// Insert a rectangle into the bin.
//
// If the rectangle was successfully positioned, add it to the array of
// positioned rectangles and return an object with this information and the
// rectangle object.
//
// If the rectangle couldn't be positioned in the bin, add it to the array of
// unpositioned rectangles and return an object with this information and the
// rectangle object (which as undefined x- and y-properties.
// Find where to put the rectangle. Searches the array of free rectangles for
// an open spot and returns one when it's found.
var r = BinPacker.findPosition(width, height, this.freeRectangles);
// Unpositioned rectangle (it has no x-property if it's unpositioned)
if (r.x == undefined) {
this.unpositionedRectangles.push(r);
return { positioned: false, rectangle: r };
};
// Split the free rectangles based on where the new rectangle is positioned
var n = this.freeRectangles.length;
for (var i = 0; i < n; i++) {
// splitRectangle() returns an array of sub-rectangles if the rectangle
// was split (which is truthy) and false otherwise
if (new_rectangles = BinPacker.splitRectangle(this.freeRectangles[i], r)) {
// remove the free rectangle that was split
this.freeRectangles.splice(i, 1);
// append new free rectangles formed by the split // split
this.freeRectangles = this.freeRectangles.concat(new_rectangles);
--i; --n;
}
}
BinPacker.pruneRectangles(this.freeRectangles);
this.positionedRectangles.push(r);
return { positioned: true, rectangle: r };
};
BinPacker.findPosition = function(width, height, F) {
// Decide where to position a rectangle (with side lengths specified by width
// and height) within the bin. The bin's free space is defined in the array
// of free rectangles, F.
var bestRectangle = new Rect(undefined, undefined, width, height);
var bestShortSideFit = Number.MAX_VALUE,
bestLongSideFit = Number.MAX_VALUE;
// Find the free rectangle into which this rectangle fits inside most snugly
// (i.e., the one with the smallest amount of space leftover after positioning
// the rectangle inside of it)
for (var i = 0; i < F.length; i++) {
var f = F[i]; // the current free rectangle
// Does the rectangle we are positioning fit inside the free rectangle?
if (f.width >= width && f.height >= height) {
var leftoverHorizontal = Math.abs(f.width - width),
leftoverVertical = Math.abs(f.height - height);
var shortSideFit = Math.min(leftoverHorizontal, leftoverVertical),
longSideFit = Math.max(leftoverHorizontal, leftoverVertical);
// Does this free rectangle have the smallest amount of space leftover
// after positioning?
if (shortSideFit < bestShortSideFit ||
(shortSideFit == bestShortSideFit && longSideFit < bestLongSideFit)) {
// Position rectangle in the bottom-left corner of the free rectangle
// (or top-left if the y-axis is inverted like in browsers)
bestRectangle.x = f.x;
bestRectangle.y = f.y;
bestShortSideFit = shortSideFit;
bestLongSideFit = longSideFit;
}
}
}
return bestRectangle;
};
BinPacker.splitRectangle = function(f, r) {
// Splits the rectangle f into at most four sub-rectangles that are formed by
// taking the geometric difference of f from r and identifying the largest
// rectangles that can be formed from the resulting polygon. Returns these
// sub-rectangles if the f was split and false otherwise.
// If they are disjoint then no splitting can be done, return false
if (r.disjointFrom(f)) return false;
var new_rectangles = [];
// Does f contain r in terms of the x-axis?
if (r.x < f.x + f.width && f.x < r.x + r.width) {
// QUESTION: Does this make an assumption about how r is positioned relative
// to f? Couldn't it be that part of r could be outside of f in
// this first if-statement? It looks like this assumes r will be
// placed along one of the edges (which, in fact, is what this
// algorithm does).
// TODO: Look into all of this in more depth. I don't fully understand why
// these conditionals are the way they are.
// New rectangle is above r
if (f.y < r.y && r.y < f.y + f.height) {
var new_rectangle = f.copy();
new_rectangle.height = r.y - new_rectangle.y;
new_rectangles.push(new_rectangle);
}
// New rectangle is below r
if (r.y + r.height < f.y + f.height) {
var new_rectangle = f.copy();
new_rectangle.y = r.y + r.height;
new_rectangle.height = f.y + f.height - (r.y + r.height);
new_rectangles.push(new_rectangle);
}
}
// Does f contain r in terms of the y-axis?
if (r.y < f.y + f.height && f.y < r.y + r.height) {
// New rectangle is to the left of r
if (f.x < r.x && r.x < f.x + f.width) {
var new_rectangle = f.copy();
new_rectangle.width = r.x - new_rectangle.x;
new_rectangles.push(new_rectangle);
}
// New rectangle is to the right of r
if (r.x + r.width < f.x + f.width) {
var new_rectangle = f.copy();
new_rectangle.x = r.x + r.width;
new_rectangle.width = f.x + f.width - (r.x + r.width);
new_rectangles.push(new_rectangle);
}
}
return new_rectangles;
};
BinPacker.pruneRectangles = function(F) {
// Go through the array of rectangles, F, and remove any that are
// completely contained within another rectangle in F
for (var i = 0; i < F.length; i++) {
for (var j = i + 1; j < F.length; j++) {
if (F[j].contains(F[i])) {
F.splice(i, 1);
--i;
break;
}
if (F[i].contains(F[j])) {
F.splice(j, 1);
--j;
}
}
}
};
function BinPack() {
var binWidth = 800,
binHeight = 800;
var rectWidth = function(d) { return d.width; },
rectHeight = function(d) { return d.height; };
var sort = false;
var binPacker = new BinPacker(binWidth, binHeight);
var pack = {};
pack.add = function(d) {
var o = binPacker.insert(rectWidth(d), rectHeight(d));
o.rectangle.datum = d;
return pack;
};
pack.addAll = function(array) {
if (sort) array.sort(sort);
array.forEach(function(d, i) {
var o = binPacker.insert(rectWidth(d), rectHeight(d));
o.rectangle.datum = d;
});
return pack;
};
pack.binWidth = function(_) {
if (!arguments.length) return binWidth;
binWidth = _;
binPacker = new BinPacker(binWidth, binHeight);
return pack;
};
pack.binHeight = function(_) {
if (!arguments.length) return binHeight;
binHeight = _;
binPacker = new BinPacker(binWidth, binHeight);
return pack;
};
pack.rectWidth = function(_) {
return arguments.length ? (rectWidth = _, pack) : rectWidth;
};
pack.rectHeight = function(_) {
return arguments.length ? (rectHeight = _, pack) : rectHeight;
};
pack.sort = function(_) {
return arguments.length ? (sort = _, pack) : sort;
};
Object.defineProperty(pack, "positioned", {
get: function() { return binPacker.positionedRectangles; }
});
Object.defineProperty(pack, "unpositioned", {
get: function() { return binPacker.unpositionedRectangles; }
});
return pack;
}
rm(list = ls())
setwd("C:/Users/molli/Projects/blocks/raw/metro-employment-vanilla")
library(dplyr)
library(tidyr)
library(readr)
library(stringr)
library(lubridate)
library(jsonlite)
setwd("raw-data")
area <- read_tsv("area.txt")
data_type <- read_tsv("data_type.txt")
industry <- read_tsv("industry.txt")
state <- read_tsv("state.txt")
supersector <- read_tsv("supersector.txt")
series <- read_tsv("series.txt")
data <- read_tsv("data.txt")
setwd("..")
data %>%
right_join(
series %>%
filter(industry_code %in% c("00000000", "30000000"),
data_type_code == "01",
seasonal == "U",
area_code != "00000"),
by = "series_id"
) %>%
mutate(month = extract_numeric(period)) %>%
filter(month < 13, year > 1989) %>%
mutate(date = ymd(str_c(year, "-", month, "-1"))) %>%
select(area_code, state_code, industry_code, date, value) %>%
spread(industry_code, value) %>%
rename(total = `00000000`,
manufacturing = `30000000`) %>%
mutate(share = manufacturing / total) %>%
select(area_code, state_code, date, share) %>%
arrange(area_code, state_code, date) %>%
filter(!is.na(share)) %>%
write_tsv("manufacturing-share.tsv")
state_abbrevs <- read_csv("http://www.fonz.net/blog/wp-content/uploads/2008/04/states.csv")
stateface_letters <- fromJSON("http://propublica.github.com/stateface/reference/stateface.json")
state %>%
merge(state_abbrevs, by.x="state_name", by.y="State", all.x=TRUE) %>%
rename(state_abbrev = Abbreviation) %>%
mutate(
stateface_letter = ifelse(is.na(state_abbrev), NA,
sapply(state_abbrev, function(a) stateface_letters[[a]])) %>%
unlist()
) %>%
write_tsv("state-names.tsv", na="")
area %>%
write_tsv("area-names.tsv")
#!/usr/bin/env bash
mkdir raw-data && cd raw-data
curl http://download.bls.gov/pub/time.series/sm/sm.area > area.txt
curl http://download.bls.gov/pub/time.series/sm/sm.data_type > data_type.txt
curl http://download.bls.gov/pub/time.series/sm/sm.industry > industry.txt
curl http://download.bls.gov/pub/time.series/sm/sm.state > state.txt
curl http://download.bls.gov/pub/time.series/sm/sm.supersector > supersector.txt
curl http://download.bls.gov/pub/time.series/sm/sm.series > series.txt
curl http://download.bls.gov/pub/time.series/sm/sm.data.1.AllData > data.txt
cd ..
state_name state_code state_abbrev stateface_letter
Alabama 01 AL B
Alaska 02 AK A
Arizona 04 AZ D
Arkansas 05 AR C
California 06 CA E
Colorado 08 CO F
Connecticut 09 CT G
Delaware 10 DE H
District of Columbia 11 DC y
Florida 12 FL I
Georgia 13 GA J
Hawaii 15 HI K
Idaho 16 ID M
Illinois 17 IL N
Indiana 18 IN O
Iowa 19 IA L
Kansas 20 KS P
Kentucky 21 KY Q
Louisiana 22 LA R
Maine 23 ME U
Maryland 24 MD T
Massachusetts 25 MA S
Michigan 26 MI V
Minnesota 27 MN W
Mississippi 28 MS Y
Missouri 29 MO X
Montana 30 MT Z
Nebraska 31 NE c
Nevada 32 NV g
New Hampshire 33 NH d
New Jersey 34 NJ e
New Mexico 35 NM f
New York 36 NY h
North Carolina 37 NC a
North Dakota 38 ND b
Ohio 39 OH i
Oklahoma 40 OK j
Oregon 41 OR k
Pennsylvania 42 PA l
Puerto Rico 72
Rhode Island 44 RI m
South Carolina 45 SC n
South Dakota 46 SD o
Tennessee 47 TN p
Texas 48 TX q
Utah 49 UT r
Vermont 50 VT t
Virgin Islands 78
Virginia 51 VA s
Washington 53 WA u
West Virginia 54 WV w
Wisconsin 55 WI v
Wyoming 56 WY x
�G �F � LP ؘ$ S t a t e F a c e R e g u l a r x V e r s i o n 1 . 1 0 0 ; P S 0 0 1 . 1 0 0 ; h o t c o n v 1 . 0 . 5 6 ; m a k e o t f . l i b 2 . 0 . 2 1 3 2 5 " S t a t e F a c e R e g u l a r BSGP e� =� >