@cdc/map 2.6.2 → 2.6.4

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 (67) hide show
  1. package/dist/cdcmap.js +37 -29
  2. package/examples/bubble-us.json +363 -0
  3. package/examples/bubble-world.json +427 -0
  4. package/examples/default-county.json +105 -0
  5. package/examples/default-hex.json +475 -0
  6. package/examples/default-single-state.json +109 -0
  7. package/examples/default-usa-regions.json +118 -0
  8. package/examples/default-usa.json +744 -603
  9. package/examples/default-world-data.json +1450 -0
  10. package/examples/default-world.json +5 -3
  11. package/examples/example-city-state.json +510 -0
  12. package/examples/example-world-map.json +1596 -0
  13. package/examples/gender-rate-map.json +1 -0
  14. package/examples/private/atsdr.json +439 -0
  15. package/examples/private/atsdr_new.json +436 -0
  16. package/examples/private/bubble.json +285 -0
  17. package/examples/private/default-world-data.json +1444 -0
  18. package/examples/private/default.json +968 -0
  19. package/examples/private/map.csv +60 -0
  20. package/examples/private/mdx.json +210 -0
  21. package/examples/private/regions.json +52 -0
  22. package/examples/private/wcmsrd-13881-data.json +2858 -0
  23. package/examples/private/wcmsrd-13881.json +5823 -0
  24. package/examples/private/wcmsrd-test.json +268 -0
  25. package/examples/private/world.json +1580 -0
  26. package/examples/private/worldmap.json +1490 -0
  27. package/package.json +11 -7
  28. package/src/CdcMap.js +726 -202
  29. package/src/components/BubbleList.js +240 -0
  30. package/src/components/CityList.js +22 -3
  31. package/src/components/CountyMap.js +557 -0
  32. package/src/components/DataTable.js +85 -24
  33. package/src/components/EditorPanel.js +2455 -1204
  34. package/src/components/Geo.js +1 -1
  35. package/src/components/Sidebar.js +5 -5
  36. package/src/components/SingleStateMap.js +326 -0
  37. package/src/components/UsaMap.js +41 -9
  38. package/src/components/UsaRegionMap.js +319 -0
  39. package/src/components/WorldMap.js +112 -35
  40. package/src/data/abbreviations.js +57 -0
  41. package/src/data/country-coordinates.js +250 -0
  42. package/src/data/county-map-halfquality.json +58453 -0
  43. package/src/data/county-map-quarterquality.json +1 -0
  44. package/src/data/county-topo.json +1 -0
  45. package/src/data/dfc-map.json +1 -0
  46. package/src/data/initial-state.js +21 -4
  47. package/src/data/newtest.json +1 -0
  48. package/src/data/state-abbreviations.js +60 -0
  49. package/src/data/state-coordinates.js +55 -0
  50. package/src/data/supported-geos.js +3592 -163
  51. package/src/data/test.json +1 -0
  52. package/src/data/us-regions-topo-2.json +360525 -0
  53. package/src/data/us-regions-topo.json +37894 -0
  54. package/src/hooks/useActiveElement.js +19 -0
  55. package/src/hooks/useColorPalette.ts +96 -0
  56. package/src/index.html +35 -20
  57. package/src/index.js +8 -4
  58. package/src/scss/datatable.scss +2 -1
  59. package/src/scss/editor-panel.scss +76 -55
  60. package/src/scss/main.scss +10 -1
  61. package/src/scss/map.scss +257 -121
  62. package/src/scss/sidebar.scss +0 -1
  63. package/uploads/upload-example-city-state.json +392 -0
  64. package/uploads/upload-example-world-data.json +1490 -0
  65. package/LICENSE +0 -201
  66. package/src/data/color-palettes.js +0 -191
  67. package/src/images/map-folded.svg +0 -1
@@ -0,0 +1,319 @@
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-regions-topo-2.json';
8
+ import { Mercator } from '@visx/geo';
9
+ import chroma from 'chroma-js';
10
+
11
+ const { features: unitedStates } = feature(topoJSON, topoJSON.objects.regions)
12
+
13
+ const Rect = ({label, text, stroke, strokeWidth, ...props}) => {
14
+ return (
15
+ <svg viewBox="0 0 45 28">
16
+ <g {...props} strokeLinejoin="round">
17
+ <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" />
18
+ <text textAnchor="middle" dominantBaseline="middle" x="50%" y="54%" fill={text}>{label}</text>
19
+ </g>
20
+ </svg>
21
+ )
22
+ }
23
+
24
+ const UsaRegionMap = (props) => {
25
+ const {
26
+ state,
27
+ applyTooltipsToGeo,
28
+ data,
29
+ geoClickHandler,
30
+ applyLegendToRow,
31
+ displayGeoName,
32
+ supportedTerritories,
33
+ rebuildTooltips,
34
+ titleCase,
35
+ handleCircleClick
36
+ } = props;
37
+
38
+ // "Choose State" options
39
+ const [extent, setExtent] = useState(null)
40
+ const [focusedStates, setFocusedStates] = useState(unitedStates)
41
+ const [translate, setTranslate] = useState([455,200])
42
+
43
+ // When returning from another map we want to reset the state
44
+ useEffect(() => {
45
+ setTranslate( [455,250] )
46
+ setExtent( null )
47
+ }, [state.general.geoType]);
48
+
49
+ const isHex = state.general.displayAsHex
50
+
51
+ const [territoriesData, setTerritoriesData] = useState([]);
52
+
53
+ const territoriesKeys = Object.keys(supportedTerritories); // data will have already mapped abbreviated territories to their full names
54
+
55
+ useEffect(() => {
56
+ // 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
57
+ const territoriesList = territoriesKeys.filter(key => data[key]);
58
+
59
+ setTerritoriesData(territoriesList);
60
+ }, [data]);
61
+
62
+ useEffect(() => rebuildTooltips());
63
+
64
+ const geoStrokeColor = state.general.geoBorderColor === 'darkGray' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255,255,255,0.7)'
65
+
66
+ const territories = territoriesData.map(territory => {
67
+ const Shape = Rect
68
+
69
+ const territoryData = data[territory];
70
+
71
+ let toolTip;
72
+
73
+ let styles = {
74
+ fill: '#E6E6E6',
75
+ color: '#202020',
76
+ };
77
+
78
+ const label = supportedTerritories[territory][1]
79
+
80
+ if(!territoryData) return <Shape key={label} label={label} css={styles} text={styles.color} />
81
+
82
+ toolTip = applyTooltipsToGeo(displayGeoName(territory), territoryData);
83
+
84
+ const legendColors = applyLegendToRow(territoryData);
85
+
86
+ let textColor = '#FFF';
87
+
88
+ if (legendColors) {
89
+ // Use white text if the background is dark, and dark grey if it's light
90
+ if (chroma.contrast(textColor, legendColors[0]) < 3.5) {
91
+ textColor = '#202020';
92
+ }
93
+
94
+ let needsPointer = false;
95
+
96
+ // If we need to add a pointer cursor
97
+ if ((state.columns.navigate && territoryData[state.columns.navigate.name]) || state.tooltips.appearanceType === 'click') {
98
+ needsPointer = true;
99
+ }
100
+
101
+ styles = {
102
+ color: textColor,
103
+ fill: legendColors[0],
104
+ cursor: needsPointer ? 'pointer' : 'default',
105
+ '&:hover': {
106
+ fill: legendColors[1],
107
+ },
108
+ '&:active': {
109
+ fill: legendColors[2],
110
+ }
111
+ };
112
+
113
+ return (<Shape
114
+ key={label}
115
+ label={label}
116
+ css={styles}
117
+ text={styles.color}
118
+ data-tip={toolTip}
119
+ data-for="tooltip"
120
+ stroke={geoStrokeColor}
121
+ strokeWidth={1.5}
122
+ onClick={() => geoClickHandler(territory, territoryData)}
123
+ />)
124
+ }
125
+ });
126
+
127
+ const geoLabel = (geo, bgColor = "#FFFFFF", projection) => {
128
+ let centroid = projection(geoCentroid(geo))
129
+ let abbr = geo.properties.iso
130
+
131
+ if(undefined === abbr) return null
132
+
133
+ let textColor = "#FFF"
134
+
135
+ // Dynamic text color
136
+ if (chroma.contrast(textColor, bgColor) < 3.5 ) {
137
+ textColor = '#202020';
138
+ }
139
+
140
+ let x = 0, y = 5
141
+
142
+
143
+ return (
144
+ <g>
145
+ <line x1={centroid[0]} y1={centroid[1]} x2={centroid[0] + dx} y2={centroid[1] + dy} stroke="rgba(0,0,0,.5)" strokeWidth={1} />
146
+ <text x={4} strokeWidth="0" fontSize={13} style={{fill: "#202020"}} alignmentBaseline="middle" transform={`translate(${centroid[0] + dx}, ${centroid[1] + dy})`}>
147
+ {abbr.substring(3)}
148
+ </text>
149
+ </g>
150
+ )
151
+ }
152
+
153
+ // Constructs and displays markup for all geos on the map (except territories right now)
154
+ const constructGeoJsx = (geographies, projection) => {
155
+ let showLabel = state.general.displayStateLabels
156
+
157
+ const geosJsx = geographies.map(( {feature: geo, path = '', index}) => {
158
+ const key = isHex ? geo.properties.iso + '-hex-group' : geo.properties.iso + '-group'
159
+
160
+ let styles = {
161
+ fill: '#E6E6E6',
162
+ cursor: 'default'
163
+ }
164
+
165
+ // Map the name from the geo data with the appropriate key for the processed data
166
+ let geoKey = geo.properties.iso;
167
+
168
+ // Manually add Washington D.C. in for Hex maps
169
+
170
+ if(!geoKey) return
171
+
172
+ const geoData = data[geoKey];
173
+
174
+ let legendColors;
175
+ // Once we receive data for this geographic item, setup variables.
176
+ if (geoData !== undefined) {
177
+ legendColors = applyLegendToRow(geoData);
178
+ }
179
+
180
+ const geoDisplayName = displayGeoName(geoKey);
181
+
182
+ // If a legend applies, return it with appropriate information.
183
+ if (legendColors && legendColors[0] !== '#000000') {
184
+ const tooltip = applyTooltipsToGeo(geoDisplayName, geoData);
185
+
186
+ styles = {
187
+ fill: state.general.type !== 'bubble' ? legendColors[0] : '#E6E6E6',
188
+ cursor: 'default',
189
+ '&:hover': {
190
+ fill: state.general.type !== 'bubble' ? legendColors[1] : '#e6e6e6',
191
+ },
192
+ '&:active': {
193
+ fill: state.general.type !== 'bubble' ? legendColors[2] : '#e6e6e6',
194
+ },
195
+ };
196
+
197
+ // When to add pointer cursor
198
+ if ((state.columns.navigate && geoData[state.columns.navigate.name]) || state.tooltips.appearanceType === 'click') {
199
+ styles.cursor = 'pointer'
200
+ }
201
+
202
+ const TerratoryRect = (props) => {
203
+ const { posX = 0, tName } = props
204
+ let textColor = "#fff"
205
+
206
+ if (chroma.contrast(textColor, legendColors[0]) < 4.5) {
207
+ textColor = '#202020';
208
+ }
209
+ return (
210
+ <>
211
+ <rect x={posX} width="36" height="24" rx="6" stroke="#fff" strokeWidth="1" />
212
+ <text x={posX + 8} y="17" fill={textColor}>{tName}</text>
213
+ </>
214
+ )
215
+ }
216
+
217
+ const circleRadius = 15;
218
+
219
+ // SIDE CHART EXPERIMENT
220
+ // const height = state.data[index].Change;
221
+ // const barHeight = Math.abs(height * 20 );
222
+ // const barPositive = height > 0;
223
+ // const barY = barPositive ? -barHeight + 15 : 15;
224
+ // const baseY = 14;
225
+ // const barFill = barPositive ? "#fff" : "#fff";
226
+
227
+ return (
228
+ <g
229
+ data-for="tooltip"
230
+ data-tip={tooltip}
231
+ key={key}
232
+ className="geo-group"
233
+ css={styles}
234
+ onClick={() => geoClickHandler(geoDisplayName, geoData)}
235
+ >
236
+
237
+ <path
238
+ tabIndex={-1}
239
+ className='single-geo'
240
+ stroke={geoStrokeColor}
241
+ strokeWidth={1.3}
242
+ d={path}
243
+ />
244
+ <g id={`region-${index+1}-label`}>
245
+ <circle fill="#fff" stroke="#999" cx={circleRadius} cy={circleRadius} r={circleRadius}/>
246
+ <text fill="#333" x="15px" y="20px" textAnchor="middle">{index+1}</text>
247
+ {/* SIDE CHART EXPERIMENT */}
248
+ {/*<g y={barY*20}>*/}
249
+ {/* <rect x="-20" y={barY} width="10" height={barHeight} fill={barFill} stroke="#333"/>*/}
250
+ {/* <rect x="-23" y={baseY} width="16" height="2" fill="#000" />*/}
251
+ {/*</g>*/}
252
+ {/* / SIDE CHART EXPERIMENT */}
253
+ </g>
254
+ {geoKey === 'region 2' &&
255
+ <g id="region-2-territories">
256
+ <TerratoryRect tName="PR" />
257
+ <TerratoryRect posX={45} tName="VI" />
258
+ </g>
259
+ }
260
+
261
+ { geoKey === 'region 9' &&
262
+ <g id="region-9-territories">
263
+ <g className="region-9-row1">
264
+ <TerratoryRect tName="AS" />
265
+ <TerratoryRect posX={45} tName="GU" />
266
+ <TerratoryRect posX={90} tName="MP" />
267
+ </g>
268
+ <g className="region-9-row2">
269
+ <TerratoryRect tName="FM" />
270
+ <TerratoryRect posX={45} tName="PW" />
271
+ <TerratoryRect posX={90} tName="MH" />
272
+ </g>
273
+ </g>
274
+ }
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
+ return geosJsx;
298
+ };
299
+
300
+ return (
301
+ <ErrorBoundary component="UsaRegionMap">
302
+ <svg viewBox="0 0 880 500">
303
+
304
+ <Mercator data={focusedStates} scale={620} translate={[1500, 735]}>
305
+ {({ features, projection }) => constructGeoJsx(features, projection)}
306
+ </Mercator>
307
+
308
+ </svg>
309
+ {territories.length > 0 && (
310
+ <section className="territories">
311
+ <span className="label">{state.general.territoriesLabel}</span>
312
+ {territories}
313
+ </section>
314
+ )}
315
+ </ErrorBoundary>
316
+ );
317
+ };
318
+
319
+ export default memo(UsaRegionMap)
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, memo } from 'react';
1
+ import React, { useEffect, memo } from 'react';
2
2
  /** @jsx jsx */
3
3
  import { jsx } from '@emotion/react'
4
4
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
@@ -9,11 +9,43 @@ import topoJSON from '../data/world-topo.json';
9
9
  import ZoomableGroup from './ZoomableGroup';
10
10
  import Geo from './Geo'
11
11
  import CityList from './CityList';
12
+ import BubbleList from './BubbleList';
12
13
 
13
14
  const { features: world } = feature(topoJSON, topoJSON.objects.countries)
14
15
 
15
16
  let projection = geoMercator()
16
17
 
18
+ const WorldMap = (props) => {
19
+ const {
20
+ state,
21
+ applyTooltipsToGeo,
22
+ data,
23
+ geoClickHandler,
24
+ applyLegendToRow,
25
+ displayGeoName,
26
+ supportedCountries,
27
+ rebuildTooltips,
28
+ setState,
29
+ setRuntimeData,
30
+ generateRuntimeData,
31
+ setFilteredCountryCode,
32
+ position,
33
+ setPosition,
34
+ hasZoom
35
+ } = props;
36
+
37
+ // TODO Refactor - state should be set together here to avoid rerenders
38
+ // Resets to original data & zooms out
39
+ const handleReset = (state, setState, setRuntimeData, generateRuntimeData) => {
40
+ let reRun = generateRuntimeData(state)
41
+ setRuntimeData(reRun)
42
+ setState({
43
+ ...state,
44
+ focusedCountry: false,
45
+ mapPosition: { coordinates: [0, 30], zoom: 1 }
46
+ })
47
+ setFilteredCountryCode('')
48
+ }
17
49
  const handleZoomIn = (position, setPosition) => {
18
50
  if (position.zoom >= 4) return;
19
51
  setPosition((pos) => ({ ...pos, zoom: pos.zoom * 1.5 }));
@@ -24,9 +56,9 @@ const handleZoomOut = (position, setPosition) => {
24
56
  setPosition((pos) => ({ ...pos, zoom: pos.zoom / 1.5 }));
25
57
  };
26
58
 
27
- const ZoomControls = ({position, setPosition}) => (
59
+ const ZoomControls = ({position, setPosition, state, setState, setRuntimeData, generateRuntimeData}) => (
28
60
  <div className="zoom-controls" data-html2canvas-ignore>
29
- <button onClick={() => handleZoomIn(position, setPosition)}>
61
+ <button onClick={() => handleZoomIn(position, setPosition)} aria-label="Zoom In">
30
62
  <svg
31
63
  viewBox="0 0 24 24"
32
64
  stroke="currentColor"
@@ -36,7 +68,7 @@ const ZoomControls = ({position, setPosition}) => (
36
68
  <line x1="5" y1="12" x2="19" y2="12" />
37
69
  </svg>
38
70
  </button>
39
- <button onClick={() => handleZoomOut(position, setPosition)}>
71
+ <button onClick={() => handleZoomOut(position, setPosition)} aria-label="Zoom Out">
40
72
  <svg
41
73
  viewBox="0 0 24 24"
42
74
  stroke="currentColor"
@@ -45,22 +77,20 @@ const ZoomControls = ({position, setPosition}) => (
45
77
  <line x1="5" y1="12" x2="19" y2="12" />
46
78
  </svg>
47
79
  </button>
80
+ {state.general.type === 'bubble' &&
81
+ <button onClick={() => handleReset(state, setState, setRuntimeData, generateRuntimeData)} className="reset" aria-label="Reset Zoom and Map Filters">
82
+ Reset Filters
83
+ </button>
84
+ }
48
85
  </div>
49
86
  );
50
87
 
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 });
88
+ // TODO Refactor - state should be set together here to avoid rerenders
89
+ const handleCircleClick = (country, state, setState, setRuntimeData, generateRuntimeData) => {
90
+ if(!state.general.allowMapZoom) return;
91
+ let newRuntimeData = state.data.filter(item => item[state.columns.geo.name] === country[state.columns.geo.name])
92
+ setFilteredCountryCode(newRuntimeData[0].uid)
93
+ }
64
94
 
65
95
  useEffect(() => rebuildTooltips());
66
96
 
@@ -95,7 +125,7 @@ const WorldMap = (props) => {
95
125
  const strokeWidth = .9
96
126
 
97
127
  // If a legend applies, return it with appropriate information.
98
- if (legendColors && legendColors[0] !== '#000000') {
128
+ if (legendColors && legendColors[0] !== '#000000' && state.general.type !== 'bubble') {
99
129
  const tooltip = applyTooltipsToGeo(geoDisplayName, geoData);
100
130
 
101
131
  styles = {
@@ -145,29 +175,76 @@ const WorldMap = (props) => {
145
175
  applyLegendToRow={applyLegendToRow}
146
176
  />)
147
177
 
178
+ // Bubbles
179
+ if(state.general.type === 'bubble') {
180
+ geosJsx.push(
181
+ <BubbleList
182
+ key="bubbles"
183
+ data={state.data}
184
+ runtimeData={data}
185
+ state={state}
186
+ projection={projection}
187
+ applyLegendToRow={applyLegendToRow}
188
+ applyTooltipsToGeo={applyTooltipsToGeo}
189
+ displayGeoName={displayGeoName}
190
+ handleCircleClick={(country) => handleCircleClick(country, state, setState, setRuntimeData, generateRuntimeData) }
191
+ />
192
+ )
193
+ }
194
+
148
195
  return geosJsx;
149
196
  };
150
197
 
151
198
  return (
152
199
  <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}
200
+ {hasZoom ? (
201
+ <svg viewBox="0 0 880 500">
202
+ <rect height={500} width={880} onClick={() => handleReset(state, setState, setRuntimeData, generateRuntimeData)} fill="white"/>
203
+ <ZoomableGroup
204
+ zoom={position.zoom}
205
+ center={position.coordinates}
206
+ onMoveEnd={handleMoveEnd}
207
+ maxZoom={4}
208
+ projection={projection}
209
+ width={880}
210
+ height={500}
165
211
  >
166
- {({features}) => constructGeoJsx(features)}
167
- </Mercator>
168
- </ZoomableGroup>
169
- </svg>
170
- {state.general.type === 'data' && <ZoomControls position={position} setPosition={setPosition} />}
212
+ <Mercator
213
+ data={world}
214
+ >
215
+ {({ features }) => constructGeoJsx(features)}
216
+ </Mercator>
217
+ </ZoomableGroup>
218
+ </svg>
219
+ ) :
220
+ <svg viewBox="0 0 880 500">
221
+ <ZoomableGroup
222
+ zoom={1}
223
+ center={position.coordinates}
224
+ onMoveEnd={handleMoveEnd}
225
+ maxZoom={0}
226
+ projection={projection}
227
+ width={880}
228
+ height={500}
229
+ >
230
+ <Mercator
231
+ data={world}
232
+ >
233
+ {({ features }) => constructGeoJsx(features)}
234
+ </Mercator>
235
+ </ZoomableGroup>
236
+ </svg>
237
+ }
238
+ {(state.general.type === 'data' || state.general.type === 'bubble' && hasZoom) &&
239
+ <ZoomControls
240
+ position={position}
241
+ setPosition={setPosition}
242
+ setRuntimeData={setRuntimeData}
243
+ state={state}
244
+ setState={setState}
245
+ generateRuntimeData={generateRuntimeData} />
246
+ }
247
+
171
248
  </ErrorBoundary>
172
249
  );
173
250
  };
@@ -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
+ };