@cdc/chart 4.25.3 → 4.25.5-1
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 +36051 -36995
- package/index.html +2 -1
- package/package.json +22 -27
- package/src/CdcChartComponent.tsx +10 -10
- package/src/_stories/Chart.CI.stories.tsx +10 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +68 -49
- package/src/_stories/Chart.stories.tsx +7 -0
- package/{examples/private/line-issue.json → src/_stories/_mock/barchart_labels.mock.json} +150 -35
- package/src/_stories/_mock/dynamic_series_bar_config.json +1 -1
- package/src/_stories/_mock/dynamic_series_suppression_mock.json +610 -0
- package/src/components/Annotations/components/AnnotationDropdown.tsx +2 -2
- package/src/components/AreaChart/components/AreaChart.jsx +33 -5
- package/src/components/Axis/Categorical.Axis.tsx +2 -2
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +50 -40
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +18 -8
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +19 -8
- package/src/components/BarChart/components/BarChart.Vertical.tsx +47 -30
- package/src/components/BarChart/components/{BarChart.jsx → BarChart.tsx} +23 -5
- package/src/components/BarChart/components/context.tsx +20 -2
- package/src/components/BarChart/helpers/getBarHeights.ts +47 -0
- package/src/components/BarChart/helpers/index.ts +5 -2
- package/src/components/BarChart/helpers/tests/getBarHeights.test.ts +83 -0
- package/src/{hooks → components/BarChart/helpers}/useBarChart.ts +9 -46
- package/src/components/BoxPlot/BoxPlot.tsx +2 -1
- package/src/components/DeviationBar.jsx +2 -1
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +34 -34
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +51 -25
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +10 -3
- package/src/components/EditorPanel/useEditorPermissions.ts +1 -4
- package/src/components/ForestPlot/ForestPlot.tsx +2 -2
- package/src/components/Legend/Legend.Component.tsx +1 -1
- package/src/components/Legend/Legend.Suppression.tsx +12 -22
- package/src/components/Legend/helpers/createFormatLabels.tsx +28 -0
- package/src/components/LineChart/LineChartProps.ts +3 -1
- package/src/components/LineChart/components/LineChart.Circle.tsx +72 -119
- package/src/components/LineChart/helpers.ts +133 -56
- package/src/components/LineChart/index.tsx +107 -53
- package/src/components/LinearChart.tsx +40 -89
- package/src/components/PairedBarChart.jsx +3 -2
- package/src/components/PieChart/PieChart.tsx +71 -91
- package/src/components/ScatterPlot/ScatterPlot.jsx +5 -0
- package/src/components/Sparkline/components/SparkLine.tsx +80 -18
- package/src/components/ZoomBrush.tsx +4 -4
- package/src/data/initial-state.js +3 -1
- package/src/helpers/countNumOfTicks.ts +1 -1
- package/src/helpers/dataHelpers.ts +23 -2
- package/src/helpers/sizeHelpers.ts +1 -1
- package/src/hooks/useMinMax.ts +21 -28
- package/src/hooks/useRightAxis.ts +4 -2
- package/src/hooks/useScales.ts +10 -6
- package/src/hooks/useTooltip.tsx +204 -203
- package/src/index.jsx +2 -2
- package/src/scss/main.scss +13 -6
- package/src/types/ChartConfig.ts +5 -0
- package/LICENSE +0 -201
- package/examples/private/DEV-8850-2.json +0 -493
- package/examples/private/DEV-9822.json +0 -574
- package/examples/private/DEV-9840.json +0 -553
- package/examples/private/DEV-9850-3.json +0 -461
- package/examples/private/chart.json +0 -1084
- package/examples/private/ci_formatted.json +0 -202
- package/examples/private/ci_issue.json +0 -3016
- package/examples/private/completed.json +0 -634
- package/examples/private/dem-data-long.csv +0 -20
- package/examples/private/dem-data-long.json +0 -36
- package/examples/private/demographic_data.csv +0 -157
- package/examples/private/demographic_data.json +0 -2654
- package/examples/private/demographic_dynamic.json +0 -443
- package/examples/private/demographic_standard.json +0 -560
- package/examples/private/ehdi.json +0 -29939
- package/examples/private/not-loading.json +0 -360
- package/examples/private/test.json +0 -493
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useContext, useState, useEffect, useRef, useMemo } from 'react'
|
|
2
|
-
import { animated, useTransition,
|
|
2
|
+
import { animated, useTransition, to } from '@react-spring/web'
|
|
3
3
|
|
|
4
4
|
// visx
|
|
5
5
|
import { Pie } from '@visx/shape'
|
|
@@ -14,16 +14,9 @@ import { useTooltip as useCoveTooltip } from '../../hooks/useTooltip'
|
|
|
14
14
|
import useIntersectionObserver from '../../hooks/useIntersectionObserver'
|
|
15
15
|
import { handleChartAriaLabels } from '../../helpers/handleChartAriaLabels'
|
|
16
16
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
17
|
-
import LegendComponent from '../Legend/Legend.Component'
|
|
18
|
-
import { createFormatLabels } from '../Legend/helpers/createFormatLabels'
|
|
19
17
|
import { scaleOrdinal } from '@visx/scale'
|
|
20
18
|
import { getContrastColor } from '@cdc/core/helpers/cove/accessibility'
|
|
21
19
|
|
|
22
|
-
const enterUpdateTransition = ({ startAngle, endAngle }) => ({
|
|
23
|
-
startAngle,
|
|
24
|
-
endAngle
|
|
25
|
-
})
|
|
26
|
-
|
|
27
20
|
type TooltipData = {
|
|
28
21
|
data: {
|
|
29
22
|
[key: string]: string | number
|
|
@@ -117,92 +110,71 @@ const PieChart = props => {
|
|
|
117
110
|
}
|
|
118
111
|
}, [dataRef?.isIntersecting, config.animate]) // eslint-disable-line
|
|
119
112
|
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
113
|
+
function AnimatedPie({ arcs, path, getKey, colorScale, onHover, onLeave }) {
|
|
114
|
+
const enterExit = ({ startAngle, endAngle }) => ({ startAngle, endAngle })
|
|
115
|
+
const transitions = useTransition(arcs, {
|
|
116
|
+
keys: getKey,
|
|
117
|
+
from: enterExit,
|
|
118
|
+
enter: enterExit,
|
|
119
|
+
update: enterExit,
|
|
120
|
+
leave: enterExit
|
|
126
121
|
})
|
|
127
122
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
// and use this useEffect to hide the tooltip so it doesn't persist when users scroll.
|
|
132
|
-
useEffect(() => {
|
|
133
|
-
const timeout = setTimeout(() => {
|
|
134
|
-
hideTooltip()
|
|
135
|
-
}, 500)
|
|
136
|
-
return () => {
|
|
137
|
-
clearTimeout(timeout)
|
|
138
|
-
}
|
|
139
|
-
}, [tooltipData])
|
|
140
|
-
|
|
141
|
-
return (
|
|
142
|
-
<>
|
|
143
|
-
{transitions.map(({ item: arc, props, key }, animatedPieIndex) => {
|
|
144
|
-
return (
|
|
145
|
-
<Group
|
|
146
|
-
className={arc.data[config.xAxis.dataKey]}
|
|
147
|
-
key={`${key}-${animatedPieIndex}`}
|
|
148
|
-
style={{
|
|
149
|
-
opacity:
|
|
150
|
-
config.legend.behavior === 'highlight' &&
|
|
151
|
-
seriesHighlight.length > 0 &&
|
|
152
|
-
seriesHighlight.indexOf(arc.data[config.runtime.xAxis.dataKey]) === -1
|
|
153
|
-
? 0.5
|
|
154
|
-
: 1
|
|
155
|
-
}}
|
|
156
|
-
>
|
|
157
|
-
<animated.path
|
|
158
|
-
d={interpolate([props.startAngle, props.endAngle], (startAngle, endAngle) =>
|
|
159
|
-
path({
|
|
160
|
-
...arc,
|
|
161
|
-
startAngle,
|
|
162
|
-
endAngle
|
|
163
|
-
})
|
|
164
|
-
)}
|
|
165
|
-
fill={_colorScale(arc.data[config.runtime.xAxis.dataKey])}
|
|
166
|
-
onMouseEnter={e => handleTooltipMouseOver(e, { data: arc.data[config.runtime.xAxis.dataKey], arc })}
|
|
167
|
-
onMouseLeave={e => handleTooltipMouseOff()}
|
|
168
|
-
/>
|
|
169
|
-
</Group>
|
|
170
|
-
)
|
|
171
|
-
})}
|
|
172
|
-
{transitions.map(({ item: arc, key }, i) => {
|
|
173
|
-
const roundTo = Number(config.dataFormat.roundTo) || 0
|
|
174
|
-
const [centroidX, centroidY] = path.centroid(arc)
|
|
175
|
-
const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.1
|
|
123
|
+
return transitions((styles, arc) => {
|
|
124
|
+
const key = getKey(arc)
|
|
125
|
+
let textColor = '#FFF'
|
|
176
126
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
127
|
+
if (key && _colorScale(key)) {
|
|
128
|
+
textColor = getContrastColor(textColor, _colorScale(arc.data[config.runtime.xAxis.dataKey]))
|
|
129
|
+
}
|
|
130
|
+
const roundTo = Number(config.dataFormat.roundTo) || 0
|
|
131
|
+
// Calculate the percentage of the full circle (360 degrees)
|
|
132
|
+
const degrees = ((arc.endAngle - arc.startAngle) * 180) / Math.PI
|
|
182
133
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
134
|
+
const percentageOfCircle = (degrees / 360) * 100
|
|
135
|
+
const roundedPercentage = percentageOfCircle.toFixed(roundTo) + '%'
|
|
136
|
+
return (
|
|
137
|
+
<Group key={key} className={`slice-${key}`}>
|
|
138
|
+
{/* ── the slice */}
|
|
139
|
+
<animated.path
|
|
140
|
+
d={to([styles.startAngle, styles.endAngle], (start, end) =>
|
|
141
|
+
path({ ...arc, startAngle: start, endAngle: end })
|
|
142
|
+
)}
|
|
143
|
+
fill={colorScale(key)}
|
|
144
|
+
onMouseEnter={e =>
|
|
145
|
+
onHover(e, {
|
|
146
|
+
data: arc.data,
|
|
147
|
+
dataXPosition: e.clientX,
|
|
148
|
+
dataYPosition: e.clientY,
|
|
149
|
+
startAngle: arc.startAngle,
|
|
150
|
+
endAngle: arc.endAngle
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
onMouseLeave={onLeave}
|
|
154
|
+
/>
|
|
186
155
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
156
|
+
{/* ── the percentage label */}
|
|
157
|
+
{arc.endAngle - arc.startAngle > 0.1 && (
|
|
158
|
+
<animated.text
|
|
159
|
+
transform={to([styles.startAngle, styles.endAngle], (start, end) => {
|
|
160
|
+
const [x, y] = path.centroid({
|
|
161
|
+
...arc,
|
|
162
|
+
startAngle: start,
|
|
163
|
+
endAngle: end
|
|
164
|
+
})
|
|
165
|
+
return `translate(${x},${y})`
|
|
166
|
+
})}
|
|
167
|
+
textAnchor='middle'
|
|
168
|
+
pointerEvents='none'
|
|
169
|
+
fill={textColor}
|
|
170
|
+
>
|
|
171
|
+
{/** compute text inside the spring callback */}
|
|
172
|
+
{roundedPercentage}
|
|
173
|
+
</animated.text>
|
|
174
|
+
)}
|
|
175
|
+
</Group>
|
|
176
|
+
)
|
|
177
|
+
})
|
|
206
178
|
}
|
|
207
179
|
|
|
208
180
|
let chartWidth = props.parentWidth
|
|
@@ -257,12 +229,20 @@ const PieChart = props => {
|
|
|
257
229
|
{/* prettier-ignore */}
|
|
258
230
|
<Pie
|
|
259
231
|
data={filteredData || _data}
|
|
260
|
-
pieValue={d => d[pivotKey || config.runtime.yAxis.dataKey]}
|
|
232
|
+
pieValue={d => parseFloat(d[pivotKey || config.runtime.yAxis.dataKey])}
|
|
261
233
|
pieSortValues={() => -1}
|
|
262
234
|
innerRadius={radius - donutThickness}
|
|
263
235
|
outerRadius={radius}
|
|
264
236
|
>
|
|
265
|
-
|
|
237
|
+
{pie => (
|
|
238
|
+
<AnimatedPie
|
|
239
|
+
{...pie}
|
|
240
|
+
getKey={d => d.data[config.runtime.xAxis.dataKey]}
|
|
241
|
+
colorScale={_colorScale}
|
|
242
|
+
onHover={handleTooltipMouseOver}
|
|
243
|
+
onLeave={handleTooltipMouseOff}
|
|
244
|
+
/>
|
|
245
|
+
)}
|
|
266
246
|
</Pie>
|
|
267
247
|
</Group>
|
|
268
248
|
</svg>
|
|
@@ -18,17 +18,29 @@ type SparkLineProps = {
|
|
|
18
18
|
|
|
19
19
|
const SparkLine: React.FC<SparkLineProps> = props => {
|
|
20
20
|
const { width: parentWidth, height: parentHeight } = props
|
|
21
|
-
const {
|
|
21
|
+
const {
|
|
22
|
+
transformedData: data,
|
|
23
|
+
config,
|
|
24
|
+
parseDate,
|
|
25
|
+
formatDate,
|
|
26
|
+
seriesHighlight,
|
|
27
|
+
formatNumber,
|
|
28
|
+
colorScale,
|
|
29
|
+
handleChartAriaLabels
|
|
30
|
+
} = useContext(ConfigContext)
|
|
22
31
|
let width = Number(parentWidth)
|
|
23
32
|
const { minValue, maxValue } = useReduceData(config, data, ConfigContext)
|
|
24
33
|
|
|
25
|
-
const margin = { top: 5, right:
|
|
34
|
+
const margin = { top: 5, right: 20, bottom: 10, left: 10 }
|
|
26
35
|
const height = Number(parentHeight)
|
|
27
36
|
|
|
28
37
|
const xMax = width - config.runtime.yAxis.size
|
|
29
38
|
const yMax = height - margin.top - 20
|
|
30
39
|
|
|
31
|
-
const getXAxisData = d =>
|
|
40
|
+
const getXAxisData = d =>
|
|
41
|
+
config.runtime.xAxis.type === 'date'
|
|
42
|
+
? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime()
|
|
43
|
+
: d[config.runtime.originalXAxis.dataKey]
|
|
32
44
|
const getYAxisData = (d, seriesKey) => d[seriesKey]
|
|
33
45
|
|
|
34
46
|
let xScale
|
|
@@ -61,7 +73,10 @@ const SparkLine: React.FC<SparkLineProps> = props => {
|
|
|
61
73
|
range: [0, xMax]
|
|
62
74
|
})
|
|
63
75
|
|
|
64
|
-
yScale =
|
|
76
|
+
yScale =
|
|
77
|
+
config.runtime.xAxis.type === 'date'
|
|
78
|
+
? scaleLinear({ domain: [Math.min(...xAxisDataMapped), Math.max(...xAxisDataMapped)] })
|
|
79
|
+
: scalePoint({ domain: xAxisDataMapped, padding: 0.5 })
|
|
65
80
|
|
|
66
81
|
seriesScale = scalePoint({
|
|
67
82
|
domain: config.runtime.barSeriesKeys || config.runtime.seriesKeys,
|
|
@@ -94,7 +109,14 @@ const SparkLine: React.FC<SparkLineProps> = props => {
|
|
|
94
109
|
|
|
95
110
|
return (
|
|
96
111
|
<ErrorBoundary component='SparkLine'>
|
|
97
|
-
<svg
|
|
112
|
+
<svg
|
|
113
|
+
role='img'
|
|
114
|
+
aria-label={handleChartAriaLabels(config)}
|
|
115
|
+
width={parentWidth}
|
|
116
|
+
height={100}
|
|
117
|
+
className={'sparkline'}
|
|
118
|
+
tabIndex={0}
|
|
119
|
+
>
|
|
98
120
|
<title>{`Spark line graphic with the title ${config.title ? config.title : 'No Title Found'}`}</title>
|
|
99
121
|
{config.runtime.lineSeriesKeys?.length > 0
|
|
100
122
|
? config.runtime.lineSeriesKeys
|
|
@@ -104,30 +126,70 @@ const SparkLine: React.FC<SparkLineProps> = props => {
|
|
|
104
126
|
style={{ width }}
|
|
105
127
|
className='sparkline-group'
|
|
106
128
|
key={`series-${seriesKey}`}
|
|
107
|
-
opacity={
|
|
108
|
-
|
|
129
|
+
opacity={
|
|
130
|
+
config.legend.behavior === 'highlight' &&
|
|
131
|
+
seriesHighlight.length > 0 &&
|
|
132
|
+
seriesHighlight.indexOf(seriesKey) === -1
|
|
133
|
+
? 0.5
|
|
134
|
+
: 1
|
|
135
|
+
}
|
|
136
|
+
display={
|
|
137
|
+
config.legend.behavior === 'highlight' ||
|
|
138
|
+
seriesHighlight.length === 0 ||
|
|
139
|
+
seriesHighlight.indexOf(seriesKey) !== -1
|
|
140
|
+
? 'block'
|
|
141
|
+
: 'none'
|
|
142
|
+
}
|
|
109
143
|
>
|
|
110
|
-
{config.labels &&
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
144
|
+
{config.labels &&
|
|
145
|
+
data.map((d, dataIndex) => {
|
|
146
|
+
return (
|
|
147
|
+
<Group key={`series-${seriesKey}-point-${dataIndex}`}>
|
|
148
|
+
<Text
|
|
149
|
+
x={xScale(getXAxisData(d))}
|
|
150
|
+
y={yScale(getYAxisData(d, seriesKey))}
|
|
151
|
+
fill={
|
|
152
|
+
colorScale
|
|
153
|
+
? colorScale(
|
|
154
|
+
config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey
|
|
155
|
+
)
|
|
156
|
+
: '#000'
|
|
157
|
+
}
|
|
158
|
+
textAnchor='middle'
|
|
159
|
+
>
|
|
160
|
+
{formatNumber(d[seriesKey])}
|
|
161
|
+
</Text>
|
|
162
|
+
</Group>
|
|
163
|
+
)
|
|
164
|
+
})}
|
|
119
165
|
<LinePath
|
|
120
166
|
curve={allCurves.curveLinear}
|
|
121
167
|
data={data}
|
|
122
168
|
x={d => xScale(getXAxisData(d))}
|
|
123
169
|
y={d => yScale(getYAxisData(d, seriesKey))}
|
|
124
|
-
stroke={
|
|
170
|
+
stroke={
|
|
171
|
+
colorScale
|
|
172
|
+
? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey)
|
|
173
|
+
: '#000'
|
|
174
|
+
}
|
|
125
175
|
strokeWidth={2}
|
|
126
176
|
strokeOpacity={1}
|
|
127
177
|
shapeRendering='geometricPrecision'
|
|
128
178
|
markerEnd={`url(#${'arrow'}--${index})`}
|
|
129
179
|
/>
|
|
130
|
-
<MarkerArrow
|
|
180
|
+
<MarkerArrow
|
|
181
|
+
id={`arrow--${index}`}
|
|
182
|
+
refX={2}
|
|
183
|
+
size={6}
|
|
184
|
+
markerEnd={`url(#${'arrow'}--${index})`}
|
|
185
|
+
strokeOpacity={1}
|
|
186
|
+
fillOpacity={1}
|
|
187
|
+
fill={
|
|
188
|
+
colorScale
|
|
189
|
+
? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey)
|
|
190
|
+
: '#000'
|
|
191
|
+
}
|
|
192
|
+
/>
|
|
131
193
|
</Group>
|
|
132
194
|
<AxisBottom
|
|
133
195
|
top={yMax + margin.top}
|
|
@@ -7,7 +7,7 @@ import { ScaleLinear, ScaleBand } from 'd3-scale'
|
|
|
7
7
|
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
8
8
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
9
9
|
import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
|
|
10
|
-
import {
|
|
10
|
+
import { APP_FONT_SIZE } from '@cdc/core/helpers/constants'
|
|
11
11
|
|
|
12
12
|
interface Props {
|
|
13
13
|
xScaleBrush: ScaleLinear<number, number>
|
|
@@ -210,7 +210,7 @@ const BrushHandle = props => {
|
|
|
210
210
|
const transform = isLeft ? 'scale(-1, 1)' : 'translate(0,0)'
|
|
211
211
|
const textAnchor = isLeft ? 'end' : 'start'
|
|
212
212
|
const tooltipText = isLeft ? ` Drag edges to focus on a specific segment ` : ''
|
|
213
|
-
const textWidth = getTextWidth(tooltipText, `${
|
|
213
|
+
const textWidth = getTextWidth(tooltipText, `${APP_FONT_SIZE / 1.1}px`)
|
|
214
214
|
|
|
215
215
|
return (
|
|
216
216
|
<>
|
|
@@ -219,7 +219,7 @@ const BrushHandle = props => {
|
|
|
219
219
|
x={(Number(textProps.xMax) - textWidth) / 2}
|
|
220
220
|
dy={-12}
|
|
221
221
|
pointerEvents='visiblePainted'
|
|
222
|
-
fontSize={
|
|
222
|
+
fontSize={APP_FONT_SIZE / 1.1}
|
|
223
223
|
>
|
|
224
224
|
{tooltipText}
|
|
225
225
|
</Text>
|
|
@@ -232,7 +232,7 @@ const BrushHandle = props => {
|
|
|
232
232
|
y={25}
|
|
233
233
|
verticalAnchor='start'
|
|
234
234
|
textAnchor={textAnchor}
|
|
235
|
-
fontSize={
|
|
235
|
+
fontSize={APP_FONT_SIZE / 1.4}
|
|
236
236
|
>
|
|
237
237
|
{isLeft ? textProps.startValue : textProps.endValue}
|
|
238
238
|
</Text>
|
|
@@ -168,11 +168,13 @@ export default {
|
|
|
168
168
|
groupBy: '',
|
|
169
169
|
shape: 'circle',
|
|
170
170
|
tickRotation: '',
|
|
171
|
+
order: 'dataColumn',
|
|
171
172
|
hideBorder: {
|
|
172
173
|
side: false,
|
|
173
174
|
topBottom: true
|
|
174
175
|
},
|
|
175
|
-
position: 'right'
|
|
176
|
+
position: 'right',
|
|
177
|
+
orderedValues: []
|
|
176
178
|
},
|
|
177
179
|
brush: {
|
|
178
180
|
height: 45,
|
|
@@ -25,7 +25,7 @@ export const countNumOfTicks = ({ axis, max, runtime, currentViewport, isHorizon
|
|
|
25
25
|
}
|
|
26
26
|
if (Number(tickCount) > Number(max) && !isHorizontal) {
|
|
27
27
|
// cap it and round it so its an integer
|
|
28
|
-
tickCount = Number(min) < 0 ? Math.round(max) * 2 : Math.round(max)
|
|
28
|
+
tickCount = Math.max(2, Number(min) < 0 ? Math.round(max) * 2 : Math.round(max))
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -1,10 +1,31 @@
|
|
|
1
|
+
import { Series } from '@cdc/core/types/Series'
|
|
1
2
|
import { ChartConfig } from '../types/ChartConfig'
|
|
2
3
|
|
|
3
4
|
export const getSeriesWithData = (config: ChartConfig) => {
|
|
4
|
-
const { filters, data, runtime } = config
|
|
5
|
+
const { filters, data, runtime, legend } = config
|
|
6
|
+
const { colorCode } = legend
|
|
5
7
|
const { series } = runtime
|
|
6
8
|
|
|
7
9
|
const filteredData = data.filter(d => filters.every(f => d[f.columnName] === f.active))
|
|
10
|
+
const colorCodeSeries = colorCode && Array.from(new Set(filteredData.map(d => d[colorCode])))
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
const result = series
|
|
13
|
+
.flatMap(s => {
|
|
14
|
+
if (!colorCode || s.type !== 'Bar') return s
|
|
15
|
+
return colorCodeSeries.map(c => ({ ...s, colorCodeSeries: c }))
|
|
16
|
+
})
|
|
17
|
+
.map(s => ({
|
|
18
|
+
...s,
|
|
19
|
+
data: filteredData
|
|
20
|
+
.filter(d => !s.dynamicCategory || d[s.dynamicCategory] === s.dataKey)
|
|
21
|
+
.filter(d => !s.colorCodeSeries || d[colorCode] === s.colorCodeSeries)
|
|
22
|
+
.filter(d => {
|
|
23
|
+
const key = s.dynamicCategory ? s.originalDataKey : s.dataKey
|
|
24
|
+
return d[key] || d[key] === 0
|
|
25
|
+
})
|
|
26
|
+
}))
|
|
27
|
+
.filter(s => s.data.length)
|
|
28
|
+
.map(s => s.colorCodeSeries || s.name || s.dataKey)
|
|
29
|
+
|
|
30
|
+
return result
|
|
10
31
|
}
|
|
@@ -2,7 +2,7 @@ import { clamp } from 'lodash'
|
|
|
2
2
|
|
|
3
3
|
import { isMobileHeightViewport } from '@cdc/core/helpers/viewports'
|
|
4
4
|
import { ChartConfig, ViewportSize } from '../types/ChartConfig'
|
|
5
|
-
import { EDITOR_WIDTH } from '
|
|
5
|
+
import { EDITOR_WIDTH } from '@cdc/core/helpers/constants'
|
|
6
6
|
|
|
7
7
|
export function getOrientation(
|
|
8
8
|
{ orientation, heights, visualizationType }: Pick<ChartConfig, 'orientation' | 'heights' | 'visualizationType'>,
|
package/src/hooks/useMinMax.ts
CHANGED
|
@@ -46,7 +46,6 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
46
46
|
|
|
47
47
|
min = enteredMinValue && isMinValid ? Number(enteredMinValue) : minValue
|
|
48
48
|
max = enteredMaxValue && isMaxValid ? Number(enteredMaxValue) : Number.MIN_VALUE
|
|
49
|
-
|
|
50
49
|
const { lower, upper } = config?.confidenceKeys || {}
|
|
51
50
|
|
|
52
51
|
if (lower && upper && config.visualizationType === 'Bar') {
|
|
@@ -167,35 +166,28 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
167
166
|
}
|
|
168
167
|
|
|
169
168
|
if (config.visualizationType === 'Line' && !convertLineToBarGraph) {
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
// Return true if the value matches and it's either the first or the last item
|
|
186
|
-
return valueMatch && (index === 0 || index === tableData.length - 1)
|
|
169
|
+
const numEnteredMin = Number(enteredMinValue)
|
|
170
|
+
const isMinValid = isLogarithmicAxis ? numEnteredMin >= 0 && numEnteredMin < minValue : numEnteredMin < minValue
|
|
171
|
+
|
|
172
|
+
const suppressedMinValue = tableData?.some((item, i, arr) =>
|
|
173
|
+
config.preliminaryData?.some(({ type, style, column, value }) => {
|
|
174
|
+
if (type !== 'suppression' || !style) return false
|
|
175
|
+
|
|
176
|
+
const values = _.values(_.pick(item, config.runtime?.seriesKeys))
|
|
177
|
+
const dynamicCategory = config.series[0].dynamicCategory
|
|
178
|
+
|
|
179
|
+
const match = column ? item[column] === value : values.includes(value)
|
|
180
|
+
const dynamic = dynamicCategory && (item[dynamicCategory] === column || !column)
|
|
181
|
+
|
|
182
|
+
return (match || dynamic) && (i === 0 || i === arr.length - 1)
|
|
187
183
|
})
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
: suppressedMinValue
|
|
194
|
-
? 0
|
|
195
|
-
: isCategoricalAxis
|
|
196
|
-
? 0
|
|
197
|
-
: minValue
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
const isCategorical = config.yAxis.type === 'categorical'
|
|
187
|
+
|
|
188
|
+
min = enteredMinValue !== '' && isMinValid ? numEnteredMin : suppressedMinValue ? 0 : isCategorical ? 0 : minValue
|
|
198
189
|
}
|
|
190
|
+
|
|
199
191
|
//If data value max wasn't provided, calculate it
|
|
200
192
|
if (max === Number.MIN_VALUE) {
|
|
201
193
|
// if all values in data are negative set max = 0
|
|
@@ -241,6 +233,7 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
241
233
|
|
|
242
234
|
if (config.visualizationType === 'Scatter Plot') {
|
|
243
235
|
max = max * 1.1
|
|
236
|
+
min = min / 1.1
|
|
244
237
|
}
|
|
245
238
|
|
|
246
239
|
return { min, max, leftMax, rightMax }
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { scaleLinear } from '@visx/scale'
|
|
2
2
|
import useReduceData from './useReduceData'
|
|
3
|
+
import { TOP_PADDING } from './useScales'
|
|
3
4
|
|
|
4
5
|
export default function useRightAxis({ config, yMax = 0, data = [], updateConfig }) {
|
|
5
6
|
const hasRightAxis = config.visualizationType === 'Combo' && config.orientation === 'vertical'
|
|
6
|
-
const rightSeriesKeys =
|
|
7
|
+
const rightSeriesKeys =
|
|
8
|
+
config.series && config.series.filter(series => series.axis === 'Right').map(key => key.dataKey)
|
|
7
9
|
let { minValue } = useReduceData(config, data)
|
|
8
10
|
|
|
9
11
|
const allRightAxisData = rightSeriesKeys => {
|
|
@@ -35,7 +37,7 @@ export default function useRightAxis({ config, yMax = 0, data = [], updateConfig
|
|
|
35
37
|
|
|
36
38
|
const yScaleRight = scaleLinear({
|
|
37
39
|
domain: [minValue, max],
|
|
38
|
-
range: [yMax,
|
|
40
|
+
range: [yMax, TOP_PADDING]
|
|
39
41
|
})
|
|
40
42
|
|
|
41
43
|
return { yScaleRight, hasRightAxis }
|
package/src/hooks/useScales.ts
CHANGED
|
@@ -22,6 +22,8 @@ const scaleTypes = {
|
|
|
22
22
|
BAND: 'band'
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
export const TOP_PADDING = 10
|
|
26
|
+
|
|
25
27
|
type useScaleProps = {
|
|
26
28
|
config: ChartConfig // standard chart config
|
|
27
29
|
data: Object[] // standard data array
|
|
@@ -80,11 +82,13 @@ const useScales = (properties: useScaleProps) => {
|
|
|
80
82
|
if (xAxis.type === 'date-time' || xAxis.type === 'continuous') {
|
|
81
83
|
let xAxisMin = Math.min(...xAxisDataMapped.map(Number))
|
|
82
84
|
let xAxisMax = Math.max(...xAxisDataMapped.map(Number))
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
let paddingRatio = config.xAxis.padding ? config.xAxis.padding * 0.01 : 0
|
|
86
|
+
if (config.brush.active) {
|
|
87
|
+
paddingRatio = config.barThickness * 0.2
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
xAxisMin -= paddingRatio * (xAxisMax - xAxisMin)
|
|
91
|
+
xAxisMax += visualizationType === 'Line' ? 0 : paddingRatio * (xAxisMax - xAxisMin)
|
|
88
92
|
const range = config.xAxis.sortByRecentDate ? [xMax, 0] : [0, xMax]
|
|
89
93
|
xScale = scaleTime({
|
|
90
94
|
domain: [xAxisMin, xAxisMax],
|
|
@@ -392,7 +396,7 @@ const composeYScale = ({ min, max, yMax, config, leftMax }) => {
|
|
|
392
396
|
|
|
393
397
|
// If the visualization type is a bump chart then the domain and range need different values
|
|
394
398
|
const domainSet = config.visualizationType === 'Bump Chart' ? [1, max] : [min, max]
|
|
395
|
-
const yRange = config.visualizationType === 'Bump Chart' ? [30, yMax] : [yMax,
|
|
399
|
+
const yRange = config.visualizationType === 'Bump Chart' ? [30, yMax] : [yMax, TOP_PADDING]
|
|
396
400
|
// Return the configured scale function
|
|
397
401
|
return scaleFunc({
|
|
398
402
|
domain: domainSet,
|