@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
@@ -6,13 +6,30 @@ import { Text } from '@visx/text'
6
6
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
7
7
  import useIntersectionObserver from '../hooks/useIntersectionObserver'
8
8
  import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
9
+ import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
9
10
 
10
11
  export default function DeviationBar({ height, xScale }) {
11
- const { transformedData: data, config, formatNumber, twoColorPalette, getTextWidth, updateConfig, parseDate, formatDate, currentViewport } = useContext(ConfigContext)
12
+ const {
13
+ transformedData: data,
14
+ config,
15
+ formatNumber,
16
+ twoColorPalette,
17
+ updateConfig,
18
+ parseDate,
19
+ formatDate,
20
+ currentViewport
21
+ } = useContext(ConfigContext)
12
22
  const { barStyle, tipRounding, roundingStyle, twoColor } = config
13
23
  const barRefs = useRef([])
14
24
  const [windowWidth, setWindowWidth] = useState(window.innerWidth)
15
- const radius = roundingStyle === 'standard' ? '8px' : roundingStyle === 'shallow' ? '5px' : roundingStyle === 'finger' ? '15px' : '0px'
25
+ const radius =
26
+ roundingStyle === 'standard'
27
+ ? '8px'
28
+ : roundingStyle === 'shallow'
29
+ ? '5px'
30
+ : roundingStyle === 'finger'
31
+ ? '15px'
32
+ : '0px'
16
33
  const fontSize = { small: 16, medium: 18, large: 20 }
17
34
  const isRounded = config.barStyle === 'rounded'
18
35
  const target = Number(config.xAxis.target)
@@ -148,7 +165,10 @@ export default function DeviationBar({ height, xScale }) {
148
165
  config.heights.horizontal = totalheight
149
166
 
150
167
  // text,labels postiions
151
- const textWidth = getTextWidth(formatNumber(barValue, 'left'), `normal ${fontSize[config.fontSize]}px sans-serif`)
168
+ const textWidth = getTextWidth(
169
+ formatNumber(barValue, 'left'),
170
+ `normal ${fontSize[config.fontSize]}px sans-serif`
171
+ )
152
172
  const textFits = textWidth < barWidth - 6
153
173
  const textX = barBaseX
154
174
  const textY = barY + barHeight / 2
@@ -167,7 +187,10 @@ export default function DeviationBar({ height, xScale }) {
167
187
  let textProps = getTextProps(config.isLollipopChart, textFits, lollipopShapeSize, fill)
168
188
  // tooltips
169
189
  const xAxisValue = formatNumber(barValue, 'left')
170
- const yAxisValue = config.runtime.yAxis.type === 'date' ? formatDate(parseDate(data[index][config.runtime.originalXAxis.dataKey])) : data[index][config.runtime.originalXAxis.dataKey]
190
+ const yAxisValue =
191
+ config.runtime.yAxis.type === 'date'
192
+ ? formatDate(parseDate(data[index][config.runtime.originalXAxis.dataKey]))
193
+ : data[index][config.runtime.originalXAxis.dataKey]
171
194
  let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${yAxisValue}` : yAxisValue
172
195
  let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${xAxisValue}` : xAxisValue
173
196
  const tooltip = `<div>
@@ -190,7 +213,15 @@ export default function DeviationBar({ height, xScale }) {
190
213
  data-tooltip-id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`}
191
214
  tabIndex={-1}
192
215
  >
193
- <div style={{ width: barWidth, height: barHeight, border: `${borderWidth}px solid #333`, backgroundColor: barColor[barPosition], ...borderRadius }}></div>
216
+ <div
217
+ style={{
218
+ width: barWidth,
219
+ height: barHeight,
220
+ border: `${borderWidth}px solid #333`,
221
+ backgroundColor: barColor[barPosition],
222
+ ...borderRadius
223
+ }}
224
+ ></div>
194
225
  </foreignObject>
195
226
  {config.yAxis.displayNumbersOnBar && (
196
227
  <Text verticalAnchor='middle' x={textX} y={textY} {...textProps[barPosition]}>
@@ -198,8 +229,25 @@ export default function DeviationBar({ height, xScale }) {
198
229
  </Text>
199
230
  )}
200
231
 
201
- {config.isLollipopChart && config.lollipopShape === 'circle' && <circle cx={circleX} cy={circleY} r={lollipopShapeSize / 2} fill={barColor[barPosition]} style={{ filter: 'unset', opacity: 1 }} />}
202
- {config.isLollipopChart && config.lollipopShape === 'square' && <rect x={squareX} y={squareY} width={lollipopShapeSize} height={lollipopShapeSize} fill={barColor[barPosition]} style={{ opacity: 1, filter: 'unset' }}></rect>}
232
+ {config.isLollipopChart && config.lollipopShape === 'circle' && (
233
+ <circle
234
+ cx={circleX}
235
+ cy={circleY}
236
+ r={lollipopShapeSize / 2}
237
+ fill={barColor[barPosition]}
238
+ style={{ filter: 'unset', opacity: 1 }}
239
+ />
240
+ )}
241
+ {config.isLollipopChart && config.lollipopShape === 'square' && (
242
+ <rect
243
+ x={squareX}
244
+ y={squareY}
245
+ width={lollipopShapeSize}
246
+ height={lollipopShapeSize}
247
+ fill={barColor[barPosition]}
248
+ style={{ opacity: 1, filter: 'unset' }}
249
+ ></rect>
250
+ )}
203
251
  </Group>
204
252
  )
205
253
  })}
@@ -209,7 +257,9 @@ export default function DeviationBar({ height, xScale }) {
209
257
  </Text>
210
258
  )}
211
259
 
212
- {shouldShowTargetLine && <Line from={{ x: targetX, y: 0 }} to={{ x: targetX, y: height }} stroke='#333' strokeWidth={2} />}
260
+ {shouldShowTargetLine && (
261
+ <Line from={{ x: targetX, y: 0 }} to={{ x: targetX, y: height }} stroke='#333' strokeWidth={2} />
262
+ )}
213
263
  </Group>
214
264
  <foreignObject y={height / 2} ref={targetRef}></foreignObject>
215
265
  </ErrorBoundary>
@@ -43,6 +43,7 @@ import { Anchor } from '@cdc/core/types/Axis'
43
43
  import EditorPanelContext from './EditorPanelContext'
44
44
  import _ from 'lodash'
45
45
  import { adjustedSymbols as symbolCodes } from '@cdc/core/helpers/footnoteSymbols'
46
+ import { updateFieldRankByValue } from './helpers/updateFieldRankByValue'
46
47
 
47
48
  interface PreliminaryProps {
48
49
  config: ChartConfig
@@ -76,7 +77,7 @@ const PreliminaryData: React.FC<PreliminaryProps> = ({ config, updateConfig, dat
76
77
  const getStyleOptions = type => {
77
78
  const options = Object.keys(lineCodes)
78
79
  if (type === 'suppression') {
79
- return options.slice(0, -1)
80
+ return options.slice(0, -2)
80
81
  } else {
81
82
  return options
82
83
  }
@@ -742,7 +743,7 @@ const EditorPanel = () => {
742
743
  updatedConfig.orientation = 'vertical'
743
744
  }
744
745
  if (isDateScale(updatedConfig.xAxis) && !updatedConfig.xAxis.padding) {
745
- updatedConfig.xAxis.padding = 6
746
+ updatedConfig.xAxis.padding = 0
746
747
  }
747
748
  // DEV-8008 - Remove Bar styling when Line is converted to Bar
748
749
  if (updatedConfig.visualizationType === 'Line') {
@@ -776,20 +777,6 @@ const EditorPanel = () => {
776
777
  return
777
778
  }
778
779
 
779
- if (section === 'boxplot' && subsection === 'labels') {
780
- updateConfig({
781
- ...config,
782
- [section]: {
783
- ...config[section],
784
- [subsection]: {
785
- ...config.boxplot[subsection],
786
- [fieldName]: newValue
787
- }
788
- }
789
- })
790
- return
791
- }
792
-
793
780
  const truthy = val => {
794
781
  if (val === 0) return true // indexes can be used as keys
795
782
  return !!val
@@ -890,13 +877,6 @@ const EditorPanel = () => {
890
877
  updateConfig({ ...config, series: newSeries }) // left axis series keys
891
878
  }
892
879
 
893
- const sortSeries = e => {
894
- const series = config.series[0].dataKey
895
- const sorted = data.sort((a, b) => a[series] - b[series])
896
- const newData = e === 'asc' ? sorted : sorted.reverse()
897
- updateConfig({ ...config }, newData)
898
- }
899
-
900
880
  const addNewExclusion = exclusionKey => {
901
881
  let newExclusion = [...config.exclusions.keys]
902
882
  newExclusion.push(exclusionKey)
@@ -1407,7 +1387,6 @@ const EditorPanel = () => {
1407
1387
  handleSeriesChange,
1408
1388
  handleAddNewHighlightedBar,
1409
1389
  setCategoryAxis,
1410
- sortSeries,
1411
1390
  updateField,
1412
1391
  warningMsg,
1413
1392
  highlightedBarValues,
@@ -1571,10 +1550,14 @@ const EditorPanel = () => {
1571
1550
  )}
1572
1551
  {visSupportsRankByValue() && config.series && config.series.length === 1 && (
1573
1552
  <Select
1574
- fieldName='visualizationType'
1553
+ value={config.rankByValue}
1554
+ fieldName='rankByValue'
1575
1555
  label='Rank by Value'
1576
1556
  initial='Select'
1577
- onChange={e => sortSeries(e.target.value)}
1557
+ updateField={(_section, _subsection, _fieldName, value) => {
1558
+ const [newConfig, newData] = updateFieldRankByValue(config, value, data)
1559
+ updateConfig(newConfig, newData)
1560
+ }}
1578
1561
  options={['asc', 'desc']}
1579
1562
  />
1580
1563
  )}
@@ -1803,6 +1786,17 @@ const EditorPanel = () => {
1803
1786
  updateField={updateField}
1804
1787
  />
1805
1788
  )}
1789
+ {visSupportsValueAxisGridLines() && (
1790
+ <CheckBox
1791
+ value={config.yAxis.labelsAboveGridlines}
1792
+ section='yAxis'
1793
+ fieldName='labelsAboveGridlines'
1794
+ label='Tick labels above gridlines'
1795
+ updateField={updateField}
1796
+ disabled={!config.yAxis.gridLines}
1797
+ title={!config.yAxis.gridLines ? 'Show gridlines to enable' : ''}
1798
+ />
1799
+ )}
1806
1800
  <CheckBox
1807
1801
  value={config.yAxis.enablePadding}
1808
1802
  section='yAxis'
@@ -2007,6 +2001,14 @@ const EditorPanel = () => {
2007
2001
  ) : (
2008
2002
  config.visualizationType !== 'Pie' && (
2009
2003
  <>
2004
+ <CheckBox
2005
+ display={!visHasCategoricalAxis()}
2006
+ value={config.dataFormat.onlyShowTopPrefixSuffix}
2007
+ section='dataFormat'
2008
+ fieldName='onlyShowTopPrefixSuffix'
2009
+ label='Only Show Top Prefix/Suffix'
2010
+ updateField={updateField}
2011
+ />
2010
2012
  <CheckBox
2011
2013
  display={!visHasCategoricalAxis()}
2012
2014
  value={config.yAxis.hideAxis}
@@ -2681,11 +2683,7 @@ const EditorPanel = () => {
2681
2683
  <>
2682
2684
  <p style={{ padding: '1.5em 0 0.5em', fontSize: '.9rem', lineHeight: '1rem' }}>
2683
2685
  Format how charts should parse and display your dates using{' '}
2684
- <a
2685
- href='https://github.com/d3/d3-time-format#locale_format'
2686
- target='_blank'
2687
- rel='noreferrer'
2688
- >
2686
+ <a href='https://d3js.org/d3-time-format#locale_format' target='_blank' rel='noreferrer'>
2689
2687
  these guidelines
2690
2688
  </a>
2691
2689
  .
@@ -2816,6 +2814,29 @@ const EditorPanel = () => {
2816
2814
  }
2817
2815
  updateField={updateField}
2818
2816
  />
2817
+ <CheckBox
2818
+ value={config.xAxis.showYearsOnce}
2819
+ section='xAxis'
2820
+ fieldName='showYearsOnce'
2821
+ label='Show years once'
2822
+ tooltip={
2823
+ <Tooltip style={{ textTransform: 'none' }}>
2824
+ <Tooltip.Target>
2825
+ <Icon
2826
+ display='question'
2827
+ style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }}
2828
+ />
2829
+ </Tooltip.Target>
2830
+ <Tooltip.Content>
2831
+ <p>
2832
+ When this option is checked and the date format for the axis includes years, each year
2833
+ will only be shown once in the axis.
2834
+ </p>
2835
+ </Tooltip.Content>
2836
+ </Tooltip>
2837
+ }
2838
+ updateField={updateField}
2839
+ />
2819
2840
  {visHasBrushChart() && (
2820
2841
  <CheckBox
2821
2842
  value={config.brush?.active}
@@ -3040,15 +3061,17 @@ const EditorPanel = () => {
3040
3061
  updateField={updateField}
3041
3062
  />
3042
3063
  )}
3043
- <TextField
3044
- value={config.xAxis.labelOffset}
3045
- section='xAxis'
3046
- fieldName='labelOffset'
3047
- label='Label offset'
3048
- type='number'
3049
- className='number-narrow'
3050
- updateField={updateField}
3051
- />
3064
+ {config.orientation === 'horizontal' && (
3065
+ <TextField
3066
+ value={config.xAxis.labelOffset}
3067
+ section='xAxis'
3068
+ fieldName='labelOffset'
3069
+ label='Label offset'
3070
+ type='number'
3071
+ className='number-narrow'
3072
+ updateField={updateField}
3073
+ />
3074
+ )}
3052
3075
 
3053
3076
  {/* Hiding this for now, not interested in moving the axis lines away from chart comp. right now. */}
3054
3077
  {/* <TextField value={config.xAxis.axisPadding} type='number' max={10} min={0} section='xAxis' fieldName='axisPadding' label={'Axis Padding'} className='number-narrow' updateField={updateField} /> */}
@@ -12,31 +12,11 @@ import { type PanelProps } from './../PanelProps'
12
12
  import './../panels.scss'
13
13
 
14
14
  const PanelAnnotate: React.FC<PanelProps> = props => {
15
- const { updateConfig, config, unfilteredData, dimensions, isDraggingAnnotation } = useContext(ConfigContext)
16
-
17
- const getColumns = (filter = true) => {
18
- const columns = {}
19
- unfilteredData.forEach(row => {
20
- Object.keys(row).forEach(columnName => (columns[columnName] = true))
21
- })
22
-
23
- if (filter) {
24
- Object.keys(columns).forEach(key => {
25
- if (
26
- (config.series && config.series.filter(series => series.dataKey === key).length > 0) ||
27
- (config.confidenceKeys && Object.keys(config.confidenceKeys).includes(key))
28
- ) {
29
- delete columns[key]
30
- }
31
- })
32
- }
33
-
34
- return Object.keys(columns)
35
- }
15
+ const { updateConfig, config, svgRef } = useContext(ConfigContext)
36
16
 
37
17
  const handleAnnotationUpdate = (value, property, index) => {
38
- const svgContainer = document.querySelector('.chart-container > div > svg')?.getBoundingClientRect()
39
- const newSvgDims = [svgContainer.width, svgContainer.height]
18
+ const svgContainer = document.querySelector('.chart-container > svg')?.getBoundingClientRect()
19
+ const newSvgDims = [svgContainer?.width, svgContainer?.height]
40
20
  const annotations = [...config?.annotations]
41
21
  annotations[index][property] = value
42
22
  annotations[index].savedDimensions = newSvgDims
@@ -48,8 +28,11 @@ const PanelAnnotate: React.FC<PanelProps> = props => {
48
28
  }
49
29
 
50
30
  const handleAddAnnotation = () => {
51
- const svgContainer = document.querySelector('.chart-container svg')?.getBoundingClientRect()
52
- const newSvgDims = [svgContainer.width, svgContainer.height]
31
+ // check if svg is animated svg or standard svg
32
+ const newSvgDims = [
33
+ svgRef?.current?.width?.baseVal?.value || svgRef?.current?.width,
34
+ svgRef?.current?.height?.baseVal?.value || svgRef?.current?.height
35
+ ]
53
36
 
54
37
  const newAnnotation = {
55
38
  text: 'New Annotation',
@@ -221,6 +221,26 @@ const PanelGeneral: FC<PanelProps> = props => {
221
221
  )}
222
222
  {visHasaAdditionalLabelsOnBars() && (
223
223
  <>
224
+ <CheckBox
225
+ tooltip={
226
+ <Tooltip style={{ textTransform: 'none' }}>
227
+ <Tooltip.Target>
228
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
229
+ </Tooltip.Target>
230
+ <Tooltip.Content>
231
+ <p>
232
+ Selecting this option will display a thin line slightly above the Date/Category Axis to indicate
233
+ "zero value" where zero values are indicated in the Data Series.
234
+ </p>
235
+ </Tooltip.Content>
236
+ </Tooltip>
237
+ }
238
+ value={config.general.showZeroValueData}
239
+ section='general'
240
+ fieldName='showZeroValueData'
241
+ label='Display "Zero Data" Label'
242
+ updateField={updateField}
243
+ />
224
244
  <CheckBox
225
245
  tooltip={
226
246
  <Tooltip style={{ textTransform: 'none' }}>
@@ -251,10 +271,7 @@ const PanelGeneral: FC<PanelProps> = props => {
251
271
  updateField={updateField}
252
272
  />
253
273
  <CheckBox
254
- display={
255
- config.visualizationSubType === 'stacked' &&
256
- (config.visualizationType === 'Bar' || config.visualizationType === 'Combo')
257
- }
274
+ display={config.visualizationType === 'Bar' || config.visualizationType === 'Combo'}
258
275
  tooltip={
259
276
  <Tooltip style={{ textTransform: 'none' }}>
260
277
  <Tooltip.Target>