@cdc/chart 4.24.10 → 4.24.11

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 (53) hide show
  1. package/dist/cdcchart.js +34618 -33995
  2. package/examples/feature/boxplot/boxplot-data.json +88 -22
  3. package/examples/feature/boxplot/boxplot.json +540 -16
  4. package/examples/feature/boxplot/testing.csv +7 -7
  5. package/examples/feature/sankey/sankey-example-data.json +0 -1
  6. package/examples/private/test.json +20092 -0
  7. package/index.html +3 -3
  8. package/package.json +2 -2
  9. package/src/CdcChart.tsx +86 -86
  10. package/src/_stories/Chart.CustomColors.stories.tsx +19 -0
  11. package/src/_stories/Chart.DynamicSeries.stories.tsx +27 -0
  12. package/src/_stories/Chart.Legend.Gradient.stories.tsx +42 -1
  13. package/src/_stories/Chart.stories.tsx +7 -8
  14. package/src/_stories/ChartEditor.stories.tsx +27 -0
  15. package/src/_stories/ChartLine.Suppression.stories.tsx +25 -0
  16. package/src/_stories/ChartPrefixSuffix.stories.tsx +8 -0
  17. package/src/_stories/_mock/boxplot_multiseries.json +647 -0
  18. package/src/_stories/_mock/dynamic_series_bar_config.json +723 -0
  19. package/src/_stories/_mock/dynamic_series_config.json +979 -0
  20. package/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
  21. package/src/_stories/_mock/suppression_mock.json +1549 -0
  22. package/src/components/BarChart/components/BarChart.StackedVertical.tsx +43 -9
  23. package/src/components/BarChart/components/BarChart.Vertical.tsx +60 -42
  24. package/src/components/BarChart/helpers/index.ts +1 -2
  25. package/src/components/BoxPlot/BoxPlot.tsx +189 -0
  26. package/src/components/EditorPanel/EditorPanel.tsx +64 -62
  27. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +3 -3
  28. package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +51 -6
  29. package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +40 -9
  30. package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
  31. package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +121 -56
  32. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -2
  33. package/src/components/EditorPanel/useEditorPermissions.ts +15 -1
  34. package/src/components/Legend/Legend.Component.tsx +9 -10
  35. package/src/components/Legend/Legend.tsx +16 -16
  36. package/src/components/LineChart/helpers.ts +48 -43
  37. package/src/components/LineChart/index.tsx +88 -82
  38. package/src/components/LinearChart.tsx +17 -10
  39. package/src/components/Sankey/index.tsx +50 -32
  40. package/src/components/Sankey/sankey.scss +6 -5
  41. package/src/components/Sankey/useSankeyAlert.tsx +60 -0
  42. package/src/components/ScatterPlot/ScatterPlot.jsx +20 -4
  43. package/src/data/initial-state.js +3 -9
  44. package/src/hooks/useLegendClasses.ts +10 -23
  45. package/src/hooks/useMinMax.ts +27 -13
  46. package/src/hooks/useReduceData.ts +43 -10
  47. package/src/hooks/useScales.ts +56 -35
  48. package/src/hooks/useTooltip.tsx +54 -49
  49. package/src/scss/main.scss +0 -18
  50. package/src/types/ChartConfig.ts +6 -19
  51. package/src/types/ChartContext.ts +4 -1
  52. package/src/types/ForestPlot.ts +8 -0
  53. package/src/components/BoxPlot/BoxPlot.jsx +0 -111
@@ -4,7 +4,14 @@ import { Group } from '@visx/group'
4
4
  import { formatNumber as formatColNumber } from '@cdc/core/helpers/cove/number'
5
5
 
6
6
  const ScatterPlot = ({ xScale, yScale }) => {
7
- const { transformedData: data, config, tableData, formatNumber, seriesHighlight, colorPalettes } = useContext(ConfigContext)
7
+ const {
8
+ transformedData: data,
9
+ config,
10
+ tableData,
11
+ formatNumber,
12
+ seriesHighlight,
13
+ colorPalettes
14
+ } = useContext(ConfigContext)
8
15
 
9
16
  // TODO: copied from line chart should probably be a constant somewhere.
10
17
  const circleRadii = 4.5
@@ -23,10 +30,19 @@ const ScatterPlot = ({ xScale, yScale }) => {
23
30
  }
24
31
  ])
25
32
  const handleTooltip = (item, s, dataIndex) => `<div>
26
- ${config.legend.showLegendValuesTooltip && config.runtime.seriesLabels && hasMultipleSeries ? `${config.runtime.seriesLabels[s] || ''}<br/>` : ''}
33
+ ${
34
+ config.legend.showLegendValuesTooltip && config.runtime.seriesLabels && hasMultipleSeries
35
+ ? `${config.runtime.seriesLabels[s] || ''}<br/>`
36
+ : ''
37
+ }
27
38
  ${config.xAxis.label}: ${formatNumber(item[config.xAxis.dataKey], 'bottom')} <br/>
28
39
  ${config.yAxis.label}: ${formatNumber(item[s], 'left')}<br/>
29
- ${additionalColumns.map(([label, name, options]) => `${label} : ${formatColNumber(tableData[dataIndex][name], 'left', false, config, options)}<br/>`).join('')}
40
+ ${additionalColumns
41
+ .map(
42
+ ([label, name, options]) =>
43
+ `${label} : ${formatColNumber(tableData[dataIndex][name], 'left', false, config, options)}<br/>`
44
+ )
45
+ .join('')}
30
46
  </div>`
31
47
 
32
48
  return (
@@ -36,7 +52,7 @@ const ScatterPlot = ({ xScale, yScale }) => {
36
52
  return config.runtime.seriesKeys.map((s, index) => {
37
53
  const transparentArea = config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(s) === -1
38
54
  const displayArea = config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(s) !== -1
39
- const seriesColor = config.palette ? colorPalettes[config.palette][index] : '#000'
55
+ const seriesColor = config?.customColors ? config.customColors[index] : config.palette ? colorPalettes[config.palette][index] : '#000'
40
56
 
41
57
  let pointStyles = {
42
58
  filter: 'unset',
@@ -68,15 +68,8 @@ export default {
68
68
  boxplot: {
69
69
  plots: [],
70
70
  borders: 'true',
71
- firstQuartilePercentage: 25,
72
- thirdQuartilePercentage: 75,
73
- boxWidthPercentage: 40,
74
71
  plotOutlierValues: false,
75
72
  plotNonOutlierValues: true,
76
- legend: {
77
- showHowToReadText: false,
78
- howToReadText: ''
79
- },
80
73
  labels: {
81
74
  q1: 'Lower Quartile',
82
75
  q2: 'q2',
@@ -88,7 +81,7 @@ export default {
88
81
  median: 'Median',
89
82
  sd: 'Standard Deviation',
90
83
  iqr: 'Interquartile Range',
91
- total: 'Total',
84
+ count: 'Count',
92
85
  outliers: 'Outliers',
93
86
  values: 'Values',
94
87
  lowerBounds: 'Lower Bounds',
@@ -128,7 +121,8 @@ export default {
128
121
  target: 0,
129
122
  maxTickRotation: 0,
130
123
  padding: 5,
131
- showYearsOnce: false
124
+ showYearsOnce: false,
125
+ sortByRecentDate: false
132
126
  },
133
127
  table: {
134
128
  label: 'Data Table',
@@ -1,22 +1,12 @@
1
- type ConfigType = {
2
- legend: {
3
- position: 'left' | 'bottom' | 'top' | 'right'
4
- singleRow?: boolean
5
- reverseLabelOrder?: boolean
6
- verticalSorted?: boolean
7
- hideBorder: {
8
- side?: boolean
9
- topBottom?: boolean
10
- }
11
- }
12
- }
1
+ import { ChartConfig } from '../types/ChartConfig'
13
2
 
14
- const useLegendClasses = (config: ConfigType) => {
3
+ const useLegendClasses = (config: ChartConfig) => {
4
+ const { position, singleRow, reverseLabelOrder, verticalSorted, hideBorder } = config.legend
15
5
  const containerClasses = ['legend-container']
16
6
  const innerClasses = ['legend-container__inner']
17
7
 
18
8
  // Handle legend positioning
19
- switch (config.legend.position) {
9
+ switch (position) {
20
10
  case 'left':
21
11
  containerClasses.push('left')
22
12
  break
@@ -34,33 +24,30 @@ const useLegendClasses = (config: ConfigType) => {
34
24
  }
35
25
 
36
26
  // Handle single row configuration for 'bottom' and 'top' positions
37
- if (['bottom', 'top'].includes(config.legend.position) && config.legend.singleRow) {
27
+ if (['bottom', 'top'].includes(position) && singleRow) {
38
28
  innerClasses.push('single-row')
39
29
  }
40
30
 
41
31
  // Reverse label order
42
- if (config.legend.reverseLabelOrder) {
32
+ if (reverseLabelOrder) {
43
33
  innerClasses.push('d-flex', 'flex-column-reverse')
44
34
  }
45
35
 
46
36
  // Vertical sorting for 'bottom' and 'top' positions
47
- if (['bottom', 'top'].includes(config.legend.position) && config.legend.verticalSorted) {
37
+ if (['bottom', 'top'].includes(position) && verticalSorted) {
48
38
  innerClasses.push('vertical-sorted')
49
39
  }
50
40
 
51
41
  // Configure border classes
52
- if (
53
- config.legend.hideBorder.side &&
54
- (['right', 'left'].includes(config.legend.position) || !config.legend.position)
55
- ) {
42
+ if (hideBorder.side && (['right', 'left'].includes(position) || !position)) {
56
43
  containerClasses.push('no-border')
57
44
  }
58
45
 
59
- if (config.legend.hideBorder.topBottom && ['top', 'bottom'].includes(config.legend.position)) {
46
+ if (hideBorder.topBottom && ['top', 'bottom'].includes(position)) {
60
47
  containerClasses.push('no-border')
61
48
  }
62
49
 
63
- if (config.legend.hideBorder.topBottom && ['top'].includes(config.legend.position)) {
50
+ if (hideBorder.topBottom && ['top'].includes(position)) {
64
51
  containerClasses.push('p-0')
65
52
  }
66
53
 
@@ -40,11 +40,13 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
40
40
  const minRequiredCIPadding = 1.15 // regardless of Editor if CI data, there must be 10% padding added
41
41
  const isLogarithmicAxis = config.yAxis.type === 'logarithmic'
42
42
  // do validation bafore applying t0 charts
43
- const isMaxValid = existPositiveValue ? enteredMaxValue >= maxValue : enteredMaxValue >= 0
44
- const isMinValid = isLogarithmicAxis ? enteredMinValue >= 0 : (enteredMinValue <= 0 && minValue >= 0) || (enteredMinValue <= minValue && minValue < 0)
43
+ const isMaxValid = existPositiveValue ? Number(enteredMaxValue) >= maxValue : Number(enteredMaxValue) >= 0
44
+ const isMinValid = isLogarithmicAxis
45
+ ? Number(enteredMinValue) >= 0
46
+ : (Number(enteredMinValue) <= 0 && minValue >= 0) || (Number(enteredMinValue) <= minValue && minValue < 0)
45
47
 
46
- min = enteredMinValue && isMinValid ? enteredMinValue : minValue
47
- max = enteredMaxValue && isMaxValid ? enteredMaxValue : Number.MIN_VALUE
48
+ min = enteredMinValue && isMinValid ? Number(enteredMinValue) : minValue
49
+ max = enteredMaxValue && isMaxValid ? Number(enteredMaxValue) : Number.MIN_VALUE
48
50
 
49
51
  const { lower, upper } = config?.confidenceKeys || {}
50
52
 
@@ -122,8 +124,8 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
122
124
  leftMax = findMaxFromSeriesKeys(data, leftAxisSeriesItems, leftMax, 'left')
123
125
  rightMax = findMaxFromSeriesKeys(data, rightAxisSeriesItems, rightMax, 'right')
124
126
 
125
- if (leftMax < enteredMaxValue) {
126
- leftMax = enteredMaxValue
127
+ if (leftMax < Number(enteredMaxValue)) {
128
+ leftMax = Number(enteredMaxValue)
127
129
  }
128
130
  } catch (e) {
129
131
  console.error(e.message)
@@ -131,10 +133,18 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
131
133
  }
132
134
 
133
135
  // this should not apply to bar charts if there is negative CI data
134
- if ((visualizationType === 'Bar' || checkLineToBarGraph() || (visualizationType === 'Combo' && !isAllLine)) && min > 0) {
136
+ if (
137
+ (visualizationType === 'Bar' || checkLineToBarGraph() || (visualizationType === 'Combo' && !isAllLine)) &&
138
+ min > 0
139
+ ) {
135
140
  min = 0
136
141
  }
137
- if ((config.visualizationType === 'Bar' || checkLineToBarGraph() || (config.visualizationType === 'Combo' && !isAllLine)) && min < 0) {
142
+ if (
143
+ (config.visualizationType === 'Bar' ||
144
+ checkLineToBarGraph() ||
145
+ (config.visualizationType === 'Combo' && !isAllLine)) &&
146
+ min < 0
147
+ ) {
138
148
  min = min * 1.1
139
149
  }
140
150
 
@@ -143,18 +153,22 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
143
153
  min = 0
144
154
  }
145
155
  if (enteredMinValue) {
146
- const isMinValid = isLogarithmicAxis ? enteredMinValue >= 0 && enteredMinValue < minValue : enteredMinValue < minValue
147
- min = enteredMinValue && isMinValid ? enteredMinValue : minValue
156
+ const isMinValid = isLogarithmicAxis
157
+ ? Number(enteredMinValue) >= 0 && Number(enteredMinValue) < minValue
158
+ : Number(enteredMinValue) < minValue
159
+ min = Number(enteredMinValue) && isMinValid ? Number(enteredMinValue) : minValue
148
160
  }
149
161
  }
150
162
 
151
163
  if (config.visualizationType === 'Deviation Bar' && min > 0) {
152
164
  const isMinValid = Number(enteredMinValue) < Math.min(minValue, Number(config.xAxis.target))
153
- min = enteredMinValue && isMinValid ? enteredMinValue : 0
165
+ min = Number(enteredMinValue) && isMinValid ? Number(enteredMinValue) : 0
154
166
  }
155
167
 
156
168
  if (config.visualizationType === 'Line' && !checkLineToBarGraph()) {
157
- const isMinValid = isLogarithmicAxis ? enteredMinValue >= 0 && enteredMinValue < minValue : enteredMinValue < minValue
169
+ const isMinValid = isLogarithmicAxis
170
+ ? Number(enteredMinValue) >= 0 && Number(enteredMinValue) < minValue
171
+ : Number(enteredMinValue) < minValue
158
172
  // update minValue for (0) Suppression points
159
173
  const suppressedMinValue = tableData?.some((dataItem, index) => {
160
174
  return config.preliminaryData?.some(pd => {
@@ -171,7 +185,7 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
171
185
  return valueMatch && (index === 0 || index === tableData.length - 1)
172
186
  })
173
187
  })
174
- min = enteredMinValue && isMinValid ? enteredMinValue : suppressedMinValue ? 0 : minValue
188
+ min = Number(enteredMinValue) && isMinValid ? Number(enteredMinValue) : suppressedMinValue ? 0 : minValue
175
189
  }
176
190
  //If data value max wasn't provided, calculate it
177
191
  if (max === Number.MIN_VALUE) {
@@ -3,12 +3,28 @@ import isNumber from '@cdc/core/helpers/isNumber'
3
3
  function useReduceData(config, data) {
4
4
  const isBar = config.series.every(({ type }) => type === 'Bar')
5
5
  const isAllLine = config.series.every(({ type }) => ['Line', 'dashed-sm', 'dashed-md', 'dashed-lg'].includes(type))
6
- const sumYValues = seriesKeys => xValue => seriesKeys.reduce((yTotal, k) => (isNaN(Number(xValue[k])) ? yTotal : yTotal + Number(xValue[k])), 0)
7
-
6
+ const sumYValues = seriesKeys => xValue =>
7
+ seriesKeys.reduce((yTotal, k) => (isNaN(Number(xValue[k])) ? yTotal : yTotal + Number(xValue[k])), 0)
8
+ const getSeriesKey = seriesKey => {
9
+ const series = config.runtime.series.find(item => item.dataKey === seriesKey)
10
+ return series?.dynamicCategory ? series.originalDataKey : seriesKey
11
+ }
8
12
  const getMaxValueFromData = () => {
9
- let max = Math.max(...data.map(d => Math.max(...config.runtime.seriesKeys.map(key => (isNumber(d[key]) ? Number(cleanChars(d[key])) : 0)))))
10
-
11
- if ((config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && isBar)) && config.visualizationSubType === 'stacked') {
13
+ let max = Math.max(
14
+ ...data.map(d =>
15
+ Math.max(
16
+ ...config.runtime.seriesKeys.map(key => {
17
+ const seriesKey = getSeriesKey(key)
18
+ return isNumber(d[seriesKey]) ? Number(cleanChars(d[seriesKey])) : 0
19
+ })
20
+ )
21
+ )
22
+ )
23
+
24
+ if (
25
+ (config.visualizationType === 'Bar' || (config.visualizationType === 'Combo' && isBar)) &&
26
+ config.visualizationSubType === 'stacked'
27
+ ) {
12
28
  const yTotals = data.map(sumYValues(config.runtime.seriesKeys)).filter(num => !isNaN(num))
13
29
  max = Math.max(...yTotals)
14
30
  }
@@ -18,15 +34,23 @@ function useReduceData(config, data) {
18
34
  max = Math.max(...yTotals)
19
35
  }
20
36
 
21
- if ((config.visualizationType === 'Bar' || config.visualizationType === 'Deviation Bar') && config.series && config.series.dataKey) {
22
- max = Math.max(...data.map(d => (isNumber(d[config.series.dataKey]) ? Number(cleanChars(d[config.series.dataKey])) : 0)))
37
+ if (
38
+ (config.visualizationType === 'Bar' || config.visualizationType === 'Deviation Bar') &&
39
+ config.series &&
40
+ config.series.dataKey
41
+ ) {
42
+ max = Math.max(
43
+ ...data.map(d => (isNumber(d[config.series.dataKey]) ? Number(cleanChars(d[config.series.dataKey])) : 0))
44
+ )
23
45
  }
24
46
 
25
47
  if (config.visualizationType === 'Combo' && config.visualizationSubType === 'stacked' && !isBar) {
26
48
  if (config.runtime.barSeriesKeys && config.runtime.lineSeriesKeys) {
27
49
  const yTotals = data.map(sumYValues(config.runtime.barSeriesKeys))
28
50
 
29
- const lineMax = Math.max(...data.map(d => Math.max(...config.runtime.lineSeriesKeys.map(key => Number(cleanChars(d[key]))))))
51
+ const lineMax = Math.max(
52
+ ...data.map(d => Math.max(...config.runtime.lineSeriesKeys.map(key => Number(cleanChars(d[key])))))
53
+ )
30
54
  const barMax = Math.max(...yTotals)
31
55
 
32
56
  max = Math.max(barMax, lineMax)
@@ -37,7 +61,16 @@ function useReduceData(config, data) {
37
61
  }
38
62
 
39
63
  const getMinValueFromData = () => {
40
- const minNumberFromData = Math.min(...data.map(d => Math.min(...config.runtime.seriesKeys.map(key => (isNumber(d[key]) ? Number(cleanChars(d[key])) : Infinity)))))
64
+ const minNumberFromData = Math.min(
65
+ ...data.map(d =>
66
+ Math.min(
67
+ ...config.runtime.seriesKeys.map(key => {
68
+ const seriesKey = getSeriesKey(key)
69
+ return isNumber(d[seriesKey]) ? Number(cleanChars(d[seriesKey])) : Infinity
70
+ })
71
+ )
72
+ )
73
+ )
41
74
 
42
75
  return String(minNumberFromData)
43
76
  }
@@ -46,7 +79,7 @@ function useReduceData(config, data) {
46
79
  if (!config.runtime.seriesKeys) {
47
80
  return false
48
81
  }
49
- return config.runtime.seriesKeys.some(key => data.some(d => d[key] >= 0))
82
+ return config.runtime.seriesKeys.some(key => data.some(d => d[getSeriesKey(key)] >= 0))
50
83
  }
51
84
 
52
85
  const cleanChars = value => {
@@ -1,9 +1,9 @@
1
- import { LogScaleConfig, scaleBand, scaleLinear, scaleLog, scalePoint, scaleTime } from '@visx/scale'
1
+ import { LinearScaleConfig, LogScaleConfig, scaleBand, scaleLinear, scaleLog, scalePoint, scaleTime } from '@visx/scale'
2
2
  import { useContext } from 'react'
3
3
  import ConfigContext from '../ConfigContext'
4
4
  import { ChartConfig } from '../types/ChartConfig'
5
5
  import { ChartContext } from '../types/ChartContext'
6
- import * as d3 from 'd3'
6
+
7
7
  const scaleTypes = {
8
8
  TIME: 'time',
9
9
  LOG: 'log',
@@ -31,8 +31,7 @@ const useScales = (properties: useScaleProps) => {
31
31
  const seriesDomain = config.runtime.barSeriesKeys || config.runtime.seriesKeys
32
32
  const xAxisType = config.runtime.xAxis.type
33
33
  const isHorizontal = config.orientation === 'horizontal'
34
-
35
- const { visualizationType } = config
34
+ const { visualizationType, xAxis, forestPlot } = config
36
35
 
37
36
  // define scales
38
37
  let xScale = null
@@ -64,11 +63,11 @@ const useScales = (properties: useScaleProps) => {
64
63
 
65
64
  // handle Linear scaled viz
66
65
  if (config.xAxis.type === 'date' && !isHorizontal) {
67
- const xAxisDataMappedSorted = xAxisDataMapped ? xAxisDataMapped.sort() : []
66
+ const xAxisDataMappedSorted = sortXAxisData(xAxisDataMapped, config.xAxis.sortByRecentDate)
68
67
  xScale = composeScaleBand(xAxisDataMappedSorted, [0, xMax], 1 - config.barThickness)
69
68
  }
70
69
 
71
- if (config.xAxis.type === 'date-time') {
70
+ if (xAxis.type === 'date-time' || xAxis.type === 'continuous') {
72
71
  let xAxisMin = Math.min(...xAxisDataMapped.map(Number))
73
72
  let xAxisMax = Math.max(...xAxisDataMapped.map(Number))
74
73
  xAxisMin -= (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
@@ -76,15 +75,17 @@ const useScales = (properties: useScaleProps) => {
76
75
  visualizationType === 'Line'
77
76
  ? 0
78
77
  : (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
78
+ const range = config.xAxis.sortByRecentDate ? [xMax, 0] : [0, xMax]
79
79
  xScale = scaleTime({
80
80
  domain: [xAxisMin, xAxisMax],
81
- range: [0, xMax]
81
+ range: range
82
82
  })
83
83
 
84
84
  xScale.type = scaleTypes.TIME
85
85
 
86
86
  let minDistance = Number.MAX_VALUE
87
- let xAxisDataMappedSorted = xAxisDataMapped ? xAxisDataMapped.sort() : []
87
+ let xAxisDataMappedSorted = sortXAxisData(xAxisDataMapped, config.xAxis.sortByRecentDate)
88
+
88
89
  for (let i = 0; i < xAxisDataMappedSorted.length - 1; i++) {
89
90
  let distance = xScale(xAxisDataMappedSorted[i + 1]) - xScale(xAxisDataMappedSorted[i])
90
91
 
@@ -106,7 +107,7 @@ const useScales = (properties: useScaleProps) => {
106
107
  range: [0, yMax]
107
108
  })
108
109
  xScale = scaleLinear({
109
- domain: [min * leftOffset, Math.max(Number(config.xAxis.target), max)],
110
+ domain: [min * leftOffset, Math.max(Number(xAxis.target), max)],
110
111
  range: [0, xMax],
111
112
  round: true,
112
113
  nice: true
@@ -116,9 +117,11 @@ const useScales = (properties: useScaleProps) => {
116
117
 
117
118
  // handle Scatter plot
118
119
  if (config.visualizationType === 'Scatter Plot') {
119
- if (config.xAxis.type === 'continuous') {
120
+ if (xAxis.type === 'continuous') {
121
+ let min = xAxis.min ? xAxis.min : Math.min.apply(null, xScale.domain())
122
+ let max = xAxis.max ? xAxis.max : Math.max.apply(null, xScale.domain())
120
123
  xScale = scaleLinear({
121
- domain: [0, Math.max.apply(null, xScale.domain())],
124
+ domain: [min, max],
122
125
  range: [0, xMax]
123
126
  })
124
127
  xScale.type = scaleTypes.LINEAR
@@ -150,19 +153,19 @@ const useScales = (properties: useScaleProps) => {
150
153
  if (highestFence > max) max = highestFence
151
154
 
152
155
  // Set Scales
156
+
153
157
  yScale = scaleLinear({
154
158
  range: [yMax, 0],
155
159
  round: true,
156
160
  domain: [min, max]
157
161
  })
158
-
159
162
  xScale = scaleBand({
160
163
  range: [0, xMax],
161
- round: true,
162
- domain: config.boxplot.categories,
163
- padding: 0.4
164
+ domain: config.boxplot.categories
164
165
  })
165
166
  xScale.type = scaleTypes.BAND
167
+
168
+ seriesScale = composeScalePoint(seriesDomain, [0, yMax])
166
169
  }
167
170
 
168
171
  // handle Paired bar
@@ -193,10 +196,10 @@ const useScales = (properties: useScaleProps) => {
193
196
 
194
197
  if (visualizationType === 'Forest Plot') {
195
198
  const resolvedYRange = () => {
196
- if (config.forestPlot.regression.showDiamond || config.forestPlot.regression.description) {
197
- return [0 + config.forestPlot.rowHeight * 2, yMax - config.forestPlot.rowHeight]
199
+ if (forestPlot.regression.showDiamond || forestPlot.regression.description) {
200
+ return [0 + forestPlot.rowHeight * 2, yMax - forestPlot.rowHeight]
198
201
  } else {
199
- return [0 + config.forestPlot.rowHeight * 2, yMax]
202
+ return [0 + forestPlot.rowHeight * 2, yMax]
200
203
  }
201
204
  }
202
205
 
@@ -207,26 +210,26 @@ const useScales = (properties: useScaleProps) => {
207
210
 
208
211
  const xAxisPadding = 5
209
212
 
210
- const leftWidthOffset = (Number(config.forestPlot.leftWidthOffset) / 100) * xMax
211
- const rightWidthOffset = (Number(config.forestPlot.rightWidthOffset) / 100) * xMax
213
+ const leftWidthOffset = (Number(forestPlot.leftWidthOffset) / 100) * xMax
214
+ const rightWidthOffset = (Number(forestPlot.rightWidthOffset) / 100) * xMax
212
215
 
213
- const rightWidthOffsetMobile = (Number(config.forestPlot.rightWidthOffsetMobile) / 100) * xMax
214
- const leftWidthOffsetMobile = (Number(config.forestPlot.leftWidthOffsetMobile) / 100) * xMax
216
+ const rightWidthOffsetMobile = (Number(forestPlot.rightWidthOffsetMobile) / 100) * xMax
217
+ const leftWidthOffsetMobile = (Number(forestPlot.leftWidthOffsetMobile) / 100) * xMax
215
218
 
216
219
  if (screenWidth > 480) {
217
- if (config.forestPlot.type === 'Linear') {
220
+ if (forestPlot.type === 'Linear') {
218
221
  xScale = scaleLinear({
219
222
  domain: [
220
- Math.min(...data.map(d => parseFloat(d[config.forestPlot.lower]))) - xAxisPadding,
221
- Math.max(...data.map(d => parseFloat(d[config.forestPlot.upper]))) + xAxisPadding
223
+ Math.min(...data.map(d => parseFloat(d[forestPlot.lower]))) - xAxisPadding,
224
+ Math.max(...data.map(d => parseFloat(d[forestPlot.upper]))) + xAxisPadding
222
225
  ],
223
226
  range: [leftWidthOffset, Number(screenWidth) - rightWidthOffset]
224
227
  })
225
228
  xScale.type = scaleTypes.LINEAR
226
229
  }
227
- if (config.forestPlot.type === 'Logarithmic') {
228
- let max = Math.max(...data.map(d => parseFloat(d[config.forestPlot.upper])))
229
- let fp_min = Math.min(...data.map(d => parseFloat(d[config.forestPlot.lower])))
230
+ if (forestPlot.type === 'Logarithmic') {
231
+ let max = Math.max(...data.map(d => parseFloat(d[forestPlot.upper])))
232
+ let fp_min = Math.min(...data.map(d => parseFloat(d[forestPlot.lower])))
230
233
 
231
234
  xScale = scaleLog<LogScaleConfig>({
232
235
  domain: [fp_min, max],
@@ -236,20 +239,20 @@ const useScales = (properties: useScaleProps) => {
236
239
  xScale.type = scaleTypes.LOG
237
240
  }
238
241
  } else {
239
- if (config.forestPlot.type === 'Linear') {
240
- xScale = scaleLinear({
242
+ if (forestPlot.type === 'Linear') {
243
+ xScale = scaleLinear<LinearScaleConfig>({
241
244
  domain: [
242
- Math.min(...data.map(d => parseFloat(d[config.forestPlot.lower]))) - xAxisPadding,
243
- Math.max(...data.map(d => parseFloat(d[config.forestPlot.upper]))) + xAxisPadding
245
+ Math.min(...data.map(d => parseFloat(d[forestPlot.lower]))) - xAxisPadding,
246
+ Math.max(...data.map(d => parseFloat(d[forestPlot.upper]))) + xAxisPadding
244
247
  ],
245
248
  range: [leftWidthOffsetMobile, xMax - rightWidthOffsetMobile],
246
249
  type: scaleTypes.LINEAR
247
250
  })
248
251
  }
249
252
 
250
- if (config.forestPlot.type === 'Logarithmic') {
251
- let max = Math.max(...data.map(d => parseFloat(d[config.forestPlot.upper])))
252
- let fp_min = Math.min(...data.map(d => parseFloat(d[config.forestPlot.lower])))
253
+ if (forestPlot.type === 'Logarithmic') {
254
+ let max = Math.max(...data.map(d => parseFloat(d[forestPlot.upper])))
255
+ let fp_min = Math.min(...data.map(d => parseFloat(d[forestPlot.lower])))
253
256
 
254
257
  xScale = scaleLog<LogScaleConfig>({
255
258
  domain: [fp_min, max],
@@ -386,3 +389,21 @@ const composeScaleBand = (domain, range, padding = 0) => {
386
389
  padding: padding
387
390
  })
388
391
  }
392
+
393
+ const sortXAxisData = (xAxisData, sortByRecentDate) => {
394
+ if (!xAxisData || xAxisData.length === 0) {
395
+ return []
396
+ }
397
+
398
+ // Check if the array has only one item
399
+ if (xAxisData.length === 1) {
400
+ return xAxisData
401
+ }
402
+ if (sortByRecentDate) {
403
+ // Sort from newest to oldes (recent dates first)
404
+ return xAxisData.sort((a, b) => Number(b) - Number(a))
405
+ } else {
406
+ // Sort from oldest to newest
407
+ return xAxisData.sort((a, b) => Number(a) - Number(b))
408
+ }
409
+ }