@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.
Files changed (38) hide show
  1. package/dist/cdcmap.js +38987 -34368
  2. package/examples/annotation/index.json +552 -0
  3. package/examples/annotation/usa-map.json +900 -0
  4. package/index.html +4 -3
  5. package/package.json +6 -5
  6. package/src/CdcMap.tsx +54 -82
  7. package/src/_stories/CdcMap.stories.tsx +7 -0
  8. package/src/_stories/_mock/DEV-7286.json +165 -0
  9. package/src/components/Annotation/Annotation.Draggable.styles.css +18 -0
  10. package/src/components/Annotation/Annotation.Draggable.tsx +152 -0
  11. package/src/components/Annotation/AnnotationDropdown.styles.css +14 -0
  12. package/src/components/Annotation/AnnotationDropdown.tsx +69 -0
  13. package/src/components/Annotation/AnnotationList.styles.css +45 -0
  14. package/src/components/Annotation/AnnotationList.tsx +42 -0
  15. package/src/components/Annotation/index.tsx +11 -0
  16. package/src/components/EditorPanel/components/EditorPanel.tsx +3 -2
  17. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +358 -0
  18. package/src/components/EditorPanel/components/{Panel.PatternSettings.tsx → Panels/Panel.PatternSettings.tsx} +2 -2
  19. package/src/components/EditorPanel/components/{Panels.tsx → Panels/index.tsx} +3 -0
  20. package/src/components/Legend/components/Legend.tsx +3 -9
  21. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +4 -3
  22. package/src/components/UsaMap/components/UsaMap.County.tsx +48 -21
  23. package/src/components/UsaMap/components/UsaMap.Region.tsx +2 -0
  24. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +2 -0
  25. package/src/components/UsaMap/components/UsaMap.State.tsx +28 -19
  26. package/src/data/initial-state.js +4 -1
  27. package/src/helpers/generateColorsArray.ts +13 -0
  28. package/src/helpers/generateRuntimeLegendHash.ts +23 -0
  29. package/src/helpers/getUniqueValues.ts +19 -0
  30. package/src/helpers/hashObj.ts +25 -0
  31. package/src/helpers/tests/generateColorsArray.test.ts +18 -0
  32. package/src/helpers/tests/generateRuntimeLegendHash.test.ts +11 -0
  33. package/src/helpers/tests/hashObj.test.ts +10 -0
  34. package/src/scss/map.scss +6 -3
  35. package/src/types/MapContext.ts +2 -0
  36. package/LICENSE +0 -201
  37. package/src/test/CdcMap.test.jsx +0 -19
  38. /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,14 @@
1
+ .cdc-open-viz-module {
2
+ .annotation__dropdown-list {
3
+ border: 1px solid red;
4
+ list-style: none;
5
+ }
6
+
7
+ .annotation-dropdown__panel {
8
+ padding: 10px;
9
+ }
10
+
11
+ .data-table-heading.annotation__dropdown-list {
12
+ margin-top: 10px;
13
+ }
14
+ }
@@ -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.tsx'
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} state={state} convertStateToConfig={convertStateToConfig} />
3060
+ <AdvancedEditor loadConfig={loadConfig} config={state} convertStateToConfig={convertStateToConfig} />
3060
3061
  </Layout.Sidebar>
3061
3062
  </ErrorBoundary>
3062
3063
  )