@cdc/chart 4.24.4 → 4.24.5
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 +32130 -31726
- package/index.html +7 -7
- package/package.json +2 -2
- package/src/CdcChart.tsx +17 -13
- package/src/_stories/Chart.stories.tsx +8 -0
- package/src/_stories/_mock/bar-chart-suppressed.json +474 -0
- package/src/components/AreaChart/components/AreaChart.jsx +2 -2
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +52 -47
- package/src/components/BarChart/components/BarChart.Vertical.tsx +77 -92
- package/src/components/DeviationBar.jsx +4 -2
- package/src/components/EditorPanel/EditorPanel.tsx +289 -601
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +19 -2
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -5
- package/src/components/EditorPanel/useEditorPermissions.js +4 -1
- package/src/components/Legend/Legend.Component.tsx +62 -10
- package/src/components/LineChart/LineChartProps.ts +13 -6
- package/src/components/LineChart/components/LineChart.Circle.tsx +22 -11
- package/src/components/LineChart/helpers.ts +134 -10
- package/src/components/LineChart/index.tsx +69 -42
- package/src/components/LinearChart.jsx +155 -139
- package/src/components/ZoomBrush.tsx +40 -21
- package/src/data/initial-state.js +4 -4
- package/src/hooks/useBarChart.js +47 -22
- package/src/hooks/useMinMax.ts +21 -2
- package/src/hooks/useScales.ts +23 -23
- package/src/hooks/useTooltip.tsx +11 -11
- package/src/scss/main.scss +56 -6
- package/src/types/ChartConfig.ts +3 -13
- package/src/types/ChartContext.ts +4 -0
- package/src/_stories/ChartLine.preliminary.tsx +0 -19
- package/src/_stories/ChartSuppress.stories.tsx +0 -19
- package/src/_stories/_mock/suppress_mock.json +0 -911
- package/src/helpers/computeMarginBottom.ts +0 -56
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import React, { useContext } from 'react'
|
|
2
2
|
|
|
3
|
+
// VisX library imports
|
|
3
4
|
import * as allCurves from '@visx/curve'
|
|
4
5
|
import { Group } from '@visx/group'
|
|
5
6
|
import { LinePath, Bar, SplitLinePath } from '@visx/shape'
|
|
6
7
|
import { Text } from '@visx/text'
|
|
7
8
|
|
|
9
|
+
// CDC core components
|
|
8
10
|
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
11
|
+
|
|
12
|
+
// Local context and hooks
|
|
9
13
|
import ConfigContext from '../../ConfigContext'
|
|
10
14
|
import useRightAxis from '../../hooks/useRightAxis'
|
|
11
|
-
|
|
15
|
+
|
|
16
|
+
// Local helpers and components
|
|
17
|
+
import { filterCircles, createStyles, createDataSegments } from './helpers'
|
|
12
18
|
import LineChartCircle from './components/LineChart.Circle'
|
|
13
19
|
|
|
14
|
-
//
|
|
20
|
+
// Types
|
|
15
21
|
import { type ChartContext } from '../../types/ChartContext'
|
|
16
22
|
import { type LineChartProps } from './LineChartProps'
|
|
17
23
|
|
|
@@ -31,28 +37,22 @@ const LineChart = (props: LineChartProps) => {
|
|
|
31
37
|
} = props
|
|
32
38
|
|
|
33
39
|
// prettier-ignore
|
|
34
|
-
const {
|
|
35
|
-
|
|
36
|
-
config,
|
|
37
|
-
formatNumber,
|
|
38
|
-
handleLineType,
|
|
39
|
-
isNumber,
|
|
40
|
-
parseDate,
|
|
41
|
-
seriesHighlight,
|
|
42
|
-
tableData,
|
|
43
|
-
transformedData: data,
|
|
44
|
-
updateConfig,
|
|
45
|
-
rawData
|
|
46
|
-
} = useContext<ChartContext>(ConfigContext)
|
|
47
|
-
const { yScaleRight } = useRightAxis({ config, yMax, data, updateConfig })
|
|
40
|
+
const { colorScale, config, formatNumber, handleLineType, isNumber, parseDate, seriesHighlight, tableData, transformedData, updateConfig, brushConfig,clean } = useContext<ChartContext>(ConfigContext)
|
|
41
|
+
const { yScaleRight } = useRightAxis({ config, yMax, data: transformedData, updateConfig })
|
|
48
42
|
if (!handleTooltipMouseOver) return
|
|
49
43
|
|
|
50
44
|
const DEBUG = false
|
|
51
45
|
const { lineDatapointStyle, showLineSeriesLabels, legend } = config
|
|
52
|
-
|
|
46
|
+
let data = transformedData
|
|
47
|
+
let tableD = tableData
|
|
48
|
+
// if brush on use brush data and clean
|
|
49
|
+
if (brushConfig.data.length) {
|
|
50
|
+
data = clean(brushConfig.data)
|
|
51
|
+
tableD = clean(brushConfig.data)
|
|
52
|
+
}
|
|
53
53
|
return (
|
|
54
54
|
<ErrorBoundary component='LineChart'>
|
|
55
|
-
<Group left={config.runtime.yAxis.size}>
|
|
55
|
+
<Group left={Number(config.runtime.yAxis.size)}>
|
|
56
56
|
{' '}
|
|
57
57
|
{/* left - expects a number not a string */}
|
|
58
58
|
{(config.runtime.lineSeriesKeys || config.runtime.seriesKeys).map((seriesKey, index) => {
|
|
@@ -60,9 +60,10 @@ const LineChart = (props: LineChartProps) => {
|
|
|
60
60
|
const seriesData = config.series.filter(item => item.dataKey === seriesKey)
|
|
61
61
|
const seriesAxis = seriesData[0].axis ? seriesData[0].axis : 'left'
|
|
62
62
|
let displayArea = legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1
|
|
63
|
-
const circleData = filterCircles(config
|
|
63
|
+
const circleData = filterCircles(config?.preliminaryData, tableD, seriesKey)
|
|
64
64
|
// styles for preliminary Data items
|
|
65
|
-
let styles = createStyles({ preliminaryData: config.preliminaryData, data:
|
|
65
|
+
let styles = createStyles({ preliminaryData: config.preliminaryData, data: tableD, stroke: colorScale(config.runtime.seriesLabels[seriesKey]), strokeWidth: seriesData[0].weight || 2, handleLineType, lineType, seriesKey })
|
|
66
|
+
const suppressedSegments = createDataSegments(tableData, seriesKey, config.preliminaryData, config.xAxis.dataKey)
|
|
66
67
|
|
|
67
68
|
let xPos = d => {
|
|
68
69
|
return xScale(getXAxisData(d)) + (xScale.bandwidth ? xScale.bandwidth() / 2 : 0)
|
|
@@ -82,6 +83,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
82
83
|
const hasMultipleSeries = Object.keys(config.runtime.seriesLabels).length > 1
|
|
83
84
|
const labeltype = axis === 'Right' ? 'rightLabel' : 'label'
|
|
84
85
|
let label = config.runtime.yAxis[labeltype]
|
|
86
|
+
|
|
85
87
|
// if has muiltiple series dont show legend value on tooltip
|
|
86
88
|
if (!hasMultipleSeries) label = config.isLegendValue ? config.runtime.seriesLabels[seriesKey] : label
|
|
87
89
|
|
|
@@ -104,6 +106,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
104
106
|
mode='ALWAYS_SHOW_POINTS'
|
|
105
107
|
dataIndex={dataIndex}
|
|
106
108
|
circleData={circleData}
|
|
109
|
+
tableData={tableData}
|
|
107
110
|
data={data}
|
|
108
111
|
d={d}
|
|
109
112
|
config={config}
|
|
@@ -123,6 +126,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
123
126
|
<LineChartCircle
|
|
124
127
|
mode='ISOLATED_POINTS'
|
|
125
128
|
dataIndex={dataIndex}
|
|
129
|
+
tableData={tableData}
|
|
126
130
|
circleData={circleData}
|
|
127
131
|
data={data}
|
|
128
132
|
d={d}
|
|
@@ -145,6 +149,7 @@ const LineChart = (props: LineChartProps) => {
|
|
|
145
149
|
<>
|
|
146
150
|
{lineDatapointStyle === 'hover' && (
|
|
147
151
|
<LineChartCircle
|
|
152
|
+
tableData={tableData}
|
|
148
153
|
dataIndex={0}
|
|
149
154
|
mode='HOVER_POINTS'
|
|
150
155
|
circleData={circleData}
|
|
@@ -163,27 +168,39 @@ const LineChart = (props: LineChartProps) => {
|
|
|
163
168
|
)}
|
|
164
169
|
</>
|
|
165
170
|
{/* SPLIT LINE */}
|
|
166
|
-
{config?.preliminaryData?.some(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
171
|
+
{config?.preliminaryData?.some(pd => pd.value && pd.type) ? (
|
|
172
|
+
<>
|
|
173
|
+
<SplitLinePath
|
|
174
|
+
curve={allCurves[seriesData[0].lineType]}
|
|
175
|
+
segments={data.map(d => [d])}
|
|
176
|
+
segmentation='x'
|
|
177
|
+
x={d => xPos(d)}
|
|
178
|
+
y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
|
|
179
|
+
styles={styles}
|
|
180
|
+
defined={(item, i) => {
|
|
181
|
+
return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
|
|
182
|
+
}}
|
|
183
|
+
/>
|
|
184
|
+
|
|
185
|
+
{suppressedSegments.map((segment, index) => {
|
|
186
|
+
return (
|
|
187
|
+
<LinePath
|
|
188
|
+
key={index}
|
|
189
|
+
data={segment.data}
|
|
190
|
+
x={d => xPos(d)}
|
|
191
|
+
y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
|
|
192
|
+
stroke={colorScale(config.runtime.seriesLabels[seriesKey])}
|
|
193
|
+
strokeWidth={seriesData[0].weight || 2}
|
|
194
|
+
strokeOpacity={1}
|
|
195
|
+
shapeRendering='geometricPrecision'
|
|
196
|
+
strokeDasharray={handleLineType(segment.style)}
|
|
197
|
+
defined={(item, i) => {
|
|
198
|
+
return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
|
|
199
|
+
}}
|
|
200
|
+
/>
|
|
201
|
+
)
|
|
202
|
+
})}
|
|
203
|
+
</>
|
|
187
204
|
) : (
|
|
188
205
|
<>
|
|
189
206
|
{/* STANDARD LINE */}
|
|
@@ -216,7 +233,17 @@ const LineChart = (props: LineChartProps) => {
|
|
|
216
233
|
|
|
217
234
|
{/* circles for preliminaryData data */}
|
|
218
235
|
{circleData.map((d, i) => {
|
|
219
|
-
return
|
|
236
|
+
return (
|
|
237
|
+
<circle
|
|
238
|
+
key={i}
|
|
239
|
+
cx={xPos(d)}
|
|
240
|
+
cy={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey)))}
|
|
241
|
+
r={6}
|
|
242
|
+
strokeWidth={seriesData[0].weight || 2}
|
|
243
|
+
stroke={colorScale ? colorScale(config.runtime.seriesLabels[seriesKey]) : '#000'}
|
|
244
|
+
fill='#fff'
|
|
245
|
+
/>
|
|
246
|
+
)
|
|
220
247
|
})}
|
|
221
248
|
|
|
222
249
|
{/* ANIMATED LINE */}
|
|
@@ -28,7 +28,7 @@ import Regions from './Regions'
|
|
|
28
28
|
import useMinMax from '../hooks/useMinMax'
|
|
29
29
|
import useReduceData from '../hooks/useReduceData'
|
|
30
30
|
import useRightAxis from '../hooks/useRightAxis'
|
|
31
|
-
import useScales, { getTickValues }
|
|
31
|
+
import useScales, { getTickValues } from '../hooks/useScales'
|
|
32
32
|
import useTopAxis from '../hooks/useTopAxis'
|
|
33
33
|
import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
|
|
34
34
|
import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
|
|
@@ -37,7 +37,7 @@ import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
|
|
|
37
37
|
import ZoomBrush from './ZoomBrush'
|
|
38
38
|
|
|
39
39
|
const LinearChart = props => {
|
|
40
|
-
const { transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, getTextWidth } = useContext(ConfigContext)
|
|
40
|
+
const { transformedData: data, tableData, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, getTextWidth, brushConfig } = useContext(ConfigContext)
|
|
41
41
|
// todo: start destructuring this file for conciseness
|
|
42
42
|
const { visualizationType, visualizationSubType, orientation, xAxis, yAxis, runtime, debugSvg } = config
|
|
43
43
|
|
|
@@ -52,7 +52,7 @@ const LinearChart = props => {
|
|
|
52
52
|
const shouldAbbreviate = true
|
|
53
53
|
let height = config.aspectRatio ? width * config.aspectRatio : config.visualizationType === 'Forest Plot' ? config.heights['vertical'] : config.heights[orientation]
|
|
54
54
|
const xMax = width - runtime.yAxis.size - (visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
|
|
55
|
-
let yMax = height - (orientation === 'horizontal' ? 0 : runtime.xAxis.
|
|
55
|
+
let yMax = height - (orientation === 'horizontal' ? 0 : (runtime.xAxis.padding || 0))
|
|
56
56
|
|
|
57
57
|
if (config.visualizationType === 'Forest Plot') {
|
|
58
58
|
height = height + config.data.length * config.forestPlot.rowHeight
|
|
@@ -80,9 +80,9 @@ const LinearChart = props => {
|
|
|
80
80
|
// getters & functions
|
|
81
81
|
const getXAxisData = d => (isDateScale(config.runtime.xAxis) ? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime() : d[config.runtime.originalXAxis.dataKey])
|
|
82
82
|
const getYAxisData = (d, seriesKey) => d[seriesKey]
|
|
83
|
-
const xAxisDataMapped = config.brush.active &&
|
|
83
|
+
const xAxisDataMapped = config.brush.active && brushConfig.data?.length ? brushConfig.data.map(d => getXAxisData(d)) : data.map(d => getXAxisData(d))
|
|
84
84
|
const section = config.orientation === 'horizontal' || config.visualizationType === 'Forest Plot' ? 'yAxis' : 'xAxis'
|
|
85
|
-
const properties = { data, config, minValue, maxValue, isAllLine, existPositiveValue, xAxisDataMapped, xMax, yMax }
|
|
85
|
+
const properties = { data, tableData, config, minValue, maxValue, isAllLine, existPositiveValue, xAxisDataMapped, xMax, yMax }
|
|
86
86
|
const { min, max, leftMax, rightMax } = useMinMax(properties)
|
|
87
87
|
const { yScaleRight, hasRightAxis } = useRightAxis({ config, yMax, data, updateConfig })
|
|
88
88
|
const { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding, xScaleBrush } = useScales({ ...properties, min, max, leftMax, rightMax, dimensions })
|
|
@@ -207,7 +207,8 @@ const LinearChart = props => {
|
|
|
207
207
|
return false
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
const padding = orientation === 'horizontal' ? Number(config.xAxis.
|
|
210
|
+
const padding = orientation === 'horizontal' ? Number(config.xAxis.padding) : Number(config.yAxis.size)
|
|
211
|
+
const fontSize = { small: 16, medium: 18, large: 20 }
|
|
211
212
|
|
|
212
213
|
const handleNumTicks = () => {
|
|
213
214
|
// On forest plots we need to return every "study" or y axis value.
|
|
@@ -226,17 +227,99 @@ const LinearChart = props => {
|
|
|
226
227
|
})
|
|
227
228
|
}
|
|
228
229
|
|
|
230
|
+
const generatePairedBarAxis = () => {
|
|
231
|
+
let axisMaxHeight = 40;
|
|
232
|
+
|
|
233
|
+
return <>
|
|
234
|
+
<AxisBottom top={yMax} left={Number(runtime.yAxis.size)} label={runtime.xAxis.label} tickFormat={isDateScale(runtime.xAxis) ? formatDate : formatNumber} scale={g1xScale} stroke='#333' tickStroke='#333' numTicks={runtime.xAxis.numTicks || undefined}>
|
|
235
|
+
{props => {
|
|
236
|
+
return (
|
|
237
|
+
<Group className='bottom-axis'>
|
|
238
|
+
{props.ticks.map((tick, i) => {
|
|
239
|
+
const angle = tick.index !== 0 ? config.yAxis.tickRotation : 0
|
|
240
|
+
const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
|
|
241
|
+
|
|
242
|
+
const textWidth = getTextWidth(tick.value, `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
243
|
+
const axisHeight = textWidth * Math.sin(angle * (Math.PI / 180)) + 25;
|
|
244
|
+
|
|
245
|
+
if(axisHeight > axisMaxHeight) axisMaxHeight = axisHeight
|
|
246
|
+
|
|
247
|
+
return (
|
|
248
|
+
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
249
|
+
{!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
|
|
250
|
+
{!runtime.yAxis.hideLabel && (
|
|
251
|
+
<Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
|
|
252
|
+
{formatNumber(tick.value, 'left')}
|
|
253
|
+
</Text>
|
|
254
|
+
)}
|
|
255
|
+
</Group>
|
|
256
|
+
)
|
|
257
|
+
})}
|
|
258
|
+
{!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
259
|
+
</Group>
|
|
260
|
+
)
|
|
261
|
+
}}
|
|
262
|
+
</AxisBottom>
|
|
263
|
+
<AxisBottom
|
|
264
|
+
top={yMax}
|
|
265
|
+
left={Number(runtime.yAxis.size)}
|
|
266
|
+
label={runtime.xAxis.label}
|
|
267
|
+
tickFormat={isDateScale(runtime.xAxis) ? formatDate : runtime.xAxis.dataKey !== 'Year' ? formatNumber : tick => tick}
|
|
268
|
+
scale={g2xScale}
|
|
269
|
+
stroke='#333'
|
|
270
|
+
tickStroke='#333'
|
|
271
|
+
numTicks={runtime.xAxis.numTicks || undefined}
|
|
272
|
+
>
|
|
273
|
+
{props => {
|
|
274
|
+
return (
|
|
275
|
+
<>
|
|
276
|
+
<Group className='bottom-axis'>
|
|
277
|
+
{props.ticks.map((tick, i) => {
|
|
278
|
+
const angle = tick.index !== 0 ? config.yAxis.tickRotation : 0
|
|
279
|
+
const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
|
|
280
|
+
|
|
281
|
+
const textWidth = getTextWidth(tick.value, `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
282
|
+
const axisHeight = textWidth * Math.sin(angle * (Math.PI / 180)) + 25;
|
|
283
|
+
|
|
284
|
+
if(axisHeight > axisMaxHeight) axisMaxHeight = axisHeight
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
288
|
+
{!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
|
|
289
|
+
{!runtime.yAxis.hideLabel && (
|
|
290
|
+
<Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
|
|
291
|
+
{formatNumber(tick.value, 'left')}
|
|
292
|
+
</Text>
|
|
293
|
+
)}
|
|
294
|
+
</Group>
|
|
295
|
+
)
|
|
296
|
+
})}
|
|
297
|
+
{!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
298
|
+
</Group>
|
|
299
|
+
<Group>
|
|
300
|
+
<Text x={xMax / 2} y={axisMaxHeight + 20} stroke='#333' textAnchor={'middle'} verticalAnchor='start'>
|
|
301
|
+
{runtime.xAxis.label}
|
|
302
|
+
</Text>
|
|
303
|
+
</Group>
|
|
304
|
+
{svgRef.current ? svgRef.current.setAttribute('height', (Number(height) + Number(axisMaxHeight) + (runtime.xAxis.label ? 50 : 0)) + 'px') : ''}
|
|
305
|
+
</>
|
|
306
|
+
)
|
|
307
|
+
}}
|
|
308
|
+
</AxisBottom>
|
|
309
|
+
</>
|
|
310
|
+
}
|
|
311
|
+
|
|
229
312
|
return isNaN(width) ? (
|
|
230
313
|
<React.Fragment></React.Fragment>
|
|
231
314
|
) : (
|
|
232
315
|
<ErrorBoundary component='LinearChart'>
|
|
233
316
|
{/* ! Notice - div needed for tooltip boundaries (flip/flop) */}
|
|
234
|
-
<div style={{ width: `${width}px`,
|
|
317
|
+
<div style={{ width: `${width}px`, overflow: 'visible' }} className='tooltip-boundary'>
|
|
235
318
|
<svg
|
|
236
319
|
// onMouseLeave={() => setPoint(null)}
|
|
237
320
|
onMouseMove={onMouseMove}
|
|
238
321
|
width={'100%'}
|
|
239
|
-
height={
|
|
322
|
+
height={height}
|
|
240
323
|
className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${debugSvg && 'debug'}`}
|
|
241
324
|
role='img'
|
|
242
325
|
aria-label={handleChartAriaLabels(config)}
|
|
@@ -246,7 +329,7 @@ const LinearChart = props => {
|
|
|
246
329
|
<Bar width={width} height={height} fill={'transparent'}></Bar> {/* Highlighted regions */}
|
|
247
330
|
{/* Y axis */}
|
|
248
331
|
{!['Spark Line', 'Forest Plot'].includes(visualizationType) && (
|
|
249
|
-
<AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(runtime.yAxis.size) - config.yAxis.axisPadding} label={runtime.yAxis.
|
|
332
|
+
<AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(runtime.yAxis.size) - config.yAxis.axisPadding} label={runtime.yAxis.label || runtime.yAxis.label} stroke='#333' tickFormat={(tick, i) => handleLeftTickFormatting(tick, i)} numTicks={handleNumTicks()}>
|
|
250
333
|
{props => {
|
|
251
334
|
const axisCenter = config.orientation === 'horizontal' ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
252
335
|
const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
|
|
@@ -369,7 +452,7 @@ const LinearChart = props => {
|
|
|
369
452
|
<AxisBottom
|
|
370
453
|
top={runtime.horizontal && config.visualizationType !== 'Forest Plot' ? Number(heightHorizontal) + Number(config.xAxis.axisPadding) : config.visualizationType === 'Forest Plot' ? yMax + Number(config.xAxis.axisPadding) : yMax}
|
|
371
454
|
left={config.visualizationType !== 'Forest Plot' ? Number(runtime.yAxis.size) : 0}
|
|
372
|
-
label={
|
|
455
|
+
label={config[section].label}
|
|
373
456
|
tickFormat={handleBottomTickFormatting}
|
|
374
457
|
scale={xScale}
|
|
375
458
|
stroke='#333'
|
|
@@ -383,7 +466,6 @@ const LinearChart = props => {
|
|
|
383
466
|
const ismultiLabel = props.ticks.some(tick => containsMultipleWords(tick.value))
|
|
384
467
|
|
|
385
468
|
// Calculate sumOfTickWidth here, before map function
|
|
386
|
-
const fontSize = { small: 16, medium: 18, large: 20 }
|
|
387
469
|
const defaultTickLength = 8
|
|
388
470
|
const tickWidthMax = Math.max(...props.ticks.map(tick => getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)))
|
|
389
471
|
// const marginTop = 20 // moved to top bc need for yMax calcs
|
|
@@ -417,135 +499,68 @@ const LinearChart = props => {
|
|
|
417
499
|
config.dynamicMarginTop = dynamicMarginTop
|
|
418
500
|
config.xAxis.tickWidthMax = tickWidthMax
|
|
419
501
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
</Group>
|
|
479
|
-
)
|
|
502
|
+
let axisMaxHeight = 40;
|
|
503
|
+
|
|
504
|
+
const axisContents = <Group className='bottom-axis' width={dimensions[0]}>
|
|
505
|
+
{props.ticks.map((tick, i, propsTicks) => {
|
|
506
|
+
// when using LogScale show major ticks values only
|
|
507
|
+
const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
|
|
508
|
+
const tickLength = showTick === 'block' ? 16 : defaultTickLength
|
|
509
|
+
const to = { x: tick.to.x, y: tickLength }
|
|
510
|
+
const textWidth = getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
511
|
+
const limitedWidth = 100 / propsTicks.length
|
|
512
|
+
//reset rotations by updating config
|
|
513
|
+
config.yAxis.tickRotation = config.isResponsiveTicks && config.orientation === 'horizontal' ? 0 : config.yAxis.tickRotation
|
|
514
|
+
config.xAxis.tickRotation = config.isResponsiveTicks && config.orientation === 'vertical' ? 0 : config.xAxis.tickRotation
|
|
515
|
+
//configure rotation
|
|
516
|
+
|
|
517
|
+
const tickRotation = config.isResponsiveTicks && areTicksTouching ? -Number(config.xAxis.maxTickRotation) || -90 : -Number(config.runtime.xAxis.tickRotation)
|
|
518
|
+
|
|
519
|
+
const axisHeight = textWidth * Math.sin(tickRotation * -1 * (Math.PI / 180)) + 25;
|
|
520
|
+
|
|
521
|
+
if(axisHeight > axisMaxHeight) axisMaxHeight = axisHeight
|
|
522
|
+
|
|
523
|
+
return (
|
|
524
|
+
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
525
|
+
{!config.xAxis.hideTicks && <Line from={tick.from} to={orientation === 'horizontal' && config.useLogScale ? to : tick.to} stroke={config.xAxis.tickColor} strokeWidth={showTick === 'block' && config.useLogScale ? 1.3 : 1} />}
|
|
526
|
+
{!config.xAxis.hideLabel && (
|
|
527
|
+
<Text
|
|
528
|
+
dy={config.orientation === 'horizontal' && config.useLogScale ? 8 : 0}
|
|
529
|
+
display={config.orientation === 'horizontal' && config.useLogScale ? showTick : 'block'}
|
|
530
|
+
x={tick.to.x}
|
|
531
|
+
y={tick.to.y}
|
|
532
|
+
angle={tickRotation}
|
|
533
|
+
verticalAnchor={tickRotation < -50 ? 'middle' : 'start'}
|
|
534
|
+
textAnchor={tickRotation ? 'end' : 'middle'}
|
|
535
|
+
width={areTicksTouching && !config.isResponsiveTicks && !Number(config[section].tickRotation) ? limitedWidth : undefined}
|
|
536
|
+
fill={config.xAxis.tickLabelColor}
|
|
537
|
+
>
|
|
538
|
+
{tick.formattedValue}
|
|
539
|
+
</Text>
|
|
540
|
+
)}
|
|
541
|
+
</Group>
|
|
542
|
+
)
|
|
543
|
+
})}
|
|
544
|
+
{!config.xAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
545
|
+
<Text
|
|
546
|
+
x={axisCenter}
|
|
547
|
+
y={axisMaxHeight + 20}
|
|
548
|
+
textAnchor='middle'
|
|
549
|
+
verticalAnchor='start'
|
|
550
|
+
fontWeight='bold'
|
|
551
|
+
fill={config.xAxis.labelColor}
|
|
552
|
+
>
|
|
553
|
+
{props.label}
|
|
554
|
+
</Text>
|
|
555
|
+
</Group>
|
|
556
|
+
|
|
557
|
+
if(svgRef.current) svgRef.current.setAttribute('height',(Number(height) + Number(axisMaxHeight) + (runtime.xAxis.label ? 50 : 0)) + 'px')
|
|
558
|
+
|
|
559
|
+
return axisContents
|
|
480
560
|
}}
|
|
481
561
|
</AxisBottom>
|
|
482
562
|
)}
|
|
483
|
-
{visualizationType === 'Paired Bar' && (
|
|
484
|
-
<>
|
|
485
|
-
<AxisBottom top={yMax} left={Number(runtime.yAxis.size)} label={runtime.xAxis.label} tickFormat={isDateScale(runtime.xAxis) ? formatDate : formatNumber} scale={g1xScale} stroke='#333' tickStroke='#333' numTicks={runtime.xAxis.numTicks || undefined}>
|
|
486
|
-
{props => {
|
|
487
|
-
return (
|
|
488
|
-
<Group className='bottom-axis'>
|
|
489
|
-
{props.ticks.map((tick, i) => {
|
|
490
|
-
const angle = tick.index !== 0 ? config.yAxis.tickRotation : 0
|
|
491
|
-
const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
|
|
492
|
-
return (
|
|
493
|
-
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
494
|
-
{!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
|
|
495
|
-
{!runtime.yAxis.hideLabel && (
|
|
496
|
-
<Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
|
|
497
|
-
{formatNumber(tick.value, 'left')}
|
|
498
|
-
</Text>
|
|
499
|
-
)}
|
|
500
|
-
</Group>
|
|
501
|
-
)
|
|
502
|
-
})}
|
|
503
|
-
{!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
504
|
-
</Group>
|
|
505
|
-
)
|
|
506
|
-
}}
|
|
507
|
-
</AxisBottom>
|
|
508
|
-
<AxisBottom
|
|
509
|
-
top={yMax}
|
|
510
|
-
left={Number(runtime.yAxis.size)}
|
|
511
|
-
label={runtime.xAxis.label}
|
|
512
|
-
tickFormat={isDateScale(runtime.xAxis) ? formatDate : runtime.xAxis.dataKey !== 'Year' ? formatNumber : tick => tick}
|
|
513
|
-
scale={g2xScale}
|
|
514
|
-
stroke='#333'
|
|
515
|
-
tickStroke='#333'
|
|
516
|
-
numTicks={runtime.xAxis.numTicks || undefined}
|
|
517
|
-
>
|
|
518
|
-
{props => {
|
|
519
|
-
return (
|
|
520
|
-
<>
|
|
521
|
-
<Group className='bottom-axis'>
|
|
522
|
-
{props.ticks.map((tick, i) => {
|
|
523
|
-
const angle = tick.index !== 0 ? config.yAxis.tickRotation : 0
|
|
524
|
-
const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
|
|
525
|
-
return (
|
|
526
|
-
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
527
|
-
{!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
|
|
528
|
-
{!runtime.yAxis.hideLabel && (
|
|
529
|
-
<Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
|
|
530
|
-
{formatNumber(tick.value, 'left')}
|
|
531
|
-
</Text>
|
|
532
|
-
)}
|
|
533
|
-
</Group>
|
|
534
|
-
)
|
|
535
|
-
})}
|
|
536
|
-
{!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
537
|
-
</Group>
|
|
538
|
-
<Group>
|
|
539
|
-
<Text x={xMax / 2} y={config.xAxis.labelOffset} stroke='#333' textAnchor={'middle'} verticalAnchor='start'>
|
|
540
|
-
{runtime.xAxis.label}
|
|
541
|
-
</Text>
|
|
542
|
-
</Group>
|
|
543
|
-
</>
|
|
544
|
-
)
|
|
545
|
-
}}
|
|
546
|
-
</AxisBottom>
|
|
547
|
-
</>
|
|
548
|
-
)}
|
|
563
|
+
{visualizationType === 'Paired Bar' && generatePairedBarAxis()}
|
|
549
564
|
{visualizationType === 'Deviation Bar' && config.series?.length === 1 && <DeviationBar animatedChart={animatedChart} xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
|
|
550
565
|
{visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
|
|
551
566
|
{visualizationType === 'Scatter Plot' && (
|
|
@@ -724,7 +739,7 @@ const LinearChart = props => {
|
|
|
724
739
|
</Group>
|
|
725
740
|
)}
|
|
726
741
|
{config.filters && config.filters.values.length === 0 && data.length === 0 && (
|
|
727
|
-
<Text x={Number(config.yAxis.size) + Number(xMax / 2)} y={height / 2 - config.xAxis.
|
|
742
|
+
<Text x={Number(config.yAxis.size) + Number(xMax / 2)} y={height / 2 - config.xAxis.padding / 2} textAnchor='middle'>
|
|
728
743
|
{config.chartMessage.noData}
|
|
729
744
|
</Text>
|
|
730
745
|
)}
|
|
@@ -741,7 +756,8 @@ const LinearChart = props => {
|
|
|
741
756
|
</svg>
|
|
742
757
|
{tooltipData && Object.entries(tooltipData.data).length > 0 && tooltipOpen && showTooltip && tooltipData.dataYPosition && tooltipData.dataXPosition && !config.tooltips.singleSeries && (
|
|
743
758
|
<>
|
|
744
|
-
<style>{`.tooltip {background-color: rgba(255,255,255, ${config.tooltips.opacity / 100}) !important
|
|
759
|
+
<style>{`.tooltip {background-color: rgba(255,255,255, ${config.tooltips.opacity / 100}) !important;`}</style>
|
|
760
|
+
<style>{`.tooltip {max-width:300px} !important; word-wrap: break-word; `}</style>
|
|
745
761
|
<TooltipWithBounds key={Math.random()} className={'tooltip cdc-open-viz-module'} left={tooltipLeft} top={tooltipTop}>
|
|
746
762
|
<ul>{typeof tooltipData === 'object' && Object.entries(tooltipData.data).map((item, index) => <TooltipListItem item={item} key={index} />)}</ul>
|
|
747
763
|
</TooltipWithBounds>
|