@cdc/map 4.24.5 → 4.24.9

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 +71853 -64936
  2. package/examples/annotation/index.json +552 -0
  3. package/examples/annotation/usa-map.json +900 -0
  4. package/examples/county-year.csv +10 -0
  5. package/examples/default-geocode.json +44 -10
  6. package/examples/default-patterns.json +0 -2
  7. package/examples/default-single-state.json +279 -108
  8. package/examples/map-issue-3.json +646 -0
  9. package/examples/single-state-filter.json +153 -0
  10. package/index.html +10 -6
  11. package/package.json +6 -5
  12. package/src/CdcMap.tsx +367 -199
  13. package/src/_stories/CdcMap.stories.tsx +14 -0
  14. package/src/_stories/_mock/DEV-7286.json +165 -0
  15. package/src/_stories/_mock/DEV-8942.json +270 -0
  16. package/src/components/Annotation/Annotation.Draggable.styles.css +18 -0
  17. package/src/components/Annotation/Annotation.Draggable.tsx +152 -0
  18. package/src/components/Annotation/AnnotationDropdown.styles.css +14 -0
  19. package/src/components/Annotation/AnnotationDropdown.tsx +70 -0
  20. package/src/components/Annotation/AnnotationList.styles.css +45 -0
  21. package/src/components/Annotation/AnnotationList.tsx +42 -0
  22. package/src/components/Annotation/index.tsx +11 -0
  23. package/src/components/{BubbleList.jsx → BubbleList.tsx} +1 -1
  24. package/src/components/{CityList.jsx → CityList.tsx} +28 -2
  25. package/src/components/{DataTable.jsx → DataTable.tsx} +2 -2
  26. package/src/components/EditorPanel/components/EditorPanel.tsx +650 -129
  27. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +336 -0
  28. package/src/components/EditorPanel/components/{Panel.PatternSettings.tsx → Panels/Panel.PatternSettings.tsx} +63 -13
  29. package/src/components/EditorPanel/components/{Panels.tsx → Panels/index.tsx} +3 -0
  30. package/src/components/Legend/components/Legend.tsx +125 -42
  31. package/src/components/Legend/components/index.scss +42 -42
  32. package/src/components/Modal.tsx +25 -0
  33. package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +74 -0
  34. package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +29 -0
  35. package/src/components/UsaMap/components/SingleState/index.tsx +9 -0
  36. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +4 -3
  37. package/src/components/UsaMap/components/UsaMap.County.tsx +114 -36
  38. package/src/components/UsaMap/components/UsaMap.Region.tsx +2 -0
  39. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +175 -206
  40. package/src/components/UsaMap/components/UsaMap.State.tsx +188 -44
  41. package/src/components/UsaMap/data/us-extended-geography.json +1 -0
  42. package/src/components/UsaMap/helpers/map.ts +111 -0
  43. package/src/components/WorldMap/WorldMap.tsx +17 -32
  44. package/src/components/ZoomControls.tsx +41 -0
  45. package/src/data/initial-state.js +11 -2
  46. package/src/data/supported-geos.js +15 -4
  47. package/src/helpers/generateColorsArray.ts +13 -0
  48. package/src/helpers/generateRuntimeLegendHash.ts +23 -0
  49. package/src/helpers/getUniqueValues.ts +19 -0
  50. package/src/helpers/hashObj.ts +25 -0
  51. package/src/helpers/tests/generateColorsArray.test.ts +18 -0
  52. package/src/helpers/tests/generateRuntimeLegendHash.test.ts +11 -0
  53. package/src/helpers/tests/hashObj.test.ts +10 -0
  54. package/src/hooks/useStateZoom.tsx +157 -0
  55. package/src/hooks/{useZoomPan.js → useZoomPan.ts} +6 -5
  56. package/src/scss/editor-panel.scss +0 -4
  57. package/src/scss/main.scss +23 -1
  58. package/src/scss/map.scss +14 -3
  59. package/src/types/MapConfig.ts +9 -1
  60. package/src/types/MapContext.ts +16 -2
  61. package/LICENSE +0 -201
  62. package/src/components/Modal.jsx +0 -22
  63. package/src/test/CdcMap.test.jsx +0 -19
  64. /package/src/components/EditorPanel/components/{Panel.PatternSettings-style.css → Panels/Panel.PatternSettings-style.css} +0 -0
  65. /package/src/components/{Geo.jsx → Geo.tsx} +0 -0
  66. /package/src/components/{NavigationMenu.jsx → NavigationMenu.tsx} +0 -0
  67. /package/src/components/{ZoomableGroup.jsx → ZoomableGroup.tsx} +0 -0
@@ -0,0 +1,336 @@
1
+ import React, { useContext } from 'react'
2
+
3
+ // CDC Core
4
+ import { approvedCurveTypes } from '@cdc/core/helpers/lineChartHelpers'
5
+ import Accordion from '@cdc/core/components/ui/Accordion'
6
+ import Button from '@cdc/core/components/elements/Button'
7
+ import { MapContext } from '../../../../types/MapContext'
8
+ import ConfigContext from '../../../../context'
9
+
10
+ // types
11
+ // styles
12
+
13
+ const PanelAnnotate: React.FC = props => {
14
+ const { state: config, setState: updateConfig, dimensions, isDraggingAnnotation } = useContext<MapContext>(ConfigContext)
15
+ const getColumns = (filter = true) => {
16
+ const columns = {}
17
+ config.data.forEach(row => {
18
+ Object.keys(row).forEach(columnName => (columns[columnName] = true))
19
+ })
20
+
21
+ if (filter) {
22
+ Object.keys(columns).forEach(key => {
23
+ if ((config.series && config.series.filter(series => series.dataKey === key).length > 0) || (config.confidenceKeys && Object.keys(config.confidenceKeys).includes(key))) {
24
+ delete columns[key]
25
+ }
26
+ })
27
+ }
28
+
29
+ return Object.keys(columns)
30
+ }
31
+
32
+ const handleAnnotationUpdate = (value, property, index) => {
33
+ const annotations = [...config?.annotations]
34
+ annotations[index][property] = value
35
+ annotations[index].savedDimensions = [dimensions[0] * 0.73, dimensions[1]]
36
+
37
+ updateConfig({
38
+ ...config,
39
+ annotations
40
+ })
41
+ }
42
+
43
+ const handleAddAnnotation = () => {
44
+ const svgContainer = document.querySelector('.map-container > section > svg, .map-container > section > canvas')?.getBoundingClientRect()
45
+ const newSvgDims = [svgContainer.width, svgContainer.height]
46
+
47
+ const newAnnotation = {
48
+ text: 'New Annotation',
49
+ snapToNearestPoint: false,
50
+ fontSize: 16,
51
+ show: {
52
+ desktop: true,
53
+ tablet: true,
54
+ mobile: true
55
+ },
56
+ markerType: 'arrow',
57
+ connectorType: 'line',
58
+ colors: {
59
+ label: 'black',
60
+ connector: 'black',
61
+ marker: 'black'
62
+ },
63
+ selected: true,
64
+ anchor: {
65
+ vertical: false,
66
+ horizontal: false
67
+ },
68
+ connectionType: 'line',
69
+ marker: 'arrow',
70
+ edit: {
71
+ subject: true,
72
+ label: true
73
+ },
74
+ seriesKey: '',
75
+ x: Number(newSvgDims?.[0]) / 2,
76
+ y: Number(newSvgDims?.[1] / 2),
77
+ xKey: null,
78
+ yKey: null,
79
+ dx: 0,
80
+ dy: 0,
81
+ opacity: '100',
82
+ savedDimensions: [dimensions[0] * 0.73, dimensions[1]]
83
+ }
84
+
85
+ const annotations = Array.isArray(config.annotations) ? config.annotations : []
86
+
87
+ updateConfig({
88
+ ...config,
89
+ annotations: [...annotations, newAnnotation]
90
+ })
91
+ }
92
+
93
+ const handleRemoveAnnotation = (annotationIndex: number) => {
94
+ const updated = config.annotations.filter((_, index) => index !== annotationIndex)
95
+ updateConfig({
96
+ ...config,
97
+ annotations: updated
98
+ })
99
+ }
100
+
101
+ return (
102
+ <Accordion>
103
+ <Accordion.Section title={props.name}>
104
+ <label>
105
+ Show Annotation Dropdown
106
+ <input
107
+ type='checkbox'
108
+ checked={config?.general?.showAnnotationDropdown}
109
+ onClick={e => {
110
+ updateConfig({
111
+ ...config,
112
+ general: {
113
+ ...config.general,
114
+ showAnnotationDropdown: e.target.checked
115
+ }
116
+ })
117
+ }}
118
+ />
119
+ </label>
120
+
121
+ <label>
122
+ Annotation Dropdown Title:
123
+ <input
124
+ type='text'
125
+ style={{ marginBottom: '10px' }}
126
+ value={config?.general?.annotationDropdownText}
127
+ onChange={e => {
128
+ updateConfig({
129
+ ...config,
130
+ general: {
131
+ ...config.general,
132
+ annotationDropdownText: e.target.value
133
+ }
134
+ })
135
+ }}
136
+ />
137
+ </label>
138
+
139
+ {config?.annotations &&
140
+ config?.annotations.map((annotation, index) => (
141
+ <Accordion>
142
+ <Accordion.Section title={annotation.text ? annotation.text.substring(0, 15) + '...' : `Annotation ${index + 1}`}>
143
+ <div className='annotation-group'>
144
+ <label>
145
+ Annotation Text:
146
+ <textarea rows={5} value={annotation.text} onChange={e => handleAnnotationUpdate(e.target.value, 'text', index)} />
147
+ </label>
148
+ {/* <label>
149
+ Vertical Anchor
150
+ <input
151
+ type='checkbox'
152
+ checked={config?.annotations[index].anchor.vertical}
153
+ onClick={e => {
154
+ const updatedAnnotations = [...config?.annotations]
155
+ updatedAnnotations[index].anchor.vertical = e.target.checked
156
+ updateConfig({
157
+ ...config,
158
+ annotations: updatedAnnotations
159
+ })
160
+ }}
161
+ />
162
+ </label>
163
+ <label>
164
+ Horizontal Anchor
165
+ <input
166
+ type='checkbox'
167
+ checked={config?.annotations[index].anchor.horizontal}
168
+ onClick={e => {
169
+ const updatedAnnotations = [...config?.annotations]
170
+ updatedAnnotations[index].anchor.horizontal = e.target.checked
171
+ updateConfig({
172
+ ...config,
173
+ annotations: updatedAnnotations
174
+ })
175
+ }}
176
+ />
177
+ </label> */}
178
+
179
+ <label>
180
+ Opacity
181
+ <br />
182
+ <input
183
+ type='range'
184
+ onChange={e => {
185
+ const updatedAnnotations = [...config?.annotations]
186
+ updatedAnnotations[index].opacity = e.target.value
187
+ updateConfig({
188
+ ...config,
189
+ annotations: updatedAnnotations
190
+ })
191
+ }}
192
+ value={config?.annotations?.[index]?.opacity || '100'}
193
+ />
194
+ </label>
195
+
196
+ <label>
197
+ Edit Subject
198
+ <input
199
+ type='checkbox'
200
+ checked={config?.annotations[index]?.edit?.subject}
201
+ onClick={e => {
202
+ const updatedAnnotations = [...config?.annotations]
203
+ updatedAnnotations[index].edit.subject = e.target.checked
204
+ updateConfig({
205
+ ...config,
206
+ annotations: updatedAnnotations
207
+ })
208
+ }}
209
+ />
210
+ </label>
211
+ <label>
212
+ Edit Label
213
+ <input
214
+ type='checkbox'
215
+ checked={config?.annotations[index]?.edit?.label}
216
+ onClick={e => {
217
+ const updatedAnnotations = [...config?.annotations]
218
+ updatedAnnotations[index].edit.label = e.target.checked
219
+ updateConfig({
220
+ ...config,
221
+ annotations: updatedAnnotations
222
+ })
223
+ }}
224
+ />
225
+ </label>
226
+
227
+ <label>
228
+ Connection Type:
229
+ <select
230
+ onChange={e => {
231
+ const updatedAnnotations = [...config?.annotations]
232
+ updatedAnnotations[index].connectionType = e.target.value
233
+ updateConfig({
234
+ ...config,
235
+ annotations: updatedAnnotations
236
+ })
237
+ }}
238
+ >
239
+ {['curve', 'line', 'elbow', 'none'].map((side, index) => (
240
+ <option key={side} value={side}>
241
+ {side}
242
+ </option>
243
+ ))}
244
+ </select>
245
+ </label>
246
+
247
+ {annotation.connectionType === 'curve' && (
248
+ <label>
249
+ Line Type:
250
+ <select
251
+ onChange={e => {
252
+ const updatedAnnotations = [...config?.annotations]
253
+ updatedAnnotations[index].lineType = e.target.value
254
+ updateConfig({
255
+ ...config,
256
+ annotations: updatedAnnotations
257
+ })
258
+ }}
259
+ >
260
+ {Object.entries(approvedCurveTypes).map(([value, key]) => (
261
+ <option key={key} value={key}>
262
+ {value}
263
+ </option>
264
+ ))}
265
+ </select>
266
+ </label>
267
+ )}
268
+
269
+ {/* <label>
270
+ Connection Location:
271
+ <select
272
+ onChange={e => {
273
+ const updatedAnnotations = [...config?.annotations]
274
+ updatedAnnotations[index].connectionLocation = e.target.value
275
+ updateConfig({
276
+ ...config,
277
+ annotations: updatedAnnotations
278
+ })
279
+ }}
280
+ >
281
+ {['auto', 'left', 'top', 'bottom', 'right'].map((side, index) => (
282
+ <option key={side} value={side}>
283
+ {side}
284
+ </option>
285
+ ))}
286
+ </select>
287
+ </label> */}
288
+
289
+ <label>
290
+ Marker
291
+ <select
292
+ onChange={e => {
293
+ const updatedAnnotations = [...config?.annotations]
294
+ updatedAnnotations[index].marker = e.target.value
295
+ updateConfig({
296
+ ...config,
297
+ annotations: updatedAnnotations
298
+ })
299
+ }}
300
+ >
301
+ {['circle', 'arrow'].map((column, columnIndex) => {
302
+ return <option>{column}</option>
303
+ })}
304
+ </select>
305
+ </label>
306
+
307
+ {/* <label>
308
+ Snap to Nearest Point
309
+ <input
310
+ type='checkbox'
311
+ checked={config?.annotations[index]?.snapToNearestPoint}
312
+ onClick={e => {
313
+ const updatedAnnotations = [...config?.annotations]
314
+ updatedAnnotations[index].snapToNearestPoint = e.target.checked
315
+ updateConfig({
316
+ ...config,
317
+ annotations: updatedAnnotations
318
+ })
319
+ }}
320
+ />
321
+ </label> */}
322
+
323
+ <Button className='warn btn-warn btn btn-remove delete' onClick={() => handleRemoveAnnotation(index)}>
324
+ Delete Annotation
325
+ </Button>
326
+ </div>
327
+ </Accordion.Section>
328
+ </Accordion>
329
+ ))}
330
+ {config?.annotations?.length < 3 && <Button onClick={handleAddAnnotation}>Add Annotation</Button>}
331
+ </Accordion.Section>
332
+ </Accordion>
333
+ )
334
+ }
335
+
336
+ export default PanelAnnotate
@@ -1,18 +1,24 @@
1
1
  import { useContext } from 'react'
2
2
  import { Accordion, AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
3
- import ConfigContext from '../../../context'
4
- import { type MapContext } from '../../../types/MapContext'
3
+ import ConfigContext from '../../../../context'
4
+ import { type MapContext } from '../../../../types/MapContext'
5
5
  import Button from '@cdc/core/components/elements/Button'
6
6
  import Tooltip from '@cdc/core/components/ui/Tooltip'
7
7
  import Icon from '@cdc/core/components/ui/Icon'
8
8
  import './Panel.PatternSettings-style.css'
9
+ import Alert from '@cdc/core/components/Alert'
10
+
11
+ // topojson helpers for checking color contrasts
12
+ import { feature } from 'topojson-client'
13
+ import { checkColorContrast, getContrastColor, getColorContrast } from '@cdc/core/helpers/cove/accessibility'
14
+ import topoJSON from '../../../UsaMap/data/us-topo.json'
9
15
 
10
16
  type PanelProps = {
11
17
  name: string
12
18
  }
13
19
 
14
20
  const PatternSettings = ({ name }: PanelProps) => {
15
- const { state, setState } = useContext<MapContext>(ConfigContext)
21
+ const { state, setState, applyLegendToRow, runtimeData } = useContext<MapContext>(ConfigContext)
16
22
  const defaultPattern = 'circles'
17
23
  const patternTypes = ['circles', 'waves', 'lines']
18
24
 
@@ -24,7 +30,7 @@ const PatternSettings = ({ name }: PanelProps) => {
24
30
  /** Updates the map config with a new pattern item */
25
31
  const handleAddGeoPattern = () => {
26
32
  let patterns = [...state.map.patterns]
27
- patterns.push({ dataKey: '', pattern: defaultPattern })
33
+ patterns.push({ dataKey: '', pattern: defaultPattern, contrastCheck: true })
28
34
  setState({
29
35
  ...state,
30
36
  map: {
@@ -36,16 +42,56 @@ const PatternSettings = ({ name }: PanelProps) => {
36
42
 
37
43
  /** Updates the map pattern at a given index */
38
44
  const handleUpdateGeoPattern = (value: string, index: number, keyToUpdate: 'dataKey' | 'pattern' | 'dataValue' | 'size' | 'label' | 'color') => {
45
+ const { features: unitedStates } = feature(topoJSON, topoJSON.objects.states)
39
46
  const updatedPatterns = [...state.map.patterns]
47
+
48
+ // Update the specific pattern with the new value
40
49
  updatedPatterns[index] = { ...updatedPatterns[index], [keyToUpdate]: value }
41
50
 
42
- setState({
43
- ...state,
51
+ // Iterate over each state feature
52
+ unitedStates.forEach(geo => {
53
+ const geoKey = geo.properties.iso
54
+ if (!geoKey || !runtimeData) return
55
+
56
+ const legendColors = runtimeData[geoKey] ? applyLegendToRow(runtimeData[geoKey]) : undefined
57
+ const geoData = runtimeData[geoKey]
58
+ if (!geoData) return
59
+
60
+ // Iterate over each pattern
61
+ state.map.patterns.forEach((patternData, patternIndex) => {
62
+ const hasMatchingValues = patternData.dataValue === geoData[patternData.dataKey]
63
+ if (!hasMatchingValues) return
64
+
65
+ const currentFill = legendColors[0]
66
+ const patternColor = keyToUpdate === 'color' && value !== '' ? value : getContrastColor('#000', currentFill)
67
+ const contrastCheck = checkColorContrast(currentFill, patternColor)
68
+
69
+ // Log a warning if the contrast check fails
70
+ if (!contrastCheck) {
71
+ console.warn(`COVE: pattern contrast check failed on ${geoData?.[state.columns.geo.name]} for ${patternData.dataKey} with:
72
+ pattern color: ${patternColor}
73
+ contrast: ${getColorContrast(currentFill, patternColor)}
74
+ `)
75
+ }
76
+
77
+ updatedPatterns[index] = { ...updatedPatterns[index], [keyToUpdate]: value, contrastCheck }
78
+ })
79
+ })
80
+
81
+ const editorErrorMessage = updatedPatterns.some(pattern => pattern.contrastCheck === false) ? 'One or more patterns do not pass the WCAG 2.1 contrast ratio of 3:1.' : ''
82
+
83
+ // Update the state with the new patterns and error message
84
+ setState(prevState => ({
85
+ ...prevState,
44
86
  map: {
45
- ...state.map,
87
+ ...prevState.map,
46
88
  patterns: updatedPatterns
89
+ },
90
+ runtime: {
91
+ ...prevState.runtime,
92
+ editorErrorMessage
47
93
  }
48
- })
94
+ }))
49
95
  }
50
96
 
51
97
  const handleRemovePattern = index => {
@@ -60,12 +106,19 @@ const PatternSettings = ({ name }: PanelProps) => {
60
106
  })
61
107
  }
62
108
 
109
+ const checkPatternContrasts = () => {
110
+ return state.map.patterns.every(pattern => pattern.contrastCheck !== false)
111
+ }
112
+
63
113
  return (
64
114
  <AccordionItem>
65
115
  <AccordionItemHeading>
66
116
  <AccordionItemButton>{name}</AccordionItemButton>
67
117
  </AccordionItemHeading>
68
118
  <AccordionItemPanel>
119
+ {patterns.length > 0 && <Alert type={checkPatternContrasts() ? 'success' : 'danger'} message='Pattern colors must comply with <br /> <a href="https://www.w3.org/TR/WCAG21/">WCAG 2.1</a> 3:1 contrast ratio.' />}
120
+ <br />
121
+
69
122
  {patterns &&
70
123
  patterns.map((pattern, patternIndex) => {
71
124
  const dataValueOptions = [...new Set(data?.map(d => d?.[pattern?.dataKey]))]
@@ -77,13 +130,14 @@ const PatternSettings = ({ name }: PanelProps) => {
77
130
  dataKeyOptions.sort()
78
131
 
79
132
  return (
80
- <Accordion allowZeroExpanded>
133
+ <Accordion allowZeroExpanded key={`accordion-pattern--${patternIndex}`}>
81
134
  <AccordionItem>
82
135
  <AccordionItemHeading>
83
136
  <AccordionItemButton>{pattern.dataKey ? `${pattern.dataKey}: ${pattern.dataValue ?? 'No Value'}` : 'Select Column'}</AccordionItemButton>
84
137
  </AccordionItemHeading>
85
138
  <AccordionItemPanel>
86
139
  <>
140
+ {pattern.contrastCheck ?? true ? <Alert type='success' message='This pattern passes contrast checks' /> : <Alert type='danger' message='Error: <a href="https://webaim.org/resources/contrastchecker/" target="_blank"> Review Color Contrast</a>' />}{' '}
87
141
  <label htmlFor={`pattern-dataKey--${patternIndex}`}>Data Key:</label>
88
142
  <select id={`pattern-dataKey--${patternIndex}`} value={pattern.dataKey !== '' ? pattern.dataKey : 'Select'} onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'dataKey')}>
89
143
  {/* TODO: sort these? */}
@@ -95,17 +149,14 @@ const PatternSettings = ({ name }: PanelProps) => {
95
149
  )
96
150
  })}
97
151
  </select>
98
-
99
152
  <label htmlFor={`pattern-dataValue--${patternIndex}`}>
100
153
  Data Value:
101
154
  <input type='text' onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'dataValue')} id={`pattern-dataValue--${patternIndex}`} value={pattern.dataValue === '' ? '' : pattern.dataValue} />
102
155
  </label>
103
-
104
156
  <label htmlFor={`pattern-label--${patternIndex}`}>
105
157
  Label (optional):
106
158
  <input type='text' onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'label')} id={`pattern-dataValue--${patternIndex}`} value={pattern.label === '' ? '' : pattern.label} />
107
159
  </label>
108
-
109
160
  <label htmlFor={`pattern-type--${patternIndex}`}>Pattern Type:</label>
110
161
  <select id={`pattern-type--${patternIndex}`} value={pattern?.pattern} onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'pattern')}>
111
162
  {patternTypes.map((patternName, index) => (
@@ -114,7 +165,6 @@ const PatternSettings = ({ name }: PanelProps) => {
114
165
  </option>
115
166
  ))}
116
167
  </select>
117
-
118
168
  <label htmlFor={`pattern-size--${patternIndex}`}>Pattern Size:</label>
119
169
  <select id={`pattern-size--${patternIndex}`} value={pattern?.size} onChange={e => handleUpdateGeoPattern(e.target.value, patternIndex, 'size')}>
120
170
  {['small', 'medium', 'large'].map((size, index) => (
@@ -1,6 +1,9 @@
1
+ import React from 'react'
2
+ import Annotate from './Panel.Annotate'
1
3
  import PatternSettings from './Panel.PatternSettings'
2
4
 
3
5
  const Panels = {
6
+ Annotate,
4
7
  PatternSettings
5
8
  }
6
9