@cdc/chart 4.23.9 → 4.23.10-alpha
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/LICENSE +201 -0
- package/dist/cdcchart.js +44124 -44458
- package/examples/feature/__data__/area-chart-date-apple.json +1 -5073
- package/examples/feature/area/area-chart-date-apple.json +73 -10316
- package/examples/feature/area/area-chart-date-city-temperature.json +204 -80
- package/examples/feature/area/area-chart-stacked.json +239 -0
- package/examples/feature/bar/lollipop.json +156 -0
- package/examples/feature/combo/planet-combo-example-config.json +99 -9
- package/examples/feature/filters/bar-filter.json +5027 -0
- package/examples/feature/legend-highlights/highlights.json +567 -0
- package/examples/private/TESTING.json +0 -0
- package/examples/private/forest-plot.json +356 -0
- package/examples/private/full.json +45324 -0
- package/examples/private/missing-color.json +333 -0
- package/index.html +11 -7
- package/package.json +3 -2
- package/src/{CdcChart.jsx → CdcChart.tsx} +81 -74
- package/src/_stories/Chart.stories.tsx +188 -0
- package/src/components/AreaChart.Stacked.jsx +73 -0
- package/src/components/AreaChart.jsx +24 -26
- package/src/components/DeviationBar.jsx +67 -13
- package/src/components/EditorPanel.jsx +483 -452
- package/src/components/Forecasting.jsx +5 -5
- package/src/components/ForestPlotSettings.jsx +5 -6
- package/src/components/Legend.jsx +7 -6
- package/src/components/LineChart.Circle.tsx +102 -0
- package/src/components/{LineChart.jsx → LineChart.tsx} +9 -48
- package/src/components/LinearChart.jsx +460 -443
- package/src/components/PieChart.jsx +54 -25
- package/src/components/Series.jsx +63 -17
- package/src/components/SparkLine.jsx +7 -19
- package/src/data/initial-state.js +6 -0
- package/src/hooks/useBarChart.js +1 -1
- package/src/hooks/useEditorPermissions.js +87 -24
- package/src/hooks/useReduceData.js +5 -0
- package/src/hooks/useScales.js +3 -3
- package/src/hooks/useTooltip.jsx +19 -6
- package/src/scss/main.scss +6 -12
- package/src/components/DataTable.jsx +0 -494
- /package/src/{components → hooks}/useIntersectionObserver.jsx +0 -0
|
@@ -6,10 +6,11 @@ import { Group } from '@visx/group'
|
|
|
6
6
|
import { Line, Bar } from '@visx/shape'
|
|
7
7
|
import { Text } from '@visx/text'
|
|
8
8
|
import { Tooltip as ReactTooltip } from 'react-tooltip'
|
|
9
|
-
import { useTooltip, TooltipWithBounds } from '@visx/tooltip'
|
|
9
|
+
import { useTooltip, TooltipWithBounds, useTooltipInPortal } from '@visx/tooltip'
|
|
10
10
|
|
|
11
11
|
// CDC Components
|
|
12
12
|
import AreaChart from './AreaChart'
|
|
13
|
+
import AreaChartStacked from './AreaChart.Stacked'
|
|
13
14
|
import BarChart from './BarChart'
|
|
14
15
|
import ConfigContext from '../ConfigContext'
|
|
15
16
|
import CoveBoxPlot from './BoxPlot'
|
|
@@ -20,7 +21,7 @@ import Forecasting from './Forecasting'
|
|
|
20
21
|
import LineChart from './LineChart'
|
|
21
22
|
import ForestPlot from './ForestPlot'
|
|
22
23
|
import PairedBarChart from './PairedBarChart'
|
|
23
|
-
import useIntersectionObserver from '
|
|
24
|
+
import useIntersectionObserver from './../hooks/useIntersectionObserver'
|
|
24
25
|
|
|
25
26
|
// Hooks
|
|
26
27
|
import useMinMax from '../hooks/useMinMax'
|
|
@@ -33,7 +34,7 @@ import { useTooltip as useCoveTooltip } from '../hooks/useTooltip'
|
|
|
33
34
|
// styles
|
|
34
35
|
import '../scss/LinearChart.scss'
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
const LinearChart = props => {
|
|
37
38
|
const { isEditor, isDashboard, transformedData: data, dimensions, config, parseDate, formatDate, currentViewport, formatNumber, handleChartAriaLabels, updateConfig, handleLineType, rawData, capitalize, setSharedFilter, setSharedFilterValue, getTextWidth, isDebug } = useContext(ConfigContext)
|
|
38
39
|
|
|
39
40
|
// todo: start destructuring this file for conciseness
|
|
@@ -151,7 +152,7 @@ export default function LinearChart() {
|
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
// Tooltip Helpers
|
|
154
|
-
const { tooltipData, showTooltip, hideTooltip, tooltipOpen } = useTooltip()
|
|
155
|
+
const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip()
|
|
155
156
|
|
|
156
157
|
// prettier-ignore
|
|
157
158
|
const {
|
|
@@ -209,303 +210,272 @@ export default function LinearChart() {
|
|
|
209
210
|
<React.Fragment></React.Fragment>
|
|
210
211
|
) : (
|
|
211
212
|
<ErrorBoundary component='LinearChart'>
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
{config.
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
213
|
+
{/* ! Notice - div needed for tooltip boundaries (flip/flop) */}
|
|
214
|
+
<div style={{ width: `${width}px`, height: `${height}px`, overflow: 'visible' }} className='tooltip-boundary'>
|
|
215
|
+
<svg width={'100%'} height={'100%'} className={`linear ${config.animate ? 'animated' : ''} ${animatedChart && config.animate ? 'animate' : ''} ${debugSvg && 'debug'}`} role='img' aria-label={handleChartAriaLabels(config)} ref={svgRef} style={{ overflow: 'visible' }}>
|
|
216
|
+
<Bar width={width} height={height} fill={'transparent'}></Bar> {/* Highlighted regions */}
|
|
217
|
+
{config.regions
|
|
218
|
+
? config.regions.map(region => {
|
|
219
|
+
if (!Object.keys(region).includes('from') || !Object.keys(region).includes('to')) return null
|
|
220
|
+
|
|
221
|
+
let from
|
|
222
|
+
let to
|
|
223
|
+
let width
|
|
224
|
+
|
|
225
|
+
if (config.xAxis.type === 'date') {
|
|
226
|
+
from = xScale(parseDate(region.from).getTime())
|
|
227
|
+
to = xScale(parseDate(region.to).getTime())
|
|
228
|
+
width = to - from
|
|
229
|
+
}
|
|
227
230
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
if (config.xAxis.type === 'categorical') {
|
|
232
|
+
from = xScale(region.from)
|
|
233
|
+
to = xScale(region.to)
|
|
234
|
+
width = to - from
|
|
235
|
+
}
|
|
233
236
|
|
|
234
|
-
|
|
235
|
-
|
|
237
|
+
if (!from) return null
|
|
238
|
+
if (!to) return null
|
|
236
239
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
240
|
+
return (
|
|
241
|
+
<Group className='regions' left={Number(runtime.yAxis.size)} key={region.label}>
|
|
242
|
+
<path
|
|
243
|
+
stroke='#333'
|
|
244
|
+
d={`M${from} -5
|
|
242
245
|
L${from} 5
|
|
243
246
|
M${from} 0
|
|
244
247
|
L${to} 0
|
|
245
248
|
M${to} -5
|
|
246
249
|
L${to} 5`}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
})
|
|
255
|
-
: ''}
|
|
256
|
-
{/* Y axis */}
|
|
257
|
-
{!['Spark Line', 'Forest Plot'].includes(visualizationType) && (
|
|
258
|
-
<AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(runtime.yAxis.size) - config.yAxis.axisPadding} label={runtime.yAxis.label} stroke='#333' tickFormat={(tick, i) => handleLeftTickFormatting(tick, i)} numTicks={handleNumTicks()}>
|
|
259
|
-
{props => {
|
|
260
|
-
const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
261
|
-
const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
|
|
262
|
-
return (
|
|
263
|
-
<Group className='left-axis'>
|
|
264
|
-
{props.ticks.map((tick, i) => {
|
|
265
|
-
const minY = props.ticks[0].to.y
|
|
266
|
-
const barMinHeight = 15 // 15 is the min height for bars by default
|
|
267
|
-
const showTicks = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
|
|
268
|
-
const tickLength = showTicks === 'block' ? 7 : 0
|
|
269
|
-
const to = { x: tick.to.x - tickLength, y: tick.to.y }
|
|
270
|
-
|
|
271
|
-
return (
|
|
272
|
-
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
273
|
-
{!runtime.yAxis.hideTicks && <Line key={`${tick.value}--hide-hideTicks`} from={tick.from} to={config.useLogScale ? to : tick.to} stroke={config.yAxis.tickColor} display={runtime.horizontal ? 'none' : 'block'} />}
|
|
274
|
-
|
|
275
|
-
{runtime.yAxis.gridLines ? <Line key={`${tick.value}--hide-hideGridLines`} display={(config.useLogScale && showTicks).toString()} from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
|
|
276
|
-
|
|
277
|
-
{orientation === 'horizontal' && visualizationSubType !== 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
|
|
278
|
-
<Text
|
|
279
|
-
transform={`translate(${tick.to.x - 5}, ${config.isLollipopChart ? tick.to.y - minY : tick.to.y - minY + (Number(config.barHeight * config.series.length) - barMinHeight) / 2}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation || 0 : 0})`}
|
|
280
|
-
verticalAnchor={'start'}
|
|
281
|
-
textAnchor={'end'}
|
|
282
|
-
>
|
|
283
|
-
{tick.formattedValue}
|
|
284
|
-
</Text>
|
|
285
|
-
)}
|
|
286
|
-
|
|
287
|
-
{orientation === 'horizontal' && visualizationSubType === 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
|
|
288
|
-
<Text transform={`translate(${tick.to.x - 5}, ${tick.to.y - minY + (Number(config.barHeight) - barMinHeight) / 2}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`} verticalAnchor={'start'} textAnchor={'end'}>
|
|
289
|
-
{tick.formattedValue}
|
|
290
|
-
</Text>
|
|
291
|
-
)}
|
|
292
|
-
|
|
293
|
-
{orientation === 'horizontal' && visualizationType === 'Paired Bar' && !config.yAxis.hideLabel && (
|
|
294
|
-
<Text transform={`translate(${tick.to.x - 5}, ${tick.to.y - minY + Number(config.barHeight) / 2}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`} textAnchor={'end'} verticalAnchor='middle'>
|
|
295
|
-
{tick.formattedValue}
|
|
296
|
-
</Text>
|
|
297
|
-
)}
|
|
298
|
-
{orientation === 'horizontal' && visualizationType === 'Deviation Bar' && !config.yAxis.hideLabel && (
|
|
299
|
-
<Text transform={`translate(${tick.to.x - 5}, ${config.isLollipopChart ? tick.to.y - minY + 2 : tick.to.y - minY + Number(config.barHeight) / 2}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`} textAnchor={'end'} verticalAnchor='middle'>
|
|
300
|
-
{tick.formattedValue}
|
|
301
|
-
</Text>
|
|
302
|
-
)}
|
|
303
|
-
|
|
304
|
-
{orientation === 'vertical' && visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
|
|
305
|
-
<Text
|
|
306
|
-
display={config.useLogScale ? showTicks : 'block'}
|
|
307
|
-
dx={config.useLogScale ? -6 : 0}
|
|
308
|
-
x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x}
|
|
309
|
-
y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)}
|
|
310
|
-
angle={-Number(config.yAxis.tickRotation) || 0}
|
|
311
|
-
verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
|
|
312
|
-
textAnchor={config.runtime.horizontal ? 'start' : 'end'}
|
|
313
|
-
fill={config.yAxis.tickLabelColor}
|
|
314
|
-
>
|
|
315
|
-
{tick.formattedValue}
|
|
316
|
-
</Text>
|
|
317
|
-
)}
|
|
318
|
-
</Group>
|
|
319
|
-
)
|
|
320
|
-
})}
|
|
321
|
-
{!config.yAxis.hideAxis && <Line from={props.axisFromPoint} to={runtime.horizontal ? { x: 0, y: config.visualizationType === 'Forest Plot' ? height : Number(heightHorizontal) } : props.axisToPoint} stroke='#000' />}
|
|
322
|
-
{yScale.domain()[0] < 0 && <Line from={{ x: props.axisFromPoint.x, y: yScale(0) }} to={{ x: xMax, y: yScale(0) }} stroke='#333' />}
|
|
323
|
-
{visualizationType === 'Bar' && orientation === 'horizontal' && xScale.domain()[0] < 0 && <Line from={{ x: xScale(0), y: 0 }} to={{ x: xScale(0), y: yMax }} stroke='#333' strokeWidth={2} />}
|
|
324
|
-
<Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${-1 * runtime.yAxis.size}, ${axisCenter}) rotate(-90)`} fontWeight='bold' fill={config.yAxis.labelColor}>
|
|
325
|
-
{props.label}
|
|
326
|
-
</Text>
|
|
327
|
-
</Group>
|
|
328
|
-
)
|
|
329
|
-
}}
|
|
330
|
-
</AxisLeft>
|
|
331
|
-
)}
|
|
332
|
-
{/* Right Axis */}
|
|
333
|
-
{hasRightAxis && (
|
|
334
|
-
<AxisRight scale={yScaleRight} left={Number(width - config.yAxis.rightAxisSize)} label={config.yAxis.rightLabel} tickFormat={tick => formatNumber(tick, 'right')} numTicks={runtime.yAxis.rightNumTicks || undefined} labelOffset={45}>
|
|
335
|
-
{props => {
|
|
336
|
-
const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
337
|
-
const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
|
|
338
|
-
return (
|
|
339
|
-
<Group className='right-axis'>
|
|
340
|
-
{props.ticks.map((tick, i) => {
|
|
341
|
-
return (
|
|
342
|
-
<Group key={`vx-tick-${tick.value}-${i}`} className='vx-axis-tick'>
|
|
343
|
-
{!runtime.yAxis.rightHideTicks && <Line from={tick.from} to={tick.to} display={runtime.horizontal ? 'none' : 'block'} stroke={config.yAxis.rightAxisTickColor} />}
|
|
344
|
-
|
|
345
|
-
{runtime.yAxis.rightGridLines ? <Line from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
|
|
346
|
-
|
|
347
|
-
{!config.yAxis.rightHideLabel && (
|
|
348
|
-
<Text x={tick.to.x} y={tick.to.y + (runtime.horizontal ? horizontalTickOffset : 0)} verticalAnchor={runtime.horizontal ? 'start' : 'middle'} textAnchor={'start'} fill={config.yAxis.rightAxisTickLabelColor}>
|
|
349
|
-
{tick.formattedValue}
|
|
350
|
-
</Text>
|
|
351
|
-
)}
|
|
352
|
-
</Group>
|
|
353
|
-
)
|
|
354
|
-
})}
|
|
355
|
-
{!config.yAxis.rightHideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
356
|
-
<Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${config.yAxis.rightLabelOffsetSize ? config.yAxis.rightLabelOffsetSize : 0}, ${axisCenter}) rotate(-90)`} fontWeight='bold' fill={config.yAxis.rightAxisLabelColor}>
|
|
357
|
-
{props.label}
|
|
358
|
-
</Text>
|
|
359
|
-
</Group>
|
|
360
|
-
)
|
|
361
|
-
}}
|
|
362
|
-
</AxisRight>
|
|
363
|
-
)}
|
|
364
|
-
{hasTopAxis && config.topAxis.hasLine && (
|
|
365
|
-
<AxisTop
|
|
366
|
-
stroke='#333'
|
|
367
|
-
left={Number(runtime.yAxis.size)}
|
|
368
|
-
scale={xScale}
|
|
369
|
-
hideTicks
|
|
370
|
-
hideZero
|
|
371
|
-
tickLabelProps={() => ({
|
|
372
|
-
fill: 'transparent'
|
|
373
|
-
})}
|
|
374
|
-
/>
|
|
375
|
-
)}
|
|
376
|
-
{/* X axis */}
|
|
377
|
-
{visualizationType !== 'Paired Bar' && visualizationType !== 'Spark Line' && (
|
|
378
|
-
<AxisBottom
|
|
379
|
-
top={runtime.horizontal && config.visualizationType !== 'Forest Plot' ? Number(heightHorizontal) + Number(config.xAxis.axisPadding) : config.visualizationType === 'Forest Plot' ? yMax + Number(config.xAxis.axisPadding) : yMax + Number(config.xAxis.axisPadding)}
|
|
380
|
-
left={Number(runtime.yAxis.size)}
|
|
381
|
-
label={runtime.xAxis.label}
|
|
382
|
-
tickFormat={handleBottomTickFormatting}
|
|
383
|
-
scale={xScale}
|
|
384
|
-
stroke='#333'
|
|
385
|
-
numTicks={countNumOfTicks('xAxis')}
|
|
386
|
-
tickStroke='#333'
|
|
387
|
-
>
|
|
388
|
-
{props => {
|
|
389
|
-
const axisCenter = config.visualizationType !== 'Forest Plot' ? (props.axisToPoint.x - props.axisFromPoint.x) / 2 : width / 2
|
|
390
|
-
const containsMultipleWords = inputString => /\s/.test(inputString)
|
|
391
|
-
const ismultiLabel = props.ticks.some(tick => containsMultipleWords(tick.value))
|
|
392
|
-
|
|
393
|
-
// Calculate sumOfTickWidth here, before map function
|
|
394
|
-
const fontSize = { small: 16, medium: 18, large: 20 }
|
|
395
|
-
const defaultTickLength = 8
|
|
396
|
-
const tickWidthMax = Math.max(...props.ticks.map(tick => getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)))
|
|
397
|
-
// const marginTop = 20 // moved to top bc need for yMax calcs
|
|
398
|
-
const accumulator = ismultiLabel ? 180 : 100
|
|
399
|
-
|
|
400
|
-
const textWidths = props.ticks.map(tick => getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`))
|
|
401
|
-
const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
|
|
402
|
-
const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (props.ticks.length - 1)
|
|
403
|
-
|
|
404
|
-
// Check if ticks are overlapping
|
|
405
|
-
// Determine the position of each tick
|
|
406
|
-
let positions = [0] // The first tick is at position 0
|
|
407
|
-
for (let i = 1; i < textWidths.length; i++) {
|
|
408
|
-
// The position of each subsequent tick is the position of the previous tick
|
|
409
|
-
// plus the width of the previous tick and the space
|
|
410
|
-
positions[i] = positions[i - 1] + textWidths[i - 1] + spaceBetweenEachTick
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Check if ticks are overlapping
|
|
414
|
-
let areTicksTouching = false
|
|
415
|
-
textWidths.forEach((_, i) => {
|
|
416
|
-
if (positions[i] + textWidths[i] > positions[i + 1]) {
|
|
417
|
-
areTicksTouching = true
|
|
418
|
-
return
|
|
419
|
-
}
|
|
250
|
+
/>
|
|
251
|
+
<rect x={from} y={0} width={width} height={yMax} fill={region.background} opacity={0.3} />
|
|
252
|
+
<Text x={from + width / 2} y={5} fill={region.color} verticalAnchor='start' textAnchor='middle'>
|
|
253
|
+
{region.label}
|
|
254
|
+
</Text>
|
|
255
|
+
</Group>
|
|
256
|
+
)
|
|
420
257
|
})
|
|
258
|
+
: ''}
|
|
259
|
+
{/* Y axis */}
|
|
260
|
+
{!['Spark Line', 'Forest Plot'].includes(visualizationType) && (
|
|
261
|
+
<AxisLeft scale={yScale} tickLength={config.useLogScale ? 6 : 8} left={Number(runtime.yAxis.size) - config.yAxis.axisPadding} label={runtime.yAxis.label} stroke='#333' tickFormat={(tick, i) => handleLeftTickFormatting(tick, i)} numTicks={handleNumTicks()}>
|
|
262
|
+
{props => {
|
|
263
|
+
const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
264
|
+
const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
|
|
265
|
+
return (
|
|
266
|
+
<Group className='left-axis'>
|
|
267
|
+
{props.ticks.map((tick, i) => {
|
|
268
|
+
const minY = props.ticks[0].to.y
|
|
269
|
+
const barMinHeight = 15 // 15 is the min height for bars by default
|
|
270
|
+
const showTicks = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
|
|
271
|
+
const tickLength = showTicks === 'block' ? 7 : 0
|
|
272
|
+
const to = { x: tick.to.x - tickLength, y: tick.to.y }
|
|
421
273
|
|
|
422
|
-
|
|
423
|
-
|
|
274
|
+
return (
|
|
275
|
+
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
276
|
+
{!runtime.yAxis.hideTicks && <Line key={`${tick.value}--hide-hideTicks`} from={tick.from} to={config.useLogScale ? to : tick.to} stroke={config.yAxis.tickColor} display={runtime.horizontal ? 'none' : 'block'} />}
|
|
424
277
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
278
|
+
{runtime.yAxis.gridLines ? <Line key={`${tick.value}--hide-hideGridLines`} display={(config.useLogScale && showTicks).toString()} from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
|
|
279
|
+
|
|
280
|
+
{orientation === 'horizontal' && visualizationSubType !== 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
|
|
281
|
+
<Text
|
|
282
|
+
transform={`translate(${tick.to.x - 5}, ${config.isLollipopChart ? tick.to.y - minY : tick.to.y - minY + (Number(config.barHeight * config.series.length) - barMinHeight) / 2}) rotate(-${config.runtime.horizontal ? config.runtime.yAxis.tickRotation || 0 : 0})`}
|
|
283
|
+
verticalAnchor={'start'}
|
|
284
|
+
textAnchor={'end'}
|
|
285
|
+
>
|
|
286
|
+
{tick.formattedValue}
|
|
287
|
+
</Text>
|
|
288
|
+
)}
|
|
289
|
+
|
|
290
|
+
{orientation === 'horizontal' && visualizationSubType === 'stacked' && config.yAxis.labelPlacement === 'On Date/Category Axis' && !config.yAxis.hideLabel && (
|
|
291
|
+
<Text transform={`translate(${tick.to.x - 5}, ${tick.to.y - minY + (Number(config.barHeight) - barMinHeight) / 2}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`} verticalAnchor={'start'} textAnchor={'end'}>
|
|
292
|
+
{tick.formattedValue}
|
|
293
|
+
</Text>
|
|
294
|
+
)}
|
|
295
|
+
|
|
296
|
+
{orientation === 'horizontal' && visualizationType === 'Paired Bar' && !config.yAxis.hideLabel && (
|
|
297
|
+
<Text transform={`translate(${tick.to.x - 5}, ${tick.to.y - minY + Number(config.barHeight) / 2}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`} textAnchor={'end'} verticalAnchor='middle'>
|
|
298
|
+
{tick.formattedValue}
|
|
299
|
+
</Text>
|
|
300
|
+
)}
|
|
301
|
+
{orientation === 'horizontal' && visualizationType === 'Deviation Bar' && !config.yAxis.hideLabel && (
|
|
302
|
+
<Text transform={`translate(${tick.to.x - 5}, ${config.isLollipopChart ? tick.to.y - minY + 2 : tick.to.y - minY + Number(config.barHeight) / 2}) rotate(-${runtime.horizontal ? runtime.yAxis.tickRotation : 0})`} textAnchor={'end'} verticalAnchor='middle'>
|
|
303
|
+
{tick.formattedValue}
|
|
304
|
+
</Text>
|
|
305
|
+
)}
|
|
306
|
+
|
|
307
|
+
{orientation === 'vertical' && visualizationType !== 'Paired Bar' && !config.yAxis.hideLabel && (
|
|
308
|
+
<Text
|
|
309
|
+
display={config.useLogScale ? showTicks : 'block'}
|
|
310
|
+
dx={config.useLogScale ? -6 : 0}
|
|
311
|
+
x={config.runtime.horizontal ? tick.from.x + 2 : tick.to.x}
|
|
312
|
+
y={tick.to.y + (config.runtime.horizontal ? horizontalTickOffset : 0)}
|
|
313
|
+
angle={-Number(config.yAxis.tickRotation) || 0}
|
|
314
|
+
verticalAnchor={config.runtime.horizontal ? 'start' : 'middle'}
|
|
315
|
+
textAnchor={config.runtime.horizontal ? 'start' : 'end'}
|
|
316
|
+
fill={config.yAxis.tickLabelColor}
|
|
317
|
+
>
|
|
318
|
+
{tick.formattedValue}
|
|
319
|
+
</Text>
|
|
320
|
+
)}
|
|
321
|
+
</Group>
|
|
322
|
+
)
|
|
323
|
+
})}
|
|
324
|
+
{!config.yAxis.hideAxis && <Line from={props.axisFromPoint} to={runtime.horizontal ? { x: 0, y: config.visualizationType === 'Forest Plot' ? height : Number(heightHorizontal) } : props.axisToPoint} stroke='#000' />}
|
|
325
|
+
{yScale.domain()[0] < 0 && <Line from={{ x: props.axisFromPoint.x, y: yScale(0) }} to={{ x: xMax, y: yScale(0) }} stroke='#333' />}
|
|
326
|
+
{visualizationType === 'Bar' && orientation === 'horizontal' && xScale.domain()[0] < 0 && <Line from={{ x: xScale(0), y: 0 }} to={{ x: xScale(0), y: yMax }} stroke='#333' strokeWidth={2} />}
|
|
327
|
+
<Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${-1 * runtime.yAxis.size}, ${axisCenter}) rotate(-90)`} fontWeight='bold' fill={config.yAxis.labelColor}>
|
|
328
|
+
{props.label}
|
|
329
|
+
</Text>
|
|
330
|
+
</Group>
|
|
331
|
+
)
|
|
332
|
+
}}
|
|
333
|
+
</AxisLeft>
|
|
334
|
+
)}
|
|
335
|
+
{/* Right Axis */}
|
|
336
|
+
{hasRightAxis && (
|
|
337
|
+
<AxisRight scale={yScaleRight} left={Number(width - config.yAxis.rightAxisSize)} label={config.yAxis.rightLabel} tickFormat={tick => formatNumber(tick, 'right')} numTicks={runtime.yAxis.rightNumTicks || undefined} labelOffset={45}>
|
|
474
338
|
{props => {
|
|
339
|
+
const axisCenter = runtime.horizontal ? (props.axisToPoint.y - props.axisFromPoint.y) / 2 : (props.axisFromPoint.y - props.axisToPoint.y) / 2
|
|
340
|
+
const horizontalTickOffset = yMax / props.ticks.length / 2 - (yMax / props.ticks.length) * (1 - config.barThickness) + 5
|
|
475
341
|
return (
|
|
476
|
-
<Group className='
|
|
342
|
+
<Group className='right-axis'>
|
|
477
343
|
{props.ticks.map((tick, i) => {
|
|
478
|
-
const angle = tick.index !== 0 ? config.yAxis.tickRotation : 0
|
|
479
|
-
const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
|
|
480
344
|
return (
|
|
481
|
-
<Group key={`vx-tick-${tick.value}-${i}`} className=
|
|
482
|
-
{!runtime.yAxis.
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
345
|
+
<Group key={`vx-tick-${tick.value}-${i}`} className='vx-axis-tick'>
|
|
346
|
+
{!runtime.yAxis.rightHideTicks && <Line from={tick.from} to={tick.to} display={runtime.horizontal ? 'none' : 'block'} stroke={config.yAxis.rightAxisTickColor} />}
|
|
347
|
+
|
|
348
|
+
{runtime.yAxis.rightGridLines ? <Line from={{ x: tick.from.x + xMax, y: tick.from.y }} to={tick.from} stroke='rgba(0,0,0,0.3)' /> : ''}
|
|
349
|
+
|
|
350
|
+
{!config.yAxis.rightHideLabel && (
|
|
351
|
+
<Text x={tick.to.x} y={tick.to.y + (runtime.horizontal ? horizontalTickOffset : 0)} verticalAnchor={runtime.horizontal ? 'start' : 'middle'} textAnchor={'start'} fill={config.yAxis.rightAxisTickLabelColor}>
|
|
352
|
+
{tick.formattedValue}
|
|
486
353
|
</Text>
|
|
487
354
|
)}
|
|
488
355
|
</Group>
|
|
489
356
|
)
|
|
490
357
|
})}
|
|
491
|
-
{!
|
|
358
|
+
{!config.yAxis.rightHideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
359
|
+
<Text className='y-label' textAnchor='middle' verticalAnchor='start' transform={`translate(${config.yAxis.rightLabelOffsetSize ? config.yAxis.rightLabelOffsetSize : 0}, ${axisCenter}) rotate(-90)`} fontWeight='bold' fill={config.yAxis.rightAxisLabelColor}>
|
|
360
|
+
{props.label}
|
|
361
|
+
</Text>
|
|
492
362
|
</Group>
|
|
493
363
|
)
|
|
494
364
|
}}
|
|
495
|
-
</
|
|
365
|
+
</AxisRight>
|
|
366
|
+
)}
|
|
367
|
+
{hasTopAxis && config.topAxis.hasLine && (
|
|
368
|
+
<AxisTop
|
|
369
|
+
stroke='#333'
|
|
370
|
+
left={Number(runtime.yAxis.size)}
|
|
371
|
+
scale={xScale}
|
|
372
|
+
hideTicks
|
|
373
|
+
hideZero
|
|
374
|
+
tickLabelProps={() => ({
|
|
375
|
+
fill: 'transparent'
|
|
376
|
+
})}
|
|
377
|
+
/>
|
|
378
|
+
)}
|
|
379
|
+
{/* X axis */}
|
|
380
|
+
{visualizationType !== 'Paired Bar' && visualizationType !== 'Spark Line' && (
|
|
496
381
|
<AxisBottom
|
|
497
|
-
top={yMax}
|
|
382
|
+
top={runtime.horizontal && config.visualizationType !== 'Forest Plot' ? Number(heightHorizontal) + Number(config.xAxis.axisPadding) : config.visualizationType === 'Forest Plot' ? yMax + Number(config.xAxis.axisPadding) : yMax + Number(config.xAxis.axisPadding)}
|
|
498
383
|
left={Number(runtime.yAxis.size)}
|
|
499
384
|
label={runtime.xAxis.label}
|
|
500
|
-
tickFormat={
|
|
501
|
-
scale={
|
|
385
|
+
tickFormat={handleBottomTickFormatting}
|
|
386
|
+
scale={xScale}
|
|
502
387
|
stroke='#333'
|
|
388
|
+
numTicks={countNumOfTicks('xAxis')}
|
|
503
389
|
tickStroke='#333'
|
|
504
|
-
numTicks={runtime.xAxis.numTicks || undefined}
|
|
505
390
|
>
|
|
506
391
|
{props => {
|
|
392
|
+
const axisCenter = config.visualizationType !== 'Forest Plot' ? (props.axisToPoint.x - props.axisFromPoint.x) / 2 : width / 2
|
|
393
|
+
const containsMultipleWords = inputString => /\s/.test(inputString)
|
|
394
|
+
const ismultiLabel = props.ticks.some(tick => containsMultipleWords(tick.value))
|
|
395
|
+
|
|
396
|
+
// Calculate sumOfTickWidth here, before map function
|
|
397
|
+
const fontSize = { small: 16, medium: 18, large: 20 }
|
|
398
|
+
const defaultTickLength = 8
|
|
399
|
+
const tickWidthMax = Math.max(...props.ticks.map(tick => getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)))
|
|
400
|
+
// const marginTop = 20 // moved to top bc need for yMax calcs
|
|
401
|
+
const accumulator = ismultiLabel ? 180 : 100
|
|
402
|
+
|
|
403
|
+
const textWidths = props.ticks.map(tick => getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`))
|
|
404
|
+
const sumOfTickWidth = textWidths.reduce((a, b) => a + b, accumulator)
|
|
405
|
+
const spaceBetweenEachTick = (xMax - sumOfTickWidth) / (props.ticks.length - 1)
|
|
406
|
+
|
|
407
|
+
// Check if ticks are overlapping
|
|
408
|
+
// Determine the position of each tick
|
|
409
|
+
let positions = [0] // The first tick is at position 0
|
|
410
|
+
for (let i = 1; i < textWidths.length; i++) {
|
|
411
|
+
// The position of each subsequent tick is the position of the previous tick
|
|
412
|
+
// plus the width of the previous tick and the space
|
|
413
|
+
positions[i] = positions[i - 1] + textWidths[i - 1] + spaceBetweenEachTick
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Check if ticks are overlapping
|
|
417
|
+
let areTicksTouching = false
|
|
418
|
+
textWidths.forEach((_, i) => {
|
|
419
|
+
if (positions[i] + textWidths[i] > positions[i + 1]) {
|
|
420
|
+
areTicksTouching = true
|
|
421
|
+
return
|
|
422
|
+
}
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
dynamicMarginTop = areTicksTouching && config.isResponsiveTicks ? tickWidthMax + defaultTickLength + marginTop : 0
|
|
426
|
+
config.dynamicMarginTop = dynamicMarginTop
|
|
427
|
+
|
|
507
428
|
return (
|
|
508
|
-
|
|
429
|
+
<Group className='bottom-axis'>
|
|
430
|
+
{props.ticks.map((tick, i, propsTicks) => {
|
|
431
|
+
// when using LogScale show major ticks values only
|
|
432
|
+
const showTick = String(tick.value).startsWith('1') || tick.value === 0.1 ? 'block' : 'none'
|
|
433
|
+
const tickLength = showTick === 'block' ? 16 : defaultTickLength
|
|
434
|
+
const to = { x: tick.to.x, y: tickLength }
|
|
435
|
+
let textWidth = getTextWidth(tick.formattedValue, `normal ${fontSize[config.fontSize]}px sans-serif`)
|
|
436
|
+
let limitedWidth = 100 / propsTicks.length
|
|
437
|
+
//reset rotations by updating config
|
|
438
|
+
config.yAxis.tickRotation = config.isResponsiveTicks && config.orientation === 'horizontal' ? 0 : config.yAxis.tickRotation
|
|
439
|
+
config.xAxis.tickRotation = config.isResponsiveTicks && config.orientation === 'vertical' ? 0 : config.xAxis.tickRotation
|
|
440
|
+
//configure rotation
|
|
441
|
+
|
|
442
|
+
const tickRotation = config.isResponsiveTicks && areTicksTouching ? -Number(config.xAxis.maxTickRotation) || -90 : -Number(config.runtime.xAxis.tickRotation)
|
|
443
|
+
|
|
444
|
+
return (
|
|
445
|
+
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
446
|
+
{!config.xAxis.hideTicks && <Line from={tick.from} to={orientation === 'horizontal' && config.useLogScale ? to : tick.to} stroke={config.xAxis.tickColor} strokeWidth={showTick === 'block' ? 1.3 : 1} />}
|
|
447
|
+
{!config.xAxis.hideLabel && (
|
|
448
|
+
<Text
|
|
449
|
+
dy={config.orientation === 'horizontal' && config.useLogScale ? 8 : 0}
|
|
450
|
+
display={config.orientation === 'horizontal' && config.useLogScale ? showTick : 'block'}
|
|
451
|
+
x={tick.to.x}
|
|
452
|
+
y={tick.to.y}
|
|
453
|
+
angle={tickRotation}
|
|
454
|
+
verticalAnchor={tickRotation < -50 ? 'middle' : 'start'}
|
|
455
|
+
textAnchor={tickRotation ? 'end' : 'middle'}
|
|
456
|
+
width={areTicksTouching && !config.isResponsiveTicks && !Number(config[section].tickRotation) ? limitedWidth : textWidth}
|
|
457
|
+
fill={config.xAxis.tickLabelColor}
|
|
458
|
+
>
|
|
459
|
+
{tick.formattedValue}
|
|
460
|
+
</Text>
|
|
461
|
+
)}
|
|
462
|
+
</Group>
|
|
463
|
+
)
|
|
464
|
+
})}
|
|
465
|
+
{!config.xAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
466
|
+
<Text x={axisCenter} y={config.orientation === 'horizontal' ? dynamicMarginTop || config.xAxis.labelOffset : dynamicMarginTop || config.xAxis.size} textAnchor='middle' fontWeight='bold' fill={config.xAxis.labelColor}>
|
|
467
|
+
{props.label}
|
|
468
|
+
</Text>
|
|
469
|
+
</Group>
|
|
470
|
+
)
|
|
471
|
+
}}
|
|
472
|
+
</AxisBottom>
|
|
473
|
+
)}
|
|
474
|
+
{visualizationType === 'Paired Bar' && (
|
|
475
|
+
<>
|
|
476
|
+
<AxisBottom top={yMax} left={Number(runtime.yAxis.size)} label={runtime.xAxis.label} tickFormat={runtime.xAxis.type === 'date' ? formatDate : formatNumber} scale={g1xScale} stroke='#333' tickStroke='#333' numTicks={runtime.xAxis.numTicks || undefined}>
|
|
477
|
+
{props => {
|
|
478
|
+
return (
|
|
509
479
|
<Group className='bottom-axis'>
|
|
510
480
|
{props.ticks.map((tick, i) => {
|
|
511
481
|
const angle = tick.index !== 0 ? config.yAxis.tickRotation : 0
|
|
@@ -523,139 +493,175 @@ export default function LinearChart() {
|
|
|
523
493
|
})}
|
|
524
494
|
{!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
525
495
|
</Group>
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
tooltipData={tooltipData}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
496
|
+
)
|
|
497
|
+
}}
|
|
498
|
+
</AxisBottom>
|
|
499
|
+
<AxisBottom
|
|
500
|
+
top={yMax}
|
|
501
|
+
left={Number(runtime.yAxis.size)}
|
|
502
|
+
label={runtime.xAxis.label}
|
|
503
|
+
tickFormat={runtime.xAxis.type === 'date' ? formatDate : runtime.xAxis.dataKey !== 'Year' ? formatNumber : tick => tick}
|
|
504
|
+
scale={g2xScale}
|
|
505
|
+
stroke='#333'
|
|
506
|
+
tickStroke='#333'
|
|
507
|
+
numTicks={runtime.xAxis.numTicks || undefined}
|
|
508
|
+
>
|
|
509
|
+
{props => {
|
|
510
|
+
return (
|
|
511
|
+
<>
|
|
512
|
+
<Group className='bottom-axis'>
|
|
513
|
+
{props.ticks.map((tick, i) => {
|
|
514
|
+
const angle = tick.index !== 0 ? config.yAxis.tickRotation : 0
|
|
515
|
+
const textAnchor = tick.index !== 0 && config.yAxis.tickRotation && config.yAxis.tickRotation > 0 ? 'end' : 'middle'
|
|
516
|
+
return (
|
|
517
|
+
<Group key={`vx-tick-${tick.value}-${i}`} className={'vx-axis-tick'}>
|
|
518
|
+
{!runtime.yAxis.hideTicks && <Line from={tick.from} to={tick.to} stroke='#333' />}
|
|
519
|
+
{!runtime.yAxis.hideLabel && (
|
|
520
|
+
<Text x={tick.to.x} y={tick.to.y} angle={-angle} verticalAnchor='start' textAnchor={textAnchor}>
|
|
521
|
+
{formatNumber(tick.value, 'left')}
|
|
522
|
+
</Text>
|
|
523
|
+
)}
|
|
524
|
+
</Group>
|
|
525
|
+
)
|
|
526
|
+
})}
|
|
527
|
+
{!runtime.yAxis.hideAxis && <Line from={props.axisFromPoint} to={props.axisToPoint} stroke='#333' />}
|
|
528
|
+
</Group>
|
|
529
|
+
<Group>
|
|
530
|
+
<Text x={xMax / 2} y={config.xAxis.labelOffset} stroke='#333' textAnchor={'middle'} verticalAnchor='start'>
|
|
531
|
+
{runtime.xAxis.label}
|
|
532
|
+
</Text>
|
|
533
|
+
</Group>
|
|
534
|
+
</>
|
|
535
|
+
)
|
|
536
|
+
}}
|
|
537
|
+
</AxisBottom>
|
|
538
|
+
</>
|
|
539
|
+
)}
|
|
540
|
+
{visualizationType === 'Deviation Bar' && <DeviationBar animatedChart={animatedChart} xScale={xScale} yScale={yScale} width={xMax} height={yMax} />}
|
|
541
|
+
{visualizationType === 'Paired Bar' && <PairedBarChart originalWidth={width} width={xMax} height={yMax} />}
|
|
542
|
+
{visualizationType === 'Scatter Plot' && (
|
|
543
|
+
<ScatterPlot
|
|
544
|
+
xScale={xScale}
|
|
545
|
+
yScale={yScale}
|
|
546
|
+
getXAxisData={getXAxisData}
|
|
547
|
+
getYAxisData={getYAxisData}
|
|
548
|
+
xMax={xMax}
|
|
549
|
+
yMax={yMax}
|
|
550
|
+
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
551
|
+
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
552
|
+
handleTooltipClick={handleTooltipClick}
|
|
553
|
+
tooltipData={tooltipData}
|
|
554
|
+
showTooltip={showTooltip}
|
|
555
|
+
/>
|
|
556
|
+
)}
|
|
557
|
+
{visualizationType === 'Box Plot' && <CoveBoxPlot xScale={xScale} yScale={yScale} />}
|
|
558
|
+
{((visualizationType === 'Area Chart' && config.visualizationSubType === 'regular') || visualizationType === 'Combo') && (
|
|
559
|
+
<AreaChart xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} chartRef={svgRef} width={xMax} height={yMax} handleTooltipMouseOver={handleTooltipMouseOver} handleTooltipMouseOff={handleTooltipMouseOff} tooltipData={tooltipData} showTooltip={showTooltip} />
|
|
560
|
+
)}
|
|
561
|
+
{((visualizationType === 'Area Chart' && config.visualizationSubType === 'stacked') || visualizationType === 'Combo') && (
|
|
562
|
+
<AreaChartStacked xScale={xScale} yScale={yScale} yMax={yMax} xMax={xMax} chartRef={svgRef} width={xMax} height={yMax} handleTooltipMouseOver={handleTooltipMouseOver} handleTooltipMouseOff={handleTooltipMouseOff} tooltipData={tooltipData} showTooltip={showTooltip} />
|
|
563
|
+
)}
|
|
564
|
+
{(visualizationType === 'Bar' || visualizationType === 'Combo') && (
|
|
565
|
+
<BarChart
|
|
566
|
+
xScale={xScale}
|
|
567
|
+
yScale={yScale}
|
|
568
|
+
seriesScale={seriesScale}
|
|
569
|
+
xMax={xMax}
|
|
570
|
+
yMax={yMax}
|
|
571
|
+
getXAxisData={getXAxisData}
|
|
572
|
+
getYAxisData={getYAxisData}
|
|
573
|
+
animatedChart={animatedChart}
|
|
574
|
+
visible={animatedChart}
|
|
575
|
+
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
576
|
+
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
577
|
+
handleTooltipClick={handleTooltipClick}
|
|
578
|
+
tooltipData={tooltipData}
|
|
579
|
+
showTooltip={showTooltip}
|
|
580
|
+
chartRef={svgRef}
|
|
581
|
+
/>
|
|
582
|
+
)}
|
|
583
|
+
{(visualizationType === 'Line' || visualizationType === 'Combo') && (
|
|
584
|
+
<LineChart
|
|
585
|
+
xScale={xScale}
|
|
586
|
+
yScale={yScale}
|
|
587
|
+
getXAxisData={getXAxisData}
|
|
588
|
+
getYAxisData={getYAxisData}
|
|
589
|
+
xMax={xMax}
|
|
590
|
+
yMax={yMax}
|
|
591
|
+
seriesStyle={config.series}
|
|
592
|
+
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
593
|
+
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
594
|
+
handleTooltipClick={handleTooltipClick}
|
|
595
|
+
tooltipData={tooltipData}
|
|
596
|
+
showTooltip={showTooltip}
|
|
597
|
+
chartRef={svgRef}
|
|
598
|
+
/>
|
|
599
|
+
)}
|
|
600
|
+
{(visualizationType === 'Forecasting' || visualizationType === 'Combo') && (
|
|
601
|
+
<Forecasting
|
|
602
|
+
showTooltip={showTooltip}
|
|
603
|
+
tooltipData={tooltipData}
|
|
604
|
+
xScale={xScale}
|
|
605
|
+
yScale={yScale}
|
|
606
|
+
width={xMax}
|
|
607
|
+
le
|
|
608
|
+
height={yMax}
|
|
609
|
+
xScaleNoPadding={xScaleNoPadding}
|
|
610
|
+
chartRef={svgRef}
|
|
611
|
+
getXValueFromCoordinate={getXValueFromCoordinate}
|
|
612
|
+
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
613
|
+
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
614
|
+
isBrush={false}
|
|
615
|
+
/>
|
|
616
|
+
)}
|
|
617
|
+
{/* y anchors */}
|
|
618
|
+
{config.yAxis.anchors &&
|
|
619
|
+
config.yAxis.anchors.map(anchor => {
|
|
620
|
+
return <Line strokeDasharray={handleLineType(anchor.lineStyle)} stroke='rgba(0,0,0,1)' className='customAnchor' from={{ x: 0 + config.yAxis.size, y: yScale(anchor.value) }} to={{ x: xMax, y: yScale(anchor.value) }} display={runtime.horizontal ? 'none' : 'block'} />
|
|
621
|
+
})}
|
|
622
|
+
{visualizationType === 'Forest Plot' && (
|
|
623
|
+
<ForestPlot
|
|
624
|
+
xScale={xScale}
|
|
625
|
+
yScale={yScale}
|
|
626
|
+
seriesScale={seriesScale}
|
|
627
|
+
width={xMax}
|
|
628
|
+
height={yMax}
|
|
629
|
+
maxWidth={width}
|
|
630
|
+
maxHeight={height}
|
|
631
|
+
getXAxisData={getXAxisData}
|
|
632
|
+
getYAxisData={getYAxisData}
|
|
633
|
+
animatedChart={animatedChart}
|
|
634
|
+
visible={animatedChart}
|
|
635
|
+
handleTooltipMouseOver={handleTooltipMouseOver}
|
|
636
|
+
handleTooltipMouseOff={handleTooltipMouseOff}
|
|
637
|
+
handleTooltipClick={handleTooltipClick}
|
|
638
|
+
tooltipData={tooltipData}
|
|
639
|
+
showTooltip={showTooltip}
|
|
640
|
+
chartRef={svgRef}
|
|
641
|
+
config={config}
|
|
642
|
+
/>
|
|
643
|
+
)}
|
|
644
|
+
{/* Line chart */}
|
|
645
|
+
{/* TODO: Make this just line or combo? */}
|
|
646
|
+
{visualizationType !== 'Bar' && visualizationType !== 'Paired Bar' && visualizationType !== 'Box Plot' && visualizationType !== 'Area Chart' && visualizationType !== 'Scatter Plot' && visualizationType !== 'Deviation Bar' && visualizationType !== 'Forecasting' && (
|
|
647
|
+
<>
|
|
648
|
+
<LineChart xScale={xScale} yScale={yScale} getXAxisData={getXAxisData} getYAxisData={getYAxisData} xMax={xMax} yMax={yMax} seriesStyle={config.series} />
|
|
649
|
+
</>
|
|
650
|
+
)}
|
|
651
|
+
{/* y anchors */}
|
|
652
|
+
{config.yAxis.anchors &&
|
|
653
|
+
config.yAxis.anchors.map((anchor, index) => {
|
|
654
|
+
let anchorPosition = yScale(anchor.value)
|
|
655
|
+
// have to move up
|
|
656
|
+
// const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
|
|
657
|
+
if (!anchor.value) return
|
|
658
|
+
const middleOffset = orientation === 'horizontal' && visualizationType === 'Bar' ? config.barHeight / 4 : 0
|
|
659
|
+
|
|
660
|
+
if (!anchorPosition) return
|
|
661
|
+
|
|
662
|
+
return (
|
|
663
|
+
// prettier-ignore
|
|
664
|
+
<Line
|
|
659
665
|
key={`yAxis-${anchor.value}--${index}`}
|
|
660
666
|
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
661
667
|
stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
@@ -663,26 +669,26 @@ export default function LinearChart() {
|
|
|
663
669
|
from={{ x: 0 + padding, y: anchorPosition - middleOffset}}
|
|
664
670
|
to={{ x: width - config.yAxis.rightAxisSize, y: anchorPosition - middleOffset }}
|
|
665
671
|
/>
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
672
|
+
)
|
|
673
|
+
})}
|
|
674
|
+
{/* x anchors */}
|
|
675
|
+
{config.xAxis.anchors &&
|
|
676
|
+
config.xAxis.anchors.map((anchor, index) => {
|
|
677
|
+
let newX = xAxis
|
|
678
|
+
if (orientation === 'horizontal') {
|
|
679
|
+
newX = yAxis
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
let anchorPosition = newX.type === 'date' ? xScale(parseDate(anchor.value, false)) : xScale(anchor.value)
|
|
683
|
+
|
|
684
|
+
// have to move up
|
|
685
|
+
// const padding = orientation === 'horizontal' ? Number(config.xAxis.size) : Number(config.yAxis.size)
|
|
686
|
+
|
|
687
|
+
if (!anchorPosition) return
|
|
688
|
+
|
|
689
|
+
return (
|
|
690
|
+
// prettier-ignore
|
|
691
|
+
<Line
|
|
686
692
|
key={`xAxis-${anchor.value}--${index}`}
|
|
687
693
|
strokeDasharray={handleLineType(anchor.lineStyle)}
|
|
688
694
|
stroke={anchor.color ? anchor.color : 'rgba(0,0,0,1)'}
|
|
@@ -691,28 +697,39 @@ export default function LinearChart() {
|
|
|
691
697
|
from={{ x: Number(anchorPosition) + Number(padding), y: 0 }}
|
|
692
698
|
to={{ x: Number(anchorPosition) + Number(padding), y: yMax }}
|
|
693
699
|
/>
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
+
)
|
|
701
|
+
})}
|
|
702
|
+
{chartHasTooltipGuides && showTooltip && tooltipData && config.visual.verticalHoverLine && (
|
|
703
|
+
<Group key='tooltipLine-vertical' className='vertical-tooltip-line'>
|
|
704
|
+
<Line from={{ x: tooltipData.dataXPosition - 10, y: 0 }} to={{ x: tooltipData.dataXPosition - 10, y: yMax }} stroke={'black'} strokeWidth={1} pointerEvents='none' strokeDasharray='5,5' className='vertical-tooltip-line' />
|
|
705
|
+
</Group>
|
|
706
|
+
)}
|
|
707
|
+
{chartHasTooltipGuides && showTooltip && tooltipData && config.visual.horizontalHoverLine && (
|
|
708
|
+
<Group key='tooltipLine-horizontal' className='horizontal-tooltip-line' left={config.yAxis.size ? config.yAxis.size : 0}>
|
|
709
|
+
<Line from={{ x: 0, y: tooltipData.dataYPosition }} to={{ x: xMax, y: tooltipData.dataYPosition }} stroke={'black'} strokeWidth={1} pointerEvents='none' strokeDasharray='5,5' className='horizontal-tooltip-line' />
|
|
710
|
+
</Group>
|
|
711
|
+
)}
|
|
712
|
+
{config.filters && config.filters.values.length === 0 && data.length === 0 && (
|
|
713
|
+
<Text x={Number(config.yAxis.size) + Number(xMax / 2)} y={height / 2 - config.xAxis.size / 2} textAnchor='middle'>
|
|
714
|
+
{config.chartMessage.noData}
|
|
715
|
+
</Text>
|
|
716
|
+
)}
|
|
717
|
+
</svg>
|
|
718
|
+
{tooltipData && Object.entries(tooltipData.data).length > 0 && tooltipOpen && showTooltip && tooltipData.dataYPosition && tooltipData.dataXPosition && (
|
|
719
|
+
<>
|
|
720
|
+
<style>{`.tooltip {background-color: rgba(255,255,255, ${config.tooltips.opacity / 100}) !important`}</style>
|
|
721
|
+
<TooltipWithBounds key={Math.random()} className={'tooltip cdc-open-viz-module'} left={tooltipLeft} top={tooltipTop}>
|
|
722
|
+
<ul>{typeof tooltipData === 'object' && Object.entries(tooltipData.data).map((item, index) => <TooltipListItem item={item} key={index} />)}</ul>
|
|
723
|
+
</TooltipWithBounds>
|
|
724
|
+
</>
|
|
700
725
|
)}
|
|
701
|
-
{
|
|
702
|
-
<
|
|
703
|
-
<Line from={{ x: 0, y: tooltipData.dataYPosition }} to={{ x: xMax, y: tooltipData.dataYPosition }} stroke={'black'} strokeWidth={1} pointerEvents='none' strokeDasharray='5,5' className='horizontal-tooltip-line' />
|
|
704
|
-
</Group>
|
|
726
|
+
{(config.orientation === 'horizontal' || config.visualizationType === 'Scatter Plot' || config.visualizationType === 'Box Plot') && (
|
|
727
|
+
<ReactTooltip id={`cdc-open-viz-tooltip-${runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black' }} />
|
|
705
728
|
)}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
<TooltipWithBounds key={Math.random()} className={'tooltip cdc-open-viz-module'} style={tooltipStyles(tooltipData)} width={width}>
|
|
709
|
-
<ul>{typeof tooltipData === 'object' && Object.entries(tooltipData.data).map((item, index) => <TooltipListItem item={item} key={index} />)}</ul>
|
|
710
|
-
</TooltipWithBounds>
|
|
711
|
-
)}
|
|
712
|
-
{(config.orientation === 'horizontal' || config.visualizationType === 'Scatter Plot' || config.visualizationType === 'Box Plot') && (
|
|
713
|
-
<ReactTooltip id={`cdc-open-viz-tooltip-${runtime.uniqueId}`} variant='light' arrowColor='rgba(0,0,0,0)' className='tooltip' style={{ background: `rgba(255,255,255, ${config.tooltips.opacity / 100})`, color: 'black' }} />
|
|
714
|
-
)}
|
|
715
|
-
<div className='animation-trigger' ref={triggerRef} />
|
|
729
|
+
<div className='animation-trigger' ref={triggerRef} />
|
|
730
|
+
</div>
|
|
716
731
|
</ErrorBoundary>
|
|
717
732
|
)
|
|
718
733
|
}
|
|
734
|
+
|
|
735
|
+
export default LinearChart
|