@cdc/chart 4.24.9 → 4.24.10

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 (65) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cdcchart.js +43919 -40370
  3. package/index.html +1 -1
  4. package/package.json +2 -2
  5. package/src/CdcChart.tsx +129 -108
  6. package/src/_stories/Chart.Legend.Gradient.stories.tsx +33 -0
  7. package/src/_stories/Chart.stories.tsx +28 -0
  8. package/src/_stories/ChartAxisLabels.stories.tsx +20 -0
  9. package/src/_stories/ChartAxisTitles.stories.tsx +53 -0
  10. package/src/_stories/ChartPrefixSuffix.stories.tsx +151 -0
  11. package/src/_stories/_mock/horizontal_bar.json +257 -0
  12. package/src/_stories/_mock/large_x_axis_labels.json +261 -0
  13. package/src/_stories/_mock/paired-bar.json +262 -0
  14. package/src/_stories/_mock/pie_with_data.json +255 -0
  15. package/src/_stories/_mock/simplified_line.json +1510 -0
  16. package/src/components/Annotations/components/AnnotationDraggable.tsx +0 -3
  17. package/src/components/Annotations/components/AnnotationDropdown.tsx +1 -1
  18. package/src/components/Axis/Categorical.Axis.tsx +22 -4
  19. package/src/components/BarChart/components/BarChart.Horizontal.tsx +95 -16
  20. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +41 -17
  21. package/src/components/BarChart/components/BarChart.Vertical.tsx +78 -20
  22. package/src/components/BarChart/helpers/index.ts +23 -4
  23. package/src/components/BrushChart.tsx +3 -2
  24. package/src/components/DeviationBar.jsx +58 -8
  25. package/src/components/EditorPanel/EditorPanel.tsx +63 -40
  26. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +8 -25
  27. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +21 -4
  28. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +297 -35
  29. package/src/components/EditorPanel/components/panels.scss +4 -6
  30. package/src/components/EditorPanel/editor-panel.scss +0 -8
  31. package/src/components/EditorPanel/helpers/tests/updateFieldRankByValue.test.ts +38 -0
  32. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +42 -0
  33. package/src/components/EditorPanel/useEditorPermissions.ts +1 -0
  34. package/src/components/ForestPlot/ForestPlot.tsx +2 -3
  35. package/src/components/ForestPlot/ForestPlotProps.ts +2 -0
  36. package/src/components/Legend/Legend.Component.tsx +16 -16
  37. package/src/components/Legend/Legend.Suppression.tsx +25 -20
  38. package/src/components/Legend/Legend.tsx +0 -2
  39. package/src/components/Legend/helpers/index.ts +16 -19
  40. package/src/components/LegendWrapper.tsx +3 -1
  41. package/src/components/LineChart/components/LineChart.Circle.tsx +10 -0
  42. package/src/components/LinearChart.tsx +740 -562
  43. package/src/components/PairedBarChart.jsx +50 -10
  44. package/src/components/PieChart/PieChart.tsx +1 -6
  45. package/src/components/Regions/components/Regions.tsx +33 -19
  46. package/src/components/ZoomBrush.tsx +25 -6
  47. package/src/coreStyles_chart.scss +3 -0
  48. package/src/data/initial-state.js +6 -2
  49. package/src/helpers/configHelpers.ts +28 -0
  50. package/src/helpers/handleRankByValue.ts +15 -0
  51. package/src/helpers/sizeHelpers.ts +25 -0
  52. package/src/helpers/tests/handleRankByValue.test.ts +37 -0
  53. package/src/helpers/tests/sizeHelpers.test.ts +80 -0
  54. package/src/hooks/useColorPalette.js +10 -2
  55. package/src/hooks/useLegendClasses.ts +4 -0
  56. package/src/hooks/useScales.ts +31 -3
  57. package/src/hooks/useTooltip.tsx +9 -5
  58. package/src/index.jsx +1 -0
  59. package/src/scss/DataTable.scss +5 -4
  60. package/src/scss/main.scss +57 -52
  61. package/src/types/ChartConfig.ts +38 -16
  62. package/src/types/ChartContext.ts +18 -14
  63. package/src/_stories/Chart.Legend.Gradient.tsx +0 -19
  64. package/src/_stories/ChartBrush.stories.tsx +0 -19
  65. package/src/components/LinearChart.jsx +0 -817
@@ -11,10 +11,11 @@ import { Line } from '@visx/shape'
11
11
  import { Label } from '../../types/Label'
12
12
  import { ChartConfig } from '../../types/ChartConfig'
13
13
  import { ColorScale } from '../../types/ChartContext'
14
- import { forwardRef } from 'react'
14
+ import { forwardRef, useState } from 'react'
15
15
  import LegendSuppression from './Legend.Suppression'
16
16
  import LegendGradient from '@cdc/core/components/Legend/Legend.Gradient'
17
17
  import { DimensionsType } from '@cdc/core/types/Dimensions'
18
+ import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
18
19
 
19
20
  export interface LegendProps {
20
21
  colorScale: ColorScale
@@ -27,7 +28,6 @@ export interface LegendProps {
27
28
  seriesHighlight: string[]
28
29
  skipId: string
29
30
  dimensions: DimensionsType // for responsive width legend
30
- getTextWidth: (text: string, font: string) => string
31
31
  }
32
32
 
33
33
  /* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
@@ -42,24 +42,21 @@ const Legend: React.FC<LegendProps> = forwardRef(
42
42
  currentViewport,
43
43
  formatLabels,
44
44
  skipId = 'legend',
45
- dimensions,
46
- getTextWidth
45
+ dimensions
47
46
  },
48
47
  ref
49
48
  ) => {
50
49
  const { innerClasses, containerClasses } = useLegendClasses(config)
51
50
  const { runtime, legend } = config
52
51
 
52
+ const [hasSuppression, setHasSuppression] = useState(false)
53
+
53
54
  const isBottomOrSmallViewport =
54
- legend?.position === 'bottom' || (['sm', 'xs', 'xxs'].includes(currentViewport) && !legend.hide)
55
+ legend?.position === 'bottom' || (isLegendWrapViewport(currentViewport) && !legend.hide)
55
56
 
56
57
  const legendClasses = {
57
- marginBottom: getMarginBottom(isBottomOrSmallViewport, config),
58
-
59
- marginTop:
60
- isBottomOrSmallViewport && config.orientation === 'horizontal'
61
- ? `${config.yAxis.label && config.isResponsiveTicks ? config.dynamicMarginTop : config.runtime.xAxis.size}px`
62
- : getMarginTop(isBottomOrSmallViewport, config.brush.active, legend)
58
+ marginBottom: getMarginBottom(config, hasSuppression),
59
+ marginTop: getMarginTop(isBottomOrSmallViewport, config)
63
60
  }
64
61
 
65
62
  const { HighLightedBarUtils } = useHighlightedBars(config)
@@ -78,7 +75,6 @@ const Legend: React.FC<LegendProps> = forwardRef(
78
75
  {legend.label && <h3>{parse(legend.label)}</h3>}
79
76
  {legend.description && <p>{parse(legend.description)}</p>}
80
77
  <LegendGradient
81
- getTextWidth={getTextWidth}
82
78
  config={config}
83
79
  {...getGradientConfig(config, formatLabels, colorScale)}
84
80
  dimensions={dimensions}
@@ -133,9 +129,9 @@ const Legend: React.FC<LegendProps> = forwardRef(
133
129
  }}
134
130
  role='button'
135
131
  >
136
- <div>
132
+ <div className='d-flex justify-content-center align-items-center'>
137
133
  {config.visualizationType === 'Line' && config.legend.style === 'lines' ? (
138
- <svg width={40} height={20}>
134
+ <svg width={40} height={25}>
139
135
  <Line
140
136
  from={{ x: 10, y: 10 }}
141
137
  to={{ x: 40, y: 10 }}
@@ -145,7 +141,7 @@ const Legend: React.FC<LegendProps> = forwardRef(
145
141
  />
146
142
  </svg>
147
143
  ) : (
148
- <div style={{ display: 'flex', flexDirection: 'column' }}>
144
+ <div className='d-flex flex-column mt-1'>
149
145
  <LegendShape
150
146
  shape={config.legend.style === 'boxes' ? 'square' : 'circle'}
151
147
  viewport={currentViewport}
@@ -204,7 +200,11 @@ const Legend: React.FC<LegendProps> = forwardRef(
204
200
  })}
205
201
  </div>
206
202
 
207
- <LegendSuppression config={config} isBottomOrSmallViewport={isBottomOrSmallViewport} />
203
+ <LegendSuppression
204
+ config={config}
205
+ isBottomOrSmallViewport={isBottomOrSmallViewport}
206
+ setHasSuppression={setHasSuppression}
207
+ />
208
208
  </>
209
209
  )
210
210
  }}
@@ -7,11 +7,11 @@ interface LegendProps {
7
7
  isBottomOrSmallViewport: boolean
8
8
  }
9
9
 
10
- const LegendSuppression: React.FC<LegendProps> = ({ config, isBottomOrSmallViewport }) => {
10
+ const LegendSuppression: React.FC<LegendProps> = ({ config, isBottomOrSmallViewport, setHasSuppression }) => {
11
11
  const { preliminaryData, visualizationType, visualizationSubType, legend } = config
12
12
 
13
13
  const hasOpenCircleEffects = () =>
14
- preliminaryData?.some(pd => pd.label && pd.type === 'effect' && pd.style === 'Open Circles') &&
14
+ preliminaryData?.some(pd => pd.label && pd.type === 'effect' && pd.style !== 'Filled Circles') &&
15
15
  ['Line', 'Combo'].includes(visualizationType)
16
16
 
17
17
  const shouldShowSuppressedLabels = () =>
@@ -100,6 +100,13 @@ const LegendSuppression: React.FC<LegendProps> = ({ config, isBottomOrSmallViewp
100
100
  const getLegendContainerClass = () =>
101
101
  legend.singleRow && isBottomOrSmallViewport ? 'legend-container__inner bottom single-row' : ''
102
102
 
103
+ const shouldShowSuppressedInfo = () =>
104
+ !config.legend.hideSuppressionLink &&
105
+ config.visualizationSubType !== 'stacked' &&
106
+ preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value && (pd?.style || pd.symbol))
107
+
108
+ setHasSuppression(shouldShowSuppressedInfo())
109
+
103
110
  return (
104
111
  <React.Fragment>
105
112
  {hasOpenCircleEffects() && (
@@ -115,24 +122,22 @@ const LegendSuppression: React.FC<LegendProps> = ({ config, isBottomOrSmallViewp
115
122
  <div className={getLegendContainerClass()}>{renderSuppressedItems()}</div>
116
123
  </React.Fragment>
117
124
  )}
118
- {!config.legend.hideSuppressionLink &&
119
- config.visualizationSubType !== 'stacked' &&
120
- preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value && (pd?.style || pd.symbol)) && (
121
- <div className='legend-container__outer definition-link'>
122
- <Icon alt='info-icon' display='info' />
123
- <p>
124
- This chart contains
125
- <a // prettier-ignore
126
- onClick={handleLinkClick}
127
- data-tooltip-content='Data is suppressed to maintain statistical reliability. This occurs when the number of respondents or reported values does not meet the minimum reporting threshold.'
128
- data-tooltip-id='my-tooltip'
129
- href='no-router-link'
130
- >
131
- suppressed data
132
- </a>
133
- </p>
134
- </div>
135
- )}
125
+ {shouldShowSuppressedInfo() && (
126
+ <div className='legend-container__outer definition-link'>
127
+ <Icon alt='info-icon' display='info' />
128
+ <p>
129
+ This chart contains
130
+ <a // prettier-ignore
131
+ onClick={handleLinkClick}
132
+ data-tooltip-content='Data is suppressed to maintain statistical reliability. This occurs when the number of respondents or reported values does not meet the minimum reporting threshold.'
133
+ data-tooltip-id='my-tooltip'
134
+ href='no-router-link'
135
+ >
136
+ suppressed data
137
+ </a>
138
+ </p>
139
+ </div>
140
+ )}
136
141
 
137
142
  <ReactTooltip // prettier-ignore
138
143
  id='my-tooltip'
@@ -17,7 +17,6 @@ const Legend = forwardRef((props, ref) => {
17
17
  transformedData: data,
18
18
  currentViewport,
19
19
  dimensions,
20
- getTextWidth,
21
20
  } = useContext(ConfigContext)
22
21
  if (!config.legend) return null
23
22
  // create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default.
@@ -28,7 +27,6 @@ const Legend = forwardRef((props, ref) => {
28
27
  !['Box Plot'].includes(config.visualizationType) && (
29
28
  <Fragment>
30
29
  <LegendComponent
31
- getTextWidth={getTextWidth}
32
30
  dimensions={dimensions}
33
31
  ref={ref}
34
32
  skipId={props.skipId || 'legend'}
@@ -1,8 +1,3 @@
1
- export const getMarginTop = (isBottomOrSmallViewport, isBrushActive, legend) => {
2
- if (!isBottomOrSmallViewport) return '0px'
3
- if (isBrushActive && legend.position === 'bottom') return '35px'
4
- }
5
-
6
1
  export const getGradientConfig = (config, formatLabels, colorScale) => {
7
2
  const defaultValue = [{ datum: '', index: 0, text: '', value: '' }]
8
3
 
@@ -15,21 +10,23 @@ export const getGradientConfig = (config, formatLabels, colorScale) => {
15
10
  return { colors, labels }
16
11
  }
17
12
 
18
- export const getMarginBottom = (isBottomOrSmallViewport, config) => {
19
- const isSuppressedActive = config.preliminaryData.some(pd => pd.label) && !config.legend.hideSuppressionLink
20
-
21
- const isLegendTop = config.legend?.position === 'top' && !config.legend.hide
22
-
23
- let marginBottom = '0px'
24
- if (isLegendTop && !isSuppressedActive) {
25
- marginBottom = config.legend.hideBorder.topBottom ? '15px' : '25px'
26
- }
27
- if (isLegendTop && isSuppressedActive) {
28
- marginBottom = '75px'
13
+ export const getMarginTop = (isBottomOrSmallViewport, config) => {
14
+ if (!isBottomOrSmallViewport) {
15
+ return '0px'
29
16
  }
30
- if (isBottomOrSmallViewport && isSuppressedActive) {
31
- marginBottom = '45px'
17
+ if (isBottomOrSmallViewport && config.brush?.active) {
18
+ return '35px'
32
19
  }
20
+ return '20px'
21
+ }
22
+ export const getMarginBottom = (config, hasSuppression) => {
23
+ const isLegendTop = config.legend?.position === 'top' && !config.legend.hide
24
+
25
+ let marginBottom = 0
26
+
27
+ if (isLegendTop) marginBottom = config.legend.hideBorder.topBottom ? 15 : 25
28
+
29
+ if (hasSuppression) marginBottom += 40
33
30
 
34
- return marginBottom
31
+ return `${marginBottom}px`
35
32
  }
@@ -1,6 +1,8 @@
1
1
  import React, { useContext } from 'react'
2
2
  import ConfigContext from '../ConfigContext'
3
3
 
4
+ import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
5
+
4
6
  type LegendWrapperProps = {
5
7
  children: React.ReactNode
6
8
  }
@@ -13,7 +15,7 @@ const LegendWrapper: React.FC<LegendWrapperProps> = props => {
13
15
  const getLegendWrappingClasses = () => {
14
16
  let classes = ['legend-wrapper', 'd-flex', 'flex-nowrap', 'w-100']
15
17
  const { legend } = config
16
- if (legend.position === 'bottom' || legend.position === 'top' || ['xxs', 'xs', 'sm'].includes(currentViewport)) {
18
+ if (legend.position === 'bottom' || legend.position === 'top' || isLegendWrapViewport(currentViewport)) {
17
19
  classes = classes.filter(item => item !== 'flex-nowrap')
18
20
  classes.push('flex-wrap')
19
21
  }
@@ -147,10 +147,17 @@ const LineChartCircle = (props: LineChartCircleProps) => {
147
147
 
148
148
  if (mode === 'ISOLATED_POINTS') {
149
149
  const drawIsolatedPoints = (currentIndex, seriesKey) => {
150
+ let isMatch = false
150
151
  const currentPoint = data[currentIndex]
151
152
  const previousPoint = currentIndex > 0 ? data[currentIndex - 1] : null
152
153
  const nextPoint = currentIndex < data.length - 1 ? data[currentIndex + 1] : null
153
154
  let res = false
155
+ // check if isolated points has overlap with circle effect
156
+ circleData.forEach(item => {
157
+ if (item?.data[seriesKey] === currentPoint[seriesKey]) {
158
+ isMatch = true
159
+ }
160
+ })
154
161
 
155
162
  // Handle the first point in the array
156
163
  if (currentIndex === 0 && nextPoint && !nextPoint[seriesKey]) {
@@ -171,6 +178,9 @@ const LineChartCircle = (props: LineChartCircleProps) => {
171
178
  res = true
172
179
  }
173
180
  }
181
+ if (isMatch) {
182
+ res = false
183
+ }
174
184
 
175
185
  return res
176
186
  }