@cdc/map 4.24.2 → 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.
@@ -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
 
@@ -379,20 +448,6 @@ const EditorPanel = props => {
379
448
  }
380
449
  })
381
450
  break
382
- case 'toggleDownloadButton':
383
- setState({
384
- ...state,
385
- general: {
386
- ...state.general,
387
- showDownloadButton: !state.general.showDownloadButton
388
- },
389
- table: {
390
- // setting both bc DataTable new core needs it here
391
- ...state.table,
392
- download: !state.general.showDownloadButton
393
- }
394
- })
395
- break
396
451
  case 'toggleShowFullGeoNameInCSV':
397
452
  setState({
398
453
  ...state,
@@ -1289,6 +1344,8 @@ const EditorPanel = props => {
1289
1344
  </select>
1290
1345
  </label>
1291
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
+
1292
1349
  {filter.order === 'cust' && (
1293
1350
  <DragDropContext onDragEnd={({ source, destination }) => handleFilterOrder(source.index, destination.index, index, state.filters[index])}>
1294
1351
  <Droppable droppableId='filter_order'>
@@ -1928,7 +1985,7 @@ const EditorPanel = props => {
1928
1985
  </label>
1929
1986
  </fieldset>
1930
1987
  )}
1931
- {(
1988
+ {
1932
1989
  <>
1933
1990
  <label>Latitude Column</label>
1934
1991
  <select
@@ -1949,7 +2006,7 @@ const EditorPanel = props => {
1949
2006
  {columnsOptions}
1950
2007
  </select>
1951
2008
  </>
1952
- )}
2009
+ }
1953
2010
 
1954
2011
  {'navigation' !== state.general.type && (
1955
2012
  <fieldset className='primary-fieldset edit-block'>
@@ -2634,16 +2691,6 @@ const EditorPanel = props => {
2634
2691
  <span className='edit-label'>Show URL to Automatically Updated Data</span>
2635
2692
  </label>
2636
2693
  )}
2637
- <label className='checkbox'>
2638
- <input
2639
- type='checkbox'
2640
- checked={state.general.showDownloadButton}
2641
- onChange={event => {
2642
- handleEditorChanges('toggleDownloadButton', event.target.checked)
2643
- }}
2644
- />
2645
- <span className='edit-label'>Show Download CSV Link</span>
2646
- </label>
2647
2694
  <label className='checkbox'>
2648
2695
  <input
2649
2696
  type='checkbox'
@@ -2849,12 +2896,10 @@ const EditorPanel = props => {
2849
2896
  )
2850
2897
  })}
2851
2898
  </ul>
2852
- {state.visual.cityStyle === 'circle' && (
2853
- <label>
2854
- Geocode Settings
2855
- <TextField type='number' value={state.visual.geoCodeCircleSize} section='visual' max='10' fieldName='geoCodeCircleSize' label='Geocode Circle Size' updateField={updateField} />
2856
- </label>
2857
- )}
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>
2858
2903
 
2859
2904
  {state.general.type === 'bubble' && (
2860
2905
  <>
@@ -2899,19 +2944,109 @@ const EditorPanel = props => {
2899
2944
  </label>
2900
2945
  )}
2901
2946
  {(state.general.geoType === 'us' || state.general.geoType === 'us-county' || state.general.geoType === 'world') && (
2902
- <label>
2903
- <span className='edit-label'>City Style</span>
2904
- <select
2905
- value={state.visual.cityStyle || false}
2906
- onChange={event => {
2907
- handleEditorChanges('handleCityStyle', event.target.value)
2908
- }}
2909
- >
2910
- <option value='circle'>Circle</option>
2911
- <option value='pin'>Pin</option>
2912
- </select>
2913
- </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
+ </>
2914
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
+ </>
2915
3050
  <label htmlFor='opacity'>
2916
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} />
2917
3052
  </label>
@@ -2961,6 +3096,7 @@ const EditorPanel = props => {
2961
3096
  <button className={'btn full-width'} onClick={handleAddLayer}>
2962
3097
  Add Map Layer
2963
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>
2964
3100
  </AccordionItemPanel>
2965
3101
  </AccordionItem>
2966
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'>
@@ -1,18 +1,20 @@
1
1
  //TODO: Move legends to core
2
- import { useContext } from 'react'
2
+ import { forwardRef, useContext } from 'react'
3
3
  import parse from 'html-react-parser'
4
- import chroma from 'chroma-js'
5
4
 
6
5
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
7
6
  import LegendCircle from '@cdc/core/components/LegendCircle'
8
7
  import LegendItemHex from './LegendItem.Hex'
8
+ import Button from '@cdc/core/components/elements/Button'
9
9
 
10
10
  import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
11
11
  import ConfigContext from '../../../context'
12
12
  import { PatternLines, PatternCircles, PatternWaves } from '@visx/pattern'
13
+ import { GlyphStar, GlyphTriangle, GlyphDiamond, GlyphSquare, GlyphCircle } from '@visx/glyph'
14
+ import { Group } from '@visx/group'
13
15
  import './index.scss'
14
16
 
15
- const Legend = () => {
17
+ const Legend = forwardRef((props, ref) => {
16
18
  // prettier-ignore
17
19
  const {
18
20
  displayDataAsText,
@@ -93,8 +95,16 @@ const Legend = () => {
93
95
  onClick={() => {
94
96
  toggleLegendActive(idx, legendLabel)
95
97
  }}
98
+ onKeyDown={e => {
99
+ if (e.key === 'Enter') {
100
+ e.preventDefault()
101
+ toggleLegendActive(idx, legendLabel)
102
+ }
103
+ }}
104
+ role='button'
105
+ tabIndex={0}
96
106
  >
97
- <LegendCircle fill={entry.color} /> <span className='label'>{legendLabel}</span>
107
+ <LegendCircle fill={entry.color} /> <span>{legendLabel}</span>
98
108
  </li>
99
109
  )
100
110
  })
@@ -114,7 +124,7 @@ const Legend = () => {
114
124
 
115
125
  legendItems.push(
116
126
  <>
117
- <li className={`legend-container__li legend-container__li--geo-pattern`}>
127
+ <li className={`legend-container__li legend-container__li--geo-pattern`} aria-label='You are on a pattern button. We dont support toggling patterns on this legend at the moment, but provide the area as being focusable for congruity.' tabIndex={0}>
118
128
  <span className='legend-item' style={{ border: 'unset' }}>
119
129
  <svg width={legendSize} height={legendSize}>
120
130
  {pattern === 'waves' && <PatternWaves id={`${dataKey}--${patternDataIndex}`} height={sizes[size] ?? 10} width={sizes[size] ?? 10} fill={defaultPatternColor} />}
@@ -136,21 +146,33 @@ const Legend = () => {
136
146
  const { legendClasses } = useDataVizClasses(state, viewport)
137
147
 
138
148
  const handleReset = e => {
139
- e.preventDefault()
149
+ const legend = ref.current
150
+ if (e) {
151
+ e.preventDefault()
152
+ }
140
153
  resetLegendToggles()
141
154
  setAccessibleStatus('Legend has been reset, please reference the data table to see updated values.')
155
+ if (legend) {
156
+ legend.focus()
157
+ }
158
+ }
159
+
160
+ const pin = <path className='marker' 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' strokeWidth={2} stroke={'black'} transform={`scale(0.5)`} />
161
+
162
+ const cityStyleShapes = {
163
+ pin: pin,
164
+ circle: <GlyphCircle color='#000' size={150} />,
165
+ square: <GlyphSquare color='#000' size={150} />,
166
+ diamond: <GlyphDiamond color='#000' size={150} />,
167
+ star: <GlyphStar color='#000' size={150} />,
168
+ triangle: <GlyphTriangle color='#000' size={150} />
142
169
  }
143
170
 
144
171
  return (
145
172
  <ErrorBoundary component='Sidebar'>
146
173
  <div className='legends'>
147
- <aside id='legend' className={legendClasses.aside.join(' ') || ''} role='region' aria-label='Legend'>
174
+ <aside id='legend' className={legendClasses.aside.join(' ') || ''} role='region' aria-label='Legend' tabIndex={0} ref={ref}>
148
175
  <section className={legendClasses.section.join(' ') || ''} aria-label='Map Legend'>
149
- {runtimeLegend.disabledAmt > 0 && (
150
- <button onClick={handleReset} className={legendClasses.resetButton.join(' ') || ''}>
151
- Clear
152
- </button>
153
- )}
154
176
  {legend.title && <span className={legendClasses.title.join(' ') || ''}>{parse(legend.title)}</span>}
155
177
  {legend.dynamicDescription === false && legend.description && <p className={legendClasses.description.join(' ') || ''}>{parse(legend.description)}</p>}
156
178
  {legend.dynamicDescription === true &&
@@ -172,12 +194,44 @@ const Legend = () => {
172
194
  <ul className={legendClasses.ul.join(' ') || ''} aria-label='Legend items'>
173
195
  {legendList()}
174
196
  </ul>
197
+ {(state.visual.additionalCityStyles.some(c => c.label) || state.visual.cityStyleLabel) && (
198
+ <>
199
+ <hr />
200
+ <div className={legendClasses.div.join(' ') || ''}>
201
+ {state.visual.cityStyleLabel && (
202
+ <div>
203
+ <svg>
204
+ <Group top={state.visual.cityStyle === 'pin' ? 19 : state.visual.cityStyle === 'triangle' ? 13 : 11} left={10}>
205
+ {cityStyleShapes[state.visual.cityStyle.toLowerCase()]}
206
+ </Group>
207
+ </svg>
208
+ <p>{state.visual.cityStyleLabel}</p>
209
+ </div>
210
+ )}
211
+
212
+ {state.visual.additionalCityStyles.map(
213
+ ({ shape, label }) =>
214
+ label && (
215
+ <div>
216
+ <svg>
217
+ <Group top={shape === 'Pin' ? 19 : shape === 'Triangle' ? 13 : 11} left={10}>
218
+ {cityStyleShapes[shape.toLowerCase()]}
219
+ </Group>
220
+ </svg>
221
+ <p>{label}</p>
222
+ </div>
223
+ )
224
+ )}
225
+ </div>
226
+ </>
227
+ )}
228
+ {runtimeLegend.disabledAmt > 0 && <Button onClick={handleReset}>Reset</Button>}
175
229
  </section>
176
230
  </aside>
177
231
  {state.hexMap.shapeGroups?.length > 0 && state.hexMap.type === 'shapes' && state.general.displayAsHex && <LegendItemHex state={state} runtimeLegend={runtimeLegend} viewport={viewport} />}
178
232
  </div>
179
233
  </ErrorBoundary>
180
234
  )
181
- }
235
+ })
182
236
 
183
237
  export default Legend
@@ -89,17 +89,14 @@
89
89
  }
90
90
  .legend-container__reset-button {
91
91
  font-size: 0.75em;
92
- color: rgba(0, 0, 0, 0.6);
93
- position: absolute;
94
92
  right: 1em;
95
- background: rgba(0, 0, 0, 0.1);
96
93
  text-transform: uppercase;
97
94
  transition: 0.2s all;
98
95
  padding: 0.2em 0.5em;
99
96
  border: rgba(0, 0, 0, 0.2) 1px solid;
97
+ padding: 0.375rem;
100
98
  &:hover {
101
99
  text-decoration: none;
102
- background: rgba(0, 0, 0, 0.15);
103
100
  transition: 0.2s all;
104
101
  }
105
102
  }
@@ -118,7 +115,7 @@
118
115
  flex-direction: column;
119
116
  }
120
117
 
121
- &:not(.vertical-sorted, .legend-container__ul--single-column) {
118
+ &:not(.vertical-sorted, .legend-container__ul--single-column, .single-row) {
122
119
  width: 100%;
123
120
  @include breakpoint(sm) {
124
121
  .legend-container__li {
@@ -231,5 +228,34 @@
231
228
  }
232
229
  }
233
230
  }
231
+ & .shape-container {
232
+ &.single-row {
233
+ display: flex;
234
+ flex-direction: row;
235
+ align-items: center;
236
+ justify-content: flex-start;
237
+ flex-wrap: wrap;
238
+
239
+ & > div {
240
+ margin-right: 2em;
241
+ }
242
+ & > div:last-child {
243
+ margin-right: 0;
244
+ }
245
+ }
246
+
247
+ & > div {
248
+ display: flex;
249
+ flex-direction: row;
250
+ }
251
+ & div > svg {
252
+ width: 25px;
253
+ height: 32px;
254
+ }
255
+ & div > p {
256
+ white-space: nowrap;
257
+ margin-top: 1px;
258
+ }
259
+ }
234
260
  }
235
261
  }
@@ -0,0 +1,41 @@
1
+ import { AiOutlineArrowUp, AiOutlineArrowDown, AiOutlineArrowRight, AiOutlineArrowLeft } from 'react-icons/ai'
2
+ import { Group } from '@visx/group'
3
+
4
+ type HexIconProps = {
5
+ item: {
6
+ operator: '=' | '≠' | '<' | '>' | '<=' | '>='
7
+ shape: 'Arrow Up' | 'Arrow Down' | 'Arrow Right' | 'Arrow Left'
8
+ key: string
9
+ value: string
10
+ }
11
+ index: number
12
+ centroid: [number, number]
13
+ iconSize: number
14
+ textColor: string
15
+ isTerritory: boolean
16
+ }
17
+
18
+ const HexIcon: React.FC<HexIconProps> = props => {
19
+ const { item, index, centroid, iconSize, textColor, isTerritory } = props
20
+ if (!centroid) return
21
+
22
+ if (isTerritory) {
23
+ return (
24
+ <Group style={{ transform: `translate(36%, 50%)`, fill: 'currentColor' }} key={`territory-hex--${index}`}>
25
+ {item.shape === 'Arrow Down' && <AiOutlineArrowDown size={12} stroke='none' fontWeight={100} />}
26
+ {item.shape === 'Arrow Up' && <AiOutlineArrowUp size={12} stroke='none' fontWeight={100} />}
27
+ {item.shape === 'Arrow Right' && <AiOutlineArrowRight size={12} stroke='none' fontWeight={100} />}
28
+ {item.shape === 'Arrow Left' && <AiOutlineArrowLeft size={12} stroke='none' fontWeight={100} />}
29
+ </Group>
30
+ )
31
+ }
32
+ return (
33
+ <Group top={centroid[1] - 5} left={centroid[0] - iconSize} color={textColor} textAnchor='start' key={`hex--${item.key}-${item.value}-${index}`}>
34
+ {item.shape === 'Arrow Down' && <AiOutlineArrowDown />}
35
+ {item.shape === 'Arrow Up' && <AiOutlineArrowUp />}
36
+ {item.shape === 'Arrow Right' && <AiOutlineArrowRight />}
37
+ {item.shape === 'Arrow Left' && <AiOutlineArrowLeft />}
38
+ </Group>
39
+ )
40
+ }
41
+ export default HexIcon
@@ -2,10 +2,9 @@ import { useContext } from 'react'
2
2
  import { geoCentroid, geoPath } from 'd3-geo'
3
3
  import ConfigContext from './../../../../context'
4
4
  import { MapContext } from './../../../../types/MapContext'
5
- import { AiOutlineArrowUp, AiOutlineArrowDown, AiOutlineArrowRight } from 'react-icons/ai'
6
- import { Group } from '@visx/group'
5
+ import HexIcon from '../HexIcon'
7
6
  import { Text } from '@visx/text'
8
- import chroma from 'chroma-js'
7
+ import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
9
8
 
10
9
  const offsets = {
11
10
  'US-VT': [50, -8],
@@ -52,16 +51,41 @@ const TerritoryHexagon = ({ label, text, stroke, strokeWidth, textColor, territo
52
51
  <>
53
52
  {state.hexMap.shapeGroups.map((group, groupIndex) => {
54
53
  return group.items.map((item, itemIndex) => {
55
- if (item.operator === '=') {
56
- if (geoData[item.key] === item.value || Number(geoData[item.key]) === Number(item.value)) {
57
- return (
58
- <Group style={{ transform: `translate(36%, 50%)`, fill: 'currentColor' }} key={`territory-hex--${itemIndex}`}>
59
- {item.shape === 'Arrow Down' && <AiOutlineArrowDown size={12} stroke='none' fontWeight={100} />}
60
- {item.shape === 'Arrow Up' && <AiOutlineArrowUp size={12} stroke='none' fontWeight={100} />}
61
- {item.shape === 'Arrow Right' && <AiOutlineArrowRight size={12} stroke='none' fontWeight={100} />}
62
- </Group>
63
- )
64
- }
54
+ switch (item.operator) {
55
+ case '=':
56
+ if (geoData[item.key] === item.value || Number(geoData[item.key]) === Number(item.value)) {
57
+ return <HexIcon item={item} index={itemIndex} centroid={centroid} isTerritory />
58
+ }
59
+ break
60
+ case '':
61
+ if (geoData[item.key] !== item.value && Number(geoData[item.key]) !== Number(item.value)) {
62
+ return <HexIcon item={item} index={itemIndex} centroid={centroid} isTerritory />
63
+ }
64
+ break
65
+ case '<':
66
+ if (Number(geoData[item.key]) < Number(item.value)) {
67
+ return <HexIcon item={item} index={itemIndex} centroid={centroid} isTerritory />
68
+ }
69
+ break
70
+ case '>':
71
+ if (Number(geoData[item.key]) > Number(item.value)) {
72
+ return <HexIcon item={item} index={itemIndex} centroid={centroid} isTerritory />
73
+ }
74
+ break
75
+ case '<=':
76
+ if (Number(geoData[item.key]) <= Number(item.value)) {
77
+ return <HexIcon item={item} index={itemIndex} centroid={centroid} isTerritory />
78
+ }
79
+ break
80
+ case '>=':
81
+ if (item.operator === '>=') {
82
+ if (Number(geoData[item.key]) >= Number(item.value)) {
83
+ return <HexIcon item={item} index={itemIndex} centroid={centroid} isTerritory />
84
+ }
85
+ }
86
+ break
87
+ default:
88
+ break
65
89
  }
66
90
  })
67
91
  })}
@@ -71,12 +95,7 @@ const TerritoryHexagon = ({ label, text, stroke, strokeWidth, textColor, territo
71
95
 
72
96
  if (undefined === abbr) return null
73
97
 
74
- let textColor = '#FFF'
75
-
76
- // Dynamic text color
77
- if (chroma.contrast(textColor, bgColor) < 3.5) {
78
- textColor = '#202020' // dark gray
79
- }
98
+ let textColor = getContrastColor('#FFF', bgColor)
80
99
 
81
100
  // always make HI black since it is off to the side
82
101
  if (abbr === 'US-HI') {