import React, { useState } from 'react';
import { Container, SvgWrapper } from 'app/shared/modules/chart/core';
import { generateStackedBars } from 'app/shared/modules/chart/bar/compute';
import { Grid } from 'app/shared/modules/chart/core/axes';
import { Axis } from 'app/shared/modules/chart/core/axes/Axis';
import { computeAxisTicks, computeTicksValues } from 'app/shared/modules/chart/core/axes/compute/cartesian';
import { Group } from 'app/shared/modules/chart/core/containers/Group';
import { maxBy, round } from 'lodash';
import { FillableBar } from 'app/shared/modules/chart/bar/FillableBar';
import { useDispatch, useSelector } from 'react-redux';
import { selectForecastChartData } from '../../store/forecast.selectors';
import { forecastSelectAggregation } from '../../store/forecast.actions';
import { FORECAST } from '../../store/forecast.constants';
import { Arrow } from 'app/shared/modules/chart/arrow/arrow';
import moneyPipe from 'app/shared/pipes/money.pipe';
import datePipe from 'app/shared/pipes/date.pipe';
import { Tooltip } from 'app/shared/modules/chart/core/tooltip/Tooltip';
import { ForecastChartTooltip } from './forecastChartTooltip';
import { getScreenWidth } from 'app/shared/store/ui/uiSelectors';
import { UI } from 'app/shared/store/ui/ui.constants';
export interface Props {
margin?: { top: number, right: number, bottom: number, left: number };
tickCount?: number;
gridPadding?: number;
paddingRight?: number;
width: number;
height: number;
}
const ForecastChart = ({
margin = { top: 10, right: 15, bottom: 30, left: 40 },
gridPadding = 16,
tickCount = 4,
paddingRight = 16,
width,
height,
}: Props) => {
const chartData = useSelector(selectForecastChartData);
const { maxValue, barSeries, selectedId } = chartData;
const isLargeScreen = useSelector(getScreenWidth) >= UI.BREAK_POINTS.LG;
const [ tooltip, setTooltip ] = useState({
isVisible: false,
content: {
currentForecast: '',
previousForecast: '',
date: '',
ratio: '',
trend: '',
},
position: {},
});
const showTooltip = (data) => {
setTooltip({
isVisible: true,
content: data.tooltipContent,
position: data.tooltipPosition,
});
};
const hideTooltip = () => {
setTooltip({
...tooltip,
isVisible: false,
});
};
const calcWidth = width - margin.left - margin.right - gridPadding;
const calcHeight = height - margin.top - margin.bottom - gridPadding;
const xScaleStartOffset = 10;
const yAxisTextOffset = 3;
const barWidth = 16;
const yTicksValues = computeTicksValues(maxValue, tickCount);
const maxTickValue = maxBy(yTicksValues);
const maxScale = maxValue > maxTickValue? maxValue : maxTickValue;
const options = {
barSeries,
keys: [ 'value' ],
minValue: 0,
maxValue: maxScale,
width: calcWidth,
height: calcHeight,
padding: 0,
innerPadding: 0,
};
const barChart = generateStackedBars(options);
const centerOffset = (calcWidth - (barChart.xScale((barSeries.length - 1).toString())! + barChart.xScale('0')!)) / 2 - xScaleStartOffset;
const horizontalTicks = computeAxisTicks({
width: calcWidth,
height: calcHeight,
scale: barChart.xScale,
values: barChart.xScale.domain(),
getLabel: (index) => barSeries[index].label.toString(),
position: 'bottom',
isSelected: (value) => barSeries[value].id === selectedId,
});
const verticalTicks = computeAxisTicks({
width: calcWidth,
height: calcHeight,
scale: barChart.yScale,
values: yTicksValues,
getLabel: (value) => value.toLocaleString('en-US'),
position: 'left',
});
const tooltipOffset = {
bottom: 60,
left: centerOffset + gridPadding + margin.left + barWidth / 2 + 8,
right: centerOffset + gridPadding + margin.right + barWidth / 2,
};
const bars = barChart.bars.map((bar, index) => {
const roundedFilledHeight = round(bar.height * round(bar.data.data.filledRatio / 100, 2));
bar.filledHeight = roundedFilledHeight !== null && roundedFilledHeight !== undefined
? roundedFilledHeight
: bar.height;
const onEnter = () => {
const tooltipPosition = isLargeScreen || index < 1 ? {
left: bar.x + tooltipOffset.left,
bottom: tooltipOffset.bottom,
} : {
right: width - bar.x - tooltipOffset.right,
bottom: tooltipOffset.bottom,
};
const tooltipContent = {
currentForecast: moneyPipe.format(bar.data.value),
previousForecast: moneyPipe.format(bar.data.data.previousValue),
date: datePipe.formatShortMonthWithYear(bar.data.data.date),
ratio: bar.data.data.filledRatio,
trend: (bar.data.value - bar.data.data.previousValue) / bar.data.value,
};
showTooltip({ tooltipPosition, tooltipContent });
};
const onLeave = () => {
hideTooltip();
};
const dispatch = useDispatch();
if (bar.data.data.type === FORECAST.TYPE_PAST) {
return (
);
}
return (
dispatch(forecastSelectAggregation(data.data.id))}
{...bar}
selected={bar.data.data.id === selectedId}
animate
onEnter={onEnter}
onLeave={onLeave}
width={barWidth}
/>
);
});
const arrows = barChart.bars.map((bar) => {
const { x, y, key } = bar;
const { trend } = bar.data.data;
return (
);
});
return (
{() => (
{/* @ts-ignore*/}
{bars}
{arrows}
USD
/DAY
)}
{/* @ts-ignore*/}
{() => }
);
};
export { ForecastChart };