@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.
Files changed (33) hide show
  1. package/dist/cdcchart.js +32130 -31726
  2. package/index.html +7 -7
  3. package/package.json +2 -2
  4. package/src/CdcChart.tsx +17 -13
  5. package/src/_stories/Chart.stories.tsx +8 -0
  6. package/src/_stories/_mock/bar-chart-suppressed.json +474 -0
  7. package/src/components/AreaChart/components/AreaChart.jsx +2 -2
  8. package/src/components/BarChart/components/BarChart.Horizontal.tsx +52 -47
  9. package/src/components/BarChart/components/BarChart.Vertical.tsx +77 -92
  10. package/src/components/DeviationBar.jsx +4 -2
  11. package/src/components/EditorPanel/EditorPanel.tsx +289 -601
  12. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +19 -2
  13. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -5
  14. package/src/components/EditorPanel/useEditorPermissions.js +4 -1
  15. package/src/components/Legend/Legend.Component.tsx +62 -10
  16. package/src/components/LineChart/LineChartProps.ts +13 -6
  17. package/src/components/LineChart/components/LineChart.Circle.tsx +22 -11
  18. package/src/components/LineChart/helpers.ts +134 -10
  19. package/src/components/LineChart/index.tsx +69 -42
  20. package/src/components/LinearChart.jsx +155 -139
  21. package/src/components/ZoomBrush.tsx +40 -21
  22. package/src/data/initial-state.js +4 -4
  23. package/src/hooks/useBarChart.js +47 -22
  24. package/src/hooks/useMinMax.ts +21 -2
  25. package/src/hooks/useScales.ts +23 -23
  26. package/src/hooks/useTooltip.tsx +11 -11
  27. package/src/scss/main.scss +56 -6
  28. package/src/types/ChartConfig.ts +3 -13
  29. package/src/types/ChartContext.ts +4 -0
  30. package/src/_stories/ChartLine.preliminary.tsx +0 -19
  31. package/src/_stories/ChartSuppress.stories.tsx +0 -19
  32. package/src/_stories/_mock/suppress_mock.json +0 -911
  33. 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
- import { filterCircles, createStyles } from './helpers'
15
+
16
+ // Local helpers and components
17
+ import { filterCircles, createStyles, createDataSegments } from './helpers'
12
18
  import LineChartCircle from './components/LineChart.Circle'
13
19
 
14
- // types
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
- colorScale,
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.preliminaryData, rawData, seriesKey)
63
+ const circleData = filterCircles(config?.preliminaryData, tableD, seriesKey)
64
64
  // styles for preliminary Data items
65
- let styles = createStyles({ preliminaryData: config.preliminaryData, data: tableData, stroke: colorScale(config.runtime.seriesLabels[seriesKey]), strokeWidth: seriesData[0].weight || 2, handleLineType, lineType, seriesKey })
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(d => d.value && d.column) ? (
167
- <SplitLinePath
168
- curve={allCurves[seriesData[0].lineType]}
169
- segments={(config.xAxis.type === 'date-time'
170
- ? data.sort((d1, d2) => {
171
- let x1 = getXAxisData(d1)
172
- let x2 = getXAxisData(d2)
173
- if (x1 < x2) return -1
174
- if (x2 < x1) return 1
175
- return 0
176
- })
177
- : data
178
- ).map(d => [d])}
179
- segmentation='x'
180
- x={d => xPos(d)}
181
- y={d => (seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey))))}
182
- styles={styles}
183
- defined={(item, i) => {
184
- return item[seriesKey] !== '' && item[seriesKey] !== null && item[seriesKey] !== undefined
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 <circle key={i} cx={xPos(d)} cy={seriesAxis === 'Right' ? yScaleRight(getYAxisData(d, seriesKey)) : yScale(Number(getYAxisData(d, seriesKey)))} r={6} strokeWidth={seriesData[0].weight || 2} stroke={colorScale ? colorScale(config.runtime.seriesLabels[seriesKey]) : '#000'} fill='#fff' />
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 } 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.size)
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 && config.brush.data?.length ? config.brush.data.map(d => getXAxisData(d)) : data.map(d => getXAxisData(d))
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.size) : Number(config.yAxis.size)
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`, height: `${height}px`, overflow: 'visible' }} className='tooltip-boundary'>
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={'100%'}
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.yAxis?.label || runtime.yAxis.label} stroke='#333' tickFormat={(tick, i) => handleLeftTickFormatting(tick, i)} numTicks={handleNumTicks()}>
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={runtime.xAxis.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
- return (
421
- <Group className='bottom-axis' width={dimensions[0]}>
422
- {props.ticks.map((tick, i, propsTicks) => {
423
- // when using LogScale show major ticks values only
424
- const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
425
- const tickLength = showTick === 'block' ? 16 : defaultTickLength
426
- const to = { x: tick.to.x, y: tickLength }
427
- let textWidth = getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
428
- let limitedWidth = 100 / propsTicks.length
429
- //reset rotations by updating config
430
- config.yAxis.tickRotation = config.isResponsiveTicks && config.orientation === 'horizontal' ? 0 : config.yAxis.tickRotation
431
- config.xAxis.tickRotation = config.isResponsiveTicks && config.orientation === 'vertical' ? 0 : config.xAxis.tickRotation
432
- //configure rotation
433
-
434
- const tickRotation = config.isResponsiveTicks && areTicksTouching ? -Number(config.xAxis.maxTickRotation) || -90 : -Number(config.runtime.xAxis.tickRotation)
435
-
436
- return (
437
- <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
438
- {!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} />}
439
- {!config.xAxis.hideLabel && (
440
- <Text
441
- dy={config.orientation === 'horizontal' && config.useLogScale ? 8 : 0}
442
- display={config.orientation === 'horizontal' && config.useLogScale ? showTick : 'block'}
443
- x={tick.to.x}
444
- y={tick.to.y}
445
- angle={tickRotation}
446
- verticalAnchor={tickRotation < -50 ? 'middle' : 'start'}
447
- textAnchor={tickRotation ? 'end' : 'middle'}
448
- width={areTicksTouching && !config.isResponsiveTicks && !Number(config[section].tickRotation) ? limitedWidth : textWidth}
449
- fill={config.xAxis.tickLabelColor}
450
- >
451
- {tick.formattedValue}
452
- </Text>
453
- )}
454
- </Group>
455
- )
456
- })}
457
- {!config.xAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
458
- <Text
459
- x={axisCenter}
460
- y={
461
- config.visualizationType === 'Forest Plot'
462
- ? config.xAxis.tickWidthMax + 40
463
- : config.orientation === 'horizontal'
464
- ? dynamicMarginTop || config.xAxis.labelOffset
465
- : config.isResponsiveTicks && dynamicMarginTop && !isHorizontal
466
- ? dynamicMarginTop
467
- : Number(rotation) && !config.isResponsiveTicks && !isHorizontal
468
- ? Number(rotation + tickWidthMax / 1.3)
469
- : Number(config.xAxis.labelOffset)
470
- }
471
- textAnchor='middle'
472
- verticalAnchor='start'
473
- fontWeight='bold'
474
- fill={config.xAxis.labelColor}
475
- >
476
- {props.label}
477
- </Text>
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.size / 2} textAnchor='middle'>
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`}</style>
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>