@cdc/chart 4.24.11 → 4.24.12-2
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 +32134 -32039
- 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/ehdi.json +29939 -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 +180 -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,54 +407,47 @@ 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 (visualizationType === 'Area Chart') return true
|
|
416
|
-
if (visualizationType === 'Line') return true
|
|
417
|
-
if (visualizationType === 'Bar') return true
|
|
418
|
-
return false
|
|
419
|
-
}
|
|
410
|
+
useEffect(() => {
|
|
411
|
+
if (lastMaxValue.current === maxValue) return
|
|
412
|
+
lastMaxValue.current = maxValue
|
|
420
413
|
|
|
421
|
-
|
|
422
|
-
|
|
414
|
+
if (!yAxisAutoPadding) return
|
|
415
|
+
setYAxisAutoPadding(0)
|
|
416
|
+
}, [maxValue])
|
|
417
|
+
useEffect(() => {
|
|
418
|
+
if (orientation === 'horizontal') return
|
|
423
419
|
|
|
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
|
-
}
|
|
420
|
+
const maxValueIsGreaterThanTopGridLine = maxValue > Math.max(...yScale.ticks(handleNumTicks))
|
|
429
421
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
422
|
+
if (!maxValueIsGreaterThanTopGridLine || !labelsOverflow) return
|
|
423
|
+
const ticks = yScale.ticks(handleNumTicks)
|
|
424
|
+
const tickGap = ticks.length === 1 ? ticks[0] : ticks[1] - ticks[0]
|
|
425
|
+
const nextTick = Math.max(...yScale.ticks(handleNumTicks)) + tickGap
|
|
426
|
+
const divideBy = minValue < 0 ? maxValue / 2 : maxValue
|
|
427
|
+
const calculatedPadding = (nextTick - maxValue) / divideBy
|
|
437
428
|
|
|
438
|
-
|
|
439
|
-
const
|
|
440
|
-
const
|
|
441
|
-
|
|
429
|
+
// if auto padding is too close to next tick, add one more ticks worth of padding
|
|
430
|
+
const PADDING_THRESHOLD = 0.025
|
|
431
|
+
const newPadding =
|
|
432
|
+
calculatedPadding > PADDING_THRESHOLD ? calculatedPadding : calculatedPadding + tickGap / divideBy
|
|
442
433
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
}
|
|
434
|
+
/* sometimes even though the padding is getting to the next tick exactly,
|
|
435
|
+
d3 still doesn't show the tick. we add 0.1 to ensure to tip it over the edge */
|
|
436
|
+
setYAxisAutoPadding(newPadding * 100 + 0.1)
|
|
437
|
+
}, [maxValue, labelsOverflow, yScale, handleNumTicks])
|
|
448
438
|
|
|
439
|
+
// Render Functions
|
|
449
440
|
const generatePairedBarAxis = () => {
|
|
450
441
|
const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
|
|
451
442
|
|
|
452
443
|
const getTickPositions = (ticks, xScale) => {
|
|
453
444
|
if (!ticks.length) return false
|
|
454
|
-
//
|
|
445
|
+
// filter out first index
|
|
455
446
|
const filteredTicks = ticks.filter(tick => tick.index !== 0)
|
|
456
447
|
const numberOfTicks = filteredTicks?.length
|
|
457
448
|
const xMaxHalf = xScale.range()[0] || xMax / 2
|
|
458
449
|
const tickWidthAll = filteredTicks.map(tick =>
|
|
459
|
-
getTextWidth(formatNumber(tick.value, 'left'), `normal ${
|
|
450
|
+
getTextWidth(formatNumber(tick.value, 'left'), `normal ${fontSizes[config.fontSize]}px sans-serif`)
|
|
460
451
|
)
|
|
461
452
|
const accumulator = 100
|
|
462
453
|
const sumOfTickWidth = tickWidthAll.reduce((a, b) => a + b, accumulator)
|
|
@@ -495,7 +486,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
495
486
|
{props.ticks.map((tick, i) => {
|
|
496
487
|
const textWidth = getTextWidth(
|
|
497
488
|
formatNumber(tick.value, 'left'),
|
|
498
|
-
`normal ${
|
|
489
|
+
`normal ${fontSizes[config.fontSize]}px sans-serif`
|
|
499
490
|
)
|
|
500
491
|
const isTicksOverlapping = getTickPositions(props.ticks, g1xScale)
|
|
501
492
|
const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
|
|
@@ -547,7 +538,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
547
538
|
{props.ticks.map((tick, i) => {
|
|
548
539
|
const textWidth = getTextWidth(
|
|
549
540
|
formatNumber(tick.value, 'left'),
|
|
550
|
-
`normal ${
|
|
541
|
+
`normal ${fontSizes[config.fontSize]}px sans-serif`
|
|
551
542
|
)
|
|
552
543
|
const isTicksOverlapping = getTickPositions(props.ticks, g2xScale)
|
|
553
544
|
const maxTickRotation = Number(config.xAxis.maxTickRotation) || 90
|
|
@@ -608,7 +599,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
608
599
|
ref={svgRef}
|
|
609
600
|
onMouseMove={onMouseMove}
|
|
610
601
|
width={parentWidth}
|
|
611
|
-
height={parentHeight}
|
|
602
|
+
height={isNoDataAvailable ? 1 : parentHeight}
|
|
612
603
|
className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${
|
|
613
604
|
debugSvg && 'debug'
|
|
614
605
|
} ${isDraggingAnnotation && 'dragging-annotation'}`}
|
|
@@ -623,7 +614,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
623
614
|
<AxisLeft
|
|
624
615
|
scale={yScale}
|
|
625
616
|
left={Number(runtime.yAxis.size) - config.yAxis.axisPadding}
|
|
626
|
-
numTicks={handleNumTicks
|
|
617
|
+
numTicks={handleNumTicks}
|
|
627
618
|
>
|
|
628
619
|
{props => {
|
|
629
620
|
const axisCenter =
|
|
@@ -785,20 +776,6 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
785
776
|
isBrush={false}
|
|
786
777
|
/>
|
|
787
778
|
)}
|
|
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
779
|
{visualizationType === 'Forest Plot' && (
|
|
803
780
|
<ForestPlot
|
|
804
781
|
xScale={xScale}
|
|
@@ -821,16 +798,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
821
798
|
/>
|
|
822
799
|
)}
|
|
823
800
|
{/*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
|
-
)}
|
|
801
|
+
{config.brush.active && config.xAxis.type !== 'categorical' && <BrushChart xMax={xMax} yMax={yMax} />}
|
|
834
802
|
{/* Line chart */}
|
|
835
803
|
{/* TODO: Make this just line or combo? */}
|
|
836
804
|
{!['Paired Bar', 'Box Plot', 'Area Chart', 'Scatter Plot', 'Deviation Bar', 'Forecasting', 'Bar'].includes(
|
|
@@ -852,14 +820,19 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
852
820
|
{/* y anchors */}
|
|
853
821
|
{config.yAxis.anchors &&
|
|
854
822
|
config.yAxis.anchors.map((anchor, index) => {
|
|
855
|
-
let
|
|
856
|
-
|
|
857
|
-
|
|
823
|
+
let position = yScale(anchor.value)
|
|
824
|
+
let middleOffset = 0
|
|
825
|
+
|
|
858
826
|
if (!anchor.value) return
|
|
859
|
-
|
|
860
|
-
|
|
827
|
+
if (config.yAxis.labelPlacement === 'Below Bar') {
|
|
828
|
+
const textOffset = -6.5
|
|
829
|
+
middleOffset = textOffset + Number(config.series.length * config.barHeight) / config.series.length
|
|
830
|
+
} else {
|
|
831
|
+
const paddingOffset = 8
|
|
832
|
+
middleOffset = paddingOffset
|
|
833
|
+
}
|
|
861
834
|
|
|
862
|
-
if (!
|
|
835
|
+
if (!position) return
|
|
863
836
|
|
|
864
837
|
return (
|
|
865
838
|
// prettier-ignore
|
|
@@ -868,8 +841,8 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
868
841
|
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
869
842
|
stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
870
843
|
className='anchor-y'
|
|
871
|
-
from={{ x: 0 + padding, y:
|
|
872
|
-
to={{ x: width - config.yAxis.rightAxisSize, y:
|
|
844
|
+
from={{ x: 0 + padding, y: position - middleOffset}}
|
|
845
|
+
to={{ x: width - config.yAxis.rightAxisSize, y: position - middleOffset }}
|
|
873
846
|
/>
|
|
874
847
|
)
|
|
875
848
|
})}
|
|
@@ -881,10 +854,19 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
881
854
|
newX = yAxis
|
|
882
855
|
}
|
|
883
856
|
|
|
884
|
-
|
|
857
|
+
const getAnchorPosition = (): number | undefined => {
|
|
858
|
+
let position: number | undefined
|
|
885
859
|
|
|
886
|
-
|
|
887
|
-
|
|
860
|
+
position = isDateScale(newX) ? xScale(parseDate(anchor.value, false)) : xScale(anchor.value)
|
|
861
|
+
if (config.xAxis.type === 'categorical' || config.xAxis.type === 'date') {
|
|
862
|
+
position = position
|
|
863
|
+
? position + (newX.type === 'categorical' || newX.type === 'date' ? xScale.bandwidth() : 0) / 2
|
|
864
|
+
: 0
|
|
865
|
+
}
|
|
866
|
+
return position
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
let anchorPosition = getAnchorPosition()
|
|
888
870
|
|
|
889
871
|
if (!anchorPosition) return
|
|
890
872
|
|
|
@@ -946,7 +928,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
946
928
|
/>
|
|
947
929
|
</Group>
|
|
948
930
|
)}
|
|
949
|
-
{
|
|
931
|
+
{isNoDataAvailable && (
|
|
950
932
|
<Text
|
|
951
933
|
x={Number(config.yAxis.size) + Number(xMax / 2)}
|
|
952
934
|
y={initialHeight / 2 - (config.xAxis.padding || 0) / 2}
|
|
@@ -1009,7 +991,7 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1009
991
|
label={runtime.yAxis.label || runtime.yAxis.label}
|
|
1010
992
|
stroke='#333'
|
|
1011
993
|
tickFormat={handleLeftTickFormatting}
|
|
1012
|
-
numTicks={handleNumTicks
|
|
994
|
+
numTicks={handleNumTicks}
|
|
1013
995
|
>
|
|
1014
996
|
{props => {
|
|
1015
997
|
const axisCenter =
|
|
@@ -1352,43 +1334,63 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1352
1334
|
tickFormat={handleBottomTickFormatting}
|
|
1353
1335
|
scale={xScale}
|
|
1354
1336
|
stroke='#333'
|
|
1355
|
-
numTicks={
|
|
1337
|
+
numTicks={useDateSpanMonths ? dateSpanMonths : xTickCount}
|
|
1356
1338
|
tickStroke='#333'
|
|
1357
1339
|
tickValues={
|
|
1358
1340
|
config.xAxis.manual
|
|
1359
|
-
? getTickValues(
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
config.xAxis.type === 'date-time' ? countNumOfTicks('xAxis') : getManualStep(),
|
|
1363
|
-
config
|
|
1364
|
-
)
|
|
1341
|
+
? getTickValues(xAxisDataMapped, xScale, isDateTime ? xTickCount : getManualStep(), config)
|
|
1342
|
+
: config.xAxis.type === 'date'
|
|
1343
|
+
? xAxisDataMapped
|
|
1365
1344
|
: undefined
|
|
1366
1345
|
}
|
|
1367
1346
|
>
|
|
1368
1347
|
{props => {
|
|
1348
|
+
// For these charts, we generated all ticks in tickValues above, and now need to filter/shift them
|
|
1349
|
+
// so the last tick is always labeled
|
|
1350
|
+
if (config.xAxis.type === 'date' && !config.xAxis.manual) {
|
|
1351
|
+
props.ticks = filterAndShiftLinearDateTicks(config, props, xAxisDataMapped, formatDate)
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
const distanceBetweenTicks =
|
|
1355
|
+
useDateSpanMonths &&
|
|
1356
|
+
xScale
|
|
1357
|
+
.ticks(xTickCount)
|
|
1358
|
+
.map(t => props.ticks.findIndex(tick => tick.value.getTime() === t.getTime()))
|
|
1359
|
+
.slice(0, 2)
|
|
1360
|
+
.reduce((acc, curr) => curr - acc)
|
|
1361
|
+
|
|
1362
|
+
// filter out every [distanceBetweenTicks] tick starting from the end, so the last tick is always labeled
|
|
1363
|
+
const filteredTicks = useDateSpanMonths
|
|
1364
|
+
? [...props.ticks]
|
|
1365
|
+
.reverse()
|
|
1366
|
+
.filter((_, i) => i % distanceBetweenTicks === 0)
|
|
1367
|
+
.reverse()
|
|
1368
|
+
.map((tick, i, arr) => ({
|
|
1369
|
+
...tick,
|
|
1370
|
+
// reformat in case showYearsOnce, since first month of year may have changed
|
|
1371
|
+
formattedValue: handleBottomTickFormatting(tick.value, i, arr)
|
|
1372
|
+
}))
|
|
1373
|
+
: props.ticks
|
|
1374
|
+
|
|
1369
1375
|
const axisMaxHeight = bottomLabelStart + BOTTOM_LABEL_PADDING
|
|
1370
1376
|
|
|
1371
|
-
const axisCenter =
|
|
1372
|
-
config.visualizationType !== 'Forest Plot'
|
|
1373
|
-
? (props.axisToPoint.x - props.axisFromPoint.x) / 2
|
|
1374
|
-
: dimensions[0] / 2
|
|
1375
1377
|
const containsMultipleWords = inputString => /\s/.test(inputString)
|
|
1376
|
-
const ismultiLabel =
|
|
1378
|
+
const ismultiLabel = filteredTicks.some(tick => containsMultipleWords(tick.value))
|
|
1377
1379
|
|
|
1378
1380
|
// Calculate sumOfTickWidth here, before map function
|
|
1379
1381
|
const tickWidthMax = Math.max(
|
|
1380
|
-
...
|
|
1381
|
-
getTextWidth(tick.formattedValue, `normal ${
|
|
1382
|
+
...filteredTicks.map(tick =>
|
|
1383
|
+
getTextWidth(tick.formattedValue, `normal ${fontSizes[config.fontSize]}px sans-serif`)
|
|
1382
1384
|
)
|
|
1383
1385
|
)
|
|
1384
1386
|
// const marginTop = 20 // moved to top bc need for yMax calcs
|
|
1385
1387
|
const accumulator = ismultiLabel ? 180 : 100
|
|
1386
1388
|
|
|
1387
|
-
const textWidths =
|
|
1388
|
-
getTextWidth(tick.formattedValue, `normal ${
|
|
1389
|
+
const textWidths = filteredTicks.map(tick =>
|
|
1390
|
+
getTextWidth(tick.formattedValue, `normal ${fontSizes[config.fontSize]}px sans-serif`)
|
|
1389
1391
|
)
|
|
1390
1392
|
const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
|
|
1391
|
-
const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (
|
|
1393
|
+
const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (filteredTicks.length - 1)
|
|
1392
1394
|
|
|
1393
1395
|
// Check if ticks are overlapping
|
|
1394
1396
|
// Determine the position of each tick
|
|
@@ -1416,6 +1418,16 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1416
1418
|
areTicksTouching = true
|
|
1417
1419
|
}
|
|
1418
1420
|
|
|
1421
|
+
// force wrap it last tick is close to the end of the axis
|
|
1422
|
+
const lastTickWidth = textWidths[textWidths.length - 1]
|
|
1423
|
+
const lastTickPosition = positions[positions.length - 1] + lastTickWidth
|
|
1424
|
+
const lastTickEnd = lastTickPosition + lastTickWidth / 2
|
|
1425
|
+
const lastTickEndThreshold = xMax - lastTickWidth
|
|
1426
|
+
|
|
1427
|
+
if (lastTickEnd > lastTickEndThreshold) {
|
|
1428
|
+
areTicksTouching = true
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1419
1431
|
const dynamicMarginTop =
|
|
1420
1432
|
areTicksTouching && config.isResponsiveTicks ? tickWidthMax + DEFAULT_TICK_LENGTH + 20 : 0
|
|
1421
1433
|
|
|
@@ -1424,15 +1436,11 @@ const LinearChart = forwardRef<SVGAElement, LinearChartProps>(({ parentHeight, p
|
|
|
1424
1436
|
|
|
1425
1437
|
return (
|
|
1426
1438
|
<Group className='bottom-axis' width={dimensions[0]}>
|
|
1427
|
-
{
|
|
1439
|
+
{filteredTicks.map((tick, i, propsTicks) => {
|
|
1428
1440
|
// when using LogScale show major ticks values only
|
|
1429
1441
|
const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
|
|
1430
1442
|
const tickLength = showTick === 'block' ? 16 : DEFAULT_TICK_LENGTH
|
|
1431
1443
|
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
1444
|
const limitedWidth = 100 / propsTicks.length
|
|
1437
1445
|
//reset rotations by updating config
|
|
1438
1446
|
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
|