@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.
Files changed (39) hide show
  1. package/dist/cdcchart.js +34377 -33726
  2. package/examples/feature/line/line-chart.json +361 -37
  3. package/examples/region-issue.json +2065 -0
  4. package/examples/test.json +5409 -0
  5. package/index.html +13 -11
  6. package/package.json +2 -2
  7. package/src/CdcChart.tsx +159 -89
  8. package/src/_stories/Chart.stories.tsx +8 -0
  9. package/src/_stories/_mock/bar-chart-suppressed.json +474 -0
  10. package/src/components/AreaChart/components/AreaChart.jsx +2 -2
  11. package/src/components/BarChart/components/BarChart.Horizontal.tsx +61 -63
  12. package/src/components/BarChart/components/BarChart.Vertical.tsx +79 -94
  13. package/src/components/DeviationBar.jsx +4 -2
  14. package/src/components/EditorPanel/EditorPanel.tsx +1580 -1924
  15. package/src/components/EditorPanel/components/Panels/Panel.General.tsx +19 -2
  16. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +0 -1
  17. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -5
  18. package/src/components/EditorPanel/editor-panel.scss +0 -724
  19. package/src/components/EditorPanel/useEditorPermissions.js +4 -1
  20. package/src/components/Legend/Legend.Component.tsx +82 -58
  21. package/src/components/Legend/Legend.tsx +5 -1
  22. package/src/components/LineChart/LineChartProps.ts +13 -6
  23. package/src/components/LineChart/components/LineChart.Circle.tsx +22 -11
  24. package/src/components/LineChart/helpers.ts +134 -10
  25. package/src/components/LineChart/index.tsx +69 -42
  26. package/src/components/LinearChart.jsx +156 -139
  27. package/src/components/ZoomBrush.tsx +40 -21
  28. package/src/data/initial-state.js +4 -4
  29. package/src/hooks/useBarChart.js +47 -22
  30. package/src/hooks/useMinMax.ts +21 -2
  31. package/src/hooks/useScales.ts +33 -1
  32. package/src/hooks/useTooltip.tsx +11 -11
  33. package/src/scss/main.scss +80 -5
  34. package/src/types/ChartConfig.ts +3 -13
  35. package/src/types/ChartContext.ts +4 -0
  36. package/src/_stories/ChartLine.preliminary.tsx +0 -19
  37. package/src/_stories/ChartSuppress.stories.tsx +0 -19
  38. package/src/_stories/_mock/suppress_mock.json +0 -911
  39. 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 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,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={runtime.xAxis.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
- return (
420
- <Group className='bottom-axis' width={dimensions[0]}>
421
- {props.ticks.map((tick, i, propsTicks) => {
422
- // when using LogScale show major ticks values only
423
- const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
424
- const tickLength = showTick === 'block' ? 16 : defaultTickLength
425
- const to = { x: tick.to.x, y: tickLength }
426
- let textWidth = getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
427
- let limitedWidth = 100 / propsTicks.length
428
- //reset rotations by updating config
429
- config.yAxis.tickRotation = config.isResponsiveTicks && config.orientation === 'horizontal' ? 0 : config.yAxis.tickRotation
430
- config.xAxis.tickRotation = config.isResponsiveTicks && config.orientation === 'vertical' ? 0 : config.xAxis.tickRotation
431
- //configure rotation
432
-
433
- const tickRotation = config.isResponsiveTicks && areTicksTouching ? -Number(config.xAxis.maxTickRotation) || -90 : -Number(config.runtime.xAxis.tickRotation)
434
-
435
- return (
436
- <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
437
- {!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} />}
438
- {!config.xAxis.hideLabel && (
439
- <Text
440
- dy={config.orientation === 'horizontal' && config.useLogScale ? 8 : 0}
441
- display={config.orientation === 'horizontal' && config.useLogScale ? showTick : 'block'}
442
- x={tick.to.x}
443
- y={tick.to.y}
444
- angle={tickRotation}
445
- verticalAnchor={tickRotation < -50 ? 'middle' : 'start'}
446
- textAnchor={tickRotation ? 'end' : 'middle'}
447
- width={areTicksTouching && !config.isResponsiveTicks && !Number(config[section].tickRotation) ? limitedWidth : textWidth}
448
- fill={config.xAxis.tickLabelColor}
449
- >
450
- {tick.formattedValue}
451
- </Text>
452
- )}
453
- </Group>
454
- )
455
- })}
456
- {!config.xAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
457
- <Text
458
- x={axisCenter}
459
- y={
460
- config.visualizationType === 'Forest Plot'
461
- ? config.xAxis.tickWidthMax + 40
462
- : config.orientation === 'horizontal'
463
- ? dynamicMarginTop || config.xAxis.labelOffset
464
- : config.isResponsiveTicks && dynamicMarginTop && !isHorizontal
465
- ? dynamicMarginTop
466
- : Number(rotation) && !config.isResponsiveTicks && !isHorizontal
467
- ? Number(rotation + tickWidthMax / 1.3)
468
- : Number(config.xAxis.labelOffset)
469
- }
470
- textAnchor='middle'
471
- verticalAnchor='start'
472
- fontWeight='bold'
473
- fill={config.xAxis.labelColor}
474
- >
475
- {props.label}
476
- </Text>
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.size / 2} textAnchor='middle'>
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`}</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>
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>