@cdc/chart 4.24.10 → 4.24.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdcchart.js +34651 -33978
- package/examples/feature/boxplot/boxplot-data.json +88 -22
- package/examples/feature/boxplot/boxplot.json +540 -16
- package/examples/feature/boxplot/testing.csv +7 -7
- package/examples/feature/sankey/sankey-example-data.json +126 -14
- package/examples/feature/tests-date-exclusions/date-exclusions-config.json +372 -12
- package/examples/private/DEV-8850-2.json +493 -0
- package/examples/private/DEV-9822.json +574 -0
- package/examples/private/DEV-9840.json +553 -0
- package/examples/private/DEV-9850-3.json +461 -0
- package/examples/private/chart.json +1084 -0
- package/examples/private/ci_formatted.json +202 -0
- package/examples/private/ci_issue.json +3016 -0
- package/examples/private/completed.json +634 -0
- package/examples/private/dem-data-long.csv +20 -0
- package/examples/private/dem-data-long.json +36 -0
- package/examples/private/demographic_data.csv +157 -0
- package/examples/private/demographic_data.json +2654 -0
- package/examples/private/demographic_dynamic.json +443 -0
- package/examples/private/demographic_standard.json +560 -0
- package/examples/private/test.json +493 -0
- package/index.html +10 -7
- package/package.json +2 -2
- package/src/CdcChart.tsx +132 -152
- package/src/_stories/Chart.Anchors.stories.tsx +31 -0
- package/src/_stories/Chart.CustomColors.stories.tsx +19 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +34 -0
- package/src/_stories/Chart.Legend.Gradient.stories.tsx +42 -1
- package/src/_stories/Chart.stories.tsx +37 -6
- package/src/_stories/ChartAxisLabels.stories.tsx +4 -1
- package/src/_stories/ChartEditor.stories.tsx +27 -0
- package/src/_stories/ChartLine.Suppression.stories.tsx +25 -0
- package/src/_stories/ChartPrefixSuffix.stories.tsx +8 -0
- package/{examples/feature/area/area-chart-date-city-temperature.json → src/_stories/_mock/area_chart_stacked.json} +125 -27
- package/src/_stories/_mock/boxplot_multiseries.json +647 -0
- package/src/_stories/_mock/dynamic_series_bar_config.json +723 -0
- package/src/_stories/_mock/dynamic_series_config.json +979 -0
- package/src/_stories/_mock/line_chart_dynamic_ci.json +493 -0
- package/src/_stories/_mock/line_chart_non_dynamic_ci.json +522 -0
- package/{examples/feature/scatterplot/scatterplot.json → src/_stories/_mock/scatterplot_mock.json} +62 -92
- package/src/_stories/_mock/short_dates.json +288 -0
- package/src/_stories/_mock/suppression_mock.json +1549 -0
- package/src/components/AreaChart/components/AreaChart.Stacked.jsx +15 -3
- package/src/components/Axis/Categorical.Axis.tsx +2 -2
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +46 -37
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +43 -9
- package/src/components/BarChart/components/BarChart.Vertical.tsx +53 -47
- package/src/components/BarChart/helpers/getBarData.ts +28 -0
- package/src/components/BarChart/helpers/index.ts +1 -2
- package/src/components/BarChart/helpers/tests/getBarData.test.ts +74 -0
- package/src/components/BoxPlot/BoxPlot.tsx +131 -0
- package/src/components/BoxPlot/helpers/index.ts +54 -0
- package/src/components/BrushChart.tsx +23 -26
- package/src/components/EditorPanel/EditorPanel.tsx +117 -139
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.BoxPlot.tsx +51 -6
- package/src/components/EditorPanel/components/Panels/Panel.Regions.tsx +40 -9
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +3 -3
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +122 -56
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -2
- package/src/components/EditorPanel/useEditorPermissions.ts +20 -2
- package/src/components/Legend/Legend.Component.tsx +11 -12
- package/src/components/Legend/Legend.tsx +16 -16
- package/src/components/Legend/helpers/getLegendClasses.ts +59 -0
- package/src/components/Legend/helpers/index.ts +2 -1
- package/src/components/Legend/tests/getLegendClasses.test.ts +115 -0
- package/src/components/LineChart/components/LineChart.Circle.tsx +1 -1
- package/src/components/LineChart/helpers.ts +49 -43
- package/src/components/LineChart/index.tsx +135 -83
- package/src/components/LinearChart.tsx +187 -181
- package/src/components/PieChart/PieChart.tsx +7 -1
- package/src/components/Sankey/components/ColumnList.tsx +19 -0
- package/src/components/Sankey/components/Sankey.tsx +479 -0
- package/src/components/Sankey/helpers/getSankeyTooltip.tsx +33 -0
- package/src/components/Sankey/index.tsx +1 -492
- package/src/components/Sankey/sankey.scss +22 -21
- package/src/components/Sankey/types/index.ts +1 -1
- package/src/components/Sankey/useSankeyAlert.tsx +60 -0
- package/src/components/ScatterPlot/ScatterPlot.jsx +20 -4
- package/src/data/initial-state.js +7 -12
- package/src/helpers/countNumOfTicks.ts +57 -0
- package/src/helpers/getQuartiles.ts +15 -18
- package/src/hooks/useMinMax.ts +44 -16
- package/src/hooks/useReduceData.ts +43 -10
- package/src/hooks/useScales.ts +90 -35
- package/src/hooks/useTooltip.tsx +59 -50
- package/src/scss/DataTable.scss +5 -0
- package/src/scss/main.scss +6 -20
- package/src/types/ChartConfig.ts +6 -19
- package/src/types/ChartContext.ts +4 -1
- package/src/types/ForestPlot.ts +8 -0
- package/src/components/BoxPlot/BoxPlot.jsx +0 -111
- package/src/hooks/useLegendClasses.ts +0 -72
|
@@ -35,12 +35,14 @@ import { calcInitialHeight } from '../helpers/sizeHelpers'
|
|
|
35
35
|
import useMinMax from '../hooks/useMinMax'
|
|
36
36
|
import useReduceData from '../hooks/useReduceData'
|
|
37
37
|
import useRightAxis from '../hooks/useRightAxis'
|
|
38
|
-
import useScales, { getTickValues } from '../hooks/useScales'
|
|
38
|
+
import useScales, { getTickValues, filterAndShiftLinearDateTicks } from '../hooks/useScales'
|
|
39
39
|
import useTopAxis from '../hooks/useTopAxis'
|
|
40
40
|
import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
|
|
41
41
|
import { useEditorPermissions } from './EditorPanel/useEditorPermissions'
|
|
42
42
|
import Annotation from './Annotations'
|
|
43
43
|
import { BlurStrokeText } from '@cdc/core/components/BlurStrokeText'
|
|
44
|
+
import { fontSizes } from '@cdc/core/helpers/cove/fontSettings'
|
|
45
|
+
import { countNumOfTicks } from '../helpers/countNumOfTicks'
|
|
44
46
|
|
|
45
47
|
type LinearChartProps = {
|
|
46
48
|
parentWidth: number
|
|
@@ -50,6 +52,7 @@ type LinearChartProps = {
|
|
|
50
52
|
const BOTTOM_LABEL_PADDING = 9
|
|
51
53
|
const X_TICK_LABEL_PADDING = 3
|
|
52
54
|
const DEFAULT_TICK_LENGTH = 8
|
|
55
|
+
const MONTH_AS_MS = 1000 * 60 * 60 * 24 * 30
|
|
53
56
|
|
|
54
57
|
const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, parentWidth }, svgRef) => {
|
|
55
58
|
// prettier-ignore
|
|
@@ -100,6 +103,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
100
103
|
const [animatedChart, setAnimatedChart] = useState(false)
|
|
101
104
|
const [point, setPoint] = useState({ x: 0, y: 0 })
|
|
102
105
|
const [suffixWidth, setSuffixWidth] = useState(0)
|
|
106
|
+
const [yAxisAutoPadding, setYAxisAutoPadding] = useState(0)
|
|
103
107
|
|
|
104
108
|
// REFS
|
|
105
109
|
const axisBottomRef = useRef(null)
|
|
@@ -109,7 +113,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
109
113
|
const triggerRef = useRef()
|
|
110
114
|
const xAxisLabelRefs = useRef([])
|
|
111
115
|
const xAxisTitleRef = useRef(null)
|
|
112
|
-
const
|
|
116
|
+
const lastMaxValue = useRef(maxValue)
|
|
113
117
|
|
|
114
118
|
const dataRef = useIntersectionObserver(triggerRef, {
|
|
115
119
|
freezeOnceVisible: false
|
|
@@ -120,8 +124,10 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
120
124
|
const isHorizontal = orientation === 'horizontal' || config.visualizationType === 'Forest Plot'
|
|
121
125
|
const isLogarithmicAxis = config.yAxis.type === 'logarithmic'
|
|
122
126
|
const isForestPlot = visualizationType === 'Forest Plot'
|
|
127
|
+
const isDateTime = config.xAxis.type === 'date-time'
|
|
123
128
|
const suffixHasNoSpace = !suffix.includes(' ')
|
|
124
|
-
|
|
129
|
+
const labelsOverflow = onlyShowTopPrefixSuffix && !suffixHasNoSpace
|
|
130
|
+
const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
|
|
125
131
|
const yLabelOffset = isNaN(parseInt(`${runtime.yAxis.labelOffset}`)) ? 0 : parseInt(`${runtime.yAxis.labelOffset}`)
|
|
126
132
|
|
|
127
133
|
// zero if not forest plot
|
|
@@ -130,7 +136,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
130
136
|
// height before bottom axis
|
|
131
137
|
const initialHeight = useMemo(
|
|
132
138
|
() => calcInitialHeight(config, currentViewport),
|
|
133
|
-
[config, currentViewport, parentHeight]
|
|
139
|
+
[config, currentViewport, parentHeight, config.heights?.vertical, config.heights?.horizontal]
|
|
134
140
|
)
|
|
135
141
|
const forestHeight = useMemo(() => initialHeight + forestRowsHeight, [initialHeight, forestRowsHeight])
|
|
136
142
|
|
|
@@ -170,11 +176,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
170
176
|
const xMax = width - runtime.yAxis.size - (visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
|
|
171
177
|
const yMax = initialHeight + forestRowsHeight
|
|
172
178
|
|
|
173
|
-
const
|
|
174
|
-
return isConvertLineToBarGraph(config.visualizationType, data, config.allowLineToBarGraph)
|
|
175
|
-
}
|
|
179
|
+
const isNoDataAvailable = config.filters && config.filters.values.length === 0 && data.length === 0
|
|
176
180
|
|
|
177
|
-
// GETTERS & FUNCTIONS
|
|
178
181
|
const getXAxisData = d =>
|
|
179
182
|
isDateScale(config.runtime.xAxis)
|
|
180
183
|
? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime()
|
|
@@ -188,7 +191,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
188
191
|
const properties = {
|
|
189
192
|
data,
|
|
190
193
|
tableData,
|
|
191
|
-
config
|
|
194
|
+
config: {
|
|
195
|
+
...config,
|
|
196
|
+
yAxis: {
|
|
197
|
+
...config.yAxis,
|
|
198
|
+
scalePadding: labelsOverflow ? yAxisAutoPadding : config.yAxis.scalePadding,
|
|
199
|
+
enablePadding: labelsOverflow || config.yAxis.enablePadding
|
|
200
|
+
}
|
|
201
|
+
},
|
|
192
202
|
minValue,
|
|
193
203
|
maxValue,
|
|
194
204
|
isAllLine,
|
|
@@ -209,6 +219,40 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
209
219
|
xMax: parentWidth - Number(config.orientation === 'horizontal' ? config.xAxis.size : config.yAxis.size)
|
|
210
220
|
})
|
|
211
221
|
|
|
222
|
+
const [yTickCount, xTickCount] = ['yAxis', 'xAxis'].map(axis =>
|
|
223
|
+
countNumOfTicks({ axis, max, runtime, currentViewport, isHorizontal, data, config, min })
|
|
224
|
+
)
|
|
225
|
+
const handleNumTicks = isForestPlot ? config.data.length : yTickCount
|
|
226
|
+
|
|
227
|
+
// Tooltip Helpers
|
|
228
|
+
const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip()
|
|
229
|
+
|
|
230
|
+
// prettier-ignore
|
|
231
|
+
const {
|
|
232
|
+
handleTooltipMouseOver,
|
|
233
|
+
handleTooltipClick,
|
|
234
|
+
handleTooltipMouseOff,
|
|
235
|
+
TooltipListItem,
|
|
236
|
+
getXValueFromCoordinate
|
|
237
|
+
} = useCoveTooltip({
|
|
238
|
+
xScale,
|
|
239
|
+
yScale,
|
|
240
|
+
showTooltip,
|
|
241
|
+
hideTooltip
|
|
242
|
+
})
|
|
243
|
+
// get the number of months between the first and last date
|
|
244
|
+
const { dataKey } = runtime.xAxis
|
|
245
|
+
const dateSpanMonths =
|
|
246
|
+
data.length && isDateTime
|
|
247
|
+
? [0, data.length - 1].map(i => parseDate(data[i][dataKey])).reduce((a, b) => Math.abs(a - b)) / MONTH_AS_MS
|
|
248
|
+
: 0
|
|
249
|
+
const useDateSpanMonths = isDateTime && dateSpanMonths > xTickCount
|
|
250
|
+
|
|
251
|
+
// GETTERS & FUNCTIONS
|
|
252
|
+
const checkLineToBarGraph = () => {
|
|
253
|
+
return isConvertLineToBarGraph(config.visualizationType, data, config.allowLineToBarGraph)
|
|
254
|
+
}
|
|
255
|
+
|
|
212
256
|
const handleLeftTickFormatting = (tick, index, ticks) => {
|
|
213
257
|
if (isLogarithmicAxis && tick === 0.1) {
|
|
214
258
|
//when logarithmic scale applied change value of first tick
|
|
@@ -227,16 +271,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
227
271
|
return tick
|
|
228
272
|
}
|
|
229
273
|
|
|
230
|
-
const handleBottomTickFormatting = tick => {
|
|
274
|
+
const handleBottomTickFormatting = (tick, i, ticks) => {
|
|
231
275
|
if (isLogarithmicAxis && tick === 0.1) {
|
|
232
276
|
// when logarithmic scale applied change value FIRST of tick
|
|
233
277
|
tick = 0
|
|
234
278
|
}
|
|
235
279
|
|
|
236
280
|
if (isDateScale(runtime.xAxis) && config.visualizationType !== 'Forest Plot') {
|
|
237
|
-
|
|
238
|
-
prevTickRef.current = tick
|
|
239
|
-
return formattedDate
|
|
281
|
+
return formatDate(tick, i, ticks)
|
|
240
282
|
}
|
|
241
283
|
if (orientation === 'horizontal' && config.visualizationType !== 'Forest Plot')
|
|
242
284
|
return formatNumber(tick, 'left', shouldAbbreviate)
|
|
@@ -254,80 +296,33 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
254
296
|
return tick
|
|
255
297
|
}
|
|
256
298
|
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
if (
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
tickCount =
|
|
266
|
-
isHorizontal && !numTicks
|
|
267
|
-
? data.length
|
|
268
|
-
: isHorizontal && numTicks
|
|
269
|
-
? numTicks
|
|
270
|
-
: !isHorizontal && !numTicks
|
|
271
|
-
? undefined
|
|
272
|
-
: !isHorizontal && numTicks && numTicks
|
|
273
|
-
// to fix edge case of small numbers with decimals
|
|
274
|
-
if (tickCount === undefined && !config.dataFormat.roundTo) {
|
|
275
|
-
// then it is set to Auto
|
|
276
|
-
if (Number(max) <= 3) {
|
|
277
|
-
tickCount = 2
|
|
278
|
-
} else {
|
|
279
|
-
tickCount = 4 // same default as standalone components
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
if (Number(tickCount) > Number(max)) {
|
|
283
|
-
// cap it and round it so its an integer
|
|
284
|
-
tickCount = Number(min) < 0 ? Math.round(max) * 2 : Math.round(max)
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (axis === 'xAxis') {
|
|
289
|
-
tickCount =
|
|
290
|
-
isHorizontal && !numTicks
|
|
291
|
-
? undefined
|
|
292
|
-
: isHorizontal && numTicks
|
|
293
|
-
? numTicks
|
|
294
|
-
: !isHorizontal && !numTicks
|
|
295
|
-
? undefined
|
|
296
|
-
: !isHorizontal && numTicks && numTicks
|
|
297
|
-
if (isHorizontal && tickCount === undefined && !config.dataFormat.roundTo) {
|
|
298
|
-
// then it is set to Auto
|
|
299
|
-
// - check for small numbers situation
|
|
300
|
-
if (max <= 3) {
|
|
301
|
-
tickCount = 2
|
|
302
|
-
} else {
|
|
303
|
-
tickCount = 4 // same default as standalone components
|
|
304
|
-
}
|
|
305
|
-
}
|
|
299
|
+
const chartHasTooltipGuides = () => {
|
|
300
|
+
const { visualizationType } = config
|
|
301
|
+
if (visualizationType === 'Combo' && runtime.forecastingSeriesKeys > 0) return true
|
|
302
|
+
if (visualizationType === 'Area Chart') return true
|
|
303
|
+
if (visualizationType === 'Line') return true
|
|
304
|
+
if (visualizationType === 'Bar') return true
|
|
305
|
+
return false
|
|
306
|
+
}
|
|
306
307
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
308
|
+
const getManualStep = () => {
|
|
309
|
+
let manualStep = config.xAxis.manualStep
|
|
310
|
+
if (config.xAxis.viewportStepCount && config.xAxis.viewportStepCount[currentViewport]) {
|
|
311
|
+
manualStep = config.xAxis.viewportStepCount[currentViewport]
|
|
310
312
|
}
|
|
311
|
-
|
|
312
|
-
return tickCount
|
|
313
|
+
return manualStep
|
|
313
314
|
}
|
|
314
315
|
|
|
315
|
-
|
|
316
|
-
|
|
316
|
+
const onMouseMove = event => {
|
|
317
|
+
const svgRect = event.currentTarget.getBoundingClientRect()
|
|
318
|
+
const x = event.clientX - svgRect.left
|
|
319
|
+
const y = event.clientY - svgRect.top
|
|
317
320
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
TooltipListItem,
|
|
324
|
-
getXValueFromCoordinate
|
|
325
|
-
} = useCoveTooltip({
|
|
326
|
-
xScale,
|
|
327
|
-
yScale,
|
|
328
|
-
showTooltip,
|
|
329
|
-
hideTooltip
|
|
330
|
-
})
|
|
321
|
+
setPoint({
|
|
322
|
+
x,
|
|
323
|
+
y
|
|
324
|
+
})
|
|
325
|
+
}
|
|
331
326
|
|
|
332
327
|
// EFFECTS
|
|
333
328
|
|
|
@@ -410,45 +405,29 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
410
405
|
const legendIsLeftOrRight =
|
|
411
406
|
legend?.position !== 'top' && legend?.position !== 'bottom' && !isLegendWrapViewport(currentViewport)
|
|
412
407
|
legendRef.current.style.transform = legendIsLeftOrRight ? `translateY(${topLabelOnGridlineHeight}px)` : 'none'
|
|
413
|
-
}, [axisBottomRef.current, config, bottomLabelStart, brush, currentViewport, topYLabelRef.current])
|
|
408
|
+
}, [axisBottomRef.current, config, bottomLabelStart, brush, currentViewport, topYLabelRef.current, initialHeight])
|
|
414
409
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
if (
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
410
|
+
useEffect(() => {
|
|
411
|
+
if (lastMaxValue.current === maxValue) return
|
|
412
|
+
lastMaxValue.current = maxValue
|
|
413
|
+
if (!yAxisAutoPadding) return
|
|
414
|
+
setYAxisAutoPadding(0)
|
|
415
|
+
}, [maxValue])
|
|
416
|
+
useEffect(() => {
|
|
417
|
+
if (orientation === 'horizontal') return
|
|
423
418
|
|
|
424
|
-
|
|
425
|
-
const fontSize = { small: 16, medium: 18, large: 20 }
|
|
419
|
+
const maxValueIsGreaterThanTopGridLine = maxValue > Math.max(...yScale.ticks(handleNumTicks))
|
|
426
420
|
|
|
427
|
-
|
|
428
|
-
// On forest plots we need to return every "study" or y axis value.
|
|
429
|
-
if (config.visualizationType === 'Forest Plot') return config.data.length
|
|
430
|
-
return countNumOfTicks('yAxis')
|
|
431
|
-
}
|
|
421
|
+
if (!maxValueIsGreaterThanTopGridLine || !labelsOverflow) return
|
|
432
422
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
manualStep = config.xAxis.viewportStepCount[currentViewport]
|
|
437
|
-
}
|
|
438
|
-
return manualStep
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
const onMouseMove = event => {
|
|
442
|
-
const svgRect = event.currentTarget.getBoundingClientRect()
|
|
443
|
-
const x = event.clientX - svgRect.left
|
|
444
|
-
const y = event.clientY - svgRect.top
|
|
423
|
+
const tickGap = yScale.ticks(handleNumTicks)[1] - yScale.ticks(handleNumTicks)[0]
|
|
424
|
+
const nextTick = Math.max(...yScale.ticks(handleNumTicks)) + tickGap
|
|
425
|
+
const newPadding = minValue < 0 ? (nextTick - maxValue) / maxValue / 2 : (nextTick - maxValue) / maxValue
|
|
445
426
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
y
|
|
449
|
-
})
|
|
450
|
-
}
|
|
427
|
+
setYAxisAutoPadding(newPadding * 100)
|
|
428
|
+
}, [maxValue, labelsOverflow, yScale, handleNumTicks])
|
|
451
429
|
|
|
430
|
+
// Render Functions
|
|
452
431
|
const generatePairedBarAxis = () => {
|
|
453
432
|
const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
|
|
454
433
|
|
|
@@ -459,7 +438,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
459
438
|
const numberOfTicks = filteredTicks?.length
|
|
460
439
|
const xMaxHalf = xScale.range()[0] || xMax / 2
|
|
461
440
|
const tickWidthAll = filteredTicks.map(tick =>
|
|
462
|
-
getTextWidth(formatNumber(tick.value, 'left'), `normal ${
|
|
441
|
+
getTextWidth(formatNumber(tick.value, 'left'), `normal ${fontSizes[config.fontSize]}px sans-serif`)
|
|
463
442
|
)
|
|
464
443
|
const accumulator = 100
|
|
465
444
|
const sumOfTickWidth = tickWidthAll.reduce((a, b) => a + b, accumulator)
|
|
@@ -498,7 +477,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
498
477
|
{props.ticks.map((tick, i) => {
|
|
499
478
|
const textWidth = getTextWidth(
|
|
500
479
|
formatNumber(tick.value, 'left'),
|
|
501
|
-
`normal ${
|
|
480
|
+
`normal ${fontSizes[config.fontSize]}px sans-serif`
|
|
502
481
|
)
|
|
503
482
|
const isTicksOverlapping = getTickPositions(props.ticks, g1xScale)
|
|
504
483
|
const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
|
|
@@ -550,7 +529,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
550
529
|
{props.ticks.map((tick, i) => {
|
|
551
530
|
const textWidth = getTextWidth(
|
|
552
531
|
formatNumber(tick.value, 'left'),
|
|
553
|
-
`normal ${
|
|
532
|
+
`normal ${fontSizes[config.fontSize]}px sans-serif`
|
|
554
533
|
)
|
|
555
534
|
const isTicksOverlapping = getTickPositions(props.ticks, g2xScale)
|
|
556
535
|
const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
|
|
@@ -611,7 +590,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
611
590
|
ref={svgRef}
|
|
612
591
|
onMouseMove={onMouseMove}
|
|
613
592
|
width={parentWidth}
|
|
614
|
-
height={parentHeight}
|
|
593
|
+
height={isNoDataAvailable ? 1 : parentHeight}
|
|
615
594
|
className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${
|
|
616
595
|
debugSvg && 'debug'
|
|
617
596
|
} ${isDraggingAnnotation && 'dragging-annotation'}`}
|
|
@@ -626,7 +605,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
626
605
|
<AxisLeft
|
|
627
606
|
scale={yScale}
|
|
628
607
|
left={Number(runtime.yAxis.size) - config.yAxis.axisPadding}
|
|
629
|
-
numTicks={handleNumTicks
|
|
608
|
+
numTicks={handleNumTicks}
|
|
630
609
|
>
|
|
631
610
|
{props => {
|
|
632
611
|
const axisCenter =
|
|
@@ -690,7 +669,17 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
690
669
|
showTooltip={showTooltip}
|
|
691
670
|
/>
|
|
692
671
|
)}
|
|
693
|
-
{visualizationType === 'Box Plot' &&
|
|
672
|
+
{visualizationType === 'Box Plot' && (
|
|
673
|
+
<BoxPlot
|
|
674
|
+
seriesScale={seriesScale}
|
|
675
|
+
xMax={xMax}
|
|
676
|
+
yMax={yMax}
|
|
677
|
+
min={min}
|
|
678
|
+
max={max}
|
|
679
|
+
xScale={xScale}
|
|
680
|
+
yScale={yScale}
|
|
681
|
+
/>
|
|
682
|
+
)}
|
|
694
683
|
{((visualizationType === 'Area Chart' && config.visualizationSubType === 'regular') ||
|
|
695
684
|
visualizationType === 'Combo') && (
|
|
696
685
|
<AreaChart
|
|
@@ -778,20 +767,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
778
767
|
isBrush={false}
|
|
779
768
|
/>
|
|
780
769
|
)}
|
|
781
|
-
{/* y anchors */}
|
|
782
|
-
{config.yAxis.anchors &&
|
|
783
|
-
config.yAxis.anchors.map(anchor => {
|
|
784
|
-
return (
|
|
785
|
-
<Line
|
|
786
|
-
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
787
|
-
stroke='rgba(0,0,0,1)'
|
|
788
|
-
className='customAnchor'
|
|
789
|
-
from={{ x: 0 + config.yAxis.size, y: yScale(anchor.value) }}
|
|
790
|
-
to={{ x: xMax, y: yScale(anchor.value) }}
|
|
791
|
-
display={runtime.horizontal ? 'none' : 'block'}
|
|
792
|
-
/>
|
|
793
|
-
)
|
|
794
|
-
})}
|
|
795
770
|
{visualizationType === 'Forest Plot' && (
|
|
796
771
|
<ForestPlot
|
|
797
772
|
xScale={xScale}
|
|
@@ -814,16 +789,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
814
789
|
/>
|
|
815
790
|
)}
|
|
816
791
|
{/*Brush chart */}
|
|
817
|
-
{config.brush.active && config.xAxis.type !== 'categorical' &&
|
|
818
|
-
<BrushChart
|
|
819
|
-
xScaleBrush={xScaleBrush}
|
|
820
|
-
yScale={yScale}
|
|
821
|
-
xMax={xMax}
|
|
822
|
-
yMax={yMax}
|
|
823
|
-
xScale={xScale}
|
|
824
|
-
seriesScale={seriesScale}
|
|
825
|
-
/>
|
|
826
|
-
)}
|
|
792
|
+
{config.brush.active && config.xAxis.type !== 'categorical' && <BrushChart xMax={xMax} yMax={yMax} />}
|
|
827
793
|
{/* Line chart */}
|
|
828
794
|
{/* TODO: Make this just line or combo? */}
|
|
829
795
|
{!['Paired Bar', 'Box Plot', 'Area Chart', 'Scatter Plot', 'Deviation Bar', 'Forecasting', 'Bar'].includes(
|
|
@@ -845,14 +811,19 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
845
811
|
{/* y anchors */}
|
|
846
812
|
{config.yAxis.anchors &&
|
|
847
813
|
config.yAxis.anchors.map((anchor, index) => {
|
|
848
|
-
let
|
|
849
|
-
|
|
850
|
-
|
|
814
|
+
let position = yScale(anchor.value)
|
|
815
|
+
let middleOffset = 0
|
|
816
|
+
|
|
851
817
|
if (!anchor.value) return
|
|
852
|
-
|
|
853
|
-
|
|
818
|
+
if (config.yAxis.labelPlacement === 'Below Bar') {
|
|
819
|
+
const textOffset = -6.5
|
|
820
|
+
middleOffset = textOffset + Number(config.series.length * config.barHeight) / config.series.length
|
|
821
|
+
} else {
|
|
822
|
+
const paddingOffset = 8
|
|
823
|
+
middleOffset = paddingOffset
|
|
824
|
+
}
|
|
854
825
|
|
|
855
|
-
if (!
|
|
826
|
+
if (!position) return
|
|
856
827
|
|
|
857
828
|
return (
|
|
858
829
|
// prettier-ignore
|
|
@@ -861,8 +832,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
861
832
|
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
862
833
|
stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
863
834
|
className='anchor-y'
|
|
864
|
-
from={{ x: 0 + padding, y:
|
|
865
|
-
to={{ x: width - config.yAxis.rightAxisSize, y:
|
|
835
|
+
from={{ x: 0 + padding, y: position - middleOffset}}
|
|
836
|
+
to={{ x: width - config.yAxis.rightAxisSize, y: position - middleOffset }}
|
|
866
837
|
/>
|
|
867
838
|
)
|
|
868
839
|
})}
|
|
@@ -874,10 +845,19 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
874
845
|
newX = yAxis
|
|
875
846
|
}
|
|
876
847
|
|
|
877
|
-
|
|
848
|
+
const getAnchorPosition = (): number | undefined => {
|
|
849
|
+
let position: number | undefined
|
|
878
850
|
|
|
879
|
-
|
|
880
|
-
|
|
851
|
+
position = isDateScale(newX) ? xScale(parseDate(anchor.value, false)) : xScale(anchor.value)
|
|
852
|
+
if (config.xAxis.type === 'categorical' || config.xAxis.type === 'date') {
|
|
853
|
+
position = position
|
|
854
|
+
? position + (newX.type === 'categorical' || newX.type === 'date' ? xScale.bandwidth() : 0) / 2
|
|
855
|
+
: 0
|
|
856
|
+
}
|
|
857
|
+
return position
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
let anchorPosition = getAnchorPosition()
|
|
881
861
|
|
|
882
862
|
if (!anchorPosition) return
|
|
883
863
|
|
|
@@ -939,7 +919,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
939
919
|
/>
|
|
940
920
|
</Group>
|
|
941
921
|
)}
|
|
942
|
-
{
|
|
922
|
+
{isNoDataAvailable && (
|
|
943
923
|
<Text
|
|
944
924
|
x={Number(config.yAxis.size) + Number(xMax / 2)}
|
|
945
925
|
y={initialHeight / 2 - (config.xAxis.padding || 0) / 2}
|
|
@@ -1002,7 +982,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1002
982
|
label={runtime.yAxis.label || runtime.yAxis.label}
|
|
1003
983
|
stroke='#333'
|
|
1004
984
|
tickFormat={handleLeftTickFormatting}
|
|
1005
|
-
numTicks={handleNumTicks
|
|
985
|
+
numTicks={handleNumTicks}
|
|
1006
986
|
>
|
|
1007
987
|
{props => {
|
|
1008
988
|
const axisCenter =
|
|
@@ -1345,43 +1325,63 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1345
1325
|
tickFormat={handleBottomTickFormatting}
|
|
1346
1326
|
scale={xScale}
|
|
1347
1327
|
stroke='#333'
|
|
1348
|
-
numTicks={
|
|
1328
|
+
numTicks={useDateSpanMonths ? dateSpanMonths : xTickCount}
|
|
1349
1329
|
tickStroke='#333'
|
|
1350
1330
|
tickValues={
|
|
1351
1331
|
config.xAxis.manual
|
|
1352
|
-
? getTickValues(
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
config.xAxis.type === 'date-time' ? countNumOfTicks('xAxis') : getManualStep(),
|
|
1356
|
-
config
|
|
1357
|
-
)
|
|
1332
|
+
? getTickValues(xAxisDataMapped, xScale, isDateTime ? xTickCount : getManualStep(), config)
|
|
1333
|
+
: config.xAxis.type === 'date'
|
|
1334
|
+
? xAxisDataMapped
|
|
1358
1335
|
: undefined
|
|
1359
1336
|
}
|
|
1360
1337
|
>
|
|
1361
1338
|
{props => {
|
|
1339
|
+
// For these charts, we generated all ticks in tickValues above, and now need to filter/shift them
|
|
1340
|
+
// so the last tick is always labeled
|
|
1341
|
+
if (config.xAxis.type === 'date' && !config.xAxis.manual) {
|
|
1342
|
+
props.ticks = filterAndShiftLinearDateTicks(config, props, xAxisDataMapped, formatDate)
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
const distanceBetweenTicks =
|
|
1346
|
+
useDateSpanMonths &&
|
|
1347
|
+
xScale
|
|
1348
|
+
.ticks(xTickCount)
|
|
1349
|
+
.map(t => props.ticks.findIndex(tick => tick.value.getTime() === t.getTime()))
|
|
1350
|
+
.slice(0, 2)
|
|
1351
|
+
.reduce((acc, curr) => curr - acc)
|
|
1352
|
+
|
|
1353
|
+
// filter out every [distanceBetweenTicks] tick starting from the end, so the last tick is always labeled
|
|
1354
|
+
const filteredTicks = useDateSpanMonths
|
|
1355
|
+
? [...props.ticks]
|
|
1356
|
+
.reverse()
|
|
1357
|
+
.filter((_, i) => i % distanceBetweenTicks === 0)
|
|
1358
|
+
.reverse()
|
|
1359
|
+
.map((tick, i, arr) => ({
|
|
1360
|
+
...tick,
|
|
1361
|
+
// reformat in case showYearsOnce, since first month of year may have changed
|
|
1362
|
+
formattedValue: handleBottomTickFormatting(tick.value, i, arr)
|
|
1363
|
+
}))
|
|
1364
|
+
: props.ticks
|
|
1365
|
+
|
|
1362
1366
|
const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
|
|
1363
1367
|
|
|
1364
|
-
const axisCenter =
|
|
1365
|
-
config.visualizationType !== 'Forest Plot'
|
|
1366
|
-
? (props.axisToPoint.x - props.axisFromPoint.x) / 2
|
|
1367
|
-
: dimensions[0] / 2
|
|
1368
1368
|
const containsMultipleWords = inputString => /\s/.test(inputString)
|
|
1369
|
-
const ismultiLabel =
|
|
1369
|
+
const ismultiLabel = filteredTicks.some(tick => containsMultipleWords(tick.value))
|
|
1370
1370
|
|
|
1371
1371
|
// Calculate sumOfTickWidth here, before map function
|
|
1372
1372
|
const tickWidthMax = Math.max(
|
|
1373
|
-
...
|
|
1374
|
-
getTextWidth(tick.formattedValue, `normal ${
|
|
1373
|
+
...filteredTicks.map(tick =>
|
|
1374
|
+
getTextWidth(tick.formattedValue, `normal ${fontSizes[config.fontSize]}px sans-serif`)
|
|
1375
1375
|
)
|
|
1376
1376
|
)
|
|
1377
1377
|
// const marginTop = 20 // moved to top bc need for yMax calcs
|
|
1378
1378
|
const accumulator = ismultiLabel ? 180 : 100
|
|
1379
1379
|
|
|
1380
|
-
const textWidths =
|
|
1381
|
-
getTextWidth(tick.formattedValue, `normal ${
|
|
1380
|
+
const textWidths = filteredTicks.map(tick =>
|
|
1381
|
+
getTextWidth(tick.formattedValue, `normal ${fontSizes[config.fontSize]}px sans-serif`)
|
|
1382
1382
|
)
|
|
1383
1383
|
const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
|
|
1384
|
-
const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (
|
|
1384
|
+
const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (filteredTicks.length - 1)
|
|
1385
1385
|
|
|
1386
1386
|
// Check if ticks are overlapping
|
|
1387
1387
|
// Determine the position of each tick
|
|
@@ -1409,6 +1409,16 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1409
1409
|
areTicksTouching = true
|
|
1410
1410
|
}
|
|
1411
1411
|
|
|
1412
|
+
// force wrap it last tick is close to the end of the axis
|
|
1413
|
+
const lastTickWidth = textWidths[textWidths.length - 1]
|
|
1414
|
+
const lastTickPosition = positions[positions.length - 1] + lastTickWidth
|
|
1415
|
+
const lastTickEnd = lastTickPosition + lastTickWidth / 2
|
|
1416
|
+
const lastTickEndThreshold = xMax - lastTickWidth
|
|
1417
|
+
|
|
1418
|
+
if (lastTickEnd > lastTickEndThreshold) {
|
|
1419
|
+
areTicksTouching = true
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1412
1422
|
const dynamicMarginTop =
|
|
1413
1423
|
areTicksTouching && config.isResponsiveTicks ? tickWidthMax + DEFAULT_TICK_LENGTH + 20 : 0
|
|
1414
1424
|
|
|
@@ -1417,15 +1427,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1417
1427
|
|
|
1418
1428
|
return (
|
|
1419
1429
|
<Group className='bottom-axis' width={dimensions[0]}>
|
|
1420
|
-
{
|
|
1430
|
+
{filteredTicks.map((tick, i, propsTicks) => {
|
|
1421
1431
|
// when using LogScale show major ticks values only
|
|
1422
1432
|
const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
|
|
1423
1433
|
const tickLength = showTick === 'block' ? 16 : DEFAULT_TICK_LENGTH
|
|
1424
1434
|
const to = { x: tick.to.x, y: tickLength }
|
|
1425
|
-
const textWidth = getTextWidth(
|
|
1426
|
-
tick.formattedValue,
|
|
1427
|
-
`normal ${fontSize[config.fontSize]}px sans-serif`
|
|
1428
|
-
)
|
|
1429
1435
|
const limitedWidth = 100 / propsTicks.length
|
|
1430
1436
|
//reset rotations by updating config
|
|
1431
1437
|
config.yAxis.tickRotation =
|
|
@@ -1476,7 +1482,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1476
1482
|
<Text
|
|
1477
1483
|
innerRef={xAxisTitleRef}
|
|
1478
1484
|
className='x-axis-title-label'
|
|
1479
|
-
x={
|
|
1485
|
+
x={xMax / 2}
|
|
1480
1486
|
y={isForestPlot ? 0 /* set via ref */ : axisMaxHeight}
|
|
1481
1487
|
textAnchor='middle'
|
|
1482
1488
|
verticalAnchor='start'
|
|
@@ -170,6 +170,7 @@ const PieChart = props => {
|
|
|
170
170
|
)
|
|
171
171
|
})}
|
|
172
172
|
{transitions.map(({ item: arc, key }, i) => {
|
|
173
|
+
const roundTo = Number(config.dataFormat.roundTo) || 0
|
|
173
174
|
const [centroidX, centroidY] = path.centroid(arc)
|
|
174
175
|
const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.1
|
|
175
176
|
|
|
@@ -177,6 +178,11 @@ const PieChart = props => {
|
|
|
177
178
|
if (_colorScale(arc.data[config.runtime.xAxis.dataKey])) {
|
|
178
179
|
textColor = getContrastColor(textColor, _colorScale(arc.data[config.runtime.xAxis.dataKey]))
|
|
179
180
|
}
|
|
181
|
+
const degrees = ((arc.endAngle - arc.startAngle) * 180) / Math.PI
|
|
182
|
+
|
|
183
|
+
// Calculate the percentage of the full circle (360 degrees)
|
|
184
|
+
const percentageOfCircle = (degrees / 360) * 100
|
|
185
|
+
const roundedPercentage = percentageOfCircle.toFixed(roundTo)
|
|
180
186
|
|
|
181
187
|
return (
|
|
182
188
|
<animated.g key={`${key}${i}`}>
|
|
@@ -189,7 +195,7 @@ const PieChart = props => {
|
|
|
189
195
|
textAnchor='middle'
|
|
190
196
|
pointerEvents='none'
|
|
191
197
|
>
|
|
192
|
-
{
|
|
198
|
+
{roundedPercentage + '%'}
|
|
193
199
|
</Text>
|
|
194
200
|
)}
|
|
195
201
|
</animated.g>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface ColumnData {
|
|
2
|
+
label: string
|
|
3
|
+
value: string
|
|
4
|
+
additional_info: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const ColumnList = ({ columnData }) => {
|
|
8
|
+
return (
|
|
9
|
+
<ul>
|
|
10
|
+
{columnData?.map((entry, index) => (
|
|
11
|
+
<li key={index}>
|
|
12
|
+
{entry.label}: {entry.value} ({entry.additional_info}%)
|
|
13
|
+
</li>
|
|
14
|
+
))}
|
|
15
|
+
</ul>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default ColumnList
|