@cdc/chart 4.26.3 → 4.26.5

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 (192) hide show
  1. package/CONFIG.md +489 -0
  2. package/README.md +42 -17
  3. package/dist/cdcchart.js +39024 -36587
  4. package/examples/feature/bar/additional-column-tooltip.json +3 -3
  5. package/examples/feature/deviation/planet-deviation-config.json +2 -2
  6. package/examples/feature/heatmap/average-age-categorical.json +313 -0
  7. package/examples/feature/heatmap/calendar-heatmap.json +134 -0
  8. package/examples/feature/heatmap/cell-values-bottom-axis.json +181 -0
  9. package/examples/feature/heatmap/sparse-aggregation.json +158 -0
  10. package/examples/feature/paired-bar/paired-bar-example.json +2 -2
  11. package/examples/feature/tests-case-rate/case-rate-example-config.json +2 -2
  12. package/examples/feature/tests-covid/covid-confidence-example-config.json +2 -2
  13. package/examples/feature/tests-covid/covid-example-config.json +2 -2
  14. package/examples/feature/tests-cutoff/cutoff-example-config.json +2 -2
  15. package/examples/feature/tests-non-numerics/planet-pie-example-config-nonnumeric.json +2 -2
  16. package/examples/metadata-variables.json +1 -1
  17. package/examples/minimal-example.json +24 -0
  18. package/examples/private/bump-chart.json +1116 -0
  19. package/examples/private/test-123.json +624 -0
  20. package/examples/private/test2.json +35012 -0
  21. package/package.json +5 -4
  22. package/src/CdcChartComponent.tsx +144 -60
  23. package/src/_stories/Chart.AltText.stories.tsx +136 -0
  24. package/src/_stories/{Chart.HTMLInDataTable.stories.tsx → Chart.HTMLInDataTable.smoke.stories.tsx} +10 -0
  25. package/src/_stories/Chart.HeatMap.stories.tsx +187 -0
  26. package/src/_stories/Chart.Minimal.smoke.stories.tsx +25 -0
  27. package/src/_stories/Chart.Sankey.smoke.stories.tsx +62 -0
  28. package/src/_stories/Chart.SmallMultiples.smoke.stories.tsx +108 -0
  29. package/src/_stories/{Chart.stories.tsx → Chart.smoke.stories.tsx} +12 -0
  30. package/src/_stories/{Chart.tooltip.stories.tsx → Chart.tooltip.smoke.stories.tsx} +68 -0
  31. package/src/_stories/{ChartAxisTitles.stories.tsx → ChartAxisTitles.smoke.stories.tsx} +13 -0
  32. package/src/_stories/ChartBar.Editor.stories.tsx +151 -85
  33. package/src/_stories/ChartBrush.Interaction.stories.tsx +112 -0
  34. package/src/_stories/{ChartBrush.stories.tsx → ChartBrush.smoke.stories.tsx} +13 -4
  35. package/src/_stories/ChartEditor.Editor.stories.tsx +23 -0
  36. package/src/_stories/ChartLine.DashedDates.smoke.stories.tsx +23 -0
  37. package/src/_stories/_mock/alt_text_metadata.json +38 -0
  38. package/src/_stories/_mock/heatmap-average-age-categorical.json +315 -0
  39. package/src/_stories/_mock/heatmap.json +156 -0
  40. package/src/_stories/_mock/line_chart_dashed_dates.json +276 -0
  41. package/src/_stories/_mock/paired-bar-abbr.json +1 -1
  42. package/src/_stories/_mock/paired-bar.json +1 -1
  43. package/src/_stories/_mock/reported_pertussis_cases.json +1643 -0
  44. package/src/_stories/_mock/sankey_mock.json +78 -0
  45. package/src/_stories/_mock/small_multiples/small_multiples_lines.json +1 -1
  46. package/src/components/Annotations/components/AnnotationDraggable.tsx +2 -2
  47. package/src/components/Annotations/components/AnnotationDropdown.tsx +5 -2
  48. package/src/components/AreaChart/components/AreaChart.Stacked.jsx +10 -2
  49. package/src/components/Axis/BottomAxis.tsx +1 -1
  50. package/src/components/Axis/LeftAxis.tsx +15 -14
  51. package/src/components/Axis/LeftAxisGridlines.tsx +14 -13
  52. package/src/components/BarChart/components/BarChart.Horizontal.tsx +9 -5
  53. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +9 -5
  54. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +9 -5
  55. package/src/components/BarChart/components/BarChart.Vertical.tsx +9 -6
  56. package/src/components/BarChart/components/BarChart.tsx +2 -6
  57. package/src/components/BarChart/helpers/useBarChart.ts +1 -1
  58. package/src/components/BoxPlot/BoxPlot.Vertical.tsx +3 -3
  59. package/src/components/Brush/BrushSelector.tsx +88 -187
  60. package/src/components/Brush/brushInteraction.ts +22 -0
  61. package/src/components/Brush/tests/brushInteraction.test.ts +38 -0
  62. package/src/components/EditorPanel/EditorPanel.tsx +946 -960
  63. package/src/components/EditorPanel/EditorPanelContext.ts +1 -1
  64. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +229 -165
  65. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +105 -13
  66. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +169 -116
  67. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +159 -109
  68. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
  69. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +14 -28
  70. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -1
  71. package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +57 -0
  72. package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +56 -0
  73. package/src/components/EditorPanel/useEditorPermissions.test.tsx +70 -0
  74. package/src/components/EditorPanel/useEditorPermissions.ts +32 -21
  75. package/src/components/Forecasting/Forecasting.tsx +2 -2
  76. package/src/components/HeatMap/components/HeatMap.tsx +762 -0
  77. package/src/components/HeatMap/components/HeatMapGradientLegend.tsx +114 -0
  78. package/src/components/HeatMap/heatmap.constants.ts +13 -0
  79. package/src/components/HeatMap/heatmap.css +71 -0
  80. package/src/components/HeatMap/helpers/index.ts +261 -0
  81. package/src/components/HeatMap/index.tsx +5 -0
  82. package/src/components/HeatMap/tests/HeatMap.test.tsx +1040 -0
  83. package/src/components/HeatMap/tests/helpers.test.ts +167 -0
  84. package/src/components/HorizonChart/HorizonChart.tsx +10 -2
  85. package/src/components/HoverLine/HoverLine.tsx +6 -8
  86. package/src/components/Legend/Legend.Component.tsx +4 -4
  87. package/src/components/Legend/LegendGroup/LegendGroup.styles.css +5 -0
  88. package/src/components/Legend/LegendGroup/LegendGroup.tsx +3 -1
  89. package/src/components/Legend/helpers/index.ts +0 -2
  90. package/src/components/LineChart/LineChartProps.ts +1 -0
  91. package/src/components/LineChart/components/LineChart.BumpCircle.tsx +22 -24
  92. package/src/components/LineChart/helpers.test.ts +345 -0
  93. package/src/components/LineChart/helpers.ts +15 -5
  94. package/src/components/LineChart/index.tsx +18 -5
  95. package/src/components/LinearChart/VisualizationRenderer.tsx +11 -1
  96. package/src/components/LinearChart/tests/LinearChart.test.tsx +228 -7
  97. package/src/components/LinearChart/tests/mockConfigContext.ts +1 -0
  98. package/src/components/LinearChart.tsx +155 -77
  99. package/src/components/PairedBarChart.jsx +2 -2
  100. package/src/components/PieChart/PieChart.tsx +15 -10
  101. package/src/components/RadarChart/RadarChart.tsx +3 -1
  102. package/src/components/Regions/components/Regions.tsx +199 -35
  103. package/src/components/Sankey/components/Sankey.tsx +7 -3
  104. package/src/components/Sankey/helpers/getSankeyTooltip.tsx +7 -6
  105. package/src/components/ScatterPlot/ScatterPlot.jsx +19 -17
  106. package/src/components/SmallMultiples/SmallMultipleTile.tsx +31 -3
  107. package/src/components/SmallMultiples/SmallMultiples.css +12 -0
  108. package/src/components/SmallMultiples/SmallMultiples.tsx +15 -9
  109. package/src/components/SmallMultiples/tests/SmallMultipleTile.test.tsx +152 -0
  110. package/src/components/Sparkline/components/SparkLine.tsx +5 -10
  111. package/src/components/WarmingStripes/WarmingStripes.tsx +5 -3
  112. package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +1 -1
  113. package/src/components/WarmingStripes/WarmingStripesGradientLegend.tsx +2 -2
  114. package/src/data/initial-state.js +12 -0
  115. package/src/helpers/axisLabelFontSize.ts +8 -0
  116. package/src/helpers/bumpChartTooltip.ts +54 -0
  117. package/src/helpers/calculateLeftYAxisWidth.ts +197 -0
  118. package/src/helpers/ensureSpecialChartAxisTypes.ts +11 -0
  119. package/src/helpers/getMinMax.ts +2 -3
  120. package/src/helpers/getYAxisAutoPadding.ts +8 -6
  121. package/src/helpers/getYAxisAutoPaddingMode.ts +17 -0
  122. package/src/helpers/handleChartAriaLabels.ts +23 -10
  123. package/src/helpers/seriesHighlight.ts +26 -0
  124. package/src/helpers/tests/axisLabelFontSize.test.ts +15 -0
  125. package/src/helpers/tests/bumpChartTooltip.test.ts +75 -0
  126. package/src/helpers/tests/ensureSpecialChartAxisTypes.test.ts +30 -0
  127. package/src/helpers/tests/getYAxisAutoPadding.test.ts +37 -0
  128. package/src/helpers/tests/getYAxisAutoPaddingMode.test.ts +90 -0
  129. package/src/helpers/tests/handleChartAriaLabels.test.ts +71 -0
  130. package/src/helpers/tests/seriesHighlight.test.ts +61 -0
  131. package/src/helpers/tests/tooltipHelpers.test.ts +151 -0
  132. package/src/helpers/tooltipHelpers.ts +176 -0
  133. package/src/hooks/tests/useScales.test.tsx +105 -0
  134. package/src/hooks/useHighlightedBars.ts +9 -16
  135. package/src/hooks/useProgrammaticTooltip.ts +16 -2
  136. package/src/hooks/useScales.ts +19 -9
  137. package/src/hooks/useSmallMultipleSynchronization.ts +3 -4
  138. package/src/hooks/useTooltip.tsx +167 -38
  139. package/src/index.jsx +5 -2
  140. package/src/scss/main.scss +32 -49
  141. package/src/test/CdcChart.test.jsx +24 -0
  142. package/src/types/ChartConfig.ts +19 -0
  143. package/src/types/ChartContext.ts +2 -0
  144. package/examples/chart-regression-2.json +0 -2360
  145. package/examples/feature/__data__/age-adjusted-rates.json +0 -1486
  146. package/examples/feature/__data__/city-temperature.json +0 -2198
  147. package/examples/feature/bar/Barchart_with_negative.json +0 -34
  148. package/examples/feature/boxplot/box-plot-data.json +0 -71
  149. package/examples/feature/boxplot/boxplot-data.json +0 -156
  150. package/examples/feature/combo/combochart-categories_are_numbers .json +0 -18
  151. package/examples/feature/forest-plot/log.json +0 -26
  152. package/examples/feature/tests-date-exclusions/date-exclusions-data.json +0 -162
  153. package/examples/region-issue.json +0 -2065
  154. package/examples/xaxis.json +0 -493
  155. package/src/_stories/Chart.SmallMultiples.stories.tsx +0 -63
  156. /package/examples/{data → __data__}/data-with-metadata.json +0 -0
  157. /package/examples/feature/bar/{tall-data.json → __data__/tall-data.json} +0 -0
  158. /package/examples/feature/deviation/{planet-deviation-data.json → __data__/planet-deviation-data.json} +0 -0
  159. /package/examples/feature/paired-bar/{paired-bar-data.json → __data__/paired-bar-data.json} +0 -0
  160. /package/examples/feature/paired-bar/{paired-bar-formatted.json → __data__/paired-bar-formatted.json} +0 -0
  161. /package/examples/feature/tests-case-rate/{case-rate-example-data.json → __data__/case-rate-example-data.json} +0 -0
  162. /package/examples/feature/tests-covid/{covid-example-data-confidence.json → __data__/covid-example-data-confidence.json} +0 -0
  163. /package/examples/feature/tests-covid/{covid-example-data.json → __data__/covid-example-data.json} +0 -0
  164. /package/examples/feature/tests-cutoff/{cutoff-example-data.json → __data__/cutoff-example-data.json} +0 -0
  165. /package/examples/feature/tests-non-numerics/{planet-example-data-nonnumeric.json → __data__/planet-example-data-nonnumeric.json} +0 -0
  166. /package/src/_stories/{Chart.Anchors.stories.tsx → Chart.Anchors.smoke.stories.tsx} +0 -0
  167. /package/src/_stories/{Chart.BoxPlot.stories.tsx → Chart.BoxPlot.smoke.stories.tsx} +0 -0
  168. /package/src/_stories/{Chart.CI.stories.tsx → Chart.CI.smoke.stories.tsx} +0 -0
  169. /package/src/_stories/{Chart.Combo.stories.tsx → Chart.Combo.smoke.stories.tsx} +0 -0
  170. /package/src/_stories/{Chart.CustomColors.stories.tsx → Chart.CustomColors.smoke.stories.tsx} +0 -0
  171. /package/src/_stories/{Chart.Defaults.stories.tsx → Chart.Defaults.smoke.stories.tsx} +0 -0
  172. /package/src/_stories/{Chart.DynamicSeries.stories.tsx → Chart.DynamicSeries.smoke.stories.tsx} +0 -0
  173. /package/src/_stories/{Chart.Filters.stories.tsx → Chart.Filters.smoke.stories.tsx} +0 -0
  174. /package/src/_stories/{Chart.Forecast.stories.tsx → Chart.Forecast.smoke.stories.tsx} +0 -0
  175. /package/src/_stories/{Chart.Legend.Gradient.stories.tsx → Chart.Legend.Gradient.smoke.stories.tsx} +0 -0
  176. /package/src/_stories/{Chart.Patterns.stories.tsx → Chart.Patterns.smoke.stories.tsx} +0 -0
  177. /package/src/_stories/{Chart.PreserveDecimals.stories.tsx → Chart.PreserveDecimals.smoke.stories.tsx} +0 -0
  178. /package/src/_stories/{Chart.Regions.Categorical.stories.tsx → Chart.Regions.Categorical.smoke.stories.tsx} +0 -0
  179. /package/src/_stories/{Chart.Regions.DateScale.stories.tsx → Chart.Regions.DateScale.smoke.stories.tsx} +0 -0
  180. /package/src/_stories/{Chart.Regions.DateTimeScale.stories.tsx → Chart.Regions.DateTimeScale.smoke.stories.tsx} +0 -0
  181. /package/src/_stories/{Chart.ScatterPlot.stories.tsx → Chart.ScatterPlot.smoke.stories.tsx} +0 -0
  182. /package/src/_stories/{Chart.SmallestLeftAxisMax.stories.tsx → Chart.SmallestLeftAxisMax.smoke.stories.tsx} +0 -0
  183. /package/src/_stories/{ChartAnnotation.stories.tsx → ChartAnnotation.smoke.stories.tsx} +0 -0
  184. /package/src/_stories/{ChartAxisLabels.stories.tsx → ChartAxisLabels.smoke.stories.tsx} +0 -0
  185. /package/src/_stories/{ChartBrush.Matrix.Continuous.stories.tsx → ChartBrush.Matrix.Continuous.smoke.stories.tsx} +0 -0
  186. /package/src/_stories/{ChartBrush.Matrix.Date.stories.tsx → ChartBrush.Matrix.Date.smoke.stories.tsx} +0 -0
  187. /package/src/_stories/{ChartBrush.Matrix.DateTime.stories.tsx → ChartBrush.Matrix.DateTime.smoke.stories.tsx} +0 -0
  188. /package/src/_stories/{ChartLine.QuadrantAngles.stories.tsx → ChartLine.QuadrantAngles.smoke.stories.tsx} +0 -0
  189. /package/src/_stories/{ChartLine.Suppression.stories.tsx → ChartLine.Suppression.smoke.stories.tsx} +0 -0
  190. /package/src/_stories/{ChartLine.Symbols.stories.tsx → ChartLine.Symbols.smoke.stories.tsx} +0 -0
  191. /package/src/_stories/{ChartPrefixSuffix.stories.tsx → ChartPrefixSuffix.smoke.stories.tsx} +0 -0
  192. /package/src/_stories/{TechAdoptionWithLinks.stories.tsx → TechAdoptionWithLinks.smoke.stories.tsx} +0 -0
@@ -8,13 +8,21 @@ import { isDateScale } from '@cdc/core/helpers/cove/date'
8
8
  // Third-party library imports
9
9
  import { localPoint } from '@visx/event'
10
10
  import { bisector } from 'd3-array'
11
- import _, { get } from 'lodash'
11
+ import _ from 'lodash'
12
12
  import { getHorizontalBarHeights } from '../components/BarChart/helpers/getBarHeights'
13
13
  import {
14
14
  findColumnConfigByName,
15
15
  getSeriesColumnFormattingParams,
16
16
  getSeriesOwnedColumnNames
17
17
  } from '../helpers/seriesColumnSettings'
18
+ import {
19
+ buildTooltipRow,
20
+ getTooltipSeriesMarker,
21
+ tooltipHasMarkerColumn,
22
+ type TooltipRow
23
+ } from '../helpers/tooltipHelpers'
24
+ import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
25
+ import { getVizSubType, getVizTitle } from '@cdc/core/helpers/metrics/utils'
18
26
 
19
27
  export const useTooltip = props => {
20
28
  // Track the last X-axis value to prevent duplicate analytics events
@@ -22,6 +30,7 @@ export const useTooltip = props => {
22
30
  const {
23
31
  tableData: data,
24
32
  config,
33
+ colorScale,
25
34
  formatNumber,
26
35
  capitalize,
27
36
  formatDate,
@@ -33,7 +42,6 @@ export const useTooltip = props => {
33
42
  const { xScale, yScale, seriesScale, showTooltip, hideTooltip, interactionLabel = '' } = props
34
43
  const { xAxis, visualizationType, orientation, yAxis, runtime } = config
35
44
  const seriesOwnedColumnNames = getSeriesOwnedColumnNames(config.series)
36
-
37
45
  // Track the latest xScale in a ref to prevent stale closures
38
46
  const xScaleRef = useRef(xScale)
39
47
 
@@ -42,7 +50,7 @@ export const useTooltip = props => {
42
50
  xScaleRef.current = xScale
43
51
  }, [xScale])
44
52
 
45
- const Y_AXIS_SIZE = Number(config.yAxis.size || 0)
53
+ const Y_AXIS_SIZE = Number(props.yAxisWidth || 0)
46
54
 
47
55
  // function handles only Single series hovered data tooltips
48
56
  const findDataKeyByThreshold = (mouseY, datapoint) => {
@@ -85,11 +93,38 @@ export const useTooltip = props => {
85
93
  const formattedValue =
86
94
  seriesKey === config.xAxis.dataKey
87
95
  ? value
88
- : formatColNumber(value, getAxisPosition(seriesKey), true, config, formattingParams)
96
+ : formatColNumber(value, getAxisPosition(seriesKey), false, config, formattingParams)
89
97
 
90
98
  return showMissingDataValue ? 'N/A' : formattedValue
91
99
  }
92
100
 
101
+ const createTooltipRow = ({
102
+ key,
103
+ value,
104
+ kind,
105
+ axisPosition,
106
+ seriesKey
107
+ }: {
108
+ key: string
109
+ value: string | number
110
+ kind: TooltipRow['kind']
111
+ axisPosition?: string
112
+ seriesKey?: string
113
+ }): TooltipRow =>
114
+ (() => {
115
+ const marker = kind === 'series' ? getTooltipSeriesMarker(config, colorScale, seriesKey || key) : null
116
+
117
+ return buildTooltipRow({
118
+ key,
119
+ value,
120
+ axisPosition,
121
+ kind,
122
+ seriesKey,
123
+ markerColor: marker?.markerColor ?? null,
124
+ markerShape: marker?.markerShape
125
+ })
126
+ })()
127
+
93
128
  /**
94
129
  * Handles the mouse over event for the tooltip.
95
130
  * @function handleTooltipMouseOver
@@ -108,7 +143,7 @@ export const useTooltip = props => {
108
143
  const singleSeriesValue = getYValueFromCoordinate(y, resolvedScaleValues)
109
144
 
110
145
  const columnsWithTooltips = []
111
- const tooltipItems = [] as any[][]
146
+ const tooltipItems: TooltipRow[] = []
112
147
  for (const [colKey, column] of Object.entries(config.columns)) {
113
148
  const columnName = column.name || colKey
114
149
  if (seriesOwnedColumnNames.includes(columnName)) continue
@@ -129,16 +164,22 @@ export const useTooltip = props => {
129
164
  : resolvedScaleValues[0]?.[colKey]
130
165
  const closestValue = config.visualizationType === 'Pie' ? pieColumnData : columnData
131
166
 
132
- const formattedValue = formatColNumber(closestValue, 'left', true, config, formattingParams)
167
+ const formattedValue = formatColNumber(closestValue, 'left', false, config, formattingParams)
133
168
 
134
169
  if (column.tooltips) {
135
170
  columnsWithTooltips.push([column.label, formattedValue])
136
171
  }
137
172
  }
138
- const additionalTooltipItems = [] as [string, string | number][]
173
+ const additionalTooltipItems: TooltipRow[] = []
139
174
 
140
175
  columnsWithTooltips.forEach(columnData => {
141
- additionalTooltipItems.push([columnData[0], columnData[1]])
176
+ additionalTooltipItems.push(
177
+ createTooltipRow({
178
+ key: columnData[0],
179
+ value: columnData[1],
180
+ kind: 'extra'
181
+ })
182
+ )
142
183
  })
143
184
 
144
185
  if (visualizationType === 'Pie') {
@@ -155,21 +196,46 @@ export const useTooltip = props => {
155
196
  const showPiePercent = config.dataFormat.showPiePercent || false
156
197
 
157
198
  if (showPiePercent && pieData[config.xAxis.dataKey] === 'Calculated Area') {
158
- tooltipItems.push(['', 'Calculated Area'])
199
+ tooltipItems.push(
200
+ createTooltipRow({
201
+ key: '',
202
+ value: 'Calculated Area',
203
+ kind: 'heading'
204
+ })
205
+ )
159
206
  } else {
160
207
  tooltipItems.push(
161
- [config.xAxis.dataKey, pieData[config.xAxis.dataKey]],
162
- [
163
- config.runtime.yAxis.label || config.runtime.yAxis.dataKey,
164
- showPiePercent ? pctString(actualPieValue) : formatNumber(pieData[config.runtime.yAxis.dataKey])
165
- ],
166
- showPiePercent ? [] : ['Percent', pctString(pctOf360)]
208
+ createTooltipRow({
209
+ key: config.xAxis.dataKey,
210
+ value: pieData[config.xAxis.dataKey],
211
+ kind: 'heading'
212
+ }),
213
+ createTooltipRow({
214
+ key: config.runtime.yAxis.label || config.runtime.yAxis.dataKey,
215
+ value: showPiePercent ? pctString(actualPieValue) : formatNumber(pieData[config.runtime.yAxis.dataKey]),
216
+ kind: 'extra'
217
+ })
167
218
  )
219
+ if (!showPiePercent) {
220
+ tooltipItems.push(
221
+ createTooltipRow({
222
+ key: 'Percent',
223
+ value: pctString(pctOf360),
224
+ kind: 'extra'
225
+ })
226
+ )
227
+ }
168
228
  }
169
229
  }
170
230
 
171
231
  if (visualizationType === 'Forest Plot') {
172
- tooltipItems.push([config.xAxis.dataKey, getClosestYValue(y)])
232
+ tooltipItems.push(
233
+ createTooltipRow({
234
+ key: config.xAxis.dataKey,
235
+ value: getClosestYValue(y),
236
+ kind: 'heading'
237
+ })
238
+ )
173
239
  }
174
240
 
175
241
  const isLinearChart = !['Pie', 'Forest Plot'].includes(visualizationType)
@@ -205,9 +271,34 @@ export const useTooltip = props => {
205
271
  ) {
206
272
  return []
207
273
  } else if (seriesObjWithName && seriesObjWithName.name === '') {
208
- return [['', formattedValue, getAxisPosition(seriesKey)]]
274
+ return [
275
+ createTooltipRow({
276
+ key: '',
277
+ value: formattedValue,
278
+ axisPosition: getAxisPosition(seriesKey),
279
+ kind: 'series',
280
+ seriesKey
281
+ })
282
+ ]
283
+ } else if (config.xAxis?.dataKey == seriesKey) {
284
+ return [
285
+ createTooltipRow({
286
+ key: seriesKey,
287
+ value: formattedValue,
288
+ axisPosition: getAxisPosition(seriesKey),
289
+ kind: 'heading'
290
+ })
291
+ ]
209
292
  } else {
210
- return [[seriesKey, formattedValue, getAxisPosition(seriesKey)]]
293
+ return [
294
+ createTooltipRow({
295
+ key: seriesKey,
296
+ value: formattedValue,
297
+ axisPosition: getAxisPosition(seriesKey),
298
+ kind: 'series',
299
+ seriesKey
300
+ })
301
+ ]
211
302
  }
212
303
  })
213
304
  )
@@ -247,7 +338,15 @@ export const useTooltip = props => {
247
338
  if (resolvedScaleValue) {
248
339
  const value = resolvedScaleValue[series.originalDataKey]
249
340
  const formattedValue = getFormattedValue(seriesKey, value, config, getAxisPosition)
250
- tooltipItems.push([seriesKey, formattedValue, getAxisPosition(seriesKey)])
341
+ tooltipItems.push(
342
+ createTooltipRow({
343
+ key: seriesKey,
344
+ value: formattedValue,
345
+ axisPosition: getAxisPosition(seriesKey),
346
+ kind: 'series',
347
+ seriesKey
348
+ })
349
+ )
251
350
  }
252
351
  }
253
352
  })
@@ -261,12 +360,31 @@ export const useTooltip = props => {
261
360
  const xVal = dataColumn[config.xAxis.dataKey]
262
361
  const closestXScaleValue = getXValueFromCoordinate(x - Y_AXIS_SIZE)
263
362
 
264
- tooltipItems.push([config.xAxis.dataKey, closestXScaleValue || xVal])
363
+ tooltipItems.push(
364
+ createTooltipRow({
365
+ key: config.xAxis.dataKey,
366
+ value: closestXScaleValue || xVal,
367
+ kind: 'heading'
368
+ })
369
+ )
265
370
  const formattedValue = getFormattedValue(seriesKey, value, config, getAxisPosition)
266
- tooltipItems.push([seriesKey, formattedValue])
371
+ tooltipItems.push(
372
+ createTooltipRow({
373
+ key: seriesKey,
374
+ value: formattedValue,
375
+ kind: 'series',
376
+ seriesKey
377
+ })
378
+ )
267
379
  } else if (dynamicSeries) {
268
380
  Object.keys(dataColumn).forEach(key => {
269
- tooltipItems.push([key, dataColumn[key]])
381
+ tooltipItems.push(
382
+ createTooltipRow({
383
+ key,
384
+ value: dataColumn[key],
385
+ kind: key === config.xAxis.dataKey ? 'heading' : 'extra'
386
+ })
387
+ )
270
388
  })
271
389
  }
272
390
  }
@@ -283,16 +401,11 @@ export const useTooltip = props => {
283
401
  }
284
402
 
285
403
  // Strip link tags from all tooltip values
286
- const cleanTooltipItems = [...tooltipItems, ...additionalTooltipItems].map(item => {
287
- // item can be [key, value] or [key, value, axisPosition]
288
- if (Array.isArray(item)) {
289
- // Only strip from value (item[1])
290
- const newItem = [...item]
291
- newItem[1] = stripLinkTags(newItem[1])
292
- return newItem
293
- }
294
- return item
295
- })
404
+ const cleanTooltipItems = [...tooltipItems, ...additionalTooltipItems].map(item => ({
405
+ ...item,
406
+ value: stripLinkTags(item.value)
407
+ }))
408
+ const useMarkerColumn = tooltipHasMarkerColumn(cleanTooltipItems)
296
409
 
297
410
  const tooltipInformation = {
298
411
  tooltipLeft: dataXPosition,
@@ -300,7 +413,8 @@ export const useTooltip = props => {
300
413
  tooltipData: {
301
414
  data: cleanTooltipItems,
302
415
  dataXPosition,
303
- dataYPosition
416
+ dataYPosition,
417
+ useMarkerColumn
304
418
  }
305
419
  }
306
420
  showTooltip(tooltipInformation)
@@ -654,9 +768,8 @@ export const useTooltip = props => {
654
768
  return originalColumnName
655
769
  }
656
770
 
657
- const TooltipListItem = ({ item }) => {
658
- const [index, additionalData] = item
659
- const [key, value, axisPosition] = additionalData
771
+ const TooltipListItem = ({ row, index, useMarkerColumn = false }) => {
772
+ const { key, value, axisPosition, kind, markerColor, markerShape = 'circle' } = row
660
773
 
661
774
  if (visualizationType === 'Forest Plot') {
662
775
  if (key === config.xAxis.dataKey)
@@ -718,10 +831,26 @@ export const useTooltip = props => {
718
831
  }
719
832
  const activeLabel = getSeriesNameFromLabel(key)
720
833
  const displayText = activeLabel ? `${activeLabel}: ${newValue}` : newValue
834
+ const shouldRenderMarkerSlot = useMarkerColumn && kind !== 'heading'
835
+ const markerSlot = shouldRenderMarkerSlot ? (
836
+ <span className='tooltip-marker-slot' aria-hidden='true'>
837
+ {markerColor ? (
838
+ <span
839
+ className={`tooltip-marker-swatch tooltip-marker-swatch--${markerShape}`}
840
+ style={{ backgroundColor: markerColor }}
841
+ />
842
+ ) : null}
843
+ </span>
844
+ ) : null
845
+ const content = displayText !== undefined ? parse(String(displayText)) : displayText
721
846
 
722
847
  return (
723
- <li style={style} className='tooltip-body mb-1'>
724
- {displayText !== undefined ? parse(String(displayText)) : displayText}
848
+ <li
849
+ style={style}
850
+ className={`tooltip-body mb-1 cove-prose ${shouldRenderMarkerSlot ? 'tooltip-body--marker-layout' : ''}`}
851
+ >
852
+ {markerSlot}
853
+ {shouldRenderMarkerSlot ? <span className='tooltip-body-content'>{content}</span> : content}
725
854
  </li>
726
855
  )
727
856
  }
package/src/index.jsx CHANGED
@@ -9,12 +9,15 @@ let isEditor = window.location.href.includes('editor=true')
9
9
  let isDebug = window.location.href.includes('debug=true')
10
10
 
11
11
  let domContainer = document.getElementsByClassName('react-container')[0]
12
+ let configUrl = domContainer.dataset.configUrl
13
+ let injectedConfig = domContainer.coveConfig
12
14
 
13
15
  ReactDOM.createRoot(domContainer).render(
14
16
  <React.StrictMode>
15
17
  <CdcChart
16
- interactionLabel={domContainer.attributes['data-config']?.value}
17
- configUrl={domContainer.attributes['data-config'].value}
18
+ interactionLabel={configUrl}
19
+ config={injectedConfig}
20
+ configUrl={injectedConfig ? undefined : configUrl}
18
21
  isEditor={isEditor}
19
22
  isDebug={isDebug}
20
23
  />
@@ -1,5 +1,6 @@
1
1
  @import '@cdc/core/styles/utils/breakpoints';
2
2
  @import '@cdc/core/styles/layout/wrapper-padding';
3
+ @import '@cdc/core/styles/layout/callout';
3
4
 
4
5
  .form-container {
5
6
  overflow-y: auto;
@@ -24,33 +25,17 @@
24
25
  background: transparent;
25
26
 
26
27
  .cdc-callout {
27
- box-shadow: 0 2px 4px rgb(159 159 159 / 10%);
28
- border: 1px solid #dff2f6;
29
- margin: 0;
30
- padding: 1.25rem;
31
- border-radius: 0.25rem;
32
- position: relative;
33
- background: transparent;
34
-
35
- .cdc-callout__flag {
36
- position: absolute;
37
- top: -0.36rem;
38
- right: 1.08rem;
39
- width: 1.84rem;
40
- height: auto;
41
- }
28
+ --cdc-callout-background: transparent;
42
29
 
43
30
  .cove-visualization__title,
44
31
  .cove-visualization__header {
45
32
  background: transparent;
46
33
  border: 0;
47
- color: var(--cool-gray-90, #1f2937);
48
34
  margin: 0 0 1rem;
49
35
  padding: 0;
50
36
  border-radius: 0;
51
37
 
52
38
  h2 {
53
- color: var(--cool-gray-90, #1f2937);
54
39
  font-family: var(--fonts-nunito, var(--app-font-secondary));
55
40
  font-size: 1.1rem;
56
41
  font-weight: 700;
@@ -64,11 +49,17 @@
64
49
  margin-bottom: 0 !important;
65
50
  }
66
51
 
52
+ .y-axis-top-title {
53
+ font-family: var(--fonts-nunito, var(--app-font-secondary));
54
+ font-weight: 700;
55
+ line-height: 1.4;
56
+ margin: 0 0 0.5rem;
57
+ }
58
+
67
59
  .legend-top {
68
60
  .legend-wrapper .tooltip-boundary {
69
61
  display: flex;
70
62
  flex-wrap: wrap;
71
- row-gap: var(--cove-visualization-section-gap, 1.5rem);
72
63
  order: 2;
73
64
 
74
65
  aside {
@@ -188,7 +179,7 @@
188
179
  white-space: nowrap;
189
180
  }
190
181
 
191
- .legend-item>.legend-item {
182
+ .legend-item > .legend-item {
192
183
  display: inline-block;
193
184
  flex: 0 0 auto;
194
185
  }
@@ -222,11 +213,11 @@
222
213
  left: 0%;
223
214
  top: 108%;
224
215
 
225
- &>* {
216
+ & > * {
226
217
  margin: 0;
227
218
  }
228
219
 
229
- &>p,
220
+ & > p,
230
221
  a {
231
222
  margin-left: 5px;
232
223
  }
@@ -235,7 +226,6 @@
235
226
  }
236
227
 
237
228
  .dynamic-legend-list {
238
-
239
229
  // overide traditional legend item that uses !important
240
230
  .legend-item {
241
231
  align-items: flex-end !important;
@@ -268,7 +258,7 @@
268
258
  font-size: 1em;
269
259
  vertical-align: middle;
270
260
 
271
- &>span {
261
+ & > span {
272
262
  display: flex;
273
263
  justify-items: center;
274
264
  align-items: center;
@@ -278,25 +268,25 @@
278
268
  max-height: 1px;
279
269
  }
280
270
 
281
- &>span[class*='Asterisk'] {
271
+ & > span[class*='Asterisk'] {
282
272
  transform: scale(1.8);
283
273
  margin-top: 15px;
284
274
  }
285
275
 
286
- &>span[class*='Dagger'] {
276
+ & > span[class*='Dagger'] {
287
277
  margin-top: 2px;
288
278
  }
289
279
 
290
- &>span[class*='Sign'] {
280
+ & > span[class*='Sign'] {
291
281
  margin-top: 1px;
292
282
  }
293
283
 
294
- &>svg {
284
+ & > svg {
295
285
  width: 50px;
296
286
  height: 23px;
297
287
  }
298
288
 
299
- &>p {
289
+ & > p {
300
290
  margin: 0;
301
291
  }
302
292
  }
@@ -325,8 +315,8 @@
325
315
  min-width: 0;
326
316
  max-width: 100%;
327
317
 
328
- >div,
329
- >aside {
318
+ > div,
319
+ > aside {
330
320
  min-width: 0;
331
321
  max-width: 100%;
332
322
  }
@@ -339,7 +329,7 @@
339
329
 
340
330
  &.legend-top:not(.legend-hidden) {
341
331
  .legend-wrapper {
342
- &>div {
332
+ & > div {
343
333
  order: 2;
344
334
  width: 100%;
345
335
  }
@@ -354,7 +344,7 @@
354
344
 
355
345
  &.legend-left:not(.legend-hidden) {
356
346
  .legend-wrapper {
357
- &>div {
347
+ & > div {
358
348
  order: 2;
359
349
  width: 75%;
360
350
  }
@@ -369,7 +359,7 @@
369
359
 
370
360
  &.legend-right:not(.legend-hidden) {
371
361
  .legend-wrapper {
372
- &>div {
362
+ & > div {
373
363
  order: 1;
374
364
  width: 75%;
375
365
  }
@@ -391,7 +381,7 @@
391
381
  flex-wrap: nowrap;
392
382
  flex-direction: column-reverse;
393
383
 
394
- &>div.tooltip-boundary {
384
+ & > div.tooltip-boundary {
395
385
  margin-top: 1.5em;
396
386
  }
397
387
  }
@@ -401,14 +391,10 @@
401
391
  flex-direction: column;
402
392
  }
403
393
 
404
- &.legend-hidden>svg {
394
+ &.legend-hidden > svg {
405
395
  width: 100% !important;
406
396
  }
407
397
 
408
- &.dashboard-brush {
409
- margin-bottom: 2.5em;
410
- }
411
-
412
398
  // Brush touch support
413
399
  .brush-overlay {
414
400
  touch-action: none;
@@ -434,7 +420,7 @@
434
420
  pointer-events: none;
435
421
  }
436
422
 
437
- >svg {
423
+ > svg {
438
424
  overflow: visible;
439
425
  font-size: 14px;
440
426
  margin: 1rem 0 2rem;
@@ -449,7 +435,7 @@
449
435
  }
450
436
 
451
437
  &.chart-line--hover {
452
- >svg circle {
438
+ > svg circle {
453
439
  opacity: 0;
454
440
 
455
441
  &:hover {
@@ -459,12 +445,12 @@
459
445
  }
460
446
 
461
447
  &.chart-line--always {
462
- >svg circle {
448
+ > svg circle {
463
449
  opacity: 1;
464
450
  }
465
451
 
466
452
  // Animations for line chart and data points
467
- >svg.animated {
453
+ > svg.animated {
468
454
  circle {
469
455
  opacity: 0;
470
456
  animation: revealLolly 0.25s linear forwards;
@@ -525,7 +511,7 @@
525
511
 
526
512
  @include breakpointClass(md) {
527
513
  .visualization-container {
528
- >svg {
514
+ > svg {
529
515
  font-size: 16px;
530
516
  width: 75%;
531
517
  order: 1;
@@ -608,7 +594,6 @@
608
594
  }
609
595
 
610
596
  &.animated {
611
-
612
597
  .vertical path,
613
598
  .vertical rect,
614
599
  .vertical foreignObject div {
@@ -626,7 +611,6 @@
626
611
  }
627
612
 
628
613
  &.animate {
629
-
630
614
  path,
631
615
  rect,
632
616
  foreignObject div {
@@ -685,7 +669,7 @@
685
669
  text-align: center;
686
670
  transform: scale(1);
687
671
 
688
- >.visx-group[style] {
672
+ > .visx-group[style] {
689
673
  transform: scale(1);
690
674
  }
691
675
  }
@@ -707,7 +691,7 @@
707
691
  }
708
692
 
709
693
  .cove-visualization.type-chart.type-sparkline {
710
- &.lg .visualization-container>svg {
694
+ &.lg .visualization-container > svg {
711
695
  width: 100%;
712
696
  }
713
697
 
@@ -738,7 +722,6 @@
738
722
  transform: translate(-20px, 0);
739
723
  }
740
724
  }
741
-
742
725
  }
743
726
 
744
727
  .subtext,
@@ -1,11 +1,35 @@
1
1
  import path from 'node:path'
2
+ import fs from 'node:fs'
3
+ import vm from 'node:vm'
2
4
  import { testStandaloneBuild } from '@cdc/core/helpers/tests/testStandaloneBuild.ts'
3
5
  import { describe, it, expect } from 'vitest'
4
6
 
7
+ const extractMarkedExampleConfig = (content, label) => {
8
+ const match = content.match(
9
+ /<!-- README_EXAMPLE_CONFIG_START -->\s*```jsx\s*([\s\S]*?)\s*```\s*<!-- README_EXAMPLE_CONFIG_END -->/
10
+ )
11
+ expect(match, `${label} should contain a marked README example block`).toBeTruthy()
12
+ const configMatch = match[1].match(/const config = (\{[\s\S]*?\})\n\nfunction App\(\)/)
13
+ expect(configMatch, `${label} should define const config before function App()`).toBeTruthy()
14
+ return vm.runInNewContext(`(${configMatch[1]})`)
15
+ }
16
+
5
17
  describe('Chart', () => {
6
18
  it('Can be built in isolation', async () => {
7
19
  const pkgDir = path.join(__dirname, '..')
8
20
  const result = await testStandaloneBuild(pkgDir)
9
21
  expect(result).toBe(true)
10
22
  }, 300000)
23
+
24
+ it('keeps the minimal example in sync with the README docs', () => {
25
+ const pkgRoot = path.join(__dirname, '..', '..')
26
+ const minimalExamplePath = path.join(pkgRoot, 'examples', 'minimal-example.json')
27
+ const readmePath = path.join(pkgRoot, 'README.md')
28
+
29
+ const minimalExample = JSON.parse(fs.readFileSync(minimalExamplePath, 'utf8'))
30
+ const readmeBlock = extractMarkedExampleConfig(fs.readFileSync(readmePath, 'utf8'), 'README.md')
31
+
32
+ expect(readmeBlock).toEqual(minimalExample)
33
+ expect(minimalExample.version).toBeTruthy()
34
+ })
11
35
  })