diff --git a/src/components/Chart/Chart.tsx b/src/components/Chart/Chart.tsx index dfd2a2f989..e30f076a15 100644 --- a/src/components/Chart/Chart.tsx +++ b/src/components/Chart/Chart.tsx @@ -26,7 +26,7 @@ import { } from "src/components/Chart/IntersectionHelper"; import { formatCurrency, formatNumber, trim } from "src/helpers"; import { getFloat } from "src/helpers/NumberHelper"; -import { getMaximumValue, objectHasProperty } from "src/helpers/subgraph/ProtocolMetricsHelper"; +import { getMaximumValue, getMinimumValue, objectHasProperty } from "src/helpers/subgraph/ProtocolMetricsHelper"; import { ChartCard, DEFAULT_HEIGHT } from "src/views/TreasuryDashboard/components/Graph/ChartCard"; const TICK_COUNT = 5; @@ -109,6 +109,7 @@ const renderAreaChart = ( isExpanded: boolean, margin: CategoricalChartProps["margin"], tickStyle: Record, + minimumYValue: number, maximumYValue: number, displayTooltipTotal?: boolean, onMouseMove?: CategoricalChartFunc, @@ -143,7 +144,7 @@ const renderAreaChart = ( tick={tickStyle} width={dataFormat == DataFormat.Percentage ? 33 : 55} tickFormatter={number => getTickFormatter(dataFormat, number)} - domain={[0, maximumYValue]} + domain={[minimumYValue, maximumYValue]} dx={3} allowDataOverflow={false} /> @@ -191,6 +192,7 @@ const renderStackedAreaChart = ( isExpanded: boolean, margin: CategoricalChartProps["margin"], tickStyle: Record, + minimumYValue: number, maximumYValue: number, displayTooltipTotal?: boolean, onMouseMove?: CategoricalChartFunc, @@ -225,7 +227,7 @@ const renderStackedAreaChart = ( tickCount={isExpanded ? TICK_COUNT_EXPANDED : TICK_COUNT} tickLine={false} tickFormatter={number => getTickFormatter(dataFormat, number)} - domain={[0, maximumYValue]} + domain={[minimumYValue, maximumYValue]} allowDataOverflow={false} /> , + minimumYValue: number, maximumYValue: number, displayTooltipTotal?: boolean, composedLineDataKeys?: string[], @@ -303,7 +306,7 @@ const renderComposedChart = ( tickCount={isExpanded ? TICK_COUNT_EXPANDED : TICK_COUNT} tickLine={false} tickFormatter={number => getTickFormatter(dataFormat, number)} - domain={[0, maximumYValue]} + domain={[minimumYValue, maximumYValue]} allowDataOverflow={false} /> , + minimumYValue: number, maximumYValue: number, scale?: string, displayTooltipTotal?: boolean, @@ -388,7 +392,7 @@ const renderLineChart = ( width={32} scale={() => scale} axisLine={false} - domain={[scale == "log" ? "dataMin" : 0, maximumYValue]} + domain={[scale == "log" ? "dataMin" : minimumYValue, maximumYValue]} allowDataOverflow={false} // Ticks tick={tickStyle} @@ -448,6 +452,7 @@ const renderAreaDifferenceChart = ( isExpanded: boolean, margin: CategoricalChartProps["margin"], tickStyle: Record, + minimumYValue: number, maximumYValue: number, itemDecimals?: number, displayTooltipTotal?: boolean, @@ -536,7 +541,7 @@ const renderAreaDifferenceChart = ( tickLine={false} width={25} tickFormatter={number => getTickFormatter(dataFormat, number)} - domain={[0, maximumYValue]} + domain={[minimumYValue, maximumYValue]} allowDataOverflow={false} /> , + minimumYValue: number, maximumYValue: number, itemDecimals?: number, displayTooltipTotal?: boolean, @@ -602,7 +608,7 @@ const renderMultiLineChart = ( tick={tickStyle} width={25} tickFormatter={number => getTickFormatter(dataFormat, number)} - domain={[0, maximumYValue]} + domain={[minimumYValue, maximumYValue]} allowDataOverflow={false} /> , + minimumYValue: number, maximumYValue: number, displayTooltipTotal?: boolean, onMouseMove?: CategoricalChartFunc, @@ -666,7 +673,7 @@ const renderBarChart = ( tickLine={false} tickCount={isExpanded ? TICK_COUNT_EXPANDED : TICK_COUNT} width={33} - domain={[0, maximumYValue]} + domain={[minimumYValue, maximumYValue]} allowDataOverflow={false} tickFormatter={number => getTickFormatter(dataFormat, number)} /> @@ -698,6 +705,7 @@ function Chart({ type, data, scale, + minimumYValue = 0, dataKeys, dataKeyColors, headerText, @@ -723,6 +731,7 @@ function Chart({ type: ChartType; data: Record[]; scale?: string; + minimumYValue?: number | "dataMin"; /** string array with all of the dataKeys that should be rendered */ dataKeys: string[]; /** mapping of data keys to colors used for stroke/fill */ @@ -747,6 +756,7 @@ function Chart({ }) { const [open, setOpen] = useState(false); const [maximumYValue, setMaximumYValue] = useState(0.0); + const [calculatedMinimumYValue, setCalculatedMinimumYValue] = useState(0.0); /** * Recharts has a bug where using "auto" or "dataMax" as the @@ -763,11 +773,32 @@ function Chart({ return; } - const tempMaxValue = getMaximumValue(data, dataKeys, type, composedLineDataKeys); - // Give a bit of a buffer - setMaximumYValue(tempMaxValue * 1.1); + // Get the maximum value, apply a buffer and get the nearest whole number above it + const maxValue = getMaximumValue(data, dataKeys, type, composedLineDataKeys); + const tempMaxValue = Math.ceil(maxValue * 1.1); + setMaximumYValue(tempMaxValue); }, [data, dataKeys, type, composedLineDataKeys]); + /** + * Calculate a minimum value for the Y-Axis. + */ + useMemo(() => { + if (!data || !data.length) { + setCalculatedMinimumYValue(0.0); + return; + } + + if (minimumYValue !== "dataMin") { + setCalculatedMinimumYValue(minimumYValue); + return; + } + + // Get the minimum value, apply a buffer and get the nearest whole number below it + const minValue = getMinimumValue(data, dataKeys, type, composedLineDataKeys); + const tempMinValue = Math.floor(minValue * 0.9); + setCalculatedMinimumYValue(tempMinValue); + }, [data, dataKeys, type, composedLineDataKeys, minimumYValue]); + const handleOpen = () => { setOpen(true); }; @@ -789,6 +820,7 @@ function Chart({ isExpanded, margin, tickStyle, + calculatedMinimumYValue, maximumYValue, scale, displayTooltipTotal, @@ -806,6 +838,7 @@ function Chart({ isExpanded, margin, tickStyle, + calculatedMinimumYValue, maximumYValue, displayTooltipTotal, onMouseMove, @@ -822,6 +855,7 @@ function Chart({ isExpanded, margin, tickStyle, + calculatedMinimumYValue, maximumYValue, displayTooltipTotal, onMouseMove, @@ -838,6 +872,7 @@ function Chart({ isExpanded, margin, tickStyle, + calculatedMinimumYValue, maximumYValue, itemDecimals, displayTooltipTotal, @@ -855,6 +890,7 @@ function Chart({ isExpanded, margin, tickStyle, + calculatedMinimumYValue, maximumYValue, itemDecimals, displayTooltipTotal, @@ -872,6 +908,7 @@ function Chart({ isExpanded, margin, tickStyle, + calculatedMinimumYValue, maximumYValue, displayTooltipTotal, onMouseMove, @@ -888,6 +925,7 @@ function Chart({ isExpanded, margin, tickStyle, + calculatedMinimumYValue, maximumYValue, displayTooltipTotal, composedLineDataKeys, diff --git a/src/helpers/subgraph/ProtocolMetricsHelper.ts b/src/helpers/subgraph/ProtocolMetricsHelper.ts index fbe226c539..d9e9daf36b 100644 --- a/src/helpers/subgraph/ProtocolMetricsHelper.ts +++ b/src/helpers/subgraph/ProtocolMetricsHelper.ts @@ -193,3 +193,64 @@ export const getMaximumValue = ( }), ); }; + +/** + * Returns the minimum value found in any of the {keys} properties across all elements of + * {data}. + * + * This supports using nested keys ("records.DAI.value"), using the `get-value` library. + * + * If {type} is stacked, the maximum value of the sum of the values corresponding to + * {keys} will be returned. + * + * If {type} is ChartType.Composed and {composedLineDataKeys} is specified, the maximum + * value corresponding to {composedLineDataKeys} and the sum of the stacked values will + * be returned. + * + * @param data + * @param keys + * @param stacked + * @returns + */ +export const getMinimumValue = ( + data: Record[], + keys: string[], + type: ChartType, + composedLineDataKeys?: string[], +): number => { + const stacked = type === ChartType.StackedArea || type === ChartType.Composed; + + return Math.min( + ...data.map(value => { + if (!stacked) { + return Math.min( + ...keys.map(key => { + return getFloat(get(value, key)); + }), + ); + } + + // If we are stacking values, then we want to add the values for the keys, + // but only if they are not within composedLineDataKeys + const stackedTotal = keys.reduce((previousValue, key) => { + if (composedLineDataKeys && composedLineDataKeys.includes(key)) { + return previousValue; + } + + return previousValue + getFloat(get(value, key)); + }, 0); + // Grab the minimum value corresponding to the keys specified in composedLineDataKeys + const maxComposedLineDataKeyValue = Math.min( + ...keys.map(key => { + if (!composedLineDataKeys || type !== ChartType.Composed) return 0; + + if (!composedLineDataKeys.includes(key)) return 0; + + return getFloat(get(value, key)); + }), + ); + + return Math.min(maxComposedLineDataKeyValue, stackedTotal); + }), + ); +}; diff --git a/src/views/TreasuryDashboard/TreasuryDashboard.tsx b/src/views/TreasuryDashboard/TreasuryDashboard.tsx index 408052fe9b..a377aef45e 100644 --- a/src/views/TreasuryDashboard/TreasuryDashboard.tsx +++ b/src/views/TreasuryDashboard/TreasuryDashboard.tsx @@ -1,6 +1,6 @@ import { Box, Container, Grid, useMediaQuery, useTheme } from "@mui/material"; import { Metric, MetricCollection, Paper, TabBar } from "@olympusdao/component-library"; -import { memo, useEffect, useState } from "react"; +import { memo, useEffect, useMemo, useState } from "react"; import { Outlet, Route, Routes, useSearchParams } from "react-router-dom"; import PageTitle from "src/components/PageTitle"; import { SafariFooter } from "src/components/SafariFooter"; @@ -48,7 +48,17 @@ const MetricsDashboard = () => { * asynchronously, so we set the initial value of daysPrior and earliestDate to null. Child components are designed to recognise this * and not load data until earliestDate is a valid value. */ - const earliestDate = !daysPrior ? null : getISO8601String(adjustDateByDays(new Date(), -1 * parseInt(daysPrior))); + const [earliestDate, setEarliestDate] = useState(null); + useMemo(() => { + if (!daysPrior) { + setEarliestDate(null); + return; + } + + const tempEarliestDate = getISO8601String(adjustDateByDays(new Date(), -1 * parseInt(daysPrior))); + console.log(`Setting earliestDate to ${tempEarliestDate}`); + setEarliestDate(tempEarliestDate); + }, [daysPrior]); // State variable for ignoring the API cache const [ignoreCache, setIgnoreCache] = useState(); @@ -168,8 +178,8 @@ const MetricsDashboard = () => { {/* Custom paddingBottom to make the filter row(s) equidistant from the metrics (above) and treasury assets (below). */} - {hideToggleSidePadding ? <> : } - + {hideToggleSidePadding ? <> : } + { ]} /> - + {/* From here onwards will break onto a new line at the "sm" breakpoint or smaller. */} diff --git a/src/views/TreasuryDashboard/components/Graph/LiquidBackingComparisonGraph.tsx b/src/views/TreasuryDashboard/components/Graph/LiquidBackingComparisonGraph.tsx index 6ca0157f89..00cd754f20 100644 --- a/src/views/TreasuryDashboard/components/Graph/LiquidBackingComparisonGraph.tsx +++ b/src/views/TreasuryDashboard/components/Graph/LiquidBackingComparisonGraph.tsx @@ -27,7 +27,6 @@ export const LiquidBackingPerOhmComparisonGraph = ({ subgraphDaysOffset, ignoreCache, }: GraphProps) => { - // TODO look at how to combine query documents const queryExplorerUrl = ""; const theme = useTheme(); const chartName = "LiquidBackingComparison"; @@ -61,9 +60,25 @@ export const LiquidBackingPerOhmComparisonGraph = ({ ohmPrice: number; }; const [byDateLiquidBacking, setByDateLiquidBacking] = useState([]); + + // Handle parameter changes + useEffect(() => { + if (!earliestDate || !subgraphDaysOffset) { + return; + } + + console.debug( + `${chartName}: earliestDate or subgraphDaysOffset was changed to ${earliestDate}, ${subgraphDaysOffset}. Removing cached data.`, + ); + setByDateLiquidBacking([]); + }, [earliestDate, subgraphDaysOffset]); + + // Chart population useMemo(() => { // While data is loading, ensure dependent data is empty if (!metricResults) { + console.debug(`${chartName}: data is loading. Clearing by date metrics.`); + setByDateLiquidBacking([]); return; } @@ -89,13 +104,6 @@ export const LiquidBackingPerOhmComparisonGraph = ({ setByDateLiquidBacking(tempByDateLiquidBacking); }, [metricResults]); - // Handle parameter changes - useEffect(() => { - // useSubgraphTokenRecords will handle the re-fetching - console.info(`${chartName}: earliestDate or subgraphDaysOffset was changed. Removing cached data.`); - setByDateLiquidBacking([]); - }, [earliestDate, subgraphDaysOffset]); - /** * Header subtext */ @@ -176,6 +184,7 @@ As data is sourced from multiple chains that may have different snapshot times, itemDecimals={2} subgraphQueryUrl={queryExplorerUrl} tickStyle={getTickStyle(theme)} + minimumYValue={"dataMin"} /> ); }; diff --git a/src/views/TreasuryDashboard/components/Graph/OhmSupplyGraph.tsx b/src/views/TreasuryDashboard/components/Graph/OhmSupplyGraph.tsx index 919d4d6599..a0485da590 100644 --- a/src/views/TreasuryDashboard/components/Graph/OhmSupplyGraph.tsx +++ b/src/views/TreasuryDashboard/components/Graph/OhmSupplyGraph.tsx @@ -100,8 +100,13 @@ export const OhmSupplyGraph = ({ earliestDate, onMouseMove, subgraphDaysOffset, // Handle parameter changes useEffect(() => { - // useSubgraphTokenRecords will handle the re-fetching - console.info(`${chartName}: earliestDate or subgraphDaysOffset was changed. Removing cached data.`); + if (!earliestDate || !subgraphDaysOffset) { + return; + } + + console.debug( + `${chartName}: earliestDate or subgraphDaysOffset was changed to ${earliestDate}, ${subgraphDaysOffset}. Removing cached data.`, + ); setByDateOhmSupply([]); }, [earliestDate, subgraphDaysOffset]); diff --git a/src/views/TreasuryDashboard/components/Graph/OhmSupplyTable.tsx b/src/views/TreasuryDashboard/components/Graph/OhmSupplyTable.tsx index 8f4a07d3fa..16cb7c969b 100644 --- a/src/views/TreasuryDashboard/components/Graph/OhmSupplyTable.tsx +++ b/src/views/TreasuryDashboard/components/Graph/OhmSupplyTable.tsx @@ -160,8 +160,13 @@ export const OhmSupplyTable = ({ // Handle parameter changes useEffect(() => { - // useSubgraphTokenRecords will handle the re-fetching - console.debug(`${chartName}: earliestDate or subgraphDaysOffset was changed. Removing cached data.`); + if (!earliestDate || !subgraphDaysOffset) { + return; + } + + console.debug( + `${chartName}: earliestDate or subgraphDaysOffset was changed to ${earliestDate}, ${subgraphDaysOffset}. Removing cached data.`, + ); setByDateCategoryTokenSupplyMap({}); }, [earliestDate, subgraphDaysOffset]); @@ -235,12 +240,18 @@ export const OhmSupplyTable = ({ { // One row per record metric.records.map(record => { + // There are some gOHM records that do not have the tokenAddress set to the gOHM address. The subgraph will need to be changed to capture them. const isGOhm = gOhmAddresses.includes(record.tokenAddress.toLowerCase()); const ohmValue: number = (isGOhm ? currentIndex : 1) * +record.supplyBalance; return ( - + {record.type == TOKEN_SUPPLY_TYPE_TOTAL_SUPPLY ? "Supply" : record.type} {isBreakpointSmall ? ( diff --git a/src/views/TreasuryDashboard/components/Graph/OwnedLiquidityGraph.tsx b/src/views/TreasuryDashboard/components/Graph/OwnedLiquidityGraph.tsx index f1dbe0dbd4..7642291099 100644 --- a/src/views/TreasuryDashboard/components/Graph/OwnedLiquidityGraph.tsx +++ b/src/views/TreasuryDashboard/components/Graph/OwnedLiquidityGraph.tsx @@ -95,8 +95,13 @@ export const ProtocolOwnedLiquidityGraph = ({ earliestDate, subgraphDaysOffset, // Handle parameter changes useEffect(() => { - // useSubgraphTokenRecords will handle the re-fetching - console.info(`${chartName}: earliestDate or subgraphDaysOffset was changed. Removing cached data.`); + if (!earliestDate || !subgraphDaysOffset) { + return; + } + + console.debug( + `${chartName}: earliestDate or subgraphDaysOffset was changed to ${earliestDate}, ${subgraphDaysOffset}. Removing cached data.`, + ); setByDateTokenSummary([]); }, [earliestDate, subgraphDaysOffset]); diff --git a/src/views/TreasuryDashboard/components/Graph/TreasuryAssetsGraph.tsx b/src/views/TreasuryDashboard/components/Graph/TreasuryAssetsGraph.tsx index eaa2128731..e14b777101 100644 --- a/src/views/TreasuryDashboard/components/Graph/TreasuryAssetsGraph.tsx +++ b/src/views/TreasuryDashboard/components/Graph/TreasuryAssetsGraph.tsx @@ -118,8 +118,13 @@ export const TreasuryAssetsGraph = ({ // Handle parameter changes useEffect(() => { - // useSubgraphTokenRecords will handle the re-fetching - console.info(`${chartName}: earliestDate or subgraphDaysOffset was changed. Removing cached data.`); + if (!earliestDate || !subgraphDaysOffset) { + return; + } + + console.debug( + `${chartName}: earliestDate or subgraphDaysOffset was changed to ${earliestDate}, ${subgraphDaysOffset}. Removing cached data.`, + ); setByDateMetrics([]); }, [earliestDate, subgraphDaysOffset]); diff --git a/src/views/TreasuryDashboard/components/Graph/TreasuryAssetsTable.tsx b/src/views/TreasuryDashboard/components/Graph/TreasuryAssetsTable.tsx index a38abf6453..0939417690 100644 --- a/src/views/TreasuryDashboard/components/Graph/TreasuryAssetsTable.tsx +++ b/src/views/TreasuryDashboard/components/Graph/TreasuryAssetsTable.tsx @@ -32,7 +32,7 @@ export const TreasuryAssetsTable = ({ const chartName = "TreasuryAssetsTable"; const tokenRecordResults = useTokenRecordsQueryComplete({ startDate: earliestDate, ignoreCache: ignoreCache }); - const metricResults = useMetricsQuery({ startDate: earliestDate, ignoreCache: ignoreCache }); + const { data: metricResults } = useMetricsQuery({ startDate: earliestDate, ignoreCache: ignoreCache }); /** * Chart population: @@ -42,7 +42,8 @@ export const TreasuryAssetsTable = ({ const [byDateTokenSummary, setByDateTokenSummary] = useState([]); const [currentTokens, setCurrentTokens] = useState([]); useMemo(() => { - if (!tokenRecordResults || !metricResults || !metricResults.data) { + if (!tokenRecordResults || !metricResults) { + setByDateTokenSummary([]); return; } @@ -66,7 +67,7 @@ export const TreasuryAssetsTable = ({ // We want the liquid backing contribution to be shown as liquid backing contribution / backed OHM newDateTokenSummary.forEach(dateTokenSummary => { // Get the metric for the date - const currentMetric = metricResults.data?.find(value => value.date == dateTokenSummary.date); + const currentMetric = metricResults.find(value => value.date == dateTokenSummary.date); if (!currentMetric || currentMetric.ohmBackedSupply == 0) { return; } @@ -77,12 +78,17 @@ export const TreasuryAssetsTable = ({ }); setByDateTokenSummary(newDateTokenSummary); - }, [isLiquidBackingActive, tokenRecordResults]); + }, [isLiquidBackingActive, tokenRecordResults, metricResults]); // Handle parameter changes useEffect(() => { - // useSubgraphTokenRecords will handle the re-fetching - console.debug(`${chartName}: earliestDate or subgraphDaysOffset was changed. Removing cached data.`); + if (!earliestDate || !subgraphDaysOffset) { + return; + } + + console.debug( + `${chartName}: earliestDate or subgraphDaysOffset was changed to ${earliestDate}, ${subgraphDaysOffset}. Removing cached data.`, + ); setByDateTokenSummary([]); }, [earliestDate, subgraphDaysOffset]); @@ -91,6 +97,12 @@ export const TreasuryAssetsTable = ({ */ const [headerSubtext, setHeaderSubtext] = useState(""); useMemo(() => { + if (byDateTokenSummary.length == 0) { + setCurrentTokens([]); + setHeaderSubtext(""); + return; + } + console.debug(`${chartName}: rebuilding current tokens`); const currentTokenSummary = byDateTokenSummary[selectedIndex]; setCurrentTokens(currentTokenSummary ? Object.values(currentTokenSummary.tokens) : []);