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

Charts Refactoring #62

Merged
merged 12 commits into from
Jul 19, 2019
1 change: 1 addition & 0 deletions packages/charts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@ui5/webcomponents-react-base": "0.4.1",
"chart.js": "^2.8.0",
"chartjs-plugin-datalabels": "^0.6.0",
"get-best-contrast-color": "^0.3.1",
"is-mergeable-object": "^1.1.0",
"react-chartjs-2": "^2.7.6",
"react-content-loader": "^4.2.1"
Expand Down
8 changes: 7 additions & 1 deletion packages/charts/src/components/BarChart/BarChart.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { renderThemedComponent } from '@shared/tests/utils';
import { mountThemedComponent, renderThemedComponent } from '@shared/tests/utils';
import * as React from 'react';
import { datasets, labels, singleDataset } from '../../test/resources/ChartProps';
import { BarChart } from './index';
Expand All @@ -16,6 +16,12 @@ describe('BarChart', () => {
renderThemedComponent(<BarChart labels={labels} datasets={singleDataset} valueAxisFormatter={(d) => `${d}%`} />);
});

test('with Ref', () => {
const ref = React.createRef();
mountThemedComponent(<BarChart ref={ref} labels={labels} datasets={singleDataset} />);
expect(ref.current.hasOwnProperty('chartInstance')).toBe(true);
});

test('stacked', () => {
renderThemedComponent(
<BarChart
Expand Down
1 change: 1 addition & 0 deletions packages/charts/src/components/BarChart/demo.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ storiesOf('Charts | BarChart', module)
datasets={datasets}
getElementAtEvent={action('getElementAtEvent')}
loading={boolean('loading')}
noLegend={boolean('noLegend')}
/>
))
.add('with Formatter', () => (
Expand Down
201 changes: 89 additions & 112 deletions packages/charts/src/components/BarChart/index.tsx
Original file line number Diff line number Diff line change
@@ -1,92 +1,66 @@
import React, { PureComponent } from 'react';
import { useConsolidatedRef } from '@ui5/webcomponents-react-base';
import bestContrast from 'get-best-contrast-color';
import React, { forwardRef, Ref, RefObject, useCallback, useEffect, useRef, useMemo } from 'react';
import { HorizontalBar } from 'react-chartjs-2';
import { populateData } from '../../util/populateData';
import { ChartInternalProps } from '../../interfaces/ChartInternalProps';
import { useTheme } from 'react-jss';
import { DEFAULT_OPTIONS } from '../../config';
import { formatTooltipLabel, getTextWidth, mergeConfig } from '../../util/utils';
import { ChartBaseProps } from '../../interfaces/ChartBaseProps';
import { withChartContainer } from '../../internal/ChartContainer/withChartContainer';
import { ChartBaseDefaultProps } from '../../util/ChartBaseDefaultProps';
import { LOG_LEVEL, Logger } from '@ui5/webcomponents-react-base';
import { withChartContainer } from '../ChartContainer/withChartContainer';
import { useChartData } from '../../util/populateData';
import { formatTooltipLabel, getTextWidth, useMergedConfig } from '../../util/utils';
import { BarChartPlaceholder } from './Placeholder';

export interface BarChartPropTypes extends ChartBaseProps {}

@withChartContainer
export class BarChart extends PureComponent<BarChartPropTypes> {
static defaultProps = {
...ChartBaseDefaultProps,
internalNoMerge: true
};

static LoadingPlaceholder = BarChartPlaceholder;

// private static checkIfDataLabelIsInScale(context) {
// const chartElement = getCurrentChartElementFromContext(context);
// const maxXAxis = chartElement._xScale.width + chartElement._xScale.left;
// const chartWidth = chartElement._model.x;
// return chartWidth / maxXAxis > 0.9;
// }

getAnchor = (context) => {
const { valueAxisFormatter } = this.props;

try {
const datasetMeta = context.chart.getDatasetMeta(context.datasetIndex);
const dataSetLength = context.chart.data.datasets.length;
const xAxisId = datasetMeta.xAxisID;
const yAxisId = datasetMeta.yAxisID;

const xAxis = context.chart.scales[xAxisId];
const yAxis = context.chart.scales[yAxisId];
if (xAxis.options.stacked) {
if (!yAxis.options.stacked) {
return 'end';
}
if (dataSetLength - 1 === context.datasetIndex) {
// highest stack
return 'end';
} else {
const chartElement = datasetMeta.data[context.dataIndex];
const barWidth = Math.abs(chartElement._model.base - chartElement._model.x);
const text = valueAxisFormatter(context.dataset.data[context.dataIndex]);
const textWidth = getTextWidth(text);
if (barWidth < 1.5 * textWidth) {
// arbitrary estimate
return 'start';
}

return 'center';
}
}
} catch (e) {
Logger.log(LOG_LEVEL.WARNING, e.message);
}
return 'end';
};

render() {
const BarChart = withChartContainer(
forwardRef((props: BarChartPropTypes, ref: Ref<any>) => {
const {
labels,
datasets,
theme,
options,
categoryAxisFormatter,
valueAxisFormatter,
getDatasetAtEvent,
getElementAtEvent,
colors
} = this.props as BarChartPropTypes & ChartInternalProps;
colors,
width,
height,
noLegend
} = props as BarChartPropTypes;

const bar = populateData(labels, datasets, colors, theme.theme);
const theme: any = useTheme();
const data = useChartData(labels, datasets, colors, theme.theme);

const mergedOptions = mergeConfig(
{
layout: {
padding: {
right: 15
}
},
const chartRef = useConsolidatedRef<any>(ref);
const legendRef: RefObject<HTMLDivElement> = useRef();

const handleLegendItemPress = useCallback(
(e) => {
const clickTarget = (e.currentTarget as unknown) as HTMLLIElement;
const datasetIndex = parseInt(clickTarget.dataset.datasetindex);
const { chartInstance } = chartRef.current;
const meta = chartInstance.getDatasetMeta(datasetIndex);
meta.hidden = meta.hidden === null ? !chartInstance.data.datasets[datasetIndex].hidden : null;
chartInstance.update();
clickTarget.style.textDecoration = meta.hidden ? 'line-through' : 'unset';
},
[legendRef.current, chartRef.current]
);

useEffect(() => {
if (noLegend) {
legendRef.current.innerHTML = '';
} else {
legendRef.current.innerHTML = chartRef.current.chartInstance.generateLegend();
legendRef.current.querySelectorAll('li').forEach((legendItem) => {
legendItem.addEventListener('click', handleLegendItemPress);
});
}
}, [chartRef.current, legendRef.current, noLegend]);

const barChartDefaultConfig = useMemo(() => {
return {
scales: {
xAxes: [
{
Expand All @@ -112,52 +86,55 @@ export class BarChart extends PureComponent<BarChartPropTypes> {
},
plugins: {
datalabels: {
// display: (context) => {
// const anchor = this.getAnchor(context);
// if (anchor === 'start') {
// // edge case
// const datasetMeta = context.chart.getDatasetMeta(context.datasetIndex);
// const chartElement = datasetMeta.data[context.dataIndex];
// const barWidth = Math.abs(chartElement._model.base - chartElement._model.x);
// const text = valueAxisFormatter(context.dataset.data[context.dataIndex]);
// const textWidth = getTextWidth(text);
// if (barWidth < textWidth - 5) {
// // arbitrary 5px tolerance
// return false;
// }
// }
// return true;
// },
anchor: this.getAnchor,
align: 'end',
offset: 0,
anchor: 'end',
align: 'start',
clip: true,
display: (context) => {
const datasetMeta = context.chart.getDatasetMeta(context.datasetIndex);
const dataMeta = datasetMeta.data[context.dataIndex];
const width = dataMeta._view.x - dataMeta._view.base;
const formattedValue = valueAxisFormatter(context.dataset.data[context.dataIndex]);
const textWidth = getTextWidth(formattedValue) + 4; // offset
return width >= textWidth;
},
formatter: valueAxisFormatter,
color: (context) => {
const anchor = this.getAnchor(context);
if (anchor === 'end') {
return '#666';
} else {
return '#fff';
}
const datasetMeta = context.chart.getDatasetMeta(context.datasetIndex);
const dataMeta = datasetMeta.data[context.dataIndex];
return bestContrast(dataMeta._view.backgroundColor, [
/* sapUiBaseText */ '#32363a',
/* sapUiContentContrastTextColor */ '#ffffff'
]);
}
}
}
},
options
);
};
}, [valueAxisFormatter, categoryAxisFormatter]);

const mergedOptions = useMergedConfig(barChartDefaultConfig, options);

return (
<HorizontalBar
ref={this.props.innerChartRef}
data={bar}
height={this.props.height}
width={this.props.width}
options={mergedOptions}
// @ts-ignore
getDatasetAtEvent={getDatasetAtEvent}
// @ts-ignore
getElementAtEvent={getElementAtEvent}
/>
<>
<HorizontalBar
ref={chartRef}
data={data}
height={height}
width={width}
options={mergedOptions}
getDatasetAtEvent={getDatasetAtEvent}
getElementAtEvent={getElementAtEvent}
/>
<div ref={legendRef} className="legend" />
</>
);
}
}
})
);

// @ts-ignore
BarChart.LoadingPlaceholder = BarChartPlaceholder;
BarChart.defaultProps = {
...ChartBaseDefaultProps
};
BarChart.displayName = 'BarChart';

export { BarChart };

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const datasets = [

{
label: 'Probable/Committed',
data: [5, 9, 8, 8, 5, 5, 4]
data: [5, 9, 8, 8, 5, 5, 1]
}
];

Expand Down
Loading