@cdc/chart 4.24.1 → 4.24.2

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 (58) hide show
  1. package/dist/cdcchart.js +30014 -29757
  2. package/examples/feature/line/line-chart-preliminary.json +84 -37
  3. package/examples/feature/regions/index.json +9 -5
  4. package/index.html +4 -2
  5. package/package.json +2 -2
  6. package/src/CdcChart.tsx +41 -24
  7. package/src/_stories/ChartEditor.stories.tsx +1 -1
  8. package/src/_stories/_mock/pie_config.json +4 -3
  9. package/src/components/AreaChart/components/AreaChart.jsx +1 -25
  10. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +7 -5
  11. package/src/components/BarChart/components/BarChart.Vertical.tsx +12 -13
  12. package/src/components/BoxPlot/BoxPlot.jsx +9 -8
  13. package/src/components/EditorPanel/EditorPanel.tsx +1563 -1959
  14. package/src/components/EditorPanel/EditorPanelContext.ts +40 -0
  15. package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +148 -0
  16. package/src/components/EditorPanel/components/{Panel.ForestPlotSettings.tsx → Panels/Panel.ForestPlotSettings.tsx} +16 -7
  17. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +160 -0
  18. package/src/components/EditorPanel/components/{Panel.Regions.tsx → Panels/Panel.Regions.tsx} +5 -5
  19. package/src/components/EditorPanel/components/{Panel.Series.tsx → Panels/Panel.Series.tsx} +4 -4
  20. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +297 -0
  21. package/src/components/EditorPanel/components/Panels/index.tsx +17 -0
  22. package/src/components/EditorPanel/editor-panel.scss +1 -13
  23. package/src/components/EditorPanel/useEditorPermissions.js +5 -0
  24. package/src/components/Legend/Legend.Component.tsx +199 -0
  25. package/src/components/Legend/Legend.tsx +5 -324
  26. package/src/components/Legend/helpers/createFormatLabels.tsx +140 -0
  27. package/src/components/LineChart/LineChartProps.ts +1 -1
  28. package/src/components/LineChart/components/LineChart.Circle.tsx +85 -52
  29. package/src/components/LineChart/helpers.ts +2 -2
  30. package/src/components/LineChart/index.tsx +97 -21
  31. package/src/components/LinearChart.jsx +3 -3
  32. package/src/components/PairedBarChart.jsx +4 -2
  33. package/src/components/PieChart/PieChart.tsx +78 -25
  34. package/src/components/Regions/components/Regions.tsx +14 -5
  35. package/src/data/initial-state.js +5 -2
  36. package/src/helpers/computeMarginBottom.ts +2 -2
  37. package/src/hooks/useHighlightedBars.js +1 -1
  38. package/src/hooks/useMinMax.ts +3 -3
  39. package/src/hooks/useScales.ts +18 -5
  40. package/src/hooks/useTooltip.tsx +11 -7
  41. package/src/scss/main.scss +0 -67
  42. package/src/types/ChartConfig.ts +17 -8
  43. package/src/types/ChartContext.ts +10 -4
  44. package/src/types/Label.ts +7 -0
  45. package/examples/private/chart-t.json +0 -3740
  46. package/examples/private/combo.json +0 -369
  47. package/examples/private/epi-data.csv +0 -13
  48. package/examples/private/epi-data.json +0 -62
  49. package/examples/private/epi.json +0 -403
  50. package/examples/private/occupancy.json +0 -109283
  51. package/examples/private/prod-line-config.json +0 -401
  52. package/examples/private/region-data.json +0 -822
  53. package/examples/private/region-testing.json +0 -312
  54. package/examples/private/scaling.json +0 -45325
  55. package/examples/private/testing-data.json +0 -1739
  56. package/examples/private/testing.json +0 -816
  57. package/src/components/EditorPanel/components/Panel.DateHighlighting.tsx +0 -109
  58. package/src/components/EditorPanel/components/Panels.tsx +0 -13
@@ -62,7 +62,11 @@ const LineChart = (props: LineChartProps) => {
62
62
  let displayArea = legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1
63
63
  const circleData = filterCircles(config.preliminaryData, rawData, seriesKey)
64
64
  // styles for preliminary Data items
65
- let styles = createStyles({ preliminaryData: config.preliminaryData, rawData, stroke: colorScale(config.runtime.seriesLabels[seriesKey]), handleLineType, lineType, seriesKey })
65
+ let styles = createStyles({ preliminaryData: config.preliminaryData, data: tableData, stroke: colorScale(config.runtime.seriesLabels[seriesKey]), handleLineType, lineType, seriesKey })
66
+
67
+ let xPos = d => {
68
+ return xScale(getXAxisData(d)) + (xScale.bandwidth ? xScale.bandwidth() / 2 : 0)
69
+ }
66
70
 
67
71
  return (
68
72
  <Group
@@ -91,12 +95,14 @@ const LineChart = (props: LineChartProps) => {
91
95
  <Bar key={'bars'} width={Number(xMax)} height={Number(yMax)} fill={DEBUG ? 'red' : 'transparent'} fillOpacity={0.05} onMouseMove={e => handleTooltipMouseOver(e, tableData)} onMouseOut={handleTooltipMouseOff} onClick={e => handleTooltipClick(e, data)} />
92
96
 
93
97
  {/* Render legend */}
94
- <Text display={config.labels ? 'block' : 'none'} x={xScale(getXAxisData(d))} y={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey))} fill={'#000'} textAnchor='middle'>
98
+ <Text display={config.labels ? 'block' : 'none'} x={xPos(d)} y={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(getYAxisData(d, seriesKey))} fill={'#000'} textAnchor='middle'>
95
99
  {formatNumber(d[seriesKey], 'left')}
96
100
  </Text>
97
101
 
98
102
  {(lineDatapointStyle === 'hidden' || lineDatapointStyle === 'always show') && (
99
103
  <LineChartCircle
104
+ mode='ALWAYS_SHOW_POINTS'
105
+ dataIndex={dataIndex}
100
106
  circleData={circleData}
101
107
  data={data}
102
108
  d={d}
@@ -113,34 +119,104 @@ const LineChart = (props: LineChartProps) => {
113
119
  key={`line-circle--${dataIndex}`}
114
120
  />
115
121
  )}
122
+
123
+ <LineChartCircle
124
+ mode='ISOLATED_POINTS'
125
+ dataIndex={dataIndex}
126
+ circleData={circleData}
127
+ data={data}
128
+ d={d}
129
+ config={config}
130
+ seriesKey={seriesKey}
131
+ displayArea={displayArea}
132
+ tooltipData={tooltipData}
133
+ xScale={xScale}
134
+ yScale={yScale}
135
+ colorScale={colorScale}
136
+ parseDate={parseDate}
137
+ yScaleRight={yScaleRight}
138
+ seriesAxis={seriesAxis}
139
+ key={`isolated-circle-${dataIndex}`}
140
+ />
116
141
  </Group>
117
142
  )
118
143
  )
119
144
  })}
120
145
  <>
121
146
  {lineDatapointStyle === 'hover' && (
122
- <LineChartCircle circleData={circleData} data={data} config={config} seriesKey={seriesKey} displayArea={displayArea} tooltipData={tooltipData} xScale={xScale} yScale={yScale} colorScale={colorScale} parseDate={parseDate} yScaleRight={yScaleRight} seriesAxis={seriesAxis} />
147
+ <LineChartCircle
148
+ dataIndex={0}
149
+ mode='HOVER_POINTS'
150
+ circleData={circleData}
151
+ data={data}
152
+ config={config}
153
+ seriesKey={seriesKey}
154
+ displayArea={displayArea}
155
+ tooltipData={tooltipData}
156
+ xScale={xScale}
157
+ yScale={yScale}
158
+ colorScale={colorScale}
159
+ parseDate={parseDate}
160
+ yScaleRight={yScaleRight}
161
+ seriesAxis={seriesAxis}
162
+ />
123
163
  )}
124
164
  </>
125
- {/* STANDARD LINE */}
126
- <LinePath
127
- curve={allCurves[seriesData[0].lineType]}
128
- data={data}
129
- x={d => xScale(getXAxisData(d))}
130
- y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
131
- stroke={colorScale(config.runtime.seriesLabels[seriesKey])}
132
- strokeWidth={2}
133
- strokeOpacity={1}
134
- shapeRendering='geometricPrecision'
135
- strokeDasharray={lineType ? handleLineType(lineType) : 0}
136
- defined={(item, i) => {
137
- return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
138
- }}
139
- />
165
+ {/* SPLIT LINE */}
166
+ {config?.preliminaryData?.some(d => d.value && d.column) ? (
167
+ <SplitLinePath
168
+ curve={allCurves[seriesData[0].lineType]}
169
+ segments={(config.xAxis.type === 'date' && config.xAxis.sortDates
170
+ ? data.sort((d1, d2) => {
171
+ let x1 = getXAxisData(d1)
172
+ let x2 = getXAxisData(d2)
173
+ if (x1 < x2) return -1
174
+ if (x2 < x1) return 1
175
+ return 0
176
+ })
177
+ : data
178
+ ).map(d => [d])}
179
+ segmentation='x'
180
+ x={d => xPos(d)}
181
+ y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
182
+ styles={styles}
183
+ defined={(item, i) => {
184
+ return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
185
+ }}
186
+ />
187
+ ) : (
188
+ <>
189
+ {/* STANDARD LINE */}
190
+ <LinePath
191
+ curve={allCurves[seriesData[0].lineType]}
192
+ data={
193
+ config.xAxis.type === 'date' && config.xAxis.sortDates
194
+ ? data.sort((d1, d2) => {
195
+ let x1 = getXAxisData(d1)
196
+ let x2 = getXAxisData(d2)
197
+ if (x1 < x2) return -1
198
+ if (x2 < x1) return 1
199
+ return 0
200
+ })
201
+ : data
202
+ }
203
+ x={d => xPos(d)}
204
+ y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
205
+ stroke={colorScale(config.runtime.seriesLabels[seriesKey])}
206
+ strokeWidth={2}
207
+ strokeOpacity={1}
208
+ shapeRendering='geometricPrecision'
209
+ strokeDasharray={lineType ? handleLineType(lineType) : 0}
210
+ defined={(item, i) => {
211
+ return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
212
+ }}
213
+ />
214
+ </>
215
+ )}
140
216
 
141
217
  {/* circles for preliminaryData data */}
142
218
  {circleData.map((d, i) => {
143
- return <circle key={i} cx={xScale(getXAxisData(d))} cy={yScale(Number(getYAxisData(d, seriesKey)))} r={6} strokeWidth={2} stroke={colorScale ? colorScale(config.runtime.seriesLabels[seriesKey]) : '#000'} fill='#fff' />
219
+ return <circle key={i} cx={xPos(d)} cy={yScale(Number(getYAxisData(d, seriesKey)))} r={6} strokeWidth={2} stroke={colorScale ? colorScale(config.runtime.seriesLabels[seriesKey]) : '#000'} fill='#fff' />
144
220
  })}
145
221
 
146
222
  {/* ANIMATED LINE */}
@@ -149,7 +225,7 @@ const LineChart = (props: LineChartProps) => {
149
225
  className='animation'
150
226
  curve={seriesData.lineType}
151
227
  data={data}
152
- x={d => xScale(getXAxisData(d))}
228
+ x={d => xPos(d)}
153
229
  y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
154
230
  stroke='#fff'
155
231
  strokeWidth={3}
@@ -175,7 +251,7 @@ const LineChart = (props: LineChartProps) => {
175
251
  return <></>
176
252
  }
177
253
  return (
178
- <text x={xScale(getXAxisData(lastDatum)) + 5} y={yScale(getYAxisData(lastDatum, seriesKey))} alignmentBaseline='middle' fill={config.colorMatchLineSeriesLabels && colorScale ? colorScale(config.runtime.seriesLabels[seriesKey] || seriesKey) : 'black'}>
254
+ <text x={xPos(lastDatum) + 5} y={yScale(getYAxisData(lastDatum, seriesKey))} alignmentBaseline='middle' fill={config.colorMatchLineSeriesLabels && colorScale ? colorScale(config.runtime.seriesLabels[seriesKey] || seriesKey) : 'black'}>
179
255
  {config.runtime.seriesLabels[seriesKey] || seriesKey}
180
256
  </text>
181
257
  )
@@ -80,8 +80,8 @@ const LinearChart = props => {
80
80
  yMax = yMax + config.data.length * config.forestPlot.rowHeight
81
81
  width = dimensions[0]
82
82
  }
83
- if (config.brush.active) {
84
- height = height + config.brush.height
83
+ if (config.brush?.active) {
84
+ height = height + config.brush?.height
85
85
  }
86
86
 
87
87
  // hooks % states
@@ -267,7 +267,7 @@ const LinearChart = props => {
267
267
  <Bar width={width} height={height} fill={'transparent'}></Bar> {/* Highlighted regions */}
268
268
  {/* Y axis */}
269
269
  {!['Spark Line', 'Forest Plot'].includes(visualizationType) && (
270
- <AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(runtime.yAxis.size) - config.yAxis.axisPadding} label={runtime.yAxis.label} stroke='#333' tickFormat={(tick, i) => handleLeftTickFormatting(tick, i)} numTicks={handleNumTicks()}>
270
+ <AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(runtime.yAxis.size) - config.yAxis.axisPadding} label={runtime.yAxis.yAxis?.label || runtime.yAxis.label} stroke='#333' tickFormat={(tick, i) => handleLeftTickFormatting(tick, i)} numTicks={handleNumTicks()}>
271
271
  {props => {
272
272
  const axisCenter = config.orientation === 'horizontal' ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
273
273
  const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
@@ -20,6 +20,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
20
20
  const groupOne = {
21
21
  parentKey: config.dataDescription.seriesKey,
22
22
  dataKey: config.series[0].dataKey,
23
+ dataKeyLabel: config.runtime.seriesLabels[config.series[0].dataKey] || config.series[0].dataKey,
23
24
  color: colorScale(config.runtime.seriesLabels[config.series[0].dataKey]),
24
25
  max: Math.max.apply(
25
26
  Math,
@@ -31,6 +32,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
31
32
  const groupTwo = {
32
33
  parentKey: config.dataDescription.seriesKey,
33
34
  dataKey: config.series[1].dataKey,
35
+ dataKeyLabel: config.runtime.seriesLabels[config.series[1].dataKey] || config.series[1].dataKey,
34
36
  color: colorScale(config.runtime.seriesLabels[config.series[1].dataKey]),
35
37
  max: Math.max.apply(
36
38
  Math,
@@ -59,7 +61,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
59
61
 
60
62
  const dataTipOne = d => {
61
63
  return `<p>
62
- ${config.dataDescription.seriesKey}: ${groupOne.dataKey}<br/>
64
+ ${config.dataDescription.seriesKey}: ${groupOne.dataKeyLabel}<br/>
63
65
  ${config.xAxis.dataKey}: ${d[config.xAxis.dataKey]}<br/>
64
66
  ${label}${formatNumber(d[groupOne.dataKey], 'left')}
65
67
  </p>`
@@ -67,7 +69,7 @@ const PairedBarChart = ({ width, height, originalWidth }) => {
67
69
 
68
70
  const dataTipTwo = d => {
69
71
  return `<p>
70
- ${config.dataDescription.seriesKey}: ${groupTwo.dataKey}<br/>
72
+ ${config.dataDescription.seriesKey}: ${groupTwo.dataKeyLabel}<br/>
71
73
  ${config.xAxis.dataKey}: ${d[config.xAxis.dataKey]}<br/>
72
74
  ${label}${formatNumber(d[groupTwo.dataKey], 'left')}
73
75
  </p>`
@@ -1,4 +1,4 @@
1
- import React, { useContext, useState, useEffect, useRef } from 'react'
1
+ import React, { useContext, useState, useEffect, useRef, useMemo } from 'react'
2
2
  import { animated, useTransition, interpolate } from 'react-spring'
3
3
  import chroma from 'chroma-js'
4
4
 
@@ -7,6 +7,7 @@ import { Pie } from '@visx/shape'
7
7
  import { Group } from '@visx/group'
8
8
  import { Text } from '@visx/text'
9
9
  import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
10
+ import { colorPalettesChart as colorPalettes } from '@cdc/core/data/colorPalettes'
10
11
 
11
12
  // cove
12
13
  import ConfigContext from '../../ConfigContext'
@@ -14,6 +15,9 @@ import { useTooltip as useCoveTooltip } from '../../hooks/useTooltip'
14
15
  import useIntersectionObserver from '../../hooks/useIntersectionObserver'
15
16
  import { handleChartAriaLabels } from '../../helpers/handleChartAriaLabels'
16
17
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
18
+ import LegendComponent from '../Legend/Legend.Component'
19
+ import { createFormatLabels } from '../Legend/helpers/createFormatLabels'
20
+ import { scaleOrdinal } from '@visx/scale'
17
21
 
18
22
  const enterUpdateTransition = ({ startAngle, endAngle }) => ({
19
23
  startAngle,
@@ -29,7 +33,7 @@ type TooltipData = {
29
33
  }
30
34
 
31
35
  const PieChart = props => {
32
- const { transformedData: data, config, dimensions, seriesHighlight, colorScale, currentViewport } = useContext(ConfigContext)
36
+ const { transformedData: data, config, colorScale, currentViewport, dimensions, highlight, highlightReset, seriesHighlight } = useContext(ConfigContext)
33
37
  const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip<TooltipData>()
34
38
  const { handleTooltipMouseOver, handleTooltipMouseOff, TooltipListItem } = useCoveTooltip({
35
39
  xScale: false,
@@ -39,6 +43,50 @@ const PieChart = props => {
39
43
  })
40
44
  const [filteredData, setFilteredData] = useState(undefined)
41
45
  const [animatedPie, setAnimatePie] = useState(false)
46
+ const pivotColumns = Object.values(config.columns).filter(column => column.showInViz)
47
+ const dataNeedsPivot = pivotColumns.length > 0
48
+ const pivotKey = dataNeedsPivot ? 'pivotColumn' : undefined
49
+ const _data = useMemo(() => {
50
+ if (dataNeedsPivot) {
51
+ let newData = []
52
+ const primaryColumn = config.yAxis.dataKey
53
+ const additionalColumns = pivotColumns.map(column => column.name)
54
+ const allColumns = [primaryColumn, ...additionalColumns]
55
+ const columnToUpdate = config.xAxis.dataKey
56
+ data.forEach(d => {
57
+ allColumns.forEach(col => {
58
+ const data = d[col]
59
+ if (data) {
60
+ newData.push({
61
+ [pivotKey]: data,
62
+ [columnToUpdate]: `${d[columnToUpdate]} - ${col}`
63
+ })
64
+ }
65
+ })
66
+ })
67
+ return newData
68
+ }
69
+ return data
70
+ }, [data, dataNeedsPivot])
71
+
72
+ const _colorScale = useMemo(() => {
73
+ if (dataNeedsPivot) {
74
+ const keys = {}
75
+ _data.forEach(d => {
76
+ if (!keys[d[config.xAxis.dataKey]]) keys[d[config.xAxis.dataKey]] = true
77
+ })
78
+ const numberOfKeys = Object.entries(keys).length
79
+ let palette = config.customColors || colorPalettes[config.palette]
80
+ palette = palette.slice(0, numberOfKeys)
81
+
82
+ return scaleOrdinal({
83
+ domain: Object.keys(keys),
84
+ range: palette,
85
+ unknown: null
86
+ })
87
+ }
88
+ return colorScale
89
+ }, [colorScale, dataNeedsPivot])
42
90
 
43
91
  const triggerRef = useRef()
44
92
  const dataRef = useIntersectionObserver(triggerRef, {
@@ -96,7 +144,7 @@ const PieChart = props => {
96
144
  endAngle
97
145
  })
98
146
  )}
99
- fill={colorScale(arc.data[config.runtime.xAxis.dataKey])}
147
+ fill={_colorScale(arc.data[config.runtime.xAxis.dataKey])}
100
148
  onMouseEnter={e => handleTooltipMouseOver(e, { data: arc.data[config.runtime.xAxis.dataKey], arc })}
101
149
  onMouseLeave={e => handleTooltipMouseOff()}
102
150
  />
@@ -108,7 +156,7 @@ const PieChart = props => {
108
156
  const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.1
109
157
 
110
158
  let textColor = '#FFF'
111
- if (colorScale(arc.data[config.runtime.xAxis.dataKey]) && chroma.contrast(textColor, colorScale(arc.data[config.runtime.xAxis.dataKey])) < 3.5) {
159
+ if (_colorScale(arc.data[config.runtime.xAxis.dataKey]) && chroma.contrast(textColor, _colorScale(arc.data[config.runtime.xAxis.dataKey])) < 3.5) {
112
160
  textColor = '000'
113
161
  }
114
162
 
@@ -143,7 +191,7 @@ const PieChart = props => {
143
191
  if (seriesHighlight.length > 0 && config.legend.behavior !== 'highlight') {
144
192
  let newFilteredData = []
145
193
 
146
- data.forEach(d => {
194
+ _data.forEach(d => {
147
195
  if (seriesHighlight.indexOf(d[config.runtime.xAxis.dataKey]) !== -1) {
148
196
  newFilteredData.push(d)
149
197
  }
@@ -155,33 +203,38 @@ const PieChart = props => {
155
203
  }
156
204
  }, [seriesHighlight]) // eslint-disable-line
157
205
 
206
+ const createLegendLabels = createFormatLabels(config, [], _data, _colorScale)
207
+
158
208
  return (
159
- <ErrorBoundary component='PieChart'>
160
- <svg width={width} height={height} className={`animated-pie group ${config.animate === false || animatedPie ? 'animated' : ''}`} role='img' aria-label={handleChartAriaLabels(config)}>
161
- <Group top={centerY} left={centerX}>
162
- {/* prettier-ignore */}
163
- <Pie
164
- data={filteredData || data}
165
- pieValue={d => d[config.runtime.yAxis.dataKey]}
209
+ <>
210
+ <ErrorBoundary component='PieChart'>
211
+ <svg width={width} height={height} className={`animated-pie group ${config.animate === false || animatedPie ? 'animated' : ''}`} role='img' aria-label={handleChartAriaLabels(config)}>
212
+ <Group top={centerY} left={centerX}>
213
+ {/* prettier-ignore */}
214
+ <Pie
215
+ data={filteredData || _data}
216
+ pieValue={d => d[pivotKey || config.runtime.yAxis.dataKey]}
166
217
  pieSortValues={() => -1}
167
218
  innerRadius={radius - donutThickness}
168
219
  outerRadius={radius}
169
220
  >
170
221
  {pie => <AnimatedPie {...pie} getKey={d => d.data[config.runtime.xAxis.dataKey]}/>}
171
222
  </Pie>
172
- </Group>
173
- </svg>
174
- <div ref={triggerRef} />
175
- {tooltipData && Object.entries(tooltipData.data).length > 0 && tooltipOpen && showTooltip && tooltipData.dataYPosition && tooltipData.dataXPosition && (
176
- <>
177
- <style>{`.tooltip {background-color: rgba(255,255,255, ${config.tooltips.opacity / 100}) !important`}</style>
178
- <TooltipWithBounds key={Math.random()} className={'tooltip cdc-open-viz-module'} left={tooltipLeft} top={tooltipTop}>
179
- <ul>{typeof tooltipData === 'object' && Object.entries(tooltipData.data).map((item, index) => <TooltipListItem item={item} key={index} />)}</ul>
180
- </TooltipWithBounds>
181
- </>
182
- )}
183
- {/* <ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' /> */}
184
- </ErrorBoundary>
223
+ </Group>
224
+ </svg>
225
+ <div ref={triggerRef} />
226
+ {tooltipData && Object.entries(tooltipData.data).length > 0 && tooltipOpen && showTooltip && tooltipData.dataYPosition && tooltipData.dataXPosition && (
227
+ <>
228
+ <style>{`.tooltip {background-color: rgba(255,255,255, ${config.tooltips.opacity / 100}) !important`}</style>
229
+ <TooltipWithBounds key={Math.random()} className={'tooltip cdc-open-viz-module'} left={tooltipLeft} top={tooltipTop}>
230
+ <ul>{typeof tooltipData === 'object' && Object.entries(tooltipData.data).map((item, index) => <TooltipListItem item={item} key={index} />)}</ul>
231
+ </TooltipWithBounds>
232
+ </>
233
+ )}
234
+ {/* <ReactTooltip id={`cdc-open-viz-tooltip-${config.runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' /> */}
235
+ </ErrorBoundary>
236
+ <LegendComponent config={config} colorScale={_colorScale} seriesHighlight={seriesHighlight} highlight={highlight} highlightReset={highlightReset} currentViewport={currentViewport} formatLabels={createLegendLabels} />
237
+ </>
185
238
  )
186
239
  }
187
240
 
@@ -5,11 +5,10 @@ import { ChartContext } from '../../../types/ChartContext'
5
5
  import { Text } from '@visx/text'
6
6
  import { Group } from '@visx/group'
7
7
  import * as d3 from 'd3'
8
+ import { formatDate } from '@cdc/core/helpers/cove/date.js'
8
9
 
9
10
  type RegionsProps = {
10
11
  xScale: Function
11
- barWidth: number
12
- totalBarsInGroup: number
13
12
  yMax: number
14
13
  barWidth?: number
15
14
  totalBarsInGroup?: number
@@ -60,9 +59,9 @@ const Regions = ({ xScale, barWidth = 0, totalBarsInGroup = 1, yMax, handleToolt
60
59
 
61
60
  let previousDays = Number(region.from)
62
61
  let lastDate = region.toType === 'Last Date' ? domain[domain.length - 1] : region.to
63
- let fromDate = new Date(lastDate)
62
+ let toDate = new Date(lastDate)
64
63
 
65
- from = new Date(fromDate.setDate(fromDate.getDate() - previousDays)).getTime()
64
+ from = new Date(toDate.setDate(toDate.getDate() - previousDays)).getTime()
66
65
  let targetValue = from
67
66
 
68
67
  let index = bisectDate(domain, targetValue)
@@ -75,7 +74,6 @@ const Regions = ({ xScale, barWidth = 0, totalBarsInGroup = 1, yMax, handleToolt
75
74
  let d1 = domain[index]
76
75
  closestValue = targetValue - d0 > d1 - targetValue ? d1 : d0
77
76
  }
78
-
79
77
  from = Number(xScale(closestValue) - (visualizationType === 'Bar' || visualizationType === 'Combo' ? (barWidth * totalBarsInGroup) / 2 : 0))
80
78
 
81
79
  width = to - from
@@ -89,6 +87,17 @@ const Regions = ({ xScale, barWidth = 0, totalBarsInGroup = 1, yMax, handleToolt
89
87
  width = to - from
90
88
  }
91
89
 
90
+ if (region.fromType === 'Previous Days' && xAxis.type === 'date' && xAxis.sortDates && config.visualizationType === 'Line') {
91
+ let domain = xScale.domain()
92
+ let previousDays = Number(region.from)
93
+ let to = region.toType === 'Last Date' ? formatDate(config.xAxis.dateParseFormat, domain[domain.length - 1]) : region.to
94
+ let toDate = new Date(to)
95
+ from = new Date(toDate.setDate(toDate.getDate() - previousDays)).getTime()
96
+ from = xScale(from)
97
+ to = xScale(parseDate(to))
98
+ width = to - from
99
+ }
100
+
92
101
  if (!from) return null
93
102
  if (!to) return null
94
103
 
@@ -53,6 +53,7 @@ export default {
53
53
  rightAxisTickColor: '#333',
54
54
  numTicks: '',
55
55
  axisPadding: 0,
56
+ scalePadding: 10,
56
57
  tickRotation: 0,
57
58
  anchors: []
58
59
  },
@@ -129,7 +130,8 @@ export default {
129
130
  showDataTableLink: true,
130
131
  indexLabel: '',
131
132
  download: false,
132
- showVertical: true
133
+ showVertical: true,
134
+ dateDisplayFormat: ''
133
135
  },
134
136
  orientation: 'vertical',
135
137
  color: 'pinkpurple',
@@ -191,7 +193,8 @@ export default {
191
193
  series: [],
192
194
  tooltips: {
193
195
  opacity: 90,
194
- singleSeries: false
196
+ singleSeries: false,
197
+ dateDisplayFormat: ''
195
198
  },
196
199
  forestPlot: {
197
200
  startAt: 0,
@@ -4,9 +4,9 @@ export const computeMarginBottom = (config: ChartConfig, legend: Legend, current
4
4
  const isLegendBottom = legend.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(currentViewport)
5
5
  const isHorizontal = config.orientation === 'horizontal'
6
6
  const tickRotation = Number(config.xAxis.tickRotation) > 0 ? Number(config.xAxis.tickRotation) : 0
7
- const isBrush = config.brush.active
7
+ const isBrush = config?.brush?.active
8
8
  const offset = 20
9
- const brushHeight = config.brush.height
9
+ const brushHeight = config?.brush?.height
10
10
  let bottom = 0
11
11
  if (!isLegendBottom && isHorizontal && !config.yAxis.label) {
12
12
  bottom = Number(config.xAxis.labelOffset)
@@ -127,7 +127,7 @@ export const useHighlightedBars = (config, updateConfig) => {
127
127
  */
128
128
  HighLightedBarUtils.findDuplicates = arr => {
129
129
  const duplicates = {}
130
- const result = arr.filter(obj => {
130
+ const result = arr?.filter(obj => {
131
131
  const { legendLabel } = obj
132
132
  if (!duplicates[legendLabel]) {
133
133
  duplicates[legendLabel] = true
@@ -181,10 +181,10 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
181
181
  if (config.yAxis.enablePadding) {
182
182
  if (min < 0) {
183
183
  // sets with negative data need more padding on the max
184
- max *= 1.2
185
- min *= 1.2
184
+ max *= 1 + (config.yAxis.scalePadding * 2) / 100
185
+ min *= 1 + (config.yAxis.scalePadding * 2) / 100
186
186
  } else {
187
- max *= 1.1
187
+ max *= 1 + config.yAxis.scalePadding / 100
188
188
  }
189
189
  }
190
190
 
@@ -56,21 +56,25 @@ const useScales = (properties: useScaleProps) => {
56
56
 
57
57
  // handle Vertical bars
58
58
  if (!isHorizontal) {
59
- xScaleBrush = composeScalePoint(xAxisDataKeysMapped, [0, xMax], 0.5)
60
- xScale = composeScalePoint(xAxisDataMapped, [0, xMax], 0.5)
61
- xScale.type = scaleTypes.POINT
59
+ xScaleBrush = composeScalePoint(xAxisDataKeysMapped, [0, xMax], .5)
60
+ xScale = composeScaleBand(xAxisDataMapped, [0, xMax], 1 - config.barThickness)
62
61
  yScale = composeYScale(properties)
63
- seriesScale = composeScalePoint(seriesDomain, [0, xMax])
62
+ seriesScale = composeScaleBand(seriesDomain, [0, xScale.bandwidth()], 0)
64
63
  }
65
64
 
66
65
  // handle Area chart
67
66
  if (config.xAxis.type === 'date' && config.xAxis.sortDates) {
67
+ let xAxisMin = Math.min(...xAxisDataMapped)
68
+ let xAxisMax = Math.max(...xAxisDataMapped)
69
+ xAxisMin -= (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
70
+ xAxisMax += (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
68
71
  xScale = scaleTime({
69
- domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)],
72
+ domain: [xAxisMin, xAxisMax],
70
73
  range: [0, xMax]
71
74
  })
72
75
  xScaleBrush = xScale
73
76
  xScale.type = scaleTypes.LINEAR
77
+ seriesScale = composeScaleBand(seriesDomain, [0, config.barThickness * (xMax)], 0)
74
78
  }
75
79
 
76
80
  // handle Deviation bar
@@ -284,3 +288,12 @@ const composeScalePoint = (domain, range, padding = 0) => {
284
288
  type: 'point'
285
289
  })
286
290
  }
291
+
292
+ const composeScaleBand = (domain, range, padding = 0) => {
293
+ return scaleBand({
294
+ domain: domain,
295
+ range: range,
296
+ padding: padding,
297
+ type: 'band'
298
+ })
299
+ }
@@ -11,9 +11,9 @@ const transform = new DataTransform()
11
11
  import { formatNumber as formatColNumber } from '@cdc/core/helpers/cove/number'
12
12
 
13
13
  export const useTooltip = props => {
14
- const { tableData, config, formatNumber, capitalize, formatDate, parseDate, setSharedFilter } = useContext<ChartContext>(ConfigContext)
14
+ const { tableData, config, formatNumber, capitalize, formatDate, formatTooltipsDate, parseDate, setSharedFilter } = useContext<ChartContext>(ConfigContext)
15
15
  const { xScale, yScale, showTooltip, hideTooltip } = props
16
- const { xAxis, visualizationType, orientation, yAxis, runtime, barWidth } = config
16
+ const { xAxis, visualizationType, orientation, yAxis, runtime } = config
17
17
  const data = transform.applySuppression(tableData, config.suppressedData)
18
18
  /**
19
19
  * Provides the tooltip information based on the tooltip data array and svg cursor coordinates
@@ -227,10 +227,12 @@ export const useTooltip = props => {
227
227
  }
228
228
 
229
229
  if (config.xAxis.type === 'categorical' || (visualizationType === 'Combo' && orientation !== 'horizontal' && visualizationType !== 'Forest Plot')) {
230
- let eachBand = xScale.step()
230
+ let range = xScale.range()[1] - xScale.range()[0]
231
+ let eachBand = range / (xScale.domain().length + 1)
232
+
231
233
  let numerator = x
232
- const index = Math.floor(Number(numerator) / eachBand)
233
- return xScale.domain()[index - 1] // fixes off by 1 error
234
+ const index = Math.floor((Number(numerator) - eachBand / 2) / eachBand)
235
+ return xScale.domain()[index] // fixes off by 1 error
234
236
  }
235
237
 
236
238
  if (config.xAxis.type === 'date' && visualizationType !== 'Combo' && orientation !== 'horizontal') {
@@ -426,10 +428,12 @@ export const useTooltip = props => {
426
428
  if (key === config.xAxis.dataKey) return <li className='tooltip-heading'>{`${capitalize(config.xAxis.dataKey ? `${config.xAxis.dataKey}: ` : '')} ${config.yAxis.type === 'date' ? formatDate(parseDate(key, false)) : value}`}</li>
427
429
  return <li className='tooltip-body'>{`${getSeriesNameFromLabel(key)}: ${formatNumber(value, 'left')}`}</li>
428
430
  }
431
+ const formattedDate = config.tooltips.dateDisplayFormat ? formatTooltipsDate(parseDate(value, false)) : formatDate(parseDate(value, false))
429
432
 
430
433
  // TOOLTIP HEADING
431
- if (visualizationType === 'Bar' && orientation === 'horizontal' && key === config.xAxis.dataKey) return <li className='tooltip-heading'>{`${capitalize(config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ` : '')} ${value}`}</li>
432
- if (key === config.xAxis.dataKey) return <li className='tooltip-heading'>{`${capitalize(config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ` : '')} ${config.xAxis.type === 'date' ? value : value}`}</li>
434
+ if (visualizationType === 'Bar' && orientation === 'horizontal' && key === config.xAxis.dataKey) return <li className='tooltip-heading'>{`${capitalize(config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ` : '')} ${config.xAxis.type === 'date' ? formattedDate : value}`}</li>
435
+
436
+ if (key === config.xAxis.dataKey) return <li className='tooltip-heading'>{`${capitalize(config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ` : '')} ${config.xAxis.type === 'date' ? formattedDate : value}`}</li>
433
437
 
434
438
  // TOOLTIP BODY
435
439
  return <li className='tooltip-body'>{`${getSeriesNameFromLabel(key)}: ${value}`}</li>