Skip to content

Commit

Permalink
Extract ResponsiveChartContainer logic from Widget
Browse files Browse the repository at this point in the history
  • Loading branch information
lappi-lynx committed Apr 2, 2024
1 parent 8d76802 commit 6ef8b13
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 124 deletions.
123 changes: 5 additions & 118 deletions src/components/Widget.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import React, { useState, useEffect } from 'react';
import React, { useState } from 'react';
import { useQuery } from '@apollo/client';
import { useCityAutocomplete } from '../hooks/useCityAutocomplete';
import { GET_FORECAST_FROM_COORDS_QUERY } from '../graphql/queries';
import { SuggestedCity } from '../domain/types/SuggestedCity';
import { useTheme, Autocomplete, TextField, CircularProgress, Grid } from "@mui/material";
import { ResponsiveChartContainer, LinePlot, ChartsXAxis, ChartsYAxis, ChartsLegend, ChartsGrid, ChartsReferenceLine, ChartsTooltip, BarPlot } from '@mui/x-charts';
import dayjs from 'dayjs';
import { WeatherData } from './../domain/types/WeatherData';
import { ForecastDaysSelector } from './ForecastDaysSelector';
import { ForecastDaysSelector } from './chart/ForecastDaysSelector';
import { WeatherChart } from './chart/WeatherChart';
import { DEFAULT_WIDGET_PARAMS } from '../infrastructure/constants';
import { renderErrorAlert } from './../utils/renderErrorAlert';

Expand Down Expand Up @@ -53,36 +51,6 @@ export const Widget: React.FC = () => {
}));
};

type ChartData = {
xAxisData: Date[];
seriesData: number[][];
seriesLabels?: string[];
};

const [chartData, setChartData] = useState<ChartData>({
xAxisData: [],
seriesData: [],
});

useEffect(() => {
if (forecastData && forecastData.getForecastByCoordinates) {
const chartData = forecastData.getForecastByCoordinates.reduce((data: ChartData, forecast: WeatherData) => {
data.xAxisData.push(new Date(forecast.timestamp));
data.seriesData[0].push(forecast.temperature);
data.seriesData[1].push(forecast.precipitation);
data.seriesData[2].push(forecast.cloudCover);
data.seriesData[3].push(forecast.windSpeed);
return data;
}, {
xAxisData: [],
seriesData: [[], [], [], []],
seriesLabels: ['Temperature, C', 'Precipitation, mm', 'Cloud Cover', 'Wind Speed, km/h'],
});

setChartData(chartData);
}
}, [forecastData]);

return (
<main style={{ backgroundColor: palette.background.default, color: palette.text.primary }}>
<Grid container justifyContent="center" alignItems="center" spacing={2} pt={2}>
Expand Down Expand Up @@ -120,89 +88,8 @@ export const Widget: React.FC = () => {
{errorCities && renderErrorAlert(errorCities, 'cities')}
{errorForecast && renderErrorAlert(errorForecast, 'forecast')}

{forecastData && chartData.seriesLabels && (
<ResponsiveChartContainer
xAxis={[{
id: 'bottomAxis',
label: "Timeline",
data: chartData.xAxisData,
scaleType: 'band',
valueFormatter: (date) => dayjs(date).format("MMM D, hA"),
},
{
id: 'topAxis',
label: "Cloud Cover %",
data: chartData.seriesData[2],
scaleType: 'point'
}]}
yAxis={[
{ id: 'leftAxis', label: "Temperature, °C", scaleType: 'linear' },
{
id: 'rightAxis',
label: "Wind speed, km/h",
data: chartData.seriesData[3],
scaleType: 'linear',
}
]}
series={[
{ type: 'line', label: chartData.seriesLabels![0], data: chartData.seriesData[0], color: palette.chart.tempterature },
{ type: 'bar', label: chartData.seriesLabels![1], data: chartData.seriesData[1], color: palette.chart.precipitation },
{ type: 'band', label: chartData.seriesLabels![2], data: chartData.seriesData[2], xAxisKey: 'topAxis' },
{ type: 'line', label: chartData.seriesLabels![3], data: chartData.seriesData[3], yAxisKey: 'rightAxis', color: palette.chart.wind },
]}
height={400}
margin={{ top: 120 }}
sx={{
"& .MuiChartsAxis-left .MuiChartsAxis-label": {
strokeWidth: "0.4",
fill: `${palette.chart.tempterature}`,
},
"& .MuiChartsAxis-left line": {
stroke: `${palette.chart.tempterature}`,
},
"& .MuiChartsAxis-left text": {
stroke: `${palette.chart.tempterature}`,
},
// right Axis styles
"& .MuiChartsAxis-right .MuiChartsAxis-label": {
strokeWidth: "0.4",
fill: `${palette.chart.wind}`,
},
"& .MuiChartsAxis-right line": {
stroke: `${palette.chart.wind}`,
},
"& .MuiChartsAxis-right text": {
stroke: `${palette.chart.wind}`,
},
// top Axis styles
"& .MuiChartsAxis-top .MuiChartsAxis-label": {
strokeWidth: "0.4",
fill: `${palette.chart.clouds}`,
},
"& .MuiChartsAxis-top line": {
stroke: `${palette.chart.clounds}`,
},
"& .MuiChartsAxis-top text": {
stroke: `${palette.chart.clouds}`,
strokeWidth: "0.6",
},
}}
>
<LinePlot />
<ChartsYAxis axisId='rightAxis' position='right' />
<ChartsXAxis axisId='bottomAxis' position='bottom' />
<ChartsXAxis axisId='topAxis' position='top' />
<ChartsYAxis axisId='leftAxis' position='left' />
<ChartsLegend />
<BarPlot />
<ChartsTooltip />
<ChartsGrid horizontal={true} vertical={true} />
<ChartsReferenceLine
y={0}
labelAlign="end"
lineStyle={{ stroke: palette.text.primary, strokeDasharray: '5 5' }}
/>
</ResponsiveChartContainer>
{forecastData && (
<WeatherChart forecastData={forecastData.getForecastByCoordinates} palette={palette} />
)}
</main>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { ToggleButton, ToggleButtonGroup } from '@mui/material';
import { ForecastDaysSelectorProps } from '../domain/types/ForecastDaysSelectorProps';
import { ForecastDaysSelectorProps } from '../../domain/types/ForecastDaysSelectorProps';

export const ForecastDaysSelector: React.FC<ForecastDaysSelectorProps> = ({ forecastDays, onChange }) => {
const forecastPeriods = [3, 7, 14];
Expand Down
121 changes: 121 additions & 0 deletions src/components/chart/WeatherChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React, { useState, useEffect } from 'react';
import { ResponsiveChartContainer, LinePlot, ChartsXAxis, ChartsYAxis, ChartsLegend, ChartsGrid, ChartsReferenceLine, ChartsTooltip, BarPlot } from '@mui/x-charts';
import { WeatherData } from './../../domain/types/WeatherData';
import { Theme } from '@mui/material';
import { formatDateForChart } from './../../utils/formatDateForWidget';
import { ChartData } from './../../domain/types/ChartData';

export const WeatherChart: React.FC<{ forecastData: WeatherData[]; palette: Theme['palette'] }> = ({ forecastData, palette }) => {
const [chartData, setChartData] = useState<ChartData>({
xAxisData: [],
seriesData: [],
});

useEffect(() => {
if (forecastData) {
const chartData = forecastData.reduce((data: ChartData, forecast: WeatherData) => {
data.xAxisData.push(new Date(forecast.timestamp));
data.seriesData[0].push(forecast.temperature);
data.seriesData[1].push(forecast.precipitation);
data.seriesData[2].push(forecast.cloudCover);
data.seriesData[3].push(forecast.windSpeed);
return data;
}, {
xAxisData: [],
seriesData: [[], [], [], []],
seriesLabels: ['Temperature, C', 'Precipitation, mm', 'Cloud Cover', 'Wind Speed, km/h'],
});

setChartData(chartData);
}
}, [forecastData]);

return (
<>
{(chartData && chartData.seriesLabels) && (
<ResponsiveChartContainer
xAxis={[{
id: 'bottomAxis',
label: "Timeline",
data: chartData.xAxisData,
scaleType: 'band',
valueFormatter: formatDateForChart,
},
{
id: 'topAxis',
label: "Cloud Cover %",
data: chartData.seriesData[2],
scaleType: 'point'
}]}
yAxis={[
{ id: 'leftAxis', label: "Temperature, °C", scaleType: 'linear' },
{
id: 'rightAxis',
label: "Wind speed, km/h",
data: chartData.seriesData[3],
scaleType: 'linear',
}
]}
series={[
{ type: 'line', label: chartData.seriesLabels![0], data: chartData.seriesData[0], color: palette.chart.tempterature },
{ type: 'bar', label: chartData.seriesLabels![1], data: chartData.seriesData[1], color: palette.chart.precipitation },
{ type: 'band', label: chartData.seriesLabels![2], data: chartData.seriesData[2], xAxisKey: 'topAxis' },
{ type: 'line', label: chartData.seriesLabels![3], data: chartData.seriesData[3], yAxisKey: 'rightAxis', color: palette.chart.wind },
]}
height={400}
margin={{ top: 120 }}
sx={{
"& .MuiChartsAxis-left .MuiChartsAxis-label": {
strokeWidth: "0.4",
fill: `${palette.chart.tempterature}`,
},
"& .MuiChartsAxis-left line": {
stroke: `${palette.chart.tempterature}`,
},
"& .MuiChartsAxis-left text": {
stroke: `${palette.chart.tempterature}`,
},
// right Axis styles
"& .MuiChartsAxis-right .MuiChartsAxis-label": {
strokeWidth: "0.4",
fill: `${palette.chart.wind}`,
},
"& .MuiChartsAxis-right line": {
stroke: `${palette.chart.wind}`,
},
"& .MuiChartsAxis-right text": {
stroke: `${palette.chart.wind}`,
},
// top Axis styles
"& .MuiChartsAxis-top .MuiChartsAxis-label": {
strokeWidth: "0.4",
fill: `${palette.chart.clouds}`,
},
"& .MuiChartsAxis-top line": {
stroke: `${palette.chart.clounds}`,
},
"& .MuiChartsAxis-top text": {
stroke: `${palette.chart.clouds}`,
strokeWidth: "0.6",
},
}}
>
<LinePlot />
<ChartsYAxis axisId='rightAxis' position='right' />
<ChartsXAxis axisId='bottomAxis' position='bottom' />
<ChartsXAxis axisId='topAxis' position='top' />
<ChartsYAxis axisId='leftAxis' position='left' />
<ChartsLegend />
<BarPlot />
<ChartsTooltip />
<ChartsGrid horizontal={true} vertical={true} />
<ChartsReferenceLine
y={0}
labelAlign="end"
lineStyle={{ stroke: palette.text.primary, strokeDasharray: '5 5' }}
/>
</ResponsiveChartContainer>
)}
</>
);
};
5 changes: 5 additions & 0 deletions src/domain/types/ChartData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type ChartData = {
xAxisData: Date[];
seriesData: number[][];
seriesLabels?: string[];
};
6 changes: 3 additions & 3 deletions src/graphql/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ export const GET_FORECAST_FROM_COORDS_QUERY = gql`
}
timestamp
temperature
humidity
windSpeed
cloudCover
sunshineDuration
precipitationProbability
precipitation
temperatureUnit
# humidity
# sunshineDuration
# precipitationProbability
}
}
`;
4 changes: 2 additions & 2 deletions src/infrastructure/providers/ThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import React from 'react';
import { useLocation } from 'react-router-dom';
import { ThemeProvider as MUIThemeProvider } from '@mui/material/styles';
import { createTheme, PaletteMode } from "@mui/material";
Expand All @@ -10,7 +10,7 @@ enum Themes {

export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const location = useLocation();
const searchParams = useMemo(() => new URLSearchParams(location.search), [location.search]);
const searchParams = new URLSearchParams(location.search);
const getThemeMode = (themeParam: string | null) => themeParam === Themes.Light ? Themes.Light : Themes.Dark;
const mode: PaletteMode = getThemeMode(searchParams.get('theme'));
const themeInit = (mode: PaletteMode) => createTheme({
Expand Down
5 changes: 5 additions & 0 deletions src/utils/formatDateForWidget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import dayjs from 'dayjs';

export const formatDateForChart = (date: Date | string): string => {
return dayjs(date).format("MMM D, hA");
};

0 comments on commit 6ef8b13

Please sign in to comment.