-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
256 additions
and
144 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import React, { useCallback } from 'react'; | ||
import { | ||
Bar, | ||
BarChart, | ||
BarProps, | ||
CartesianGrid, | ||
Cell, | ||
Label, | ||
ResponsiveContainer, | ||
Tooltip, | ||
XAxis, | ||
YAxis, | ||
} from 'recharts'; | ||
import { | ||
TOOL_TIP_STYLE, | ||
Check failure on line 15 in src/Components/Charts/BaseBarChart.tsx GitHub Actions / Release
Check failure on line 15 in src/Components/Charts/BaseBarChart.tsx GitHub Actions / Release
|
||
COUNT_STYLE, | ||
LABEL_STYLE, | ||
MAX_TICK_LABEL_CHARS, | ||
TITLE_STYLE, | ||
TICKS_SHOW_ALL_LABELS_BELOW, | ||
UNITS_LABEL_OFFSET, | ||
TICK_MARGIN, | ||
COUNT_KEY, | ||
} from '../../constants/chartConstants'; | ||
|
||
import type { BaseBarChartProps, CategoricalChartDataItem, TooltipPayload } from '../../types/chartTypes'; | ||
import { useChartTranslation } from '../../ChartConfigProvider'; | ||
import NoData from '../NoData'; | ||
import { useTransformedChartData } from '../../util/chartUtils'; | ||
import ChartWrapper from './ChartWrapper'; | ||
|
||
const tickFormatter = (tickLabel: string) => { | ||
if (tickLabel.length <= MAX_TICK_LABEL_CHARS) { | ||
return tickLabel; | ||
} | ||
return `${tickLabel.substring(0, MAX_TICK_LABEL_CHARS)}...`; | ||
}; | ||
|
||
const BAR_CHART_MARGINS = { top: 10, bottom: 100, right: 20 }; | ||
|
||
const BaseBarChart: React.FC<BaseBarChartProps> = ({ | ||
height, | ||
width, | ||
units, | ||
title, | ||
onClick, | ||
chartFill, | ||
otherFill, | ||
...params | ||
}) => { | ||
const t = useChartTranslation(); | ||
|
||
const fill = (entry: CategoricalChartDataItem, index: number) => | ||
entry.x === 'missing' ? otherFill : chartFill[index % chartFill.length]; | ||
|
||
const data = useTransformedChartData(params, true); | ||
|
||
const totalCount = data.reduce((sum, e) => sum + e.y, 0); | ||
|
||
const onHover: BarProps['onMouseEnter'] = useCallback( | ||
(_data, _index, e) => { | ||
const { target } = e; | ||
if (onClick && target) (target as SVGElement).style.cursor = 'pointer'; | ||
}, | ||
[onClick] | ||
); | ||
|
||
if (data.length === 0) { | ||
return <NoData height={height} />; | ||
} | ||
|
||
// Regarding XAxis.ticks below: | ||
// The weird conditional is added from https://github.com/recharts/recharts/issues/2593#issuecomment-1311678397 | ||
// Basically, if data is empty, Recharts will default to a domain of [0, "auto"] and our tickFormatter trips up | ||
// on formatting a non-string. This hack manually overrides the ticks for the axis and blanks it out. | ||
// - David L, 2023-01-03 | ||
return ( | ||
<ChartWrapper> | ||
Check failure on line 78 in src/Components/Charts/BaseBarChart.tsx GitHub Actions / Release
Check failure on line 78 in src/Components/Charts/BaseBarChart.tsx GitHub Actions / Release
|
||
<div style={TITLE_STYLE}>{title}</div> | ||
<ResponsiveContainer width={width ?? '100%'} height={height}> | ||
<BarChart data={data} margin={BAR_CHART_MARGINS}> | ||
<XAxis | ||
dataKey="x" | ||
height={20} | ||
angle={-45} | ||
ticks={data.length ? undefined : ['']} | ||
tickFormatter={tickFormatter} | ||
tickMargin={TICK_MARGIN} | ||
textAnchor="end" | ||
interval={data.length < TICKS_SHOW_ALL_LABELS_BELOW ? 0 : 'preserveStartEnd'} | ||
> | ||
<Label value={units} offset={UNITS_LABEL_OFFSET} position="insideBottom" /> | ||
</XAxis> | ||
<YAxis> | ||
<Label value={t[COUNT_KEY]} offset={-10} position="left" angle={270} /> | ||
</YAxis> | ||
<CartesianGrid strokeDasharray="3 3" vertical={false} /> | ||
<Tooltip content={<BarTooltip totalCount={totalCount} />} /> | ||
<Bar dataKey="y" isAnimationActive={false} onClick={onClick} onMouseEnter={onHover} maxBarSize={70}> | ||
{data.map((entry, index) => ( | ||
<Cell key={entry.x} fill={fill(entry, index)} /> | ||
))} | ||
</Bar> | ||
</BarChart> | ||
</ResponsiveContainer> | ||
</ChartWrapper> | ||
); | ||
}; | ||
|
||
const BarTooltip = ({ | ||
active, | ||
payload, | ||
totalCount, | ||
}: { | ||
active?: boolean; | ||
payload?: TooltipPayload; | ||
totalCount: number; | ||
}) => { | ||
if (!active) { | ||
return null; | ||
} | ||
|
||
const name = (payload && payload[0]?.payload?.x) || ''; | ||
const value = (payload && payload[0]?.value) || 0; | ||
const percentage = totalCount ? Math.round((value / totalCount) * 100) : 0; | ||
|
||
return ( | ||
<div style={TOOL_TIP_STYLE}> | ||
<p style={LABEL_STYLE}>{name}</p> | ||
<p style={COUNT_STYLE}> | ||
{value} ({percentage}%) | ||
</p> | ||
</div> | ||
); | ||
}; | ||
|
||
export default BaseBarChart; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,129 +1,13 @@ | ||
import React, { useCallback } from 'react'; | ||
import { | ||
Bar, | ||
BarChart, | ||
BarProps, | ||
CartesianGrid, | ||
Cell, | ||
Label, | ||
ResponsiveContainer, | ||
Tooltip, | ||
XAxis, | ||
YAxis, | ||
} from 'recharts'; | ||
import type { TooltipProps } from 'recharts'; | ||
import React from 'react'; | ||
import { BarChartProps } from '../../types/chartTypes'; | ||
|
||
import { | ||
TOOLTIP_STYLE, | ||
TOOLTIP_OTHER_PROPS, | ||
COUNT_STYLE, | ||
LABEL_STYLE, | ||
MAX_TICK_LABEL_CHARS, | ||
TITLE_STYLE, | ||
TICKS_SHOW_ALL_LABELS_BELOW, | ||
UNITS_LABEL_OFFSET, | ||
TICK_MARGIN, | ||
COUNT_KEY, | ||
} from '../../constants/chartConstants'; | ||
import { useChartTheme } from '../../ChartConfigProvider'; | ||
import BaseBarChart from './BaseBarChart'; | ||
|
||
import type { BarChartProps, CategoricalChartDataItem, TooltipPayload } from '../../types/chartTypes'; | ||
import { useChartTheme, useChartTranslation } from '../../ChartConfigProvider'; | ||
import NoData from '../NoData'; | ||
import { useTransformedChartData } from '../../util/chartUtils'; | ||
import ChartWrapper from './ChartWrapper'; | ||
const BentoBarChart: React.FC<BarChartProps> = ({ colorTheme = 'default', ...params }) => { | ||
const { fill: chartFill, other: otherFill } = useChartTheme().bar[colorTheme]; | ||
|
||
const tickFormatter = (tickLabel: string) => { | ||
if (tickLabel.length <= MAX_TICK_LABEL_CHARS) { | ||
return tickLabel; | ||
} | ||
return `${tickLabel.substring(0, MAX_TICK_LABEL_CHARS)}...`; | ||
}; | ||
|
||
const BAR_CHART_MARGINS = { top: 10, bottom: 100, right: 20 }; | ||
|
||
const BentoBarChart = ({ height, width, units, title, onClick, colorTheme = 'default', ...params }: BarChartProps) => { | ||
const t = useChartTranslation(); | ||
const { fill: chartFill, other } = useChartTheme().bar[colorTheme]; | ||
|
||
const fill = (entry: CategoricalChartDataItem, index: number) => | ||
entry.x === 'missing' ? other : chartFill[index % chartFill.length]; | ||
|
||
const data = useTransformedChartData(params, true); | ||
|
||
const totalCount = data.reduce((sum, e) => sum + e.y, 0); | ||
|
||
const onHover: BarProps['onMouseEnter'] = useCallback( | ||
(_data, _index, e) => { | ||
const { target } = e; | ||
if (onClick && target) (target as SVGElement).style.cursor = 'pointer'; | ||
}, | ||
[onClick] | ||
); | ||
|
||
if (data.length === 0) { | ||
return <NoData height={height} />; | ||
} | ||
|
||
// Regarding XAxis.ticks below: | ||
// The weird conditional is added from https://github.com/recharts/recharts/issues/2593#issuecomment-1311678397 | ||
// Basically, if data is empty, Recharts will default to a domain of [0, "auto"] and our tickFormatter trips up | ||
// on formatting a non-string. This hack manually overrides the ticks for the axis and blanks it out. | ||
// - David L, 2023-01-03 | ||
return ( | ||
<ChartWrapper responsive={typeof width !== 'number'}> | ||
<div style={TITLE_STYLE}>{title}</div> | ||
<ResponsiveContainer width={width ?? '100%'} height={height}> | ||
<BarChart data={data} margin={BAR_CHART_MARGINS}> | ||
<XAxis | ||
dataKey="x" | ||
height={20} | ||
angle={-45} | ||
ticks={data.length ? undefined : ['']} | ||
tickFormatter={tickFormatter} | ||
tickMargin={TICK_MARGIN} | ||
textAnchor="end" | ||
interval={data.length < TICKS_SHOW_ALL_LABELS_BELOW ? 0 : 'preserveStartEnd'} | ||
> | ||
<Label value={units} offset={UNITS_LABEL_OFFSET} position="insideBottom" /> | ||
</XAxis> | ||
<YAxis> | ||
<Label value={t[COUNT_KEY]} offset={-10} position="left" angle={270} /> | ||
</YAxis> | ||
<CartesianGrid strokeDasharray="3 3" vertical={false} /> | ||
<Tooltip {...TOOLTIP_OTHER_PROPS} content={<BarTooltip totalCount={totalCount} />} /> | ||
<Bar dataKey="y" isAnimationActive={false} onClick={onClick} onMouseEnter={onHover} maxBarSize={70}> | ||
{data.map((entry, index) => ( | ||
<Cell key={entry.x} fill={fill(entry, index)} /> | ||
))} | ||
</Bar> | ||
</BarChart> | ||
</ResponsiveContainer> | ||
</ChartWrapper> | ||
); | ||
}; | ||
|
||
interface BarTooltipProps extends TooltipProps<number, string> { | ||
payload?: TooltipPayload; | ||
totalCount: number; | ||
} | ||
|
||
const BarTooltip = ({ active, payload, totalCount }: BarTooltipProps) => { | ||
if (!active) { | ||
return null; | ||
} | ||
|
||
const name = (payload && payload[0]?.payload?.x) || ''; | ||
const value = (payload && payload[0]?.value) || 0; | ||
const percentage = totalCount ? Math.round((value / totalCount) * 100) : 0; | ||
|
||
return ( | ||
<div style={TOOLTIP_STYLE}> | ||
<p style={LABEL_STYLE}>{name}</p> | ||
<p style={COUNT_STYLE}> | ||
{value} ({percentage}%) | ||
</p> | ||
</div> | ||
); | ||
return <BaseBarChart chartFill={chartFill} otherFill={otherFill} {...params} />; | ||
}; | ||
|
||
export default BentoBarChart; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import React from 'react'; | ||
import { HistogramProps } from '../../types/chartTypes'; | ||
|
||
import { useChartTheme } from '../../ChartConfigProvider'; | ||
import BaseBarChart from './BaseBarChart'; | ||
|
||
const BentoHistogram: React.FC<HistogramProps> = ({ colorTheme = 'default', ...params }) => { | ||
const { fill: chartFill, other: otherFill } = useChartTheme().histogram[colorTheme]; | ||
|
||
return <BaseBarChart chartFill={chartFill} otherFill={otherFill} {...params} />; | ||
}; | ||
|
||
export default BentoHistogram; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.