Skip to content

Commit b942f5f

Browse files
authored
feat(ColumnChartWithTrend): add tooltipConfig prop (#7607)
Closes #7597
1 parent 4d45358 commit b942f5f

File tree

11 files changed

+57
-57
lines changed

11 files changed

+57
-57
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,6 @@ packages/main/scripts/wrapperGeneration/json
2626
packages/main/tmp
2727
packages/*/src/generated/
2828

29+
cypress/downloads
30+
2931
.nx

packages/charts/src/components/ColumnChartWithTrend/ColumnChartWithTrend.cy.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ describe('ColumnChartWithTrend', () => {
2828
cy.mount(<ColumnChartWithTrend dataset={complexDataSet} dimensions={dimensions} measures={measures} />);
2929
cy.get('.recharts-responsive-container').should('be.visible');
3030
cy.get('.recharts-bar').should('have.length', 1);
31-
cy.get('.recharts-line').should('have.length', 2); // the column chart includes an empty line
31+
cy.get('.recharts-line').should('have.length', 1);
3232
cy.get('.recharts-bar-rectangles').should('have.length', 1);
33-
cy.get('.recharts-line-curve').should('have.length', 2); // the column chart includes an empty line
33+
cy.get('.recharts-line-curve').should('have.length', 1);
3434
});
3535

3636
it('click handlers', () => {
@@ -58,10 +58,9 @@ describe('ColumnChartWithTrend', () => {
5858
detail: Cypress.sinon.match({
5959
payload: {
6060
name: 'January',
61-
users: 1,
61+
users: 100,
6262
sessions: 300,
6363
volume: 756,
64-
__users: 100,
6564
},
6665
}),
6766
}),

packages/charts/src/components/ColumnChartWithTrend/ColumnChartWithTrend.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ControlsWithNote, DocsHeader, Footer } from '@sb/components';
22
import { Canvas, Meta } from '@storybook/addon-docs';
33
import * as ComponentStories from './ColumnChartWithTrend.stories';
44
import LegendStory from '../../resources/LegendConfig.mdx';
5+
import TooltipStory from '../../resources/TooltipConfig.mdx';
56

67
<Meta of={ComponentStories} />
78

@@ -17,6 +18,8 @@ import LegendStory from '../../resources/LegendConfig.mdx';
1718

1819
<Canvas of={ComponentStories.LoadingPlaceholder} />
1920

21+
<TooltipStory of={ComponentStories.WithCustomTooltipConfig} additionalDescription={<><b>Note: </b>The <code>tooltipConfig</code> is used for both LineChart and ColumnChart.</>} />
22+
2023
<LegendStory of={ComponentStories.WithCustomLegendConfig} />
2124

2225
<br />

packages/charts/src/components/ColumnChartWithTrend/ColumnChartWithTrend.stories.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Meta, StoryObj } from '@storybook/react';
2-
import { complexDataSet, legendConfig } from '../../resources/DemoProps.js';
2+
import { complexDataSet, legendConfig, tooltipConfig } from '../../resources/DemoProps.js';
33
import { ColumnChartWithTrend } from './ColumnChartWithTrend.js';
44

55
const meta = {
@@ -18,7 +18,6 @@ const meta = {
1818
{
1919
accessor: 'users',
2020
label: 'Users',
21-
formatter: (val) => val.toLocaleString(),
2221
type: 'line',
2322
},
2423
{
@@ -44,3 +43,7 @@ export const LoadingPlaceholder: Story = {
4443
export const WithCustomLegendConfig: Story = {
4544
args: legendConfig,
4645
};
46+
47+
export const WithCustomTooltipConfig: Story = {
48+
args: tooltipConfig,
49+
};

packages/charts/src/components/ColumnChartWithTrend/ColumnChartWithTrend.tsx

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { ThemingParameters, useStylesheet } from '@ui5/webcomponents-react-base'
44
import { clsx } from 'clsx';
55
import type { CSSProperties } from 'react';
66
import { forwardRef, useId } from 'react';
7-
import type { TooltipProps, YAxisProps } from 'recharts';
7+
import type { DefaultLegendContentProps, YAxisProps } from 'recharts';
8+
import { DefaultLegendContent } from 'recharts';
89
import { useLongestYAxisLabel } from '../../hooks/useLongestYAxisLabel.js';
910
import { usePrepareDimensionsAndMeasures } from '../../hooks/usePrepareDimensionsAndMeasures.js';
1011
import { usePrepareTrendMeasures } from '../../hooks/usePrepareTrendMeasures.js';
@@ -51,10 +52,7 @@ interface DimensionConfig extends IChartDimension {
5152
}
5253

5354
export interface ColumnChartWithTrendProps
54-
extends Omit<
55-
IChartBaseProps<Omit<ICartesianChartConfig, 'secondYAxis' | 'secondYAxisConfig'>>,
56-
'syncId' | 'tooltipConfig'
57-
> {
55+
extends Omit<IChartBaseProps<Omit<ICartesianChartConfig, 'secondYAxis' | 'secondYAxisConfig'>>, 'syncId'> {
5856
/**
5957
* An array of config objects. Each object will define one dimension of the chart.
6058
*
@@ -101,8 +99,6 @@ const measureDefaults = {
10199
opacity: 1,
102100
};
103101

104-
const lineTooltipConfig = { wrapperStyle: { visibility: 'hidden' } } as TooltipProps<any, any>;
105-
106102
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
107103
type AvailableChartTypes = 'line' | 'bar' | string;
108104
/**
@@ -120,12 +116,12 @@ const ColumnChartWithTrend = forwardRef<HTMLDivElement, ColumnChartWithTrendProp
120116
onDataPointClick,
121117
onLegendClick,
122118
ChartPlaceholder,
119+
tooltipConfig,
123120
...rest
124121
} = props;
125122
const syncId = useId();
126123

127124
useStylesheet(content, ColumnChartWithTrend.displayName);
128-
129125
const chartConfig: ColumnChartWithTrendProps['chartConfig'] = {
130126
yAxisVisible: false,
131127
xAxisVisible: true,
@@ -147,21 +143,8 @@ const ColumnChartWithTrend = forwardRef<HTMLDivElement, ColumnChartWithTrendProp
147143
measureDefaults,
148144
);
149145

150-
const { lineMeasures, columnMeasures, columnDataset } = usePrepareTrendMeasures(measures, dataset);
151-
const [yAxisWidth] = useLongestYAxisLabel(columnDataset, columnMeasures, chartConfig.legendPosition);
152-
153-
const columnTooltipConfig = {
154-
formatter: (value, name, tooltipProps) => {
155-
const line = lineMeasures.find(
156-
(currLine) => currLine.type === 'line' && currLine.accessor === tooltipProps.dataKey,
157-
);
158-
if (line) {
159-
return line.formatter(tooltipProps.payload[`__${line.accessor}`]);
160-
}
161-
const column = columnMeasures.find((currLine) => currLine.accessor === tooltipProps.dataKey);
162-
return column.formatter(value, name, tooltipProps);
163-
},
164-
} as TooltipProps<any, any>;
146+
const { lineMeasures, columnMeasures } = usePrepareTrendMeasures(measures);
147+
const [yAxisWidth] = useLongestYAxisLabel(dataset, columnMeasures, chartConfig.legendPosition);
165148

166149
const { chartConfig: _0, dimensions: _1, measures: _2, ...propsWithoutOmitted } = rest;
167150

@@ -173,7 +156,7 @@ const ColumnChartWithTrend = forwardRef<HTMLDivElement, ColumnChartWithTrendProp
173156
typeof onDataPointClick === 'function' || typeof onClick === 'function' ? 'has-click-handler' : undefined,
174157
classNames.trendContainer,
175158
)}
176-
tooltipConfig={lineTooltipConfig}
159+
tooltipConfig={{ ...tooltipConfig, wrapperStyle: { visibility: 'hidden' } }}
177160
noAnimation={noAnimation}
178161
loading={loading}
179162
loadingDelay={loadingDelay}
@@ -201,7 +184,10 @@ const ColumnChartWithTrend = forwardRef<HTMLDivElement, ColumnChartWithTrendProp
201184
classNames.chartContainer,
202185
)}
203186
onLegendClick={onLegendClick}
204-
tooltipConfig={columnTooltipConfig}
187+
tooltipConfig={{
188+
includeHidden: true,
189+
...tooltipConfig,
190+
}}
205191
noAnimation={noAnimation}
206192
noLegend={noLegend}
207193
loading={loading}
@@ -210,10 +196,13 @@ const ColumnChartWithTrend = forwardRef<HTMLDivElement, ColumnChartWithTrendProp
210196
onDataPointClick={onDataPointClick}
211197
syncId={syncId}
212198
ChartPlaceholder={ChartPlaceholder ?? ColumnChartWithTrendPlaceholder}
213-
dataset={columnDataset}
199+
dataset={dataset}
214200
measures={columnMeasures}
215201
dimensions={dimensions}
216-
chartConfig={chartConfig}
202+
chartConfig={{
203+
...chartConfig,
204+
legendConfig: { ...chartConfig?.legendConfig, content: <DefaultLegendContentWithoutInactive /> },
205+
}}
217206
/>
218207
</div>
219208
);
@@ -222,3 +211,17 @@ const ColumnChartWithTrend = forwardRef<HTMLDivElement, ColumnChartWithTrendProp
222211
ColumnChartWithTrend.displayName = 'ColumnChartWithTrend';
223212

224213
export { ColumnChartWithTrend };
214+
215+
/**
216+
* Helper component to always keep legend items interactive.
217+
* This is required, as otherwise internally hidden measures are greyed out in the Legend.
218+
*/
219+
const DefaultLegendContentWithoutInactive = (props: DefaultLegendContentProps) => {
220+
const updatedPayload = props.payload.map((item) => {
221+
return { ...item, inactive: undefined };
222+
});
223+
// @ts-expect-error: Type doesn't seem to allow class components which `DefaultLegendContent` is.
224+
return <DefaultLegendContent {...props} payload={updatedPayload} />;
225+
};
226+
227+
DefaultLegendContentWithoutInactive.displayName = 'DefaultLegendContentWithoutInactive';

packages/charts/src/components/ComposedChart/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ const ComposedChart = forwardRef<HTMLDivElement, ComposedChartProps>((props, ref
453453
chartElementProps.strokeWidth = element.width;
454454
chartElementProps.strokeOpacity = element.opacity;
455455
chartElementProps.dot = element.showDot ?? !isBigDataSet;
456+
chartElementProps.hide = element.hide;
456457
break;
457458
case 'bar':
458459
chartElementProps.hide = element.hide;

packages/charts/src/hooks/useLabelFormatter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { useCallback } from 'react';
2-
import type { DefaultTooltipContentProps } from 'recharts';
2+
import type { TooltipProps } from 'recharts';
33
import type { NameType, TooltipLabelFormatter, ValueType } from '../interfaces/index.js';
44

55
export const useLabelFormatter = (formatter: TooltipLabelFormatter) => {
66
return useCallback<TooltipLabelFormatter>(
7-
(label: DefaultTooltipContentProps<ValueType, NameType>['label'], payload) => {
7+
(label: TooltipProps<ValueType, NameType>['label'], payload) => {
88
if (typeof formatter === 'function') {
99
return formatter(label, payload);
1010
}
Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
import { useMemo } from 'react';
2-
import { getValueByDataKey } from 'recharts/lib/util/ChartUtils.js';
32
import type { IChartMeasure } from '../interfaces/IChartMeasure.js';
43
import { defaultFormatter } from '../internal/defaults.js';
54

65
interface ITrendChartMeasure extends IChartMeasure {
76
type: 'line' | 'bar';
87
}
98

10-
export const usePrepareTrendMeasures = (measures: ITrendChartMeasure[], dataset: Record<string, unknown>[]) =>
9+
export const usePrepareTrendMeasures = (measures: ITrendChartMeasure[]) =>
1110
useMemo(() => {
1211
const lineMeasures = [];
1312
const columnMeasures = [];
14-
const columnDataset = [];
1513

1614
measures?.forEach((measure, index) => {
1715
if (measure.type === 'bar') {
@@ -39,28 +37,13 @@ export const usePrepareTrendMeasures = (measures: ITrendChartMeasure[], dataset:
3937
columnMeasures.push({
4038
...measure,
4139
opacity: 0,
40+
hide: true,
4241
hideDataLabel: true,
4342
showDot: false,
4443
formatter: defaultFormatter,
4544
});
4645
}
4746
});
4847

49-
dataset?.forEach((data) => {
50-
const reducedLineValues = {};
51-
52-
lineMeasures.forEach((line) => {
53-
if (line.type === 'line') {
54-
reducedLineValues[`__${line.accessor}`] = getValueByDataKey(data, line.accessor);
55-
reducedLineValues[line.accessor] = 1;
56-
}
57-
});
58-
59-
columnDataset.push({
60-
...data,
61-
...reducedLineValues,
62-
});
63-
});
64-
65-
return { lineMeasures, columnMeasures, columnDataset };
66-
}, [measures, dataset]);
48+
return { lineMeasures, columnMeasures };
49+
}, [measures]);

packages/charts/src/resources/DemoProps.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export const tooltipConfig: IChartBaseProps = {
4848
cursor: { stroke: 'red', strokeWidth: 2, fill: 'transparent' },
4949
separator: ':~:',
5050
// eslint-disable-next-line @typescript-eslint/no-unused-vars
51-
formatter: (value, name, props) => {
51+
formatter: (value, name, props, index, payload) => {
5252
if (name === 'Users') {
5353
return [`${value}👨‍💻`, 'Custom Name in Tooltip!'];
5454
}

packages/charts/src/resources/LegendConfig.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { Canvas } from '@storybook/addon-docs';
55
Via the `chartConfig.legendConfig` property you can override the configuration object for the internally used `recharts` Legend component.
66
You can find all possible configuration properties [here](https://recharts.org/en-US/api/Legend).
77

8+
**Note**: It is possible to override the internally used Legend configurations. Use with caution!
9+
810
<Canvas of={props.of} />
911

1012
<details>

0 commit comments

Comments
 (0)