Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[@mantine/charts] BarChart: added waterfall type #6231

Merged
merged 3 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions apps/mantine.dev/src/pages/charts/bar-chart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ contribution of each series in terms of percentages.

<Demo data={BarChartDemos.percent} />

## Waterfall bar chart

Set `type="waterfall"` to render a waterfall bar chart. This chart type illustrates how an
initial value is influenced by subsequent positive or negative values,
with each bar starting where the previous one ended.
Use the `color` prop inside data to color each bar individually. Note that the series color gets overwritten for this specific bar.
Use the `standalone` prop inside data to decouple the bar from the flow.

<Demo data={BarChartDemos.waterfall} />

## Legend

To display chart legend, set `withLegend` prop. When one of the items in the legend
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { BarChart } from '@mantine/charts';
import { MantineDemo } from '@mantinex/demo';
import { waterfallCode, waterfallData } from './_data';

const code = `
import { BarChart } from '@mantine/charts';
import { data } from './data';


function Demo() {
return (
<BarChart
h={300}
data={data}
dataKey="item"
type="waterfall"
series={[{ name: 'Effective tax rate in %', color: 'blue' }]}
withLegend
/>
);
}
`;

function Demo() {
return (
<BarChart
h={300}
data={waterfallData}
dataKey="item"
type="waterfall"
series={[{ name: 'Effective tax rate in %', color: 'blue' }]}
withLegend
/>
);
}

export const waterfall: MantineDemo = {
type: 'code',
component: Demo,
code: [
{ code, language: 'tsx', fileName: 'Demo.tsx' },
{ code: waterfallCode, language: 'tsx', fileName: 'data.ts' },
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ export const Demo_stacked = {
render: renderDemo(demos.stacked),
};

export const Demo_waterfall = {
name: '⭐ Demo: waterfall',
render: renderDemo(demos.waterfall),
};

export const Demo_percent = {
name: '⭐ Demo: percent',
render: renderDemo(demos.percent),
Expand Down
25 changes: 25 additions & 0 deletions packages/@docs/demos/src/demos/charts/BarChart/_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,28 @@ export const data = [
{ month: 'June', Smartphones: 750, Laptops: 600, Tablets: 1000 },
];
`;

export const waterfallData = [
{ item: 'TaxRate', 'Effective tax rate in %': 21, color: 'blue' },
{ item: 'Foreign inc.', 'Effective tax rate in %': -15.5, color: 'teal' },
{ item: 'Perm. diff.', 'Effective tax rate in %': -3, color: 'teal' },
{ item: 'Credits', 'Effective tax rate in %': -3, color: 'teal' },
{ item: 'Loss carryf. ', 'Effective tax rate in %': -2, color: 'teal' },
{ item: 'Law changes', 'Effective tax rate in %': 2, color: 'red' },
{ item: 'Reven. adj.', 'Effective tax rate in %': 4, color: 'red' },
{ item: 'ETR', 'Effective tax rate in %': 3.5, color: 'blue', standalone: true },
];

export const waterfallCode = `
export const data =
[
{ item: 'TaxRate', 'Effective tax rate in %': 21, color: 'blue' },
{ item: 'Foreign inc.', 'Effective tax rate in %': -15.5, color: 'teal' },
{ item: 'Perm. diff.', 'Effective tax rate in %': -3, color: 'teal' },
{ item: 'Credits', 'Effective tax rate in %': -3, color: 'teal' },
{ item: 'Loss carryf. ', 'Effective tax rate in %': -2, color: 'teal' },
{ item: 'Law changes', 'Effective tax rate in %': 2, color: 'red' },
{ item: 'Reven. adj.', 'Effective tax rate in %': 4, color: 'red' },
{ item: 'ETR', 'Effective tax rate in %': 3.5, color: 'blue', standalone: true },
];
`;
1 change: 1 addition & 0 deletions packages/@docs/demos/src/demos/charts/BarChart/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export { unit } from './BarChart.demo.unit';
export { xAxisOffset } from './BarChart.demo.xAxisOffset';
export { yScale } from './BarChart.demo.yScale';
export { stacked } from './BarChart.demo.stacked';
export { waterfall } from './BarChart.demo.waterfall';
export { percent } from './BarChart.demo.percent';
export { vertical } from './BarChart.demo.vertical';
export { seriesLabels } from './BarChart.demo.seriesLabels';
Expand Down
1 change: 1 addition & 0 deletions packages/@mantine/charts/src/AreaChart/AreaChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ function valueToPercent(value: number) {

export interface AreaChartSeries extends ChartSeries {
strokeDasharray?: string | number;
color: MantineColor;
}

export type AreaChartType = 'default' | 'stacked' | 'percent' | 'split';
Expand Down
27 changes: 27 additions & 0 deletions packages/@mantine/charts/src/BarChart/BarChart.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ const data = [
{ month: 'June', Smartphones: 40, Laptops: 45, Tablets: 50 },
];

const waterfallData = [
{ item: 'TaxRate', 'Effective tax rate in %': 21, color: 'blue.3' },
{ item: 'Foreign inc.', 'Effective tax rate in %': -15.5, color: 'green' },
{ item: 'Perm. diff.', 'Effective tax rate in %': -3, color: 'green' },
{ item: 'Credits', 'Effective tax rate in %': -3, color: 'green' },
{ item: 'Loss carryf. ', 'Effective tax rate in %': -2, color: 'green' },
{ item: 'Law changes', 'Effective tax rate in %': 2, color: 'red' },
{ item: 'Reven. adj.', 'Effective tax rate in %': 4, color: 'red' },
{ item: 'ETR', 'Effective tax rate in %': 3.5, color: 'blue.3', standalone: true },
];

export function Usage() {
return (
<div style={{ padding: 40 }}>
Expand Down Expand Up @@ -73,6 +84,22 @@ export function Stacked() {
);
}

export function Waterfall() {
return (
<div style={{ padding: 40 }}>
<BarChart
h={300}
data={waterfallData}
dataKey="item"
type="waterfall"
fillOpacity={0.6}
withLegend
series={[{ name: 'Effective tax rate in %', color: 'blue' }]}
/>
</div>
);
}

export function Vertical() {
return (
<div style={{ padding: 40 }}>
Expand Down
43 changes: 39 additions & 4 deletions packages/@mantine/charts/src/BarChart/BarChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
Bar,
BarProps,
CartesianGrid,
Cell,
Label,
Legend,
BarChart as ReChartsBarChart,
Expand Down Expand Up @@ -38,7 +39,7 @@ function valueToPercent(value: number) {

export interface BarChartSeries extends ChartSeries {}

export type BarChartType = 'default' | 'stacked' | 'percent';
export type BarChartType = 'default' | 'stacked' | 'percent' | 'waterfall';

export type BarChartStylesNames =
| 'bar'
Expand All @@ -55,7 +56,7 @@ export interface BarChartProps
GridChartBaseProps,
StylesApiProps<BarChartFactory>,
ElementProps<'div'> {
/** Data used to display chart */
/** Data used to display chart. */
data: Record<string, any>[];

/** An array of objects with `name` and `color` keys. Determines which data should be consumed from the `data` array. */
Expand Down Expand Up @@ -128,6 +129,29 @@ function BarLabel({ value, valueFormatter, ...others }: Record<string, any>) {
);
}

function calculateCumulativeTotal(waterfallData: Record<string, any>[], dataKey: string) {
let start: number = 0;
let end: number = 0;
return waterfallData.map((item) => {
if (item.standalone) {
for (const prop in item) {
if (typeof item[prop] === 'number' && prop !== dataKey) {
item[prop] = [0, item[prop]];
}
}
} else {
for (const prop in item) {
if (typeof item[prop] === 'number' && prop !== dataKey) {
end += item[prop];
item[prop] = [start, end];
start = end;
}
}
}
return item;
});
}

export const BarChart = factory<BarChartFactory>((_props, ref) => {
const props = useProps('BarChart', defaultProps, _props);
const {
Expand Down Expand Up @@ -186,6 +210,8 @@ export const BarChart = factory<BarChartFactory>((_props, ref) => {
props,
});

const inputData = type === 'waterfall' ? calculateCumulativeTotal(data, dataKey) : data;

const getStyles = useStyles<BarChartFactory>({
name: 'BarChart',
classes,
Expand Down Expand Up @@ -217,7 +243,14 @@ export const BarChart = factory<BarChartFactory>((_props, ref) => {
stackId={stacked ? 'stack' : undefined}
label={withBarValueLabel ? <BarLabel valueFormatter={valueFormatter} /> : undefined}
{...(typeof barProps === 'function' ? barProps(item) : barProps)}
/>
>
{inputData.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={entry.color ? getThemeColor(entry.color, theme) : color}
/>
))}
</Bar>
);
});

Expand Down Expand Up @@ -250,7 +283,7 @@ export const BarChart = factory<BarChartFactory>((_props, ref) => {
>
<ResponsiveContainer {...getStyles('container')}>
<ReChartsBarChart
data={data}
data={inputData}
stackOffset={type === 'percent' ? 'expand' : undefined}
layout={orientation}
margin={{
Expand All @@ -271,6 +304,7 @@ export const BarChart = factory<BarChartFactory>((_props, ref) => {
classNames={resolvedClassNames}
styles={resolvedStyles}
series={series}
showColor={type !== 'waterfall'}
/>
)}
{...legendProps}
Expand Down Expand Up @@ -346,6 +380,7 @@ export const BarChart = factory<BarChartFactory>((_props, ref) => {
<ChartTooltip
label={label}
payload={payload}
type={type === 'waterfall' ? 'scatter' : undefined}
unit={unit}
classNames={resolvedClassNames}
styles={resolvedStyles}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
background-color: var(--mantine-color-dark-5);
}
}

&[data-without-color] .legendItemColor {
display: none;
}
}

.legendItemName {
Expand Down
5 changes: 5 additions & 0 deletions packages/@mantine/charts/src/ChartLegend/ChartLegend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export interface ChartLegendProps

/** Data used for labels, only applicable for area charts: AreaChart, LineChart, BarChart */
series?: ChartSeries[];

/** Determines whether color swatch should be shown next to the label, `true` by default */
showColor?: boolean;
}

export type ChartLegendFactory = Factory<{
Expand All @@ -58,6 +61,7 @@ export const ChartLegend = factory<ChartLegendFactory>((_props, ref) => {
legendPosition,
mod,
series,
showColor,
...others
} = props;

Expand Down Expand Up @@ -85,6 +89,7 @@ export const ChartLegend = factory<ChartLegendFactory>((_props, ref) => {
{...getStyles('legendItem')}
onMouseEnter={() => onHighlight(item.dataKey)}
onMouseLeave={() => onHighlight(null)}
data-without-color={showColor === false || undefined}
>
<ColorSwatch
color={item.color}
Expand Down
6 changes: 6 additions & 0 deletions packages/@mantine/charts/src/ChartTooltip/ChartTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,15 @@ export function getFilteredChartTooltipPayload(payload: Record<string, any>[], s

function getData(item: Record<string, any>, type: 'area' | 'radial' | 'scatter') {
if (type === 'radial' || type === 'scatter') {
if (Array.isArray(item.value)) {
return item.value[1] - item.value[0];
}
return item.value;
}

if (Array.isArray(item.payload[item.dataKey])) {
return item.payload[item.dataKey][1] - item.payload[item.dataKey][0];
}
return item.payload[item.dataKey];
}

Expand Down
2 changes: 1 addition & 1 deletion packages/@mantine/charts/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export interface ChartReferenceLineProps extends Omit<ReferenceLineProps, 'ref'

export interface ChartSeries {
name: string;
color: MantineColor;
color?: MantineColor;
label?: string;
}

Expand Down
Loading