Skip to content

Commit

Permalink
[charts] Add label to be displayed inside bars in BarChart (#12988)
Browse files Browse the repository at this point in the history
Signed-off-by: Jose C Quintas Jr <[email protected]>
Co-authored-by: Lukas <[email protected]>
Co-authored-by: Alexandre Fauquette <[email protected]>
  • Loading branch information
3 people authored May 21, 2024
1 parent 11decfc commit 5585ea1
Show file tree
Hide file tree
Showing 31 changed files with 717 additions and 9 deletions.
4 changes: 4 additions & 0 deletions docs/data/charts-component-api-pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ const apiPages: MuiPage[] = [
pathname: '/x/api/charts/bar-element',
title: 'BarElement',
},
{
pathname: '/x/api/charts/bar-label',
title: 'BarLabel',
},
{
pathname: '/x/api/charts/bar-plot',
title: 'BarPlot',
Expand Down
14 changes: 14 additions & 0 deletions docs/data/charts/bars/BarLabel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as React from 'react';
import { BarChart } from '@mui/x-charts/BarChart';

export default function BarLabel() {
return (
<BarChart
xAxis={[{ scaleType: 'band', data: ['group A', 'group B', 'group C'] }]}
series={[{ data: [4, 3, 5] }, { data: [1, 6, 3] }, { data: [2, 5, 6] }]}
width={500}
height={300}
barLabel="value"
/>
);
}
14 changes: 14 additions & 0 deletions docs/data/charts/bars/BarLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as React from 'react';
import { BarChart } from '@mui/x-charts/BarChart';

export default function BarLabel() {
return (
<BarChart
xAxis={[{ scaleType: 'band', data: ['group A', 'group B', 'group C'] }]}
series={[{ data: [4, 3, 5] }, { data: [1, 6, 3] }, { data: [2, 5, 6] }]}
width={500}
height={300}
barLabel="value"
/>
);
}
7 changes: 7 additions & 0 deletions docs/data/charts/bars/BarLabel.tsx.preview
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<BarChart
xAxis={[{ scaleType: 'band', data: ['group A', 'group B', 'group C'] }]}
series={[{ data: [4, 3, 5] }, { data: [1, 6, 3] }, { data: [2, 5, 6] }]}
width={500}
height={300}
barLabel="value"
/>
22 changes: 22 additions & 0 deletions docs/data/charts/bars/CustomLabels.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as React from 'react';
import { BarChart } from '@mui/x-charts/BarChart';

export default function CustomLabels() {
return (
<BarChart
series={[
{ data: [4, 2, 5, 4, 1], stack: 'A', label: 'Series A1' },
{ data: [2, 8, 1, 3, 1], stack: 'A', label: 'Series A2' },
{ data: [14, 6, 5, 8, 9], label: 'Series B1' },
]}
barLabel={(item, context) => {
if ((item.value ?? 0) > 10) {
return 'High';
}
return context.bar.height < 60 ? null : item.value?.toString();
}}
width={600}
height={350}
/>
);
}
22 changes: 22 additions & 0 deletions docs/data/charts/bars/CustomLabels.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as React from 'react';
import { BarChart } from '@mui/x-charts/BarChart';

export default function CustomLabels() {
return (
<BarChart
series={[
{ data: [4, 2, 5, 4, 1], stack: 'A', label: 'Series A1' },
{ data: [2, 8, 1, 3, 1], stack: 'A', label: 'Series A2' },
{ data: [14, 6, 5, 8, 9], label: 'Series B1' },
]}
barLabel={(item, context) => {
if ((item.value ?? 0) > 10) {
return 'High';
}
return context.bar.height < 60 ? null : item.value?.toString();
}}
width={600}
height={350}
/>
);
}
15 changes: 15 additions & 0 deletions docs/data/charts/bars/CustomLabels.tsx.preview
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<BarChart
series={[
{ data: [4, 2, 5, 4, 1], stack: 'A', label: 'Series A1' },
{ data: [2, 8, 1, 3, 1], stack: 'A', label: 'Series A2' },
{ data: [14, 6, 5, 8, 9], label: 'Series B1' },
]}
barLabel={(item, context) => {
if ((item.value ?? 0) > 10) {
return 'High';
}
return context.bar.height < 60 ? null : item.value?.toString();
}}
width={600}
height={350}
/>
21 changes: 20 additions & 1 deletion docs/data/charts/bars/bars.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: React Bar chart
productId: x-charts
components: BarChart, BarElement, BarPlot, ChartsGrid, ChartsOnAxisClickHandler
components: BarChart, BarElement, BarPlot, ChartsGrid, ChartsOnAxisClickHandler, BarLabel
---

# Charts - Bars
Expand Down Expand Up @@ -108,6 +108,25 @@ It will work with any positive value and will be properly applied to horizontal

{{"demo": "BorderRadius.js"}}

## Labels

You can display labels on the bars.
To do so, the `BarChart` or `BarPlot` accepts a `barLabel` property.
It can either get a function that gets the bar item and some context.
Or you can pass `'value'` to display the raw value of the bar.

{{"demo": "BarLabel.js"}}

### Custom Labels

You can display, change or hide labels based on conditional logic.
To do so, provide a function to the `barLabel`.
Labels are not displayed if the function returns `null`.

In the example we display a `'High'` text on values higher than 10, and hide values when the generated bar height is lower than 60px.

{{"demo": "CustomLabels.js"}}

## Click event

Bar charts provides two click handlers:
Expand Down
7 changes: 7 additions & 0 deletions docs/pages/x/api/charts/bar-chart.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"text": "highlight docs"
}
},
"barLabel": { "type": { "name": "union", "description": "'value'<br>&#124;&nbsp;func" } },
"borderRadius": { "type": { "name": "number" } },
"bottomAxis": {
"type": { "name": "union", "description": "object<br>&#124;&nbsp;string" },
Expand Down Expand Up @@ -131,6 +132,12 @@
"default": "BarElementPath",
"class": null
},
{
"name": "barLabel",
"description": "The component that renders the bar label.",
"default": "BarLabel",
"class": null
},
{
"name": "legend",
"description": "Custom rendering of the legend.",
Expand Down
23 changes: 23 additions & 0 deletions docs/pages/x/api/charts/bar-label.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as React from 'react';
import ApiPage from 'docs/src/modules/components/ApiPage';
import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
import jsonPageContent from './bar-label.json';

export default function Page(props) {
const { descriptions, pageContent } = props;
return <ApiPage descriptions={descriptions} pageContent={pageContent} />;
}

Page.getInitialProps = () => {
const req = require.context(
'docsx/translations/api-docs/charts/bar-label',
false,
/\.\/bar-label.*.json$/,
);
const descriptions = mapApiPageTranslations(req);

return {
descriptions,
pageContent: jsonPageContent,
};
};
41 changes: 41 additions & 0 deletions docs/pages/x/api/charts/bar-label.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"props": {},
"name": "BarLabel",
"imports": [
"import { BarLabel } from '@mui/x-charts/BarChart';",
"import { BarLabel } from '@mui/x-charts';"
],
"slots": [
{
"name": "barLabel",
"description": "The component that renders the bar label.",
"default": "BarLabel",
"class": null
}
],
"classes": [
{
"key": "faded",
"className": "MuiBarLabel-faded",
"description": "Styles applied to the root element if it is faded.",
"isGlobal": false
},
{
"key": "highlighted",
"className": "MuiBarLabel-highlighted",
"description": "Styles applied to the root element if it is highlighted.",
"isGlobal": false
},
{
"key": "root",
"className": "MuiBarLabel-root",
"description": "Styles applied to the root element.",
"isGlobal": false
}
],
"muiName": "MuiBarLabel",
"filename": "/packages/x-charts/src/BarChart/BarLabel/BarLabel.tsx",
"inheritance": null,
"demos": "<ul><li><a href=\"/x/react-charts/bars/\">Charts - Bars</a></li></ul>",
"cssComponent": false
}
7 changes: 7 additions & 0 deletions docs/pages/x/api/charts/bar-plot.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"props": {
"barLabel": { "type": { "name": "union", "description": "'value'<br>&#124;&nbsp;func" } },
"borderRadius": { "type": { "name": "number" } },
"onItemClick": {
"type": { "name": "func" },
Expand Down Expand Up @@ -27,6 +28,12 @@
"description": "The component that renders the bar.",
"default": "BarElementPath",
"class": null
},
{
"name": "barLabel",
"description": "The component that renders the bar label.",
"default": "BarLabel",
"class": null
}
],
"classes": [],
Expand Down
4 changes: 4 additions & 0 deletions docs/translations/api-docs/charts/bar-chart/bar-chart.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
"description": "The configuration of axes highlight. Default is set to &#39;band&#39; in the bar direction. Depends on <code>layout</code> prop.",
"seeMoreText": "See {{link}} for more details."
},
"barLabel": {
"description": "If provided, the function will be used to format the label of the bar. It can be set to &#39;value&#39; to display the current value."
},
"borderRadius": { "description": "Defines the border radius of the bar element." },
"bottomAxis": {
"description": "Indicate which axis to display the bottom of the charts. Can be a string (the id of the axis) or an object <code>ChartsXAxisProps</code>."
Expand Down Expand Up @@ -76,6 +79,7 @@
"axisTick": "Custom component for the axis tick.",
"axisTickLabel": "Custom component for tick label.",
"bar": "The component that renders the bar.",
"barLabel": "The component that renders the bar label.",
"itemContent": "Custom component for displaying tooltip content when triggered by item event.",
"legend": "Custom rendering of the legend.",
"loadingOverlay": "Overlay component rendered when the chart is in a loading state.",
Expand Down
18 changes: 18 additions & 0 deletions docs/translations/api-docs/charts/bar-label/bar-label.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"componentDescription": "",
"propDescriptions": {},
"classDescriptions": {
"faded": {
"description": "Styles applied to {{nodeName}} if {{conditions}}.",
"nodeName": "the root element",
"conditions": "it is faded"
},
"highlighted": {
"description": "Styles applied to {{nodeName}} if {{conditions}}.",
"nodeName": "the root element",
"conditions": "it is highlighted"
},
"root": { "description": "Styles applied to the root element." }
},
"slotDescriptions": { "barLabel": "The component that renders the bar label." }
}
8 changes: 7 additions & 1 deletion docs/translations/api-docs/charts/bar-plot/bar-plot.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"componentDescription": "",
"propDescriptions": {
"barLabel": {
"description": "If provided, the function will be used to format the label of the bar. It can be set to &#39;value&#39; to display the current value."
},
"borderRadius": { "description": "Defines the border radius of the bar element." },
"onItemClick": {
"description": "Callback fired when a bar item is clicked.",
Expand All @@ -14,5 +17,8 @@
"slots": { "description": "Overridable component slots." }
},
"classDescriptions": {},
"slotDescriptions": { "bar": "The component that renders the bar." }
"slotDescriptions": {
"bar": "The component that renders the bar.",
"barLabel": "The component that renders the bar label."
}
}
10 changes: 10 additions & 0 deletions packages/x-charts/src/BarChart/BarChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ const BarChart = React.forwardRef(function BarChart(props: BarChartProps, ref) {
slots,
slotProps,
loading,
barLabel,
} = props;

const id = useId();
Expand Down Expand Up @@ -197,6 +198,7 @@ const BarChart = React.forwardRef(function BarChart(props: BarChartProps, ref) {
skipAnimation={skipAnimation}
onItemClick={onItemClick}
borderRadius={borderRadius}
barLabel={barLabel}
/>
<ChartsOverlay loading={loading} slots={slots} slotProps={slotProps} />
</g>
Expand Down Expand Up @@ -232,6 +234,14 @@ BarChart.propTypes = {
x: PropTypes.oneOf(['band', 'line', 'none']),
y: PropTypes.oneOf(['band', 'line', 'none']),
}),
/**
* If provided, the function will be used to format the label of the bar.
* It can be set to 'value' to display the current value.
* @param {BarItem} item The item to format.
* @param {BarLabelContext} context data about the bar.
* @returns {string} The formatted label.
*/
barLabel: PropTypes.oneOfType([PropTypes.oneOf(['value']), PropTypes.func]),
/**
* Defines the border radius of the bar element.
*/
Expand Down
52 changes: 52 additions & 0 deletions packages/x-charts/src/BarChart/BarLabel/BarLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as React from 'react';
import { styled, useThemeProps } from '@mui/material/styles';
import { animated } from '@react-spring/web';
import PropTypes from 'prop-types';
import { barLabelClasses } from './barLabelClasses';
import { BarLabelOwnerState } from './BarLabel.types';

export const BarLabelComponent = styled(animated.text, {
name: 'MuiBarLabel',
slot: 'Root',
overridesResolver: (_, styles) => [
{ [`&.${barLabelClasses.faded}`]: styles.faded },
{ [`&.${barLabelClasses.highlighted}`]: styles.highlighted },
styles.root,
],
})(({ theme }) => ({
...theme?.typography?.body2,
stroke: 'none',
fill: (theme.vars || theme)?.palette?.text?.primary,
transition: 'opacity 0.2s ease-in, fill 0.2s ease-in',
textAnchor: 'middle',
dominantBaseline: 'central',
pointerEvents: 'none',
opacity: 1,
[`&.${barLabelClasses.faded}`]: {
opacity: 0.3,
},
}));

export type BarLabelProps = Omit<React.SVGProps<SVGTextElement>, 'ref' | 'id'> & BarLabelOwnerState;

function BarLabel(props: BarLabelProps) {
const themeProps = useThemeProps({ props, name: 'MuiBarLabel' });

const { seriesId, dataIndex, color, isFaded, isHighlighted, classes, ...otherProps } = themeProps;

return <BarLabelComponent {...otherProps} />;
}

BarLabel.propTypes = {
// ----------------------------- Warning --------------------------------
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
classes: PropTypes.object,
dataIndex: PropTypes.number.isRequired,
isFaded: PropTypes.bool.isRequired,
isHighlighted: PropTypes.bool.isRequired,
seriesId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
} as any;

export { BarLabel };
Loading

0 comments on commit 5585ea1

Please sign in to comment.