Skip to content

Commit a343567

Browse files
refactor(Charts): Update ref API and color maps (#62)
BREAKING CHANGE `innerChartRef` is replaced by `ref` BREAKING CHANGE `sap_belize` and `sap_belize_plus` stylings are not longer exported BREAKING CHANGE Name of color variables for all themes changed. Now `sapUiChartAccent1` to `sapUiChartAccent12` plus `sapUiChartGood`, `sapUiChartBad` and `sapUiChartHighlight` are available
1 parent 0e42b87 commit a343567

34 files changed

+981
-948
lines changed

packages/charts/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@ui5/webcomponents-react-base": "0.4.1",
2121
"chart.js": "^2.8.0",
2222
"chartjs-plugin-datalabels": "^0.6.0",
23+
"get-best-contrast-color": "^0.3.1",
2324
"is-mergeable-object": "^1.1.0",
2425
"react-chartjs-2": "^2.7.6",
2526
"react-content-loader": "^4.2.1"

packages/charts/src/components/BarChart/BarChart.test.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { renderThemedComponent } from '@shared/tests/utils';
1+
import { mountThemedComponent, renderThemedComponent } from '@shared/tests/utils';
22
import * as React from 'react';
33
import { datasets, labels, singleDataset } from '../../test/resources/ChartProps';
44
import { BarChart } from './index';
@@ -16,6 +16,12 @@ describe('BarChart', () => {
1616
renderThemedComponent(<BarChart labels={labels} datasets={singleDataset} valueAxisFormatter={(d) => `${d}%`} />);
1717
});
1818

19+
test('with Ref', () => {
20+
const ref = React.createRef();
21+
mountThemedComponent(<BarChart ref={ref} labels={labels} datasets={singleDataset} />);
22+
expect(ref.current.hasOwnProperty('chartInstance')).toBe(true);
23+
});
24+
1925
test('stacked', () => {
2026
renderThemedComponent(
2127
<BarChart

packages/charts/src/components/BarChart/demo.stories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ storiesOf('Charts | BarChart', module)
3939
datasets={datasets}
4040
getElementAtEvent={action('getElementAtEvent')}
4141
loading={boolean('loading')}
42+
noLegend={boolean('noLegend')}
4243
/>
4344
))
4445
.add('with Formatter', () => (
Lines changed: 89 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,66 @@
1-
import React, { PureComponent } from 'react';
1+
import { useConsolidatedRef } from '@ui5/webcomponents-react-base';
2+
import bestContrast from 'get-best-contrast-color';
3+
import React, { forwardRef, Ref, RefObject, useCallback, useEffect, useRef, useMemo } from 'react';
24
import { HorizontalBar } from 'react-chartjs-2';
3-
import { populateData } from '../../util/populateData';
4-
import { ChartInternalProps } from '../../interfaces/ChartInternalProps';
5+
import { useTheme } from 'react-jss';
56
import { DEFAULT_OPTIONS } from '../../config';
6-
import { formatTooltipLabel, getTextWidth, mergeConfig } from '../../util/utils';
77
import { ChartBaseProps } from '../../interfaces/ChartBaseProps';
8+
import { withChartContainer } from '../../internal/ChartContainer/withChartContainer';
89
import { ChartBaseDefaultProps } from '../../util/ChartBaseDefaultProps';
9-
import { LOG_LEVEL, Logger } from '@ui5/webcomponents-react-base';
10-
import { withChartContainer } from '../ChartContainer/withChartContainer';
10+
import { useChartData } from '../../util/populateData';
11+
import { formatTooltipLabel, getTextWidth, useMergedConfig } from '../../util/utils';
1112
import { BarChartPlaceholder } from './Placeholder';
1213

1314
export interface BarChartPropTypes extends ChartBaseProps {}
1415

15-
@withChartContainer
16-
export class BarChart extends PureComponent<BarChartPropTypes> {
17-
static defaultProps = {
18-
...ChartBaseDefaultProps,
19-
internalNoMerge: true
20-
};
21-
22-
static LoadingPlaceholder = BarChartPlaceholder;
23-
24-
// private static checkIfDataLabelIsInScale(context) {
25-
// const chartElement = getCurrentChartElementFromContext(context);
26-
// const maxXAxis = chartElement._xScale.width + chartElement._xScale.left;
27-
// const chartWidth = chartElement._model.x;
28-
// return chartWidth / maxXAxis > 0.9;
29-
// }
30-
31-
getAnchor = (context) => {
32-
const { valueAxisFormatter } = this.props;
33-
34-
try {
35-
const datasetMeta = context.chart.getDatasetMeta(context.datasetIndex);
36-
const dataSetLength = context.chart.data.datasets.length;
37-
const xAxisId = datasetMeta.xAxisID;
38-
const yAxisId = datasetMeta.yAxisID;
39-
40-
const xAxis = context.chart.scales[xAxisId];
41-
const yAxis = context.chart.scales[yAxisId];
42-
if (xAxis.options.stacked) {
43-
if (!yAxis.options.stacked) {
44-
return 'end';
45-
}
46-
if (dataSetLength - 1 === context.datasetIndex) {
47-
// highest stack
48-
return 'end';
49-
} else {
50-
const chartElement = datasetMeta.data[context.dataIndex];
51-
const barWidth = Math.abs(chartElement._model.base - chartElement._model.x);
52-
const text = valueAxisFormatter(context.dataset.data[context.dataIndex]);
53-
const textWidth = getTextWidth(text);
54-
if (barWidth < 1.5 * textWidth) {
55-
// arbitrary estimate
56-
return 'start';
57-
}
58-
59-
return 'center';
60-
}
61-
}
62-
} catch (e) {
63-
Logger.log(LOG_LEVEL.WARNING, e.message);
64-
}
65-
return 'end';
66-
};
67-
68-
render() {
16+
const BarChart = withChartContainer(
17+
forwardRef((props: BarChartPropTypes, ref: Ref<any>) => {
6918
const {
7019
labels,
7120
datasets,
72-
theme,
7321
options,
7422
categoryAxisFormatter,
7523
valueAxisFormatter,
7624
getDatasetAtEvent,
7725
getElementAtEvent,
78-
colors
79-
} = this.props as BarChartPropTypes & ChartInternalProps;
26+
colors,
27+
width,
28+
height,
29+
noLegend
30+
} = props as BarChartPropTypes;
8031

81-
const bar = populateData(labels, datasets, colors, theme.theme);
32+
const theme: any = useTheme();
33+
const data = useChartData(labels, datasets, colors, theme.theme);
8234

83-
const mergedOptions = mergeConfig(
84-
{
85-
layout: {
86-
padding: {
87-
right: 15
88-
}
89-
},
35+
const chartRef = useConsolidatedRef<any>(ref);
36+
const legendRef: RefObject<HTMLDivElement> = useRef();
37+
38+
const handleLegendItemPress = useCallback(
39+
(e) => {
40+
const clickTarget = (e.currentTarget as unknown) as HTMLLIElement;
41+
const datasetIndex = parseInt(clickTarget.dataset.datasetindex);
42+
const { chartInstance } = chartRef.current;
43+
const meta = chartInstance.getDatasetMeta(datasetIndex);
44+
meta.hidden = meta.hidden === null ? !chartInstance.data.datasets[datasetIndex].hidden : null;
45+
chartInstance.update();
46+
clickTarget.style.textDecoration = meta.hidden ? 'line-through' : 'unset';
47+
},
48+
[legendRef.current, chartRef.current]
49+
);
50+
51+
useEffect(() => {
52+
if (noLegend) {
53+
legendRef.current.innerHTML = '';
54+
} else {
55+
legendRef.current.innerHTML = chartRef.current.chartInstance.generateLegend();
56+
legendRef.current.querySelectorAll('li').forEach((legendItem) => {
57+
legendItem.addEventListener('click', handleLegendItemPress);
58+
});
59+
}
60+
}, [chartRef.current, legendRef.current, noLegend]);
61+
62+
const barChartDefaultConfig = useMemo(() => {
63+
return {
9064
scales: {
9165
xAxes: [
9266
{
@@ -112,52 +86,55 @@ export class BarChart extends PureComponent<BarChartPropTypes> {
11286
},
11387
plugins: {
11488
datalabels: {
115-
// display: (context) => {
116-
// const anchor = this.getAnchor(context);
117-
// if (anchor === 'start') {
118-
// // edge case
119-
// const datasetMeta = context.chart.getDatasetMeta(context.datasetIndex);
120-
// const chartElement = datasetMeta.data[context.dataIndex];
121-
// const barWidth = Math.abs(chartElement._model.base - chartElement._model.x);
122-
// const text = valueAxisFormatter(context.dataset.data[context.dataIndex]);
123-
// const textWidth = getTextWidth(text);
124-
// if (barWidth < textWidth - 5) {
125-
// // arbitrary 5px tolerance
126-
// return false;
127-
// }
128-
// }
129-
// return true;
130-
// },
131-
anchor: this.getAnchor,
132-
align: 'end',
133-
offset: 0,
89+
anchor: 'end',
90+
align: 'start',
91+
clip: true,
92+
display: (context) => {
93+
const datasetMeta = context.chart.getDatasetMeta(context.datasetIndex);
94+
const dataMeta = datasetMeta.data[context.dataIndex];
95+
const width = dataMeta._view.x - dataMeta._view.base;
96+
const formattedValue = valueAxisFormatter(context.dataset.data[context.dataIndex]);
97+
const textWidth = getTextWidth(formattedValue) + 4; // offset
98+
return width >= textWidth;
99+
},
134100
formatter: valueAxisFormatter,
135101
color: (context) => {
136-
const anchor = this.getAnchor(context);
137-
if (anchor === 'end') {
138-
return '#666';
139-
} else {
140-
return '#fff';
141-
}
102+
const datasetMeta = context.chart.getDatasetMeta(context.datasetIndex);
103+
const dataMeta = datasetMeta.data[context.dataIndex];
104+
return bestContrast(dataMeta._view.backgroundColor, [
105+
/* sapUiBaseText */ '#32363a',
106+
/* sapUiContentContrastTextColor */ '#ffffff'
107+
]);
142108
}
143109
}
144110
}
145-
},
146-
options
147-
);
111+
};
112+
}, [valueAxisFormatter, categoryAxisFormatter]);
113+
114+
const mergedOptions = useMergedConfig(barChartDefaultConfig, options);
148115

149116
return (
150-
<HorizontalBar
151-
ref={this.props.innerChartRef}
152-
data={bar}
153-
height={this.props.height}
154-
width={this.props.width}
155-
options={mergedOptions}
156-
// @ts-ignore
157-
getDatasetAtEvent={getDatasetAtEvent}
158-
// @ts-ignore
159-
getElementAtEvent={getElementAtEvent}
160-
/>
117+
<>
118+
<HorizontalBar
119+
ref={chartRef}
120+
data={data}
121+
height={height}
122+
width={width}
123+
options={mergedOptions}
124+
getDatasetAtEvent={getDatasetAtEvent}
125+
getElementAtEvent={getElementAtEvent}
126+
/>
127+
<div ref={legendRef} className="legend" />
128+
</>
161129
);
162-
}
163-
}
130+
})
131+
);
132+
133+
// @ts-ignore
134+
BarChart.LoadingPlaceholder = BarChartPlaceholder;
135+
BarChart.defaultProps = {
136+
...ChartBaseDefaultProps
137+
};
138+
BarChart.displayName = 'BarChart';
139+
140+
export { BarChart };

packages/charts/src/components/ChartContainer/withChartContainer.tsx

Lines changed: 0 additions & 52 deletions
This file was deleted.

packages/charts/src/components/ColumnChart/demo.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const datasets = [
1212

1313
{
1414
label: 'Probable/Committed',
15-
data: [5, 9, 8, 8, 5, 5, 4]
15+
data: [5, 9, 8, 8, 5, 5, 1]
1616
}
1717
];
1818

0 commit comments

Comments
 (0)