@cdc/chart 4.24.1 → 4.24.3
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.
- package/dist/cdcchart.js +48948 -37923
- package/examples/{private/combo.json → chart-regression-1.json} +40 -31
- package/examples/chart-regression-2.json +2360 -0
- package/examples/feature/filters/url-filter.json +1076 -0
- package/examples/feature/line/line-chart-preliminary.json +84 -37
- package/examples/feature/line/line-chart.json +2 -1
- package/examples/feature/regions/index.json +55 -5
- package/examples/feature/sankey/sankey-example-data.json +1364 -0
- package/examples/feature/sankey/sankey_chart_data.csv +20 -0
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +306 -19
- package/examples/sparkline.json +868 -0
- package/index.html +128 -121
- package/package.json +4 -2
- package/src/CdcChart.tsx +73 -38
- package/src/_stories/ChartEditor.stories.tsx +15 -4
- package/src/_stories/_mock/pie_config.json +4 -3
- package/src/_stories/_mock/url_filter.json +1076 -0
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +2 -1
- package/src/components/AreaChart/components/AreaChart.jsx +2 -25
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +39 -49
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +36 -56
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +36 -41
- package/src/components/BarChart/components/BarChart.Vertical.tsx +48 -64
- package/src/components/BoxPlot/BoxPlot.jsx +11 -9
- package/src/components/DeviationBar.jsx +3 -3
- package/src/components/EditorPanel/EditorPanel.tsx +1717 -1961
- package/src/components/EditorPanel/EditorPanelContext.ts +40 -0
- package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +148 -0
- package/src/components/EditorPanel/components/{Panel.ForestPlotSettings.tsx → Panels/Panel.ForestPlotSettings.tsx} +16 -7
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +160 -0
- package/src/components/EditorPanel/components/{Panel.Regions.tsx → Panels/Panel.Regions.tsx} +6 -6
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +108 -0
- package/src/components/EditorPanel/components/{Panel.Series.tsx → Panels/Panel.Series.tsx} +50 -6
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +338 -0
- package/src/components/EditorPanel/components/Panels/index.tsx +19 -0
- package/src/components/EditorPanel/components/panels.scss +11 -0
- package/src/components/EditorPanel/editor-panel.scss +1 -13
- package/src/components/EditorPanel/useEditorPermissions.js +44 -13
- package/src/components/Legend/Legend.Component.tsx +207 -0
- package/src/components/Legend/Legend.tsx +8 -327
- package/src/components/Legend/helpers/createFormatLabels.tsx +140 -0
- package/src/components/LineChart/LineChartProps.ts +2 -1
- package/src/components/LineChart/components/LineChart.Circle.tsx +85 -52
- package/src/components/LineChart/helpers.ts +3 -3
- package/src/components/LineChart/index.tsx +99 -23
- package/src/components/LinearChart.jsx +12 -33
- package/src/components/PairedBarChart.jsx +10 -12
- package/src/components/PieChart/PieChart.tsx +80 -27
- package/src/components/Regions/components/Regions.tsx +120 -69
- package/src/components/Sankey/index.tsx +434 -0
- package/src/components/Sankey/sankey.scss +153 -0
- package/src/components/Sankey/types/index.ts +16 -0
- package/src/components/ScatterPlot/ScatterPlot.jsx +1 -0
- package/src/components/Sparkline/{SparkLine.jsx → components/SparkLine.tsx} +14 -30
- package/src/components/Sparkline/index.scss +3 -0
- package/src/components/Sparkline/index.tsx +1 -1
- package/src/components/ZoomBrush.tsx +2 -1
- package/src/data/initial-state.js +51 -4
- package/src/helpers/computeMarginBottom.ts +4 -3
- package/src/helpers/tests/computeMarginBottom.test.ts +2 -1
- package/src/hooks/useBarChart.js +5 -2
- package/src/hooks/useHighlightedBars.js +1 -1
- package/src/hooks/useMinMax.ts +3 -3
- package/src/hooks/useScales.ts +28 -18
- package/src/hooks/useTooltip.tsx +19 -14
- package/src/scss/main.scss +8 -96
- package/src/types/ChartConfig.ts +47 -20
- package/src/types/ChartContext.ts +17 -4
- package/src/types/Label.ts +7 -0
- package/examples/private/chart-t.json +0 -3740
- package/examples/private/epi-data.csv +0 -13
- package/examples/private/epi-data.json +0 -62
- package/examples/private/epi.json +0 -403
- package/examples/private/occupancy.json +0 -109283
- package/examples/private/prod-line-config.json +0 -401
- package/examples/private/region-data.json +0 -822
- package/examples/private/region-testing.json +0 -312
- package/examples/private/scaling.json +0 -45325
- package/examples/private/testing-data.json +0 -1739
- package/examples/private/testing.json +0 -816
- package/src/components/EditorPanel/components/Panel.DateHighlighting.tsx +0 -109
- package/src/components/EditorPanel/components/Panels.tsx +0 -13
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
|
-
|
|
3
2
|
import * as allCurves from '@visx/curve'
|
|
4
3
|
import { Group } from '@visx/group'
|
|
5
4
|
import { LinePath } from '@visx/shape'
|
|
6
5
|
import { Text } from '@visx/text'
|
|
7
6
|
import { scaleLinear, scalePoint } from '@visx/scale'
|
|
8
7
|
import { AxisBottom } from '@visx/axis'
|
|
9
|
-
|
|
10
8
|
import { MarkerArrow } from '@visx/marker'
|
|
11
|
-
|
|
12
9
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
10
|
+
import useReduceData from '../../../hooks/useReduceData'
|
|
11
|
+
import ConfigContext from '../../../ConfigContext'
|
|
12
|
+
import './../index.scss'
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
type SparkLineProps = {
|
|
15
|
+
width: string | number
|
|
16
|
+
height: string | number
|
|
17
|
+
}
|
|
17
18
|
|
|
18
|
-
const SparkLine = props => {
|
|
19
|
+
const SparkLine: React.FC<SparkLineProps> = props => {
|
|
19
20
|
const { width: parentWidth, height: parentHeight } = props
|
|
20
21
|
const { transformedData: data, config, parseDate, formatDate, seriesHighlight, formatNumber, colorScale, handleChartAriaLabels } = useContext(ConfigContext)
|
|
21
|
-
let width = parentWidth
|
|
22
|
+
let width = Number(parentWidth)
|
|
22
23
|
const { minValue, maxValue } = useReduceData(config, data, ConfigContext)
|
|
23
24
|
|
|
24
25
|
const margin = { top: 5, right: 10, bottom: 10, left: 10 }
|
|
25
|
-
const height = parentHeight
|
|
26
|
+
const height = Number(parentHeight)
|
|
26
27
|
|
|
27
28
|
const xMax = width - config.runtime.yAxis.size
|
|
28
29
|
const yMax = height - margin.top - 20
|
|
@@ -38,8 +39,8 @@ const SparkLine = props => {
|
|
|
38
39
|
const isMinValid = Number(enteredMinValue) <= Number(minValue)
|
|
39
40
|
|
|
40
41
|
if (data) {
|
|
41
|
-
let min = enteredMinValue && isMinValid ? enteredMinValue : minValue
|
|
42
|
-
let max = enteredMaxValue && isMaxValid ? enteredMaxValue : Number.MIN_VALUE
|
|
42
|
+
let min = enteredMinValue && isMinValid ? Number(enteredMinValue) : Number(minValue)
|
|
43
|
+
let max = enteredMaxValue && isMaxValid ? Number(enteredMaxValue) : Number(Number.MIN_VALUE)
|
|
43
44
|
|
|
44
45
|
if (max === Number.MIN_VALUE) {
|
|
45
46
|
max = maxValue
|
|
@@ -94,6 +95,7 @@ const SparkLine = props => {
|
|
|
94
95
|
return (
|
|
95
96
|
<ErrorBoundary component='SparkLine'>
|
|
96
97
|
<svg role='img' aria-label={handleChartAriaLabels(config)} width={parentWidth} height={100} className={'sparkline'} tabIndex={0}>
|
|
98
|
+
<title>{`Spark line graphic with the title ${config.title ? config.title : 'No Title Found'}`}</title>
|
|
97
99
|
{config.runtime.lineSeriesKeys?.length > 0
|
|
98
100
|
? config.runtime.lineSeriesKeys
|
|
99
101
|
: config.runtime.seriesKeys.map((seriesKey, index) => (
|
|
@@ -106,15 +108,6 @@ const SparkLine = props => {
|
|
|
106
108
|
display={config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1 ? 'block' : 'none'}
|
|
107
109
|
>
|
|
108
110
|
{data.map((d, dataIndex) => {
|
|
109
|
-
let yAxisTooltip = config.runtime.yAxis.label ? `${config.runtime.yAxis.label}: ${formatNumber(getYAxisData(d, seriesKey))}` : formatNumber(getYAxisData(d, seriesKey))
|
|
110
|
-
let xAxisTooltip = config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ${d[config.runtime.xAxis.dataKey]}` : d[config.runtime.xAxis.dataKey]
|
|
111
|
-
|
|
112
|
-
const tooltip = `<div>
|
|
113
|
-
${yAxisTooltip}<br />
|
|
114
|
-
${xAxisTooltip}<br />
|
|
115
|
-
${config.seriesLabel ? `${config.seriesLabel}: ${seriesKey}` : ''}
|
|
116
|
-
</div>`
|
|
117
|
-
|
|
118
111
|
return (
|
|
119
112
|
<Group key={`series-${seriesKey}-point-${dataIndex}`}>
|
|
120
113
|
<Text display={config.labels ? 'block' : 'none'} x={xScale(getXAxisData(d))} y={yScale(getYAxisData(d, seriesKey))} fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'} textAnchor='middle'>
|
|
@@ -134,16 +127,7 @@ const SparkLine = props => {
|
|
|
134
127
|
shapeRendering='geometricPrecision'
|
|
135
128
|
markerEnd={`url(#${'arrow'}--${index})`}
|
|
136
129
|
/>
|
|
137
|
-
<MarkerArrow
|
|
138
|
-
id={`arrow--${index}`}
|
|
139
|
-
refX={2}
|
|
140
|
-
size={6}
|
|
141
|
-
markerEnd={`url(#${'arrow'}--${index})`}
|
|
142
|
-
strokeOpacity={1}
|
|
143
|
-
fillOpacity={1}
|
|
144
|
-
// stroke={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
|
|
145
|
-
fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'}
|
|
146
|
-
/>
|
|
130
|
+
<MarkerArrow id={`arrow--${index}`} refX={2} size={6} markerEnd={`url(#${'arrow'}--${index})`} strokeOpacity={1} fillOpacity={1} fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'} />
|
|
147
131
|
</Group>
|
|
148
132
|
<AxisBottom
|
|
149
133
|
top={yMax + margin.top}
|
|
@@ -5,6 +5,7 @@ import { useBarChart } from '../hooks/useBarChart'
|
|
|
5
5
|
import { FC, useContext, useEffect, useRef, useState } from 'react'
|
|
6
6
|
import ConfigContext from '../ConfigContext'
|
|
7
7
|
import { ScaleLinear, ScaleBand } from 'd3-scale'
|
|
8
|
+
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
8
9
|
|
|
9
10
|
interface Props {
|
|
10
11
|
xScaleBrush: ScaleLinear<number, number>
|
|
@@ -55,7 +56,7 @@ const ZoomBrush: FC<Props> = props => {
|
|
|
55
56
|
.find(item => item !== undefined)
|
|
56
57
|
const startValue = xValues.find(item => item !== undefined)
|
|
57
58
|
|
|
58
|
-
const formatIfDate = value => (config.runtime.xAxis
|
|
59
|
+
const formatIfDate = value => (isDateScale(config.runtime.xAxis) ? formatDate(parseDate(value)) : value)
|
|
59
60
|
|
|
60
61
|
setTextProps(prev => ({
|
|
61
62
|
...prev,
|
|
@@ -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
|
},
|
|
@@ -115,7 +116,7 @@ export default {
|
|
|
115
116
|
tickColor: '#333',
|
|
116
117
|
numTicks: '',
|
|
117
118
|
labelOffset: 65,
|
|
118
|
-
axisPadding:
|
|
119
|
+
axisPadding: 200,
|
|
119
120
|
target: 0,
|
|
120
121
|
maxTickRotation: 0
|
|
121
122
|
},
|
|
@@ -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',
|
|
@@ -139,6 +141,7 @@ export default {
|
|
|
139
141
|
legend: {
|
|
140
142
|
hide: false,
|
|
141
143
|
behavior: 'isolate',
|
|
144
|
+
axisAlign: true,
|
|
142
145
|
singleRow: true,
|
|
143
146
|
colorCode: '',
|
|
144
147
|
reverseLabelOrder: false,
|
|
@@ -150,7 +153,8 @@ export default {
|
|
|
150
153
|
dynamicLegendChartMessage: 'Select Options from the Legend',
|
|
151
154
|
lineMode: false,
|
|
152
155
|
verticalSorted: false,
|
|
153
|
-
highlightOnHover: false
|
|
156
|
+
highlightOnHover: false,
|
|
157
|
+
seriesHighlight: []
|
|
154
158
|
},
|
|
155
159
|
brush: {
|
|
156
160
|
height: 25,
|
|
@@ -191,7 +195,8 @@ export default {
|
|
|
191
195
|
series: [],
|
|
192
196
|
tooltips: {
|
|
193
197
|
opacity: 90,
|
|
194
|
-
singleSeries: false
|
|
198
|
+
singleSeries: false,
|
|
199
|
+
dateDisplayFormat: ''
|
|
195
200
|
},
|
|
196
201
|
forestPlot: {
|
|
197
202
|
startAt: 0,
|
|
@@ -239,5 +244,47 @@ export default {
|
|
|
239
244
|
},
|
|
240
245
|
area: {
|
|
241
246
|
isStacked: false
|
|
247
|
+
},
|
|
248
|
+
sankey: {
|
|
249
|
+
title: {
|
|
250
|
+
defaultColor: 'black'
|
|
251
|
+
},
|
|
252
|
+
iterations: 1,
|
|
253
|
+
rxValue: 0.9,
|
|
254
|
+
overallSize: {
|
|
255
|
+
width: 900,
|
|
256
|
+
height: 700
|
|
257
|
+
},
|
|
258
|
+
margin: {
|
|
259
|
+
margin_y: 25,
|
|
260
|
+
margin_x: 0
|
|
261
|
+
},
|
|
262
|
+
nodeSize: {
|
|
263
|
+
nodeWidth: 26,
|
|
264
|
+
nodeHeight: 40
|
|
265
|
+
},
|
|
266
|
+
nodePadding: 55,
|
|
267
|
+
nodeFontColor: 'black',
|
|
268
|
+
nodeColor: {
|
|
269
|
+
default: '#ff8500',
|
|
270
|
+
inactive: '#808080'
|
|
271
|
+
},
|
|
272
|
+
linkColor: {
|
|
273
|
+
default: '#ffc900',
|
|
274
|
+
inactive: '#D3D3D3'
|
|
275
|
+
},
|
|
276
|
+
opacity: {
|
|
277
|
+
nodeOpacityDefault: 1.0,
|
|
278
|
+
nodeOpacityInactive: 0.1,
|
|
279
|
+
LinkOpacityDefault: 1.0,
|
|
280
|
+
LinkOpacityInactive: 0.1
|
|
281
|
+
},
|
|
282
|
+
storyNodeFontColor: '#006778',
|
|
283
|
+
storyNodeText: [],
|
|
284
|
+
nodeValueStyle: {
|
|
285
|
+
textBefore: '(',
|
|
286
|
+
textAfter: ')'
|
|
287
|
+
},
|
|
288
|
+
data: []
|
|
242
289
|
}
|
|
243
290
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { ChartConfig
|
|
1
|
+
import { ChartConfig } from '../types/ChartConfig'
|
|
2
|
+
import { Legend } from '@cdc/core/types/Legend'
|
|
2
3
|
|
|
3
4
|
export const computeMarginBottom = (config: ChartConfig, legend: Legend, currentViewport: string): string | number => {
|
|
4
5
|
const isLegendBottom = legend.position === 'bottom' || ['sm', 'xs', 'xxs'].includes(currentViewport)
|
|
5
6
|
const isHorizontal = config.orientation === 'horizontal'
|
|
6
7
|
const tickRotation = Number(config.xAxis.tickRotation) > 0 ? Number(config.xAxis.tickRotation) : 0
|
|
7
|
-
const isBrush = config
|
|
8
|
+
const isBrush = config?.brush?.active
|
|
8
9
|
const offset = 20
|
|
9
|
-
const brushHeight = config
|
|
10
|
+
const brushHeight = config?.brush?.height
|
|
10
11
|
let bottom = 0
|
|
11
12
|
if (!isLegendBottom && isHorizontal && !config.yAxis.label) {
|
|
12
13
|
bottom = Number(config.xAxis.labelOffset)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ChartConfig
|
|
1
|
+
import { ChartConfig } from '../../types/ChartConfig'
|
|
2
|
+
import { Legend } from '@cdc/core/types/Legend'
|
|
2
3
|
import { computeMarginBottom } from '../computeMarginBottom'
|
|
3
4
|
|
|
4
5
|
describe('computeMarginBottom', () => {
|
package/src/hooks/useBarChart.js
CHANGED
|
@@ -2,7 +2,7 @@ import React, { useContext, useEffect, useState } from 'react'
|
|
|
2
2
|
import ConfigContext from '../ConfigContext'
|
|
3
3
|
import { formatNumber as formatColNumber } from '@cdc/core/helpers/cove/number'
|
|
4
4
|
export const useBarChart = () => {
|
|
5
|
-
const { config, colorPalettes, tableData, updateConfig, parseDate, formatDate, setSeriesHighlight } = useContext(ConfigContext)
|
|
5
|
+
const { config, colorPalettes, tableData, updateConfig, parseDate, formatDate, setSeriesHighlight, seriesHighlight } = useContext(ConfigContext)
|
|
6
6
|
const { orientation } = config
|
|
7
7
|
const [hoveredBar, setHoveredBar] = useState(null)
|
|
8
8
|
|
|
@@ -21,6 +21,8 @@ export const useBarChart = () => {
|
|
|
21
21
|
const stackCount = config.runtime.seriesKeys.length
|
|
22
22
|
const fontSize = { small: 16, medium: 18, large: 20 }
|
|
23
23
|
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
|
|
24
26
|
|
|
25
27
|
useEffect(() => {
|
|
26
28
|
if (orientation === 'horizontal' && !config.yAxis.labelPlacement) {
|
|
@@ -194,7 +196,7 @@ export const useBarChart = () => {
|
|
|
194
196
|
return d[config.xAxis.dataKey] === xAxisDataValue
|
|
195
197
|
}) || {}
|
|
196
198
|
Object.keys(columns).forEach(colKeys => {
|
|
197
|
-
if(series && config.columns[colKeys].series && config.columns[colKeys].series !== series) return
|
|
199
|
+
if (series && config.columns[colKeys].series && config.columns[colKeys].series !== series) return
|
|
198
200
|
const formattingParams = {
|
|
199
201
|
addColPrefix: config.columns[colKeys].prefix,
|
|
200
202
|
addColSuffix: config.columns[colKeys].suffix,
|
|
@@ -235,6 +237,7 @@ export const useBarChart = () => {
|
|
|
235
237
|
tipRounding,
|
|
236
238
|
radius,
|
|
237
239
|
stackCount,
|
|
240
|
+
barStackedSeriesKeys,
|
|
238
241
|
fontSize,
|
|
239
242
|
hasMultipleSeries,
|
|
240
243
|
applyRadius,
|
|
@@ -127,7 +127,7 @@ export const useHighlightedBars = (config, updateConfig) => {
|
|
|
127
127
|
*/
|
|
128
128
|
HighLightedBarUtils.findDuplicates = arr => {
|
|
129
129
|
const duplicates = {}
|
|
130
|
-
const result = arr
|
|
130
|
+
const result = arr?.filter(obj => {
|
|
131
131
|
const { legendLabel } = obj
|
|
132
132
|
if (!duplicates[legendLabel]) {
|
|
133
133
|
duplicates[legendLabel] = true
|
package/src/hooks/useMinMax.ts
CHANGED
|
@@ -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.
|
|
187
|
+
max *= 1 + config.yAxis.scalePadding / 100
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
|
package/src/hooks/useScales.ts
CHANGED
|
@@ -4,6 +4,14 @@ import ConfigContext from '../ConfigContext'
|
|
|
4
4
|
import { ChartConfig } from '../types/ChartConfig'
|
|
5
5
|
import { ChartContext } from '../types/ChartContext'
|
|
6
6
|
|
|
7
|
+
const scaleTypes = {
|
|
8
|
+
TIME: 'time',
|
|
9
|
+
LOG: 'log',
|
|
10
|
+
POINT: 'point',
|
|
11
|
+
LINEAR: 'linear',
|
|
12
|
+
BAND: 'band'
|
|
13
|
+
}
|
|
14
|
+
|
|
7
15
|
type useScaleProps = {
|
|
8
16
|
config: ChartConfig // standard chart config
|
|
9
17
|
data: Object[] // standard data array
|
|
@@ -37,14 +45,6 @@ const useScales = (properties: useScaleProps) => {
|
|
|
37
45
|
let xScaleNoPadding = null
|
|
38
46
|
let xScaleBrush = null
|
|
39
47
|
|
|
40
|
-
const scaleTypes = {
|
|
41
|
-
TIME: 'time',
|
|
42
|
-
LOG: 'log',
|
|
43
|
-
POINT: 'point',
|
|
44
|
-
LINEAR: 'linear',
|
|
45
|
-
BAND: 'band'
|
|
46
|
-
}
|
|
47
|
-
|
|
48
48
|
// handle Horizontal bars
|
|
49
49
|
if (isHorizontal) {
|
|
50
50
|
xScale = composeXScale({ min: min * 1.03, ...properties })
|
|
@@ -57,20 +57,24 @@ const useScales = (properties: useScaleProps) => {
|
|
|
57
57
|
// handle Vertical bars
|
|
58
58
|
if (!isHorizontal) {
|
|
59
59
|
xScaleBrush = composeScalePoint(xAxisDataKeysMapped, [0, xMax], 0.5)
|
|
60
|
-
xScale =
|
|
61
|
-
xScale.type = scaleTypes.POINT
|
|
60
|
+
xScale = composeScaleBand(xAxisDataMapped, [0, xMax], 1 - config.barThickness)
|
|
62
61
|
yScale = composeYScale(properties)
|
|
63
|
-
seriesScale =
|
|
62
|
+
seriesScale = composeScaleBand(seriesDomain, [0, xScale.bandwidth()], 0)
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
// handle Area chart
|
|
67
|
-
if (config.xAxis.type === 'date'
|
|
66
|
+
if (config.xAxis.type === 'date-time') {
|
|
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: [
|
|
72
|
+
domain: [xAxisMin, xAxisMax],
|
|
70
73
|
range: [0, xMax]
|
|
71
74
|
})
|
|
72
75
|
xScaleBrush = xScale
|
|
73
|
-
xScale.type = scaleTypes.
|
|
76
|
+
xScale.type = scaleTypes.TIME
|
|
77
|
+
seriesScale = composeScaleBand(seriesDomain, [0, config.barThickness * xMax], 0)
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
// handle Deviation bar
|
|
@@ -245,8 +249,7 @@ const composeXScale = ({ min, max, xMax, config }) => {
|
|
|
245
249
|
domain: [min, max],
|
|
246
250
|
range: [0, xMax],
|
|
247
251
|
nice: config.useLogScale,
|
|
248
|
-
zero: config.useLogScale
|
|
249
|
-
type: config.useLogScale ? 'log' : 'linear'
|
|
252
|
+
zero: config.useLogScale
|
|
250
253
|
})
|
|
251
254
|
}
|
|
252
255
|
|
|
@@ -280,7 +283,14 @@ const composeScalePoint = (domain, range, padding = 0) => {
|
|
|
280
283
|
return scalePoint({
|
|
281
284
|
domain: domain,
|
|
282
285
|
range: range,
|
|
283
|
-
padding: padding
|
|
284
|
-
|
|
286
|
+
padding: padding
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const composeScaleBand = (domain, range, padding = 0) => {
|
|
291
|
+
return scaleBand({
|
|
292
|
+
domain: domain,
|
|
293
|
+
range: range,
|
|
294
|
+
padding: padding
|
|
285
295
|
})
|
|
286
296
|
}
|
package/src/hooks/useTooltip.tsx
CHANGED
|
@@ -9,11 +9,12 @@ import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
|
9
9
|
const transform = new DataTransform()
|
|
10
10
|
|
|
11
11
|
import { formatNumber as formatColNumber } from '@cdc/core/helpers/cove/number'
|
|
12
|
+
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
12
13
|
|
|
13
14
|
export const useTooltip = props => {
|
|
14
|
-
const { tableData, config, formatNumber, capitalize, formatDate, parseDate, setSharedFilter } = useContext<ChartContext>(ConfigContext)
|
|
15
|
+
const { tableData, config, formatNumber, capitalize, formatDate, formatTooltipsDate, parseDate, setSharedFilter } = useContext<ChartContext>(ConfigContext)
|
|
15
16
|
const { xScale, yScale, showTooltip, hideTooltip } = props
|
|
16
|
-
const { xAxis, visualizationType, orientation, yAxis, runtime
|
|
17
|
+
const { xAxis, visualizationType, orientation, yAxis, runtime } = config
|
|
17
18
|
const data = transform.applySuppression(tableData, config.suppressedData)
|
|
18
19
|
/**
|
|
19
20
|
* Provides the tooltip information based on the tooltip data array and svg cursor coordinates
|
|
@@ -188,7 +189,7 @@ export const useTooltip = props => {
|
|
|
188
189
|
return xScale.domain()[index - 1] // fixes off by 1 error
|
|
189
190
|
}
|
|
190
191
|
|
|
191
|
-
if (config.xAxis
|
|
192
|
+
if (isDateScale(config.xAxis) && config.visualizationType !== 'Combo') {
|
|
192
193
|
const bisectDate = bisector(d => parseDate(d[config.xAxis.dataKey])).left
|
|
193
194
|
const x0 = xScale.invert(xScale(x))
|
|
194
195
|
const index = bisectDate(config.data, x0, 1)
|
|
@@ -207,33 +208,35 @@ export const useTooltip = props => {
|
|
|
207
208
|
if (orientation === 'horizontal') return
|
|
208
209
|
|
|
209
210
|
// Check the type of x equal to point or if the type of xAxis is equal to continuous or date
|
|
210
|
-
if (xScale.type === 'point' || xAxis.type === 'continuous' || xAxis
|
|
211
|
+
if (xScale.type === 'point' || xAxis.type === 'continuous' || isDateScale(xAxis)) {
|
|
211
212
|
// Find the closest x value by calculating the minimum distance
|
|
212
213
|
let closestX = null
|
|
213
214
|
let minDistance = Number.MAX_VALUE
|
|
214
215
|
let offset = x
|
|
215
216
|
|
|
216
217
|
data.forEach(d => {
|
|
217
|
-
const xPosition = xAxis
|
|
218
|
+
const xPosition = isDateScale(xAxis) ? xScale(parseDate(d[xAxis.dataKey])) : xScale(d[xAxis.dataKey])
|
|
218
219
|
let bwOffset = config.barHeight
|
|
219
220
|
const distance = Math.abs(Number(xPosition - offset + (isClick ? bwOffset * 2 : 0)))
|
|
220
221
|
|
|
221
222
|
if (distance <= minDistance) {
|
|
222
223
|
minDistance = distance
|
|
223
|
-
closestX = xAxis
|
|
224
|
+
closestX = isDateScale(xAxis) ? d[xAxis.dataKey] : d[xAxis.dataKey]
|
|
224
225
|
}
|
|
225
226
|
})
|
|
226
227
|
return closestX
|
|
227
228
|
}
|
|
228
229
|
|
|
229
230
|
if (config.xAxis.type === 'categorical' || (visualizationType === 'Combo' && orientation !== 'horizontal' && visualizationType !== 'Forest Plot')) {
|
|
230
|
-
let
|
|
231
|
+
let range = xScale.range()[1] - xScale.range()[0]
|
|
232
|
+
let eachBand = range / (xScale.domain().length + 1)
|
|
233
|
+
|
|
231
234
|
let numerator = x
|
|
232
|
-
const index = Math.floor(Number(numerator) / eachBand)
|
|
233
|
-
return xScale.domain()[index
|
|
235
|
+
const index = Math.floor((Number(numerator) - eachBand / 2) / eachBand)
|
|
236
|
+
return xScale.domain()[index] // fixes off by 1 error
|
|
234
237
|
}
|
|
235
238
|
|
|
236
|
-
if (
|
|
239
|
+
if (isDateScale(xAxis) && visualizationType !== 'Combo' && orientation !== 'horizontal') {
|
|
237
240
|
const bisectDate = bisector(d => parseDate(d[config.xAxis.dataKey])).left
|
|
238
241
|
const x0 = xScale.invert(x)
|
|
239
242
|
const index = bisectDate(config.data, x0, 1)
|
|
@@ -278,7 +281,7 @@ export const useTooltip = props => {
|
|
|
278
281
|
let closestXScaleValue = getXValueFromCoordinate(x, true)
|
|
279
282
|
let datum = config.data?.filter(item => item[config.xAxis.dataKey] === closestXScaleValue)
|
|
280
283
|
if (!closestXScaleValue) throw new Error('COVE: no closest x scale value in handleTooltipClick')
|
|
281
|
-
if (xAxis
|
|
284
|
+
if (isDateScale(xAxis) && closestXScaleValue) {
|
|
282
285
|
closestXScaleValue = new Date(closestXScaleValue)
|
|
283
286
|
closestXScaleValue = formatDate(closestXScaleValue)
|
|
284
287
|
datum = config.data?.filter(item => formatDate(new Date(item[config.xAxis.dataKey])) === closestXScaleValue)
|
|
@@ -423,13 +426,15 @@ export const useTooltip = props => {
|
|
|
423
426
|
const [key, value, axisPosition] = additionalData
|
|
424
427
|
|
|
425
428
|
if (visualizationType === 'Forest Plot') {
|
|
426
|
-
if (key === config.xAxis.dataKey) return <li className='tooltip-heading'>{`${capitalize(config.xAxis.dataKey ? `${config.xAxis.dataKey}: ` : '')} ${
|
|
429
|
+
if (key === config.xAxis.dataKey) return <li className='tooltip-heading'>{`${capitalize(config.xAxis.dataKey ? `${config.xAxis.dataKey}: ` : '')} ${isDateScale(yAxis) ? formatDate(parseDate(key, false)) : value}`}</li>
|
|
427
430
|
return <li className='tooltip-body'>{`${getSeriesNameFromLabel(key)}: ${formatNumber(value, 'left')}`}</li>
|
|
428
431
|
}
|
|
432
|
+
const formattedDate = config.tooltips.dateDisplayFormat ? formatTooltipsDate(parseDate(value, false)) : formatDate(parseDate(value, false))
|
|
429
433
|
|
|
430
434
|
// 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
|
-
|
|
435
|
+
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>
|
|
436
|
+
|
|
437
|
+
if (key === config.xAxis.dataKey) return <li className='tooltip-heading'>{`${capitalize(config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ` : '')} ${isDateScale(xAxis) ? formattedDate : value}`}</li>
|
|
433
438
|
|
|
434
439
|
// TOOLTIP BODY
|
|
435
440
|
return <li className='tooltip-body'>{`${getSeriesNameFromLabel(key)}: ${value}`}</li>
|
package/src/scss/main.scss
CHANGED
|
@@ -1,73 +1,6 @@
|
|
|
1
1
|
@import '@cdc/core/styles/base';
|
|
2
2
|
@import '@cdc/core/styles/heading-colors';
|
|
3
3
|
@import '@cdc/core/styles/v2/themes/color-definitions';
|
|
4
|
-
.dash-container {
|
|
5
|
-
display: flex;
|
|
6
|
-
align-items: center;
|
|
7
|
-
flex-direction: row;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
.legend-dash-left {
|
|
11
|
-
margin-left: 8px !important;
|
|
12
|
-
display: flex;
|
|
13
|
-
flex-direction: column;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.dash-inner {
|
|
17
|
-
width: 20px;
|
|
18
|
-
margin-left: 0px !important;
|
|
19
|
-
display: flex;
|
|
20
|
-
align-items: center;
|
|
21
|
-
justify-content: center;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.dashes {
|
|
25
|
-
display: inline-block;
|
|
26
|
-
|
|
27
|
-
&.open-circles {
|
|
28
|
-
width: 12px;
|
|
29
|
-
height: 12px;
|
|
30
|
-
border: 2px solid currentColor;
|
|
31
|
-
border-radius: 50%;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
&.dashed-small {
|
|
35
|
-
margin: 0 0px;
|
|
36
|
-
font-size: 20px;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
&.dashed-medium,
|
|
40
|
-
&.dashed-large {
|
|
41
|
-
span {
|
|
42
|
-
display: inline-block;
|
|
43
|
-
position: relative;
|
|
44
|
-
margin-right: 12px;
|
|
45
|
-
margin-left: 0 !important;
|
|
46
|
-
|
|
47
|
-
&::before {
|
|
48
|
-
content: '';
|
|
49
|
-
display: block;
|
|
50
|
-
height: 2px;
|
|
51
|
-
background-color: currentColor;
|
|
52
|
-
position: absolute;
|
|
53
|
-
top: 50%;
|
|
54
|
-
transform: translateY(-20%);
|
|
55
|
-
width: 10px;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
&.dashed-large {
|
|
61
|
-
span {
|
|
62
|
-
margin-right: 12px;
|
|
63
|
-
margin-left: 0 !important;
|
|
64
|
-
|
|
65
|
-
&::before {
|
|
66
|
-
width: 13px;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
4
|
|
|
72
5
|
.form-container {
|
|
73
6
|
overflow-y: auto;
|
|
@@ -187,30 +120,6 @@
|
|
|
187
120
|
}
|
|
188
121
|
}
|
|
189
122
|
|
|
190
|
-
.legend-reset {
|
|
191
|
-
font-size: 0.7em;
|
|
192
|
-
color: rgba(0, 0, 0, 0.6);
|
|
193
|
-
position: absolute;
|
|
194
|
-
right: 1em;
|
|
195
|
-
background: rgba(0, 0, 0, 0.1);
|
|
196
|
-
text-transform: uppercase;
|
|
197
|
-
transition: 0.3s all;
|
|
198
|
-
padding: 0.375rem;
|
|
199
|
-
top: 1em;
|
|
200
|
-
border-radius: 3px;
|
|
201
|
-
|
|
202
|
-
&--dynamic {
|
|
203
|
-
position: relative;
|
|
204
|
-
float: right;
|
|
205
|
-
right: unset;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
&:hover {
|
|
209
|
-
background: rgba(0, 0, 0, 0.15);
|
|
210
|
-
transition: 0.3s all;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
123
|
.legend-item {
|
|
215
124
|
text-align: left;
|
|
216
125
|
align-items: flex-start !important;
|
|
@@ -234,11 +143,11 @@
|
|
|
234
143
|
flex: 0 0 auto;
|
|
235
144
|
}
|
|
236
145
|
|
|
237
|
-
|
|
238
|
-
font-size: 1.
|
|
146
|
+
h3 {
|
|
147
|
+
font-size: 1.3rem;
|
|
239
148
|
}
|
|
240
149
|
|
|
241
|
-
|
|
150
|
+
h3,
|
|
242
151
|
p {
|
|
243
152
|
margin-bottom: 0.8em;
|
|
244
153
|
}
|
|
@@ -595,21 +504,24 @@
|
|
|
595
504
|
}
|
|
596
505
|
|
|
597
506
|
&.animated {
|
|
507
|
+
.vertical path,
|
|
598
508
|
.vertical rect,
|
|
599
509
|
.vertical foreignObject div {
|
|
600
510
|
opacity: 0;
|
|
601
|
-
animation: growBar 0.5s linear
|
|
511
|
+
animation: growBar 0.5s linear both;
|
|
602
512
|
animation-play-state: paused;
|
|
603
513
|
}
|
|
604
514
|
|
|
515
|
+
.horizontal path,
|
|
605
516
|
.horizontal rect,
|
|
606
517
|
.horizontal foreignObject div {
|
|
607
518
|
opacity: 0;
|
|
608
|
-
animation: growBarH 0.5s linear
|
|
519
|
+
animation: growBarH 0.5s linear both;
|
|
609
520
|
animation-play-state: paused;
|
|
610
521
|
}
|
|
611
522
|
|
|
612
523
|
&.animate {
|
|
524
|
+
path,
|
|
613
525
|
rect,
|
|
614
526
|
foreignObject div {
|
|
615
527
|
animation-play-state: running;
|