parent
121f5c6e92
commit
c62106f2c1
1 changed files with 295 additions and 218 deletions
@ -1,239 +1,316 @@ |
||||
import React, { useState ,useEffect } from 'react'; |
||||
import React, { useState, useEffect } from 'react'; |
||||
import { fetchRoads, fetchCities, getShortestPath } from './requests'; |
||||
import { Select, Button, Icon } from 'antd'; |
||||
import { Layout, Select, Button, Icon } from 'antd'; |
||||
import Graph from 'react-graph-vis'; |
||||
import styled from 'styled-components'; |
||||
import 'antd/dist/antd.css'; |
||||
import arrow from '../assets/arrow-right.png'; |
||||
|
||||
const { Header, Footer, Content } = Layout; |
||||
|
||||
const { Option } = Select; |
||||
|
||||
function HomeView() { |
||||
const [startingPoint, setStartingPoint] = useState(1); |
||||
const [destination, setDestination] = useState(9); |
||||
const [errorSelect, setError] = useState({ message: '', flag: false }); |
||||
const [shortestPath, setShortestPath] = useState(); |
||||
|
||||
const [cities, setCities] = useState([]); |
||||
useEffect(() => { |
||||
fetchCities() |
||||
.then(data => setCities(data)) |
||||
.catch(error => console.log(error)) |
||||
}, []); |
||||
|
||||
const [roads, setRoads] = useState([]); |
||||
useEffect(() => { |
||||
fetchRoads() |
||||
.then(data => setRoads(data)) |
||||
.catch(error => console.log(error)); |
||||
}, []); |
||||
|
||||
const [graph, setGraph] = useState({nodes: [], edges: []}); |
||||
|
||||
useEffect(() => { |
||||
let nodes = cities.map(node => ( |
||||
{ id: node.id, label: node.name, title: node.name} |
||||
)); |
||||
|
||||
let edges = roads.map(edge => ( |
||||
{ from: edge.start_city_id, to: edge.end_city_id, length: edge.distance * 2 } |
||||
)) |
||||
|
||||
setGraph({nodes, edges}) |
||||
}, [cities, roads, shortestPath]) |
||||
|
||||
function handleStart(city_id) { |
||||
// eslint-disable-next-line |
||||
let [ startingPoint ] = cities.filter(city => city.id == city_id); |
||||
setStartingPoint(startingPoint); |
||||
// Check if start and destination are the same |
||||
if (startingPoint === destination) { |
||||
setError({ message: 'The start and destination must be different.', flag: true }); |
||||
return |
||||
} |
||||
// Will reset the error message and shown path |
||||
setShortestPath(); |
||||
setError({ flag: false }); |
||||
} |
||||
|
||||
function handleDestination(city_id) { |
||||
// eslint-disable-next-line |
||||
let [ destination ] = cities.filter(city => city.id == city_id); |
||||
setDestination(destination); |
||||
// Check if start and destination are the same |
||||
if (startingPoint === destination) { |
||||
setError({ message: 'The start and destination must be different.', flag: true }); |
||||
return |
||||
} |
||||
// Will reset the error message and shown path |
||||
setShortestPath(); |
||||
setError({ flag: false }); |
||||
} |
||||
|
||||
function handleSubmit() { |
||||
if (startingPoint === destination) { |
||||
setError({ message: 'Please select a start and a destination', flag: true}); |
||||
return |
||||
} else |
||||
if (startingPoint && destination) { |
||||
getShortestPath(startingPoint.id, destination.id) |
||||
.then(data => setShortestPath(data)) |
||||
.catch(error => console.log(error)); |
||||
setError({ flag: false }); |
||||
} else { |
||||
setError({ message: 'Please select a start and a destination', flag: true}); |
||||
} |
||||
} |
||||
|
||||
// Graph visualization settings |
||||
const options = { |
||||
layout: { |
||||
hierarchical: false |
||||
}, |
||||
edges: { |
||||
color: "#000000" |
||||
}, |
||||
height: "500px" |
||||
}; |
||||
const events = { |
||||
select: function(event) { |
||||
let { nodes, edges } = event; |
||||
} |
||||
} |
||||
|
||||
// TODO Find a solution to trigger a re-render of the map |
||||
// Add dashes to shortest path edges |
||||
// let path = test.path; |
||||
// let pairs = []; |
||||
|
||||
// for (let i = 0; path.length - 1 > i; i++) { |
||||
// let obj = { from: path[i], to: path[i+1]} |
||||
// pairs.push(obj); |
||||
// } |
||||
|
||||
// let updatedEdges = graph.edges.map(edge => { |
||||
// if (pairs.includes(edge)){ |
||||
// edge['dashes'] = true; |
||||
// } |
||||
// return edge |
||||
// }) |
||||
// console.log(updatedEdges) |
||||
// setGraph({...graph, edges: updatedEdges}); |
||||
|
||||
return( |
||||
<Main> |
||||
<h1>Dijkstra</h1> |
||||
<p>Find the shortest path between different cities in Belgium with Dijkstra algorithm.</p> |
||||
<Section> |
||||
<h2>Starting Point</h2> |
||||
<div> |
||||
<StyledSelect defaultValue="Belgium" disabled> |
||||
</StyledSelect> |
||||
<Select |
||||
defaultValue="Select a city" |
||||
onChange={handleStart} |
||||
style={{ width: 139 }} |
||||
> |
||||
{cities.map(city => ( |
||||
<Option key={city.id}>{city.name}</Option> |
||||
))} |
||||
</Select> |
||||
</div> |
||||
</Section> |
||||
<Section> |
||||
<h2>Destination</h2> |
||||
<div> |
||||
<StyledSelect defaultValue="Belgium" disabled> |
||||
</StyledSelect> |
||||
<Select |
||||
defaultValue="Select a city" |
||||
onChange={handleDestination} |
||||
style={{ width: 139 }} |
||||
> |
||||
{cities.map(city => ( |
||||
<Option key={city.id}>{city.name}</Option> |
||||
))} |
||||
</Select> |
||||
</div> |
||||
</Section> |
||||
<Section> |
||||
<Button |
||||
type="primary" |
||||
onClick={handleSubmit} |
||||
> |
||||
Get shortest path between the cities |
||||
<Icon type="right" /> |
||||
</Button> |
||||
{errorSelect.flag && |
||||
<p>{errorSelect.message}</p> |
||||
} |
||||
</Section> |
||||
{ |
||||
shortestPath && |
||||
<Section> |
||||
<h2>Shortest path from {startingPoint.name} to {destination.name} ({shortestPath.distance}km)</h2> |
||||
<StyledP>{shortestPath.path.map(city => ( |
||||
<StyledSpan key={city.id} class="cityPath">{city.name}</StyledSpan> |
||||
))} |
||||
</StyledP> |
||||
|
||||
</Section> |
||||
} |
||||
{ graph.nodes.length > 0 && |
||||
|
||||
<Graph |
||||
graph={graph} |
||||
options={options} |
||||
events={events} |
||||
getNetwork={network => { |
||||
// if you want access to vis.js network api you can set the state in a parent component using this property |
||||
}} |
||||
/> |
||||
} |
||||
<Section> |
||||
<p>Git: <a href="https://git.armada.digital/rui/dijkstra-backend">backend</a> & <a href="https://git.armada.digital/rui/dijkstra-frontend">frontend</a> </p> |
||||
</Section> |
||||
</Main> |
||||
); |
||||
const [startingPoint, setStartingPoint] = useState(1); |
||||
const [destination, setDestination] = useState(9); |
||||
const [errorSelect, setError] = useState({ message: '', flag: false }); |
||||
const [shortestPath, setShortestPath] = useState(); |
||||
|
||||
const [cities, setCities] = useState([]); |
||||
useEffect(() => { |
||||
fetchCities() |
||||
.then((data) => setCities(data)) |
||||
.catch((error) => console.log(error)); |
||||
}, []); |
||||
|
||||
const [roads, setRoads] = useState([]); |
||||
useEffect(() => { |
||||
fetchRoads() |
||||
.then((data) => setRoads(data)) |
||||
.catch((error) => console.log(error)); |
||||
}, []); |
||||
|
||||
const [graph, setGraph] = useState({ nodes: [], edges: [] }); |
||||
|
||||
useEffect(() => { |
||||
let nodes = cities.map((node) => ({ |
||||
id: node.id, |
||||
label: node.name, |
||||
title: node.name, |
||||
})); |
||||
|
||||
let edges = roads.map((edge) => ({ |
||||
from: edge.start_city_id, |
||||
to: edge.end_city_id, |
||||
length: edge.distance * 2, |
||||
})); |
||||
|
||||
setGraph({ nodes, edges }); |
||||
}, [cities, roads, shortestPath]); |
||||
|
||||
function handleStart(city_id) { |
||||
// eslint-disable-next-line |
||||
let [startingPoint] = cities.filter((city) => city.id == city_id); |
||||
setStartingPoint(startingPoint); |
||||
// Check if start and destination are the same |
||||
if (startingPoint === destination) { |
||||
setError({ |
||||
message: 'The start and destination must be different.', |
||||
flag: true, |
||||
}); |
||||
return; |
||||
} |
||||
// Will reset the error message and shown path |
||||
setShortestPath(); |
||||
setError({ flag: false }); |
||||
} |
||||
|
||||
function handleDestination(city_id) { |
||||
// eslint-disable-next-line |
||||
let [destination] = cities.filter((city) => city.id == city_id); |
||||
setDestination(destination); |
||||
// Check if start and destination are the same |
||||
if (startingPoint === destination) { |
||||
setError({ |
||||
message: 'The start and destination must be different.', |
||||
flag: true, |
||||
}); |
||||
return; |
||||
} |
||||
// Will reset the error message and shown path |
||||
setShortestPath(); |
||||
setError({ flag: false }); |
||||
} |
||||
|
||||
function handleSubmit() { |
||||
if (startingPoint === destination) { |
||||
setError({ |
||||
message: 'Please select a start and a destination', |
||||
flag: true, |
||||
}); |
||||
return; |
||||
} else if (startingPoint && destination) { |
||||
getShortestPath(startingPoint.id, destination.id) |
||||
.then((data) => setShortestPath(data)) |
||||
.catch((error) => console.log(error)); |
||||
setError({ flag: false }); |
||||
} else { |
||||
setError({ |
||||
message: 'Please select a start and a destination', |
||||
flag: true, |
||||
}); |
||||
} |
||||
} |
||||
|
||||
// Graph visualization settings |
||||
const options = { |
||||
layout: { |
||||
hierarchical: false, |
||||
}, |
||||
edges: { |
||||
color: '#000000', |
||||
}, |
||||
height: '500px', |
||||
}; |
||||
const events = { |
||||
select: function (event) { |
||||
let { nodes, edges } = event; |
||||
}, |
||||
}; |
||||
|
||||
// // TODO Find a solution to trigger a re-render of the map |
||||
// // Add dashes to shortest path edges |
||||
// let path = test.path; |
||||
// let pairs = []; |
||||
|
||||
// for (let i = 0; path.length - 1 > i; i++) { |
||||
// let obj = { from: path[i], to: path[i+1]} |
||||
// pairs.push(obj); |
||||
// } |
||||
|
||||
// let updatedEdges = graph.edges.map(edge => { |
||||
// if (pairs.includes(edge)){ |
||||
// edge['dashes'] = true; |
||||
// } |
||||
// return edge |
||||
// }) |
||||
// console.log(updatedEdges) |
||||
// setGraph({...graph, edges: updatedEdges}); |
||||
|
||||
return ( |
||||
<> |
||||
<Header style={{ backgroundColor: '#ededed' }}> |
||||
<div style={{ margin: '0 auto', maxWidth: '800px' }}> |
||||
<InlineH1>Dijkstra | </InlineH1> |
||||
<InlineP> |
||||
Find the shortest path between different cities in |
||||
Belgium with Dijkstra algorithm |
||||
</InlineP> |
||||
</div> |
||||
</Header> |
||||
|
||||
<Content> |
||||
<Main |
||||
style={{ |
||||
display: 'flex', |
||||
flexFlow: 'row wrap', |
||||
justifyContent: 'space-between', |
||||
alignItems: 'center' |
||||
}} |
||||
> |
||||
<div style={{margin: '0 20px'}}> |
||||
<Section> |
||||
<h2>Starting Point</h2> |
||||
<div> |
||||
<StyledSelect |
||||
defaultValue='Belgium' |
||||
disabled |
||||
></StyledSelect> |
||||
<Select |
||||
defaultValue='Select a city' |
||||
onChange={handleStart} |
||||
style={{ width: 139 }} |
||||
> |
||||
{cities.map((city) => ( |
||||
<Option key={city.id}> |
||||
{city.name} |
||||
</Option> |
||||
))} |
||||
</Select> |
||||
</div> |
||||
</Section> |
||||
<Section> |
||||
<h2>Destination</h2> |
||||
<div> |
||||
<StyledSelect |
||||
defaultValue='Belgium' |
||||
disabled |
||||
></StyledSelect> |
||||
<Select |
||||
defaultValue='Select a city' |
||||
onChange={handleDestination} |
||||
style={{ width: 139 }} |
||||
> |
||||
{cities.map((city) => ( |
||||
<Option key={city.id}> |
||||
{city.name} |
||||
</Option> |
||||
))} |
||||
</Select> |
||||
</div> |
||||
</Section> |
||||
<Section> |
||||
<Button type='primary' onClick={handleSubmit}> |
||||
Get shortest path between the cities |
||||
<Icon type='right' /> |
||||
</Button> |
||||
{errorSelect.flag && <p>{errorSelect.message}</p>} |
||||
</Section> |
||||
</div> |
||||
|
||||
<div style={{ maxWidth: '660px', minWidth: '400px' }}> |
||||
{graph.nodes.length > 0 && ( |
||||
<Graph |
||||
graph={graph} |
||||
options={options} |
||||
events={events} |
||||
getNetwork={(network) => { |
||||
// if you want access to vis.js network api you can set the state in a parent component using this property |
||||
}} |
||||
/> |
||||
)} |
||||
</div> |
||||
|
||||
<div> |
||||
{shortestPath && ( |
||||
<Section> |
||||
<h2> |
||||
Shortest path from {startingPoint.name} to{' '} |
||||
{destination.name} ({shortestPath.distance} |
||||
km) |
||||
</h2> |
||||
<StyledP> |
||||
{shortestPath.path.map((city) => ( |
||||
<StyledSpan |
||||
key={city.id} |
||||
class='cityPath' |
||||
> |
||||
{city.name} |
||||
</StyledSpan> |
||||
))} |
||||
</StyledP> |
||||
</Section> |
||||
)} |
||||
</div> |
||||
</Main> |
||||
</Content> |
||||
|
||||
<Footer> |
||||
<div style={{ margin: '0 auto', maxWidth: '800px' }}> |
||||
<p> |
||||
Git |{' '} |
||||
<a href='https://git.armada.digital/rui/dijkstra-backend'> |
||||
backend |
||||
</a>{' '} |
||||
&{' '} |
||||
<a href='https://git.armada.digital/rui/dijkstra-frontend'> |
||||
frontend |
||||
</a>{' '} |
||||
</p> |
||||
</div> |
||||
</Footer> |
||||
</> |
||||
); |
||||
} |
||||
|
||||
export default HomeView |
||||
export default HomeView; |
||||
|
||||
// STYLED COMPONENTS |
||||
|
||||
const InlineP = styled.p` |
||||
display: inline; |
||||
`; |
||||
|
||||
const InlineH1 = styled.h1` |
||||
display: inline; |
||||
`; |
||||
|
||||
const Main = styled.main` |
||||
padding-top: 20px; |
||||
max-width: 800px; |
||||
margin: 0 auto; |
||||
` |
||||
padding-top: 20px; |
||||
max-width: 1200px; |
||||
margin: 0 auto; |
||||
height: calc(100vh - 147px); |
||||
`; |
||||
|
||||
const Section = styled.section` |
||||
margin: 20px 0; |
||||
` |
||||
margin: 20px 0; |
||||
`; |
||||
|
||||
const StyledSelect = styled(Select)` |
||||
margin-right:10px; |
||||
` |
||||
margin-right: 10px; |
||||
`; |
||||
|
||||
const StyledSpan = styled.span` |
||||
::after { |
||||
content: ""; |
||||
background-image: url(${arrow}); |
||||
background-size: 13px 17px; |
||||
display: inline-block; |
||||
width: 13px; |
||||
height: 17px; |
||||
margin-right: 10px; |
||||
margin-left: 10px; |
||||
padding-top: 10px; |
||||
bottom: -10px; |
||||
} |
||||
font-size:1.5rem; |
||||
color: #40a9ff; |
||||
` |
||||
::after { |
||||
content: ''; |
||||
background-image: url(${arrow}); |
||||
background-size: 13px 17px; |
||||
display: inline-block; |
||||
width: 13px; |
||||
height: 17px; |
||||
margin-right: 10px; |
||||
margin-left: 10px; |
||||
padding-top: 10px; |
||||
bottom: -10px; |
||||
} |
||||
font-size: 1.5rem; |
||||
color: #40a9ff; |
||||
`; |
||||
|
||||
const StyledP = styled.p` |
||||
span:last-child { |
||||
::after { |
||||
background-image: none; |
||||
} |
||||
} |
||||
` |
||||
span:last-child { |
||||
::after { |
||||
background-image: none; |
||||
} |
||||
} |
||||
`; |
||||
|
Loading…
Reference in new issue