@cdc/map 2.6.0 → 2.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/convert-topojson.js +70 -0
  2. package/dist/cdcmap.js +190 -0
  3. package/examples/default-county.json +105 -0
  4. package/examples/default-single-state.json +109 -0
  5. package/examples/default-usa.json +968 -0
  6. package/examples/default-world.json +1495 -0
  7. package/examples/example-city-state.json +474 -0
  8. package/examples/example-world-map.json +1596 -0
  9. package/examples/gender-rate-map.json +1 -0
  10. package/package.json +50 -50
  11. package/src/CdcMap.js +1384 -0
  12. package/src/components/CityList.js +93 -0
  13. package/src/components/CountyMap.js +556 -0
  14. package/src/components/DataTable.js +357 -0
  15. package/src/components/EditorPanel.js +2111 -0
  16. package/src/components/Geo.js +21 -0
  17. package/src/components/Modal.js +31 -0
  18. package/src/components/NavigationMenu.js +66 -0
  19. package/src/components/Sidebar.js +167 -0
  20. package/src/components/SingleStateMap.js +326 -0
  21. package/src/components/UsaMap.js +342 -0
  22. package/src/components/WorldMap.js +175 -0
  23. package/src/components/ZoomableGroup.js +47 -0
  24. package/src/data/abbreviations.js +57 -0
  25. package/src/data/color-palettes.js +200 -0
  26. package/src/data/county-map-halfquality.json +58453 -0
  27. package/src/data/county-map-quarterquality.json +1 -0
  28. package/src/data/county-topo.json +1 -0
  29. package/src/data/dfc-map.json +1 -0
  30. package/src/data/initial-state.js +60 -0
  31. package/src/data/newtest.json +1 -0
  32. package/src/data/state-abbreviations.js +60 -0
  33. package/src/data/supported-geos.js +3775 -0
  34. package/src/data/test.json +1 -0
  35. package/src/data/us-hex-topo.json +1 -0
  36. package/src/data/us-topo.json +1 -0
  37. package/src/data/world-topo.json +1 -0
  38. package/src/hooks/useActiveElement.js +19 -0
  39. package/src/hooks/useZoomPan.js +110 -0
  40. package/src/images/active-checkmark.svg +1 -0
  41. package/src/images/asc.svg +1 -0
  42. package/src/images/close.svg +1 -0
  43. package/src/images/desc.svg +1 -0
  44. package/src/images/external-link.svg +1 -0
  45. package/src/images/icon-download-img.svg +1 -0
  46. package/src/images/icon-download-pdf.svg +1 -0
  47. package/src/images/inactive-checkmark.svg +1 -0
  48. package/src/images/map-folded.svg +1 -0
  49. package/src/index.html +29 -0
  50. package/src/index.js +20 -0
  51. package/src/scss/btn.scss +69 -0
  52. package/src/scss/datatable.scss +7 -0
  53. package/src/scss/editor-panel.scss +654 -0
  54. package/src/scss/main.scss +224 -0
  55. package/src/scss/map.scss +188 -0
  56. package/src/scss/sidebar.scss +146 -0
  57. package/src/scss/tooltips.scss +30 -0
  58. package/src/scss/variables.scss +1 -0
  59. package/uploads/upload-example-city-state.json +392 -0
  60. package/uploads/upload-example-world-data.json +1490 -0
  61. package/LICENSE +0 -201
@@ -0,0 +1,342 @@
1
+ import React, { useState, useEffect, memo } from 'react';
2
+ /** @jsx jsx */
3
+ import { jsx } from '@emotion/react'
4
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
5
+ import { geoCentroid } from "d3-geo";
6
+ import { feature } from "topojson-client";
7
+ import topoJSON from '../data/us-topo.json';
8
+ import hexTopoJSON from '../data/us-hex-topo.json';
9
+ import { AlbersUsa, Mercator } from '@visx/geo';
10
+ import chroma from 'chroma-js';
11
+ import CityList from './CityList';
12
+
13
+ const { features: unitedStates } = feature(topoJSON, topoJSON.objects.states)
14
+ const { features: unitedStatesHex } = feature(hexTopoJSON, hexTopoJSON.objects.states)
15
+
16
+ const Hexagon = ({label, text, stroke, strokeWidth, ...props}) => {
17
+ return (
18
+ <svg viewBox="0 0 45 51">
19
+ <g {...props}>
20
+ <polygon stroke={stroke} strokeWidth={strokeWidth} points="22 0 44 12.702 44 38.105 22 50.807 0 38.105 0 12.702"/>
21
+ <text textAnchor="middle" dominantBaseline="middle" x="50%" y="54%" fill={text}>{label}</text>
22
+ </g>
23
+ </svg>
24
+ )
25
+ }
26
+
27
+ const Rect = ({label, text, stroke, strokeWidth, ...props}) => {
28
+ return (
29
+ <svg viewBox="0 0 45 28">
30
+ <g {...props} strokeLinejoin="round">
31
+ <path stroke={stroke} strokeWidth={strokeWidth} d="M40,0.5 C41.2426407,0.5 42.3676407,1.00367966 43.1819805,1.81801948 C43.9963203,2.63235931 44.5,3.75735931 44.5,5 L44.5,5 L44.5,23 C44.5,24.2426407 43.9963203,25.3676407 43.1819805,26.1819805 C42.3676407,26.9963203 41.2426407,27.5 40,27.5 L40,27.5 L5,27.5 C3.75735931,27.5 2.63235931,26.9963203 1.81801948,26.1819805 C1.00367966,25.3676407 0.5,24.2426407 0.5,23 L0.5,23 L0.5,5 C0.5,3.75735931 1.00367966,2.63235931 1.81801948,1.81801948 C2.63235931,1.00367966 3.75735931,0.5 5,0.5 L5,0.5 Z" />
32
+ <text textAnchor="middle" dominantBaseline="middle" x="50%" y="54%" fill={text}>{label}</text>
33
+ </g>
34
+ </svg>
35
+ )
36
+ }
37
+
38
+ const offsets = {
39
+ 'US-VT': [50, -8],
40
+ 'US-NH': [34, 2],
41
+ 'US-MA': [30, -1],
42
+ 'US-RI': [28, 2],
43
+ 'US-CT': [35, 10],
44
+ 'US-NJ': [42, 1],
45
+ 'US-DE': [33, 0],
46
+ 'US-MD': [47, 10]
47
+ };
48
+
49
+ const nudges = {
50
+ 'US-FL': [15, 3],
51
+ 'US-AK': [0, -8],
52
+ 'US-CA': [-10, 0],
53
+ 'US-NY': [5, 0],
54
+ 'US-MI': [13, 20],
55
+ 'US-LA': [-10, -3],
56
+ 'US-HI': [-10, 10],
57
+ 'US-ID': [0, 10],
58
+ 'US-WV': [-2, 2]
59
+ }
60
+
61
+ const UsaMap = (props) => {
62
+ const {
63
+ state,
64
+ applyTooltipsToGeo,
65
+ data,
66
+ geoClickHandler,
67
+ applyLegendToRow,
68
+ displayGeoName,
69
+ supportedTerritories,
70
+ rebuildTooltips,
71
+ titleCase
72
+ } = props;
73
+
74
+ // "Choose State" options
75
+ const [extent, setExtent] = useState(null)
76
+ const [focusedStates, setFocusedStates] = useState(unitedStates)
77
+ const [translate, setTranslate] = useState([455,200])
78
+
79
+ // When returning from another map we want to reset the state
80
+ useEffect(() => {
81
+ setTranslate( [455,250] )
82
+ setExtent( null )
83
+ }, [state.general.geoType]);
84
+
85
+ const isHex = state.general.displayAsHex
86
+
87
+ const [territoriesData, setTerritoriesData] = useState([]);
88
+
89
+ const territoriesKeys = Object.keys(supportedTerritories); // data will have already mapped abbreviated territories to their full names
90
+
91
+ useEffect(() => {
92
+ // Territories need to show up if they're in the data at all, not just if they're "active". That's why this is different from Cities
93
+ const territoriesList = territoriesKeys.filter(key => data[key]);
94
+
95
+ setTerritoriesData(territoriesList);
96
+ }, [data]);
97
+
98
+ useEffect(() => rebuildTooltips());
99
+
100
+ const geoStrokeColor = state.general.geoBorderColor === 'darkGray' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255,255,255,0.7)'
101
+
102
+ const territories = territoriesData.map(territory => {
103
+ const Shape = isHex ? Hexagon : Rect
104
+
105
+ const territoryData = data[territory];
106
+
107
+ let toolTip;
108
+
109
+ let styles = {
110
+ fill: '#E6E6E6',
111
+ color: '#202020',
112
+ };
113
+
114
+ const label = supportedTerritories[territory][1]
115
+
116
+ if(!territoryData) return <Shape key={label} label={label} css={styles} text={styles.color} />
117
+
118
+ toolTip = applyTooltipsToGeo(displayGeoName(territory), territoryData);
119
+
120
+ const legendColors = applyLegendToRow(territoryData);
121
+
122
+ let textColor = '#FFF';
123
+
124
+ if (legendColors) {
125
+ // Use white text if the background is dark, and dark grey if it's light
126
+ if (chroma.contrast(textColor, legendColors[0]) < 4.5) {
127
+ textColor = '#202020';
128
+ }
129
+
130
+ let needsPointer = false;
131
+
132
+ // If we need to add a pointer cursor
133
+ if ((state.columns.navigate && territoryData[state.columns.navigate.name]) || state.tooltips.appearanceType === 'click') {
134
+ needsPointer = true;
135
+ }
136
+
137
+ styles = {
138
+ color: textColor,
139
+ fill: legendColors[0],
140
+ cursor: needsPointer ? 'pointer' : 'default',
141
+ '&:hover': {
142
+ fill: legendColors[1],
143
+ },
144
+ '&:active': {
145
+ fill: legendColors[2],
146
+ }
147
+ };
148
+
149
+ return (<Shape
150
+ key={label}
151
+ label={label}
152
+ css={styles}
153
+ text={styles.color}
154
+ data-tip={toolTip}
155
+ data-for="tooltip"
156
+ stroke={geoStrokeColor}
157
+ strokeWidth={1.5}
158
+ onClick={() => geoClickHandler(territory, territoryData)}
159
+ />)
160
+ }
161
+ });
162
+
163
+ const geoLabel = (geo, bgColor = "#FFFFFF", projection) => {
164
+ let centroid = projection(geoCentroid(geo))
165
+ let abbr = geo.properties.iso
166
+
167
+ if(undefined === abbr) return null
168
+
169
+ let textColor = "#FFF"
170
+
171
+ // Dynamic text color
172
+ if (chroma.contrast(textColor, bgColor) < 4.5 ) {
173
+ textColor = '#202020';
174
+ }
175
+
176
+ let x = 0, y = 5
177
+
178
+ if(nudges[abbr] && false === isHex) {
179
+ x += nudges[abbr][0]
180
+ y += nudges[abbr][1]
181
+ }
182
+
183
+ if( undefined === offsets[abbr] || isHex ) {
184
+ return (
185
+ <g transform={`translate(${centroid})`}>
186
+ <text x={x} y={y} fontSize={14} strokeWidth="0" style={{fill: textColor}} textAnchor="middle">
187
+ {abbr.substring(3)}
188
+ </text>
189
+ </g>
190
+ )
191
+ }
192
+
193
+ let [dx, dy] = offsets[abbr]
194
+
195
+ return (
196
+ <g>
197
+ <line x1={centroid[0]} y1={centroid[1]} x2={centroid[0] + dx} y2={centroid[1] + dy} stroke="rgba(0,0,0,.5)" strokeWidth={1} />
198
+ <text x={4} strokeWidth="0" fontSize={13} style={{fill: "#202020"}} alignmentBaseline="middle" transform={`translate(${centroid[0] + dx}, ${centroid[1] + dy})`}>
199
+ {abbr.substring(3)}
200
+ </text>
201
+ </g>
202
+ )
203
+ }
204
+
205
+ // Constructs and displays markup for all geos on the map (except territories right now)
206
+ const constructGeoJsx = (geographies, projection) => {
207
+ let showLabel = state.general.displayStateLabels
208
+
209
+ const geosJsx = geographies.map(( {feature: geo, path = ''}) => {
210
+ const key = isHex ? geo.properties.iso + '-hex-group' : geo.properties.iso + '-group'
211
+
212
+ let styles = {
213
+ fill: '#E6E6E6',
214
+ cursor: 'default'
215
+ }
216
+
217
+ // Map the name from the geo data with the appropriate key for the processed data
218
+ let geoKey = geo.properties.iso;
219
+
220
+ // Manually add Washington D.C. in for Hex maps
221
+ if(isHex && geoKey === 'US-DC') {
222
+ geoKey = 'District of Columbia'
223
+ }
224
+
225
+ if(!geoKey) return
226
+
227
+ const geoData = data[geoKey];
228
+
229
+ let legendColors;
230
+
231
+ // Once we receive data for this geographic item, setup variables.
232
+ if (geoData !== undefined) {
233
+ legendColors = applyLegendToRow(geoData);
234
+ }
235
+
236
+ const geoDisplayName = displayGeoName(geoKey);
237
+
238
+ // If a legend applies, return it with appropriate information.
239
+ if (legendColors && legendColors[0] !== '#000000') {
240
+ const tooltip = applyTooltipsToGeo(geoDisplayName, geoData);
241
+
242
+ styles = {
243
+ fill: legendColors[0],
244
+ cursor: 'default',
245
+ '&:hover': {
246
+ fill: legendColors[1],
247
+ },
248
+ '&:active': {
249
+ fill: legendColors[2],
250
+ },
251
+ };
252
+
253
+ // When to add pointer cursor
254
+ if ((state.columns.navigate && geoData[state.columns.navigate.name]) || state.tooltips.appearanceType === 'click') {
255
+ styles.cursor = 'pointer'
256
+ }
257
+
258
+ return (
259
+ <g
260
+ data-for="tooltip"
261
+ data-tip={tooltip}
262
+ key={key}
263
+ className="geo-group"
264
+ css={styles}
265
+ onClick={() => geoClickHandler(geoDisplayName, geoData)}
266
+ >
267
+ <path
268
+ tabIndex={-1}
269
+ className='single-geo'
270
+ stroke={geoStrokeColor}
271
+ strokeWidth={1.3}
272
+ d={path}
273
+ />
274
+ {(isHex || showLabel) && geoLabel(geo, legendColors[0], projection)}
275
+ </g>
276
+ )
277
+ }
278
+
279
+ // Default return state, just geo with no additional information
280
+ return (
281
+ <g
282
+ key={key}
283
+ className="geo-group"
284
+ css={styles}
285
+ >
286
+ <path
287
+ tabIndex={-1}
288
+ className='single-geo'
289
+ stroke={geoStrokeColor}
290
+ strokeWidth={1.3}
291
+ d={path}
292
+ />
293
+ {(isHex || showLabel) && geoLabel(geo, styles.fill, projection)}
294
+ </g>
295
+ )
296
+ });
297
+
298
+ if(isHex) return geosJsx;
299
+
300
+ // Cities
301
+ geosJsx.push(<CityList
302
+ projection={projection}
303
+ key="cities"
304
+ data={data}
305
+ state={state}
306
+ geoClickHandler={geoClickHandler}
307
+ applyTooltipsToGeo={applyTooltipsToGeo}
308
+ displayGeoName={displayGeoName}
309
+ applyLegendToRow={applyLegendToRow}
310
+ titleCase={titleCase}
311
+ />)
312
+
313
+ return geosJsx;
314
+ };
315
+
316
+ return (
317
+ <ErrorBoundary component="UsaMap">
318
+ <svg viewBox="0 0 880 500">
319
+ {state.general.displayAsHex ?
320
+ (<Mercator data={unitedStatesHex} scale={650} translate={[1600, 775]}>
321
+ {({ features, projection }) => constructGeoJsx(features, projection)}
322
+ </Mercator>) :
323
+ (<AlbersUsa
324
+ data={focusedStates}
325
+ translate={translate}
326
+ fitExtent={extent}
327
+ >
328
+ {({ features, projection }) => constructGeoJsx(features, projection)}
329
+ </AlbersUsa>)
330
+ }
331
+ </svg>
332
+ {territories.length > 0 && (
333
+ <section className="territories">
334
+ <span className="label">{state.general.territoriesLabel}</span>
335
+ {territories}
336
+ </section>
337
+ )}
338
+ </ErrorBoundary>
339
+ );
340
+ };
341
+
342
+ export default memo(UsaMap)
@@ -0,0 +1,175 @@
1
+ import React, { useState, useEffect, memo } from 'react';
2
+ /** @jsx jsx */
3
+ import { jsx } from '@emotion/react'
4
+ import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
5
+ import { geoMercator } from "d3-geo";
6
+ import { Mercator } from '@visx/geo';
7
+ import { feature } from "topojson-client";
8
+ import topoJSON from '../data/world-topo.json';
9
+ import ZoomableGroup from './ZoomableGroup';
10
+ import Geo from './Geo'
11
+ import CityList from './CityList';
12
+
13
+ const { features: world } = feature(topoJSON, topoJSON.objects.countries)
14
+
15
+ let projection = geoMercator()
16
+
17
+ const handleZoomIn = (position, setPosition) => {
18
+ if (position.zoom >= 4) return;
19
+ setPosition((pos) => ({ ...pos, zoom: pos.zoom * 1.5 }));
20
+ };
21
+
22
+ const handleZoomOut = (position, setPosition) => {
23
+ if (position.zoom <= 1) return;
24
+ setPosition((pos) => ({ ...pos, zoom: pos.zoom / 1.5 }));
25
+ };
26
+
27
+ const ZoomControls = ({position, setPosition}) => (
28
+ <div className="zoom-controls" data-html2canvas-ignore>
29
+ <button onClick={() => handleZoomIn(position, setPosition)}>
30
+ <svg
31
+ viewBox="0 0 24 24"
32
+ stroke="currentColor"
33
+ strokeWidth="3"
34
+ >
35
+ <line x1="12" y1="5" x2="12" y2="19" />
36
+ <line x1="5" y1="12" x2="19" y2="12" />
37
+ </svg>
38
+ </button>
39
+ <button onClick={() => handleZoomOut(position, setPosition)}>
40
+ <svg
41
+ viewBox="0 0 24 24"
42
+ stroke="currentColor"
43
+ strokeWidth="3"
44
+ >
45
+ <line x1="5" y1="12" x2="19" y2="12" />
46
+ </svg>
47
+ </button>
48
+ </div>
49
+ );
50
+
51
+ const WorldMap = (props) => {
52
+ const {
53
+ state,
54
+ applyTooltipsToGeo,
55
+ data,
56
+ geoClickHandler,
57
+ applyLegendToRow,
58
+ displayGeoName,
59
+ supportedCountries,
60
+ rebuildTooltips
61
+ } = props;
62
+
63
+ const [position, setPosition] = useState({ coordinates: [0, 30], zoom: 1 });
64
+
65
+ useEffect(() => rebuildTooltips());
66
+
67
+ const handleMoveEnd = (position) => {
68
+ setPosition(position);
69
+ };
70
+
71
+ const constructGeoJsx = (geographies) => {
72
+ const geosJsx = geographies.map(({ feature: geo, path }, i) => {
73
+ const geoKey = geo.properties.iso
74
+
75
+ if(!geoKey) return
76
+
77
+ const geoData = data[geoKey];
78
+
79
+ const geoDisplayName = displayGeoName(supportedCountries[geoKey][0]);
80
+
81
+ let legendColors;
82
+
83
+ // Once we receive data for this geographic item, setup variables.
84
+ if (geoData !== undefined) {
85
+ legendColors = applyLegendToRow(geoData);
86
+ }
87
+
88
+ const geoStrokeColor = state.general.geoBorderColor === 'darkGray' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255,255,255,0.7)'
89
+
90
+ let styles = {
91
+ fill: '#E6E6E6',
92
+ cursor: 'default'
93
+ }
94
+
95
+ const strokeWidth = .9
96
+
97
+ // If a legend applies, return it with appropriate information.
98
+ if (legendColors && legendColors[0] !== '#000000') {
99
+ const tooltip = applyTooltipsToGeo(geoDisplayName, geoData);
100
+
101
+ styles = {
102
+ ...styles,
103
+ fill: legendColors[0],
104
+ cursor: 'default',
105
+ '&:hover': {
106
+ fill: legendColors[1]
107
+ },
108
+ '&:active': {
109
+ fill: legendColors[2],
110
+ },
111
+ };
112
+
113
+ // When to add pointer cursor
114
+ if ((state.columns.navigate && geoData[state.columns.navigate.name]) || state.tooltips.appearanceType === 'click') {
115
+ styles.cursor = 'pointer'
116
+ }
117
+
118
+ return (
119
+ <Geo
120
+ key={i + '-geo'}
121
+ css={styles}
122
+ data-for="tooltip"
123
+ data-tip={tooltip}
124
+ path={path}
125
+ stroke={geoStrokeColor}
126
+ strokeWidth={strokeWidth}
127
+ onClick={() => geoClickHandler(geoDisplayName, geoData)}
128
+ />
129
+ )
130
+ }
131
+
132
+ // Default return state, just geo with no additional information
133
+ return <Geo key={i + '-geo'} stroke={geoStrokeColor} strokeWidth={strokeWidth} css={styles} path={path} />
134
+ });
135
+
136
+ // Cities
137
+ geosJsx.push(<CityList
138
+ projection={projection}
139
+ key="cities"
140
+ data={data}
141
+ state={state}
142
+ geoClickHandler={geoClickHandler}
143
+ applyTooltipsToGeo={applyTooltipsToGeo}
144
+ displayGeoName={displayGeoName}
145
+ applyLegendToRow={applyLegendToRow}
146
+ />)
147
+
148
+ return geosJsx;
149
+ };
150
+
151
+ return (
152
+ <ErrorBoundary component="WorldMap">
153
+ <svg viewBox="0 0 880 500">
154
+ <ZoomableGroup
155
+ zoom={position.zoom}
156
+ center={position.coordinates}
157
+ onMoveEnd={handleMoveEnd}
158
+ maxZoom={4}
159
+ projection={projection}
160
+ width={880}
161
+ height={500}
162
+ >
163
+ <Mercator
164
+ data={world}
165
+ >
166
+ {({features}) => constructGeoJsx(features)}
167
+ </Mercator>
168
+ </ZoomableGroup>
169
+ </svg>
170
+ {state.general.type === 'data' && <ZoomControls position={position} setPosition={setPosition} />}
171
+ </ErrorBoundary>
172
+ );
173
+ };
174
+
175
+ export default memo(WorldMap)
@@ -0,0 +1,47 @@
1
+
2
+ import React, { useContext } from "react"
3
+ import useZoomPan from "../hooks/useZoomPan"
4
+
5
+ const ZoomableGroup = ({
6
+ center = [0, 0],
7
+ zoom = 1,
8
+ minZoom = 1,
9
+ maxZoom = 8,
10
+ translateExtent,
11
+ filterZoomEvent,
12
+ onMoveStart,
13
+ onMove,
14
+ onMoveEnd,
15
+ className,
16
+ projection,
17
+ width,
18
+ height,
19
+ ...restProps
20
+ }) => {
21
+
22
+ const {
23
+ mapRef,
24
+ transformString,
25
+ } = useZoomPan({
26
+ center,
27
+ filterZoomEvent,
28
+ onMoveStart,
29
+ onMove,
30
+ onMoveEnd,
31
+ scaleExtent: [minZoom, maxZoom],
32
+ translateExtent,
33
+ zoom,
34
+ width,
35
+ height,
36
+ projection
37
+ })
38
+
39
+ return (
40
+ <g ref={mapRef}>
41
+ <rect width={width} height={height} fill="transparent" />
42
+ <g transform={transformString} {...restProps} />
43
+ </g>
44
+ )
45
+ }
46
+
47
+ export default ZoomableGroup
@@ -0,0 +1,57 @@
1
+ export const abbrs = {
2
+ Alabama: 'AL',
3
+ Alaska: 'AK',
4
+ Arizona: 'AZ',
5
+ Arkansas: 'AR',
6
+ California: 'CA',
7
+ Colorado: 'CO',
8
+ Connecticut: 'CT',
9
+ Delaware: 'DE',
10
+ Florida: 'FL',
11
+ Georgia: 'GA',
12
+ Hawaii: 'HI',
13
+ Idaho: 'ID',
14
+ Illinois: 'IL',
15
+ Indiana: 'IN',
16
+ Iowa: 'IA',
17
+ Kansas: 'KS',
18
+ Kentucky: 'KY',
19
+ Louisiana: 'LA',
20
+ Maine: 'ME',
21
+ Maryland: 'MD',
22
+ Massachusetts: 'MA',
23
+ Michigan: 'MI',
24
+ Minnesota: 'MN',
25
+ Mississippi: 'MS',
26
+ Missouri: 'MO',
27
+ Montana: 'MT',
28
+ Nebraska: 'NE',
29
+ Nevada: 'NV',
30
+ 'New Hampshire': 'NH',
31
+ 'New Jersey': 'NJ',
32
+ 'New Mexico': 'NM',
33
+ 'New York': 'NY',
34
+ 'North Carolina': 'NC',
35
+ 'North Dakota': 'ND',
36
+ Ohio: 'OH',
37
+ Oklahoma: 'OK',
38
+ Oregon: 'OR',
39
+ Pennsylvania: 'PA',
40
+ 'Rhode Island': 'RI',
41
+ 'South Carolina': 'SC',
42
+ 'South Dakota': 'SD',
43
+ Tennessee: 'TN',
44
+ Texas: 'TX',
45
+ Utah: 'UT',
46
+ Vermont: 'VT',
47
+ Virginia: 'VA',
48
+ Washington: 'WA',
49
+ 'West Virginia': 'WV',
50
+ Wisconsin: 'WI',
51
+ Wyoming: 'WY',
52
+ 'District of Columbia': 'DC',
53
+ Guam: 'GU',
54
+ 'Virgin Islands': 'VI',
55
+ 'Puerto Rico': 'PR',
56
+ 'American Samoa': 'AS',
57
+ };