diff --git a/.gitignore b/.gitignore index fc81720..0cd5bbe 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -.vscode \ No newline at end of file +.vscode +.idea \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index bea74f5..0000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1624384994664 - - - 1624387898827 - - - 1624388208242 - - - - - - - - - - - - \ No newline at end of file diff --git a/package.json b/package.json index 71cd016..dea4b4a 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@types/react-dom": "^17.0.9", "@types/react-map-gl": "^6.1.1", "@types/react-router-dom": "^5.3.1", + "@types/react-vis": "^1.11.9", "@typescript-eslint/eslint-plugin": "^4.29.3", "@typescript-eslint/parser": "^4.29.3", "babel-eslint": "^10.1.0", @@ -68,6 +69,7 @@ "react-redux": "^7.2.5", "react-refresh": "^0.8.3", "react-router-dom": "^5.3.0", + "react-vis": "^1.11.7", "resolve": "1.18.1", "resolve-url-loader": "^3.1.2", "sass-loader": "^10.0.5", diff --git a/src/App.tsx b/src/App.tsx index d991707..676b166 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,38 +1,32 @@ import * as React from 'react'; -import { useState } from 'react'; -import ReactMapGL from 'react-map-gl'; import { Switch, Route, BrowserRouter } from 'react-router-dom'; +import HomePage from './features/page/home/HomePage'; +import SimulationList from './features/page/simulation-list/SimulationList'; import SimulationPage from './features/simulation/components/SimulationPage'; +import CompareSimulationsPageWrapper from './features/simulation/statistics/CompareSimulationsPageWrapper'; +import StatisticsPageWrapper from './features/simulation/statistics/StatisticsPageWrapper'; -type ViewPort = { - width: number; - height: number; - latitude: number; - longitude: number; - zoom: number; -}; function App(): JSX.Element { - const [viewport, setViewport] = useState({ - width: 1500, - height: 1000, - latitude: 37.7577, - longitude: -122.4376, - zoom: 8, - }); return ( - + + + + + + + + + + - setViewport(nextViewport)} - /> + diff --git a/src/features/page/home/HomePage.tsx b/src/features/page/home/HomePage.tsx new file mode 100644 index 0000000..73993f1 --- /dev/null +++ b/src/features/page/home/HomePage.tsx @@ -0,0 +1,65 @@ +import { + Button, Typography, Box, styled, +} from '@mui/material'; +import React, { useState } from 'react'; +import { useHistory } from 'react-router-dom'; + + +import CreateSimulationDialog from './dialogs/CreateSimulationDialog'; + +const MainContainer = styled(Box)(() => ({ + width: '100%', + height: '100%', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + flexDirection: 'column', + textAlign: 'center', +})); + +const SectionBox = styled(Box)(() => ({ + margin: '10px', +})); + +const ActionButton = styled(Button)(() => ({ + padding: '10px', + margin: '10px', +})); + +export default function HomePage(): JSX.Element{ + + const [newSimulationDialogOpened, setNewSimulationDialogOpened] = useState(false); + const history = useHistory(); + + const onViewSimulationsClicked = () => { + history.push('/simulations/all'); + }; + + return ( + + + + Kraksim v2 + + + Traffic Simulator + + + + setNewSimulationDialogOpened(true)}> + Create new simulation + + setNewSimulationDialogOpened(false)} + /> + + View simulation statistics + + + Compare simulations + + + + ); +} \ No newline at end of file diff --git a/src/features/page/home/dialogs/CreateSimulationDialog.tsx b/src/features/page/home/dialogs/CreateSimulationDialog.tsx new file mode 100644 index 0000000..ff30fea --- /dev/null +++ b/src/features/page/home/dialogs/CreateSimulationDialog.tsx @@ -0,0 +1,48 @@ +import { + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + DialogActions, + Button, + DialogProps, + Select, + MenuItem, +} from '@mui/material'; +import React, { useState } from 'react'; +import { useHistory } from 'react-router-dom'; + +import { useGetAllMapsBasicInfoQuery } from '../../../map/mapApi'; + +export default function CreateSimulationDialog({ open, onClose }: DialogProps): JSX.Element{ + + const { data } = useGetAllMapsBasicInfoQuery(); + const [mapId, setMapId] = useState(''); + const history = useHistory(); + + const handleClick = () => { + history.push(`/simulations/create?mapId=${mapId}`); + }; + + return ( + + Select Map + + + To create a simulation, select a map for it + + + + + + + + ); +} \ No newline at end of file diff --git a/src/features/page/simulation-list/SimulationList.tsx b/src/features/page/simulation-list/SimulationList.tsx new file mode 100644 index 0000000..4473c26 --- /dev/null +++ b/src/features/page/simulation-list/SimulationList.tsx @@ -0,0 +1,39 @@ +import { + TableContainer, Paper, Table, TableHead, TableRow, TableCell, TableBody, +} from '@mui/material'; +import React from 'react'; + +import { useGetAllSimulationsQuery } from '../../simulation/simulationApi'; + +export default function SimulationList() : JSX.Element { + + const { data } = useGetAllSimulationsQuery(); + + return ( + + + + + ID + Name + Map ID + Type + + + + {data?.map((row) => ( + + {row.id} + {row.name} + {row.mapId} + {row.type} + + ))} + +
+
+ ); +} \ No newline at end of file diff --git a/src/features/simulation/components/form/SimulationFormWrapper.tsx b/src/features/simulation/components/form/SimulationFormWrapper.tsx index 780bd23..4af720f 100644 --- a/src/features/simulation/components/form/SimulationFormWrapper.tsx +++ b/src/features/simulation/components/form/SimulationFormWrapper.tsx @@ -1,33 +1,19 @@ -import { InputLabel, MenuItem } from '@mui/material'; -import React, { useState } from 'react'; +import React from 'react'; -import { useGetAllMapsBasicInfoQuery } from '../../../map/mapApi'; +import { useUrlParamsQuery } from '../../../common/hooks'; -import { FormSelect } from './common'; import CreateSimulationForm from './CreateSimulationForm'; export default function SimulationFormWrapper(): JSX.Element{ - const { data } = useGetAllMapsBasicInfoQuery(); - const [selectedMapId, setSelectedMapId] = useState(''); - const parsedId = selectedMapId === '' ? null : parseInt(selectedMapId); + const selectedMapId = useUrlParamsQuery().get('mapId'); + const parsedId = selectedMapId === null ? null : parseInt(selectedMapId); return (
- { data && -
-

Select Map for a new Simulation

- Map - setSelectedMapId(e.target.value as string)} - label="Map"> - {data.map(({ id, name }) => {name})} - -
- } - {parsedId && } + {parsedId ? + : +
Sorry, no valid mapId found in querystring
}
); } \ No newline at end of file diff --git a/src/features/simulation/simulationApi.ts b/src/features/simulation/simulationApi.ts index 3f782f6..83a2100 100644 --- a/src/features/simulation/simulationApi.ts +++ b/src/features/simulation/simulationApi.ts @@ -6,11 +6,14 @@ import type { import type { CreateSimulationRequest, SimulateRequest, } from './requests'; +import type { + StateStatistics, +} from './statistics/types'; export const simulationApi = createApi({ reducerPath: 'simulationApi', baseQuery: fetchBaseQuery({ baseUrl: process.env.REACT_APP_API_URL || 'http://localhost:8080/' }), - tagTypes: ['Simulation', 'SimplifiedSimulation'], + tagTypes: ['Simulation', 'SimplifiedSimulation', 'Statistics'], endpoints: (builder) => ({ getSimulationById: builder.query({ query: (id) => ({ url: `simulation/${id}` }), @@ -28,7 +31,11 @@ export const simulationApi = createApi({ simulate: builder.mutation({ query: ({ id, times }) => ({ url: `simulation/simulate?id=${id}×=${times}`, method: 'POST' }), invalidatesTags: (result) => - (result ? [{ id: result.id, type: 'Simulation' as const }, 'Simulation'] : ['Simulation']), + (result ? + [{ id: result.id, type: 'Simulation' as const }, + { id: result.id, type: 'Statistics' as const }, + 'Simulation', 'Statistics'] : + ['Simulation', 'Statistics']), }), deleteSimulation: builder.mutation({ query: (id) => ({ url: `simulation/delete/${id}`, method: 'DELETE' }), @@ -38,6 +45,12 @@ export const simulationApi = createApi({ query: () => ({ url: 'simulation/populate', method: 'POST' }), invalidatesTags: ['Simulation', 'SimplifiedSimulation'], }), + getStatisticsFromSimulation: builder.query({ + query: (simulationId) => ({ url:`statistics/simulation/${simulationId}` }), + providesTags: (result) => + (result && result.length > 0 ? + [{ id: result[0].simulationId, type: 'Statistics' as const }, 'Statistics'] : ['Statistics']), + }), }), }); @@ -49,4 +62,5 @@ export const { useSimulateMutation, useDeleteSimulationMutation, usePopulateMutation, + useGetStatisticsFromSimulationQuery, } = simulationApi; \ No newline at end of file diff --git a/src/features/simulation/statistics/CompareSImulationsPage.tsx b/src/features/simulation/statistics/CompareSImulationsPage.tsx new file mode 100644 index 0000000..6892978 --- /dev/null +++ b/src/features/simulation/statistics/CompareSImulationsPage.tsx @@ -0,0 +1,135 @@ +import { Typography } from '@mui/material'; +import React from 'react'; + +import { useGetStatisticsFromSimulationQuery } from '../simulationApi'; + +import { LineBarChart } from './components/charts/LineBarChart'; +import LineBarChartWithDropdown from './components/charts/LineBarChartWithDropdown'; +import { ChartBox, StatisticsContainer } from './components/style'; +import { getAllStatsFromData, renderLoadingErrorOrComponent } from './utils'; + +interface Props { + firstSimulationId: number, + secondSimulationId: number, +} + +export default function CompareSimulationsPage({ firstSimulationId, secondSimulationId }: Props): JSX.Element{ + + const { + data: firstSimulationData, + isLoading: isFirstSimulationLoading, + error: firstSimulationError, + } = useGetStatisticsFromSimulationQuery(firstSimulationId); + + const { + data: secondSimulationData, + isLoading: isSecondSimulationLoading, + error: secondSimulationError, + } = useGetStatisticsFromSimulationQuery(secondSimulationId); + + const roadNames = firstSimulationData ? firstSimulationData[0].roadNames : {}; + + + const firstSimulationParsedStats = getAllStatsFromData(firstSimulationData); + const secondSimulationParsedStats = getAllStatsFromData(secondSimulationData); + + const averageVelocityChart = renderLoadingErrorOrComponent( + , + isFirstSimulationLoading && isSecondSimulationLoading, + firstSimulationError ?? secondSimulationError, + ); + + const flowChart = renderLoadingErrorOrComponent( + , + isFirstSimulationLoading && isSecondSimulationLoading, + firstSimulationError ?? secondSimulationError, + ); + + const densityChart = renderLoadingErrorOrComponent( + , + isFirstSimulationLoading && isSecondSimulationLoading, + firstSimulationError ?? secondSimulationError, + ); + + const roadAvgChart = renderLoadingErrorOrComponent( + , + isFirstSimulationLoading && isSecondSimulationLoading, + firstSimulationError ?? secondSimulationError, + ); + + return ( + + + {`Statistics comparison for simulation ID: ${firstSimulationId} and ${secondSimulationId}`} + + +
+ {averageVelocityChart} +
+
+ +
+ {flowChart} +
+
+ {densityChart} +
+
+ +
+ {roadAvgChart} +
+
+
+ ); +} \ No newline at end of file diff --git a/src/features/simulation/statistics/CompareSimulationsPageWrapper.tsx b/src/features/simulation/statistics/CompareSimulationsPageWrapper.tsx new file mode 100644 index 0000000..29dcd10 --- /dev/null +++ b/src/features/simulation/statistics/CompareSimulationsPageWrapper.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +import { useUrlParamsQuery } from '../../common/hooks'; + +import CompareSimulationsPage from './CompareSImulationsPage'; + +export default function CompareSimulationsPageWrapper(): JSX.Element{ + + const queryParams = useUrlParamsQuery(); + const firstSimulationId = queryParams.get('firstSimulationId'); + const secondSimulationId = queryParams.get('secondSimulationId'); + + return ( +
+ {(firstSimulationId && secondSimulationId) ? + : +
firstSimulationId or secondSimulationId not in querystring :(
} +
+ ); +} \ No newline at end of file diff --git a/src/features/simulation/statistics/StatisticsPage.tsx b/src/features/simulation/statistics/StatisticsPage.tsx new file mode 100644 index 0000000..34f3600 --- /dev/null +++ b/src/features/simulation/statistics/StatisticsPage.tsx @@ -0,0 +1,117 @@ +import { Typography } from '@mui/material'; +import React from 'react'; + +import { useGetStatisticsFromSimulationQuery } from '../simulationApi'; + +import { LineBarChart } from './components/charts/LineBarChart'; +import LineBarChartWithDropdown from './components/charts/LineBarChartWithDropdown'; +import { ChartBox, StatisticsContainer } from './components/style'; +import { getAllStatsFromData, renderLoadingErrorOrComponent } from './utils'; + +interface Props { + selectedSimulationId: number +} + +export default function StatisticsPage({ selectedSimulationId }: Props): JSX.Element{ + + const { data, isLoading, error } = useGetStatisticsFromSimulationQuery(selectedSimulationId); + + const roadNames = data ? data[0].roadNames : {}; + + const { + currentAverageVelocityByTurn, + totalAverageVelocityByTurn, + currentFlowMap, + totalFlowMap, + currentDensityMap, + totalDensityMap, + currentRoadAvgVelocityMap, + totalRoadAvgVelocityMap, + } = getAllStatsFromData(data); + + const averageVelocityChart = renderLoadingErrorOrComponent( + , + isLoading, + error, + ); + + const flowChart = renderLoadingErrorOrComponent( + , + isLoading, + error, + ); + + const densityChart = renderLoadingErrorOrComponent( + , + isLoading, + error, + ); + + const roadAvgChart = renderLoadingErrorOrComponent( + , + isLoading, + error, + ); + + return ( + + + {`Statistics for simulation ID: ${selectedSimulationId}`} + + +
+ {averageVelocityChart} +
+
+ +
+ {flowChart} +
+
+ {densityChart} +
+
+ +
+ {roadAvgChart} +
+
+
+ ); +} \ No newline at end of file diff --git a/src/features/simulation/statistics/StatisticsPageWrapper.tsx b/src/features/simulation/statistics/StatisticsPageWrapper.tsx new file mode 100644 index 0000000..5361da5 --- /dev/null +++ b/src/features/simulation/statistics/StatisticsPageWrapper.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +import { useUrlParamsQuery } from '../../common/hooks'; + +import StatisticsPage from './StatisticsPage'; + + +export default function StatisticsPageWrapper(): JSX.Element{ + + const simulationId = useUrlParamsQuery().get('simulationId'); + + return ( +
+ {simulationId ? + : +
simulationId not in querystring :(
} +
+ ); +} \ No newline at end of file diff --git a/src/features/simulation/statistics/components/charts/LineBarChart.tsx b/src/features/simulation/statistics/components/charts/LineBarChart.tsx new file mode 100644 index 0000000..c2a6e21 --- /dev/null +++ b/src/features/simulation/statistics/components/charts/LineBarChart.tsx @@ -0,0 +1,89 @@ +import { Box, Typography } from '@mui/material'; +import React from 'react'; +import { + FlexibleWidthXYPlot as XYPlot, + XAxis, + YAxis, + VerticalGridLines, + HorizontalGridLines, + VerticalBarSeries, + DiscreteColorLegend, + LineSeries, +} from 'react-vis'; + +import 'react-vis/dist/style.css'; +import { Series } from './types'; + + + +interface Props { + barSeries: Series[], + lineSeries: Series[] + height: number, + barWidth: number, + title: string, +} + +export function LineBarChart({ + barSeries, height, lineSeries, barWidth, title, +}: Props): JSX.Element { + const barDataToPresent = barSeries + .map(series => series.data.map(entityData => ({ x: entityData.turn, y: entityData.value }))); + + + const lineDataToPresent = lineSeries + .map(series => series.data.map(entityData => ({ x: entityData.turn, y: entityData.value }))); + + + const labels = [...barSeries.map(x => ({ title: x.label })), ...lineSeries.map(x => ({ title: x.label }))]; + + const render = barDataToPresent.length > 0 || lineDataToPresent.length > 0 ? ( + + + {title} + + + + + + + {barDataToPresent.map(series => )} + {lineDataToPresent.map(series => )} +
+ +
+
+
+ ) : ( + + + {title} + + + No data found :( + + + + ''}/> + + + + + ); + + + return render; +} \ No newline at end of file diff --git a/src/features/simulation/statistics/components/charts/LineBarChartWithDropdown.tsx b/src/features/simulation/statistics/components/charts/LineBarChartWithDropdown.tsx new file mode 100644 index 0000000..25fdad3 --- /dev/null +++ b/src/features/simulation/statistics/components/charts/LineBarChartWithDropdown.tsx @@ -0,0 +1,47 @@ +import { MenuItem, Select } from '@mui/material'; +import React, { useState } from 'react'; + +import { LineBarChart } from './LineBarChart'; +import { Series } from './types'; + +interface Props{ + dropdownValues: number[], + barSeriesByEntity: Array>, + lineSeriesByEntity: Array>, + height: number, + barWidth: number, + roadNames: Record, + title: string, +} + +export default function LineBarChartWithDropdown({ + dropdownValues, barSeriesByEntity, lineSeriesByEntity, height, barWidth, roadNames, title, +}: Props, +): JSX.Element { + const [selectedElement, setSelectedElement] = useState(''); + + const parsedSelectedElement = parseInt(selectedElement); + const barSeries = barSeriesByEntity.filter(map => map.has(parsedSelectedElement)) + .map(map => map.get(parsedSelectedElement)) as Series[]; + + const lineSeries = lineSeriesByEntity.filter(map => map.has(parsedSelectedElement)) + .map(map => map.get(parsedSelectedElement)) as Series[]; + + return ( +
+ +
+ +
+
+ ); + +} \ No newline at end of file diff --git a/src/features/simulation/statistics/components/charts/types.ts b/src/features/simulation/statistics/components/charts/types.ts new file mode 100644 index 0000000..472f917 --- /dev/null +++ b/src/features/simulation/statistics/components/charts/types.ts @@ -0,0 +1,10 @@ +export interface StatisticsForEntity{ + turn: number, + entityId: number, + value: T, +} + +export interface Series { + data: Array>, + label: string, +} \ No newline at end of file diff --git a/src/features/simulation/statistics/components/style/index.tsx b/src/features/simulation/statistics/components/style/index.tsx new file mode 100644 index 0000000..e4c506f --- /dev/null +++ b/src/features/simulation/statistics/components/style/index.tsx @@ -0,0 +1,13 @@ +import styled from '@emotion/styled'; +import { + Box, Container, +} from '@mui/material'; + +export const ChartBox = styled(Box)(() => ({ + display: 'flex', +})); + +export const StatisticsContainer = styled(Container)(() => ({ + display: 'block', + margin: 'auto', +})); diff --git a/src/features/simulation/statistics/types.ts b/src/features/simulation/statistics/types.ts new file mode 100644 index 0000000..6993845 --- /dev/null +++ b/src/features/simulation/statistics/types.ts @@ -0,0 +1,20 @@ +interface SpeedStatistics { + wholeMapAverageSpeed: number, + roadAverageSpeed: IdToValue +} + +interface StatisticsValues { + speedStatistics: SpeedStatistics, + density: IdToValue, //roadId, density + roadFlowRatio: IdToValue //roadId, flowRatio +} + +export type IdToValue = Record; + +export interface StateStatistics { + simulationId: number, + turn: number, + currentStatisticsValues: StatisticsValues, + totalStatisticsValues: StatisticsValues, + roadNames: Record, +} \ No newline at end of file diff --git a/src/features/simulation/statistics/utils.tsx b/src/features/simulation/statistics/utils.tsx new file mode 100644 index 0000000..da1a7f4 --- /dev/null +++ b/src/features/simulation/statistics/utils.tsx @@ -0,0 +1,102 @@ +import { SerializedError } from '@reduxjs/toolkit'; +import { FetchBaseQueryError } from '@reduxjs/toolkit/dist/query'; +import React from 'react'; + +import { Series, StatisticsForEntity } from './components/charts/types'; +import { IdToValue, StateStatistics } from './types'; + +export function renderLoadingErrorOrComponent( + component: JSX.Element, isLoading: boolean, error: FetchBaseQueryError | SerializedError | undefined, +): JSX.Element { + if (error){ + return
There was an error. Refresh the page or try again later
; + } + if (isLoading){ + return
Loading...
; + } + return component; +} + +interface AllStats { + currentAverageVelocityByTurn: Array>, + totalAverageVelocityByTurn: Array>, + currentFlowMap: Map, + totalFlowMap: Map, + currentDensityMap: Map, + totalDensityMap: Map, + currentRoadAvgVelocityMap: Map, + totalRoadAvgVelocityMap: Map, +} + +export function getAllStatsFromData(data: StateStatistics[] | undefined) : AllStats{ + const roadNames = data && data.length != 0 ? data[0].roadNames : {}; + + const currentAverageVelocityByTurn = (data ?? []).map(({ turn, simulationId, currentStatisticsValues }) => + ({ turn, entityId: simulationId, value: currentStatisticsValues.speedStatistics.wholeMapAverageSpeed })); + + const totalAverageVelocityByTurn = (data ?? []).map(({ turn, simulationId, totalStatisticsValues }) => + ({ turn, entityId: simulationId, value: totalStatisticsValues.speedStatistics.wholeMapAverageSpeed })); + + const currentFlowMap = mapsToSeriesMap((data ?? []).map( + ({ turn, currentStatisticsValues }) => ({ turn, data: currentStatisticsValues.roadFlowRatio }), + ), 'Current Flow', roadNames); + + const totalFlowMap = mapsToSeriesMap((data ?? []).map( + ({ turn, totalStatisticsValues }) => ({ turn, data: totalStatisticsValues.roadFlowRatio }), + ), 'Total Flow', roadNames); + + const currentDensityMap = mapsToSeriesMap((data ?? []).map( + ({ turn, currentStatisticsValues }) => ({ turn, data: currentStatisticsValues.density }), + ), 'Current Density', roadNames); + + const totalDensityMap = mapsToSeriesMap((data ?? []).map( + ({ turn, totalStatisticsValues }) => ({ turn, data: totalStatisticsValues.density }), + ), 'Total Density', roadNames); + + const currentRoadAvgVelocityMap = mapsToSeriesMap((data ?? []).map( + ({ turn, currentStatisticsValues }) => ({ turn, data: currentStatisticsValues.speedStatistics.roadAverageSpeed }), + ), 'Current Average Velocity', roadNames); + + const totalRoadAvgVelocityMap = mapsToSeriesMap((data ?? []).map( + ({ turn, totalStatisticsValues }) => ({ turn, data: totalStatisticsValues.speedStatistics.roadAverageSpeed }), + ), 'Total Average Velocity', roadNames); + + return { + currentAverageVelocityByTurn, + totalAverageVelocityByTurn, + currentFlowMap, + totalFlowMap, + currentDensityMap, + totalDensityMap, + currentRoadAvgVelocityMap, + totalRoadAvgVelocityMap, + }; +} + +export function mapsToSeriesMap(maps: Array<{ turn: number, data: IdToValue }>, + label: string, + roadNames: Record, +): Map{ + const ret = new Map(); + const helperMap = new Map>(); + + maps.forEach(map => { + [...Object.entries(map.data)].forEach(([entityId, value]) => { + const parsedId = parseInt(entityId); + if (helperMap.has(parsedId)){ + helperMap.get(parsedId)?.push({ turn: map.turn, value }); + } else { + helperMap.set(parsedId, [{ turn: map.turn, value }]); + } + }); + }); + + [...helperMap.entries()].forEach(([entityId, data]) => { + ret.set(entityId, { + label: label + ' for entity: ' + (roadNames[entityId] || entityId), + data: data.map((x) => ({ ...x, entityId })), + }); + }); + + return ret; +} \ No newline at end of file diff --git a/src/index.css b/src/index.css index ec2585e..dbde0ca 100644 --- a/src/index.css +++ b/src/index.css @@ -1,5 +1,7 @@ body { margin: 0; + width: 100%; + height: 100%; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; diff --git a/tsconfig.json b/tsconfig.json index e8e503d..fb6dda9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "baseUrl": ".", "outDir": "build/dist", "module": "esnext", - "target": "es5", + "target": "es6", "lib": [ "es6", "dom" diff --git a/yarn.lock b/yarn.lock index 8c25938..f37eb92 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2171,6 +2171,13 @@ dependencies: "@types/react" "*" +"@types/react-vis@^1.11.9": + version "1.11.9" + resolved "https://registry.yarnpkg.com/@types/react-vis/-/react-vis-1.11.9.tgz#7d1534cf491d4563dd18c5ad0251d9ae66549323" + integrity sha512-n6sbTQuxpIzjf1FTIPhMdwBG0VNU96Oon7z3ASRSCnWT7ehL/zYJ/np0WAYFR/+c8OwNoSzny0OWlUmfvr/YCA== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@^17.0.30": version "17.0.30" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.30.tgz#2f8e6f5ab6415c091cc5e571942ee9064b17609e" @@ -4320,6 +4327,108 @@ cyclist@^1.0.1: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= +d3-array@1, d3-array@^1.1.1, d3-array@^1.2.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f" + integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw== + +d3-collection@1, d3-collection@^1.0.3: + version "1.0.7" + resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.7.tgz#349bd2aa9977db071091c13144d5e4f16b5b310e" + integrity sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A== + +d3-color@1, d3-color@^1.0.3: + version "1.4.1" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.4.1.tgz#c52002bf8846ada4424d55d97982fef26eb3bc8a" + integrity sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q== + +d3-contour@^1.1.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-1.3.2.tgz#652aacd500d2264cb3423cee10db69f6f59bead3" + integrity sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg== + dependencies: + d3-array "^1.1.1" + +d3-format@1, d3-format@^1.2.0: + version "1.4.5" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4" + integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ== + +d3-geo@^1.6.4: + version "1.12.1" + resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.12.1.tgz#7fc2ab7414b72e59fbcbd603e80d9adc029b035f" + integrity sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg== + dependencies: + d3-array "1" + +d3-hexbin@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/d3-hexbin/-/d3-hexbin-0.2.2.tgz#9c5837dacfd471ab05337a9e91ef10bfc4f98831" + integrity sha1-nFg32s/UcasFM3qeke8Qv8T5iDE= + +d3-hierarchy@^1.1.4: + version "1.1.9" + resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz#2f6bee24caaea43f8dc37545fa01628559647a83" + integrity sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ== + +d3-interpolate@1, d3-interpolate@^1.1.4: + version "1.4.0" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.4.0.tgz#526e79e2d80daa383f9e0c1c1c7dcc0f0583e987" + integrity sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA== + dependencies: + d3-color "1" + +d3-path@1: + version "1.0.9" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf" + integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg== + +d3-sankey@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/d3-sankey/-/d3-sankey-0.7.1.tgz#d229832268fc69a7fec84803e96c2256a614c521" + integrity sha1-0imDImj8aaf+yEgD6WwiVqYUxSE= + dependencies: + d3-array "1" + d3-collection "1" + d3-shape "^1.2.0" + +d3-scale@^1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-1.0.7.tgz#fa90324b3ea8a776422bd0472afab0b252a0945d" + integrity sha512-KvU92czp2/qse5tUfGms6Kjig0AhHOwkzXG0+PqIJB3ke0WUv088AHMZI0OssO9NCkXt4RP8yju9rpH8aGB7Lw== + dependencies: + d3-array "^1.2.0" + d3-collection "1" + d3-color "1" + d3-format "1" + d3-interpolate "1" + d3-time "1" + d3-time-format "2" + +d3-shape@^1.1.0, d3-shape@^1.2.0: + version "1.3.7" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7" + integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw== + dependencies: + d3-path "1" + +d3-time-format@2: + version "2.3.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.3.0.tgz#107bdc028667788a8924ba040faf1fbccd5a7850" + integrity sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ== + dependencies: + d3-time "1" + +d3-time@1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1" + integrity sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA== + +d3-voronoi@^1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/d3-voronoi/-/d3-voronoi-1.1.4.tgz#dd3c78d7653d2bb359284ae478645d95944c8297" + integrity sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg== + d@1, d@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" @@ -4611,6 +4720,11 @@ dom-serializer@0: domelementtype "^2.0.1" entities "^2.0.0" +dom-walk@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" + integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== + domain-browser@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" @@ -5818,6 +5932,14 @@ global-prefix@^3.0.0: kind-of "^6.0.2" which "^1.3.1" +global@^4.3.1: + version "4.4.0" + resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" + integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== + dependencies: + min-document "^2.19.0" + process "^0.11.10" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -6029,6 +6151,11 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hoek@4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" + integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA== + hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -7817,6 +7944,13 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +min-document@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" + integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU= + dependencies: + dom-walk "^0.1.0" + min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -8645,6 +8779,11 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" +performance-now@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + integrity sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU= + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -9492,7 +9631,7 @@ prompts@2.4.0, prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -9634,7 +9773,7 @@ quickselect@^2.0.0: resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018" integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw== -raf@^3.4.1: +raf@^3.1.0, raf@^3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== @@ -9761,6 +9900,15 @@ react-map-gl@^6.1.16: resize-observer-polyfill "^1.5.1" viewport-mercator-project "^7.0.3" +react-motion@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316" + integrity sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ== + dependencies: + performance-now "^0.2.0" + prop-types "^15.5.8" + raf "^3.1.0" + react-redux@^7.2.5: version "7.2.5" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.5.tgz#213c1b05aa1187d9c940ddfc0b29450957f6a3b8" @@ -9817,6 +9965,30 @@ react-transition-group@^4.4.2: loose-envify "^1.4.0" prop-types "^15.6.2" +react-vis@^1.11.7: + version "1.11.7" + resolved "https://registry.yarnpkg.com/react-vis/-/react-vis-1.11.7.tgz#909902af00158895d14da1adfe1d0dc0045228ff" + integrity sha512-vJqS12l/6RHeSq8DVl4PzX0j8iPgbT8H8PtgTRsimKsBNcPjPseO4RICw1FUPrwj8MPrrna34LBtzyC4ATd5Ow== + dependencies: + d3-array "^1.2.0" + d3-collection "^1.0.3" + d3-color "^1.0.3" + d3-contour "^1.1.0" + d3-format "^1.2.0" + d3-geo "^1.6.4" + d3-hexbin "^0.2.2" + d3-hierarchy "^1.1.4" + d3-interpolate "^1.1.4" + d3-sankey "^0.7.1" + d3-scale "^1.0.5" + d3-shape "^1.1.0" + d3-voronoi "^1.1.2" + deep-equal "^1.0.1" + global "^4.3.1" + hoek "4.2.1" + prop-types "^15.5.8" + react-motion "^0.5.2" + react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"