@cdc/chart 4.24.2 → 4.24.4
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 +47933 -36918
- package/examples/chart-regression-1.json +378 -0
- package/examples/chart-regression-2.json +2360 -0
- package/examples/feature/filters/url-filter.json +1076 -0
- package/examples/feature/line/line-chart.json +362 -37
- package/examples/feature/regions/index.json +50 -4
- 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/region-issue.json +2065 -0
- package/examples/sparkline.json +868 -0
- package/examples/test.json +5409 -0
- package/index.html +130 -123
- package/package.json +4 -2
- package/src/CdcChart.tsx +178 -94
- package/src/_stories/ChartEditor.stories.tsx +14 -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 -1
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +46 -63
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +36 -56
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +32 -39
- package/src/components/BarChart/components/BarChart.Vertical.tsx +44 -59
- package/src/components/BoxPlot/BoxPlot.jsx +2 -1
- package/src/components/DeviationBar.jsx +3 -3
- package/src/components/EditorPanel/EditorPanel.tsx +1684 -1564
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +1 -1
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +107 -0
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +48 -4
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +41 -0
- package/src/components/EditorPanel/components/Panels/index.tsx +9 -7
- package/src/components/EditorPanel/components/panels.scss +11 -0
- package/src/components/EditorPanel/editor-panel.scss +0 -724
- package/src/components/EditorPanel/useEditorPermissions.js +40 -14
- package/src/components/Legend/Legend.Component.tsx +43 -63
- package/src/components/Legend/Legend.tsx +8 -4
- package/src/components/LineChart/LineChartProps.ts +1 -0
- package/src/components/LineChart/helpers.ts +2 -2
- package/src/components/LineChart/index.tsx +7 -7
- package/src/components/LinearChart.jsx +11 -31
- package/src/components/PairedBarChart.jsx +6 -10
- package/src/components/PieChart/PieChart.tsx +3 -3
- package/src/components/Regions/components/Regions.tsx +120 -78
- 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 +46 -2
- package/src/helpers/computeMarginBottom.ts +2 -1
- package/src/helpers/tests/computeMarginBottom.test.ts +2 -1
- package/src/hooks/useBarChart.js +5 -2
- package/src/hooks/useScales.ts +47 -18
- package/src/hooks/useTooltip.tsx +9 -8
- package/src/scss/main.scss +33 -29
- package/src/types/ChartConfig.ts +32 -14
- package/src/types/ChartContext.ts +7 -0
|
@@ -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,
|
|
@@ -116,7 +116,7 @@ export default {
|
|
|
116
116
|
tickColor: '#333',
|
|
117
117
|
numTicks: '',
|
|
118
118
|
labelOffset: 65,
|
|
119
|
-
axisPadding:
|
|
119
|
+
axisPadding: 200,
|
|
120
120
|
target: 0,
|
|
121
121
|
maxTickRotation: 0
|
|
122
122
|
},
|
|
@@ -141,6 +141,7 @@ export default {
|
|
|
141
141
|
legend: {
|
|
142
142
|
hide: false,
|
|
143
143
|
behavior: 'isolate',
|
|
144
|
+
axisAlign: true,
|
|
144
145
|
singleRow: true,
|
|
145
146
|
colorCode: '',
|
|
146
147
|
reverseLabelOrder: false,
|
|
@@ -152,7 +153,8 @@ export default {
|
|
|
152
153
|
dynamicLegendChartMessage: 'Select Options from the Legend',
|
|
153
154
|
lineMode: false,
|
|
154
155
|
verticalSorted: false,
|
|
155
|
-
highlightOnHover: false
|
|
156
|
+
highlightOnHover: false,
|
|
157
|
+
seriesHighlight: []
|
|
156
158
|
},
|
|
157
159
|
brush: {
|
|
158
160
|
height: 25,
|
|
@@ -242,5 +244,47 @@ export default {
|
|
|
242
244
|
},
|
|
243
245
|
area: {
|
|
244
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: []
|
|
245
289
|
}
|
|
246
290
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
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)
|
|
@@ -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,
|
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 })
|
|
@@ -56,14 +56,14 @@ const useScales = (properties: useScaleProps) => {
|
|
|
56
56
|
|
|
57
57
|
// handle Vertical bars
|
|
58
58
|
if (!isHorizontal) {
|
|
59
|
-
xScaleBrush = composeScalePoint(xAxisDataKeysMapped, [0, xMax], .5)
|
|
59
|
+
xScaleBrush = composeScalePoint(xAxisDataKeysMapped, [0, xMax], 0.5)
|
|
60
60
|
xScale = composeScaleBand(xAxisDataMapped, [0, xMax], 1 - config.barThickness)
|
|
61
61
|
yScale = composeYScale(properties)
|
|
62
62
|
seriesScale = composeScaleBand(seriesDomain, [0, xScale.bandwidth()], 0)
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// handle Area chart
|
|
66
|
-
if (config.xAxis.type === 'date'
|
|
66
|
+
if (config.xAxis.type === 'date-time') {
|
|
67
67
|
let xAxisMin = Math.min(...xAxisDataMapped)
|
|
68
68
|
let xAxisMax = Math.max(...xAxisDataMapped)
|
|
69
69
|
xAxisMin -= (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
|
|
@@ -73,8 +73,8 @@ const useScales = (properties: useScaleProps) => {
|
|
|
73
73
|
range: [0, xMax]
|
|
74
74
|
})
|
|
75
75
|
xScaleBrush = xScale
|
|
76
|
-
xScale.type = scaleTypes.
|
|
77
|
-
seriesScale = composeScaleBand(seriesDomain, [0, config.barThickness *
|
|
76
|
+
xScale.type = scaleTypes.TIME
|
|
77
|
+
seriesScale = composeScaleBand(seriesDomain, [0, config.barThickness * xMax], 0)
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// handle Deviation bar
|
|
@@ -238,6 +238,38 @@ const useScales = (properties: useScaleProps) => {
|
|
|
238
238
|
|
|
239
239
|
export default useScales
|
|
240
240
|
|
|
241
|
+
export const getTickValues = (xAxisDataMapped, xScale, num) => {
|
|
242
|
+
const xDomain = xScale.domain();
|
|
243
|
+
|
|
244
|
+
if(xScale.type === 'time'){
|
|
245
|
+
const xDomainMax = xAxisDataMapped[xAxisDataMapped.length - 1];
|
|
246
|
+
const xDomainMin = xAxisDataMapped[0];
|
|
247
|
+
const step = (xDomainMax - xDomainMin) / (num - 1);
|
|
248
|
+
const tickValues = [];
|
|
249
|
+
for(let i = xDomainMax; i >= xDomainMin; i -= step){
|
|
250
|
+
tickValues.push(i);
|
|
251
|
+
}
|
|
252
|
+
if(tickValues[tickValues.length - 1] !== xDomainMin){
|
|
253
|
+
tickValues.push(xDomainMin);
|
|
254
|
+
}
|
|
255
|
+
tickValues.reverse();
|
|
256
|
+
|
|
257
|
+
return tickValues;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if(xDomain.length > 2){
|
|
261
|
+
const step = num || 1;
|
|
262
|
+
const tickValues = [];
|
|
263
|
+
for(let i = xDomain.length; i > 0; i -= step){
|
|
264
|
+
const adjustedIndex = Math.max(Math.round(i) - 1, 0);
|
|
265
|
+
tickValues.push(xDomain[adjustedIndex]);
|
|
266
|
+
}
|
|
267
|
+
tickValues.reverse();
|
|
268
|
+
|
|
269
|
+
return tickValues;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
241
273
|
/// helper functions
|
|
242
274
|
const composeXScale = ({ min, max, xMax, config }) => {
|
|
243
275
|
// Adjust min value if using logarithmic scale
|
|
@@ -249,8 +281,7 @@ const composeXScale = ({ min, max, xMax, config }) => {
|
|
|
249
281
|
domain: [min, max],
|
|
250
282
|
range: [0, xMax],
|
|
251
283
|
nice: config.useLogScale,
|
|
252
|
-
zero: config.useLogScale
|
|
253
|
-
type: config.useLogScale ? 'log' : 'linear'
|
|
284
|
+
zero: config.useLogScale
|
|
254
285
|
})
|
|
255
286
|
}
|
|
256
287
|
|
|
@@ -284,8 +315,7 @@ const composeScalePoint = (domain, range, padding = 0) => {
|
|
|
284
315
|
return scalePoint({
|
|
285
316
|
domain: domain,
|
|
286
317
|
range: range,
|
|
287
|
-
padding: padding
|
|
288
|
-
type: 'point'
|
|
318
|
+
padding: padding
|
|
289
319
|
})
|
|
290
320
|
}
|
|
291
321
|
|
|
@@ -293,7 +323,6 @@ const composeScaleBand = (domain, range, padding = 0) => {
|
|
|
293
323
|
return scaleBand({
|
|
294
324
|
domain: domain,
|
|
295
325
|
range: range,
|
|
296
|
-
padding: padding
|
|
297
|
-
type: 'band'
|
|
326
|
+
padding: padding
|
|
298
327
|
})
|
|
299
328
|
}
|
package/src/hooks/useTooltip.tsx
CHANGED
|
@@ -9,6 +9,7 @@ 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
15
|
const { tableData, config, formatNumber, capitalize, formatDate, formatTooltipsDate, parseDate, setSharedFilter } = useContext<ChartContext>(ConfigContext)
|
|
@@ -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,20 +208,20 @@ 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
|
|
@@ -235,7 +236,7 @@ export const useTooltip = props => {
|
|
|
235
236
|
return xScale.domain()[index] // fixes off by 1 error
|
|
236
237
|
}
|
|
237
238
|
|
|
238
|
-
if (
|
|
239
|
+
if (isDateScale(xAxis) && visualizationType !== 'Combo' && orientation !== 'horizontal') {
|
|
239
240
|
const bisectDate = bisector(d => parseDate(d[config.xAxis.dataKey])).left
|
|
240
241
|
const x0 = xScale.invert(x)
|
|
241
242
|
const index = bisectDate(config.data, x0, 1)
|
|
@@ -280,7 +281,7 @@ export const useTooltip = props => {
|
|
|
280
281
|
let closestXScaleValue = getXValueFromCoordinate(x, true)
|
|
281
282
|
let datum = config.data?.filter(item => item[config.xAxis.dataKey] === closestXScaleValue)
|
|
282
283
|
if (!closestXScaleValue) throw new Error('COVE: no closest x scale value in handleTooltipClick')
|
|
283
|
-
if (xAxis
|
|
284
|
+
if (isDateScale(xAxis) && closestXScaleValue) {
|
|
284
285
|
closestXScaleValue = new Date(closestXScaleValue)
|
|
285
286
|
closestXScaleValue = formatDate(closestXScaleValue)
|
|
286
287
|
datum = config.data?.filter(item => formatDate(new Date(item[config.xAxis.dataKey])) === closestXScaleValue)
|
|
@@ -425,7 +426,7 @@ export const useTooltip = props => {
|
|
|
425
426
|
const [key, value, axisPosition] = additionalData
|
|
426
427
|
|
|
427
428
|
if (visualizationType === 'Forest Plot') {
|
|
428
|
-
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>
|
|
429
430
|
return <li className='tooltip-body'>{`${getSeriesNameFromLabel(key)}: ${formatNumber(value, 'left')}`}</li>
|
|
430
431
|
}
|
|
431
432
|
const formattedDate = config.tooltips.dateDisplayFormat ? formatTooltipsDate(parseDate(value, false)) : formatDate(parseDate(value, false))
|
|
@@ -433,7 +434,7 @@ export const useTooltip = props => {
|
|
|
433
434
|
// TOOLTIP HEADING
|
|
434
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>
|
|
435
436
|
|
|
436
|
-
if (key === config.xAxis.dataKey) return <li className='tooltip-heading'>{`${capitalize(config.runtime.xAxis.label ? `${config.runtime.xAxis.label}: ` : '')} ${
|
|
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>
|
|
437
438
|
|
|
438
439
|
// TOOLTIP BODY
|
|
439
440
|
return <li className='tooltip-body'>{`${getSeriesNameFromLabel(key)}: ${value}`}</li>
|
package/src/scss/main.scss
CHANGED
|
@@ -120,30 +120,6 @@
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
.legend-reset {
|
|
124
|
-
font-size: 0.7em;
|
|
125
|
-
color: rgba(0, 0, 0, 0.6);
|
|
126
|
-
position: absolute;
|
|
127
|
-
right: 1em;
|
|
128
|
-
background: rgba(0, 0, 0, 0.1);
|
|
129
|
-
text-transform: uppercase;
|
|
130
|
-
transition: 0.3s all;
|
|
131
|
-
padding: 0.375rem;
|
|
132
|
-
top: 1em;
|
|
133
|
-
border-radius: 3px;
|
|
134
|
-
|
|
135
|
-
&--dynamic {
|
|
136
|
-
position: relative;
|
|
137
|
-
float: right;
|
|
138
|
-
right: unset;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
&:hover {
|
|
142
|
-
background: rgba(0, 0, 0, 0.15);
|
|
143
|
-
transition: 0.3s all;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
123
|
.legend-item {
|
|
148
124
|
text-align: left;
|
|
149
125
|
align-items: flex-start !important;
|
|
@@ -154,6 +130,9 @@
|
|
|
154
130
|
word-wrap: break-word;
|
|
155
131
|
white-space: pre-wrap;
|
|
156
132
|
word-break: break-word;
|
|
133
|
+
@include breakpoint(xs) {
|
|
134
|
+
font-size: $font-small;
|
|
135
|
+
}
|
|
157
136
|
}
|
|
158
137
|
}
|
|
159
138
|
|
|
@@ -167,13 +146,16 @@
|
|
|
167
146
|
flex: 0 0 auto;
|
|
168
147
|
}
|
|
169
148
|
|
|
170
|
-
|
|
171
|
-
font-size: 1.
|
|
149
|
+
h3 {
|
|
150
|
+
font-size: 1.3rem;
|
|
172
151
|
}
|
|
173
152
|
|
|
174
|
-
|
|
153
|
+
h3,
|
|
175
154
|
p {
|
|
176
155
|
margin-bottom: 0.8em;
|
|
156
|
+
@include breakpoint(xs) {
|
|
157
|
+
font-size: $font-small + 0.2em;
|
|
158
|
+
}
|
|
177
159
|
}
|
|
178
160
|
& div.legend-item {
|
|
179
161
|
margin-bottom: 0.5em !important;
|
|
@@ -216,6 +198,20 @@
|
|
|
216
198
|
}
|
|
217
199
|
}
|
|
218
200
|
|
|
201
|
+
.legend-preliminary {
|
|
202
|
+
display: flex;
|
|
203
|
+
flex-direction: row;
|
|
204
|
+
align-items: center;
|
|
205
|
+
@include breakpoint(xs) {
|
|
206
|
+
font-size: $font-small;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
& > svg {
|
|
210
|
+
width: 50px;
|
|
211
|
+
height: 23px;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
219
215
|
.visx-tooltip {
|
|
220
216
|
z-index: 100000;
|
|
221
217
|
}
|
|
@@ -528,21 +524,24 @@
|
|
|
528
524
|
}
|
|
529
525
|
|
|
530
526
|
&.animated {
|
|
527
|
+
.vertical path,
|
|
531
528
|
.vertical rect,
|
|
532
529
|
.vertical foreignObject div {
|
|
533
530
|
opacity: 0;
|
|
534
|
-
animation: growBar 0.5s linear
|
|
531
|
+
animation: growBar 0.5s linear both;
|
|
535
532
|
animation-play-state: paused;
|
|
536
533
|
}
|
|
537
534
|
|
|
535
|
+
.horizontal path,
|
|
538
536
|
.horizontal rect,
|
|
539
537
|
.horizontal foreignObject div {
|
|
540
538
|
opacity: 0;
|
|
541
|
-
animation: growBarH 0.5s linear
|
|
539
|
+
animation: growBarH 0.5s linear both;
|
|
542
540
|
animation-play-state: paused;
|
|
543
541
|
}
|
|
544
542
|
|
|
545
543
|
&.animate {
|
|
544
|
+
path,
|
|
546
545
|
rect,
|
|
547
546
|
foreignObject div {
|
|
548
547
|
animation-play-state: running;
|
|
@@ -620,6 +619,11 @@
|
|
|
620
619
|
}
|
|
621
620
|
}
|
|
622
621
|
|
|
622
|
+
.cdc-open-viz-module .is-editor .cdc-chart-inner-container {
|
|
623
|
+
overflow: hidden;
|
|
624
|
+
background-color: var(--white);
|
|
625
|
+
}
|
|
626
|
+
|
|
623
627
|
.isEditor.type-sparkline .cdc-chart-inner-container {
|
|
624
628
|
margin: 3em auto 0;
|
|
625
629
|
max-width: 60%;
|
package/src/types/ChartConfig.ts
CHANGED
|
@@ -7,20 +7,14 @@ import { FilterBehavior } from '@cdc/core/types/FilterBehavior'
|
|
|
7
7
|
import { Table } from '@cdc/core/types/Table'
|
|
8
8
|
import { BoxPlot } from '@cdc/core/types/BoxPlot'
|
|
9
9
|
import { General } from '@cdc/core/types/General'
|
|
10
|
+
import { type Link } from './../components/Sankey/types'
|
|
11
|
+
import { Legend } from '@cdc/core/types/Legend'
|
|
12
|
+
import { ConfidenceInterval } from '@cdc/core/types/ConfidenceInterval'
|
|
13
|
+
import { Region } from '@cdc/core/types/Region'
|
|
14
|
+
import { type PreliminaryDataItem } from '../components/LineChart/LineChartProps'
|
|
10
15
|
|
|
11
16
|
export type ChartColumns = Record<string, Column>
|
|
12
17
|
|
|
13
|
-
type Region = {
|
|
14
|
-
from: string
|
|
15
|
-
to: string
|
|
16
|
-
fromType: 'Previous Days' | 'Fixed Date'
|
|
17
|
-
toType: 'Last Date' | 'Fised Date'
|
|
18
|
-
label: string
|
|
19
|
-
color: string
|
|
20
|
-
background: string
|
|
21
|
-
range: 'Custom' | string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
18
|
type DataFormat = {
|
|
25
19
|
abbreviated: boolean
|
|
26
20
|
bottomAbbreviated: boolean
|
|
@@ -58,9 +52,11 @@ type Filter = {
|
|
|
58
52
|
}
|
|
59
53
|
|
|
60
54
|
export type Legend = {
|
|
55
|
+
seriesHighlight: string[]
|
|
61
56
|
additionalCategories: string[]
|
|
62
57
|
// general legend onClick behavior
|
|
63
58
|
behavior: 'highlight' | 'isolate' | string
|
|
59
|
+
axisAlign: boolean
|
|
64
60
|
colorCode: string
|
|
65
61
|
description: string
|
|
66
62
|
// show or hide the legend
|
|
@@ -102,7 +98,7 @@ type AllChartsConfig = {
|
|
|
102
98
|
color: string
|
|
103
99
|
colorMatchLineSeriesLabels: boolean
|
|
104
100
|
columns: ChartColumns
|
|
105
|
-
confidenceKeys:
|
|
101
|
+
confidenceKeys: ConfidenceInterval
|
|
106
102
|
customColors: string[]
|
|
107
103
|
data: Object[]
|
|
108
104
|
dataUrl: string
|
|
@@ -139,6 +135,7 @@ type AllChartsConfig = {
|
|
|
139
135
|
orientation: 'vertical' | 'horizontal'
|
|
140
136
|
palette: string
|
|
141
137
|
pieType?: string
|
|
138
|
+
preliminaryData: PreliminaryDataItem[]
|
|
142
139
|
primary?: DataFormat
|
|
143
140
|
roundingStyle: string
|
|
144
141
|
runtime: Runtime
|
|
@@ -162,16 +159,37 @@ type AllChartsConfig = {
|
|
|
162
159
|
}
|
|
163
160
|
topAxis: { hasLine: boolean }
|
|
164
161
|
twoColor: { palette: string }
|
|
165
|
-
type:
|
|
162
|
+
type: 'chart' | 'dashboard'
|
|
166
163
|
useLogScale: boolean
|
|
167
164
|
visual: Visual
|
|
168
|
-
visualizationType: 'Area Chart' | 'Bar' | 'Box Plot' | 'Deviation Bar' | 'Forest Plot' | 'Line' | 'Paired Bar' | 'Pie' | 'Scatter Plot' | 'Spark Line' | 'Combo' | 'Forecasting'
|
|
165
|
+
visualizationType: 'Area Chart' | 'Bar' | 'Box Plot' | 'Deviation Bar' | 'Forest Plot' | 'Line' | 'Paired Bar' | 'Pie' | 'Scatter Plot' | 'Spark Line' | 'Combo' | 'Forecasting' | 'Sankey'
|
|
169
166
|
visualizationSubType: string
|
|
170
167
|
xAxis: Axis
|
|
171
168
|
yAxis: Axis
|
|
172
169
|
xScale: Function
|
|
173
170
|
yScale: Function
|
|
174
171
|
regions: Region[]
|
|
172
|
+
sankey: {
|
|
173
|
+
data: { links: Link[]; storyNodeText: Object[]; tooltips: Object[] }[]
|
|
174
|
+
nodePadding: number
|
|
175
|
+
iterations: number
|
|
176
|
+
nodeSize: {
|
|
177
|
+
nodeWidth: number
|
|
178
|
+
}
|
|
179
|
+
margin: { margin_x: number; margin_y: number }
|
|
180
|
+
nodeColor: { default: boolean; inactive: boolean }
|
|
181
|
+
opacity: { LinkOpacityInactive: string; LinkOpacityDefault: string; nodeOpacityInactive: boolean; nodeOpacityDefault: boolean }
|
|
182
|
+
rxValue: number
|
|
183
|
+
nodeFontColor: string
|
|
184
|
+
nodeValueStyle: {
|
|
185
|
+
textBefore: string
|
|
186
|
+
textAfter: string
|
|
187
|
+
}
|
|
188
|
+
linkColor: {
|
|
189
|
+
inactive: string
|
|
190
|
+
default: string
|
|
191
|
+
}
|
|
192
|
+
}
|
|
175
193
|
}
|
|
176
194
|
|
|
177
195
|
export type ForestPlotConfig = {
|