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 };