@cdc/chart 4.25.5-1 → 4.25.6-1

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 (43) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cdcchart.js +32053 -27935
  3. package/index.html +130 -130
  4. package/package.json +2 -2
  5. package/src/CdcChartComponent.tsx +66 -26
  6. package/src/_stories/Chart.stories.tsx +99 -93
  7. package/src/_stories/ChartPrefixSuffix.stories.tsx +29 -32
  8. package/src/_stories/_mock/pie_calculated_area.json +417 -0
  9. package/src/components/BarChart/components/BarChart.Horizontal.tsx +4 -13
  10. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +3 -14
  11. package/src/components/BarChart/components/BarChart.Vertical.tsx +2 -8
  12. package/src/components/Brush/BrushChart.tsx +73 -0
  13. package/src/components/Brush/BrushController..tsx +39 -0
  14. package/src/components/DeviationBar.jsx +0 -1
  15. package/src/components/EditorPanel/EditorPanel.tsx +246 -156
  16. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +2 -2
  17. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +3 -2
  18. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +2 -1
  19. package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +8 -0
  20. package/src/components/EditorPanel/useEditorPermissions.ts +7 -4
  21. package/src/components/HoverLine/HoverLine.tsx +74 -0
  22. package/src/components/Legend/Legend.Suppression.tsx +47 -3
  23. package/src/components/Legend/helpers/index.ts +1 -1
  24. package/src/components/LineChart/helpers.ts +7 -7
  25. package/src/components/LineChart/index.tsx +3 -6
  26. package/src/components/LinearChart.tsx +108 -72
  27. package/src/components/PieChart/PieChart.tsx +58 -13
  28. package/src/data/initial-state.js +8 -5
  29. package/src/helpers/countNumOfTicks.ts +4 -19
  30. package/src/helpers/getNewRuntime.ts +35 -0
  31. package/src/helpers/getPiePercent.ts +22 -0
  32. package/src/helpers/getTransformedData.ts +22 -0
  33. package/src/helpers/tests/getNewRuntime.test.ts +82 -0
  34. package/src/helpers/tests/getPiePercent.test.ts +38 -0
  35. package/src/hooks/useRightAxis.ts +1 -1
  36. package/src/hooks/useScales.ts +8 -3
  37. package/src/hooks/useTooltip.tsx +24 -10
  38. package/src/scss/main.scss +8 -4
  39. package/src/store/chart.actions.ts +2 -6
  40. package/src/store/chart.reducer.ts +23 -23
  41. package/src/types/ChartConfig.ts +7 -4
  42. package/src/types/ChartContext.ts +0 -2
  43. package/src/components/ZoomBrush.tsx +0 -251
@@ -435,9 +435,9 @@ const PanelGeneral: FC<PanelProps> = props => {
435
435
  {visSupportsFootnotes() && (
436
436
  <TextField
437
437
  type='textarea'
438
- value={config.footnotes}
438
+ value={config.legacyFootnotes}
439
439
  updateField={updateField}
440
- fieldName='footnotes'
440
+ fieldName='legacyFootnotes'
441
441
  label='Footnotes'
442
442
  tooltip={
443
443
  <Tooltip style={{ textTransform: 'none' }}>
@@ -627,6 +627,7 @@ const SeriesItem = props => {
627
627
  ['Bar', 'Line'].includes(config.visualizationType) &&
628
628
  config.visualizationSubType !== 'Stacked' &&
629
629
  !config.series.find(s => s.dynamicCategory && s.dataKey !== series.dataKey)
630
+ const SELECT = '- Select -'
630
631
  return (
631
632
  <Draggable key={series.dataKey} draggableId={`draggableFilter-${series.dataKey}`} index={i}>
632
633
  {(provided, snapshot) => (
@@ -661,9 +662,9 @@ const SeriesItem = props => {
661
662
  <Select
662
663
  label='Dynamic Category'
663
664
  value={series.dynamicCategory}
664
- options={['- Select - ', ...getColumns().filter(col => series.dataKey !== col)]}
665
+ options={[SELECT, ...getColumns().filter(col => series.dataKey !== col)]}
665
666
  updateField={(_section, _subsection, _fieldName, value) => {
666
- if (value === '- Select -') value = ''
667
+ if (value === SELECT) value = ''
667
668
  updateSeries(i, value, 'dynamicCategory')
668
669
  }}
669
670
  tooltip={
@@ -23,6 +23,7 @@ import { useEditorPanelContext } from '../../EditorPanelContext.js'
23
23
  import ConfigContext from '../../../../ConfigContext.js'
24
24
  import { PanelProps } from '../PanelProps'
25
25
  import { LineChartConfig } from '../../../../types/ChartConfig'
26
+ import './panelVisual.styles.css'
26
27
 
27
28
  const PanelVisual: FC<PanelProps> = props => {
28
29
  const { config, updateConfig, colorPalettes, twoColorPalette } = useContext<ChartContext>(ConfigContext)
@@ -45,7 +46,7 @@ const PanelVisual: FC<PanelProps> = props => {
45
46
  const { twoColorPalettes, sequential, nonSequential, accessibleColors } = useColorPalette(config, updateConfig)
46
47
 
47
48
  const updateColor = (property, _value) => {
48
- console.log('value', _value)
49
+ console.error('value', _value)
49
50
  if (property === 'storyNodeFontColor') {
50
51
  updateConfig({
51
52
  ...config,
@@ -0,0 +1,8 @@
1
+ .type-chart .sidebar .color-palette button:not(.selected) {
2
+ border: var(--cool-gray-30) 2px solid !important;
3
+
4
+ }
5
+
6
+ .type-chart .sidebar .color-palette button.selected {
7
+ border: black 2px solid !important;
8
+ }
@@ -197,7 +197,7 @@ export const useEditorPermissions = () => {
197
197
  }
198
198
 
199
199
  const visSupportsTooltipLines = () => {
200
- const enabledCharts = ['Combo', 'Forecasting', 'Area Chart', 'Line', 'Bar']
200
+ const enabledCharts = ['Combo', 'Forecasting', 'Area Chart', 'Line', 'Bar', 'Scatter Plot']
201
201
  if (enabledCharts.includes(visualizationType)) return true
202
202
  return false
203
203
  }
@@ -357,8 +357,11 @@ export const useEditorPermissions = () => {
357
357
  }
358
358
 
359
359
  const visSupportsReactTooltip = () => {
360
- if (config.yAxis.type === 'categorical') return true
361
- if (['Deviation Bar', 'Box Plot', 'Scatter Plot', 'Paired Bar'].includes(visualizationType)) {
360
+ if (config.yAxis.type === 'categorical' && config.tooltips.singleSeries) return true
361
+ if (
362
+ ['Deviation Bar', 'Box Plot', 'Scatter Plot', 'Paired Bar', 'Bar'].includes(visualizationType) &&
363
+ config.tooltips.singleSeries
364
+ ) {
362
365
  return true
363
366
  }
364
367
  }
@@ -381,7 +384,7 @@ export const useEditorPermissions = () => {
381
384
  }
382
385
 
383
386
  const visSupportsYPadding = () => {
384
- return !config.dataFormat.onlyShowTopPrefixSuffix || !config.dataFormat.suffix?.includes(' ')
387
+ return !config.yAxis.inlineLabel || !config.yAxis.inlineLabel?.includes(' ')
385
388
  }
386
389
 
387
390
  const visHasSingleSeriesTooltip = () => {
@@ -0,0 +1,74 @@
1
+ import React, { useContext } from 'react'
2
+ import ConfigContext from '../../ConfigContext'
3
+ import { Group } from '@visx/group'
4
+ import { Line } from '@visx/shape'
5
+
6
+ type HoverLineProps = {
7
+ tooltipData?: any // same as @visx/tooltip TooltipData
8
+ xMax?: number
9
+ yMax?: number
10
+ point: { x: number; y: number }
11
+ orientation: 'horizontal' | 'vertical'
12
+ }
13
+
14
+ const HoverLine: React.FC<HoverLineProps> = ({ tooltipData, xMax, yMax, point, orientation }) => {
15
+ const { config } = useContext(ConfigContext)
16
+ const { verticalHoverLine, horizontalHoverLine } = config.visual
17
+ const { visualizationType } = config
18
+ const isScatterPlot = visualizationType === 'Scatter Plot'
19
+
20
+ const isVertical = orientation === 'vertical'
21
+ const isHorizontal = orientation === 'horizontal'
22
+ const showVerticalHoverLine = verticalHoverLine || (verticalHoverLine && isScatterPlot)
23
+ const showHorizontalHoverLine = horizontalHoverLine || (horizontalHoverLine && isScatterPlot)
24
+
25
+ const getX = () => {
26
+ if (point.x > xMax + Number(config.yAxis.size)) return xMax
27
+ if (point.x < config.yAxis.size) return config.yAxis.size
28
+ return point.x
29
+ }
30
+
31
+ const getY = () => {
32
+ if (point.y > yMax) return yMax
33
+ return point.y
34
+ }
35
+ if (isVertical) {
36
+ return (
37
+ showVerticalHoverLine && (
38
+ <Group key={`tooltipLine-vertical${point.y}${point.x}`} className='vertical-tooltip-line'>
39
+ <Line
40
+ from={{ x: getX(), y: 0 }}
41
+ to={{ x: getX(), y: yMax }}
42
+ stroke={'black'}
43
+ strokeWidth={1}
44
+ pointerEvents='none'
45
+ strokeDasharray='5,5'
46
+ className='vertical-tooltip-line'
47
+ />
48
+ </Group>
49
+ )
50
+ )
51
+ }
52
+ if (isHorizontal) {
53
+ return (
54
+ showHorizontalHoverLine && (
55
+ <Group
56
+ key={`tooltipLine-horizontal${point.y}${point.x}`}
57
+ className='horizontal-tooltip-line'
58
+ left={config.yAxis.size ? config.yAxis.size : 0}
59
+ >
60
+ <Line
61
+ from={{ x: 0, y: getY() }}
62
+ to={{ x: xMax, y: getY() }}
63
+ stroke={'black'}
64
+ strokeWidth={1}
65
+ pointerEvents='none'
66
+ strokeDasharray='5,5'
67
+ className='horizontal-tooltip-line'
68
+ />
69
+ </Group>
70
+ )
71
+ )
72
+ }
73
+ }
74
+ export default HoverLine
@@ -1,4 +1,4 @@
1
- import React from 'react'
1
+ import React, { useMemo } from 'react'
2
2
  import { ChartConfig } from '../../types/ChartConfig'
3
3
  import RichTooltip from '@cdc/core/components/RichTooltip/RichTooltip'
4
4
  interface LegendProps {
@@ -7,8 +7,8 @@ interface LegendProps {
7
7
  }
8
8
 
9
9
  const LegendSuppression: React.FC<LegendProps> = ({ config, isLegendBottom }) => {
10
- const { preliminaryData, visualizationType, visualizationSubType, legend } = config
11
-
10
+ const { preliminaryData, visualizationType, visualizationSubType, legend, data } = config
11
+ const showPiePercent = config.dataFormat.showPiePercent && config.visualizationType === 'Pie'
12
12
  const hasOpenCircleEffects = () =>
13
13
  preliminaryData?.some(pd => pd.label && pd.type === 'effect' && pd.style !== 'Filled Circles') &&
14
14
  ['Line', 'Combo'].includes(visualizationType)
@@ -95,10 +95,31 @@ const LegendSuppression: React.FC<LegendProps> = ({ config, isLegendBottom }) =>
95
95
  const getLegendContainerClass = () =>
96
96
  legend.singleRow && isLegendBottom ? 'legend-container__inner bottom single-row' : ''
97
97
 
98
+ const sumSeries = (data, seriesKey) => {
99
+ if (!Array.isArray(data)) return 0
100
+
101
+ return data.reduce((total, row) => {
102
+ const raw = row[seriesKey]
103
+ const num = parseFloat(raw)
104
+ return total + (Number.isFinite(num) ? num : 0)
105
+ }, 0)
106
+ }
107
+ const total = sumSeries(data, config.runtime.yAxis.dataKey)
108
+
98
109
  const shouldShowSuppressedInfo = () =>
99
110
  !config.legend.hideSuppressionLink &&
100
111
  config.visualizationSubType !== 'stacked' &&
101
112
  preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value && (pd?.style || pd.symbol))
113
+ // controls Pie chart Legend for calculated Area
114
+
115
+ const renderCalculatedAreaItems = () => {
116
+ return (
117
+ <div key={'pie-asterisk'} className='legend-preliminary'>
118
+ <span className={'Asterisk'}>{'**'}</span>
119
+ <p>{'Calculated Area'}</p>
120
+ </div>
121
+ )
122
+ }
102
123
 
103
124
  return (
104
125
  <React.Fragment>
@@ -108,6 +129,12 @@ const LegendSuppression: React.FC<LegendProps> = ({ config, isLegendBottom }) =>
108
129
  <div className={getLegendContainerClass()}>{renderEffectItems()}</div>
109
130
  </React.Fragment>
110
131
  )}
132
+ {showPiePercent && total < 100 && (
133
+ <React.Fragment>
134
+ <hr />
135
+ <div className={getLegendContainerClass()}>{renderCalculatedAreaItems()}</div>
136
+ </React.Fragment>
137
+ )}
111
138
 
112
139
  {shouldShowSuppressedLabels() && (
113
140
  <React.Fragment>
@@ -132,6 +159,23 @@ const LegendSuppression: React.FC<LegendProps> = ({ config, isLegendBottom }) =>
132
159
  </p>
133
160
  </div>
134
161
  )}
162
+ {showPiePercent && (total < 100 || total > 100) && (
163
+ <div className='legend-container__outer link-container'>
164
+ <p>
165
+ {total < 100 ? '** This graph contains a' : 'The sum of percentages in this graph is larger than 100 '}
166
+ <RichTooltip
167
+ tooltipContent={
168
+ total < 100
169
+ ? 'Calculated Areas are used to supplement the pie chart when the sum of the values in the data is less than 100%.'
170
+ : 'Calculated Areas are disabled when the total exceeds 100%.'
171
+ }
172
+ linkText={total < 100 ? 'calculated area' : ''}
173
+ href={null}
174
+ tooltipOpacity={config.tooltips.opacity}
175
+ />
176
+ </p>
177
+ </div>
178
+ )}
135
179
  </React.Fragment>
136
180
  )
137
181
  }
@@ -36,7 +36,7 @@ export const getMarginBottom = (isLegendBottom, config) => {
36
36
 
37
37
  if (isLegendTop) marginBottom = 27
38
38
 
39
- if (isLegendTop && config.dataFormat?.onlyShowTopPrefixSuffix) marginBottom += 9
39
+ if (isLegendTop && config.yAxis?.inlineLabel) marginBottom += 9
40
40
 
41
41
  if (isLegendBottom) marginBottom += 9
42
42
 
@@ -17,19 +17,19 @@ export const createStyles = (props: StyleProps): Style[] => {
17
17
 
18
18
  const dynamicSeriesKey = dynamicCategory ? originalSeriesKey : seriesKey
19
19
  const validPreliminaryData: PreliminaryDataItem[] = preliminaryData.filter(
20
- pd => pd.seriesKey && pd.column && pd.value && pd.type && pd.style && pd.type === 'effect'
20
+ pd => pd.seriesKeys?.length && pd.column && pd.value && pd.type && pd.style && pd.type === 'effect'
21
21
  )
22
22
  const isEffectLine = (pd, dataPoint) => {
23
23
  if (dynamicCategory) {
24
24
  return (
25
25
  pd.type === 'effect' &&
26
26
  pd.style !== 'Open Circles' &&
27
- pd.seriesKey === seriesKey &&
27
+ pd.seriesKeys.includes(seriesKey) &&
28
28
  String(dataPoint[dynamicSeriesKey]) === String(pd.value)
29
29
  )
30
30
  } else {
31
31
  return (
32
- pd.seriesKey === seriesKey &&
32
+ pd.seriesKeys.includes(seriesKey) &&
33
33
  dataPoint[pd.column] === pd.value &&
34
34
  pd.type === 'effect' &&
35
35
  pd.style !== 'Open Circles'
@@ -71,11 +71,11 @@ export const filterCircles = (
71
71
  ): DataItem[] => {
72
72
  // Filter and map preliminaryData to get circlesFiltered
73
73
  const circlesFiltered = preliminaryData
74
- ?.filter(item => item.style.includes('Circles') && item.type === 'effect')
74
+ ?.filter(item => item.style.includes('Circles') && item.type === 'effect' && item.seriesKeys?.length)
75
75
  .map(item => ({
76
76
  column: item.column,
77
77
  value: item.value,
78
- seriesKey: item.seriesKey,
78
+ seriesKeys: item.seriesKeys,
79
79
  circleSize: item.circleSize,
80
80
  style: item.style
81
81
  }))
@@ -85,7 +85,7 @@ export const filterCircles = (
85
85
  circlesFiltered.forEach(fc => {
86
86
  if (
87
87
  item[fc.column] === fc.value &&
88
- fc.seriesKey === seriesKey &&
88
+ fc.seriesKeys.includes(seriesKey) &&
89
89
  item[seriesKey] &&
90
90
  fc.style === 'Open Circles'
91
91
  ) {
@@ -98,7 +98,7 @@ export const filterCircles = (
98
98
  }
99
99
  if (
100
100
  (!fc.value || item[fc.column] === fc.value) &&
101
- fc.seriesKey === seriesKey &&
101
+ fc.seriesKeys.includes(seriesKey) &&
102
102
  item[seriesKey] &&
103
103
  fc.style === 'Filled Circles'
104
104
  ) {
@@ -38,16 +38,13 @@ const LineChart = (props: LineChartProps) => {
38
38
  } = props
39
39
 
40
40
  // prettier-ignore
41
- const { colorScale, config, formatNumber, handleLineType, parseDate, seriesHighlight, tableData, transformedData, updateConfig, brushConfig,clean } = useContext<ChartContext>(ConfigContext)
42
- const { yScaleRight } = useRightAxis({ config, yMax, data: transformedData, updateConfig })
41
+ const { colorScale, config, formatNumber, handleLineType, parseDate, seriesHighlight, tableData, transformedData:data, updateConfig, } = useContext<ChartContext>(ConfigContext)
42
+ const { yScaleRight } = useRightAxis({ config, yMax, data, updateConfig })
43
43
  const showSingleSeries = config.tooltips.singleSeries
44
+ if (!handleTooltipMouseOver) return
44
45
 
45
46
  const DEBUG = false
46
47
  const { lineDatapointStyle, showLineSeriesLabels, legend } = config
47
- const isBrushOn = brushConfig.data.length > 0 && config.brush?.active
48
- // if brush on use brush data and clean
49
- const data = isBrushOn ? clean(brushConfig.data) : transformedData
50
- const _tableData = isBrushOn ? clean(brushConfig.data) : tableData
51
48
 
52
49
  const xPos = d => {
53
50
  return xScale(getXAxisData(d)) + (xScale.bandwidth ? xScale.bandwidth() / 2 : 0)