Skip to content

Commit

Permalink
feat: add scroll into view for charts
Browse files Browse the repository at this point in the history
  • Loading branch information
cadeban committed Oct 15, 2024
1 parent c2796b5 commit 362f88e
Show file tree
Hide file tree
Showing 13 changed files with 158 additions and 14 deletions.
7 changes: 6 additions & 1 deletion web/src/features/charts/BreakdownChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Factory, Zap } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { ElectricityModeType } from 'types';
import trackEvent from 'utils/analytics';
import { TimeAverages, TrackEvent } from 'utils/constants';
import { Charts, TimeAverages, TrackEvent } from 'utils/constants';
import { formatCo2 } from 'utils/formatting';
import {
dataSourcesCollapsedBreakdownAtom,
Expand Down Expand Up @@ -87,6 +87,11 @@ function BreakdownChart({
translationKey={`country-history.${titleDisplayMode}${titleMixMode}`}
badge={badge}
unit={valueAxisLabel}
id={
displayByEmissions
? Charts.CARBON_EMISSION_ORIGIN_CHART
: Charts.ELECTRICITY_ORIGIN_CHART
}
/>
<div className="relative ">
<AreaGraph
Expand Down
3 changes: 2 additions & 1 deletion web/src/features/charts/CarbonChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useAtom } from 'jotai';
import { Factory, Zap } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import trackEvent from 'utils/analytics';
import { TimeAverages, TrackEvent } from 'utils/constants';
import { Charts, TimeAverages, TrackEvent } from 'utils/constants';
import { dataSourcesCollapsedEmissionAtom } from 'utils/state/atoms';

import { ChartTitle } from './ChartTitle';
Expand Down Expand Up @@ -58,6 +58,7 @@ function CarbonChart({ datetimes, timeAverage }: CarbonChartProps) {
translationKey="country-history.carbonintensity"
badge={badge}
unit={'gCO₂eq / kWh'}
id={Charts.CARBON_INTENSITY_CHART}
/>
<AreaGraph
testId="details-carbon-graph"
Expand Down
11 changes: 9 additions & 2 deletions web/src/features/charts/ChartTitle.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
/* eslint-disable react/jsx-no-target-blank */
import { useScrollAnchorIntoView } from 'hooks/useScrollAnchorIntoView';
import { useAtomValue } from 'jotai';
import { useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { timeAverageAtom } from 'utils/state/atoms';

type Props = {
translationKey: string;
unit?: string;
badge?: React.ReactElement;
id?: string;
};

export function ChartTitle({ translationKey, unit, badge }: Props) {
export function ChartTitle({ translationKey, unit, badge, id }: Props) {
const { t } = useTranslation();
const timeAverage = useAtomValue(timeAverageAtom);
const reference = useRef(null);
useScrollAnchorIntoView(reference);
/*
Use local for timeAverage if exists, otherwise use local default if exists. If no translation exists, use english
*/
return (
<div className="flex flex-col pb-0.5">
<div className="flex items-center gap-1.5 pt-4">
<h2 className="grow">{t(`${translationKey}.${timeAverage}`)}</h2>
<h2 className="grow" id={id} ref={reference}>
{t(`${translationKey}.${timeAverage}`)}
</h2>
{badge}
</div>
{unit && <div className="text-sm dark:text-gray-300">{unit}</div>}
Expand Down
13 changes: 11 additions & 2 deletions web/src/features/charts/EmissionChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import { useAtom } from 'jotai';
import { Factory, Zap } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import trackEvent from 'utils/analytics';
import { TimeAverages, TrackEvent } from 'utils/constants';
import { Charts, TimeAverages, TrackEvent } from 'utils/constants';
import { formatCo2 } from 'utils/formatting';
import { dataSourcesCollapsedEmissionAtom } from 'utils/state/atoms';
import {
dataSourcesCollapsedEmissionAtom,
displayByEmissionsAtom,
} from 'utils/state/atoms';

import { ChartTitle } from './ChartTitle';
import { DataSources } from './DataSources';
Expand All @@ -33,6 +36,7 @@ function EmissionChart({ timeAverage, datetimes }: EmissionChartProps) {
powerGenerationSources,
emissionFactorSourcesToProductionSources,
} = useZoneDataSources();
const [displayByEmissions] = useAtom(displayByEmissionsAtom);
const { t } = useTranslation();
if (isLoading || isError || !data) {
return null;
Expand All @@ -52,6 +56,11 @@ function EmissionChart({ timeAverage, datetimes }: EmissionChartProps) {
translationKey="country-history.emissions"
badge={badge}
unit={'CO₂eq'}
id={
displayByEmissions
? Charts.CARBON_EMISSION_CHART
: Charts.CARBON_INTENSITY_CHART
}
/>
<AreaGraph
testId="history-emissions-graph"
Expand Down
12 changes: 10 additions & 2 deletions web/src/features/charts/NetExchangeChart.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useAtom } from 'jotai';
import { TimeAverages } from 'utils/constants';
import { Charts, TimeAverages } from 'utils/constants';
import { formatCo2 } from 'utils/formatting';
import { displayByEmissionsAtom, productionConsumptionAtom } from 'utils/state/atoms';

Expand Down Expand Up @@ -42,7 +42,15 @@ function NetExchangeChart({ datetimes, timeAverage }: NetExchangeChartProps) {

return (
<RoundedCard className="pb-2">
<ChartTitle translationKey="country-history.netExchange" unit={valueAxisLabel} />
<ChartTitle
translationKey="country-history.netExchange"
unit={valueAxisLabel}
id={
displayByEmissions
? Charts.EMISSIONS_NET_EXCHANGE_CHART
: Charts.ELECTRICITY_NET_EXCHANGE_CHART
}
/>
<div className="relative">
<AreaGraph
testId="history-exchange-graph"
Expand Down
10 changes: 9 additions & 1 deletion web/src/features/charts/PriceChart.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { useAtom } from 'jotai';
import { useTranslation } from 'react-i18next';
import { TimeAverages } from 'utils/constants';
import { Charts, TimeAverages } from 'utils/constants';
import { displayByEmissionsAtom } from 'utils/state/atoms';

import { ChartTitle } from './ChartTitle';
import { DisabledMessage } from './DisabledMessage';
Expand All @@ -18,6 +20,7 @@ interface PriceChartProps {

function PriceChart({ datetimes, timeAverage }: PriceChartProps) {
const { data, isLoading, isError } = usePriceChartData();
const [displayByEmissions] = useAtom(displayByEmissionsAtom);
const { t } = useTranslation();

if (isLoading || isError || !data) {
Expand Down Expand Up @@ -59,6 +62,11 @@ function PriceChart({ datetimes, timeAverage }: PriceChartProps) {
<ChartTitle
translationKey="country-history.electricityprices"
unit={valueAxisLabel}
id={
displayByEmissions
? Charts.EMISSIONS_ELECTRICITY_PRICES_CHART
: Charts.ELECTRICITY_PRICES_CHART
}
/>
<div className="relative">
{isPriceDisabled && (
Expand Down
7 changes: 6 additions & 1 deletion web/src/features/charts/bar-breakdown/BarBreakdownChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next';
import { ElectricityModeType, ZoneKey } from 'types';
import useResizeObserver from 'use-resize-observer';
import trackEvent from 'utils/analytics';
import { TrackEvent } from 'utils/constants';
import { Charts, TrackEvent } from 'utils/constants';
import {
dataSourcesCollapsedBarBreakdownAtom,
displayByEmissionsAtom,
Expand Down Expand Up @@ -137,6 +137,11 @@ function BarBreakdownChart({
estimatedPercentage={currentZoneDetail.estimatedPercentage}
unit={graphUnit}
estimationMethod={currentZoneDetail.estimationMethod}
id={
displayByEmissions
? Charts.CARBON_EMISSIONS_BAR_BREAKDOWN
: Charts.ELECTRICITY_CONSUMPTION_BAR_BREAKDOWN
}
/>
{!displayByEmissions && (
<CapacityLegend>
Expand Down
11 changes: 10 additions & 1 deletion web/src/features/charts/bar-breakdown/elements/BySource.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import EstimationBadge from 'components/EstimationBadge';
import { useGetEstimationTranslation } from 'hooks/getEstimationTranslation';
import { useScrollAnchorIntoView } from 'hooks/useScrollAnchorIntoView';
import { TFunction } from 'i18next';
import { useAtom } from 'jotai';
import { CircleDashed, TrendingUpDown } from 'lucide-react';
import { useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { EstimationMethods, TimeAverages } from 'utils/constants';
import {
Expand Down Expand Up @@ -38,12 +40,14 @@ export default function BySource({
estimatedPercentage,
unit,
estimationMethod,
id,
}: {
className?: string;
hasEstimationPill?: boolean;
estimatedPercentage?: number;
unit?: string | number;
estimationMethod?: EstimationMethods;
id?: string;
}) {
const { t } = useTranslation();
const [timeAverage] = useAtom(timeAverageAtom);
Expand All @@ -58,13 +62,18 @@ export default function BySource({
estimatedPercentage
);

const reference = useRef(null);
useScrollAnchorIntoView(reference);

return (
<div className="flex flex-col pb-1 pt-4">
<div
className={`text-md relative flex flex-row justify-between font-bold ${className}`}
>
<div className="flex gap-1">
<h2>{text}</h2>
<h2 id={id} ref={reference}>
{text}
</h2>
</div>
{hasEstimationPill && (
<EstimationBadge
Expand Down
3 changes: 3 additions & 0 deletions web/src/features/panels/zone/DisplayByEmissionToggle.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ToggleButton from 'components/ToggleButton';
import { useClearFragment } from 'hooks/useClearFragment';
import { useAtom, useAtomValue } from 'jotai';
import type { ReactElement } from 'react';
import trackEvent from 'utils/analytics';
Expand All @@ -8,6 +9,7 @@ import { displayByEmissionsAtom, isConsumptionAtom } from 'utils/state/atoms';
export default function EmissionToggle(): ReactElement {
const isConsumption = useAtomValue(isConsumptionAtom);
const [displayByEmissions, setDisplayByEmissions] = useAtom(displayByEmissionsAtom);
const clearFragment = useClearFragment();

// TODO: perhaps togglebutton should accept boolean values
const options = [
Expand All @@ -34,6 +36,7 @@ export default function EmissionToggle(): ReactElement {
(option === LeftPanelToggleOptions.EMISSIONS && !displayByEmissions)
) {
setDisplayByEmissions(!displayByEmissions);
clearFragment();
}
};

Expand Down
41 changes: 38 additions & 3 deletions web/src/features/panels/zone/ZoneDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ import useGetZone from 'api/getZone';
import { CommercialApiButton } from 'components/buttons/CommercialApiButton';
import LoadingSpinner from 'components/LoadingSpinner';
import BarBreakdownChart from 'features/charts/bar-breakdown/BarBreakdownChart';
import { useAtomValue, useSetAtom } from 'jotai';
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import { useEffect } from 'react';
import { Navigate, useParams } from 'react-router-dom';
import { Navigate, useLocation, useParams } from 'react-router-dom';
import { twMerge } from 'tailwind-merge';
import { ZoneMessage } from 'types';
import { EstimationMethods, SpatialAggregate } from 'utils/constants';
import {
Charts,
ChartsToPanel,
EstimationMethods,
LeftPanelToggleOptions,
SpatialAggregate,
} from 'utils/constants';
import {
displayByEmissionsAtom,
isHourlyAtom,
Expand All @@ -28,6 +34,33 @@ import { getHasSubZones, getZoneDataStatus, ZoneDataStatus } from './util';
import { ZoneHeaderGauges } from './ZoneHeaderGauges';
import ZoneHeaderTitle from './ZoneHeaderTitle';

const useScrollHashIntoView = (isLoading: boolean) => {
const location = useLocation();
const [displayByEmissions, setDisplayByEmissions] = useAtom(displayByEmissionsAtom);

useEffect(() => {
const hash = location.hash.slice(1);

if (!hash) {
return;
}

const panel = ChartsToPanel[hash as Charts];

if (!panel) {
return;
}

if (
(displayByEmissions && panel === LeftPanelToggleOptions.EMISSIONS) ||
(!displayByEmissions && panel === LeftPanelToggleOptions.ELECTRICITY)
) {
return;
}
setDisplayByEmissions(panel === LeftPanelToggleOptions.EMISSIONS);
}, [location.hash, isLoading, displayByEmissions, setDisplayByEmissions]);
};

export default function ZoneDetails(): JSX.Element {
const { zoneId } = useParams();
const timeAverage = useAtomValue(timeAverageAtom);
Expand All @@ -40,6 +73,8 @@ export default function ZoneDetails(): JSX.Element {
const hasSubZones = getHasSubZones(zoneId);
const isSubZone = zoneId ? zoneId.includes('-') : true;

useScrollHashIntoView(isLoading);

useEffect(() => {
if (hasSubZones === null) {
return;
Expand Down
11 changes: 11 additions & 0 deletions web/src/hooks/useClearFragment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useCallback } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';

export function useClearFragment() {
const navigate = useNavigate();
const location = useLocation();

return useCallback(() => {
navigate(`${location.pathname}${location.search}`);
}, [navigate, location.pathname, location.search]);
}
17 changes: 17 additions & 0 deletions web/src/hooks/useScrollAnchorIntoView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { RefObject, useEffect } from 'react';
import { useLocation } from 'react-router-dom';

import { useClearFragment } from './useClearFragment';

export function useScrollAnchorIntoView(reference: RefObject<HTMLElement | undefined>) {
const { hash } = useLocation();
const clearFragment = useClearFragment();
useEffect(() => {
if (reference.current?.id === hash.slice(1)) {
reference.current.scrollIntoView({ behavior: 'smooth' });
clearFragment();
}
}, [hash, clearFragment, reference]);

return;
}
26 changes: 26 additions & 0 deletions web/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,32 @@ export enum LeftPanelToggleOptions {
EMISSIONS = 'emissions',
}

export enum Charts {
CARBON_EMISSIONS_BAR_BREAKDOWN = 'carbon_emissions_bar_breakdown',
ELECTRICITY_CONSUMPTION_BAR_BREAKDOWN = 'electricity_consumption_bar_breakdown',
ELECTRICITY_NET_EXCHANGE_CHART = 'electricity_net_exchange_chart',
EMISSIONS_NET_EXCHANGE_CHART = 'emissions_net_exchange_chart',
CARBON_EMISSION_CHART = 'carbon_emission_chart',
CARBON_INTENSITY_CHART = 'carbon_intensity_chart',
ELECTRICITY_ORIGIN_CHART = 'electricity_origin_chart',
CARBON_EMISSION_ORIGIN_CHART = 'carbon_emission_origin_chart',
ELECTRICITY_PRICES_CHART = 'electricity_prices_chart',
EMISSIONS_ELECTRICITY_PRICES_CHART = 'emissions_electricity_prices_chart',
}

export const ChartsToPanel = {
[Charts.CARBON_EMISSIONS_BAR_BREAKDOWN]: LeftPanelToggleOptions.EMISSIONS,
[Charts.ELECTRICITY_CONSUMPTION_BAR_BREAKDOWN]: LeftPanelToggleOptions.ELECTRICITY,
[Charts.ELECTRICITY_NET_EXCHANGE_CHART]: LeftPanelToggleOptions.ELECTRICITY,
[Charts.EMISSIONS_NET_EXCHANGE_CHART]: LeftPanelToggleOptions.EMISSIONS,
[Charts.CARBON_EMISSION_CHART]: LeftPanelToggleOptions.EMISSIONS,
[Charts.CARBON_INTENSITY_CHART]: LeftPanelToggleOptions.ELECTRICITY,
[Charts.ELECTRICITY_ORIGIN_CHART]: LeftPanelToggleOptions.ELECTRICITY,
[Charts.CARBON_EMISSION_ORIGIN_CHART]: LeftPanelToggleOptions.EMISSIONS,
[Charts.ELECTRICITY_PRICES_CHART]: LeftPanelToggleOptions.ELECTRICITY,
[Charts.EMISSIONS_ELECTRICITY_PRICES_CHART]: LeftPanelToggleOptions.EMISSIONS,
};

export enum TrackEvent {
DATA_SOURCES_CLICKED = 'Data Sources Clicked',
APP_BANNER_CTA_CLICKED = 'App Banner CTA Clicked',
Expand Down

0 comments on commit 362f88e

Please sign in to comment.