Move cost overview chart legend to separate component

This commit is contained in:
Brenda Sukh
2020-11-24 16:56:20 -05:00
parent 86ab16244a
commit bf9d87dc16
3 changed files with 163 additions and 116 deletions
@@ -24,24 +24,15 @@ import {
Tab,
Tabs,
} from '@material-ui/core';
import { CostGrowth } from '../CostGrowth';
import { CostOverviewChart } from './CostOverviewChart';
import { CostOverviewByProduct } from './CostOverviewByProduct';
import { CostOverviewByProductChart } from './CostOverviewByProductChart';
import { CostOverviewHeader } from './CostOverviewHeader';
import { LegendItem } from '../LegendItem';
import { MetricSelect } from '../MetricSelect';
import { PeriodSelect } from '../PeriodSelect';
import {
useScroll,
useFilters,
useConfig,
useLastCompleteBillingDate,
} from '../../hooks';
import { useScroll, useFilters, useConfig } from '../../hooks';
import { mapFiltersToProps } from './selector';
import { DefaultNavigation } from '../../utils/navigation';
import { formatPercent } from '../../utils/formatters';
import { findAlways } from '../../utils/assert';
import { getComparedChange } from '../../utils/change';
import { Cost, CostInsightsTheme, MetricData } from '../../types';
import { useOverviewTabsStyles } from '../../utils/styles';
@@ -56,7 +47,6 @@ export const CostOverviewCard = ({
}: CostOverviewCardProps) => {
const theme = useTheme<CostInsightsTheme>();
const config = useConfig();
const lastCompleteBillingDate = useLastCompleteBillingDate();
const [tabIndex, setTabIndex] = useState(0);
const { ScrollAnchor } = useScroll(DefaultNavigation.CostOverviewCard);
@@ -67,19 +57,11 @@ export const CostOverviewCard = ({
const metric = filters.metric
? findAlways(config.metrics, m => m.kind === filters.metric)
: null;
const comparedChange = metricData
? getComparedChange(
dailyCostData,
metricData,
filters.duration,
lastCompleteBillingDate,
)
: null;
const styles = useOverviewTabsStyles(theme);
const tabs = [
{ id: 'overview', label: 'OVERVIEW' },
{ id: 'breakdown', label: 'BREAKDOWN BY PRODUCT' },
{ id: 'overview', label: 'Total cost' },
{ id: 'breakdown', label: 'Breakdown by product' },
];
const OverviewTabs = () => {
@@ -104,34 +86,8 @@ export const CostOverviewCard = ({
);
};
const OverviewLegend = () => {
return (
<Box display="flex" flexDirection="row">
<Box mr={2}>
<LegendItem title="Cost Trend" markerColor={theme.palette.blue}>
{formatPercent(dailyCostData.change!.ratio)}
</LegendItem>
</Box>
{metric && metricData && comparedChange && (
<>
<Box mr={2}>
<LegendItem
title={`${metric.name} Trend`}
markerColor={theme.palette.magenta}
>
{formatPercent(metricData.change.ratio)}
</LegendItem>
</Box>
<LegendItem
title={comparedChange.ratio <= 0 ? 'Your Savings' : 'Your Excess'}
>
<CostGrowth change={comparedChange} duration={filters.duration} />
</LegendItem>
</>
)}
</Box>
);
};
// Metrics can only be selected on the total cost graph
const showMetricSelect = config.metrics.length && tabIndex === 0;
return (
<Card style={{ position: 'relative' }}>
@@ -146,20 +102,19 @@ export const CostOverviewCard = ({
<Divider />
<Box ml={2} my={1} display="flex" flexDirection="column">
{tabIndex === 0 ? (
<>
<OverviewLegend />
<CostOverviewChart
dailyCostData={dailyCostData}
metric={metric}
metricData={metricData}
/>
</>
<CostOverviewChart
dailyCostData={dailyCostData}
metric={metric}
metricData={metricData}
/>
) : (
<CostOverviewByProduct dailyCostData={dailyCostData} />
<CostOverviewByProductChart
costsByProduct={dailyCostData.groupedCosts!}
/>
)}
</Box>
<Box display="flex" justifyContent="flex-end" alignItems="center">
{config.metrics.length && tabIndex === 0 && (
{showMetricSelect && (
<MetricSelect
metric={filters.metric}
metrics={config.metrics}
@@ -16,7 +16,7 @@
import React from 'react';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { useTheme } from '@material-ui/core';
import { useTheme, Box } from '@material-ui/core';
import {
ComposedChart,
ContentRenderer,
@@ -50,6 +50,7 @@ import {
import { useCostOverviewStyles as useStyles } from '../../utils/styles';
import { groupByDate, toDataMax, trendFrom } from '../../utils/charts';
import { aggregationSort } from '../../utils/sort';
import { CostOverviewLegend } from './CostOverviewLegend';
dayjs.extend(utc);
@@ -135,67 +136,74 @@ export const CostOverviewChart = ({
};
return (
<ResponsiveContainer
width={responsive ? '100%' : styles.container.width}
height={styles.container.height}
className="cost-overview-chart"
>
<ComposedChart margin={styles.chart.margin} data={chartData}>
<CartesianGrid stroke={styles.cartesianGrid.stroke} />
<XAxis
dataKey="date"
domain={['dataMin', 'dataMax']}
tickFormatter={overviewGraphTickFormatter}
tickCount={6}
type="number"
stroke={styles.axis.fill}
/>
<YAxis
domain={[() => 0, 'dataMax']}
tick={{ fill: styles.axis.fill }}
tickFormatter={formatGraphValue}
width={styles.yAxis.width}
yAxisId={data.dailyCost.dataKey}
/>
{metric && (
<YAxis
hide
domain={[() => 0, toDataMax(data.metric.dataKey, chartData)]}
width={styles.yAxis.width}
yAxisId={data.metric.dataKey}
<Box display="flex" flexDirection="column">
<CostOverviewLegend
dailyCostData={dailyCostData}
metric={metric}
metricData={metricData}
/>
<ResponsiveContainer
width={responsive ? '100%' : styles.container.width}
height={styles.container.height}
className="cost-overview-chart"
>
<ComposedChart margin={styles.chart.margin} data={chartData}>
<CartesianGrid stroke={styles.cartesianGrid.stroke} />
<XAxis
dataKey="date"
domain={['dataMin', 'dataMax']}
tickFormatter={overviewGraphTickFormatter}
tickCount={6}
type="number"
stroke={styles.axis.fill}
/>
<YAxis
domain={[() => 0, 'dataMax']}
tick={{ fill: styles.axis.fill }}
tickFormatter={formatGraphValue}
width={styles.yAxis.width}
yAxisId={data.dailyCost.dataKey}
/>
{metric && (
<YAxis
hide
domain={[() => 0, toDataMax(data.metric.dataKey, chartData)]}
width={styles.yAxis.width}
yAxisId={data.metric.dataKey}
/>
)}
<Area
dataKey={data.dailyCost.dataKey}
isAnimationActive={false}
fill={theme.palette.blue}
fillOpacity={0.4}
stroke="none"
yAxisId={data.dailyCost.dataKey}
/>
)}
<Area
dataKey={data.dailyCost.dataKey}
isAnimationActive={false}
fill={theme.palette.blue}
fillOpacity={0.4}
stroke="none"
yAxisId={data.dailyCost.dataKey}
/>
<Line
activeDot={false}
dataKey="trend"
dot={false}
isAnimationActive={false}
label={false}
strokeWidth={2}
stroke={theme.palette.blue}
yAxisId={data.dailyCost.dataKey}
/>
{metric && (
<Line
dataKey={data.metric.dataKey}
activeDot={false}
dataKey="trend"
dot={false}
isAnimationActive={false}
label={false}
strokeWidth={2}
stroke={theme.palette.magenta}
yAxisId={data.metric.dataKey}
stroke={theme.palette.blue}
yAxisId={data.dailyCost.dataKey}
/>
)}
<RechartsTooltip content={tooltipRenderer} animationDuration={100} />
</ComposedChart>
</ResponsiveContainer>
{metric && (
<Line
dataKey={data.metric.dataKey}
dot={false}
isAnimationActive={false}
label={false}
strokeWidth={2}
stroke={theme.palette.magenta}
yAxisId={data.metric.dataKey}
/>
)}
<RechartsTooltip content={tooltipRenderer} animationDuration={100} />
</ComposedChart>
</ResponsiveContainer>
</Box>
);
};
@@ -0,0 +1,84 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { PropsWithChildren } from 'react';
import { Box, useTheme } from '@material-ui/core';
import { LegendItem } from '../LegendItem';
import {
CostInsightsTheme,
MetricData,
Maybe,
Cost,
Metric,
} from '../../types';
import { useLastCompleteBillingDate, useFilters } from '../../hooks';
import { getComparedChange } from '../../utils/change';
import { mapFiltersToProps } from './selector';
import { formatPercent } from '../../utils/formatters';
import { CostGrowth } from '../CostGrowth';
type CostOverviewLegendProps = {
metric: Maybe<Metric>;
metricData: Maybe<MetricData>;
dailyCostData: Cost;
};
export const CostOverviewLegend = ({
dailyCostData,
metric,
metricData,
}: PropsWithChildren<CostOverviewLegendProps>) => {
const theme = useTheme<CostInsightsTheme>();
const lastCompleteBillingDate = useLastCompleteBillingDate();
const { duration } = useFilters(mapFiltersToProps);
const comparedChange = metricData
? getComparedChange(
dailyCostData,
metricData,
duration,
lastCompleteBillingDate,
)
: null;
return (
<Box display="flex" flexDirection="row">
<Box mr={2}>
<LegendItem title="Cost Trend" markerColor={theme.palette.blue}>
{formatPercent(dailyCostData.change!.ratio)}
</LegendItem>
</Box>
{metric && metricData && comparedChange && (
<>
<Box mr={2}>
<LegendItem
title={`${metric.name} Trend`}
markerColor={theme.palette.magenta}
>
{formatPercent(metricData.change.ratio)}
</LegendItem>
</Box>
<LegendItem
title={comparedChange.ratio <= 0 ? 'Your Savings' : 'Your Excess'}
>
<CostGrowth change={comparedChange} duration={duration} />
</LegendItem>
</>
)}
</Box>
);
};