@cdc/chart 4.24.2 → 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 +47386 -36618
- 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 +2 -1
- 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/sparkline.json +868 -0
- package/index.html +128 -123
- package/package.json +4 -2
- package/src/CdcChart.tsx +40 -22
- 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 +39 -49
- 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 +40 -55
- package/src/components/BoxPlot/BoxPlot.jsx +2 -1
- package/src/components/DeviationBar.jsx +3 -3
- package/src/components/EditorPanel/EditorPanel.tsx +167 -15
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +1 -1
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +108 -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/useEditorPermissions.js +40 -14
- package/src/components/Legend/Legend.Component.tsx +23 -15
- package/src/components/Legend/Legend.tsx +4 -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 +9 -30
- 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 +15 -18
- package/src/hooks/useTooltip.tsx +9 -8
- package/src/scss/main.scss +8 -29
- package/src/types/ChartConfig.ts +32 -14
- package/src/types/ChartContext.ts +7 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/* KPI */
|
|
2
|
+
.kpis-container {
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: row;
|
|
5
|
+
column-gap: 30px;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.cdc-open-viz-module .sankey-chart {
|
|
9
|
+
--font-size-small: 12px;
|
|
10
|
+
--font-size-medium: 14px;
|
|
11
|
+
--font-size-large: 18px;
|
|
12
|
+
--font-size-xl: 24px;
|
|
13
|
+
|
|
14
|
+
--storynode-font-size--small: 24px;
|
|
15
|
+
--storynode-font-size--medium: 28px;
|
|
16
|
+
--storynode-font-size--large: 32px;
|
|
17
|
+
|
|
18
|
+
--font-weight-normal: 400;
|
|
19
|
+
--font-weight-bold: 700;
|
|
20
|
+
|
|
21
|
+
overflow: visible;
|
|
22
|
+
|
|
23
|
+
.divider {
|
|
24
|
+
border-top: 1px solid #000;
|
|
25
|
+
margin: 10px 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
svg.sankey-chart__diagram {
|
|
29
|
+
position:relative;
|
|
30
|
+
font-family: 'Roboto', sans-serif;
|
|
31
|
+
height: auto;
|
|
32
|
+
width: 100%;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.node-id {
|
|
36
|
+
font-weight: var(--font-weight-bold);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.node-value {
|
|
40
|
+
font-weight: var(--font-weight-normal);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.node-text {
|
|
44
|
+
font-weight: var(--font-weight-normal);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.node-value--storynode {
|
|
48
|
+
font-weight: var(--font-weight-bold);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Hover card */
|
|
52
|
+
.sankey-chart__tooltip {
|
|
53
|
+
color: black;
|
|
54
|
+
display: flex;
|
|
55
|
+
flex-direction: column;
|
|
56
|
+
margin: 10px;
|
|
57
|
+
&--tooltip-header {
|
|
58
|
+
font-weight: var(--font-weight-bold);
|
|
59
|
+
}
|
|
60
|
+
&--info-section {
|
|
61
|
+
column-gap: 10px;
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: row;
|
|
64
|
+
justify-content: space-between;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
span {
|
|
69
|
+
max-width: 500px;
|
|
70
|
+
word-wrap: break-word;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Autoscaling */
|
|
74
|
+
//large - default
|
|
75
|
+
@media only screen and (min-width: 1200px) {
|
|
76
|
+
min-width: none;
|
|
77
|
+
.node-text {
|
|
78
|
+
font-size: var(--font-size-xl);
|
|
79
|
+
}
|
|
80
|
+
.node-value--storynode {
|
|
81
|
+
font-size: var(--storynode-font-size--large);
|
|
82
|
+
}
|
|
83
|
+
.node-id {
|
|
84
|
+
font-size: var(--font-size-large);
|
|
85
|
+
}
|
|
86
|
+
.node-value {
|
|
87
|
+
font-size: var(--font-size-large);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//medium
|
|
91
|
+
@media only screen and (max-width: 1199px) {
|
|
92
|
+
.node-text {
|
|
93
|
+
font-size: var(--font-size-medium);
|
|
94
|
+
}
|
|
95
|
+
.node-value--storynode {
|
|
96
|
+
font-size: var(--storynode-font-size--medium);
|
|
97
|
+
}
|
|
98
|
+
.node-id {
|
|
99
|
+
font-size: var(--font-size-medium);
|
|
100
|
+
}
|
|
101
|
+
.node-value {
|
|
102
|
+
font-size: var(--font-size-medium);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
//small
|
|
107
|
+
@media only screen and (max-width: 799px) {
|
|
108
|
+
.node-text {
|
|
109
|
+
font-size: var(--font-size-small);
|
|
110
|
+
}
|
|
111
|
+
.node-value--storynode {
|
|
112
|
+
font-size: var(--storynode-font-size--small);
|
|
113
|
+
}
|
|
114
|
+
.node-id {
|
|
115
|
+
font-size: var(--font-size-small);
|
|
116
|
+
}
|
|
117
|
+
.node-value {
|
|
118
|
+
font-size: var(--font-size-small);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
//x-small
|
|
123
|
+
@media only screen and (max-width: 600px) {
|
|
124
|
+
.popup {
|
|
125
|
+
display: block; /* Show the popup on smaller screens */
|
|
126
|
+
}
|
|
127
|
+
.sankey-chart__diagram {
|
|
128
|
+
opacity: .1;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Pop up */
|
|
134
|
+
|
|
135
|
+
.popup {
|
|
136
|
+
position: absolute;
|
|
137
|
+
top: 50%;
|
|
138
|
+
left: 50%;
|
|
139
|
+
transform: translate(-50%, -50%);
|
|
140
|
+
background-color: beige;
|
|
141
|
+
border: 2px solid gray !important;
|
|
142
|
+
border-radius: 8px;
|
|
143
|
+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
144
|
+
width: 80%;
|
|
145
|
+
z-index: 999;
|
|
146
|
+
display: none;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.popup-content {
|
|
150
|
+
font-size: 30px;
|
|
151
|
+
padding: 10px;
|
|
152
|
+
text-align: center;
|
|
153
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type Link = { source: string; target: string; value: number }
|
|
2
|
+
|
|
3
|
+
export type Data = {
|
|
4
|
+
links: Link[]
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type SankeyNode = {
|
|
8
|
+
id: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type SankeyProps = {
|
|
12
|
+
width: number
|
|
13
|
+
height: number
|
|
14
|
+
data: Data
|
|
15
|
+
runtime: any
|
|
16
|
+
}
|
|
@@ -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
|
|
@@ -249,8 +249,7 @@ const composeXScale = ({ min, max, xMax, config }) => {
|
|
|
249
249
|
domain: [min, max],
|
|
250
250
|
range: [0, xMax],
|
|
251
251
|
nice: config.useLogScale,
|
|
252
|
-
zero: config.useLogScale
|
|
253
|
-
type: config.useLogScale ? 'log' : 'linear'
|
|
252
|
+
zero: config.useLogScale
|
|
254
253
|
})
|
|
255
254
|
}
|
|
256
255
|
|
|
@@ -284,8 +283,7 @@ const composeScalePoint = (domain, range, padding = 0) => {
|
|
|
284
283
|
return scalePoint({
|
|
285
284
|
domain: domain,
|
|
286
285
|
range: range,
|
|
287
|
-
padding: padding
|
|
288
|
-
type: 'point'
|
|
286
|
+
padding: padding
|
|
289
287
|
})
|
|
290
288
|
}
|
|
291
289
|
|
|
@@ -293,7 +291,6 @@ const composeScaleBand = (domain, range, padding = 0) => {
|
|
|
293
291
|
return scaleBand({
|
|
294
292
|
domain: domain,
|
|
295
293
|
range: range,
|
|
296
|
-
padding: padding
|
|
297
|
-
type: 'band'
|
|
294
|
+
padding: padding
|
|
298
295
|
})
|
|
299
296
|
}
|
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;
|
|
@@ -167,11 +143,11 @@
|
|
|
167
143
|
flex: 0 0 auto;
|
|
168
144
|
}
|
|
169
145
|
|
|
170
|
-
|
|
171
|
-
font-size: 1.
|
|
146
|
+
h3 {
|
|
147
|
+
font-size: 1.3rem;
|
|
172
148
|
}
|
|
173
149
|
|
|
174
|
-
|
|
150
|
+
h3,
|
|
175
151
|
p {
|
|
176
152
|
margin-bottom: 0.8em;
|
|
177
153
|
}
|
|
@@ -528,21 +504,24 @@
|
|
|
528
504
|
}
|
|
529
505
|
|
|
530
506
|
&.animated {
|
|
507
|
+
.vertical path,
|
|
531
508
|
.vertical rect,
|
|
532
509
|
.vertical foreignObject div {
|
|
533
510
|
opacity: 0;
|
|
534
|
-
animation: growBar 0.5s linear
|
|
511
|
+
animation: growBar 0.5s linear both;
|
|
535
512
|
animation-play-state: paused;
|
|
536
513
|
}
|
|
537
514
|
|
|
515
|
+
.horizontal path,
|
|
538
516
|
.horizontal rect,
|
|
539
517
|
.horizontal foreignObject div {
|
|
540
518
|
opacity: 0;
|
|
541
|
-
animation: growBarH 0.5s linear
|
|
519
|
+
animation: growBarH 0.5s linear both;
|
|
542
520
|
animation-play-state: paused;
|
|
543
521
|
}
|
|
544
522
|
|
|
545
523
|
&.animate {
|
|
524
|
+
path,
|
|
546
525
|
rect,
|
|
547
526
|
foreignObject div {
|
|
548
527
|
animation-play-state: running;
|