@cdc/chart 4.24.12 → 4.25.2-25

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 (84) hide show
  1. package/dist/cdcchart.js +79900 -78999
  2. package/examples/feature/boxplot/boxplot.json +2 -157
  3. package/examples/feature/boxplot/testing.csv +23 -38
  4. package/examples/feature/tests-non-numerics/example-combo-bar-nonnumeric.json +579 -49
  5. package/examples/private/ehdi.json +29939 -0
  6. package/examples/private/line-issue.json +497 -0
  7. package/examples/private/not-loading.json +360 -0
  8. package/index.html +11 -15
  9. package/package.json +2 -2
  10. package/src/CdcChart.tsx +92 -1512
  11. package/src/CdcChartComponent.tsx +1113 -0
  12. package/src/ConfigContext.tsx +6 -1
  13. package/src/_stories/Chart.Anchors.stories.tsx +1 -1
  14. package/src/_stories/Chart.CustomColors.stories.tsx +1 -1
  15. package/src/_stories/Chart.DynamicSeries.stories.tsx +17 -2
  16. package/src/_stories/Chart.Filters.stories.tsx +19 -0
  17. package/src/_stories/Chart.Legend.Gradient.stories.tsx +2 -2
  18. package/src/_stories/Chart.ScatterPlot.stories.tsx +19 -0
  19. package/src/_stories/Chart.tooltip.stories.tsx +1 -2
  20. package/src/_stories/ChartAnnotation.stories.tsx +1 -1
  21. package/src/_stories/ChartAxisLabels.stories.tsx +1 -1
  22. package/src/_stories/ChartAxisTitles.stories.tsx +1 -1
  23. package/src/_stories/ChartEditor.stories.tsx +1 -1
  24. package/src/_stories/ChartLine.Suppression.stories.tsx +1 -1
  25. package/src/_stories/ChartLine.Symbols.stories.tsx +18 -0
  26. package/src/_stories/ChartPrefixSuffix.stories.tsx +1 -1
  27. package/src/_stories/_mock/line_chart_symbols.json +437 -0
  28. package/src/_stories/_mock/scatterplot-image-download.json +1244 -0
  29. package/src/components/Annotations/components/AnnotationDraggable.tsx +3 -11
  30. package/src/components/Annotations/components/AnnotationDropdown.tsx +3 -3
  31. package/src/components/Axis/Categorical.Axis.tsx +3 -4
  32. package/src/components/BarChart/components/BarChart.Horizontal.tsx +14 -5
  33. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +10 -4
  34. package/src/components/BarChart/components/BarChart.Vertical.tsx +5 -7
  35. package/src/components/BarChart/components/BarChart.jsx +24 -4
  36. package/src/components/BarChart/components/context.tsx +1 -0
  37. package/src/components/BoxPlot/BoxPlot.tsx +34 -32
  38. package/src/components/BoxPlot/helpers/index.ts +108 -18
  39. package/src/components/BrushChart.tsx +44 -24
  40. package/src/components/DeviationBar.jsx +2 -6
  41. package/src/components/EditorPanel/EditorPanel.tsx +64 -8
  42. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +4 -0
  43. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +3 -1
  44. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +44 -7
  45. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +6 -1
  46. package/src/components/ForestPlot/ForestPlot.tsx +176 -26
  47. package/src/components/Legend/Legend.Component.tsx +29 -38
  48. package/src/components/Legend/Legend.Suppression.tsx +3 -5
  49. package/src/components/Legend/Legend.tsx +2 -2
  50. package/src/components/Legend/LegendLine.Shape.tsx +51 -0
  51. package/src/components/Legend/helpers/createFormatLabels.tsx +29 -26
  52. package/src/components/Legend/helpers/getLegendClasses.ts +20 -38
  53. package/src/components/Legend/helpers/index.ts +22 -9
  54. package/src/components/Legend/tests/getLegendClasses.test.ts +3 -20
  55. package/src/components/LineChart/components/LineChart.Circle.tsx +104 -94
  56. package/src/components/LineChart/index.tsx +6 -2
  57. package/src/components/LinearChart.tsx +77 -43
  58. package/src/components/PairedBarChart.jsx +2 -9
  59. package/src/components/ZoomBrush.tsx +5 -7
  60. package/src/data/initial-state.js +6 -3
  61. package/src/helpers/getBoxPlotConfig.ts +68 -0
  62. package/src/helpers/getColorScale.ts +24 -0
  63. package/src/helpers/getComboChartConfig.ts +42 -0
  64. package/src/helpers/getExcludedData.ts +37 -0
  65. package/src/helpers/getTopAxis.ts +7 -0
  66. package/src/helpers/isConvertLineToBarGraph.ts +10 -3
  67. package/src/hooks/useBarChart.ts +40 -13
  68. package/src/hooks/{useHighlightedBars.js → useHighlightedBars.ts} +2 -1
  69. package/src/hooks/useIntersectionObserver.ts +37 -0
  70. package/src/hooks/useMinMax.ts +11 -8
  71. package/src/hooks/useReduceData.ts +1 -1
  72. package/src/hooks/useScales.ts +10 -0
  73. package/src/hooks/useTooltip.tsx +21 -2
  74. package/src/index.jsx +1 -0
  75. package/src/scss/DataTable.scss +0 -5
  76. package/src/scss/main.scss +31 -116
  77. package/src/store/chart.actions.ts +40 -0
  78. package/src/store/chart.reducer.ts +83 -0
  79. package/src/types/ChartConfig.ts +6 -3
  80. package/src/types/ChartContext.ts +1 -3
  81. package/src/helpers/getQuartiles.ts +0 -27
  82. package/src/hooks/useColorScale.ts +0 -50
  83. package/src/hooks/useIntersectionObserver.jsx +0 -29
  84. package/src/hooks/useTopAxis.js +0 -6
@@ -0,0 +1,42 @@
1
+ import _ from 'lodash'
2
+ import { ChartConfig } from '../types/ChartConfig'
3
+ import * as d3 from 'd3-array'
4
+
5
+ export const getComboChartConfig = (newConfig: ChartConfig) => {
6
+ if (newConfig.visualizationType !== 'Combo' || !newConfig.series) return
7
+
8
+ const runtimeKeys = {
9
+ barSeriesKeys: [],
10
+ lineSeriesKeys: [],
11
+ areaSeriesKeys: [],
12
+ forecastingSeriesKeys: []
13
+ }
14
+
15
+ // Define a mapping of series types to runtime keys
16
+ const seriesTypeMap = new Map([
17
+ ['Area Chart', 'areaSeriesKeys'],
18
+ ['Forecasting', 'forecastingSeriesKeys'],
19
+ ['Bar', 'barSeriesKeys'],
20
+ ['Combo', 'barSeriesKeys'],
21
+ ['Line', 'lineSeriesKeys'],
22
+ ['dashed-sm', 'lineSeriesKeys'],
23
+ ['dashed-md', 'lineSeriesKeys'],
24
+ ['dashed-lg', 'lineSeriesKeys']
25
+ ])
26
+
27
+ newConfig.series.forEach(series => {
28
+ const runtimeKey = seriesTypeMap.get(series.type)
29
+ if (runtimeKey) {
30
+ const valueToPush = runtimeKey === 'barSeriesKeys' || runtimeKey === 'lineSeriesKeys' ? series.dataKey : series
31
+ runtimeKeys[runtimeKey].push(valueToPush)
32
+ }
33
+
34
+ // Change Combo series type to Bar
35
+ if (series.type === 'Combo') {
36
+ series.type = 'Bar'
37
+ }
38
+ })
39
+
40
+ // Assign the processed runtime keys to the configuration
41
+ return { ...newConfig.runtime, ...runtimeKeys }
42
+ }
@@ -0,0 +1,37 @@
1
+ import { isDateScale } from '@cdc/core/helpers/cove/date'
2
+ import _ from 'lodash'
3
+ import { ChartConfig } from '../types/ChartConfig'
4
+ export const getExcludedData = (newConfig: ChartConfig, data: object[]) => {
5
+ let newExcludedData = data
6
+ if (newConfig.exclusions && newConfig.exclusions.active) {
7
+ if (newConfig.xAxis.type === 'categorical' && newConfig.exclusions.keys?.length > 0) {
8
+ newExcludedData = data.filter(e => !newConfig.exclusions.keys.includes(e[newConfig.xAxis.dataKey]))
9
+ } else if (
10
+ isDateScale(newConfig.xAxis) &&
11
+ (newConfig.exclusions.dateStart || newConfig.exclusions.dateEnd) &&
12
+ newConfig.xAxis.dateParseFormat
13
+ ) {
14
+ // Filter dates
15
+ const timestamp = e => new Date(e).getTime()
16
+
17
+ let startDate = timestamp(newConfig.exclusions.dateStart)
18
+ let endDate = timestamp(newConfig.exclusions.dateEnd) + 86399999 //Increase by 24h in ms (86400000ms - 1ms) to include selected end date for .getTime() comparative
19
+
20
+ let startDateValid = undefined !== typeof startDate && false === isNaN(startDate)
21
+ let endDateValid = undefined !== typeof endDate && false === isNaN(endDate)
22
+
23
+ if (startDateValid && endDateValid) {
24
+ newExcludedData = data.filter(
25
+ e => timestamp(e[newConfig.xAxis.dataKey]) >= startDate && timestamp(e[newConfig.xAxis.dataKey]) <= endDate
26
+ )
27
+ } else if (startDateValid) {
28
+ newExcludedData = data.filter(e => timestamp(e[newConfig.xAxis.dataKey]) >= startDate)
29
+ } else if (endDateValid) {
30
+ newExcludedData = data.filter(e => timestamp(e[newConfig.xAxis.dataKey]) <= endDate)
31
+ }
32
+ } else {
33
+ newExcludedData = data
34
+ }
35
+ }
36
+ return newExcludedData
37
+ }
@@ -0,0 +1,7 @@
1
+ export default function getTopAxis(config) {
2
+ // When to show top axis
3
+ const hasTopAxis =
4
+ config.visualizationType === 'Bar' || config.visualizationType === 'Combo' || config.visualizationType === 'Line'
5
+
6
+ return { hasTopAxis }
7
+ }
@@ -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, formattedData, 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(formattedData?.map(data => data[xAxis.dataKey])).length <= 2
10
+ return lineWithLessThanThreePoints || isDynamicWithLessThanThreePoints
4
11
  }
@@ -1,8 +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
+ import { appFontSize } from '@cdc/core/helpers/cove/fontSettings'
4
5
  export const useBarChart = () => {
5
- const { config, colorPalettes, tableData, updateConfig, parseDate, formatDate, setSeriesHighlight, seriesHighlight } = useContext(ConfigContext)
6
+ const { config, colorPalettes, tableData, updateConfig, parseDate, formatDate, setSeriesHighlight, seriesHighlight } =
7
+ useContext(ConfigContext)
8
+ const dispatch = useContext(ChartDispatchContext)
6
9
  const { orientation } = config
7
10
  const [hoveredBar, setHoveredBar] = useState(null)
8
11
 
@@ -17,12 +20,22 @@ export const useBarChart = () => {
17
20
  const isRounded = config.barStyle === 'rounded'
18
21
  const isStacked = config.visualizationSubType === 'stacked'
19
22
  const tipRounding = config.tipRounding
20
- const radius = config.roundingStyle === 'standard' ? '8px' : config.roundingStyle === 'shallow' ? '5px' : config.roundingStyle === 'finger' ? '15px' : '0px'
23
+ const radius =
24
+ config.roundingStyle === 'standard'
25
+ ? '8px'
26
+ : config.roundingStyle === 'shallow'
27
+ ? '5px'
28
+ : config.roundingStyle === 'finger'
29
+ ? '15px'
30
+ : '0px'
21
31
  const stackCount = config.runtime.seriesKeys.length
22
- const fontSize = { small: 16, medium: 18, large: 20 }
23
32
  const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
24
- const isBarAndLegendIsolate = config.visualizationType === 'Bar' && config.legend.behavior === 'isolate' && config.legend.axisAlign
25
- const barStackedSeriesKeys = isBarAndLegendIsolate && seriesHighlight?.length ? seriesHighlight : config.runtime.barSeriesKeys || config.runtime.seriesKeys
33
+ const isBarAndLegendIsolate =
34
+ config.visualizationType === 'Bar' && config.legend.behavior === 'isolate' && config.legend.axisAlign
35
+ const barStackedSeriesKeys =
36
+ isBarAndLegendIsolate && seriesHighlight?.length
37
+ ? seriesHighlight
38
+ : config.runtime.barSeriesKeys || config.runtime.seriesKeys
26
39
 
27
40
  useEffect(() => {
28
41
  if (orientation === 'horizontal' && !config.yAxis.labelPlacement) {
@@ -68,7 +81,9 @@ export const useBarChart = () => {
68
81
  style = isHorizontal ? { borderRadius: `0 ${radius} ${radius} 0` } : { borderRadius: `${radius} ${radius} 0 0` }
69
82
  }
70
83
  if (!isStacked && index === -1) {
71
- style = isHorizontal ? { borderRadius: `${radius} 0 0 ${radius} ` } : { borderRadius: ` 0 0 ${radius} ${radius}` }
84
+ style = isHorizontal
85
+ ? { borderRadius: `${radius} 0 0 ${radius} ` }
86
+ : { borderRadius: ` 0 0 ${radius} ${radius}` }
72
87
  }
73
88
  if (tipRounding === 'full' && isStacked && index === 0 && stackCount > 1) {
74
89
  style = isHorizontal ? { borderRadius: `${radius} 0 0 ${radius}` } : { borderRadius: `0 0 ${radius} ${radius}` }
@@ -126,7 +141,7 @@ export const useBarChart = () => {
126
141
  barHeight = heights.stacked
127
142
  }
128
143
 
129
- const labelHeight = isLabelBelowBar ? fontSize[config.fontSize] * 1.2 : 0
144
+ const labelHeight = isLabelBelowBar ? appFontSize * 1.2 : 0
130
145
  let barSpace = Number(config.barSpace)
131
146
 
132
147
  // calculate height of container based height, space and fontSize of labels
@@ -178,9 +193,11 @@ export const useBarChart = () => {
178
193
  const columns = config.columns
179
194
  const columnsWithTooltips = []
180
195
  let additionalTooltipItems = ''
196
+ const dynamicCategorySeries = config.runtime?.series?.find(series => series?.dynamicCategory)
181
197
  const closestVal =
182
198
  tableData.find(d => {
183
- return d[config.xAxis.dataKey] === xAxisDataValue
199
+ const dynamicCategoryMatch = dynamicCategorySeries ? d[dynamicCategorySeries.dynamicCategory] === series : true
200
+ return d[config.xAxis.dataKey] === xAxisDataValue && dynamicCategoryMatch
184
201
  }) || {}
185
202
  Object.keys(columns).forEach(colKeys => {
186
203
  if (series && config.columns[colKeys].series && config.columns[colKeys].series !== series) return
@@ -191,7 +208,13 @@ export const useBarChart = () => {
191
208
  addColCommas: config.columns[colKeys].commas
192
209
  }
193
210
 
194
- const formattedValue = formatColNumber(closestVal[config.columns[colKeys].name], 'left', true, config, formattingParams)
211
+ const formattedValue = formatColNumber(
212
+ closestVal[config.columns[colKeys].name],
213
+ 'left',
214
+ true,
215
+ config,
216
+ formattingParams
217
+ )
195
218
  if (config.columns[colKeys].tooltips) {
196
219
  columnsWithTooltips.push([config.columns[colKeys].label, formattedValue])
197
220
  }
@@ -203,11 +226,16 @@ export const useBarChart = () => {
203
226
  }
204
227
 
205
228
  const onMouseOverBar = (categoryValue, barKey) => {
206
- 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
+
207
233
  setHoveredBar(categoryValue)
208
234
  }
209
235
  const onMouseLeaveBar = () => {
210
- 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
+ }
211
239
  }
212
240
 
213
241
  return {
@@ -225,7 +253,6 @@ export const useBarChart = () => {
225
253
  radius,
226
254
  stackCount,
227
255
  barStackedSeriesKeys,
228
- fontSize,
229
256
  hasMultipleSeries,
230
257
  applyRadius,
231
258
  updateBars,
@@ -1,7 +1,8 @@
1
1
  import React, { useContext } from 'react'
2
2
  import ConfigContext from '../ConfigContext'
3
+ import { ChartConfig } from '../types/ChartConfig'
3
4
 
4
- export const useHighlightedBars = (config, updateConfig) => {
5
+ export const useHighlightedBars = (config: ChartConfig, updateConfig: (config) => void) => {
5
6
  const { formatDate, parseDate } = useContext(ConfigContext)
6
7
 
7
8
  let highlightedSeries = [] // only allow single series for highlights
@@ -0,0 +1,37 @@
1
+ import { useEffect, useState, MutableRefObject } from 'react'
2
+
3
+ interface IntersectionObserverOptions {
4
+ threshold?: number | number[]
5
+ root?: Element | null
6
+ rootMargin?: string
7
+ freezeOnceVisible?: boolean
8
+ }
9
+
10
+ export default function useIntersectionObserver(
11
+ elementRef: MutableRefObject<Element | null>,
12
+ { threshold = 0, root = null, rootMargin = '0%', freezeOnceVisible = false }: IntersectionObserverOptions
13
+ ) {
14
+ const [entry, setEntry] = useState<IntersectionObserverEntry | undefined>()
15
+
16
+ const frozen = entry?.isIntersecting && freezeOnceVisible
17
+
18
+ const updateEntry = ([entry]: IntersectionObserverEntry[]) => {
19
+ setEntry(entry)
20
+ }
21
+
22
+ useEffect(() => {
23
+ const node = elementRef?.current
24
+ const hasIOSupport = !!window.IntersectionObserver
25
+
26
+ if (!hasIOSupport || frozen || !node) return
27
+
28
+ const observerParams = { threshold, root, rootMargin }
29
+ const observer = new IntersectionObserver(updateEntry, observerParams)
30
+
31
+ observer.observe(node)
32
+
33
+ return () => observer.disconnect()
34
+ }, [elementRef, threshold, root, rootMargin, frozen])
35
+
36
+ return entry
37
+ }
@@ -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
@@ -240,6 +239,10 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
240
239
  min = 0
241
240
  }
242
241
 
242
+ if (config.visualizationType === 'Scatter Plot') {
243
+ max = max * 1.1
244
+ }
245
+
243
246
  return { min, max, leftMax, rightMax }
244
247
  }
245
248
  export default useMinMax
@@ -11,7 +11,7 @@ function useReduceData(config, data) {
11
11
  }
12
12
  const getMaxValueFromData = () => {
13
13
  let max = Math.max(
14
- ...data.map(d =>
14
+ ...data?.map(d =>
15
15
  Math.max(
16
16
  ...config.runtime.seriesKeys.map(key => {
17
17
  const seriesKey = getSeriesKey(key)
@@ -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
@@ -143,11 +143,19 @@ export const useTooltip = props => {
143
143
  })
144
144
 
145
145
  if (visualizationType === 'Pie') {
146
+ const roundTo = Number(config.dataFormat.roundTo) || 0
147
+
148
+ const degrees = ((arc.endAngle - arc.startAngle) * 180) / Math.PI
149
+
150
+ // Calculate the percentage of the full circle (360 degrees)
151
+ const percentageOfCircle = (degrees / 360) * 100
152
+ const roundedPercentage = percentageOfCircle.toFixed(roundTo)
153
+
146
154
  tooltipItems.push(
147
155
  // ignore
148
156
  [config.xAxis.dataKey, pieChartData],
149
157
  [config.runtime.yAxis.dataKey, formatNumber(arc?.data[config.runtime.yAxis.dataKey])],
150
- ['Percent', `${Math.round((((arc?.endAngle - arc?.startAngle) * 180) / Math.PI / 360) * 100) + '%'}`]
158
+ ['Percent', `${roundedPercentage + '%'}`]
151
159
  )
152
160
  }
153
161
 
@@ -167,11 +175,16 @@ export const useTooltip = props => {
167
175
  ?.flatMap(seriesKey => {
168
176
  const value = resolvedScaleValues[0]?.[seriesKey]
169
177
  const formattedValue = getFormattedValue(seriesKey, value, config, getAxisPosition)
178
+ const seriesObjWithName = config.runtime.series.find(
179
+ series => series.dataKey === seriesKey && series.name !== undefined
180
+ )
170
181
  if (
171
182
  (value === null || value === undefined || value === '' || formattedValue === 'N/A') &&
172
183
  config.general.hideNullValue
173
184
  ) {
174
185
  return []
186
+ } else if (seriesObjWithName && seriesObjWithName.name === '') {
187
+ return [['', formattedValue, getAxisPosition(seriesKey)]]
175
188
  } else {
176
189
  return [[seriesKey, formattedValue, getAxisPosition(seriesKey)]]
177
190
  }
@@ -559,8 +572,14 @@ export const useTooltip = props => {
559
572
  if (index == 1 && config.dataFormat.onlyShowTopPrefixSuffix) {
560
573
  newValue = `${config.dataFormat.prefix}${newValue}${config.dataFormat.suffix}`
561
574
  }
575
+ const activeLabel = getSeriesNameFromLabel(key)
576
+ const displayText = activeLabel ? `${activeLabel}: ${newValue}` : newValue
562
577
 
563
- 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
+ )
564
583
  }
565
584
 
566
585
  return {
package/src/index.jsx CHANGED
@@ -4,6 +4,7 @@ import ReactDOM from 'react-dom/client'
4
4
  import CdcChart from './CdcChart'
5
5
  import './coreStyles_chart.scss'
6
6
 
7
+ import '@cdc/core/styles/cove-main.scss'
7
8
  import 'react-tooltip/dist/react-tooltip.css'
8
9
 
9
10
  let isEditor = window.location.href.includes('editor=true')
@@ -1,14 +1,9 @@
1
1
  .data-table-container {
2
- margin: 20px 0 0;
3
-
4
2
  &.brush-active {
5
3
  margin: 80px 0 0;
6
4
  }
7
5
 
8
6
  width: 100%;
9
- &.download-link-above {
10
- margin: 0;
11
- }
12
7
  }
13
8
 
14
9
  .data-table-container {
@@ -50,13 +50,6 @@
50
50
  overflow-y: auto;
51
51
  }
52
52
 
53
- .d-flex {
54
- display: flex;
55
- }
56
- .flex-column-reverse {
57
- flex-direction: column-reverse;
58
- }
59
-
60
53
  .cdc-open-viz-module.type-dashboard {
61
54
  .cdc-open-viz-module.type-chart.isEditor {
62
55
  .cdc-chart-inner-container {
@@ -140,7 +133,7 @@
140
133
  .subtext--responsive-ticks,
141
134
  .section-subtext {
142
135
  &--brush-active {
143
- margin-top: 3em !important;
136
+ margin-top: 3rem !important;
144
137
  }
145
138
  }
146
139
 
@@ -153,34 +146,39 @@
153
146
  .legend-container {
154
147
  background: #fff;
155
148
  width: 100%;
156
- padding: 15px;
157
149
  vertical-align: top;
158
150
  text-align: left;
159
- border: 1px solid var(--lightGray);
151
+ border: 1px solid var(--cool-gray-10);
152
+ border-radius: 6px;
160
153
  position: relative;
161
154
 
155
+ h3 {
156
+ font-size: var(--legend-title-font-size);
157
+ }
158
+
159
+ p {
160
+ font-size: var(--legend-description-font-size);
161
+ }
162
+
163
+ tspan,
164
+ div {
165
+ font-size: var(--legend-item-font-size);
166
+ }
167
+
162
168
  &.border-0 {
163
169
  border: 1px solid transparent;
164
170
  padding: 0;
165
171
  }
166
172
 
167
173
  &__inner {
174
+ display: flex;
175
+ flex-direction: column;
176
+ row-gap: var(--space-between-legend-item-rows);
177
+ column-gap: var(--space-between-legend-item-columns);
168
178
  &.double-column,
169
179
  &.single-row {
170
180
  display: grid;
171
181
  grid-template-columns: 1fr 1fr;
172
- grid-column-gap: 1.5em;
173
- }
174
-
175
- &.vertical-sorted {
176
- display: block;
177
-
178
- @include breakpoint(sm) {
179
- column-count: 2;
180
- column-width: 100%;
181
- }
182
- column-gap: 1.5em;
183
- column-fill: balance;
184
182
  }
185
183
 
186
184
  &.single-row {
@@ -192,13 +190,16 @@
192
190
  flex-basis: auto;
193
191
  }
194
192
  }
193
+
194
+ &.double-column.reverse-items div.legend-item:last-child {
195
+ margin-bottom: 0.2rem !important;
196
+ }
195
197
  }
196
198
 
197
199
  .legend-item {
198
200
  text-align: left;
199
201
  user-select: none;
200
- white-space: nowrap;
201
-
202
+ line-height: var(--legend-item-font-size);
202
203
  .visx-legend-label {
203
204
  word-wrap: break-word;
204
205
  white-space: pre-wrap;
@@ -212,27 +213,9 @@
212
213
 
213
214
  .legend-item > .legend-item {
214
215
  display: inline-block;
215
- margin-right: 0.5rem;
216
216
  flex: 0 0 auto;
217
217
  }
218
218
 
219
- h3 {
220
- font-size: 1.3rem;
221
- }
222
-
223
- h3,
224
- p {
225
- margin-bottom: 0.4em;
226
- }
227
-
228
- & div.legend-item {
229
- margin-bottom: 0.2em !important;
230
-
231
- &:last-child {
232
- margin: 0 !important;
233
- }
234
- }
235
-
236
219
  .legend-item {
237
220
  cursor: pointer;
238
221
  transition: 0.2s all;
@@ -241,6 +224,11 @@
241
224
  opacity: 0.5;
242
225
  transition: 0.2s all;
243
226
  }
227
+ &.highlighted {
228
+ outline: 1px solid #005ea2;
229
+ outline-offset: 5px;
230
+ border-radius: 1px;
231
+ }
244
232
  }
245
233
 
246
234
  &__outer {
@@ -261,10 +249,6 @@
261
249
  }
262
250
  }
263
251
 
264
- .legend-container__inner.flex-column-reverse div.legend-item:last-child {
265
- margin-bottom: 0.2rem !important;
266
- }
267
-
268
252
  .dynamic-legend-list {
269
253
  // overide traditional legend item that uses !important
270
254
  .legend-item {
@@ -297,7 +281,6 @@
297
281
  align-items: center;
298
282
  font-size: 1em;
299
283
  vertical-align: middle;
300
- margin-bottom: 0.5em;
301
284
 
302
285
  & > span {
303
286
  display: flex;
@@ -305,7 +288,7 @@
305
288
  align-items: center;
306
289
  white-space: nowrap;
307
290
  font-size: 1em;
308
- margin-right: 8px;
291
+ margin-right: 9px;
309
292
  max-height: 1px;
310
293
  }
311
294
 
@@ -483,32 +466,6 @@
483
466
  }
484
467
  }
485
468
 
486
- @include breakpointClass(xs) {
487
- &.font-small {
488
- font-size: 0.8em;
489
-
490
- .chart-container > svg {
491
- font-size: 12px;
492
- }
493
- }
494
-
495
- &.font-medium {
496
- font-size: 0.9em;
497
-
498
- .chart-container > svg {
499
- font-size: 14px;
500
- }
501
- }
502
-
503
- &.font-large {
504
- font-size: 1em;
505
-
506
- .chart-container > svg {
507
- font-size: 16px;
508
- }
509
- }
510
- }
511
-
512
469
  @include breakpointClass(sm) {
513
470
  .chart-container {
514
471
  .no-wrap {
@@ -547,48 +504,6 @@
547
504
  }
548
505
  }
549
506
  }
550
-
551
- &.font-small {
552
- font-size: 0.9em;
553
-
554
- .chart-container > svg {
555
- font-size: 14px;
556
- }
557
- }
558
-
559
- &.font-large {
560
- font-size: 1.1em;
561
-
562
- .chart-container > svg {
563
- font-size: 18px;
564
- }
565
- }
566
- }
567
-
568
- @include breakpointClass(lg) {
569
- &.font-small {
570
- font-size: 1em;
571
-
572
- .chart-container > svg {
573
- font-size: 16px;
574
- }
575
- }
576
-
577
- &.font-medium {
578
- font-size: 1.1em;
579
-
580
- .chart-container > svg {
581
- font-size: 18px;
582
- }
583
- }
584
-
585
- &.font-large {
586
- font-size: 1.2em;
587
-
588
- .chart-container > svg {
589
- font-size: 20px;
590
- }
591
- }
592
507
  }
593
508
 
594
509
  [tabindex]:focus-visible {