Implement logic for visualising paths between drivers

This commit is contained in:
Maciej Pędzich 2024-04-28 22:39:15 +02:00
parent a5efe6be12
commit 0e72f6ce97
Signed by: maciejpedzich
GPG Key ID: CE4A303D84882F0D
3 changed files with 138 additions and 4 deletions

View File

@ -0,0 +1,114 @@
---
import type { Path } from 'neo4j-driver';
import type { Edge, Node } from 'vis-network';
import Graph from '@/components/Graph.astro';
import { db } from '@/db';
import type { Driver } from '@/models/driver';
interface Props {
source: string;
dest: string;
}
const { source, dest } = Astro.props;
const pluralise = (word: string, num: number) =>
num + ' ' + word + (num > 1 ? 's' : '');
let paramsNotEqual = source !== dest;
let numPaths = 0;
let degsOfSeparation = 0;
let nodes: Node[] = [];
let edges: Edge[] = [];
if (paramsNotEqual) {
const { records } = await db.executeQuery(
`MATCH (n:Driver)
WHERE
(n.forename + " " + n.surname) = $source
OR (n.forename + " " + n.surname) = $dest
WITH collect(n) AS nodes
UNWIND nodes AS s
UNWIND nodes AS d
WITH *
WHERE
(s.forename + " " + s.surname) = $source
AND (d.forename + " " + d.surname) = $dest
MATCH path = allShortestPaths((s)-[*..20]-(d))
RETURN path`,
{ source, dest }
);
numPaths = records.length;
degsOfSeparation =
records.length > 0 ? (records[0].get('path') as Path).length : 0;
const pairsOfRelatedDrivers = records.flatMap((rec) =>
(rec.get('path') as Path).segments.map(
({ start, end }) => [start.properties, end.properties] as Driver[]
)
);
nodes = [
...new Map(
pairsOfRelatedDrivers.flatMap((pair) =>
pair.map(({ driverId, forename, surname }) => [
driverId.toNumber(),
forename + ' ' + surname
])
)
).entries()
].map(([id, label]) => ({ id, label }));
edges = [
...new Set(
pairsOfRelatedDrivers.map(
([from, to]) => from.driverId.toString() + '-' + to.driverId.toString()
)
).values()
].map((pair) => {
const [from, to] = pair.split('-').map(Number);
return { from, to };
});
}
---
<p>
{
paramsNotEqual ? (
nodes.length > 0 ? (
degsOfSeparation == 1 ? (
'Colosally, these drivers were teammates!'
) : (
<span>
Found <strong>{pluralise('path', numPaths)}</strong> with
<strong>{pluralise('degree', degsOfSeparation)}</strong>
of separation.
</span>
)
) : (
'No paths were found!'
)
) : (
"There's nothing wrong with the inputs, except they're identical."
)
}
</p>
{
nodes.length > 0 && (
<div id="graph-container">
<Graph nodes={nodes} edges={edges} />
</div>
)
}
<style>
#graph-container {
height: 600px;
border: 1px solid gray;
}
</style>

7
src/models/driver.ts Normal file
View File

@ -0,0 +1,7 @@
import type { Integer } from 'neo4j-driver';
export interface Driver {
driverId: Integer;
forename: string;
surname: string;
}

View File

@ -1,21 +1,27 @@
--- ---
import BaseLayout from '../layouts/BaseLayout.astro'; import BaseLayout from '@/layouts/BaseLayout.astro';
import DriversList from '@/components/DriversList.astro'; import DriversList from '@/components/DriversList.astro';
import PathVisualiser from '@/components/PathVisualiser.astro';
Astro.response.headers.set('Cache-Control', 'public, max-age=604800'); const source = (Astro.url.searchParams.get('source')! || '').trim();
const dest = (Astro.url.searchParams.get('dest')! || '').trim();
// Cache the page for 2 weeks
Astro.response.headers.set('Cache-Control', 'public, max-age=1209600');
--- ---
<BaseLayout> <BaseLayout>
<hgroup> <hgroup>
<h1>Six Degrees of Formula One</h1> <h1>Six Degrees of F1</h1>
<p>Find out which driver is the sport's Kevin Bacon</p> <p>Find out which driver is the sport's Kevin Bacon</p>
</hgroup> </hgroup>
<p id="prompt">Get all the shortest paths from/to:</p> <p id="prompt">Get all the shortest paths from/to:</p>
<form action="/paths" method="GET"> <form action="/" method="GET">
<fieldset class="grid"> <fieldset class="grid">
<input <input
type="text" type="text"
name="source" name="source"
value={source}
placeholder="Source" placeholder="Source"
aria-label="Source" aria-label="Source"
list="drivers" list="drivers"
@ -25,6 +31,7 @@ Astro.response.headers.set('Cache-Control', 'public, max-age=604800');
<input <input
type="text" type="text"
name="dest" name="dest"
value={dest}
placeholder="Destination" placeholder="Destination"
aria-label="Destination" aria-label="Destination"
list="drivers" list="drivers"
@ -35,6 +42,11 @@ Astro.response.headers.set('Cache-Control', 'public, max-age=604800');
</fieldset> </fieldset>
<button type="submit">Submit</button> <button type="submit">Submit</button>
</form> </form>
{
source !== '' && dest !== '' && (
<PathVisualiser source={source} dest={dest} />
)
}
</BaseLayout> </BaseLayout>
<style> <style>
@ -47,6 +59,7 @@ Astro.response.headers.set('Cache-Control', 'public, max-age=604800');
} }
button[type='submit'] { button[type='submit'] {
margin-bottom: 2rem;
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
width: unset; width: unset;
} }