Browse Source

feat: implement multiple charts on multiple sites

master
erdar2 4 years ago
parent
commit
c6fca1a38a
  1. 4
      package.json
  2. 116
      resources/js/components/App.tsx
  3. 45
      resources/js/components/AreaChart.tsx
  4. 31
      resources/js/components/DatePicker.tsx
  5. 131
      resources/js/components/Home.tsx
  6. 45
      resources/js/components/LineChart.tsx
  7. 48
      resources/js/components/PieChart.tsx
  8. 114
      resources/js/components/RecoveryRate.tsx
  9. 10
      resources/js/services/covidDataApi.ts
  10. 31
      resources/js/types.ts

4
package.json

@ -27,15 +27,19 @@
"@emotion/react": "^11.7.1", "@emotion/react": "^11.7.1",
"@emotion/styled": "^11.6.0", "@emotion/styled": "^11.6.0",
"@mui/icons-material": "^5.2.5", "@mui/icons-material": "^5.2.5",
"@mui/lab": "^5.0.0-alpha.61",
"@mui/material": "^5.2.5", "@mui/material": "^5.2.5",
"@reduxjs/toolkit": "^1.7.1", "@reduxjs/toolkit": "^1.7.1",
"@rtk-query/graphql-request-base-query": "^1.0.3", "@rtk-query/graphql-request-base-query": "^1.0.3",
"@types/react": "^17.0.37", "@types/react": "^17.0.37",
"@types/react-dom": "^17.0.11", "@types/react-dom": "^17.0.11",
"@types/react-redux": "^7.1.20", "@types/react-redux": "^7.1.20",
"@types/recharts": "^1.8.23",
"date-fns": "^2.27.0",
"graphql-request": "^3.7.0", "graphql-request": "^3.7.0",
"react-redux": "^7.2.6", "react-redux": "^7.2.6",
"react-router-dom": "^6.2.1", "react-router-dom": "^6.2.1",
"recharts": "^2.1.8",
"ts-loader": "^9.2.6", "ts-loader": "^9.2.6",
"typescript": "^4.5.4" "typescript": "^4.5.4"
} }

116
resources/js/components/App.tsx

@ -15,10 +15,12 @@ import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import ListItem from "@mui/material/ListItem"; import ListItem from "@mui/material/ListItem";
import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText"; import ListItemText from "@mui/material/ListItemText";
import InboxIcon from "@mui/icons-material/MoveToInbox"; import HomeIcon from "@mui/icons-material/Home";
import MailIcon from "@mui/icons-material/Mail"; import TrendingUpIcon from '@mui/icons-material/TrendingUp';
import { Routes, Route, Link } from "react-router-dom"; import { Routes, Route, Link } from "react-router-dom";
import Paper from "@mui/material/Paper"; import Paper from "@mui/material/Paper";
import Home from "./Home";
import RecoveryRate from "./RecoveryRate";
const drawerWidth = 240; const drawerWidth = 240;
@ -93,8 +95,8 @@ const Drawer = styled(MuiDrawer, {
const Container = styled(Paper)(({ theme }) => ({ const Container = styled(Paper)(({ theme }) => ({
...theme.typography.body2, ...theme.typography.body2,
padding: 16 padding: 16,
})); }));
export default function App() { export default function App() {
const theme = useTheme(); const theme = useTheme();
@ -142,20 +144,18 @@ export default function App() {
</DrawerHeader> </DrawerHeader>
<Divider /> <Divider />
<List> <List>
{["Inbox", "Starred", "Send email", "Drafts"].map( <ListItem button component={Link} to="/">
(text, index) => ( <ListItemIcon>
<ListItem button key={text}> <HomeIcon />
<ListItemIcon> </ListItemIcon>
{index % 2 === 0 ? ( <ListItemText primary={"Főoldal"} />
<InboxIcon /> </ListItem>
) : ( <ListItem button component={Link} to="/gyogyulas-halalozas">
<MailIcon /> <ListItemIcon>
)} <TrendingUpIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={text} /> <ListItemText primary={"Gyógyulási ráta"} />
</ListItem> </ListItem>
)
)}
</List> </List>
</Drawer> </Drawer>
<Box component="main" sx={{ flexGrow: 1, p: 3 }}> <Box component="main" sx={{ flexGrow: 1, p: 3 }}>
@ -164,85 +164,11 @@ export default function App() {
<Routes> <Routes>
<Route <Route
path="/" path="/"
element={ element={<Home />}
<>
<Typography paragraph>
Lorem ipsum dolor sit amet, consectetur
adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna
aliqua. Rhoncus dolor purus non enim
praesent elementum facilisis leo vel.
Risus at ultrices mi tempus imperdiet.
Semper risus in hendrerit gravida rutrum
quisque non tellus. Convallis convallis
tellus id interdum velit laoreet id
donec ultrices. Odio morbi quis commodo
odio aenean sed adipiscing. Amet nisl
suscipit adipiscing bibendum est
ultricies integer quis. Cursus euismod
quis viverra nibh cras. Metus vulputate
eu scelerisque felis imperdiet proin
fermentum leo. Mauris commodo quis
imperdiet massa tincidunt. Cras
tincidunt lobortis feugiat vivamus at
augue. At augue eget arcu dictum varius
duis at consectetur lorem. Velit sed
ullamcorper morbi tincidunt. Lorem donec
massa sapien faucibus et molestie ac.
</Typography>
<Typography paragraph>
Consequat mauris nunc congue nisi vitae
suscipit. Fringilla est ullamcorper eget
nulla facilisi etiam dignissim diam.
Pulvinar elementum integer enim neque
volutpat ac tincidunt. Ornare
suspendisse sed nisi lacus sed viverra
tellus. Purus sit amet volutpat
consequat mauris. Elementum eu facilisis
sed odio morbi. Euismod lacinia at quis
risus sed vulputate odio. Morbi
tincidunt ornare massa eget egestas
purus viverra accumsan in. In hendrerit
gravida rutrum quisque non tellus orci
ac. Pellentesque nec nam aliquam sem et
tortor. Habitant morbi tristique
senectus et. Adipiscing elit duis
tristique sollicitudin nibh sit. Ornare
aenean euismod elementum nisi quis
eleifend. Commodo viverra maecenas
accumsan lacus vel facilisis. Nulla
posuere sollicitudin aliquam ultrices
sagittis orci a.
</Typography>
</>
}
/> />
<Route <Route
path="/about" path="/gyogyulas-halalozas"
element={ element={<RecoveryRate />}
<Typography paragraph>
Consequat mauris nunc congue nisi vitae
suscipit. Fringilla est ullamcorper eget
nulla facilisi etiam dignissim diam.
Pulvinar elementum integer enim neque
volutpat ac tincidunt. Ornare suspendisse
sed nisi lacus sed viverra tellus. Purus sit
amet volutpat consequat mauris. Elementum eu
facilisis sed odio morbi. Euismod lacinia at
quis risus sed vulputate odio. Morbi
tincidunt ornare massa eget egestas purus
viverra accumsan in. In hendrerit gravida
rutrum quisque non tellus orci ac.
Pellentesque nec nam aliquam sem et tortor.
Habitant morbi tristique senectus et.
Adipiscing elit duis tristique sollicitudin
nibh sit. Ornare aenean euismod elementum
nisi quis eleifend. Commodo viverra maecenas
accumsan lacus vel facilisis. Nulla posuere
sollicitudin aliquam ultrices sagittis orci
a.
</Typography>
}
/> />
</Routes> </Routes>
</Container> </Container>

45
resources/js/components/AreaChart.tsx

@ -0,0 +1,45 @@
import React from "react";
import {
ResponsiveContainer,
AreaChart as AC,
CartesianGrid,
XAxis,
YAxis,
Tooltip,
Legend,
Area,
} from "recharts";
import { ChartProps } from "../types";
export default function AreaChart(props: ChartProps) {
return (
<ResponsiveContainer height={300}>
<AC
width={600}
height={300}
data={props.data.map((d) => {
return {
name: d.name,
...d.values,
};
})}
>
{Object.keys(props.data[0].values).map((k, i) => (
<Area
type="monotone"
key={i}
dataKey={k}
stroke="#8884d8"
fill="#8884d8"
name={k}
/>
))}
<CartesianGrid stroke="#ccc" />
<XAxis dataKey="name" />
<YAxis scale={props.scale ?? "auto"} />
<Tooltip />
<Legend verticalAlign="top" />
</AC>
</ResponsiveContainer>
);
}

31
resources/js/components/DatePicker.tsx

@ -0,0 +1,31 @@
import React from "react";
import TextField from "@mui/material/TextField";
import AdapterDateFns from "@mui/lab/AdapterDateFns";
import LocalizationProvider from "@mui/lab/LocalizationProvider";
import DateRangePicker, { DateRange } from "@mui/lab/DateRangePicker";
import Box from "@mui/material/Box";
import { DatePickerProps } from "../types";
export default function DatePicker(props: DatePickerProps) {
return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DateRangePicker
value={props.value}
minDate={props.minDate}
maxDate={props.maxDate}
onChange={(newValue: DateRange<Date>) => {
props.setValue(newValue);
}}
startText="Kezdeti dátum"
endText="Végső dátum"
renderInput={(startProps, endProps) => (
<>
<TextField {...startProps} />
<Box sx={{ mx: 2 }}> - </Box>
<TextField {...endProps} />
</>
)}
/>
</LocalizationProvider>
);
}

131
resources/js/components/Home.tsx

@ -0,0 +1,131 @@
import React from "react";
import AreaChart from "./AreaChart";
import Typography from "@mui/material/Typography";
import Grid from "@mui/material/Grid";
import { useGetAllDataQuery } from "../services/covidDataApi";
import { ChartData } from "../types";
import { DateRange } from "@mui/lab/DateRangePicker";
import DatePicker from "./DatePicker";
export default function Home() {
const { data, error, isLoading } = useGetAllDataQuery({});
const [dateRange, setDateRange] = React.useState<DateRange<Date>>([
null,
null,
]);
let allData: ChartData[] = [];
let newInfected: ChartData[] = [];
if (data) {
if (dateRange[0] == null && dateRange[1] == null) {
setDateRange([
new Date(data[0].created_at),
new Date(data[data.length - 1].created_at),
]);
}
allData = data
.map((d) => {
const date = new Date(d.created_at);
const currentInfected = d.infected - d.recovered - d.deceased;
return {
name:
date.getFullYear() +
"." +
("0" + (date.getMonth() + 1)).slice(-2) +
"." +
("0" + date.getDate()).slice(-2),
values: {
"Aktív fertőzöttek napi alakulása": currentInfected > 0 ? currentInfected : 0,
},
};
})
.filter((d) => {
const ddate = new Date(d.name);
if (dateRange[0] == null || dateRange[1] == null) return true;
let endDate = new Date(dateRange[1].getTime());
endDate.setDate(endDate.getDate() + 1);
return ddate >= dateRange[0] && ddate < endDate;
});
let newData = 0;
newInfected = data
.map((d) => {
const date = new Date(d.created_at);
const newActual = d.infected - newData;
newData = d.infected;
return {
name:
date.getFullYear() +
"." +
("0" + (date.getMonth() + 1)).slice(-2) +
"." +
("0" + date.getDate()).slice(-2),
values: {
"Új fertőzöttek napi alakulása": newActual > 0 ? newActual : 0,
},
};
})
.filter((d) => {
const ddate = new Date(d.name);
if (dateRange[0] == null || dateRange[1] == null) return true;
let endDate = new Date(dateRange[1].getTime());
endDate.setDate(endDate.getDate() + 1);
return ddate >= dateRange[0] && ddate < endDate;
});
}
return (
<>
<Typography paragraph>
Ez az oldal elsősorban a hivatalos, kormány által közölt
adatokat dolgozza fel, de nem tekinthető hivatalos
tájékoztatásnak, továbbá nem vállalunk felelősséget a kormány
által közölt adatok és információk valóságtartalmáért. Kérjük, a
hivatalos információkért látogasson el a kormány által
működtetett{" "}
<a href="https://koronavirus.gov.hu/" target="_blank">
koronavirus.gov.hu
</a>{" "}
oldalra, illetve olvassa a nagyobb hírportálok híreit.
</Typography>
{typeof data != "undefined" ? (
<Grid container spacing={2}>
<Grid
item
xs={12}
container
spacing={2}
justifyContent="center"
>
<Grid item>
<DatePicker
minDate={new Date(data[0].created_at)}
maxDate={
new Date(data[data.length - 1].created_at)
}
value={dateRange}
setValue={(n: DateRange<Date>) =>
setDateRange(n)
}
/>
</Grid>
</Grid>
<Grid item md={6} xs={12}>
<AreaChart
data={allData}
scale="sqrt"
/>
</Grid>
<Grid item md={6} xs={12}>
<AreaChart
data={newInfected}
scale="sqrt"
/>
</Grid>
</Grid>
) : null}
</>
);
}

45
resources/js/components/LineChart.tsx

@ -0,0 +1,45 @@
import React from "react";
import {
ResponsiveContainer,
LineChart as LC,
CartesianGrid,
XAxis,
YAxis,
Tooltip,
Legend,
Line,
} from "recharts";
import { ChartProps } from "../types";
export default function LineChart(props: ChartProps) {
return (
<ResponsiveContainer height={300}>
<LC
width={600}
height={300}
data={props.data.map((d) => {
return {
name: d.name,
...d.values,
};
})}
>
{Object.keys(props.data[0].values).map((k, i) => (
<Line
type="monotone"
key={i}
dataKey={k}
stroke="#8884d8"
name={k}
dot={false}
/>
))}
<CartesianGrid stroke="#ccc" />
<XAxis dataKey="name" />
<YAxis scale={props.scale ?? "auto"} />
<Tooltip />
<Legend verticalAlign="top" />
</LC>
</ResponsiveContainer>
);
}

48
resources/js/components/PieChart.tsx

@ -0,0 +1,48 @@
import React from "react";
import {
PieChart as PC,
Pie,
Legend,
Tooltip,
ResponsiveContainer,
Cell,
} from "recharts";
import { ChartProps } from "../types";
const colors = ["#82ca9d", "#8884d8"];
export default function PieChart(props: ChartProps) {
return (
<ResponsiveContainer height={300}>
<PC width={600} height={300}>
<Pie
dataKey="value"
data={Object.keys(
props.data[props.data.length - 1].values
).map((k) => ({
name: k,
value: props.data[props.data.length - 1].values[k],
}))}
innerRadius={40}
outerRadius={80}
fill="#82ca9d"
label
>
{Object.keys(props.data[props.data.length - 1].values)
.map((k) => ({
name: k,
value: props.data[props.data.length - 1].values[k],
}))
.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={colors[index % colors.length]}
/>
))}
</Pie>
<Tooltip />
<Legend />
</PC>
</ResponsiveContainer>
);
}

114
resources/js/components/RecoveryRate.tsx

@ -0,0 +1,114 @@
import React from "react";
import Typography from "@mui/material/Typography";
import Grid from "@mui/material/Grid";
import { useGetAllDataQuery } from "../services/covidDataApi";
import { ChartData } from "../types";
import { DateRange } from "@mui/lab/DateRangePicker";
import DatePicker from "./DatePicker";
import LineChart from "./LineChart";
import PieChart from "./PieChart";
export default function RecoveryRate() {
const { data, error, isLoading } = useGetAllDataQuery({});
const [dateRange, setDateRange] = React.useState<DateRange<Date>>([
null,
null,
]);
let lineData: ChartData[] = [];
let pieData: ChartData[] = [];
if (data) {
if (dateRange[0] == null && dateRange[1] == null) {
setDateRange([
new Date(data[0].created_at),
new Date(data[data.length - 1].created_at),
]);
}
lineData = data
.map((d) => {
const date = new Date(d.created_at);
return {
name:
date.getFullYear() +
"." +
("0" + (date.getMonth() + 1)).slice(-2) +
"." +
("0" + date.getDate()).slice(-2),
values: {
"Gyógyultak száma": d.recovered ?? 0,
"Elhalálozottak száma": d.deceased ?? 0,
},
};
})
.filter((d) => {
const ddate = new Date(d.name);
if (dateRange[0] == null || dateRange[1] == null) return true;
let endDate = new Date(dateRange[1].getTime());
endDate.setDate(endDate.getDate() + 1);
return ddate >= dateRange[0] && ddate < endDate;
});
pieData = data
.filter((n) => {
const ddate = new Date(n.created_at);
if (dateRange[0] == null || dateRange[1] == null) return true;
let endDate = new Date(dateRange[1].getTime());
endDate.setDate(endDate.getDate() + 1);
return ddate >= dateRange[0] && ddate < endDate;
})
.map((d) => {
const date = new Date(d.created_at);
return {
name:
date.getFullYear() +
"." +
("0" + (date.getMonth() + 1)).slice(-2) +
"." +
("0" + date.getDate()).slice(-2),
values: {
Gyógyultak: d.recovered ?? 0,
Elhalálozottak: d.deceased ?? 0,
},
};
});
}
return (
<>
<Typography paragraph>
A gyógyultak és az elhalálozottak számának alakulása
</Typography>
{typeof data != "undefined" ? (
<Grid container spacing={2}>
<Grid
item
xs={12}
container
spacing={2}
justifyContent="center"
>
<Grid item>
<DatePicker
minDate={new Date(data[0].created_at)}
maxDate={
new Date(data[data.length - 1].created_at)
}
value={dateRange}
setValue={(n: DateRange<Date>) =>
setDateRange(n)
}
/>
</Grid>
</Grid>
<Grid item md={6} xs={12}>
<LineChart data={lineData} scale="sqrt" />
</Grid>
<Grid item md={6} xs={12}>
<PieChart data={pieData} />
</Grid>
</Grid>
) : null}
</>
);
}

10
resources/js/services/covidDataApi.ts

@ -1,15 +1,7 @@
import { createApi } from "@reduxjs/toolkit/query/react"; import { createApi } from "@reduxjs/toolkit/query/react";
import { gql } from "graphql-request"; import { gql } from "graphql-request";
import { graphqlRequestBaseQuery } from "@rtk-query/graphql-request-base-query"; import { graphqlRequestBaseQuery } from "@rtk-query/graphql-request-base-query";
import { CovidData } from "../types";
export interface CovidData {
infected: number;
deceased: number;
recovered: number;
quarantined: number;
tested: number;
created_at: Date;
}
export interface GetAllDataResponse { export interface GetAllDataResponse {
allData: CovidData[]; allData: CovidData[];

31
resources/js/types.ts

@ -0,0 +1,31 @@
import { DateRange } from "@mui/lab/DateRangePicker";
import { ScaleType } from "recharts/types/util/types";
export interface CovidData {
infected: number;
deceased: number;
recovered: number;
quarantined: number;
tested: number;
created_at: Date;
}
export interface ChartData {
name: string;
values: {
[key: string]: number
};
}
export interface ChartProps {
data: ChartData[];
scale?: ScaleType;
}
export interface DatePickerProps {
minDate: Date;
maxDate: Date;
value: DateRange<Date>;
setValue: Function;
}
Loading…
Cancel
Save