@cdc/chart 4.24.11 → 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 +31248 -31198
- package/examples/feature/sankey/sankey-example-data.json +126 -13
- 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 +448 -20047
- package/index.html +9 -6
- package/package.json +2 -2
- package/src/CdcChart.tsx +62 -82
- package/src/_stories/Chart.Anchors.stories.tsx +31 -0
- package/src/_stories/Chart.DynamicSeries.stories.tsx +8 -1
- package/src/_stories/Chart.stories.tsx +32 -0
- package/src/_stories/ChartAxisLabels.stories.tsx +4 -1
- package/{examples/feature/area/area-chart-date-city-temperature.json → src/_stories/_mock/area_chart_stacked.json} +125 -27
- package/src/_stories/_mock/line_chart_dynamic_ci.json +493 -0
- package/src/_stories/_mock/line_chart_non_dynamic_ci.json +522 -0
- package/src/_stories/_mock/short_dates.json +288 -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.Vertical.tsx +28 -40
- package/src/components/BarChart/helpers/getBarData.ts +28 -0
- package/src/components/BarChart/helpers/tests/getBarData.test.ts +74 -0
- package/src/components/BoxPlot/BoxPlot.tsx +12 -70
- package/src/components/BoxPlot/helpers/index.ts +54 -0
- package/src/components/BrushChart.tsx +23 -26
- package/src/components/EditorPanel/EditorPanel.tsx +55 -79
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +1 -0
- package/src/components/EditorPanel/useEditorPermissions.ts +5 -1
- package/src/components/Legend/Legend.Component.tsx +2 -2
- package/src/{hooks/useLegendClasses.ts → components/Legend/helpers/getLegendClasses.ts} +5 -5
- 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 +1 -0
- package/src/components/LineChart/index.tsx +47 -1
- package/src/components/LinearChart.tsx +171 -172
- 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 -510
- package/src/components/Sankey/sankey.scss +16 -16
- package/src/components/Sankey/types/index.ts +1 -1
- package/src/data/initial-state.js +4 -3
- package/src/helpers/countNumOfTicks.ts +57 -0
- package/src/helpers/getQuartiles.ts +15 -18
- package/src/hooks/useMinMax.ts +18 -4
- package/src/hooks/useScales.ts +38 -4
- package/src/hooks/useTooltip.tsx +5 -1
- package/src/scss/DataTable.scss +5 -0
- package/src/scss/main.scss +6 -2
|
@@ -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,6 +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)
|
|
116
|
+
const lastMaxValue = useRef(maxValue)
|
|
112
117
|
|
|
113
118
|
const dataRef = useIntersectionObserver(triggerRef, {
|
|
114
119
|
freezeOnceVisible: false
|
|
@@ -119,8 +124,10 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
119
124
|
const isHorizontal = orientation === 'horizontal' || config.visualizationType === 'Forest Plot'
|
|
120
125
|
const isLogarithmicAxis = config.yAxis.type === 'logarithmic'
|
|
121
126
|
const isForestPlot = visualizationType === 'Forest Plot'
|
|
127
|
+
const isDateTime = config.xAxis.type === 'date-time'
|
|
122
128
|
const suffixHasNoSpace = !suffix.includes(' ')
|
|
123
|
-
|
|
129
|
+
const labelsOverflow = onlyShowTopPrefixSuffix && !suffixHasNoSpace
|
|
130
|
+
const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
|
|
124
131
|
const yLabelOffset = isNaN(parseInt(`${runtime.yAxis.labelOffset}`)) ? 0 : parseInt(`${runtime.yAxis.labelOffset}`)
|
|
125
132
|
|
|
126
133
|
// zero if not forest plot
|
|
@@ -169,11 +176,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
169
176
|
const xMax = width - runtime.yAxis.size - (visualizationType === 'Combo' ? config.yAxis.rightAxisSize : 0)
|
|
170
177
|
const yMax = initialHeight + forestRowsHeight
|
|
171
178
|
|
|
172
|
-
const
|
|
173
|
-
return isConvertLineToBarGraph(config.visualizationType, data, config.allowLineToBarGraph)
|
|
174
|
-
}
|
|
179
|
+
const isNoDataAvailable = config.filters && config.filters.values.length === 0 && data.length === 0
|
|
175
180
|
|
|
176
|
-
// GETTERS & FUNCTIONS
|
|
177
181
|
const getXAxisData = d =>
|
|
178
182
|
isDateScale(config.runtime.xAxis)
|
|
179
183
|
? parseDate(d[config.runtime.originalXAxis.dataKey]).getTime()
|
|
@@ -187,7 +191,14 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
187
191
|
const properties = {
|
|
188
192
|
data,
|
|
189
193
|
tableData,
|
|
190
|
-
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
|
+
},
|
|
191
202
|
minValue,
|
|
192
203
|
maxValue,
|
|
193
204
|
isAllLine,
|
|
@@ -208,6 +219,40 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
208
219
|
xMax: parentWidth - Number(config.orientation === 'horizontal' ? config.xAxis.size : config.yAxis.size)
|
|
209
220
|
})
|
|
210
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
|
+
|
|
211
256
|
const handleLeftTickFormatting = (tick, index, ticks) => {
|
|
212
257
|
if (isLogarithmicAxis && tick === 0.1) {
|
|
213
258
|
//when logarithmic scale applied change value of first tick
|
|
@@ -251,80 +296,33 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
251
296
|
return tick
|
|
252
297
|
}
|
|
253
298
|
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
if (
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
tickCount =
|
|
263
|
-
isHorizontal && !numTicks
|
|
264
|
-
? data.length
|
|
265
|
-
: isHorizontal && numTicks
|
|
266
|
-
? numTicks
|
|
267
|
-
: !isHorizontal && !numTicks
|
|
268
|
-
? undefined
|
|
269
|
-
: !isHorizontal && numTicks && numTicks
|
|
270
|
-
// to fix edge case of small numbers with decimals
|
|
271
|
-
if (tickCount === undefined && !config.dataFormat.roundTo) {
|
|
272
|
-
// then it is set to Auto
|
|
273
|
-
if (Number(max) <= 3) {
|
|
274
|
-
tickCount = 2
|
|
275
|
-
} else {
|
|
276
|
-
tickCount = 4 // same default as standalone components
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
if (Number(tickCount) > Number(max) && !isHorizontal) {
|
|
280
|
-
// cap it and round it so its an integer
|
|
281
|
-
tickCount = Number(min) < 0 ? Math.round(max) * 2 : Math.round(max)
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (axis === 'xAxis') {
|
|
286
|
-
tickCount =
|
|
287
|
-
isHorizontal && !numTicks
|
|
288
|
-
? undefined
|
|
289
|
-
: isHorizontal && numTicks
|
|
290
|
-
? numTicks
|
|
291
|
-
: !isHorizontal && !numTicks
|
|
292
|
-
? undefined
|
|
293
|
-
: !isHorizontal && numTicks && numTicks
|
|
294
|
-
if (isHorizontal && tickCount === undefined && !config.dataFormat.roundTo) {
|
|
295
|
-
// then it is set to Auto
|
|
296
|
-
// - check for small numbers situation
|
|
297
|
-
if (max <= 3) {
|
|
298
|
-
tickCount = 2
|
|
299
|
-
} else {
|
|
300
|
-
tickCount = 4 // same default as standalone components
|
|
301
|
-
}
|
|
302
|
-
}
|
|
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
|
+
}
|
|
303
307
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
308
|
+
const getManualStep = () => {
|
|
309
|
+
let manualStep = config.xAxis.manualStep
|
|
310
|
+
if (config.xAxis.viewportStepCount && config.xAxis.viewportStepCount[currentViewport]) {
|
|
311
|
+
manualStep = config.xAxis.viewportStepCount[currentViewport]
|
|
307
312
|
}
|
|
308
|
-
|
|
309
|
-
return tickCount
|
|
313
|
+
return manualStep
|
|
310
314
|
}
|
|
311
315
|
|
|
312
|
-
|
|
313
|
-
|
|
316
|
+
const onMouseMove = event => {
|
|
317
|
+
const svgRect = event.currentTarget.getBoundingClientRect()
|
|
318
|
+
const x = event.clientX - svgRect.left
|
|
319
|
+
const y = event.clientY - svgRect.top
|
|
314
320
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
TooltipListItem,
|
|
321
|
-
getXValueFromCoordinate
|
|
322
|
-
} = useCoveTooltip({
|
|
323
|
-
xScale,
|
|
324
|
-
yScale,
|
|
325
|
-
showTooltip,
|
|
326
|
-
hideTooltip
|
|
327
|
-
})
|
|
321
|
+
setPoint({
|
|
322
|
+
x,
|
|
323
|
+
y
|
|
324
|
+
})
|
|
325
|
+
}
|
|
328
326
|
|
|
329
327
|
// EFFECTS
|
|
330
328
|
|
|
@@ -409,43 +407,27 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
409
407
|
legendRef.current.style.transform = legendIsLeftOrRight ? `translateY(${topLabelOnGridlineHeight}px)` : 'none'
|
|
410
408
|
}, [axisBottomRef.current, config, bottomLabelStart, brush, currentViewport, topYLabelRef.current, initialHeight])
|
|
411
409
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
if (
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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
|
|
420
418
|
|
|
421
|
-
|
|
422
|
-
const fontSize = { small: 16, medium: 18, large: 20 }
|
|
419
|
+
const maxValueIsGreaterThanTopGridLine = maxValue > Math.max(...yScale.ticks(handleNumTicks))
|
|
423
420
|
|
|
424
|
-
|
|
425
|
-
// On forest plots we need to return every "study" or y axis value.
|
|
426
|
-
if (config.visualizationType === 'Forest Plot') return config.data.length
|
|
427
|
-
return countNumOfTicks('yAxis')
|
|
428
|
-
}
|
|
421
|
+
if (!maxValueIsGreaterThanTopGridLine || !labelsOverflow) return
|
|
429
422
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
manualStep = config.xAxis.viewportStepCount[currentViewport]
|
|
434
|
-
}
|
|
435
|
-
return manualStep
|
|
436
|
-
}
|
|
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
|
|
437
426
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
const x = event.clientX - svgRect.left
|
|
441
|
-
const y = event.clientY - svgRect.top
|
|
442
|
-
|
|
443
|
-
setPoint({
|
|
444
|
-
x,
|
|
445
|
-
y
|
|
446
|
-
})
|
|
447
|
-
}
|
|
427
|
+
setYAxisAutoPadding(newPadding * 100)
|
|
428
|
+
}, [maxValue, labelsOverflow, yScale, handleNumTicks])
|
|
448
429
|
|
|
430
|
+
// Render Functions
|
|
449
431
|
const generatePairedBarAxis = () => {
|
|
450
432
|
const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
|
|
451
433
|
|
|
@@ -456,7 +438,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
456
438
|
const numberOfTicks = filteredTicks?.length
|
|
457
439
|
const xMaxHalf = xScale.range()[0] || xMax / 2
|
|
458
440
|
const tickWidthAll = filteredTicks.map(tick =>
|
|
459
|
-
getTextWidth(formatNumber(tick.value, 'left'), `normal ${
|
|
441
|
+
getTextWidth(formatNumber(tick.value, 'left'), `normal ${fontSizes[config.fontSize]}px sans-serif`)
|
|
460
442
|
)
|
|
461
443
|
const accumulator = 100
|
|
462
444
|
const sumOfTickWidth = tickWidthAll.reduce((a, b) => a + b, accumulator)
|
|
@@ -495,7 +477,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
495
477
|
{props.ticks.map((tick, i) => {
|
|
496
478
|
const textWidth = getTextWidth(
|
|
497
479
|
formatNumber(tick.value, 'left'),
|
|
498
|
-
`normal ${
|
|
480
|
+
`normal ${fontSizes[config.fontSize]}px sans-serif`
|
|
499
481
|
)
|
|
500
482
|
const isTicksOverlapping = getTickPositions(props.ticks, g1xScale)
|
|
501
483
|
const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
|
|
@@ -547,7 +529,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
547
529
|
{props.ticks.map((tick, i) => {
|
|
548
530
|
const textWidth = getTextWidth(
|
|
549
531
|
formatNumber(tick.value, 'left'),
|
|
550
|
-
`normal ${
|
|
532
|
+
`normal ${fontSizes[config.fontSize]}px sans-serif`
|
|
551
533
|
)
|
|
552
534
|
const isTicksOverlapping = getTickPositions(props.ticks, g2xScale)
|
|
553
535
|
const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
|
|
@@ -608,7 +590,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
608
590
|
ref={svgRef}
|
|
609
591
|
onMouseMove={onMouseMove}
|
|
610
592
|
width={parentWidth}
|
|
611
|
-
height={parentHeight}
|
|
593
|
+
height={isNoDataAvailable ? 1 : parentHeight}
|
|
612
594
|
className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${
|
|
613
595
|
debugSvg && 'debug'
|
|
614
596
|
} ${isDraggingAnnotation && 'dragging-annotation'}`}
|
|
@@ -623,7 +605,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
623
605
|
<AxisLeft
|
|
624
606
|
scale={yScale}
|
|
625
607
|
left={Number(runtime.yAxis.size) - config.yAxis.axisPadding}
|
|
626
|
-
numTicks={handleNumTicks
|
|
608
|
+
numTicks={handleNumTicks}
|
|
627
609
|
>
|
|
628
610
|
{props => {
|
|
629
611
|
const axisCenter =
|
|
@@ -785,20 +767,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
785
767
|
isBrush={false}
|
|
786
768
|
/>
|
|
787
769
|
)}
|
|
788
|
-
{/* y anchors */}
|
|
789
|
-
{config.yAxis.anchors &&
|
|
790
|
-
config.yAxis.anchors.map(anchor => {
|
|
791
|
-
return (
|
|
792
|
-
<Line
|
|
793
|
-
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
794
|
-
stroke='rgba(0,0,0,1)'
|
|
795
|
-
className='customAnchor'
|
|
796
|
-
from={{ x: 0 + config.yAxis.size, y: yScale(anchor.value) }}
|
|
797
|
-
to={{ x: xMax, y: yScale(anchor.value) }}
|
|
798
|
-
display={runtime.horizontal ? 'none' : 'block'}
|
|
799
|
-
/>
|
|
800
|
-
)
|
|
801
|
-
})}
|
|
802
770
|
{visualizationType === 'Forest Plot' && (
|
|
803
771
|
<ForestPlot
|
|
804
772
|
xScale={xScale}
|
|
@@ -821,16 +789,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
821
789
|
/>
|
|
822
790
|
)}
|
|
823
791
|
{/*Brush chart */}
|
|
824
|
-
{config.brush.active && config.xAxis.type !== 'categorical' &&
|
|
825
|
-
<BrushChart
|
|
826
|
-
xScaleBrush={xScaleBrush}
|
|
827
|
-
yScale={yScale}
|
|
828
|
-
xMax={xMax}
|
|
829
|
-
yMax={yMax}
|
|
830
|
-
xScale={xScale}
|
|
831
|
-
seriesScale={seriesScale}
|
|
832
|
-
/>
|
|
833
|
-
)}
|
|
792
|
+
{config.brush.active && config.xAxis.type !== 'categorical' && <BrushChart xMax={xMax} yMax={yMax} />}
|
|
834
793
|
{/* Line chart */}
|
|
835
794
|
{/* TODO: Make this just line or combo? */}
|
|
836
795
|
{!['Paired Bar', 'Box Plot', 'Area Chart', 'Scatter Plot', 'Deviation Bar', 'Forecasting', 'Bar'].includes(
|
|
@@ -852,14 +811,19 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
852
811
|
{/* y anchors */}
|
|
853
812
|
{config.yAxis.anchors &&
|
|
854
813
|
config.yAxis.anchors.map((anchor, index) => {
|
|
855
|
-
let
|
|
856
|
-
|
|
857
|
-
|
|
814
|
+
let position = yScale(anchor.value)
|
|
815
|
+
let middleOffset = 0
|
|
816
|
+
|
|
858
817
|
if (!anchor.value) return
|
|
859
|
-
|
|
860
|
-
|
|
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
|
+
}
|
|
861
825
|
|
|
862
|
-
if (!
|
|
826
|
+
if (!position) return
|
|
863
827
|
|
|
864
828
|
return (
|
|
865
829
|
// prettier-ignore
|
|
@@ -868,8 +832,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
868
832
|
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
869
833
|
stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
870
834
|
className='anchor-y'
|
|
871
|
-
from={{ x: 0 + padding, y:
|
|
872
|
-
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 }}
|
|
873
837
|
/>
|
|
874
838
|
)
|
|
875
839
|
})}
|
|
@@ -881,10 +845,19 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
881
845
|
newX = yAxis
|
|
882
846
|
}
|
|
883
847
|
|
|
884
|
-
|
|
848
|
+
const getAnchorPosition = (): number | undefined => {
|
|
849
|
+
let position: number | undefined
|
|
885
850
|
|
|
886
|
-
|
|
887
|
-
|
|
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()
|
|
888
861
|
|
|
889
862
|
if (!anchorPosition) return
|
|
890
863
|
|
|
@@ -946,7 +919,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
946
919
|
/>
|
|
947
920
|
</Group>
|
|
948
921
|
)}
|
|
949
|
-
{
|
|
922
|
+
{isNoDataAvailable && (
|
|
950
923
|
<Text
|
|
951
924
|
x={Number(config.yAxis.size) + Number(xMax / 2)}
|
|
952
925
|
y={initialHeight / 2 - (config.xAxis.padding || 0) / 2}
|
|
@@ -1009,7 +982,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1009
982
|
label={runtime.yAxis.label || runtime.yAxis.label}
|
|
1010
983
|
stroke='#333'
|
|
1011
984
|
tickFormat={handleLeftTickFormatting}
|
|
1012
|
-
numTicks={handleNumTicks
|
|
985
|
+
numTicks={handleNumTicks}
|
|
1013
986
|
>
|
|
1014
987
|
{props => {
|
|
1015
988
|
const axisCenter =
|
|
@@ -1352,43 +1325,63 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1352
1325
|
tickFormat={handleBottomTickFormatting}
|
|
1353
1326
|
scale={xScale}
|
|
1354
1327
|
stroke='#333'
|
|
1355
|
-
numTicks={
|
|
1328
|
+
numTicks={useDateSpanMonths ? dateSpanMonths : xTickCount}
|
|
1356
1329
|
tickStroke='#333'
|
|
1357
1330
|
tickValues={
|
|
1358
1331
|
config.xAxis.manual
|
|
1359
|
-
? getTickValues(
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
config.xAxis.type === 'date-time' ? countNumOfTicks('xAxis') : getManualStep(),
|
|
1363
|
-
config
|
|
1364
|
-
)
|
|
1332
|
+
? getTickValues(xAxisDataMapped, xScale, isDateTime ? xTickCount : getManualStep(), config)
|
|
1333
|
+
: config.xAxis.type === 'date'
|
|
1334
|
+
? xAxisDataMapped
|
|
1365
1335
|
: undefined
|
|
1366
1336
|
}
|
|
1367
1337
|
>
|
|
1368
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
|
+
|
|
1369
1366
|
const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
|
|
1370
1367
|
|
|
1371
|
-
const axisCenter =
|
|
1372
|
-
config.visualizationType !== 'Forest Plot'
|
|
1373
|
-
? (props.axisToPoint.x - props.axisFromPoint.x) / 2
|
|
1374
|
-
: dimensions[0] / 2
|
|
1375
1368
|
const containsMultipleWords = inputString => /\s/.test(inputString)
|
|
1376
|
-
const ismultiLabel =
|
|
1369
|
+
const ismultiLabel = filteredTicks.some(tick => containsMultipleWords(tick.value))
|
|
1377
1370
|
|
|
1378
1371
|
// Calculate sumOfTickWidth here, before map function
|
|
1379
1372
|
const tickWidthMax = Math.max(
|
|
1380
|
-
...
|
|
1381
|
-
getTextWidth(tick.formattedValue, `normal ${
|
|
1373
|
+
...filteredTicks.map(tick =>
|
|
1374
|
+
getTextWidth(tick.formattedValue, `normal ${fontSizes[config.fontSize]}px sans-serif`)
|
|
1382
1375
|
)
|
|
1383
1376
|
)
|
|
1384
1377
|
// const marginTop = 20 // moved to top bc need for yMax calcs
|
|
1385
1378
|
const accumulator = ismultiLabel ? 180 : 100
|
|
1386
1379
|
|
|
1387
|
-
const textWidths =
|
|
1388
|
-
getTextWidth(tick.formattedValue, `normal ${
|
|
1380
|
+
const textWidths = filteredTicks.map(tick =>
|
|
1381
|
+
getTextWidth(tick.formattedValue, `normal ${fontSizes[config.fontSize]}px sans-serif`)
|
|
1389
1382
|
)
|
|
1390
1383
|
const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
|
|
1391
|
-
const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (
|
|
1384
|
+
const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (filteredTicks.length - 1)
|
|
1392
1385
|
|
|
1393
1386
|
// Check if ticks are overlapping
|
|
1394
1387
|
// Determine the position of each tick
|
|
@@ -1416,6 +1409,16 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1416
1409
|
areTicksTouching = true
|
|
1417
1410
|
}
|
|
1418
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
|
+
|
|
1419
1422
|
const dynamicMarginTop =
|
|
1420
1423
|
areTicksTouching && config.isResponsiveTicks ? tickWidthMax + DEFAULT_TICK_LENGTH + 20 : 0
|
|
1421
1424
|
|
|
@@ -1424,15 +1427,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1424
1427
|
|
|
1425
1428
|
return (
|
|
1426
1429
|
<Group className='bottom-axis' width={dimensions[0]}>
|
|
1427
|
-
{
|
|
1430
|
+
{filteredTicks.map((tick, i, propsTicks) => {
|
|
1428
1431
|
// when using LogScale show major ticks values only
|
|
1429
1432
|
const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
|
|
1430
1433
|
const tickLength = showTick === 'block' ? 16 : DEFAULT_TICK_LENGTH
|
|
1431
1434
|
const to = { x: tick.to.x, y: tickLength }
|
|
1432
|
-
const textWidth = getTextWidth(
|
|
1433
|
-
tick.formattedValue,
|
|
1434
|
-
`normal ${fontSize[config.fontSize]}px sans-serif`
|
|
1435
|
-
)
|
|
1436
1435
|
const limitedWidth = 100 / propsTicks.length
|
|
1437
1436
|
//reset rotations by updating config
|
|
1438
1437
|
config.yAxis.tickRotation =
|
|
@@ -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
|