@cdc/chart 4.26.2 → 4.26.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/LICENSE +201 -0
- package/dist/cdcchart.js +35674 -32430
- package/examples/data/data-with-metadata.json +10 -0
- package/examples/feature/pie/planet-pie-example-config.json +2 -1
- package/examples/metadata-variables.json +58 -0
- package/package.json +3 -3
- package/src/CdcChart.tsx +8 -4
- package/src/CdcChartComponent.tsx +321 -288
- package/src/_stories/Chart.CustomColors.stories.tsx +74 -0
- package/src/_stories/Chart.Defaults.stories.tsx +95 -0
- package/src/_stories/Chart.SmallestLeftAxisMax.stories.tsx +64 -0
- package/src/_stories/Chart.stories.tsx +36 -2
- package/src/_stories/ChartBar.Editor.stories.tsx +97 -38
- package/src/_stories/ChartBrush.Editor.stories.tsx +11 -25
- package/src/_stories/ChartEditor.Editor.stories.tsx +1 -1
- package/src/_stories/_mock/paired-bar-abbr.json +421 -0
- package/src/_stories/_mock/pie_custom_colors.json +268 -0
- package/src/_stories/_mock/smallest_left_axis_max.json +104 -0
- package/src/components/Annotations/components/AnnotationDraggable.styles.css +10 -10
- package/src/components/Annotations/components/AnnotationDropdown.styles.css +1 -1
- package/src/components/Annotations/components/AnnotationList.styles.css +11 -11
- package/src/components/Axis/BottomAxis.tsx +10 -3
- package/src/components/Axis/PairedBarAxis.tsx +10 -4
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +12 -28
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +12 -30
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +12 -31
- package/src/components/BarChart/components/BarChart.Vertical.tsx +12 -28
- package/src/components/BarChart/helpers/getPatternUrl.ts +94 -0
- package/src/components/BarChart/helpers/tests/getPatternUrl.test.ts +134 -0
- package/src/components/BarChart/helpers/useBarChart.ts +3 -0
- package/src/components/Brush/BrushSelector.tsx +2 -1
- package/src/components/Brush/MiniChartPreview.tsx +21 -26
- package/src/components/EditorPanel/EditorPanel.tsx +56 -43
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +9 -9
- package/src/components/EditorPanel/components/Panels/Panel.ForestPlotSettings.tsx +0 -78
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +39 -1
- package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +24 -42
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +83 -2
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +45 -42
- package/src/components/EditorPanel/editor-panel.scss +1 -1
- package/src/components/ForestPlot/ForestPlot.tsx +26 -22
- package/src/components/Legend/LegendGroup/LegendGroup.styles.css +4 -4
- package/src/components/Legend/helpers/createFormatLabels.tsx +3 -2
- package/src/components/LinearChart/tests/LinearChart.test.tsx +77 -0
- package/src/components/LinearChart/tests/mockConfigContext.ts +2 -0
- package/src/components/LinearChart.tsx +26 -6
- package/src/components/PieChart/PieChart.tsx +19 -4
- package/src/components/RadarChart/RadarChart.tsx +1 -1
- package/src/components/Regions/components/Regions.tsx +6 -6
- package/src/components/Sankey/components/Sankey.tsx +3 -3
- package/src/components/Sankey/sankey.scss +1 -1
- package/src/components/SmallMultiples/SmallMultiples.css +5 -5
- package/src/components/Sparkline/index.scss +4 -2
- package/src/components/WarmingStripes/WarmingStripesGradientLegend.css +8 -8
- package/src/data/initial-state.js +23 -14
- package/src/data/legacy-defaults.ts +18 -0
- package/src/helpers/abbreviateNumber.ts +24 -17
- package/src/helpers/getChartPatternId.ts +17 -0
- package/src/helpers/getMinMax.ts +16 -2
- package/src/helpers/seriesColumnSettings.ts +114 -0
- package/src/helpers/tests/countNumOfTicks.test.ts +77 -0
- package/src/helpers/tests/seriesColumnSettings.test.ts +84 -0
- package/src/hooks/useRightAxis.ts +14 -0
- package/src/hooks/useScales.ts +92 -56
- package/src/hooks/useTooltip.tsx +20 -3
- package/src/scss/main.scss +152 -79
- package/src/test/CdcChart.test.jsx +2 -2
- package/src/types/ChartConfig.ts +4 -0
- package/tests/fixtures/chart-config-with-metadata.json +29 -0
- package/tests/fixtures/data-with-metadata.json +10 -0
|
@@ -26,7 +26,7 @@ const ForestPlot = ({
|
|
|
26
26
|
handleTooltipMouseOver,
|
|
27
27
|
forestPlotRightLabelRef
|
|
28
28
|
}: ForestPlotProps) => {
|
|
29
|
-
const {
|
|
29
|
+
const { transformedData: data, updateConfig } = useContext<ChartContext>(ConfigContext)
|
|
30
30
|
const { forestPlot } = config as ChartConfig
|
|
31
31
|
const labelPosition = config.xAxis.tickWidthMax + 10
|
|
32
32
|
const [initialLogTicksSet, setInitialLogTicks] = useState(false)
|
|
@@ -90,7 +90,9 @@ const ForestPlot = ({
|
|
|
90
90
|
}
|
|
91
91
|
}, [config.forestPlot.type])
|
|
92
92
|
|
|
93
|
-
const pooledData =
|
|
93
|
+
const pooledData = data.find(d => d[config.xAxis.dataKey] === config.forestPlot.pooledResult.column)
|
|
94
|
+
const [plotStart, plotEnd] = [...xScale.range()].sort((a, b) => a - b)
|
|
95
|
+
const plotWidth = plotEnd - plotStart
|
|
94
96
|
|
|
95
97
|
const regressionPoints = pooledData
|
|
96
98
|
? [
|
|
@@ -112,12 +114,12 @@ const ForestPlot = ({
|
|
|
112
114
|
|
|
113
115
|
const topLine = [
|
|
114
116
|
{ x: 0, y: topMarginOffset },
|
|
115
|
-
{ x:
|
|
117
|
+
{ x: plotEnd, y: topMarginOffset }
|
|
116
118
|
]
|
|
117
119
|
|
|
118
120
|
const bottomLine = [
|
|
119
121
|
{ x: 0, y: height },
|
|
120
|
-
{ x:
|
|
122
|
+
{ x: plotEnd, y: height }
|
|
121
123
|
]
|
|
122
124
|
|
|
123
125
|
type Columns = {
|
|
@@ -289,30 +291,21 @@ const ForestPlot = ({
|
|
|
289
291
|
{forestPlot.regression.description}
|
|
290
292
|
</Text>
|
|
291
293
|
)}
|
|
292
|
-
|
|
293
|
-
<Bar
|
|
294
|
-
key='forest-plot-tooltip-area'
|
|
295
|
-
className='forest-plot-tooltip-area'
|
|
296
|
-
width={width}
|
|
297
|
-
height={height}
|
|
298
|
-
fill={false ? 'red' : 'transparent'}
|
|
299
|
-
fillOpacity={0.5}
|
|
300
|
-
onMouseMove={e => handleTooltipMouseOver(e, data)}
|
|
301
|
-
onMouseOut={handleTooltipMouseOff}
|
|
302
|
-
/>
|
|
303
294
|
</Group>
|
|
304
295
|
<Line
|
|
305
296
|
from={topLine[0]}
|
|
306
297
|
to={topLine[1]}
|
|
307
|
-
style={{ stroke: '
|
|
298
|
+
style={{ stroke: '#333', strokeWidth: 1 }}
|
|
308
299
|
className='forestplot__top-line'
|
|
309
300
|
/>
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
301
|
+
{config.xAxis.hideAxis && (
|
|
302
|
+
<Line
|
|
303
|
+
from={bottomLine[0]}
|
|
304
|
+
to={bottomLine[1]}
|
|
305
|
+
style={{ stroke: '#333', strokeWidth: 1 }}
|
|
306
|
+
className='forestplot__bottom-line'
|
|
307
|
+
/>
|
|
308
|
+
)}
|
|
316
309
|
|
|
317
310
|
{/* column data */}
|
|
318
311
|
{columnsOnChart.map((column, colIndex) => {
|
|
@@ -400,6 +393,17 @@ const ForestPlot = ({
|
|
|
400
393
|
{forestPlot.rightLabel}
|
|
401
394
|
</Text>
|
|
402
395
|
)}
|
|
396
|
+
<Bar
|
|
397
|
+
key='forest-plot-tooltip-area'
|
|
398
|
+
className='forest-plot-tooltip-area'
|
|
399
|
+
x={0}
|
|
400
|
+
width={width}
|
|
401
|
+
height={height}
|
|
402
|
+
fill={false ? 'red' : 'transparent'}
|
|
403
|
+
fillOpacity={0.5}
|
|
404
|
+
onMouseMove={e => handleTooltipMouseOver(e, data)}
|
|
405
|
+
onMouseOut={handleTooltipMouseOff}
|
|
406
|
+
/>
|
|
403
407
|
</>
|
|
404
408
|
)
|
|
405
409
|
}
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
margin-bottom: 0.5rem;
|
|
7
7
|
|
|
8
8
|
.group-item .visx-legend-label {
|
|
9
|
-
font-weight: 400;
|
|
10
9
|
font-size: 0.889rem;
|
|
10
|
+
font-weight: 400;
|
|
11
11
|
margin-bottom: 0.5rem;
|
|
12
12
|
}
|
|
13
13
|
.group-label {
|
|
14
|
-
font-weight: 500;
|
|
15
14
|
font-family: Nunito, sans-serif;
|
|
16
15
|
font-size: 1rem;
|
|
16
|
+
font-weight: 500;
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
grid-gap: 10px;
|
|
23
23
|
|
|
24
24
|
&.group-item {
|
|
25
|
+
align-items: flex-start;
|
|
25
26
|
display: flex;
|
|
26
27
|
flex-direction: column;
|
|
27
|
-
align-items: flex-start;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
& .inactive {
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
transition: 0.2s all;
|
|
33
33
|
}
|
|
34
34
|
& .highlighted {
|
|
35
|
+
border-radius: 1px;
|
|
35
36
|
outline: 1px solid #005ea2;
|
|
36
37
|
outline-offset: 5px;
|
|
37
|
-
border-radius: 1px;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -17,7 +17,8 @@ import { FaStar } from 'react-icons/fa'
|
|
|
17
17
|
import { Label } from '../../../types/Label'
|
|
18
18
|
import { ColorScale, TransformedData } from '../../../types/ChartContext'
|
|
19
19
|
import { ChartConfig } from '../../../types/ChartConfig'
|
|
20
|
-
import
|
|
20
|
+
import cloneDeep from 'lodash/cloneDeep'
|
|
21
|
+
import sortBy from 'lodash/sortBy'
|
|
21
22
|
import { scaleSequential } from 'd3-scale'
|
|
22
23
|
import { interpolateRgbBasis } from 'd3-interpolate'
|
|
23
24
|
import { filterChartColorPalettes } from '@cdc/core/helpers/filterColorPalettes'
|
|
@@ -173,7 +174,7 @@ export const createFormatLabels =
|
|
|
173
174
|
|
|
174
175
|
const sortVertical = labels =>
|
|
175
176
|
legend.verticalSorted
|
|
176
|
-
?
|
|
177
|
+
? sortBy(cloneDeep(labels), label => {
|
|
177
178
|
const match = label.datum?.match(/-?\d+(\.\d+)?/)
|
|
178
179
|
return match ? parseFloat(match[0]) : Number.MAX_SAFE_INTEGER
|
|
179
180
|
})
|
|
@@ -4,6 +4,7 @@ import { describe, expect, it, vi, beforeAll } from 'vitest'
|
|
|
4
4
|
import LinearChart from '../../LinearChart'
|
|
5
5
|
import ConfigContext from '../../../ConfigContext'
|
|
6
6
|
import { createMockChartContext } from './mockConfigContext'
|
|
7
|
+
import forestPlotConfig from '../../../../examples/feature/forest-plot/forest-plot.json'
|
|
7
8
|
|
|
8
9
|
// Mock ResizeObserver
|
|
9
10
|
vi.stubGlobal(
|
|
@@ -147,6 +148,82 @@ describe('LinearChart', () => {
|
|
|
147
148
|
})
|
|
148
149
|
expect(container).toBeTruthy()
|
|
149
150
|
})
|
|
151
|
+
|
|
152
|
+
it('keeps forest plot lines inside the computed plot bounds at narrow and wide widths', () => {
|
|
153
|
+
const forestContextOverrides = {
|
|
154
|
+
transformedData: forestPlotConfig.data,
|
|
155
|
+
rawData: forestPlotConfig.data
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const narrowRender = renderLinearChart(forestPlotConfig as any, forestContextOverrides, {
|
|
159
|
+
parentWidth: 320,
|
|
160
|
+
parentHeight: 500
|
|
161
|
+
})
|
|
162
|
+
const wideRender = renderLinearChart(forestPlotConfig as any, forestContextOverrides, {
|
|
163
|
+
parentWidth: 960,
|
|
164
|
+
parentHeight: 500
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
const narrowTopLine = narrowRender.container.querySelector('.forestplot__top-line')
|
|
168
|
+
const wideTopLine = wideRender.container.querySelector('.forestplot__top-line')
|
|
169
|
+
const narrowCiLine = narrowRender.container.querySelector('line[class^="line-"]')
|
|
170
|
+
const wideCiLine = wideRender.container.querySelector('line[class^="line-"]')
|
|
171
|
+
|
|
172
|
+
expect(narrowTopLine).toBeTruthy()
|
|
173
|
+
expect(wideTopLine).toBeTruthy()
|
|
174
|
+
expect(narrowCiLine).toBeTruthy()
|
|
175
|
+
expect(wideCiLine).toBeTruthy()
|
|
176
|
+
|
|
177
|
+
const narrowStart = Number(narrowTopLine?.getAttribute('x1'))
|
|
178
|
+
const narrowEnd = Number(narrowTopLine?.getAttribute('x2'))
|
|
179
|
+
const wideStart = Number(wideTopLine?.getAttribute('x1'))
|
|
180
|
+
const wideEnd = Number(wideTopLine?.getAttribute('x2'))
|
|
181
|
+
|
|
182
|
+
expect(narrowStart).toBe(0)
|
|
183
|
+
expect(narrowEnd).toBeLessThanOrEqual(320)
|
|
184
|
+
expect(wideStart).toBe(0)
|
|
185
|
+
expect(wideEnd).toBeLessThanOrEqual(960)
|
|
186
|
+
expect(wideEnd - wideStart).toBeGreaterThan(narrowEnd - narrowStart)
|
|
187
|
+
|
|
188
|
+
expect(Number(narrowCiLine?.getAttribute('x1'))).toBeGreaterThan(narrowStart)
|
|
189
|
+
expect(Number(narrowCiLine?.getAttribute('x2'))).toBeLessThanOrEqual(narrowEnd)
|
|
190
|
+
expect(Number(wideCiLine?.getAttribute('x1'))).toBeGreaterThan(wideStart)
|
|
191
|
+
expect(Number(wideCiLine?.getAttribute('x2'))).toBeLessThanOrEqual(wideEnd)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('avoids rendering a duplicate manual bottom border when the forest plot x-axis is visible', () => {
|
|
195
|
+
const { container } = renderLinearChart(
|
|
196
|
+
forestPlotConfig as any,
|
|
197
|
+
{
|
|
198
|
+
transformedData: forestPlotConfig.data,
|
|
199
|
+
rawData: forestPlotConfig.data
|
|
200
|
+
},
|
|
201
|
+
{ parentWidth: 800, parentHeight: 500 }
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
expect(container.querySelector('.forestplot__top-line')).toBeTruthy()
|
|
205
|
+
expect(container.querySelector('.forestplot__bottom-line')).toBeFalsy()
|
|
206
|
+
const bottomAxisLine = container.querySelector('.bottom-axis > line[stroke="#333"]')
|
|
207
|
+
expect(bottomAxisLine?.getAttribute('x1')).toBe('0')
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('renders forest plot rows from transformedData instead of rawData', () => {
|
|
211
|
+
const filteredData = forestPlotConfig.data.slice(0, 2)
|
|
212
|
+
const { container } = renderLinearChart(
|
|
213
|
+
forestPlotConfig as any,
|
|
214
|
+
{
|
|
215
|
+
transformedData: filteredData,
|
|
216
|
+
rawData: forestPlotConfig.data
|
|
217
|
+
},
|
|
218
|
+
{ parentWidth: 800, parentHeight: 500 }
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
expect(container.querySelectorAll('.lower-ci')).toHaveLength(filteredData.length)
|
|
222
|
+
expect(container.querySelectorAll('line[class^="line-"]')).toHaveLength(filteredData.length)
|
|
223
|
+
expect(container.textContent).not.toContain(
|
|
224
|
+
String(forestPlotConfig.data[forestPlotConfig.data.length - 1]['Author(s) and Year'])
|
|
225
|
+
)
|
|
226
|
+
})
|
|
150
227
|
})
|
|
151
228
|
|
|
152
229
|
describe('axis rendering', () => {
|
|
@@ -124,6 +124,8 @@ export const createMockChartContext = (
|
|
|
124
124
|
clean: (s: any) => s,
|
|
125
125
|
formatTooltipsDate: (date: any) => String(date),
|
|
126
126
|
legendId: 'test-legend',
|
|
127
|
+
rawData: config.data,
|
|
128
|
+
updateConfig: () => {},
|
|
127
129
|
...contextOverrides
|
|
128
130
|
} as ChartContext
|
|
129
131
|
}
|
|
@@ -18,8 +18,7 @@ import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
|
18
18
|
import 'react-tooltip/dist/react-tooltip.css'
|
|
19
19
|
import { Text } from '@visx/text'
|
|
20
20
|
import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
|
|
21
|
-
import
|
|
22
|
-
|
|
21
|
+
import ResizeObserver from 'resize-observer-polyfill'
|
|
23
22
|
// CDC Components
|
|
24
23
|
import { isDateScale } from '@cdc/core/helpers/cove/date'
|
|
25
24
|
import ConfigContext from '../ConfigContext'
|
|
@@ -152,6 +151,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
152
151
|
const [suffixWidth, setSuffixWidth] = useState(0)
|
|
153
152
|
const [calculatedSvgHeight, setCalculatedSvgHeight] = useState<number | null>(null)
|
|
154
153
|
const [axisUpdateKey, setAxisUpdateKey] = useState(0)
|
|
154
|
+
const [axisBottomSizeKey, setAxisBottomSizeKey] = useState(0)
|
|
155
155
|
const [synchronizedXValue, setSynchronizedXValue] = useState<any>(null)
|
|
156
156
|
|
|
157
157
|
// REFS
|
|
@@ -245,6 +245,17 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
245
245
|
setAxisUpdateKey(prev => prev + 1)
|
|
246
246
|
}, [data.length, xAxisDataMapped?.[0], xAxisDataMapped?.[xAxisDataMapped.length - 1]])
|
|
247
247
|
|
|
248
|
+
// Recompute heights when bottom axis size changes (e.g., font load or wrap).
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
const axisBottomEl = axisBottomRef.current
|
|
251
|
+
if (!axisBottomEl) return
|
|
252
|
+
const observer = new ResizeObserver(() => {
|
|
253
|
+
setAxisBottomSizeKey(prev => prev + 1)
|
|
254
|
+
})
|
|
255
|
+
observer.observe(axisBottomEl)
|
|
256
|
+
return () => observer.disconnect()
|
|
257
|
+
}, [axisBottomRef.current])
|
|
258
|
+
|
|
248
259
|
const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data })
|
|
249
260
|
|
|
250
261
|
// State for computed y-axis width - allows re-render when horizontal bar label space is calculated
|
|
@@ -415,7 +426,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
415
426
|
|
|
416
427
|
// Make sure the chart is visible if in the editor
|
|
417
428
|
useEffect(() => {
|
|
418
|
-
const element = document.querySelector('.
|
|
429
|
+
const element = document.querySelector('.is-editor')
|
|
419
430
|
if (element) {
|
|
420
431
|
setAnimatedChart(true)
|
|
421
432
|
}
|
|
@@ -498,7 +509,16 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
498
509
|
const legendIsLeftOrRight =
|
|
499
510
|
legend?.position !== 'top' && legend?.position !== 'bottom' && !isLegendWrapViewport(currentViewport)
|
|
500
511
|
legendRef.current.style.transform = legendIsLeftOrRight ? `translateY(${topLabelOnGridlineHeight}px)` : 'none'
|
|
501
|
-
}, [
|
|
512
|
+
}, [
|
|
513
|
+
axisBottomRef.current,
|
|
514
|
+
config,
|
|
515
|
+
config.xAxis.brushActive,
|
|
516
|
+
currentViewport,
|
|
517
|
+
topYLabelRef.current,
|
|
518
|
+
initialHeight,
|
|
519
|
+
parentWidth,
|
|
520
|
+
axisBottomSizeKey
|
|
521
|
+
])
|
|
502
522
|
|
|
503
523
|
useEffect(() => {
|
|
504
524
|
if (!tooltipOpen) return
|
|
@@ -542,7 +562,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
542
562
|
<svg
|
|
543
563
|
ref={internalSvgRef}
|
|
544
564
|
onMouseMove={onMouseMove}
|
|
545
|
-
width={parentWidth
|
|
565
|
+
width={parentWidth}
|
|
546
566
|
height={isNoDataAvailable ? 1 : calculatedSvgHeight ?? parentHeight}
|
|
547
567
|
className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${
|
|
548
568
|
debugSvg && 'debug'
|
|
@@ -834,7 +854,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
834
854
|
<TooltipWithBounds
|
|
835
855
|
ref={tooltipRef}
|
|
836
856
|
key={Math.random()}
|
|
837
|
-
className={'tooltip
|
|
857
|
+
className={'tooltip cove-visualization'}
|
|
838
858
|
left={tooltipLeft}
|
|
839
859
|
top={tooltipTop}
|
|
840
860
|
>
|
|
@@ -172,8 +172,22 @@ const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) =>
|
|
|
172
172
|
const domainKeys = isPercentageMode ? dataKeys.filter(k => k !== labelForCalcArea) : dataKeys
|
|
173
173
|
const numberOfKeys = domainKeys.length
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
const orderedCustomColors = config.general?.palette?.customColorsOrdered
|
|
176
|
+
const customColors = config.general?.palette?.customColors
|
|
177
|
+
const shouldUseOrderedCustomColors = Array.isArray(orderedCustomColors) && orderedCustomColors.length > 0
|
|
178
|
+
|
|
179
|
+
let palette = shouldUseOrderedCustomColors ? orderedCustomColors : getPaletteColors(config, colorPalettes)
|
|
180
|
+
if (!shouldUseOrderedCustomColors && customColors?.length) {
|
|
181
|
+
palette = customColors
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
while (palette.length > 0 && palette.length < numberOfKeys) {
|
|
185
|
+
palette = palette.concat(palette)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
palette = shouldUseOrderedCustomColors
|
|
189
|
+
? palette.slice(0, numberOfKeys)
|
|
190
|
+
: applyEnhancedColorDistribution(config, palette, numberOfKeys)
|
|
177
191
|
|
|
178
192
|
const unknownColor = isPercentageMode
|
|
179
193
|
? getComputedStyle(document.documentElement).getPropertyValue('--cool-gray-10').trim()
|
|
@@ -206,6 +220,7 @@ const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) =>
|
|
|
206
220
|
config.general?.palette?.name,
|
|
207
221
|
config.general?.palette?.isReversed,
|
|
208
222
|
config.general?.palette?.customColors,
|
|
223
|
+
config.general?.palette?.customColorsOrdered,
|
|
209
224
|
config.palette
|
|
210
225
|
])
|
|
211
226
|
|
|
@@ -216,7 +231,7 @@ const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) =>
|
|
|
216
231
|
|
|
217
232
|
// Make sure the chart is visible if in the editor
|
|
218
233
|
useEffect(() => {
|
|
219
|
-
const element = document.querySelector('.
|
|
234
|
+
const element = document.querySelector('.is-editor')
|
|
220
235
|
if (element) {
|
|
221
236
|
// parent element is visible
|
|
222
237
|
setAnimatePie(true)
|
|
@@ -421,7 +436,7 @@ const PieChart = React.forwardRef<SVGSVGElement, PieChartProps>((props, ref) =>
|
|
|
421
436
|
config.tooltips.opacity / 100
|
|
422
437
|
}) !important`}</style>
|
|
423
438
|
<TooltipWithBounds
|
|
424
|
-
className={'tooltip
|
|
439
|
+
className={'tooltip cove-visualization'}
|
|
425
440
|
left={tooltipLeft + centerX - radius}
|
|
426
441
|
top={tooltipTop}
|
|
427
442
|
>
|
|
@@ -278,7 +278,7 @@ const RadarChart = React.forwardRef<SVGSVGElement, RadarChartProps>((props, ref)
|
|
|
278
278
|
<style>{`.tooltip {background-color: rgba(255,255,255, ${
|
|
279
279
|
(config.tooltips?.opacity || 90) / 100
|
|
280
280
|
}) !important`}</style>
|
|
281
|
-
<TooltipWithBounds className='tooltip
|
|
281
|
+
<TooltipWithBounds className='tooltip cove-visualization' left={tooltipLeft} top={tooltipTop}>
|
|
282
282
|
<div style={{ fontWeight: 'bold', marginBottom: '8px' }}>{tooltipData.entityName}</div>
|
|
283
283
|
<ul style={{ margin: 0, padding: 0, listStyle: 'none' }}>
|
|
284
284
|
{tooltipData.values.map((item, index) => (
|
|
@@ -91,7 +91,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
91
91
|
? new Date(domain[domain.length - 1] as string | number).getTime()
|
|
92
92
|
: new Date(region.to)
|
|
93
93
|
|
|
94
|
-
const toFormatted = formatDate(config.xAxis.dateParseFormat, toRefDate)
|
|
94
|
+
const toFormatted = formatDate(config.xAxis.dateParseFormat, toRefDate, config.locale)
|
|
95
95
|
const toDate = new Date(toFormatted)
|
|
96
96
|
const fromDate = new Date(toDate)
|
|
97
97
|
fromDate.setDate(fromDate.getDate() - previousDays)
|
|
@@ -99,7 +99,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
99
99
|
let closestValue: unknown
|
|
100
100
|
|
|
101
101
|
if (axisType === 'date') {
|
|
102
|
-
const fromTime = new Date(formatDate(xAxis.dateParseFormat, fromDate)).getTime()
|
|
102
|
+
const fromTime = new Date(formatDate(xAxis.dateParseFormat, fromDate, config.locale)).getTime()
|
|
103
103
|
closestValue = findClosestDate(fromTime, domain as number[], d => d)
|
|
104
104
|
} else if (axisType === 'categorical') {
|
|
105
105
|
const fromTime = fromDate.getTime()
|
|
@@ -151,7 +151,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
151
151
|
// For date scale (band), we need to find the value in the domain
|
|
152
152
|
// Parse the region date to match the format in the domain
|
|
153
153
|
const date = new Date(region.from)
|
|
154
|
-
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date)).getTime()
|
|
154
|
+
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date, config.locale)).getTime()
|
|
155
155
|
|
|
156
156
|
// For band scales, find the closest date in the domain
|
|
157
157
|
const domain = xScale.domain() as number[]
|
|
@@ -193,7 +193,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
193
193
|
return from + Number(config.yAxis.size)
|
|
194
194
|
}
|
|
195
195
|
const date = new Date(region.from)
|
|
196
|
-
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date)).getTime()
|
|
196
|
+
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date, config.locale)).getTime()
|
|
197
197
|
let from = xScale(parsedDate)
|
|
198
198
|
// For date-time, xScale returns correct position (no bandwidth), just add left padding
|
|
199
199
|
return from + Number(config.yAxis.size)
|
|
@@ -252,7 +252,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
252
252
|
}
|
|
253
253
|
// For date scale (band), we need to find the value in the domain
|
|
254
254
|
const date = new Date(region.from)
|
|
255
|
-
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date)).getTime()
|
|
255
|
+
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date, config.locale)).getTime()
|
|
256
256
|
|
|
257
257
|
// For band scales, find the closest date in the domain
|
|
258
258
|
const domain = xScale.domain() as number[]
|
|
@@ -281,7 +281,7 @@ const Regions: React.FC<RegionsProps> = ({ xScale, barWidth = 0, totalBarsInGrou
|
|
|
281
281
|
from = calculatePreviousDaysFrom(region, 'date-time')
|
|
282
282
|
} else {
|
|
283
283
|
const date = new Date(region.from)
|
|
284
|
-
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date)).getTime()
|
|
284
|
+
const parsedDate = parseDate(formatDate(config.xAxis.dateParseFormat, date, config.locale)).getTime()
|
|
285
285
|
from = xScale(parsedDate)
|
|
286
286
|
}
|
|
287
287
|
return from - getBarOffset()
|
|
@@ -226,7 +226,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
226
226
|
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
227
227
|
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
228
228
|
>
|
|
229
|
-
{typeof node.value === 'number' ? node.value.toLocaleString() : node.value}
|
|
229
|
+
{typeof node.value === 'number' ? node.value.toLocaleString(config.locale) : node.value}
|
|
230
230
|
</Text>
|
|
231
231
|
<Text
|
|
232
232
|
width={linkLength()}
|
|
@@ -281,7 +281,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
281
281
|
>
|
|
282
282
|
<tspan className={classStyle}>
|
|
283
283
|
{sankeyConfig.nodeValueStyle.textBefore +
|
|
284
|
-
(typeof node.value === 'number' ? node.value.toLocaleString() : node.value) +
|
|
284
|
+
(typeof node.value === 'number' ? node.value.toLocaleString(config.locale) : node.value) +
|
|
285
285
|
sankeyConfig.nodeValueStyle.textAfter}
|
|
286
286
|
</tspan>
|
|
287
287
|
</text>
|
|
@@ -394,7 +394,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
394
394
|
textAnchor='start'
|
|
395
395
|
style={{ pointerEvents: 'none' }}
|
|
396
396
|
>
|
|
397
|
-
{typeof node.value === 'number' ? node.value.toLocaleString() : node.value}
|
|
397
|
+
{typeof node.value === 'number' ? node.value.toLocaleString(config.locale) : node.value}
|
|
398
398
|
</Text>
|
|
399
399
|
<Text
|
|
400
400
|
x={node.x0! + textPositionHorizontal}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
.small-multiples-container {
|
|
2
|
-
width: 100%;
|
|
3
2
|
display: flex;
|
|
4
3
|
flex-direction: column;
|
|
4
|
+
width: 100%;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
.small-multiples-grid {
|
|
8
8
|
display: grid;
|
|
9
|
-
width: 100%;
|
|
10
9
|
flex: 1;
|
|
10
|
+
width: 100%;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
.small-multiple-tile {
|
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
.tile-title {
|
|
23
|
-
margin: 0;
|
|
24
23
|
font-weight: 700;
|
|
25
|
-
text-align: left;
|
|
26
24
|
line-height: 1.3;
|
|
25
|
+
margin: 0;
|
|
26
|
+
text-align: left;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
.tile-chart {
|
|
30
|
-
width: 100%;
|
|
31
30
|
flex-shrink: 0;
|
|
31
|
+
width: 100%;
|
|
32
32
|
}
|
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
.warming-stripes-gradient-legend__title {
|
|
7
|
+
font-size: 16px;
|
|
7
8
|
font-weight: 600;
|
|
8
9
|
margin-bottom: 0.5rem;
|
|
9
|
-
font-size: 16px;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
.warming-stripes-gradient-legend__description {
|
|
13
|
-
margin-top: 0.5rem;
|
|
14
|
-
margin-bottom: 1rem;
|
|
15
|
-
font-size: 14px;
|
|
16
13
|
color: #555;
|
|
14
|
+
font-size: 14px;
|
|
15
|
+
margin-bottom: 1rem;
|
|
16
|
+
margin-top: 0.5rem;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
.warming-stripes-gradient-legend__container {
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
|
|
23
23
|
.warming-stripes-gradient-legend__svg {
|
|
24
24
|
display: block;
|
|
25
|
-
width: 100%;
|
|
26
25
|
overflow: visible;
|
|
26
|
+
width: 100%;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
.warming-stripes-gradient-legend__series-label {
|
|
30
|
-
|
|
31
|
-
margin-top: 0.5rem;
|
|
30
|
+
color: #333;
|
|
32
31
|
font-size: 14px;
|
|
33
32
|
font-weight: 500;
|
|
34
|
-
|
|
33
|
+
margin-top: 0.5rem;
|
|
34
|
+
text-align: center;
|
|
35
35
|
}
|
|
@@ -54,12 +54,12 @@ const createInitialState = () => {
|
|
|
54
54
|
},
|
|
55
55
|
preliminaryData: [],
|
|
56
56
|
yAxis: {
|
|
57
|
-
hideAxis:
|
|
57
|
+
hideAxis: true,
|
|
58
58
|
displayNumbersOnBar: false,
|
|
59
59
|
hideLabel: false,
|
|
60
|
-
hideTicks:
|
|
60
|
+
hideTicks: true,
|
|
61
61
|
size: 50,
|
|
62
|
-
gridLines:
|
|
62
|
+
gridLines: true,
|
|
63
63
|
enablePadding: false,
|
|
64
64
|
min: '',
|
|
65
65
|
max: '',
|
|
@@ -73,7 +73,7 @@ const createInitialState = () => {
|
|
|
73
73
|
rightAxisLabelColor: '#1c1d1f',
|
|
74
74
|
rightAxisTickLabelColor: '#1c1d1f',
|
|
75
75
|
rightAxisTickColor: '#1c1d1f',
|
|
76
|
-
numTicks:
|
|
76
|
+
numTicks: 4,
|
|
77
77
|
axisPadding: 0,
|
|
78
78
|
scalePadding: 10,
|
|
79
79
|
tickRotation: 0,
|
|
@@ -133,7 +133,8 @@ const createInitialState = () => {
|
|
|
133
133
|
labelColor: '#1c1d1f',
|
|
134
134
|
tickLabelColor: '#1c1d1f',
|
|
135
135
|
tickColor: '#1c1d1f',
|
|
136
|
-
numTicks:
|
|
136
|
+
numTicks: 6,
|
|
137
|
+
dateDisplayFormat: '%b. %-d %Y',
|
|
137
138
|
labelOffset: 0,
|
|
138
139
|
axisPadding: 200,
|
|
139
140
|
target: 0,
|
|
@@ -142,11 +143,15 @@ const createInitialState = () => {
|
|
|
142
143
|
showYearsOnce: false,
|
|
143
144
|
sortByRecentDate: false,
|
|
144
145
|
brushActive: false,
|
|
145
|
-
brushDefaultRecentDateCount: undefined
|
|
146
|
+
brushDefaultRecentDateCount: undefined,
|
|
147
|
+
viewportNumTicks: {
|
|
148
|
+
xs: 4,
|
|
149
|
+
xxs: 4
|
|
150
|
+
}
|
|
146
151
|
},
|
|
147
152
|
table: {
|
|
148
153
|
label: 'Data Table',
|
|
149
|
-
expanded:
|
|
154
|
+
expanded: false,
|
|
150
155
|
limitHeight: false,
|
|
151
156
|
height: '',
|
|
152
157
|
caption: '',
|
|
@@ -156,7 +161,7 @@ const createInitialState = () => {
|
|
|
156
161
|
indexLabel: '',
|
|
157
162
|
download: false,
|
|
158
163
|
showVertical: true,
|
|
159
|
-
dateDisplayFormat: '',
|
|
164
|
+
dateDisplayFormat: '%B %-d, %Y',
|
|
160
165
|
showMissingDataLabel: true,
|
|
161
166
|
showSuppressedSymbol: true,
|
|
162
167
|
collapsible: true
|
|
@@ -196,7 +201,7 @@ const createInitialState = () => {
|
|
|
196
201
|
side: false,
|
|
197
202
|
topBottom: true
|
|
198
203
|
},
|
|
199
|
-
position: '
|
|
204
|
+
position: 'top',
|
|
200
205
|
orderedValues: [],
|
|
201
206
|
patterns: {},
|
|
202
207
|
patternField: ''
|
|
@@ -224,7 +229,7 @@ const createInitialState = () => {
|
|
|
224
229
|
},
|
|
225
230
|
labels: false,
|
|
226
231
|
dataFormat: {
|
|
227
|
-
commas:
|
|
232
|
+
commas: true,
|
|
228
233
|
prefix: '',
|
|
229
234
|
suffix: '',
|
|
230
235
|
abbreviated: false,
|
|
@@ -235,9 +240,13 @@ const createInitialState = () => {
|
|
|
235
240
|
filters: [],
|
|
236
241
|
confidenceKeys: {},
|
|
237
242
|
visual: {
|
|
238
|
-
border:
|
|
239
|
-
|
|
240
|
-
|
|
243
|
+
border: false,
|
|
244
|
+
borderColorTheme: false,
|
|
245
|
+
accent: false,
|
|
246
|
+
background: false,
|
|
247
|
+
hideBackgroundColor: false,
|
|
248
|
+
tp5Treatment: false,
|
|
249
|
+
tp5Background: false,
|
|
241
250
|
verticalHoverLine: false,
|
|
242
251
|
horizontalHoverLine: false,
|
|
243
252
|
lineDatapointSymbol: 'none',
|
|
@@ -250,7 +259,7 @@ const createInitialState = () => {
|
|
|
250
259
|
tooltips: {
|
|
251
260
|
opacity: 90,
|
|
252
261
|
singleSeries: false,
|
|
253
|
-
dateDisplayFormat: ''
|
|
262
|
+
dateDisplayFormat: '%B %-d, %Y'
|
|
254
263
|
},
|
|
255
264
|
forestPlot: {
|
|
256
265
|
startAt: 0,
|