@cdc/map 4.24.1 → 4.24.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 (43) hide show
  1. package/dist/cdcmap.js +53648 -47918
  2. package/examples/508.json +548 -0
  3. package/examples/default-county.json +0 -28
  4. package/examples/default-hex.json +110 -13
  5. package/examples/default-usa.json +69 -28
  6. package/examples/test.json +0 -9614
  7. package/examples/usa-special-class-legend.json +501 -0
  8. package/examples/{private/zika-issue.json → zika.json} +47 -51
  9. package/index.html +11 -5
  10. package/package.json +3 -3
  11. package/src/CdcMap.tsx +84 -32
  12. package/src/components/BubbleList.jsx +9 -1
  13. package/src/components/CityList.jsx +94 -31
  14. package/src/components/DataTable.jsx +7 -7
  15. package/src/components/EditorPanel/components/EditorPanel.tsx +181 -46
  16. package/src/components/EditorPanel/components/HexShapeSettings.tsx +18 -3
  17. package/src/components/Geo.jsx +4 -2
  18. package/src/components/Legend/components/Legend.tsx +67 -13
  19. package/src/components/Legend/components/LegendItem.Hex.tsx +5 -9
  20. package/src/components/Legend/components/index.scss +31 -5
  21. package/src/components/UsaMap/components/HexIcon.tsx +41 -0
  22. package/src/components/UsaMap/components/Territory/Territory.Hexagon.tsx +38 -19
  23. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +10 -21
  24. package/src/components/UsaMap/components/UsaMap.Region.tsx +11 -37
  25. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +0 -1
  26. package/src/components/UsaMap/components/UsaMap.State.tsx +62 -61
  27. package/src/components/UsaMap/helpers/patternSizes.tsx +5 -0
  28. package/src/components/WorldMap/components/WorldMap.jsx +16 -8
  29. package/src/components/WorldMap/data/world-topo-guiana-update.json +1 -0
  30. package/src/components/WorldMap/data/world-topo-old.json +1 -0
  31. package/{examples/private/new-world.json → src/components/WorldMap/data/world-topo-recent.json} +23137 -22280
  32. package/src/components/WorldMap/data/world-topo.json +1 -1
  33. package/src/data/initial-state.js +5 -2
  34. package/src/data/supported-geos.js +21 -1
  35. package/src/hooks/useTooltip.ts +4 -4
  36. package/src/scss/editor-panel.scss +5 -3
  37. package/src/scss/main.scss +2 -1
  38. package/src/scss/map.scss +22 -12
  39. package/src/types/MapConfig.ts +7 -0
  40. package/examples/private/map-text-wrap.json +0 -574
  41. package/examples/private/map-world-data.json +0 -1046
  42. package/examples/world-geocode-data.json +0 -18
  43. package/examples/world-geocode.json +0 -108
@@ -3,86 +3,98 @@ import { useState, useEffect } from 'react'
3
3
  import { jsx } from '@emotion/react'
4
4
  import { supportedCities } from '../data/supported-geos'
5
5
  import { scaleLinear } from 'd3-scale'
6
+ import { GlyphStar, GlyphTriangle, GlyphDiamond, GlyphSquare, GlyphCircle } from '@visx/glyph'
6
7
 
7
- const CityList = ({ data, state, geoClickHandler, applyTooltipsToGeo, displayGeoName, applyLegendToRow, projection, titleCase, setSharedFilterValue, isFilterValueSupported, isGeoCodeMap }) => {
8
+ const CityList = ({ data, state, geoClickHandler, applyTooltipsToGeo, displayGeoName, applyLegendToRow, projection, titleCase, setSharedFilterValue, isFilterValueSupported }) => {
8
9
  const [citiesData, setCitiesData] = useState({})
9
10
 
10
11
  useEffect(() => {
11
- if (!isGeoCodeMap) {
12
- const citiesList = Object.keys(data).filter(item => Object.keys(supportedCities).includes(item))
12
+ const citiesDictionary = {}
13
13
 
14
- const citiesDictionary = {}
15
-
16
- citiesList.map(city => (citiesDictionary[city] = data[city]))
17
-
18
- setCitiesData(citiesDictionary)
19
- } else {
20
- const citiesDictionary = {}
21
- state.data.map(city => (citiesDictionary[city[state.columns.geo.name]] = city))
22
- setCitiesData(citiesDictionary)
14
+ if (data) {
15
+ Object.keys(data).forEach(key => {
16
+ const city = data[key]
17
+ citiesDictionary[city[state.columns.geo.name]] = city
18
+ })
23
19
  }
24
- }, [data, state.data])
20
+
21
+ setCitiesData(citiesDictionary)
22
+ }, [data])
25
23
 
26
24
  if (state.general.type === 'bubble') {
27
- const maxDataValue = Math.max(...state.data.map(d => d[state.columns.primary.name]))
25
+ const maxDataValue = Math.max(...(data ? Object.keys(data).map(key => data[key][state.columns.primary.name]) : [0]))
28
26
  const sortedRuntimeData = Object.values(data).sort((a, b) => (a[state.columns.primary.name] < b[state.columns.primary.name] ? 1 : -1))
29
27
  if (!sortedRuntimeData) return
30
28
 
31
29
  // Set bubble sizes
32
30
  var size = scaleLinear().domain([1, maxDataValue]).range([state.visual.minBubbleSize, state.visual.maxBubbleSize])
33
31
  }
34
- let cityList = isGeoCodeMap ? Object.keys(citiesData).filter(c => undefined !== c) : Object.keys(citiesData).filter(c => undefined !== data[c])
32
+ let cityList = Object.keys(citiesData).filter(c => undefined !== c || undefined !== data[c])
35
33
  if (!cityList) return true
36
34
 
37
35
  // Cities output
38
36
  const cities = cityList.map((city, i) => {
39
- const geoData = isGeoCodeMap ? state.data.filter(item => city === item[state.columns.geo.name])[0] : data[city]
40
- const cityDisplayName = isGeoCodeMap ? city : titleCase(displayGeoName(city))
37
+ let geoData
38
+ if (data) {
39
+ Object.keys(data).forEach(key => {
40
+ if (city === data[key][state.columns.geo.name]) {
41
+ geoData = data[key]
42
+ }
43
+ })
44
+ }
45
+ if (!geoData) {
46
+ geoData = data ? data[city] : undefined
47
+ }
48
+ const cityDisplayName = titleCase(displayGeoName(city))
41
49
 
42
- const legendColors = isGeoCodeMap && geoData ? applyLegendToRow(geoData) : data[city] ? applyLegendToRow(data[city]) : false
50
+ const legendColors = geoData ? applyLegendToRow(geoData) : data[city] ? applyLegendToRow(data[city]) : false
43
51
 
44
52
  if (legendColors === false) {
45
53
  return true
46
54
  }
47
55
 
48
- const toolTip = applyTooltipsToGeo(cityDisplayName, isGeoCodeMap ? geoData : data[city])
56
+ const toolTip = applyTooltipsToGeo(cityDisplayName, geoData || data[city])
49
57
 
50
- const radius = state.general.geoType === 'us' && !isGeoCodeMap ? 8 : isGeoCodeMap ? state.visual.geoCodeCircleSize : 4
58
+ const radius = state.visual.geoCodeCircleSize || 8
51
59
 
52
60
  const additionalProps = {
53
61
  fillOpacity: state.general.type === 'bubble' ? 0.4 : 1
54
62
  }
55
63
 
56
- const circle = <circle cx={0} cy={0} r={state.general.type === 'bubble' ? size(geoData[state.columns.primary.name]) : radius} title='Click for more information' onClick={() => geoClickHandler(cityDisplayName, geoData)} data-tooltip-id='tooltip' data-tooltip-html={toolTip} {...additionalProps} />
57
-
58
64
  const pin = (
59
65
  <path
60
66
  className='marker'
61
67
  d='M0,0l-8.8-17.7C-12.1-24.3-7.4-32,0-32h0c7.4,0,12.1,7.7,8.8,14.3L0,0z'
62
- title='Click for more information'
68
+ title='Select for more information'
63
69
  onClick={() => geoClickHandler(cityDisplayName, geoData)}
64
- strokeWidth={2}
65
- stroke={'black'}
66
70
  data-tooltip-id='tooltip'
67
71
  data-tooltip-html={toolTip}
72
+ transform={`scale(${radius / 9})`}
73
+ stroke={state.general.geoBorderColor === 'sameAsBackground' ? '#ffffff' : '#000000'}
74
+ strokeWidth={'2px'}
75
+ tabIndex='-1'
68
76
  {...additionalProps}
69
77
  />
70
78
  )
71
79
 
72
80
  let transform = ''
73
81
 
74
- if (!isGeoCodeMap) {
75
- transform = `translate(${projection(supportedCities[city])})`
82
+ if (!geoData?.[state.columns.longitude.name] && !geoData?.[state.columns.latitude.name] && city && supportedCities[city.toUpperCase()]) {
83
+ transform = `translate(${projection(supportedCities[city.toUpperCase()])})`
76
84
  }
77
85
 
78
86
  let needsPointer = false
79
87
 
80
- if (isGeoCodeMap) {
88
+ if (geoData?.[state.columns.longitude.name] && geoData?.[state.columns.latitude.name]) {
81
89
  let coords = [Number(geoData?.[state.columns.longitude.name]), Number(geoData?.[state.columns.latitude.name])]
82
90
  transform = `translate(${projection(coords)})`
83
91
  needsPointer = true
84
92
  }
85
93
 
94
+ if (!transform) {
95
+ return
96
+ }
97
+
86
98
  const styles = {
87
99
  fill: legendColors[0],
88
100
  opacity: setSharedFilterValue && isFilterValueSupported && data[city][state.columns.geo.name] !== setSharedFilterValue ? 0.5 : 1,
@@ -103,10 +115,61 @@ const CityList = ({ data, state, geoClickHandler, applyTooltipsToGeo, displayGeo
103
115
  styles.cursor = 'pointer'
104
116
  }
105
117
 
118
+ const shapeProps = {
119
+ onClick: () => geoClickHandler(cityDisplayName, geoData),
120
+ size: state.general.type === 'bubble' ? size(geoData[state.columns.primary.name]) : radius * 30,
121
+ title: 'Select for more information',
122
+ 'data-tooltip-id': 'tooltip',
123
+ 'data-tooltip-html': toolTip,
124
+ stroke: state.general.geoBorderColor === 'sameAsBackground' ? '#ffffff' : '#000000',
125
+ strokeWidth: '2px',
126
+ tabIndex: -1,
127
+ ...additionalProps
128
+ }
129
+
130
+ const cityStyleShapes = {
131
+ circle: <GlyphCircle {...shapeProps} />,
132
+ pin: pin,
133
+ square: <GlyphSquare {...shapeProps} />,
134
+ diamond: <GlyphDiamond {...shapeProps} />,
135
+ star: <GlyphStar {...shapeProps} />,
136
+ triangle: <GlyphTriangle {...shapeProps} />
137
+ }
138
+
139
+ const { additionalCityStyles } = state.visual || []
140
+ const cityStyle = Object.values(data)
141
+ .filter(d => additionalCityStyles.some(style => String(d[style.column]) === String(style.value)))
142
+ .map(d => {
143
+ const conditionsMatched = additionalCityStyles.find(style => String(d[style.column]) === String(style.value))
144
+ return { ...conditionsMatched, ...d }
145
+ })
146
+ .find(item => {
147
+ return Object.keys(item).find(key => item[key] === city)
148
+ })
149
+
150
+ if (cityStyle !== undefined && cityStyle.shape) {
151
+ if (!geoData?.[state.columns.longitude.name] && !geoData?.[state.columns.latitude.name] && city && supportedCities[city.toUpperCase()]) {
152
+ let translate = `translate(${projection(supportedCities[city.toUpperCase()])})`
153
+ return (
154
+ <g key={i} transform={translate} style={styles} className='geo-point' tabIndex={-1}>
155
+ {cityStyleShapes[cityStyle.shape.toLowerCase()]}
156
+ </g>
157
+ )
158
+ }
159
+
160
+ if (geoData?.[state.columns.longitude.name] && geoData?.[state.columns.latitude.name]) {
161
+ const coords = [Number(geoData?.[state.columns.longitude.name]), Number(geoData?.[state.columns.latitude.name])]
162
+ let translate = `translate(${projection(coords)})`
163
+ return (
164
+ <g key={i} transform={translate} style={styles} className='geo-point' tabIndex={-1}>
165
+ {cityStyleShapes[cityStyle.shape.toLowerCase()]}
166
+ </g>
167
+ )
168
+ }
169
+ }
106
170
  return (
107
- <g key={i} transform={transform} style={styles} className='geo-point'>
108
- {state.visual.cityStyle === 'circle' && circle}
109
- {state.visual.cityStyle === 'pin' && pin}
171
+ <g key={i} transform={transform} style={styles} className='geo-point' tabIndex={-1}>
172
+ {cityStyleShapes[state.visual.cityStyle.toLowerCase()]}
110
173
  </g>
111
174
  )
112
175
  })
@@ -7,6 +7,7 @@ import Icon from '@cdc/core/components/ui/Icon'
7
7
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
8
8
  import LegendCircle from '@cdc/core/components/LegendCircle'
9
9
  import MediaControls from '@cdc/core/components/MediaControls'
10
+ import SkipTo from '@cdc/core/components/elements/SkipTo'
10
11
 
11
12
  import Loading from '@cdc/core/components/Loading'
12
13
 
@@ -193,9 +194,7 @@ const DataTable = props => {
193
194
  {state.general.showDownloadButton && <DownloadButton />}
194
195
  </MediaControls.Section>
195
196
  <section id={tabbingId.replace('#', '')} className={`data-table-container ${viewport}`} aria-label={accessibilityLabel}>
196
- <a id='skip-nav' className='cdcdataviz-sr-only-focusable' href={`#${skipId}`}>
197
- Skip Navigation or Skip to Content
198
- </a>
197
+ <SkipTo skipId={skipId} skipMessage='Skip Data Table' />
199
198
  <div
200
199
  className={expanded ? 'data-table-heading' : 'collapsed data-table-heading'}
201
200
  onClick={() => {
@@ -229,7 +228,7 @@ const DataTable = props => {
229
228
  return (
230
229
  <th
231
230
  key={`col-header-${column}`}
232
- tabIndex='0'
231
+ tabIndex={0}
233
232
  title={text}
234
233
  role='columnheader'
235
234
  scope='col'
@@ -245,9 +244,7 @@ const DataTable = props => {
245
244
  {...(sortBy.column === column ? (sortBy.asc ? { 'aria-sort': 'ascending' } : { 'aria-sort': 'descending' }) : null)}
246
245
  >
247
246
  {text}
248
- <button>
249
- <span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'} `} order</span>
250
- </button>
247
+ <span className='cdcdataviz-sr-only'>{`Sort by ${text} in ${sortBy.column === column ? (!sortBy.asc ? 'descending' : 'ascending') : 'descending'} order`}</span>
251
248
  </th>
252
249
  )
253
250
  })}
@@ -298,6 +295,9 @@ const DataTable = props => {
298
295
  </table>
299
296
  </div>
300
297
  </section>
298
+ <div id={skipId} className='cdcdataviz-sr-only'>
299
+ Skipped data table.
300
+ </div>
301
301
  </ErrorBoundary>
302
302
  )
303
303
  }
@@ -117,6 +117,75 @@ const EditorPanel = props => {
117
117
  specialClasses = legend.specialClasses || []
118
118
  }
119
119
 
120
+ const getCityStyleOptions = target => {
121
+ switch (target) {
122
+ case 'value': {
123
+ const values = ['Circle', 'Square', 'Triangle', 'Diamond', 'Star', 'Pin']
124
+ const filteredValues = values.filter(val => String(state.visual.cityStyle).toLocaleLowerCase() !== val.toLocaleLowerCase())
125
+
126
+ return (
127
+ <>
128
+ <option value='' key={'Select Option'}>
129
+ - Select Option -
130
+ </option>
131
+ {filteredValues.map((val, i) => {
132
+ return (
133
+ <option key={i} value={val}>
134
+ {val}
135
+ </option>
136
+ )
137
+ })}
138
+ </>
139
+ )
140
+ }
141
+ }
142
+ }
143
+
144
+ const editCityStyles = (target, index, fieldName, value) => {
145
+ switch (target) {
146
+ case 'add': {
147
+ const additionalCityStyles = state.visual.additionalCityStyles ? [...state.visual.additionalCityStyles] : []
148
+ additionalCityStyles.push({ label: '', column: '', value: '', shape: '' })
149
+ setState({
150
+ ...state,
151
+ visual: {
152
+ ...state.visual,
153
+ additionalCityStyles: additionalCityStyles
154
+ }
155
+ })
156
+ break
157
+ }
158
+ case 'remove': {
159
+ let additionalCityStyles = []
160
+ if (state.visual.additionalCityStyles) {
161
+ additionalCityStyles = [...state.visual.additionalCityStyles]
162
+ }
163
+
164
+ additionalCityStyles.splice(index, 1)
165
+ setState({
166
+ ...state,
167
+ visual: {
168
+ ...state.visual,
169
+ additionalCityStyles: additionalCityStyles
170
+ }
171
+ })
172
+ break
173
+ }
174
+ case 'update': {
175
+ let additionalCityStyles = []
176
+ additionalCityStyles = [...state.visual.additionalCityStyles]
177
+ additionalCityStyles[index][fieldName] = value
178
+ setState({
179
+ ...state,
180
+ visual: {
181
+ ...state.visual,
182
+ additionalCityStyles: additionalCityStyles
183
+ }
184
+ })
185
+ }
186
+ }
187
+ }
188
+
120
189
  const DynamicDesc = ({ label, fieldName, value: stateValue, type = 'input', ...attributes }) => {
121
190
  const [value, setValue] = useState(stateValue)
122
191
 
@@ -134,8 +203,6 @@ const EditorPanel = props => {
134
203
  }
135
204
 
136
205
  const handleEditorChanges = async (property, value) => {
137
- console.log('prop', property)
138
- console.log('value', value)
139
206
  switch (property) {
140
207
  // change these to be more generic.
141
208
  // updateVisualPropertyValue
@@ -278,6 +345,7 @@ const EditorPanel = props => {
278
345
  setState({
279
346
  ...state,
280
347
  visual: {
348
+ ...state.visual,
281
349
  cityStyle: value
282
350
  }
283
351
  })
@@ -380,20 +448,6 @@ const EditorPanel = props => {
380
448
  }
381
449
  })
382
450
  break
383
- case 'toggleDownloadButton':
384
- setState({
385
- ...state,
386
- general: {
387
- ...state.general,
388
- showDownloadButton: !state.general.showDownloadButton
389
- },
390
- table: {
391
- // setting both bc DataTable new core needs it here
392
- ...state.table,
393
- download: !state.general.showDownloadButton
394
- }
395
- })
396
- break
397
451
  case 'toggleShowFullGeoNameInCSV':
398
452
  setState({
399
453
  ...state,
@@ -1290,6 +1344,8 @@ const EditorPanel = props => {
1290
1344
  </select>
1291
1345
  </label>
1292
1346
 
1347
+ <TextField value={state.filters[index].setByQueryParameter} section='filters' subsection={index} fieldName='setByQueryParameter' label='Default Value Set By Query String Parameter' updateField={updateField} />
1348
+
1293
1349
  {filter.order === 'cust' && (
1294
1350
  <DragDropContext onDragEnd={({ source, destination }) => handleFilterOrder(source.index, destination.index, index, state.filters[index])}>
1295
1351
  <Droppable droppableId='filter_order'>
@@ -1929,7 +1985,7 @@ const EditorPanel = props => {
1929
1985
  </label>
1930
1986
  </fieldset>
1931
1987
  )}
1932
- {('us-geocode' === state.general.type || 'world-geocode' === state.general.type) && (
1988
+ {
1933
1989
  <>
1934
1990
  <label>Latitude Column</label>
1935
1991
  <select
@@ -1950,7 +2006,7 @@ const EditorPanel = props => {
1950
2006
  {columnsOptions}
1951
2007
  </select>
1952
2008
  </>
1953
- )}
2009
+ }
1954
2010
 
1955
2011
  {'navigation' !== state.general.type && (
1956
2012
  <fieldset className='primary-fieldset edit-block'>
@@ -2635,16 +2691,6 @@ const EditorPanel = props => {
2635
2691
  <span className='edit-label'>Show URL to Automatically Updated Data</span>
2636
2692
  </label>
2637
2693
  )}
2638
- <label className='checkbox'>
2639
- <input
2640
- type='checkbox'
2641
- checked={state.general.showDownloadButton}
2642
- onChange={event => {
2643
- handleEditorChanges('toggleDownloadButton', event.target.checked)
2644
- }}
2645
- />
2646
- <span className='edit-label'>Show Download CSV Link</span>
2647
- </label>
2648
2694
  <label className='checkbox'>
2649
2695
  <input
2650
2696
  type='checkbox'
@@ -2850,12 +2896,10 @@ const EditorPanel = props => {
2850
2896
  )
2851
2897
  })}
2852
2898
  </ul>
2853
- {('us-geocode' === state.general.type || 'world-geocode' === state.general.type) && state.visual.cityStyle === 'circle' && (
2854
- <label>
2855
- Geocode Settings
2856
- <TextField type='number' value={state.visual.geoCodeCircleSize} section='visual' max='10' fieldName='geoCodeCircleSize' label='Geocode Circle Size' updateField={updateField} />
2857
- </label>
2858
- )}
2899
+ <label>
2900
+ Geocode Settings
2901
+ <TextField type='number' value={state.visual.geoCodeCircleSize} section='visual' max='10' fieldName='geoCodeCircleSize' label='Geocode Circle Size' updateField={updateField} />
2902
+ </label>
2859
2903
 
2860
2904
  {state.general.type === 'bubble' && (
2861
2905
  <>
@@ -2900,19 +2944,109 @@ const EditorPanel = props => {
2900
2944
  </label>
2901
2945
  )}
2902
2946
  {(state.general.geoType === 'us' || state.general.geoType === 'us-county' || state.general.geoType === 'world') && (
2903
- <label>
2904
- <span className='edit-label'>City Style</span>
2905
- <select
2906
- value={state.visual.cityStyle || false}
2907
- onChange={event => {
2908
- handleEditorChanges('handleCityStyle', event.target.value)
2909
- }}
2910
- >
2911
- <option value='circle'>Circle</option>
2912
- <option value='pin'>Pin</option>
2913
- </select>
2914
- </label>
2947
+ <>
2948
+ <label>
2949
+ <span className='edit-label'>Default City Style</span>
2950
+ <select
2951
+ value={state.visual.cityStyle || false}
2952
+ onChange={event => {
2953
+ handleEditorChanges('handleCityStyle', event.target.value)
2954
+ }}
2955
+ >
2956
+ <option value='circle'>Circle</option>
2957
+ <option value='pin'>Pin</option>
2958
+ <option value='square'>Square</option>
2959
+ <option value='triangle'>Triangle</option>
2960
+ <option value='diamond'>Diamond</option>
2961
+ <option value='star'>Star</option>
2962
+ </select>
2963
+ </label>
2964
+ <TextField
2965
+ value={state.visual.cityStyleLabel}
2966
+ section='visual'
2967
+ fieldName='cityStyleLabel'
2968
+ label='Label (Optional) '
2969
+ updateField={updateField}
2970
+ tooltip={
2971
+ <Tooltip style={{ textTransform: 'none' }}>
2972
+ <Tooltip.Target>
2973
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
2974
+ </Tooltip.Target>
2975
+ <Tooltip.Content>
2976
+ <p>When a label is provided, the default city style will appear in the legend.</p>
2977
+ </Tooltip.Content>
2978
+ </Tooltip>
2979
+ }
2980
+ />
2981
+ </>
2915
2982
  )}
2983
+ {/* <AdditionalCityStyles /> */}
2984
+ <>
2985
+ {state.visual.additionalCityStyles.length > 0 &&
2986
+ state.visual.additionalCityStyles.map(({ label, column, value, shape }, i) => {
2987
+ return (
2988
+ <div className='edit-block' key={`additional-city-style-${i}`}>
2989
+ <button
2990
+ className='remove-column'
2991
+ onClick={e => {
2992
+ e.preventDefault()
2993
+ editCityStyles('remove', i, '', '')
2994
+ }}
2995
+ >
2996
+ Remove
2997
+ </button>
2998
+ <p>City Style {i + 1}</p>
2999
+ <label>
3000
+ <span className='edit-label column-heading'>Column with configuration value</span>
3001
+ <select
3002
+ value={column}
3003
+ onChange={e => {
3004
+ editCityStyles('update', i, 'column', e.target.value)
3005
+ }}
3006
+ >
3007
+ {columnsOptions}
3008
+ </select>
3009
+ </label>
3010
+ <label>
3011
+ <span className='edit-label column-heading'>Value to Trigger</span>
3012
+ <input
3013
+ type='text'
3014
+ value={value}
3015
+ onChange={e => {
3016
+ editCityStyles('update', i, 'value', e.target.value)
3017
+ }}
3018
+ ></input>
3019
+ </label>
3020
+ <label>
3021
+ <span className='edit-label column-heading'>Shape</span>
3022
+ <select
3023
+ value={shape}
3024
+ onChange={e => {
3025
+ editCityStyles('update', i, 'shape', e.target.value)
3026
+ }}
3027
+ >
3028
+ {getCityStyleOptions('value')}
3029
+ </select>
3030
+ </label>
3031
+ <label>
3032
+ <span className='edit-label column-heading'>Label</span>
3033
+ <input
3034
+ key={i}
3035
+ type='text'
3036
+ value={label}
3037
+ onChange={e => {
3038
+ editCityStyles('update', i, 'label', e.target.value)
3039
+ }}
3040
+ />
3041
+ </label>
3042
+ </div>
3043
+ )
3044
+ })}
3045
+
3046
+ <button type='button' onClick={() => editCityStyles('add', 0, '', '')} className='btn full-width'>
3047
+ Add city style
3048
+ </button>
3049
+ </>
2916
3050
  <label htmlFor='opacity'>
2917
3051
  <TextField type='number' min={0} max={100} value={state.tooltips.opacity ? state.tooltips.opacity : 100} section='tooltips' fieldName='opacity' label='Tooltip Opacity (%)' updateField={updateField} />
2918
3052
  </label>
@@ -2962,6 +3096,7 @@ const EditorPanel = props => {
2962
3096
  <button className={'btn full-width'} onClick={handleAddLayer}>
2963
3097
  Add Map Layer
2964
3098
  </button>
3099
+ <p className='layer-purpose-details'>Context should be added to your visualization or associated page to describe the significance of layers that are added to maps.</p>
2965
3100
  </AccordionItemPanel>
2966
3101
  </AccordionItem>
2967
3102
  {state.general.geoType === 'us' && <Panels.PatternSettings name='Pattern Settings' />}
@@ -4,7 +4,7 @@ import parse from 'html-react-parser'
4
4
  import { AiOutlineArrowUp, AiOutlineArrowDown, AiOutlineArrowRight } from 'react-icons/ai'
5
5
  import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
6
6
 
7
- const shapeOptions = ['Arrow Up', 'Arrow Down', 'Arrow Right', 'None']
7
+ const shapeOptions = ['Arrow Up', 'Arrow Down', 'Arrow Right', 'Arrow Left', 'None']
8
8
 
9
9
  // todo: Move duplicated operators to CORE
10
10
  export const DATA_OPERATOR_LESS = '<'
@@ -221,6 +221,21 @@ const HexSettingShapeColumns = props => {
221
221
  {[DATA_OPERATOR_EQUAL].map(option => {
222
222
  return <option value={option}>{option}</option>
223
223
  })}
224
+ {[DATA_OPERATOR_NOTEQUAL].map(option => {
225
+ return <option value={option}>{option}</option>
226
+ })}
227
+ {[DATA_OPERATOR_LESS].map(option => {
228
+ return <option value={option}>{option}</option>
229
+ })}
230
+ {[DATA_OPERATOR_GREATER].map(option => {
231
+ return <option value={option}>{option}</option>
232
+ })}
233
+ {[DATA_OPERATOR_LESSEQUAL].map(option => {
234
+ return <option value={option}>{option}</option>
235
+ })}
236
+ {[DATA_OPERATOR_GREATEREQUAL].map(option => {
237
+ return <option value={option}>{option}</option>
238
+ })}
224
239
  </select>
225
240
  </div>
226
241
  <div className='cove-accordion__panel-col cove-input'>
@@ -255,7 +270,7 @@ const HexSettingShapeColumns = props => {
255
270
  ...group.items,
256
271
  {
257
272
  key: '',
258
- shape: 'Arrow up',
273
+ shape: 'Arrow Up',
259
274
  column: '',
260
275
  operator: '=',
261
276
  value: ''
@@ -309,7 +324,7 @@ const HexSettingShapeColumns = props => {
309
324
  copy.push({
310
325
  legendTitle: '',
311
326
  legendDescription: '',
312
- items: [{ key: '', shape: 'Arrow up', column: '', operator: '=', value: '' }]
327
+ items: [{ key: '', shape: 'Arrow Up', column: '', operator: '=', value: '' }]
313
328
  })
314
329
  copy.legendTitle = ''
315
330
  copy.legendDescription = ''
@@ -1,9 +1,11 @@
1
1
  import React, { memo } from 'react'
2
2
 
3
3
  const Geo = ({ path, styles, stroke, strokeWidth, ...props }) => {
4
+ const { className, ...restProps } = props
5
+ const geoClassName = String(props.additionalData?.name)?.toLowerCase()?.replaceAll(' ', '') || 'country'
4
6
  return (
5
- <g className='geo-group' css={styles} {...props}>
6
- <path tabIndex={-1} className='single-geo' stroke={stroke} strokeWidth={strokeWidth} d={path} />
7
+ <g className={`geo-group ${geoClassName}`} style={styles} {...restProps}>
8
+ <path tabIndex={-1} className={`single-geo ${geoClassName}`} stroke={stroke} strokeWidth={strokeWidth} d={path} />
7
9
  </g>
8
10
  )
9
11
  }