Skip to content

Commit 1de452e

Browse files
committed
Basic implementation
1 parent c1291fe commit 1de452e

File tree

3 files changed

+92
-16
lines changed

3 files changed

+92
-16
lines changed

web/src/features/charts/OriginChart.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import EstimationBadge from 'components/EstimationBadge';
21
import { max, sum } from 'd3-array';
32
import { useAtomValue } from 'jotai';
43
import { useEffect, useMemo, useState } from 'react';
@@ -11,7 +10,8 @@ import { isConsumptionAtom, isHourlyAtom } from 'utils/state/atoms';
1110

1211
import { ChartSubtitle, ChartTitle } from './ChartTitle';
1312
import AreaGraph from './elements/AreaGraph';
14-
import { getBadgeTextAndIcon, getGenerationTypeKey, noop } from './graphUtils';
13+
import { EstimationLegendIcon } from './elements/EstimationMarkers';
14+
import { getGenerationTypeKey, noop } from './graphUtils';
1515
import useOriginChartData from './hooks/useOriginChartData';
1616
import { MissingExchangeDataDisclaimer } from './MissingExchangeData';
1717
import { NotEnoughDataMessage } from './NotEnoughDataMessage';
@@ -91,10 +91,6 @@ function OriginChart({ displayByEmissions, datetimes, timeRange }: OriginChartPr
9191

9292
const hasEnoughDataToDisplay = datetimes?.length > 2;
9393

94-
const { text, icon } = getBadgeTextAndIcon(chartData, t);
95-
96-
const badge = <EstimationBadge text={text} Icon={icon} />;
97-
9894
if (!hasEnoughDataToDisplay) {
9995
return (
10096
<NotEnoughDataMessage
@@ -115,12 +111,27 @@ function OriginChart({ displayByEmissions, datetimes, timeRange }: OriginChartPr
115111
return hasEstimationPill;
116112
});
117113

114+
// const { text, icon } = getBadgeTextAndIcon(chartData, t);
115+
// const badge = <EstimationBadge text={text} Icon={icon} />;
116+
117+
const someEstimated = estimated.some(Boolean);
118+
119+
const badge = someEstimated ? (
120+
<div
121+
className="flex h-6 flex-row items-center gap-1 whitespace-nowrap px-2 py-1 text-xs font-semibold"
122+
style={{ color: '#DB58FF' }}
123+
>
124+
<EstimationLegendIcon />
125+
Preliminary
126+
</div>
127+
) : undefined;
128+
118129
return (
119130
<RoundedCard>
120131
<ChartTitle
121132
titleText={t(`country-history.${titleDisplayMode}${titleMixMode}.${timeRange}`)}
122133
badge={badge}
123-
isEstimated={Boolean(text)}
134+
isEstimated={someEstimated}
124135
unit={valueAxisLabel}
125136
id={Charts.ORIGIN_CHART}
126137
subtitle={<ChartSubtitle datetimes={datetimes} timeRange={timeRange} />}

web/src/features/charts/elements/AreaGraph.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ interface AreagraphProps {
107107
isDisabled?: boolean;
108108
height: string;
109109
datetimes: Date[];
110-
estimated: boolean[];
110+
estimated?: boolean[];
111111
selectedTimeRange: TimeRange;
112112
tooltip: (props: InnerAreaGraphTooltipProps) => JSX.Element | null;
113113
tooltipSize?: 'small' | 'large';
@@ -188,7 +188,7 @@ function AreaGraph({
188188
[datetimes, endTime]
189189
);
190190
const estimatedWithNext = useMemo(
191-
() => (estimated ? [...estimated, estimated?.at(-1)] : estimated),
191+
() => (estimated ? [...estimated, estimated?.at(-1) || false] : estimated),
192192
[estimated]
193193
);
194194

@@ -295,6 +295,7 @@ function AreaGraph({
295295
datetimes={datetimesWithNext}
296296
estimated={estimatedWithNext}
297297
scaleWidth={containerWidth}
298+
scaleHeight={containerHeight}
298299
transform={`translate(0 ${containerHeight})`}
299300
className="h-[22px] w-full overflow-visible"
300301
/>

web/src/features/charts/elements/EstimationMarkers.tsx

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,41 @@
11
import { scaleTime } from 'd3-scale';
22
import { area, curveStepAfter } from 'd3-shape';
3+
import { useDarkMode } from 'hooks/theme';
34
import PulseLoader from 'react-spinners/PulseLoader';
45
import useResizeObserver from 'use-resize-observer/polyfilled';
56

6-
function EstimationMarkers({ datetimes, scaleWidth, className, transform, estimated }) {
7+
const DARK_MODE_PATTERN_STROKE = '#404040';
8+
const LIGHT_MODE_PATTERN_STROKE = 'darkgray';
9+
const MARKER_COLOR = '#DB58FF';
10+
11+
export function EstimationLegendIcon() {
12+
return (
13+
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12">
14+
<rect width="24" height="24" fill="url(#pattern)" transform="scale(0.5)" />
15+
<line x1="0" y1="11" x2="12" y2="11" stroke="#DB58FF" strokeWidth="2" />
16+
</svg>
17+
);
18+
}
19+
20+
function EstimationMarkers({
21+
datetimes,
22+
scaleWidth,
23+
scaleHeight,
24+
className,
25+
transform,
26+
estimated,
27+
markerHeight = 3,
28+
}: {
29+
datetimes: any[] | undefined;
30+
scaleWidth: number;
31+
scaleHeight: number;
32+
className?: string;
33+
transform?: string;
34+
estimated?: boolean[];
35+
markerHeight?: number;
36+
}) {
737
const { ref } = useResizeObserver<SVGSVGElement>();
38+
const isDarkMode = useDarkMode();
839

940
if (datetimes === undefined || estimated == null) {
1041
return (
@@ -16,25 +47,58 @@ function EstimationMarkers({ datetimes, scaleWidth, className, transform, estima
1647

1748
const startDate = datetimes[0];
1849
const endDate = datetimes.at(-1);
50+
if (!startDate || !endDate) {
51+
return null; // No valid dates to render
52+
}
1953
const timeScale = scaleTime().domain([startDate, endDate]).range([0, scaleWidth]);
2054

21-
const layerArea = area()
55+
const markerPathData = area()
2256
// see https://github.com/d3/d3-shape#curveStep
2357
.curve(curveStepAfter)
24-
.x(timeScale)
58+
.x((d: any) => timeScale(d))
59+
.y0(0)
60+
.y1(markerHeight)
61+
.defined((d, index) => Boolean(estimated?.at(index)));
62+
const backgroundPathData = area()
63+
.curve(curveStepAfter)
64+
.x((d: any) => timeScale(d))
2565
.y0(0)
26-
.y1(3)
27-
.defined((d, index) => estimated?.at(index));
66+
.y1(-scaleHeight)
67+
.defined((d, index) => Boolean(estimated?.at(index)));
68+
69+
const patternStroke = isDarkMode ? DARK_MODE_PATTERN_STROKE : LIGHT_MODE_PATTERN_STROKE;
70+
const patternBackground = isDarkMode ? 'black' : 'white';
2871

2972
return (
3073
<svg className={className} ref={ref}>
74+
<defs>
75+
<pattern
76+
id="pattern"
77+
width="8"
78+
height="8"
79+
viewBox="0 0 8 8"
80+
patternUnits="userSpaceOnUse"
81+
>
82+
<rect width="8" height="8" fill={patternBackground} />
83+
<path d="M-5 13L15 -7" stroke={patternStroke} />
84+
<path d="M-1 17L19 -3" stroke={patternStroke} />
85+
<path d="M-10 10L10 -10" stroke={patternStroke} />
86+
</pattern>
87+
</defs>
3188
<g
32-
fill="orange"
89+
fill={MARKER_COLOR}
3390
textAnchor="middle"
3491
transform={transform}
3592
style={{ pointerEvents: 'none' }}
3693
>
37-
<path d={layerArea(datetimes)} />
94+
<path d={markerPathData(datetimes) || undefined} />
95+
</g>
96+
<g textAnchor="middle" transform={transform} style={{ pointerEvents: 'none' }}>
97+
<path
98+
d={backgroundPathData(datetimes) || undefined}
99+
fill="url(#pattern)"
100+
opacity={0.4}
101+
/>
38102
</g>
39103
</svg>
40104
);

0 commit comments

Comments
 (0)