@cdc/map 4.24.5 → 4.24.7
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.
- package/dist/cdcmap.js +38987 -34368
- package/examples/annotation/index.json +552 -0
- package/examples/annotation/usa-map.json +900 -0
- package/index.html +4 -3
- package/package.json +6 -5
- package/src/CdcMap.tsx +54 -82
- package/src/_stories/CdcMap.stories.tsx +7 -0
- package/src/_stories/_mock/DEV-7286.json +165 -0
- package/src/components/Annotation/Annotation.Draggable.styles.css +18 -0
- package/src/components/Annotation/Annotation.Draggable.tsx +152 -0
- package/src/components/Annotation/AnnotationDropdown.styles.css +14 -0
- package/src/components/Annotation/AnnotationDropdown.tsx +69 -0
- package/src/components/Annotation/AnnotationList.styles.css +45 -0
- package/src/components/Annotation/AnnotationList.tsx +42 -0
- package/src/components/Annotation/index.tsx +11 -0
- package/src/components/EditorPanel/components/EditorPanel.tsx +3 -2
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +358 -0
- package/src/components/EditorPanel/components/{Panel.PatternSettings.tsx → Panels/Panel.PatternSettings.tsx} +2 -2
- package/src/components/EditorPanel/components/{Panels.tsx → Panels/index.tsx} +3 -0
- package/src/components/Legend/components/Legend.tsx +3 -9
- package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +4 -3
- package/src/components/UsaMap/components/UsaMap.County.tsx +48 -21
- package/src/components/UsaMap/components/UsaMap.Region.tsx +2 -0
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +2 -0
- package/src/components/UsaMap/components/UsaMap.State.tsx +28 -19
- package/src/data/initial-state.js +4 -1
- package/src/helpers/generateColorsArray.ts +13 -0
- package/src/helpers/generateRuntimeLegendHash.ts +23 -0
- package/src/helpers/getUniqueValues.ts +19 -0
- package/src/helpers/hashObj.ts +25 -0
- package/src/helpers/tests/generateColorsArray.test.ts +18 -0
- package/src/helpers/tests/generateRuntimeLegendHash.test.ts +11 -0
- package/src/helpers/tests/hashObj.test.ts +10 -0
- package/src/scss/map.scss +6 -3
- package/src/types/MapContext.ts +2 -0
- package/LICENSE +0 -201
- package/src/test/CdcMap.test.jsx +0 -19
- /package/src/components/EditorPanel/components/{Panel.PatternSettings-style.css → Panels/Panel.PatternSettings-style.css} +0 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { useContext, useState, useEffect, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
// helpers
|
|
4
|
+
import { applyBandScaleOffset, handleConnectionHorizontalType, handleConnectionVerticalType, createPoints } from '@cdc/chart/src/components/Annotations/components/helpers'
|
|
5
|
+
|
|
6
|
+
// visx
|
|
7
|
+
import { HtmlLabel, CircleSubject, LineSubject, EditableAnnotation, Connector, Annotation as VisxAnnotation } from '@visx/annotation'
|
|
8
|
+
import { Drag, raise } from '@visx/drag'
|
|
9
|
+
import { MarkerArrow } from '@visx/marker'
|
|
10
|
+
import { LinePath } from '@visx/shape'
|
|
11
|
+
import * as allCurves from '@visx/curve'
|
|
12
|
+
import { Annotation } from '@cdc/core/types/Annotation'
|
|
13
|
+
|
|
14
|
+
// styles
|
|
15
|
+
import './Annotation.Draggable.styles.css'
|
|
16
|
+
import ConfigContext from '../../context'
|
|
17
|
+
import { MapContext } from '../../types/MapContext'
|
|
18
|
+
|
|
19
|
+
const Annotations = ({ xScale, yScale, xMax, svgRef, onDragStateChange }) => {
|
|
20
|
+
const [draggingItems, setDraggingItems] = useState([])
|
|
21
|
+
const { state: config, dimensions, setState: updateConfig, isEditor, isDraggingAnnotation } = useContext<MapContext>(ConfigContext)
|
|
22
|
+
const [width, height] = dimensions
|
|
23
|
+
const { annotations } = config
|
|
24
|
+
// const { colorScale } = useColorScale()
|
|
25
|
+
const prevDimensions = useRef(dimensions)
|
|
26
|
+
const AnnotationComponent = isEditor ? EditableAnnotation : VisxAnnotation
|
|
27
|
+
|
|
28
|
+
const handleMobileXPosition = annotation => {
|
|
29
|
+
if (annotation.snapToNearestPoint) {
|
|
30
|
+
return Number(annotation.dx) + xScale(annotation.xKey) + (config.xAxis.type !== 'date-time' ? xScale.bandwidth() / 2 : 0) + Number(config.yAxis.size)
|
|
31
|
+
}
|
|
32
|
+
return Number(annotation.x) + Number(annotation.dx)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const handleMobileYPosition = annotation => {
|
|
36
|
+
if (annotation.snapToNearestPoint) {
|
|
37
|
+
return yScale(annotation.yKey) + Number(annotation.dy)
|
|
38
|
+
}
|
|
39
|
+
return Number(annotation.dy) + Number(annotation.y)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const handleTextX = annotation => {
|
|
43
|
+
if (annotation.snapToNearestPoint) {
|
|
44
|
+
return Number(annotation.dx) + Number(xScale(annotation.xKey)) + (config.xAxis.type !== 'date-time' ? xScale.bandwidth() / 2 : 0) + Number(config.yAxis.size) - 16 / 3
|
|
45
|
+
}
|
|
46
|
+
return Number(annotation.dx) + Number(annotation.x) - 16 / 3
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const handleTextY = annotation => {
|
|
50
|
+
if (annotation.snapToNearestPoint) {
|
|
51
|
+
return yScale(annotation.yKey) + Number(annotation.dy) + 5
|
|
52
|
+
}
|
|
53
|
+
return Number(annotation.y) + Number(annotation.dy) + 16 / 3
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
annotations &&
|
|
58
|
+
annotations.map((annotation, index) => {
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
<Drag
|
|
62
|
+
key={`annotation--${index}`}
|
|
63
|
+
width={width}
|
|
64
|
+
height={height}
|
|
65
|
+
x={annotation.x} // subject x
|
|
66
|
+
y={annotation.y} // subject y
|
|
67
|
+
onDragStart={() => {
|
|
68
|
+
setDraggingItems(raise(annotations, index))
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
{({ dragStart, dragEnd, dragMove, isDragging, x, y, dx, dy }) => {
|
|
72
|
+
return (
|
|
73
|
+
<>
|
|
74
|
+
<AnnotationComponent
|
|
75
|
+
dx={annotation.dx} // label position
|
|
76
|
+
dy={annotation.dy} // label postion
|
|
77
|
+
x={annotation.x}
|
|
78
|
+
y={annotation.y}
|
|
79
|
+
canEditLabel={annotation.edit.label || false}
|
|
80
|
+
canEditSubject={annotation.edit.subject || false}
|
|
81
|
+
labelDragHandleProps={{ r: 15, stroke: isDraggingAnnotation ? 'red' : 'var(--primary)' }}
|
|
82
|
+
subjectDragHandleProps={{ r: 15, stroke: isDraggingAnnotation ? 'red' : 'var(--primary)' }}
|
|
83
|
+
onDragEnd={props => {
|
|
84
|
+
onDragStateChange(false)
|
|
85
|
+
const updatedAnnotations = annotations.map((annotation, idx) => {
|
|
86
|
+
if (idx === index) {
|
|
87
|
+
const nearestDatum = annotation
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
...annotation,
|
|
91
|
+
x: props.x,
|
|
92
|
+
y: props.y,
|
|
93
|
+
dx: props.dx,
|
|
94
|
+
dy: props.dy
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return annotation
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
updateConfig({
|
|
101
|
+
...config,
|
|
102
|
+
annotations: updatedAnnotations
|
|
103
|
+
})
|
|
104
|
+
}}
|
|
105
|
+
onMouseMove={dragMove}
|
|
106
|
+
onMouseUp={dragEnd}
|
|
107
|
+
onMouseDown={dragStart}
|
|
108
|
+
onTouchStart={dragStart}
|
|
109
|
+
onTouchMove={dragMove}
|
|
110
|
+
onTouchEnd={dragEnd}
|
|
111
|
+
anchorPosition={'auto'}
|
|
112
|
+
onDragStart={() => {
|
|
113
|
+
onDragStateChange(true)
|
|
114
|
+
}}
|
|
115
|
+
>
|
|
116
|
+
<HtmlLabel className='' showAnchorLine={false}>
|
|
117
|
+
<div
|
|
118
|
+
style={{
|
|
119
|
+
padding: '10px',
|
|
120
|
+
borderRadius: 5, // Optional: set border radius
|
|
121
|
+
backgroundColor: `rgba(255, 255, 255, ${annotation?.opacity ? Number(annotation?.opacity) / 100 : 1})`
|
|
122
|
+
}}
|
|
123
|
+
role='presentation'
|
|
124
|
+
// ! IMPORTANT: Workaround for 508
|
|
125
|
+
// - HTML needs to be set from the editor and we need a wrapper with the tabIndex
|
|
126
|
+
// - TabIndex is only supposed to be used on interactive elements. This is a workaround.
|
|
127
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
128
|
+
tabIndex={0}
|
|
129
|
+
aria-label={`Annotation text that reads: ${annotation.text}`}
|
|
130
|
+
dangerouslySetInnerHTML={{ __html: annotation.text }}
|
|
131
|
+
/>
|
|
132
|
+
</HtmlLabel>
|
|
133
|
+
|
|
134
|
+
{annotation.connectionType === 'line' && <Connector type='line' pathProps={{ markerStart: 'url(#marker-start)' }} />}
|
|
135
|
+
|
|
136
|
+
{annotation.connectionType === 'elbow' && <Connector type='elbow' pathProps={{ markerStart: 'url(#marker-start)' }} />}
|
|
137
|
+
|
|
138
|
+
{/* MARKERS */}
|
|
139
|
+
{annotation.marker === 'circle' && <CircleSubject className='circle-subject' stroke={'black'} radius={8} />}
|
|
140
|
+
{annotation.marker === 'arrow' && <MarkerArrow fill='black' id='marker-start' x={annotation.x} y={annotation.dy} stroke='#333' markerWidth={10} size={10} strokeWidth={1} orient='auto-start-reverse' />}
|
|
141
|
+
</AnnotationComponent>
|
|
142
|
+
</>
|
|
143
|
+
)
|
|
144
|
+
}}
|
|
145
|
+
</Drag>
|
|
146
|
+
</>
|
|
147
|
+
)
|
|
148
|
+
})
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export default Annotations
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React, { useContext, useState } from 'react'
|
|
2
|
+
import ConfigContext from '../../context'
|
|
3
|
+
import './AnnotationDropdown.styles.css'
|
|
4
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
5
|
+
import { fontSizes } from '@cdc/core/helpers/cove/fontSettings'
|
|
6
|
+
import AnnotationList from './AnnotationList'
|
|
7
|
+
|
|
8
|
+
const AnnotationDropdown = () => {
|
|
9
|
+
const { currentViewport: viewport, state: config } = useContext(ConfigContext)
|
|
10
|
+
const [expanded, setExpanded] = useState(false)
|
|
11
|
+
|
|
12
|
+
const titleFontSize = ['sm', 'xs', 'xxs'].includes(viewport) ? '13px' : `${fontSizes[config?.fontSize]}px`
|
|
13
|
+
|
|
14
|
+
const annotations = config?.annotations || []
|
|
15
|
+
|
|
16
|
+
const limitHeight = {
|
|
17
|
+
maxHeight: config.table.limitHeight && `${config.table.height}px`,
|
|
18
|
+
OverflowY: 'scroll'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const handleAccordionClassName = () => {
|
|
22
|
+
const classNames = ['data-table-heading', 'annotation__dropdown-list']
|
|
23
|
+
if (!expanded) {
|
|
24
|
+
classNames.push('collapsed')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return classNames.join(' ')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const handleSectionClasses = () => {
|
|
31
|
+
const classes = [`data-table-container`, viewport, `d-block`, `d-lg-none`]
|
|
32
|
+
|
|
33
|
+
if (config.general.showAnnotationDropdown) {
|
|
34
|
+
classes.push('d-lg-block')
|
|
35
|
+
}
|
|
36
|
+
return classes.join(' ')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<>
|
|
41
|
+
<section className={handleSectionClasses()}>
|
|
42
|
+
<div
|
|
43
|
+
style={{ fontSize: titleFontSize }}
|
|
44
|
+
role='button'
|
|
45
|
+
className={handleAccordionClassName()}
|
|
46
|
+
onClick={() => {
|
|
47
|
+
setExpanded(!expanded)
|
|
48
|
+
}}
|
|
49
|
+
tabIndex={0}
|
|
50
|
+
onKeyDown={e => {
|
|
51
|
+
if (e.keyCode === 13) {
|
|
52
|
+
setExpanded(!expanded)
|
|
53
|
+
}
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
<Icon display={expanded ? 'minus' : 'plus'} base />
|
|
57
|
+
{config.general.annotationDropdownText === '' ? 'Annotations' : config?.general?.annotationDropdownText}
|
|
58
|
+
</div>
|
|
59
|
+
{expanded && (
|
|
60
|
+
<div className='table-container annotation-dropdown__panel' style={limitHeight}>
|
|
61
|
+
<AnnotationList useBootstrapVisibilityClasses={false} />
|
|
62
|
+
</div>
|
|
63
|
+
)}
|
|
64
|
+
</section>
|
|
65
|
+
</>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default AnnotationDropdown
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
.cdc-open-viz-module {
|
|
2
|
+
.annotation__title-circle {
|
|
3
|
+
display: flex;
|
|
4
|
+
justify-content: center;
|
|
5
|
+
align-items: center;
|
|
6
|
+
border-radius: 50%;
|
|
7
|
+
padding: 8px;
|
|
8
|
+
width: 16px;
|
|
9
|
+
height: 16px;
|
|
10
|
+
margin-right: 5px;
|
|
11
|
+
line-height: 1.5rem;
|
|
12
|
+
|
|
13
|
+
border: 2px solid #666;
|
|
14
|
+
text-align: center;
|
|
15
|
+
font-size: 14px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.annotation__title-wrapper {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-wrap: nowrap;
|
|
21
|
+
align-items: center;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.annotation__title-wrapper .annotation__title-text {
|
|
25
|
+
margin-left: 5px;
|
|
26
|
+
font-size: 16px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.annotation__subtext {
|
|
30
|
+
font-size: 12px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.annotation-list {
|
|
34
|
+
list-style: none;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.annotation-list li {
|
|
38
|
+
margin-top: 5px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.cove-component__content {
|
|
42
|
+
container-type: inline-size;
|
|
43
|
+
container-name: content;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
|
+
import ConfigContext from './../../context'
|
|
3
|
+
import './AnnotationList.styles.css'
|
|
4
|
+
import DOMPurify from 'dompurify'
|
|
5
|
+
|
|
6
|
+
type AnnotationListProps = {
|
|
7
|
+
useBootstrapVisibilityClasses?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const AnnotationList: React.FC<AnnotationListProps> = ({ useBootstrapVisibilityClasses = true }) => {
|
|
11
|
+
const { state: config } = useContext(ConfigContext)
|
|
12
|
+
const annotations = config.annotations || []
|
|
13
|
+
|
|
14
|
+
const ulClasses = () => {
|
|
15
|
+
const classes = ['annotation-list']
|
|
16
|
+
if (useBootstrapVisibilityClasses) {
|
|
17
|
+
classes.push('d-block', 'd-md-none')
|
|
18
|
+
}
|
|
19
|
+
return classes.join(' ')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const annotationListItems = annotations.map((annotation, annotationIndex) => {
|
|
23
|
+
const text = annotation.text || ''
|
|
24
|
+
|
|
25
|
+
// sanitize the text for setting dangerouslySetInnerHTML
|
|
26
|
+
const sanitizedData = () => ({
|
|
27
|
+
__html: DOMPurify.sanitize(text)
|
|
28
|
+
})
|
|
29
|
+
return (
|
|
30
|
+
<li key={`annotation-li-item__annotationIndex`}>
|
|
31
|
+
<div className='annotation__title-wrapper'>
|
|
32
|
+
<div className='annotation__title-circle'>{annotationIndex + 1}</div>
|
|
33
|
+
<p className='annotation__subtext' dangerouslySetInnerHTML={sanitizedData()} />
|
|
34
|
+
</div>
|
|
35
|
+
</li>
|
|
36
|
+
)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
return <ul className={ulClasses()}>{annotationListItems}</ul>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default AnnotationList
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import Draggable from './Annotation.Draggable'
|
|
2
|
+
import AnnotationDropdown from './AnnotationDropdown'
|
|
3
|
+
import AnnotationList from './AnnotationList'
|
|
4
|
+
|
|
5
|
+
const Annotation = {
|
|
6
|
+
Draggable,
|
|
7
|
+
Dropdown: AnnotationDropdown,
|
|
8
|
+
List: AnnotationList
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default Annotation
|
|
@@ -6,7 +6,7 @@ import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'
|
|
|
6
6
|
import { useDebounce } from 'use-debounce'
|
|
7
7
|
// import ReactTags from 'react-tag-autocomplete'
|
|
8
8
|
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
9
|
-
import Panels from './Panels
|
|
9
|
+
import Panels from './Panels'
|
|
10
10
|
import Layout from '@cdc/core/components/Layout'
|
|
11
11
|
|
|
12
12
|
// Data
|
|
@@ -3055,8 +3055,9 @@ const EditorPanel = ({ columnsRequiredChecker }) => {
|
|
|
3055
3055
|
</AccordionItemPanel>
|
|
3056
3056
|
</AccordionItem>
|
|
3057
3057
|
{state.general.geoType === 'us' && <Panels.PatternSettings name='Pattern Settings' />}
|
|
3058
|
+
{state.general.geoType !== 'us-county' && <Panels.Annotate name='Text Annotations' />}
|
|
3058
3059
|
</Accordion>
|
|
3059
|
-
<AdvancedEditor loadConfig={loadConfig}
|
|
3060
|
+
<AdvancedEditor loadConfig={loadConfig} config={state} convertStateToConfig={convertStateToConfig} />
|
|
3060
3061
|
</Layout.Sidebar>
|
|
3061
3062
|
</ErrorBoundary>
|
|
3062
3063
|
)
|