@cdc/chart 4.25.2-25 → 4.25.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/cdcchart-1a1724a1.es.js +4886 -0
  2. package/dist/cdcchart.js +36771 -61000
  3. package/index.html +1 -1
  4. package/package.json +2 -2
  5. package/src/CdcChart.tsx +1 -22
  6. package/src/CdcChartComponent.tsx +27 -13
  7. package/src/_stories/Chart.CI.stories.tsx +33 -0
  8. package/src/_stories/Chart.Legend.Gradient.stories.tsx +6 -0
  9. package/src/_stories/Chart.stories.tsx +0 -16
  10. package/src/_stories/_mock/bar_chart_ci_labels.json +620 -0
  11. package/src/_stories/_mock/legend_groupBy_mock.json +474 -0
  12. package/src/components/BarChart/components/BarChart.Horizontal.tsx +1 -1
  13. package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +1 -1
  14. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +1 -1
  15. package/src/components/BarChart/components/BarChart.Vertical.tsx +2 -2
  16. package/src/components/EditorPanel/EditorPanel.tsx +60 -24
  17. package/src/components/EditorPanel/helpers/updateFieldRankByValue.ts +4 -3
  18. package/src/components/Legend/Legend.Component.tsx +69 -58
  19. package/src/components/Legend/Legend.tsx +3 -1
  20. package/src/components/Legend/LegendGroup/LegendGroup.styles.css +40 -0
  21. package/src/components/Legend/LegendGroup/LegendGroup.tsx +103 -0
  22. package/src/components/Legend/LegendGroup/index.tsx +3 -0
  23. package/src/components/LineChart/components/LineChart.Circle.tsx +14 -9
  24. package/src/components/LineChart/index.tsx +18 -7
  25. package/src/components/LinearChart.tsx +38 -30
  26. package/src/data/initial-state.js +1 -1
  27. package/src/helpers/dataHelpers.ts +10 -0
  28. package/src/helpers/isConvertLineToBarGraph.ts +2 -2
  29. package/src/helpers/sizeHelpers.ts +23 -0
  30. package/src/hooks/useBarChart.ts +2 -1
  31. package/src/hooks/useScales.ts +2 -8
  32. package/src/store/chart.actions.ts +1 -1
  33. package/src/types/ChartConfig.ts +2 -1
@@ -15,6 +15,8 @@ import LegendGradient from '@cdc/core/components/Legend/Legend.Gradient'
15
15
  import { DimensionsType } from '@cdc/core/types/Dimensions'
16
16
  import { isLegendWrapViewport } from '@cdc/core/helpers/viewports'
17
17
  import LegendLineShape from './LegendLine.Shape'
18
+ import LegendGroup from './LegendGroup'
19
+ import { getSeriesWithData } from '../../helpers/dataHelpers'
18
20
 
19
21
  const LEGEND_PADDING = 36
20
22
 
@@ -29,6 +31,7 @@ export interface LegendProps {
29
31
  seriesHighlight: string[]
30
32
  skipId: string
31
33
  dimensions: DimensionsType // for responsive width legend
34
+ transformedData: any
32
35
  }
33
36
 
34
37
  /* eslint-disable jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */
@@ -43,12 +46,17 @@ const Legend: React.FC<LegendProps> = forwardRef(
43
46
  currentViewport,
44
47
  formatLabels,
45
48
  skipId = 'legend',
46
- dimensions
49
+ dimensions,
50
+ transformedData: data
47
51
  },
48
52
  ref
49
53
  ) => {
50
54
  const { innerClasses, containerClasses } = getLegendClasses(config)
51
55
  const { runtime, legend } = config
56
+ const { series } = runtime
57
+
58
+ const seriesWithData = getSeriesWithData(config)
59
+ const dontFilterLegendItems = !series.length || legend.unified
52
60
 
53
61
  const isLegendBottom =
54
62
  legend?.position === 'bottom' ||
@@ -84,77 +92,80 @@ const Legend: React.FC<LegendProps> = forwardRef(
84
92
  dimensions={dimensions}
85
93
  parentPaddingToSubtract={legend.hideBorder ? 0 : LEGEND_PADDING}
86
94
  />
95
+ <LegendGroup formatLabels={formatLabels} />
87
96
 
88
97
  <LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
89
98
  {labels => {
90
99
  return (
91
100
  <>
92
101
  <div className={innerClasses.join(' ')}>
93
- {formatLabels(labels as Label[]).map((label, i) => {
94
- let className = ['legend-item', `legend-text--${label.text.replace(' ', '').toLowerCase()}`]
95
- let itemName = label.datum
96
-
97
- // Filter excluded data keys from legend
98
- if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
99
- return null
100
- }
102
+ {formatLabels(labels as Label[])
103
+ .filter(label => dontFilterLegendItems || seriesWithData.includes(label.datum))
104
+ .map((label, i) => {
105
+ let className = ['legend-item', `legend-text--${label.text.replace(' ', '').toLowerCase()}`]
106
+ let itemName = label.datum
107
+
108
+ // Filter excluded data keys from legend
109
+ if (config.exclusions.active && config.exclusions.keys?.includes(itemName)) {
110
+ return null
111
+ }
101
112
 
102
- if (runtime.seriesLabels) {
103
- let index = config.runtime.seriesLabelsAll.indexOf(itemName)
104
- itemName = config.runtime.seriesKeys[index]
113
+ if (runtime.seriesLabels) {
114
+ let index = config.runtime.seriesLabelsAll.indexOf(itemName)
115
+ itemName = config.runtime.seriesKeys[index]
105
116
 
106
- if (runtime?.forecastingSeriesKeys?.length > 0) {
107
- itemName = label.text
117
+ if (runtime?.forecastingSeriesKeys?.length > 0) {
118
+ itemName = label.text
119
+ }
108
120
  }
109
- }
110
121
 
111
- if (seriesHighlight.length) {
112
- if (!seriesHighlight.includes(itemName)) {
113
- className.push('inactive')
114
- } else className.push('highlighted')
115
- }
122
+ if (seriesHighlight.length) {
123
+ if (!seriesHighlight.includes(itemName)) {
124
+ className.push('inactive')
125
+ } else className.push('highlighted')
126
+ }
116
127
 
117
- if (config.legend.style === 'gradient') {
118
- return <></>
119
- }
128
+ if (config.legend.style === 'gradient' || config.legend.groupBy) {
129
+ return <></>
130
+ }
120
131
 
121
- return (
122
- <LegendItem
123
- className={className.join(' ')}
124
- tabIndex={0}
125
- key={`legend-quantile-${i}`}
126
- onKeyDown={e => {
127
- if (e.key === 'Enter') {
132
+ return (
133
+ <LegendItem
134
+ className={className.join(' ')}
135
+ tabIndex={0}
136
+ key={`legend-quantile-${i}`}
137
+ onKeyDown={e => {
138
+ if (e.key === 'Enter') {
139
+ e.preventDefault()
140
+ highlight(label)
141
+ }
142
+ }}
143
+ onClick={e => {
128
144
  e.preventDefault()
129
145
  highlight(label)
130
- }
131
- }}
132
- onClick={e => {
133
- e.preventDefault()
134
- highlight(label)
135
- }}
136
- role='button'
137
- >
138
- <>
139
- {config.visualizationType === 'Line' && config.legend.style === 'lines' ? (
140
- <React.Fragment>
141
- <LegendLineShape index={i} label={label} config={config} />
142
- </React.Fragment>
143
- ) : (
144
- <>
145
- <LegendShape
146
- shape={config.legend.style === 'boxes' ? 'square' : 'circle'}
147
- fill={label.value}
148
- />
149
- </>
150
- )}
151
- </>
152
- <LegendLabel align='left' className='m-0'>
153
- {label.text}
154
- </LegendLabel>
155
- </LegendItem>
156
- )
157
- })}
146
+ }}
147
+ role='button'
148
+ >
149
+ <>
150
+ {config.visualizationType === 'Line' && config.legend.style === 'lines' ? (
151
+ <React.Fragment>
152
+ <LegendLineShape index={i} label={label} config={config} />
153
+ </React.Fragment>
154
+ ) : (
155
+ <>
156
+ <LegendShape
157
+ shape={config.legend.style === 'boxes' ? 'square' : 'circle'}
158
+ fill={label.value}
159
+ />
160
+ </>
161
+ )}
162
+ </>
163
+ <LegendLabel align='left' className='m-0'>
164
+ {parse(label.text)}
165
+ </LegendLabel>
166
+ </LegendItem>
167
+ )
168
+ })}
158
169
 
159
170
  {highLightedLegendItems.map((bar, i) => {
160
171
  // if duplicates only return first item
@@ -17,7 +17,8 @@ const Legend = forwardRef((props, ref) => {
17
17
  transformedData: data,
18
18
  currentViewport,
19
19
  dimensions,
20
- getTextWidth
20
+ getTextWidth,
21
+ transformedData
21
22
  } = useContext(ConfigContext)
22
23
  if (!config.legend) return null
23
24
  // create fn to reverse labels while legend is Bottom. Legend-right , legend-left works by default.
@@ -30,6 +31,7 @@ const Legend = forwardRef((props, ref) => {
30
31
  getTextWidth={getTextWidth}
31
32
  dimensions={dimensions}
32
33
  ref={ref}
34
+ transformedData={transformedData}
33
35
  skipId={props.skipId || 'legend'}
34
36
  config={config}
35
37
  colorScale={colorScale}
@@ -0,0 +1,40 @@
1
+ .legend-container {
2
+ .legend-group {
3
+ font-size: 1rem;
4
+
5
+ line-height: 18px;
6
+ margin-bottom: 0.5rem;
7
+
8
+ .group-item .visx-legend-label {
9
+ font-weight: 400;
10
+ font-size: 0.889rem;
11
+ margin-bottom: 0.5rem;
12
+ }
13
+ .group-label {
14
+ font-weight: 500;
15
+ font-family: Nunito, sans-serif;
16
+ font-size: 1rem;
17
+ }
18
+ }
19
+
20
+ .legend-group.top,
21
+ .legend-group.bottom {
22
+ grid-gap: 10px;
23
+
24
+ &.group-item {
25
+ display: flex;
26
+ flex-direction: column;
27
+ align-items: flex-start;
28
+ }
29
+
30
+ & .inactive {
31
+ opacity: 0.5;
32
+ transition: 0.2s all;
33
+ }
34
+ & .highlighted {
35
+ outline: 1px solid #005ea2;
36
+ outline-offset: 5px;
37
+ border-radius: 1px;
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,103 @@
1
+ import React, { useContext } from 'react'
2
+ import { LegendOrdinal, LegendItem, LegendLabel } from '@visx/legend'
3
+ import LegendShape from '@cdc/core/components/LegendShape'
4
+ import ConfigContext from '../../../ConfigContext'
5
+ import _ from 'lodash'
6
+ import './LegendGroup.styles.css'
7
+
8
+ interface LegendGroup {
9
+ formatLabels: Function
10
+ }
11
+ const LegendGroup = ({ formatLabels }) => {
12
+ const {
13
+ highlight,
14
+ seriesHighlight,
15
+ colorScale,
16
+ transformedData: data,
17
+ config,
18
+ currentViewport
19
+ } = useContext(ConfigContext)
20
+
21
+ const getSubGroups = (data, key: string | undefined) => {
22
+ const uniqueGroups = new Set()
23
+ data.forEach(d => {
24
+ config.series.forEach(series => {
25
+ if (d[key] && d[series.dataKey]) {
26
+ uniqueGroups.add(d[key])
27
+ }
28
+ })
29
+ })
30
+ return Array.from(uniqueGroups) as string[]
31
+ }
32
+
33
+ const groups: string[] = getSubGroups(data, config.legend.groupBy)
34
+
35
+ const classNames = ['legend-group', 'container', config.legend.position, currentViewport, 'row']
36
+
37
+ const gridCol =
38
+ currentViewport === 'xs'
39
+ ? 'col-12'
40
+ : currentViewport === 'sm'
41
+ ? 'col-6'
42
+ : currentViewport === 'md'
43
+ ? 'col-4'
44
+ : 'col-3'
45
+
46
+ const isSigleCol = config.legend.position === 'bottom' || config.legend.position === 'top' ? gridCol : 'col-12'
47
+
48
+ let classNameItem = ['legend-group group-item', isSigleCol]
49
+
50
+ return (
51
+ <div className={classNames.join(' ')}>
52
+ {groups.map(group => {
53
+ return (
54
+ <div className={classNameItem.join(' ')} key={group}>
55
+ <div>
56
+ <p className='legend-group group-label'>{group}</p>
57
+ </div>
58
+ <LegendOrdinal scale={colorScale} itemDirection='row' labelMargin='0 20px 0 0' shapeMargin='0 10px 0'>
59
+ {labels =>
60
+ formatLabels(labels)
61
+ .filter(label => {
62
+ const groupBy = config.legend.groupBy || ''
63
+ return data.some(d => d[groupBy] === group && d[label.text] !== undefined && d[label.text] !== 'NA')
64
+ })
65
+
66
+ .map((label, i) => {
67
+ let className = ['legend-group', 'group-item']
68
+ if (seriesHighlight.length) {
69
+ if (!seriesHighlight.includes(label.datum)) {
70
+ className.push('inactive')
71
+ } else {
72
+ className.push('highlighted')
73
+ }
74
+ }
75
+
76
+ return (
77
+ <LegendItem
78
+ alignItems='start'
79
+ className={className.join(' ')}
80
+ onClick={e => {
81
+ e.preventDefault()
82
+ highlight(label)
83
+ }}
84
+ key={`legend-item-${i}`}
85
+ tabIndex={0}
86
+ >
87
+ <LegendShape shape={config.legend.style === 'boxes' ? 'square' : 'circle'} fill={label.value} />
88
+ <LegendLabel align='left' margin='0'>
89
+ {label.text}
90
+ </LegendLabel>
91
+ </LegendItem>
92
+ )
93
+ })
94
+ }
95
+ </LegendOrdinal>
96
+ </div>
97
+ )
98
+ })}
99
+ </div>
100
+ )
101
+ }
102
+
103
+ export default LegendGroup
@@ -0,0 +1,3 @@
1
+ import LegendGroup from './LegendGroup'
2
+
3
+ export default LegendGroup
@@ -46,7 +46,7 @@ const Glyphs = [
46
46
  const LineChartCircle = (props: LineChartCircleProps) => {
47
47
  const {
48
48
  config,
49
- d,
49
+ d: pointData,
50
50
  tableData,
51
51
  displayArea,
52
52
  seriesKey,
@@ -96,7 +96,7 @@ const LineChartCircle = (props: LineChartCircleProps) => {
96
96
  if (mode === 'ALWAYS_SHOW_POINTS' && lineDatapointStyle !== 'hidden') {
97
97
  if (lineDatapointStyle === 'always show') {
98
98
  const isMatch = circleData?.some(
99
- cd => cd[config.xAxis.dataKey] === d[config.xAxis.dataKey] && cd[seriesKey] === d[seriesKey]
99
+ cd => cd[config.xAxis.dataKey] === pointData[config.xAxis.dataKey] && cd[seriesKey] === pointData[seriesKey]
100
100
  )
101
101
 
102
102
  if (
@@ -105,13 +105,14 @@ const LineChartCircle = (props: LineChartCircleProps) => {
105
105
  (visual.maximumShapeAmount === seriesIndex && visual.lineDatapointSymbol === 'standard')
106
106
  )
107
107
  return <></>
108
- const positionLeft = getXPos(d[config.xAxis.dataKey])
109
- const positionTop = filtered.axis === 'Right' ? yScaleRight(d[filtered.dataKey]) : yScale(d[filtered.dataKey])
108
+ const positionLeft = getXPos(pointData[config.xAxis.dataKey])
109
+ const positionTop =
110
+ filtered.axis === 'Right' ? yScaleRight(pointData[filtered.dataKey]) : yScale(pointData[filtered.dataKey])
110
111
 
111
112
  return (
112
113
  <g transform={transformShape(positionTop, positionLeft)}>
113
114
  <Shape
114
- opacity={d[seriesKey] ? 1 : 0}
115
+ opacity={pointData[seriesKey] ? 1 : 0}
115
116
  fillOpacity={1}
116
117
  fill={getColor(displayArea, colorScale, config, seriesKey, seriesKey)}
117
118
  style={{ filter: 'unset', opacity: 1 }}
@@ -197,10 +198,14 @@ const LineChartCircle = (props: LineChartCircleProps) => {
197
198
 
198
199
  return isFirstPoint || isLastPoint || isMiddlePoint
199
200
  }
200
-
201
- if (drawIsolatedPoints(dataIndex, seriesKey)) {
202
- const positionTop = filtered?.axis === 'Right' ? yScaleRight(d[filtered?.dataKey]) : yScale(d[filtered?.dataKey])
203
- const positionLeft = getXPos(d[config.xAxis?.dataKey])
201
+ const _dataIndex = pointData
202
+ ? data.findIndex(item => item[config.xAxis.dataKey] === pointData[config.xAxis.dataKey])
203
+ : dataIndex
204
+
205
+ if (drawIsolatedPoints(_dataIndex, seriesKey)) {
206
+ const positionTop =
207
+ filtered?.axis === 'Right' ? yScaleRight(pointData[filtered?.dataKey]) : yScale(pointData[filtered?.dataKey])
208
+ const positionLeft = getXPos(pointData[config.xAxis?.dataKey])
204
209
  const color = colorScale(config.runtime.seriesLabelsAll[seriesIndex])
205
210
 
206
211
  return (
@@ -393,21 +393,32 @@ const LineChart = (props: LineChartProps) => {
393
393
  break
394
394
  }
395
395
  }
396
- if (!lastDatum) {
396
+ if (!lastDatum || legend.position === 'right') {
397
397
  return <></>
398
398
  }
399
+
400
+ let labelText = config.runtime.seriesLabels[seriesKey] || seriesKey
401
+ // truncate labels longer that 10 chars
402
+ const ellipsis = '...'
403
+ if (labelText.length > 10) {
404
+ labelText = labelText.substring(0, 10) + ellipsis
405
+ }
406
+
399
407
  return (
400
408
  <Text
409
+ display={
410
+ legend.behavior === 'highlight' ||
411
+ (seriesHighlight.length === 0 && !legend.dynamicLegend) ||
412
+ seriesHighlight.indexOf(seriesKey) !== -1
413
+ ? 'block'
414
+ : 'none'
415
+ }
401
416
  x={xPos(lastDatum) + 5}
402
417
  y={yScale(getYAxisData(lastDatum, seriesKey))}
403
418
  alignmentBaseline='middle'
404
- fill={
405
- config.colorMatchLineSeriesLabels && colorScale
406
- ? colorScale(config.runtime.seriesLabels[seriesKey] || seriesKey)
407
- : 'black'
408
- }
419
+ fill={colorScale(config.runtime.seriesLabels[seriesKey] || seriesKey)}
409
420
  >
410
- {config.runtime.seriesLabels[seriesKey] || seriesKey}
421
+ {labelText}
411
422
  </Text>
412
423
  )
413
424
  })}
@@ -7,9 +7,11 @@ import { Line, Bar } from '@visx/shape'
7
7
  import { Text } from '@visx/text'
8
8
  import { Tooltip as ReactTooltip } from 'react-tooltip'
9
9
  import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
10
+ import _ from 'lodash'
11
+
12
+ // CDC Components
10
13
  import { isDateScale } from '@cdc/core/helpers/cove/date'
11
14
  import BrushChart from './BrushChart'
12
- // CDC Components
13
15
  import { AreaChart, AreaChartStacked } from './AreaChart'
14
16
  import BarChart from './BarChart'
15
17
  import ConfigContext from '../ConfigContext'
@@ -28,7 +30,7 @@ import CategoricalYAxis from './Axis/Categorical.Axis'
28
30
  // Helpers
29
31
  import { isLegendWrapViewport, isMobileHeightViewport } from '@cdc/core/helpers/viewports'
30
32
  import { getTextWidth } from '@cdc/core/helpers/getTextWidth'
31
- import { calcInitialHeight } from '../helpers/sizeHelpers'
33
+ import { calcInitialHeight, handleAutoPaddingRight } from '../helpers/sizeHelpers'
32
34
 
33
35
  // Hooks
34
36
  import useMinMax from '../hooks/useMinMax'
@@ -41,7 +43,6 @@ import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
41
43
  import Annotation from './Annotations'
42
44
  import { BlurStrokeText } from '@cdc/core/components/BlurStrokeText'
43
45
  import { countNumOfTicks } from '../helpers/countNumOfTicks'
44
- import _ from 'lodash'
45
46
 
46
47
  type LinearChartProps = {
47
48
  parentWidth: number
@@ -119,6 +120,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
119
120
  const xAxisLabelRefs = useRef([])
120
121
  const xAxisTitleRef = useRef(null)
121
122
  const lastMaxValue = useRef(maxValue)
123
+ const gridLineRefs = useRef([])
122
124
 
123
125
  const dataRef = useIntersectionObserver(triggerRef, {
124
126
  freezeOnceVisible: false
@@ -329,6 +331,21 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
329
331
  }
330
332
 
331
333
  // EFFECTS
334
+ // Adjust padding on the right side of the chart to accommodate for overflow
335
+ useEffect(() => {
336
+ if (!parentRef.current || !parentWidth || !gridLineRefs.current.length) return
337
+
338
+ const [updatePadding, paddingToAdd] = handleAutoPaddingRight(parentRef, xAxisLabelRefs, parentWidth)
339
+
340
+ if (!updatePadding) return
341
+
342
+ parentRef.current.style.paddingRight = `${paddingToAdd}px`
343
+ // subtract padding from grid line's x1 value
344
+ gridLineRefs.current.forEach(gridLine => {
345
+ if (!gridLine) return
346
+ gridLine.setAttribute('x1', xMax - paddingToAdd)
347
+ })
348
+ }, [parentWidth, parentHeight, data])
332
349
 
333
350
  // Make sure the chart is visible if in the editor
334
351
  /* eslint-disable react-hooks/exhaustive-deps */
@@ -421,8 +438,10 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
421
438
  }, [maxValue])
422
439
 
423
440
  useEffect(() => {
424
- if (orientation === 'horizontal') return
425
- if (!labelsOverflow) return
441
+ if (orientation === 'horizontal' || !labelsOverflow || config.yAxis?.max) {
442
+ setYAxisAutoPadding(0)
443
+ return
444
+ }
426
445
 
427
446
  // minimum percentage of the max value that the distance should be from the top grid line
428
447
  const MINIMUM_DISTANCE_PERCENTAGE = 0.025
@@ -638,6 +657,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
638
657
  <Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
639
658
  {runtime.yAxis.gridLines && !hideFirstGridLine ? (
640
659
  <Line
660
+ innerRef={el => (gridLineRefs.current[i] = el)}
641
661
  key={`${tick.value}--hide-hideGridLines`}
642
662
  display={(isLogarithmicAxis && showTicks).toString()}
643
663
  from={{ x: tick.from.x + xMax, y: tick.from.y }}
@@ -698,7 +718,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
698
718
  />
699
719
  )}
700
720
  {((visualizationType === 'Area Chart' && config.visualizationSubType === 'regular') ||
701
- visualizationType === 'Combo') && (
721
+ (visualizationType === 'Combo' && config.visualizationSubType === 'regular')) && (
702
722
  <AreaChart
703
723
  xScale={xScale}
704
724
  yScale={yScale}
@@ -714,7 +734,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
714
734
  />
715
735
  )}
716
736
  {((visualizationType === 'Area Chart' && config.visualizationSubType === 'stacked') ||
717
- visualizationType === 'Combo') && (
737
+ (visualizationType === 'Combo' && config.visualizationSubType === 'stacked')) && (
718
738
  <AreaChartStacked
719
739
  xScale={xScale}
720
740
  yScale={yScale}
@@ -1399,20 +1419,21 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1399
1419
  const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
1400
1420
 
1401
1421
  const containsMultipleWords = inputString => /\s/.test(inputString)
1402
- const ismultiLabel = filteredTicks.some(tick => containsMultipleWords(tick.value))
1422
+ const isMultiLabel = filteredTicks.some(tick => containsMultipleWords(tick.value))
1403
1423
 
1404
1424
  // Calculate sumOfTickWidth here, before map function
1405
- const tickWidthMax = Math.max(
1425
+ const longestTickLength = Math.max(
1406
1426
  ...filteredTicks.map(tick => getTextWidth(tick.formattedValue, GET_TEXT_WIDTH_FONT))
1407
1427
  )
1408
1428
  // const marginTop = 20 // moved to top bc need for yMax calcs
1409
- const accumulator = ismultiLabel ? 180 : 100
1429
+ const accumulator = isMultiLabel ? 180 : 100
1410
1430
 
1411
1431
  const textWidths = filteredTicks.map(tick => getTextWidth(tick.formattedValue, GET_TEXT_WIDTH_FONT))
1412
1432
  const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
1413
1433
  const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (filteredTicks.length - 1)
1434
+ const bufferBetweenTicks = 40
1435
+ const maxLengthOfTick = width / filteredTicks.length - X_TICK_LABEL_PADDING * 2 - bufferBetweenTicks
1414
1436
 
1415
- // Check if ticks are overlapping
1416
1437
  // Determine the position of each tick
1417
1438
  let positions = [0] // The first tick is at position 0
1418
1439
  for (let i = 1; i < textWidths.length; i++) {
@@ -1424,35 +1445,22 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
1424
1445
  const axisBBox = axisBottomRef?.current?.getBBox().height
1425
1446
  config.xAxis.axisBBox = axisBBox
1426
1447
 
1427
- // Check if ticks are overlapping
1428
- let areTicksTouching = false
1429
- textWidths.forEach((_, i) => {
1430
- if (positions[i] + textWidths[i] > positions[i + 1]) {
1431
- areTicksTouching = true
1432
- return
1433
- }
1434
- })
1435
-
1436
- // Force wrap when showing years once so it's easier to read
1437
- if (config.xAxis.showYearsOnce) {
1438
- areTicksTouching = true
1439
- }
1440
-
1441
1448
  // force wrap it last tick is close to the end of the axis
1442
1449
  const lastTickWidth = textWidths[textWidths.length - 1]
1443
1450
  const lastTickPosition = positions[positions.length - 1] + lastTickWidth
1444
1451
  const lastTickEnd = lastTickPosition + lastTickWidth / 2
1445
1452
  const lastTickEndThreshold = xMax - lastTickWidth
1446
1453
 
1447
- if (lastTickEnd > lastTickEndThreshold) {
1448
- areTicksTouching = true
1449
- }
1454
+ const areTicksTouching =
1455
+ textWidths.some(textWidth => textWidth > maxLengthOfTick) || // Force wrap if any tick is too long
1456
+ config.xAxis.showYearsOnce || // Force wrap when showing years once so it's easier to read
1457
+ lastTickEnd > lastTickEndThreshold // Force wrap it last tick is close to the end of the axis
1450
1458
 
1451
1459
  const dynamicMarginTop =
1452
- areTicksTouching && config.isResponsiveTicks ? tickWidthMax + DEFAULT_TICK_LENGTH + 20 : 0
1460
+ areTicksTouching && config.isResponsiveTicks ? longestTickLength + DEFAULT_TICK_LENGTH + 20 : 0
1453
1461
 
1454
1462
  config.dynamicMarginTop = dynamicMarginTop
1455
- config.xAxis.tickWidthMax = tickWidthMax
1463
+ config.xAxis.tickWidthMax = longestTickLength
1456
1464
 
1457
1465
  return (
1458
1466
  <Group className='bottom-axis' width={dimensions[0]}>
@@ -24,7 +24,6 @@ export default {
24
24
  isResponsiveTicks: false,
25
25
  general: {
26
26
  annotationDropdownText: 'Annotations',
27
- showDownloadButton: false,
28
27
  showMissingDataLabel: true,
29
28
  showSuppressedSymbol: true,
30
29
  showZeroValueData: true,
@@ -166,6 +165,7 @@ export default {
166
165
  seriesHighlight: [],
167
166
  style: 'circles',
168
167
  subStyle: 'linear blocks',
168
+ groupBy: '',
169
169
  shape: 'circle',
170
170
  tickRotation: '',
171
171
  hideBorder: {
@@ -0,0 +1,10 @@
1
+ import { ChartConfig } from '../types/ChartConfig'
2
+
3
+ export const getSeriesWithData = (config: ChartConfig) => {
4
+ const { filters, data, runtime } = config
5
+ const { series } = runtime
6
+
7
+ const filteredData = data.filter(d => filters.every(f => d[f.columnName] === f.active))
8
+
9
+ return series.filter(s => filteredData.some(d => d[s.dynamicCategory || s.dataKey])).map(s => s.name || s.dataKey)
10
+ }
@@ -1,11 +1,11 @@
1
1
  import _ from 'lodash'
2
2
 
3
3
  export const isConvertLineToBarGraph = (configObj, filteredData) => {
4
- const { allowLineToBarGraph, formattedData, series, visualizationType, xAxis } = configObj
4
+ const { allowLineToBarGraph, series, visualizationType, xAxis } = configObj
5
5
  if (!allowLineToBarGraph) return false
6
6
  const lineWithLessThanThreePoints = visualizationType === 'Line' && filteredData?.length < 3
7
7
  const isDynamicSeries = series?.some(series => series.dynamicCategory)
8
8
  const isDynamicWithLessThanThreePoints =
9
- isDynamicSeries && _.uniq(formattedData?.map(data => data[xAxis.dataKey])).length <= 2
9
+ isDynamicSeries && _.uniq(filteredData?.map(data => data[xAxis.dataKey])).length <= 2
10
10
  return lineWithLessThanThreePoints || isDynamicWithLessThanThreePoints
11
11
  }