Move cost overview chart legend to separate component
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user