@cdc/chart 4.25.1 → 4.25.3-6

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 (32) hide show
  1. package/dist/cdcchart.js +40346 -40084
  2. package/examples/feature/tests-non-numerics/example-combo-bar-nonnumeric.json +396 -230
  3. package/examples/private/line-issue.json +497 -0
  4. package/index.html +6 -3
  5. package/package.json +2 -2
  6. package/src/CdcChartComponent.tsx +113 -105
  7. package/src/ConfigContext.tsx +6 -1
  8. package/src/_stories/Chart.DynamicSeries.stories.tsx +16 -1
  9. package/src/_stories/Chart.Filters.stories.tsx +19 -0
  10. package/src/components/Axis/Categorical.Axis.tsx +1 -1
  11. package/src/components/BarChart/components/BarChart.Vertical.tsx +3 -5
  12. package/src/components/BarChart/components/BarChart.jsx +24 -4
  13. package/src/components/BarChart/components/context.tsx +1 -0
  14. package/src/components/BrushChart.tsx +44 -24
  15. package/src/components/DeviationBar.jsx +2 -2
  16. package/src/components/EditorPanel/EditorPanel.tsx +2 -2
  17. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +3 -1
  18. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +6 -1
  19. package/src/components/Legend/helpers/index.ts +10 -4
  20. package/src/components/LineChart/components/LineChart.Circle.tsx +17 -9
  21. package/src/components/LineChart/index.tsx +2 -2
  22. package/src/components/LinearChart.tsx +12 -12
  23. package/src/components/ZoomBrush.tsx +1 -1
  24. package/src/helpers/getColorScale.ts +5 -9
  25. package/src/helpers/isConvertLineToBarGraph.ts +10 -3
  26. package/src/hooks/useBarChart.ts +12 -4
  27. package/src/hooks/useMinMax.ts +7 -8
  28. package/src/hooks/useScales.ts +10 -0
  29. package/src/hooks/useTooltip.tsx +12 -1
  30. package/src/scss/main.scss +1 -1
  31. package/src/store/chart.actions.ts +40 -0
  32. package/src/store/chart.reducer.ts +83 -0
@@ -1,6 +1,6 @@
1
1
  import { Group } from '@visx/group'
2
2
  import { useContext, useEffect, useRef, useState } from 'react'
3
- import ConfigContext from '../ConfigContext'
3
+ import ConfigContext, { ChartDispatchContext } from '../ConfigContext'
4
4
  import * as d3 from 'd3'
5
5
  import { Text } from '@visx/text'
6
6
  import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
@@ -12,7 +12,8 @@ interface BrushChartProps {
12
12
  }
13
13
 
14
14
  const BrushChart = ({ xMax, yMax }: BrushChartProps) => {
15
- const { tableData, config, setBrushConfig, dashboardConfig, formatDate, parseDate } = useContext(ConfigContext)
15
+ const { tableData, config, dashboardConfig, formatDate, parseDate } = useContext(ConfigContext)
16
+ const dispatch = useContext(ChartDispatchContext)
16
17
  const [brushState, setBrushState] = useState({ isBrushing: false, selection: [] })
17
18
  const [brushKey, setBrushKey] = useState(0)
18
19
  const sharedFilters = dashboardConfig?.dashboard?.sharedFilters ?? []
@@ -54,6 +55,9 @@ const BrushChart = ({ xMax, yMax }: BrushChartProps) => {
54
55
 
55
56
  const brushHandle = (g, selection, firstDate, lastDate) => {
56
57
  const textWidth = getTextWidth(firstDate, `normal ${16 / 1.1}px sans-serif`)
58
+ const textPositionLeft = selection[0] < textWidth ? 0 : -textWidth
59
+ const textPositionRight = xMax - selection[1] < textWidth ? -textWidth : 0
60
+
57
61
  return g
58
62
  .selectAll('.handle--custom')
59
63
  .data([{ side: 'left' }, { side: 'right' }])
@@ -61,7 +65,7 @@ const BrushChart = ({ xMax, yMax }: BrushChartProps) => {
61
65
  const handleGroup = enter.append('g').attr('class', 'handle--custom')
62
66
  handleGroup
63
67
  .append('text')
64
- .attr('x', d => (d.side === 'left' ? 0 : -textWidth))
68
+ .attr('x', d => (d.side === 'left' ? textPositionLeft : textPositionRight))
65
69
  .attr('y', 30)
66
70
  .text(d => (d.side === 'left' ? firstDate : lastDate))
67
71
  .attr('font-size', '13px')
@@ -102,22 +106,25 @@ const BrushChart = ({ xMax, yMax }: BrushChartProps) => {
102
106
  const sortedData = _.sortBy(newFilteredData, item => new Date(item[config.xAxis.dataKey]))
103
107
 
104
108
  // If ascending is false, reverse the sorted array
105
- const finalData = !sortByRecentDate ? sortedData : sortedData.reverse()
109
+ const finalData: object[] = !sortByRecentDate ? sortedData : sortedData.reverse()
106
110
 
107
111
  // Retrieve the start and end dates based on the sorted data array
108
- const startDate = _.get(_.first(finalData), config.xAxis.dataKey, '')
109
- const endDate = _.get(_.last(finalData), config.xAxis.dataKey, '')
112
+ const startDate: string = _.get(_.first(finalData), config.xAxis.dataKey, '')
113
+ const endDate: string = _.get(_.last(finalData), config.xAxis.dataKey, '')
110
114
  // add custom blue colored handlers to each corners of brush
111
115
  svg.selectAll('.handle--custom').remove()
112
- // append handler
113
- const [formattedStartDate, formattedEndDate] = [startDate, endDate].map(date => formatDate(parseDate(date)))
116
+ // Parse and format the dates, setting them to an empty string if undefined
117
+ const parseAndFormatDate = date => (date ? formatDate(parseDate(date)) : '')
118
+ const formattedStartDate = parseAndFormatDate(startDate)
119
+ const formattedEndDate = parseAndFormatDate(endDate)
114
120
  svg.call(brushHandle, selection, formattedStartDate, formattedEndDate)
115
121
 
116
- setBrushConfig({
122
+ const payload = {
117
123
  active: config.brush.active,
118
124
  isBrushing: isUserBrushing,
119
125
  data: finalData
120
- })
126
+ }
127
+ dispatch({ type: 'SET_BRUSH_CONFIG', payload: payload })
121
128
  setBrushState({
122
129
  isBrushing: true,
123
130
  selection
@@ -150,22 +157,35 @@ const BrushChart = ({ xMax, yMax }: BrushChartProps) => {
150
157
 
151
158
  if ((isFiltersActive || isExclusionsActive || isDashboardFilters) && config.brush?.active) {
152
159
  setBrushKey(prevKey => prevKey + 1)
153
- setBrushConfig(prev => {
154
- return {
155
- ...prev,
156
- data: tableData
157
- }
158
- })
159
160
  }
160
- return () =>
161
- setBrushConfig(prev => {
162
- return {
163
- ...prev,
164
- data: []
165
- }
166
- })
161
+ dispatch({ type: 'SET_BRUSH_CONFIG', payload: { ...config.brush, data: tableData } })
162
+
163
+ return () => dispatch({ type: 'SET_BRUSH_CONFIG', payload: { ...config.brush, data: [] } })
167
164
  }, [config.filters, config.exclusions, config.brush?.active, isDashboardFilters])
168
- // Initialize brush when component is first rendered
165
+
166
+ // this effect handles where brush chart is missing on production. it helpes re render
167
+ useEffect(() => {
168
+ let timeoutId = null
169
+
170
+ const checkAndInitializeBrush = () => {
171
+ if (xMax > 0) {
172
+ initializeBrush()
173
+ } else {
174
+ // Clear the existing timeout and set a new one
175
+ clearTimeout(timeoutId)
176
+ timeoutId = setTimeout(checkAndInitializeBrush, 500)
177
+ }
178
+ }
179
+
180
+ checkAndInitializeBrush()
181
+
182
+ // Cleanup function to clear timeout
183
+ return () => {
184
+ if (timeoutId) {
185
+ clearTimeout(timeoutId)
186
+ }
187
+ }
188
+ }, [xMax])
169
189
 
170
190
  // reset brush on keychange
171
191
  useEffect(() => {
@@ -64,7 +64,7 @@ export default function DeviationBar({ height, xScale }) {
64
64
  const firstBarValue = data[0][seriesKey]
65
65
  const barPosition = firstBarValue < target ? 'left' : 'right'
66
66
  const label = `${config.xAxis.targetLabel} ${formatNumber(config.xAxis.target || 0, 'left')}`
67
- const labelWidth = getTextWidth(label)
67
+ const labelWidth = getTextWidth(label, `bold ${20}px sans-serif`)
68
68
  let labelY = config.isLollipopChart ? lollipopBarHeight / 2 : Number(config.barHeight) / 2
69
69
  let paddingX = 0
70
70
  let labelX = 0
@@ -164,7 +164,7 @@ export default function DeviationBar({ height, xScale }) {
164
164
  config.heights.horizontal = totalheight
165
165
 
166
166
  // text,labels postiions
167
- const textWidth = getTextWidth(formatNumber(barValue, 'left'))
167
+ const textWidth = getTextWidth(formatNumber(barValue, 'left'), `normal ${16}px sans-serif`)
168
168
  const textFits = textWidth < barWidth - 6
169
169
  const textX = barBaseX
170
170
  const textY = barY + barHeight / 2
@@ -596,7 +596,7 @@ const EditorPanel = () => {
596
596
  updateConfig,
597
597
  tableData,
598
598
  transformedData: data,
599
- loading,
599
+ isLoading,
600
600
  colorScale,
601
601
  colorPalettes,
602
602
  twoColorPalette,
@@ -813,7 +813,7 @@ const EditorPanel = () => {
813
813
  const [displayPanel, setDisplayPanel] = useState(true)
814
814
  const [displayViewportOverrides, setDisplayViewportOverrides] = useState(false)
815
815
 
816
- if (loading) {
816
+ if (isLoading) {
817
817
  return null
818
818
  }
819
819
 
@@ -500,6 +500,8 @@ const SeriesInputName = props => {
500
500
 
501
501
  updateConfig(newConfig)
502
502
  }
503
+ // if series name is emty show default data value.
504
+ const value = series.name !== undefined && series.name !== series.dataKey ? series.name : series.dataKey
503
505
 
504
506
  return (
505
507
  <>
@@ -507,7 +509,7 @@ const SeriesInputName = props => {
507
509
  <input
508
510
  type='text'
509
511
  key={`series-name-${i}`}
510
- value={series.name ? series.name : ''}
512
+ value={value}
511
513
  onChange={event => {
512
514
  changeSeriesName(i, event.target.value)
513
515
  }}
@@ -30,7 +30,12 @@ export const updateFieldRankByValue = (
30
30
  newConfig.rankByValue = newValue
31
31
 
32
32
  if (config.rankByValue && !newValue) {
33
- const cleanData = config?.xAxis?.dataKey ? transform.cleanData(config.data, config.xAxis.dataKey) : config.data
33
+ const CIkeys: string[] = Object.values(config.confidenceKeys) as string[]
34
+ const seriesKeys: string[] = config.series.map(s => s.dataKey)
35
+ const keysToClean: string[] = seriesKeys.concat(CIkeys)
36
+ const cleanData = config?.xAxis?.dataKey
37
+ ? transform.cleanData(config.data, config.xAxis.dataKey, keysToClean)
38
+ : config.data
34
39
  const newData = preTransformedData.sort((a, b) => {
35
40
  const aIndex = indexOfObj(cleanData, a)
36
41
  const bIndex = indexOfObj(cleanData, b)
@@ -11,14 +11,20 @@ export const getGradientConfig = (config, formatLabels, colorScale) => {
11
11
  }
12
12
 
13
13
  export const getMarginTop = (isLegendBottom, config) => {
14
+ // margin between charts xAxis legend not to overlap axis labels,ticks.
15
+ const DEFAULT_MARGIN_TOP = 27
16
+ if (isLegendBottom && config.legend.hide) {
17
+ return '0px'
18
+ }
14
19
  if (!isLegendBottom) {
15
20
  return '0px'
16
21
  }
17
- if (isLegendBottom && config.brush?.active) {
18
- const BRUSH_HEIGHT_MULTIPLIER = 1.5
19
- return `${config.brush.height * BRUSH_HEIGHT_MULTIPLIER}px`
22
+ if (isLegendBottom && config.brush.active && !config.legend.hide) {
23
+ const additiolMargin = 25
24
+ return `${DEFAULT_MARGIN_TOP + config.brush.height + additiolMargin}px`
25
+ } else {
26
+ return `${DEFAULT_MARGIN_TOP}px`
20
27
  }
21
- return '27px'
22
28
  }
23
29
  export const getMarginBottom = (isLegendBottom, config) => {
24
30
  const isLegendTop = config.legend?.position === 'top' && !config.legend.hide
@@ -63,7 +63,7 @@ const LineChartCircle = (props: LineChartCircleProps) => {
63
63
  seriesIndex
64
64
  } = props
65
65
  const { lineDatapointStyle, visual } = config
66
- const filtered = config?.runtime?.series.filter(s => s.dataKey === seriesKey)?.[0]
66
+ const filtered = config?.series.filter(s => s.dataKey === seriesKey)?.[0]
67
67
  const Shape =
68
68
  Glyphs[
69
69
  config.visual.lineDatapointSymbol === 'standard' && seriesIndex < visual.maximumShapeAmount ? seriesIndex : 0
@@ -80,8 +80,8 @@ const LineChartCircle = (props: LineChartCircleProps) => {
80
80
  seriesKey: string
81
81
  ) => {
82
82
  const seriesLabels = config.runtime.seriesLabels || []
83
- let color = displayArea ? colorScale(seriesLabels[hoveredKey] || seriesKey) : ' transparent'
84
-
83
+ const seriesLabelsAll = config.runtime.seriesLabelsAll || []
84
+ let color = displayArea ? colorScale(seriesLabels[hoveredKey] || seriesLabelsAll[seriesIndex]) : ' transparent'
85
85
  if (config.lineDatapointColor === 'Lighter than Line' && color !== 'transparent' && color) {
86
86
  color = chroma(color).brighten(1)
87
87
  }
@@ -130,15 +130,23 @@ const LineChartCircle = (props: LineChartCircleProps) => {
130
130
  if (!hoveredXValue) return
131
131
 
132
132
  let hoveredSeriesValue
133
- let hoveredSeriesIndex
134
133
  let hoveredSeriesData = tooltipData.data.filter(d => d[0] === seriesKey)
135
134
  let hoveredSeriesKey = hoveredSeriesData?.[0]?.[0]
136
135
  let hoveredSeriesAxis = hoveredSeriesData?.[0]?.[2]
136
+ const dynamicSeriesConfig = config.runtime.series.find(s => s.dynamicCategory)
137
+ const originalDataKey = dynamicSeriesConfig?.originalDataKey ?? seriesKey
138
+
137
139
  if (!hoveredSeriesKey) return
138
- hoveredSeriesIndex = tooltipData?.data.indexOf(hoveredSeriesKey)
139
140
  hoveredSeriesValue = tableData?.find(d => {
140
- return d[config?.xAxis.dataKey] === hoveredXValue
141
- })?.[seriesKey]
141
+ const dynamicCategory = dynamicSeriesConfig?.dynamicCategory
142
+ const matchingXValue = d[config.xAxis.dataKey] === hoveredXValue
143
+ if (!matchingXValue) return false
144
+ if (dynamicCategory) {
145
+ const match = d[dynamicCategory] === hoveredSeriesKey
146
+ return match
147
+ }
148
+ return true
149
+ })?.[originalDataKey]
142
150
 
143
151
  // hoveredSeriesValue = extractNumber(hoveredSeriesValue)
144
152
  return tooltipData?.data.map((tooltipItem, index) => {
@@ -190,10 +198,10 @@ const LineChartCircle = (props: LineChartCircleProps) => {
190
198
  return isFirstPoint || isLastPoint || isMiddlePoint
191
199
  }
192
200
 
193
- if (drawIsolatedPoints(dataIndex, seriesKey) && !config.series.some(s => s.dynamicCategory)) {
201
+ if (drawIsolatedPoints(dataIndex, seriesKey)) {
194
202
  const positionTop = filtered?.axis === 'Right' ? yScaleRight(d[filtered?.dataKey]) : yScale(d[filtered?.dataKey])
195
203
  const positionLeft = getXPos(d[config.xAxis?.dataKey])
196
- const color = colorScale(config.runtime.seriesLabels[seriesKey])
204
+ const color = colorScale(config.runtime.seriesLabelsAll[seriesIndex])
197
205
 
198
206
  return (
199
207
  <g transform={transformShape(positionTop, positionLeft)}>
@@ -89,14 +89,14 @@ const LineChart = (props: LineChartProps) => {
89
89
  opacity={
90
90
  legend.behavior === 'highlight' &&
91
91
  seriesHighlight.length > 0 &&
92
- seriesHighlight.indexOf(_seriesKey) === -1
92
+ seriesHighlight.indexOf(seriesKey) === -1
93
93
  ? 0.5
94
94
  : 1
95
95
  }
96
96
  display={
97
97
  legend.behavior === 'highlight' ||
98
98
  (seriesHighlight.length === 0 && !legend.dynamicLegend) ||
99
- seriesHighlight.indexOf(_seriesKey) !== -1
99
+ seriesHighlight.indexOf(seriesKey) !== -1
100
100
  ? 'block'
101
101
  : 'none'
102
102
  }
@@ -26,7 +26,6 @@ import Regions from './Regions'
26
26
  import CategoricalYAxis from './Axis/Categorical.Axis'
27
27
 
28
28
  // Helpers
29
- import { isConvertLineToBarGraph } from '../helpers/isConvertLineToBarGraph'
30
29
  import { isLegendWrapViewport, isMobileHeightViewport } from '@cdc/core/helpers/viewports'
31
30
  import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
32
31
  import { calcInitialHeight } from '../helpers/sizeHelpers'
@@ -65,6 +64,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
65
64
  brushConfig,
66
65
  colorScale,
67
66
  config,
67
+ convertLineToBarGraph,
68
68
  currentViewport,
69
69
  dimensions,
70
70
  formatDate,
@@ -257,10 +257,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
257
257
  const useDateSpanMonths = isDateTime && dateSpanMonths > xTickCount
258
258
 
259
259
  // GETTERS & FUNCTIONS
260
- const checkLineToBarGraph = () => {
261
- return isConvertLineToBarGraph(config.visualizationType, data, config.allowLineToBarGraph)
262
- }
263
-
264
260
  const handleLeftTickFormatting = (tick, index, ticks) => {
265
261
  if (isLogarithmicAxis && tick === 0.1) {
266
262
  //when logarithmic scale applied change value of first tick
@@ -385,7 +381,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
385
381
  const topLabelOnGridline = topYLabelRef.current && yAxis.labelsAboveGridlines
386
382
 
387
383
  // Heights to add
388
- const brushHeight = brush?.active ? brush?.height : 0
384
+
385
+ const brushHeight = brush?.active ? brush?.height + brush?.height : 0
389
386
  const forestRowsHeight = isForestPlot ? config.data.length * forestPlot.rowHeight : 0
390
387
  const topLabelOnGridlineHeight = topLabelOnGridline ? topYLabelRef.current.getBBox().height : 0
391
388
  const additionalHeight = axisBottomHeight + brushHeight + forestRowsHeight + topLabelOnGridlineHeight
@@ -732,7 +729,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
732
729
  showTooltip={showTooltip}
733
730
  />
734
731
  )}
735
- {(visualizationType === 'Bar' || visualizationType === 'Combo' || checkLineToBarGraph()) && (
732
+ {(visualizationType === 'Bar' || visualizationType === 'Combo' || convertLineToBarGraph) && (
736
733
  <BarChart
737
734
  xScale={xScale}
738
735
  yScale={yScale}
@@ -751,7 +748,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
751
748
  chartRef={svgRef}
752
749
  />
753
750
  )}
754
- {((visualizationType === 'Line' && !checkLineToBarGraph()) ||
751
+ {((visualizationType === 'Line' && !convertLineToBarGraph) ||
755
752
  visualizationType === 'Combo' ||
756
753
  visualizationType === 'Bump Chart') && (
757
754
  <LineChart
@@ -815,7 +812,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
815
812
  {!['Paired Bar', 'Box Plot', 'Area Chart', 'Scatter Plot', 'Deviation Bar', 'Forecasting', 'Bar'].includes(
816
813
  visualizationType
817
814
  ) &&
818
- !checkLineToBarGraph() && (
815
+ !convertLineToBarGraph && (
819
816
  <>
820
817
  <LineChart
821
818
  xScale={xScale}
@@ -948,7 +945,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
948
945
  {config.chartMessage.noData}
949
946
  </Text>
950
947
  )}
951
- {(config.visualizationType === 'Bar' || checkLineToBarGraph()) &&
948
+ {(config.visualizationType === 'Bar' || convertLineToBarGraph) &&
952
949
  config.tooltips.singleSeries &&
953
950
  config.visual.horizontalHoverLine && (
954
951
  <Group
@@ -967,7 +964,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
967
964
  />
968
965
  </Group>
969
966
  )}
970
- {(config.visualizationType === 'Bar' || checkLineToBarGraph()) &&
967
+ {(config.visualizationType === 'Bar' || convertLineToBarGraph) &&
971
968
  config.tooltips.singleSeries &&
972
969
  config.visual.verticalHoverLine && (
973
970
  <Group key='tooltipLine-vertical' className='vertical-tooltip-line'>
@@ -1536,6 +1533,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1536
1533
  Object.entries(tooltipData.data).length > 0 &&
1537
1534
  tooltipOpen &&
1538
1535
  showTooltip &&
1536
+ !tooltipData?.data?.some(subArray => subArray.some(item => item === undefined)) &&
1539
1537
  tooltipData.dataYPosition &&
1540
1538
  tooltipData.dataXPosition && (
1541
1539
  <>
@@ -1551,7 +1549,9 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1551
1549
  >
1552
1550
  <ul>
1553
1551
  {typeof tooltipData === 'object' &&
1554
- Object.entries(tooltipData.data).map((item, index) => <TooltipListItem item={item} key={index} />)}
1552
+ Object.entries(tooltipData.data)
1553
+ .filter(([_, values]) => Array.isArray(values) && !values.includes(undefined))
1554
+ .map((item, index) => <TooltipListItem item={item} key={index} />)}
1555
1555
  </ul>
1556
1556
  </TooltipWithBounds>
1557
1557
  </>
@@ -210,7 +210,7 @@ const BrushHandle = props => {
210
210
  const transform = isLeft ? 'scale(-1, 1)' : 'translate(0,0)'
211
211
  const textAnchor = isLeft ? 'end' : 'start'
212
212
  const tooltipText = isLeft ? ` Drag edges to focus on a specific segment ` : ''
213
- const textWidth = getTextWidth(tooltipText, `normal ${appFontSize / 1.1}px sans-serif`)
213
+ const textWidth = getTextWidth(tooltipText, `${appFontSize / 1.1}px`)
214
214
 
215
215
  return (
216
216
  <>
@@ -9,7 +9,6 @@ export const getColorScale = (config: ChartConfig): ((value: string) => string)
9
9
  const allPalettes: Record<string, string[]> = { ...colorPalettes, ...twoColorPalette }
10
10
  let palette = config.customColors || allPalettes[configPalette]
11
11
  let numberOfKeys = config.runtime.seriesKeys.length
12
- let newColorScale
13
12
 
14
13
  while (numberOfKeys > palette.length) {
15
14
  palette = palette.concat(palette)
@@ -17,12 +16,9 @@ export const getColorScale = (config: ChartConfig): ((value: string) => string)
17
16
 
18
17
  palette = palette.slice(0, numberOfKeys)
19
18
 
20
- newColorScale = () =>
21
- scaleOrdinal({
22
- domain: config.runtime.seriesLabelsAll,
23
- range: palette,
24
- unknown: null
25
- })
26
-
27
- return newColorScale
19
+ return scaleOrdinal({
20
+ domain: config.runtime.seriesLabelsAll,
21
+ range: palette,
22
+ unknown: null
23
+ })
28
24
  }
@@ -1,4 +1,11 @@
1
- export const isConvertLineToBarGraph = (visualizationType, filteredData, allowLineToBarGraph) => {
2
- const convertLineToBarGraph = visualizationType === 'Line' && filteredData?.length < 3 && allowLineToBarGraph ? true : false
3
- return convertLineToBarGraph
1
+ import _ from 'lodash'
2
+
3
+ export const isConvertLineToBarGraph = (configObj, filteredData) => {
4
+ const { allowLineToBarGraph, series, visualizationType, xAxis } = configObj
5
+ if (!allowLineToBarGraph) return false
6
+ const lineWithLessThanThreePoints = visualizationType === 'Line' && filteredData?.length < 3
7
+ const isDynamicSeries = series?.some(series => series.dynamicCategory)
8
+ const isDynamicWithLessThanThreePoints =
9
+ isDynamicSeries && _.uniq(filteredData?.map(data => data[xAxis.dataKey])).length <= 2
10
+ return lineWithLessThanThreePoints || isDynamicWithLessThanThreePoints
4
11
  }
@@ -1,10 +1,11 @@
1
1
  import React, { useContext, useEffect, useState } from 'react'
2
- import ConfigContext from '../ConfigContext'
2
+ import ConfigContext, { ChartDispatchContext } from '../ConfigContext'
3
3
  import { formatNumber as formatColNumber } from '@cdc/core/helpers/cove/number'
4
4
  import { appFontSize } from '@cdc/core/helpers/cove/fontSettings'
5
5
  export const useBarChart = () => {
6
6
  const { config, colorPalettes, tableData, updateConfig, parseDate, formatDate, setSeriesHighlight, seriesHighlight } =
7
7
  useContext(ConfigContext)
8
+ const dispatch = useContext(ChartDispatchContext)
8
9
  const { orientation } = config
9
10
  const [hoveredBar, setHoveredBar] = useState(null)
10
11
 
@@ -192,9 +193,11 @@ export const useBarChart = () => {
192
193
  const columns = config.columns
193
194
  const columnsWithTooltips = []
194
195
  let additionalTooltipItems = ''
196
+ const dynamicCategorySeries = config.runtime?.series?.find(series => series?.dynamicCategory)
195
197
  const closestVal =
196
198
  tableData.find(d => {
197
- return d[config.xAxis.dataKey] === xAxisDataValue
199
+ const dynamicCategoryMatch = dynamicCategorySeries ? d[dynamicCategorySeries.dynamicCategory] === series : true
200
+ return d[config.xAxis.dataKey] === xAxisDataValue && dynamicCategoryMatch
198
201
  }) || {}
199
202
  Object.keys(columns).forEach(colKeys => {
200
203
  if (series && config.columns[colKeys].series && config.columns[colKeys].series !== series) return
@@ -223,11 +226,16 @@ export const useBarChart = () => {
223
226
  }
224
227
 
225
228
  const onMouseOverBar = (categoryValue, barKey) => {
226
- if (config.legend.highlightOnHover && config.legend.behavior === 'highlight' && barKey) setSeriesHighlight([barKey])
229
+ if (config.legend.highlightOnHover && config.legend.behavior === 'highlight' && barKey) {
230
+ dispatch({ type: 'SET_SERIES_HIGHLIGHT', payload: [barKey] })
231
+ }
232
+
227
233
  setHoveredBar(categoryValue)
228
234
  }
229
235
  const onMouseLeaveBar = () => {
230
- if (config.legend.highlightOnHover && config.legend.behavior === 'highlight') setSeriesHighlight([])
236
+ if (config.legend.highlightOnHover && config.legend.behavior === 'highlight') {
237
+ dispatch({ type: 'SET_SERIES_HIGHLIGHT', payload: [] })
238
+ }
231
239
  }
232
240
 
233
241
  return {
@@ -1,6 +1,7 @@
1
1
  import { ChartConfig } from '../types/ChartConfig'
2
2
  import _ from 'lodash'
3
- import { isConvertLineToBarGraph } from '../helpers/isConvertLineToBarGraph'
3
+ import ConfigContext from '../ConfigContext'
4
+ import { useContext } from 'react'
4
5
 
5
6
  type UseMinMaxProps = {
6
7
  /** config - standard chart config */
@@ -27,14 +28,12 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
27
28
  let leftMax = 0
28
29
  let rightMax = 0
29
30
 
31
+ const { convertLineToBarGraph } = useContext(ConfigContext)
32
+
30
33
  if (!data) {
31
34
  return { min, max }
32
35
  }
33
36
 
34
- const checkLineToBarGraph = () => {
35
- return isConvertLineToBarGraph(config.visualizationType, data, config.allowLineToBarGraph)
36
- }
37
-
38
37
  const { visualizationType, series } = config
39
38
  const { max: enteredMaxValue, min: enteredMinValue } = config.runtime.yAxis
40
39
  const paddingAddedToAxis = config.yAxis.enablePadding ? 1 + config.yAxis.scalePadding / 100 : 1
@@ -136,14 +135,14 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
136
135
 
137
136
  // this should not apply to bar charts if there is negative CI data
138
137
  if (
139
- (visualizationType === 'Bar' || checkLineToBarGraph() || (visualizationType === 'Combo' && !isAllLine)) &&
138
+ (visualizationType === 'Bar' || convertLineToBarGraph || (visualizationType === 'Combo' && !isAllLine)) &&
140
139
  min > 0
141
140
  ) {
142
141
  min = 0
143
142
  }
144
143
  if (
145
144
  (config.visualizationType === 'Bar' ||
146
- checkLineToBarGraph() ||
145
+ convertLineToBarGraph ||
147
146
  (config.visualizationType === 'Combo' && !isAllLine)) &&
148
147
  min < 0
149
148
  ) {
@@ -167,7 +166,7 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
167
166
  min = Number(enteredMinValue) && isMinValid ? Number(enteredMinValue) : 0
168
167
  }
169
168
 
170
- if (config.visualizationType === 'Line' && !checkLineToBarGraph()) {
169
+ if (config.visualizationType === 'Line' && !convertLineToBarGraph) {
171
170
  const isMinValid = isLogarithmicAxis
172
171
  ? Number(enteredMinValue) >= 0 && Number(enteredMinValue) < minValue
173
172
  : Number(enteredMinValue) < minValue
@@ -136,6 +136,16 @@ const useScales = (properties: useScaleProps) => {
136
136
  })
137
137
  xScale.type = scaleTypes.LINEAR
138
138
  }
139
+ if (xAxis.type === 'categorical') {
140
+ // Map items to rounded numbers if numeric, skip formatting non-numeric strings.
141
+ const xAxisDataMappedRoundedItems = xAxisDataMapped.map(item => {
142
+ const strItem = String(item)
143
+ const parsed = parseFloat(strItem)
144
+ return !isNaN(parsed) ? Math.round(parsed).toString() : strItem
145
+ })
146
+
147
+ xScale = composeScaleBand(xAxisDataMappedRoundedItems, [0, xMax], 1 - config.barThickness)
148
+ }
139
149
  }
140
150
 
141
151
  // handle Box plot
@@ -175,11 +175,16 @@ export const useTooltip = props => {
175
175
  ?.flatMap(seriesKey => {
176
176
  const value = resolvedScaleValues[0]?.[seriesKey]
177
177
  const formattedValue = getFormattedValue(seriesKey, value, config, getAxisPosition)
178
+ const seriesObjWithName = config.runtime.series.find(
179
+ series => series.dataKey === seriesKey && series.name !== undefined
180
+ )
178
181
  if (
179
182
  (value === null || value === undefined || value === '' || formattedValue === 'N/A') &&
180
183
  config.general.hideNullValue
181
184
  ) {
182
185
  return []
186
+ } else if (seriesObjWithName && seriesObjWithName.name === '') {
187
+ return [['', formattedValue, getAxisPosition(seriesKey)]]
183
188
  } else {
184
189
  return [[seriesKey, formattedValue, getAxisPosition(seriesKey)]]
185
190
  }
@@ -567,8 +572,14 @@ export const useTooltip = props => {
567
572
  if (index == 1 && config.dataFormat.onlyShowTopPrefixSuffix) {
568
573
  newValue = `${config.dataFormat.prefix}${newValue}${config.dataFormat.suffix}`
569
574
  }
575
+ const activeLabel = getSeriesNameFromLabel(key)
576
+ const displayText = activeLabel ? `${activeLabel}: ${newValue}` : newValue
570
577
 
571
- return <li style={style} className='tooltip-body'>{`${getSeriesNameFromLabel(key)}: ${newValue}`}</li>
578
+ return (
579
+ <li style={style} className='tooltip-body'>
580
+ {displayText}
581
+ </li>
582
+ )
572
583
  }
573
584
 
574
585
  return {
@@ -133,7 +133,7 @@
133
133
  .subtext--responsive-ticks,
134
134
  .section-subtext {
135
135
  &--brush-active {
136
- margin-top: 3em !important;
136
+ margin-top: 3rem !important;
137
137
  }
138
138
  }
139
139
 
@@ -0,0 +1,40 @@
1
+ import { DimensionsType } from '@cdc/core/types/Dimensions'
2
+ import { ChartConfig } from '../types/ChartConfig'
3
+
4
+ type Action<T, P = undefined, R = undefined> = {
5
+ type: T
6
+ payload?: P
7
+ }
8
+
9
+ // Action Types
10
+ type SET_CONFIG = Action<'SET_CONFIG', ChartConfig>
11
+ type SET_LOADING = Action<'SET_LOADING', boolean>
12
+ type UPDATE_CONFIG = Action<'UPDATE_CONFIG', ChartConfig>
13
+ type SET_COLOR_SCALE = Action<'SET_COLOR_SCALE', Function>
14
+ type SET_STATE_DATA = Action<'SET_STATE_DATA', object[]>
15
+ type SET_EXCLUDED_DATA = Action<'SET_EXCLUDED_DATA', object[]>
16
+ type SET_FILTERED_DATA = Action<'SET_FILTERED_DATA', object[]>
17
+ type SET_SERIES_HIGHLIGHT = Action<'SET_SERIES_HIGHLIGHT', object>
18
+ type SET_VIEWPORT = Action<'SET_VIEWPORT', string>
19
+ type SET_DIMENSIONS = Action<'SET_DIMENSIONS', DimensionsType>
20
+ type SET_CONTAINER = Action<'SET_CONTAINER', object>
21
+ type SET_LOADED_EVENT = Action<'SET_LOADED_EVENT', boolean>
22
+ type SET_DRAG_ANNOTATIONS = Action<'SET_DRAG_ANNOTATIONS', boolean>
23
+ type SET_BRUSH_CONFIG = Action<'SET_BRUSH_CONFIG', object>
24
+ type ChartActions =
25
+ | SET_CONFIG
26
+ | UPDATE_CONFIG
27
+ | SET_COLOR_SCALE
28
+ | SET_STATE_DATA
29
+ | SET_EXCLUDED_DATA
30
+ | SET_FILTERED_DATA
31
+ | SET_SERIES_HIGHLIGHT
32
+ | SET_VIEWPORT
33
+ | SET_DIMENSIONS
34
+ | SET_CONTAINER
35
+ | SET_LOADED_EVENT
36
+ | SET_DRAG_ANNOTATIONS
37
+ | SET_BRUSH_CONFIG
38
+ | SET_LOADING
39
+
40
+ export default ChartActions