@cdc/chart 4.24.3 → 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 +34377 -33726
- package/examples/feature/line/line-chart.json +361 -37
- package/examples/region-issue.json +2065 -0
- package/examples/test.json +5409 -0
- package/index.html +13 -11
- package/package.json +2 -2
- package/src/CdcChart.tsx +159 -89
- 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 +61 -63
- package/src/components/BarChart/components/BarChart.Vertical.tsx +79 -94
- package/src/components/DeviationBar.jsx +4 -2
- package/src/components/EditorPanel/EditorPanel.tsx +1580 -1924
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +19 -2
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +0 -1
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -5
- package/src/components/EditorPanel/editor-panel.scss +0 -724
- package/src/components/EditorPanel/useEditorPermissions.js +4 -1
- package/src/components/Legend/Legend.Component.tsx +82 -58
- package/src/components/Legend/Legend.tsx +5 -1
- 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 +156 -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 +33 -1
- package/src/hooks/useTooltip.tsx +11 -11
- package/src/scss/main.scss +80 -5
- 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 from '../hooks/useScales'
|
|
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,12 +452,13 @@ 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'
|
|
376
459
|
numTicks={countNumOfTicks('xAxis')}
|
|
377
460
|
tickStroke='#333'
|
|
461
|
+
tickValues={config.xAxis.manual ? getTickValues(xAxisDataMapped, xScale, config.xAxis.type === 'date-time' ? countNumOfTicks('xAxis') : config.xAxis.manualStep) : undefined}
|
|
378
462
|
>
|
|
379
463
|
{props => {
|
|
380
464
|
const axisCenter = config.visualizationType !== 'Forest Plot' ? (props.axisToPoint.x - props.axisFromPoint.x) / 2 : dimensions[0] / 2
|
|
@@ -382,7 +466,6 @@ const LinearChart = props => {
|
|
|
382
466
|
const ismultiLabel = props.ticks.some(tick => containsMultipleWords(tick.value))
|
|
383
467
|
|
|
384
468
|
// Calculate sumOfTickWidth here, before map function
|
|
385
|
-
const fontSize = { small: 16, medium: 18, large: 20 }
|
|
386
469
|
const defaultTickLength = 8
|
|
387
470
|
const tickWidthMax = Math.max(...props.ticks.map(tick => getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)))
|
|
388
471
|
// const marginTop = 20 // moved to top bc need for yMax calcs
|
|
@@ -416,135 +499,68 @@ const LinearChart = props => {
|
|
|
416
499
|
config.dynamicMarginTop = dynamicMarginTop
|
|
417
500
|
config.xAxis.tickWidthMax = tickWidthMax
|
|
418
501
|
|
|
419
|
-
|
|
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
|
-
</Group>
|
|
478
|
-
)
|
|
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
|
|
479
560
|
}}
|
|
480
561
|
</AxisBottom>
|
|
481
562
|
)}
|
|
482
|
-
{visualizationType === 'Paired Bar' && (
|
|
483
|
-
<>
|
|
484
|
-
<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}>
|
|
485
|
-
{props => {
|
|
486
|
-
return (
|
|
487
|
-
<Group className='bottom-axis'>
|
|
488
|
-
{props.ticks.map((tick, i) => {
|
|
489
|
-
const angle = tick.index !== 0 ? config.yAxis.tickRotation : 0
|
|
490
|
-
const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
|
|
491
|
-
return (
|
|
492
|
-
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
493
|
-
{!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
|
|
494
|
-
{!runtime.yAxis.hideLabel && (
|
|
495
|
-
<Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
|
|
496
|
-
{formatNumber(tick.value, 'left')}
|
|
497
|
-
</Text>
|
|
498
|
-
)}
|
|
499
|
-
</Group>
|
|
500
|
-
)
|
|
501
|
-
})}
|
|
502
|
-
{!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
503
|
-
</Group>
|
|
504
|
-
)
|
|
505
|
-
}}
|
|
506
|
-
</AxisBottom>
|
|
507
|
-
<AxisBottom
|
|
508
|
-
top={yMax}
|
|
509
|
-
left={Number(runtime.yAxis.size)}
|
|
510
|
-
label={runtime.xAxis.label}
|
|
511
|
-
tickFormat={isDateScale(runtime.xAxis) ? formatDate : runtime.xAxis.dataKey !== 'Year' ? formatNumber : tick => tick}
|
|
512
|
-
scale={g2xScale}
|
|
513
|
-
stroke='#333'
|
|
514
|
-
tickStroke='#333'
|
|
515
|
-
numTicks={runtime.xAxis.numTicks || undefined}
|
|
516
|
-
>
|
|
517
|
-
{props => {
|
|
518
|
-
return (
|
|
519
|
-
<>
|
|
520
|
-
<Group className='bottom-axis'>
|
|
521
|
-
{props.ticks.map((tick, i) => {
|
|
522
|
-
const angle = tick.index !== 0 ? config.yAxis.tickRotation : 0
|
|
523
|
-
const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
|
|
524
|
-
return (
|
|
525
|
-
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
526
|
-
{!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
|
|
527
|
-
{!runtime.yAxis.hideLabel && (
|
|
528
|
-
<Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
|
|
529
|
-
{formatNumber(tick.value, 'left')}
|
|
530
|
-
</Text>
|
|
531
|
-
)}
|
|
532
|
-
</Group>
|
|
533
|
-
)
|
|
534
|
-
})}
|
|
535
|
-
{!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
536
|
-
</Group>
|
|
537
|
-
<Group>
|
|
538
|
-
<Text x={xMax / 2} y={config.xAxis.labelOffset} stroke='#333' textAnchor={'middle'} verticalAnchor='start'>
|
|
539
|
-
{runtime.xAxis.label}
|
|
540
|
-
</Text>
|
|
541
|
-
</Group>
|
|
542
|
-
</>
|
|
543
|
-
)
|
|
544
|
-
}}
|
|
545
|
-
</AxisBottom>
|
|
546
|
-
</>
|
|
547
|
-
)}
|
|
563
|
+
{visualizationType === 'Paired Bar' && generatePairedBarAxis()}
|
|
548
564
|
{visualizationType === 'Deviation Bar' && config.series?.length === 1 && <DeviationBar animatedChart={animatedChart} xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
|
|
549
565
|
{visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
|
|
550
566
|
{visualizationType === 'Scatter Plot' && (
|
|
@@ -723,7 +739,7 @@ const LinearChart = props => {
|
|
|
723
739
|
</Group>
|
|
724
740
|
)}
|
|
725
741
|
{config.filters && config.filters.values.length === 0 && data.length === 0 && (
|
|
726
|
-
<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'>
|
|
727
743
|
{config.chartMessage.noData}
|
|
728
744
|
</Text>
|
|
729
745
|
)}
|
|
@@ -740,7 +756,8 @@ const LinearChart = props => {
|
|
|
740
756
|
</svg>
|
|
741
757
|
{tooltipData && Object.entries(tooltipData.data).length > 0 && tooltipOpen && showTooltip && tooltipData.dataYPosition && tooltipData.dataXPosition && !config.tooltips.singleSeries && (
|
|
742
758
|
<>
|
|
743
|
-
<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>
|
|
744
761
|
<TooltipWithBounds key={Math.random()} className={'tooltip cdc-open-viz-module'} left={tooltipLeft} top={tooltipTop}>
|
|
745
762
|
<ul>{typeof tooltipData === 'object' && Object.entries(tooltipData.data).map((item, index) => <TooltipListItem item={item} key={index} />)}</ul>
|
|
746
763
|
</TooltipWithBounds>
|