Skip to content
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ packages/main/scripts/wrapperGeneration/json
packages/main/tmp
packages/*/src/generated/

cypress/downloads

.nx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ describe('ColumnChartWithTrend', () => {
cy.mount(<ColumnChartWithTrend dataset={complexDataSet} dimensions={dimensions} measures={measures} />);
cy.get('.recharts-responsive-container').should('be.visible');
cy.get('.recharts-bar').should('have.length', 1);
cy.get('.recharts-line').should('have.length', 2); // the column chart includes an empty line
cy.get('.recharts-line').should('have.length', 1);
cy.get('.recharts-bar-rectangles').should('have.length', 1);
cy.get('.recharts-line-curve').should('have.length', 2); // the column chart includes an empty line
cy.get('.recharts-line-curve').should('have.length', 1);
});

it('click handlers', () => {
Expand Down Expand Up @@ -58,10 +58,9 @@ describe('ColumnChartWithTrend', () => {
detail: Cypress.sinon.match({
payload: {
name: 'January',
users: 1,
users: 100,
sessions: 300,
volume: 756,
__users: 100,
},
}),
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ControlsWithNote, DocsHeader, Footer } from '@sb/components';
import { Canvas, Meta } from '@storybook/addon-docs';
import * as ComponentStories from './ColumnChartWithTrend.stories';
import LegendStory from '../../resources/LegendConfig.mdx';
import TooltipStory from '../../resources/TooltipConfig.mdx';

<Meta of={ComponentStories} />

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

<Canvas of={ComponentStories.LoadingPlaceholder} />

<TooltipStory of={ComponentStories.WithCustomTooltipConfig} additionalDescription={<><b>Note: </b>The <code>tooltipConfig</code> is used for both LineChart and ColumnChart.</>} />

<LegendStory of={ComponentStories.WithCustomLegendConfig} />

<br />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react';
import { complexDataSet, legendConfig } from '../../resources/DemoProps.js';
import { complexDataSet, legendConfig, tooltipConfig } from '../../resources/DemoProps.js';
import { ColumnChartWithTrend } from './ColumnChartWithTrend.js';

const meta = {
Expand All @@ -18,7 +18,6 @@ const meta = {
{
accessor: 'users',
label: 'Users',
formatter: (val) => val.toLocaleString(),
type: 'line',
},
{
Expand All @@ -44,3 +43,7 @@ export const LoadingPlaceholder: Story = {
export const WithCustomLegendConfig: Story = {
args: legendConfig,
};

export const WithCustomTooltipConfig: Story = {
args: tooltipConfig,
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { ThemingParameters, useStylesheet } from '@ui5/webcomponents-react-base'
import { clsx } from 'clsx';
import type { CSSProperties } from 'react';
import { forwardRef, useId } from 'react';
import type { TooltipProps, YAxisProps } from 'recharts';
import type { DefaultLegendContentProps, YAxisProps } from 'recharts';
import { DefaultLegendContent } from 'recharts';
import { useLongestYAxisLabel } from '../../hooks/useLongestYAxisLabel.js';
import { usePrepareDimensionsAndMeasures } from '../../hooks/usePrepareDimensionsAndMeasures.js';
import { usePrepareTrendMeasures } from '../../hooks/usePrepareTrendMeasures.js';
Expand Down Expand Up @@ -51,10 +52,7 @@ interface DimensionConfig extends IChartDimension {
}

export interface ColumnChartWithTrendProps
extends Omit<
IChartBaseProps<Omit<ICartesianChartConfig, 'secondYAxis' | 'secondYAxisConfig'>>,
'syncId' | 'tooltipConfig'
> {
extends Omit<IChartBaseProps<Omit<ICartesianChartConfig, 'secondYAxis' | 'secondYAxisConfig'>>, 'syncId'> {
/**
* An array of config objects. Each object will define one dimension of the chart.
*
Expand Down Expand Up @@ -101,8 +99,6 @@ const measureDefaults = {
opacity: 1,
};

const lineTooltipConfig = { wrapperStyle: { visibility: 'hidden' } } as TooltipProps<any, any>;

// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
type AvailableChartTypes = 'line' | 'bar' | string;
/**
Expand All @@ -120,12 +116,12 @@ const ColumnChartWithTrend = forwardRef<HTMLDivElement, ColumnChartWithTrendProp
onDataPointClick,
onLegendClick,
ChartPlaceholder,
tooltipConfig,
...rest
} = props;
const syncId = useId();

useStylesheet(content, ColumnChartWithTrend.displayName);

const chartConfig: ColumnChartWithTrendProps['chartConfig'] = {
yAxisVisible: false,
xAxisVisible: true,
Expand All @@ -147,21 +143,8 @@ const ColumnChartWithTrend = forwardRef<HTMLDivElement, ColumnChartWithTrendProp
measureDefaults,
);

const { lineMeasures, columnMeasures, columnDataset } = usePrepareTrendMeasures(measures, dataset);
const [yAxisWidth] = useLongestYAxisLabel(columnDataset, columnMeasures, chartConfig.legendPosition);

const columnTooltipConfig = {
formatter: (value, name, tooltipProps) => {
const line = lineMeasures.find(
(currLine) => currLine.type === 'line' && currLine.accessor === tooltipProps.dataKey,
);
if (line) {
return line.formatter(tooltipProps.payload[`__${line.accessor}`]);
}
const column = columnMeasures.find((currLine) => currLine.accessor === tooltipProps.dataKey);
return column.formatter(value, name, tooltipProps);
},
} as TooltipProps<any, any>;
const { lineMeasures, columnMeasures } = usePrepareTrendMeasures(measures);
const [yAxisWidth] = useLongestYAxisLabel(dataset, columnMeasures, chartConfig.legendPosition);

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

Expand All @@ -173,7 +156,7 @@ const ColumnChartWithTrend = forwardRef<HTMLDivElement, ColumnChartWithTrendProp
typeof onDataPointClick === 'function' || typeof onClick === 'function' ? 'has-click-handler' : undefined,
classNames.trendContainer,
)}
tooltipConfig={lineTooltipConfig}
tooltipConfig={{ ...tooltipConfig, wrapperStyle: { visibility: 'hidden' } }}
noAnimation={noAnimation}
loading={loading}
loadingDelay={loadingDelay}
Expand Down Expand Up @@ -201,7 +184,10 @@ const ColumnChartWithTrend = forwardRef<HTMLDivElement, ColumnChartWithTrendProp
classNames.chartContainer,
)}
onLegendClick={onLegendClick}
tooltipConfig={columnTooltipConfig}
tooltipConfig={{
includeHidden: true,
...tooltipConfig,
}}
noAnimation={noAnimation}
noLegend={noLegend}
loading={loading}
Expand All @@ -210,10 +196,13 @@ const ColumnChartWithTrend = forwardRef<HTMLDivElement, ColumnChartWithTrendProp
onDataPointClick={onDataPointClick}
syncId={syncId}
ChartPlaceholder={ChartPlaceholder ?? ColumnChartWithTrendPlaceholder}
dataset={columnDataset}
dataset={dataset}
measures={columnMeasures}
dimensions={dimensions}
chartConfig={chartConfig}
chartConfig={{
...chartConfig,
legendConfig: { ...chartConfig?.legendConfig, content: <DefaultLegendContentWithoutInactive /> },
}}
/>
</div>
);
Expand All @@ -222,3 +211,17 @@ const ColumnChartWithTrend = forwardRef<HTMLDivElement, ColumnChartWithTrendProp
ColumnChartWithTrend.displayName = 'ColumnChartWithTrend';

export { ColumnChartWithTrend };

/**
* Helper component to always keep legend items interactive.
* This is required, as otherwise internally hidden measures are greyed out in the Legend.
*/
const DefaultLegendContentWithoutInactive = (props: DefaultLegendContentProps) => {
const updatedPayload = props.payload.map((item) => {
return { ...item, inactive: undefined };
});
// @ts-expect-error: Type doesn't seem to allow class components which `DefaultLegendContent` is.
return <DefaultLegendContent {...props} payload={updatedPayload} />;
};

DefaultLegendContentWithoutInactive.displayName = 'DefaultLegendContentWithoutInactive';
1 change: 1 addition & 0 deletions packages/charts/src/components/ComposedChart/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ const ComposedChart = forwardRef<HTMLDivElement, ComposedChartProps>((props, ref
chartElementProps.strokeWidth = element.width;
chartElementProps.strokeOpacity = element.opacity;
chartElementProps.dot = element.showDot ?? !isBigDataSet;
chartElementProps.hide = element.hide;
break;
case 'bar':
chartElementProps.hide = element.hide;
Expand Down
4 changes: 2 additions & 2 deletions packages/charts/src/hooks/useLabelFormatter.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useCallback } from 'react';
import type { DefaultTooltipContentProps } from 'recharts';
import type { TooltipProps } from 'recharts';
import type { NameType, TooltipLabelFormatter, ValueType } from '../interfaces/index.js';

export const useLabelFormatter = (formatter: TooltipLabelFormatter) => {
return useCallback<TooltipLabelFormatter>(
(label: DefaultTooltipContentProps<ValueType, NameType>['label'], payload) => {
(label: TooltipProps<ValueType, NameType>['label'], payload) => {
if (typeof formatter === 'function') {
return formatter(label, payload);
}
Expand Down
25 changes: 4 additions & 21 deletions packages/charts/src/hooks/usePrepareTrendMeasures.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { useMemo } from 'react';
import { getValueByDataKey } from 'recharts/lib/util/ChartUtils.js';
import type { IChartMeasure } from '../interfaces/IChartMeasure.js';
import { defaultFormatter } from '../internal/defaults.js';

interface ITrendChartMeasure extends IChartMeasure {
type: 'line' | 'bar';
}

export const usePrepareTrendMeasures = (measures: ITrendChartMeasure[], dataset: Record<string, unknown>[]) =>
export const usePrepareTrendMeasures = (measures: ITrendChartMeasure[]) =>
useMemo(() => {
const lineMeasures = [];
const columnMeasures = [];
const columnDataset = [];

measures?.forEach((measure, index) => {
if (measure.type === 'bar') {
Expand Down Expand Up @@ -39,28 +37,13 @@ export const usePrepareTrendMeasures = (measures: ITrendChartMeasure[], dataset:
columnMeasures.push({
...measure,
opacity: 0,
hide: true,
hideDataLabel: true,
showDot: false,
formatter: defaultFormatter,
});
}
});

dataset?.forEach((data) => {
const reducedLineValues = {};

lineMeasures.forEach((line) => {
if (line.type === 'line') {
reducedLineValues[`__${line.accessor}`] = getValueByDataKey(data, line.accessor);
reducedLineValues[line.accessor] = 1;
}
});

columnDataset.push({
...data,
...reducedLineValues,
});
});

return { lineMeasures, columnMeasures, columnDataset };
}, [measures, dataset]);
return { lineMeasures, columnMeasures };
}, [measures]);
2 changes: 1 addition & 1 deletion packages/charts/src/resources/DemoProps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const tooltipConfig: IChartBaseProps = {
cursor: { stroke: 'red', strokeWidth: 2, fill: 'transparent' },
separator: ':~:',
// eslint-disable-next-line @typescript-eslint/no-unused-vars
formatter: (value, name, props) => {
formatter: (value, name, props, index, payload) => {
if (name === 'Users') {
return [`${value}👨‍💻`, 'Custom Name in Tooltip!'];
}
Expand Down
2 changes: 2 additions & 0 deletions packages/charts/src/resources/LegendConfig.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { Canvas } from '@storybook/addon-docs';
Via the `chartConfig.legendConfig` property you can override the configuration object for the internally used `recharts` Legend component.
You can find all possible configuration properties [here](https://recharts.org/en-US/api/Legend).

**Note**: It is possible to override the internally used Legend configurations. Use with caution!

<Canvas of={props.of} />

<details>
Expand Down
4 changes: 4 additions & 0 deletions packages/charts/src/resources/TooltipConfig.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { Canvas } from '@storybook/addon-docs';
Via the `tooltipConfig` prop you can override the configuration object for the internally used `recharts` Tooltip popover that is displayed when hovering over data points.
You can find all possible configuration properties [here](https://recharts.org/en-US/api/Tooltip).

**Note**: It is possible to override the internally used tooltip configurations. Use with caution!

<p>{props.additionalDescription}</p>

<Canvas of={props.of} />

<details>
Expand Down
Loading