Compare commits
2 commits
c8187f3f5e
...
5c35ce9a04
Author | SHA1 | Date | |
---|---|---|---|
|
5c35ce9a04 | ||
|
bcc96dea31 |
8 changed files with 423 additions and 99 deletions
File diff suppressed because one or more lines are too long
20
index.html
20
index.html
|
@ -39,6 +39,26 @@
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* #libraries g circle{
|
||||||
|
animation-timing-function: easeInOutCubic;
|
||||||
|
} */
|
||||||
|
#libraries g.send circle:nth-child(2){
|
||||||
|
animation: send .5s 1 cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
|
/* fill: red; */
|
||||||
|
}
|
||||||
|
#libraries g.recv circle:nth-child(2){
|
||||||
|
fill: rgba(199, 162, 217, 0.0)
|
||||||
|
}
|
||||||
|
#libraries g.recv circle:nth-child(2){
|
||||||
|
/* fill: blue; */
|
||||||
|
animation: recv 1s 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes send {15% {opacity: 1} 40%{r:25px; opacity:0;} 41% {r:10px; opacity:0.05} 100% {r: 20px; opacity:1}}
|
||||||
|
@keyframes recv {50%{fill: rgba(199, 162, 217, 0.3);}}
|
||||||
|
/* @keyframes recv {50%{transform:rotateY(180deg);}} */
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: black;
|
background: black;
|
||||||
}
|
}
|
||||||
|
|
7
poetry.lock
generated
7
poetry.lock
generated
|
@ -1,8 +1,7 @@
|
||||||
|
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
||||||
package = []
|
package = []
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.9"
|
python-versions = "^3.9"
|
||||||
content-hash = "5b80bcf39bc22fc51a0c05260792ccc8bde494467670046c58c64725c897eb75"
|
content-hash = "c595a0588c25d58f3e3834ad7169126836d262b925fe6ca9b5d540dcf301d254"
|
||||||
|
|
||||||
[metadata.files]
|
|
||||||
|
|
207
src/LibrariesSvg.svelte
Normal file
207
src/LibrariesSvg.svelte
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
<g
|
||||||
|
id="APM"
|
||||||
|
transform="translate(298.72484444444126, 433.0485336906215)"
|
||||||
|
class="s-vynWWFEzB3Z5"
|
||||||
|
>
|
||||||
|
<circle r="5" class="s-vynWWFEzB3Z5" id="circle484" />
|
||||||
|
<circle r="20" class="s-vynWWFEzB3Z5" id="circle486" />
|
||||||
|
<text class="nodeTitle s-vynWWFEzB3Z5" y="13" x="35" id="text488"
|
||||||
|
>Archaeological Collection</text
|
||||||
|
>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="OTM"
|
||||||
|
transform="translate(1745.5955555555556, 3603.1193459656633)"
|
||||||
|
class="s-vynWWFEzB3Z5"
|
||||||
|
>
|
||||||
|
<circle r="5" class="s-vynWWFEzB3Z5" id="circle491" />
|
||||||
|
<circle r="20" class="s-vynWWFEzB3Z5" id="circle493" />
|
||||||
|
<text
|
||||||
|
class="nodeTitle s-vynWWFEzB3Z5"
|
||||||
|
y="4.1199999"
|
||||||
|
x="-35" text-anchor="end"
|
||||||
|
id="text495">Allard Pierson Depot</text
|
||||||
|
>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="BC"
|
||||||
|
transform="translate(298.72484444444126, 393.0485336906215)"
|
||||||
|
class="s-vynWWFEzB3Z5"
|
||||||
|
>
|
||||||
|
<circle r="5" class="s-vynWWFEzB3Z5" id="circle498" />
|
||||||
|
<circle r="20" class="s-vynWWFEzB3Z5" id="circle500" />
|
||||||
|
<text class="nodeTitle s-vynWWFEzB3Z5" y="13" x="35" id="text502"
|
||||||
|
>Handbibliotheek</text
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
class="nodeTitle s-vynWWFEzB3Z5"
|
||||||
|
y="38"
|
||||||
|
x="311.19113"
|
||||||
|
id="text1097">Allard Pierson</text
|
||||||
|
>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="ARTIS"
|
||||||
|
transform="translate(807.0959555555783, 551.9470916540013)"
|
||||||
|
class="s-vynWWFEzB3Z5"
|
||||||
|
>
|
||||||
|
<circle r="5" class="s-vynWWFEzB3Z5" id="circle505" />
|
||||||
|
<circle r="20" class="s-vynWWFEzB3Z5" id="circle507" />
|
||||||
|
<text class="nodeTitle s-vynWWFEzB3Z5" y="13" x="35" id="text509"
|
||||||
|
>Artis Bibliotheek</text
|
||||||
|
>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="BHBPU"
|
||||||
|
transform="translate(408.1404000000112, 307.58142297433403)"
|
||||||
|
class="s-vynWWFEzB3Z5"
|
||||||
|
>
|
||||||
|
<circle r="5" class="s-vynWWFEzB3Z5" id="circle512" />
|
||||||
|
<circle r="20" class="s-vynWWFEzB3Z5" id="circle514" />
|
||||||
|
<text class="nodeTitle s-vynWWFEzB3Z5" y="13" x="35" id="text516"
|
||||||
|
>Bushuis - pickup location</text
|
||||||
|
>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="CEDLA"
|
||||||
|
transform="translate(710.2181777777914, 820.5062916831351)"
|
||||||
|
class="s-vynWWFEzB3Z5"
|
||||||
|
>
|
||||||
|
<circle r="5" class="s-vynWWFEzB3Z5" id="circle519" />
|
||||||
|
<circle r="20" class="s-vynWWFEzB3Z5" id="circle521" />
|
||||||
|
<text class="nodeTitle s-vynWWFEzB3Z5" y="13" x="35" id="text523">CEDLA</text
|
||||||
|
>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="UBGW"
|
||||||
|
transform="translate(160.2826222222393, 520.1182633671162)"
|
||||||
|
class="s-vynWWFEzB3Z5"
|
||||||
|
>
|
||||||
|
<circle r="5" class="s-vynWWFEzB3Z5" id="circle526" />
|
||||||
|
<circle r="20" class="s-vynWWFEzB3Z5" id="circle528" />
|
||||||
|
<text class="nodeTitle s-vynWWFEzB3Z5" y="13" x="35" id="text530"
|
||||||
|
>GW-collecties UB</text
|
||||||
|
>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="IVIR"
|
||||||
|
transform="translate(730.9048444444488, 682.295035560214)"
|
||||||
|
class="s-vynWWFEzB3Z5"
|
||||||
|
>
|
||||||
|
<circle r="5" class="s-vynWWFEzB3Z5" id="circle533" />
|
||||||
|
<circle r="20" class="s-vynWWFEzB3Z5" id="circle535" />
|
||||||
|
<text class="nodeTitle s-vynWWFEzB3Z5" y="13" x="35" id="text537">IViR</text>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="IWO"
|
||||||
|
transform="translate(1775.5955555555554, 3553.1193459656633)"
|
||||||
|
class="s-vynWWFEzB3Z5"
|
||||||
|
>
|
||||||
|
<circle r="5" class="s-vynWWFEzB3Z5" id="circle540" />
|
||||||
|
<circle r="20" class="s-vynWWFEzB3Z5" id="circle542" />
|
||||||
|
<text class="nodeTitle s-vynWWFEzB3Z5" y="13" x="35" id="text544"
|
||||||
|
>IWO-depot</text
|
||||||
|
>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="JB"
|
||||||
|
transform="translate(740.9048444444488, 750.295035560214)"
|
||||||
|
class="s-vynWWFEzB3Z5"
|
||||||
|
>
|
||||||
|
<circle r="5" class="s-vynWWFEzB3Z5" id="circle547" />
|
||||||
|
<circle r="20" class="s-vynWWFEzB3Z5" id="circle549" />
|
||||||
|
<text class="nodeTitle s-vynWWFEzB3Z5" y="13" x="35" id="text551"
|
||||||
|
>Juridische Bibliotheek</text
|
||||||
|
>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="REC"
|
||||||
|
transform="translate(699.5315111111087, 623.2141384588231)"
|
||||||
|
class="s-vynWWFEzB3Z5"
|
||||||
|
>
|
||||||
|
<circle r="5" class="s-vynWWFEzB3Z5" id="circle554" />
|
||||||
|
<circle r="20" class="s-vynWWFEzB3Z5" id="circle556" />
|
||||||
|
<text class="nodeTitle s-vynWWFEzB3Z5" y="13" x="35" id="text558"
|
||||||
|
>Library Learning Centre Roeterseiland</text
|
||||||
|
>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="AMC"
|
||||||
|
transform="translate(1767.38706666667, 3460.8911949512853)"
|
||||||
|
class="s-vynWWFEzB3Z5"
|
||||||
|
>
|
||||||
|
<circle r="5" class="s-vynWWFEzB3Z5" id="circle561" />
|
||||||
|
<circle r="20" class="s-vynWWFEzB3Z5" id="circle563" />
|
||||||
|
<text class="nodeTitle s-vynWWFEzB3Z5" y="3.5" text-anchor="end" x="-35" id="text565"
|
||||||
|
>Medische Bibliotheek</text
|
||||||
|
>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="PCHH"
|
||||||
|
transform="translate(222.92706666665254, 150.79690464423038)"
|
||||||
|
class="s-vynWWFEzB3Z5"
|
||||||
|
>
|
||||||
|
<circle r="5" class="s-vynWWFEzB3Z5" id="circle568" />
|
||||||
|
<circle r="20" class="s-vynWWFEzB3Z5" id="circle570" />
|
||||||
|
<text class="nodeTitle s-vynWWFEzB3Z5" y="13" x="35" id="text572"
|
||||||
|
>P.C.Hoofthuis</text
|
||||||
|
>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="BETA"
|
||||||
|
transform="translate(1687.8959555555593, 1008.3027457312287)"
|
||||||
|
class="s-vynWWFEzB3Z5"
|
||||||
|
>
|
||||||
|
<circle r="5" class="s-vynWWFEzB3Z5" id="circle575" />
|
||||||
|
<circle r="20" class="s-vynWWFEzB3Z5" id="circle577" />
|
||||||
|
<text
|
||||||
|
class="nodeTitle s-vynWWFEzB3Z5"
|
||||||
|
y="4.1199999"
|
||||||
|
x="-35" text-anchor="end"
|
||||||
|
id="text579">Sciencepark Bibliotheek</text
|
||||||
|
>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="UBB"
|
||||||
|
transform="translate(220.2826222222393, 630.1182633671162)"
|
||||||
|
class="s-vynWWFEzB3Z5"
|
||||||
|
>
|
||||||
|
<circle r="5" class="s-vynWWFEzB3Z5" id="circle582" />
|
||||||
|
<circle r="20" class="s-vynWWFEzB3Z5" id="circle584" />
|
||||||
|
<text class="nodeTitle s-vynWWFEzB3Z5" y="13" x="35" id="text586"
|
||||||
|
>UB Singel</text
|
||||||
|
>
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
style=""
|
||||||
|
d="M 492.42998,401.08039 H 593.04204 V 442.57506 L 579.95449,442.42775"
|
||||||
|
id="path1093"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
style=""
|
||||||
|
d="M 593.04204,421.82772 604.96062,421.82773"
|
||||||
|
id="path1095"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
circle:first-child {
|
||||||
|
fill: white;
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
circle {
|
||||||
|
fill: none;
|
||||||
|
stroke: white;
|
||||||
|
stroke-width: 10;
|
||||||
|
}
|
||||||
|
text {
|
||||||
|
fill: white;
|
||||||
|
font-size: 15pt;
|
||||||
|
}
|
||||||
|
path{
|
||||||
|
stroke: white;
|
||||||
|
fill: none;
|
||||||
|
stroke-width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
110
src/Viz.svelte
110
src/Viz.svelte
|
@ -1,7 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import parsed_requests from "/data/parsed_requests.json";
|
import parsed_requests from "/data/parsed_requests.json";
|
||||||
import { draw, slide } from "svelte/transition";
|
import { draw, slide, fade } from "svelte/transition";
|
||||||
import { fps } from "@sveu/browser";
|
import { fps } from "@sveu/browser";
|
||||||
|
import LibrariesSvg from "./LibrariesSvg.svelte";
|
||||||
|
|
||||||
import { All, Scene, Timeline } from "./scenes/Scenes";
|
import { All, Scene, Timeline } from "./scenes/Scenes";
|
||||||
import {
|
import {
|
||||||
|
@ -58,20 +59,20 @@
|
||||||
|
|
||||||
// filter nodes with only having both Latitude and Longitude.
|
// filter nodes with only having both Latitude and Longitude.
|
||||||
// then map these coordinates to the canvas space
|
// then map these coordinates to the canvas space
|
||||||
const locations = new Map(_nodes
|
const locations = new Map(
|
||||||
|
_nodes
|
||||||
.filter((n) => n.lat && n.lon)
|
.filter((n) => n.lat && n.lon)
|
||||||
.map((node) => {
|
.map((node) => {
|
||||||
node["x"] = node_positions[node.code][0];
|
node["x"] = node_positions[node.code][0];
|
||||||
node["y"] = node_positions[node.code][1];
|
node["y"] = node_positions[node.code][1];
|
||||||
return node;
|
return node;
|
||||||
})
|
})
|
||||||
.map((d) => [d["name"], d])
|
.map((d) => [d["name"], d]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
_requests
|
_requests
|
||||||
// remove entries that stay at the same place
|
// remove entries that stay at the same place
|
||||||
.filter((n) => n['Owning Library Name'] != n['Pickup Location'])
|
.filter((n) => n["Owning Library Name"] != n["Pickup Location"])
|
||||||
.filter(
|
.filter(
|
||||||
(l) =>
|
(l) =>
|
||||||
locations.has(l["Owning Library Name"]) &&
|
locations.has(l["Owning Library Name"]) &&
|
||||||
|
@ -93,6 +94,7 @@
|
||||||
target: locations.get(r["Pickup Location"]),
|
target: locations.get(r["Pickup Location"]),
|
||||||
nr: idx,
|
nr: idx,
|
||||||
item: items.get(identifier),
|
item: items.get(identifier),
|
||||||
|
date: new Date(r["Request Completion Date"]), // TODO: validate unfortunate M/D/Y format
|
||||||
_original: r,
|
_original: r,
|
||||||
d: "", // bit of a hacky workaround, refactor in object
|
d: "", // bit of a hacky workaround, refactor in object
|
||||||
};
|
};
|
||||||
|
@ -151,8 +153,8 @@
|
||||||
|
|
||||||
const occurences = new Map<Item, Movement[]>();
|
const occurences = new Map<Item, Movement[]>();
|
||||||
movements.forEach((movement, idx) => {
|
movements.forEach((movement, idx) => {
|
||||||
let movements = occurences.get(movement.item) ?? []
|
let movements = occurences.get(movement.item) ?? [];
|
||||||
movements.push(movement)
|
movements.push(movement);
|
||||||
occurences.set(movement.item, movements);
|
occurences.set(movement.item, movements);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -160,25 +162,27 @@
|
||||||
locations: locations,
|
locations: locations,
|
||||||
items: items,
|
items: items,
|
||||||
movements: movements,
|
movements: movements,
|
||||||
occurences: occurences
|
occurences: occurences,
|
||||||
}
|
};
|
||||||
|
|
||||||
let drawn_motions = writable(<Motion[]>[]); //.filter((m, i) => i < 100);
|
let drawn_motions = writable(<Motion[]>[]); //.filter((m, i) => i < 100);
|
||||||
$: opacity = Math.max(0.055, Math.min(1, 200 / $drawn_motions.length));
|
$: opacity = Math.max(0.055, Math.min(.3, 70 / $drawn_motions.length));
|
||||||
let overlay_motions = writable(<Motion[]>[]); //.filter((m, i) => i < 100);
|
let overlay_motions = writable(<Motion[]>[]); //.filter((m, i) => i < 100);
|
||||||
let events = writable(<Log[]>[]); //.filter((m, i) => i < 100);
|
let events = writable(<Log[]>[]); //.filter((m, i) => i < 100);
|
||||||
|
let current_item = writable(<Item | null>null); //.filter((m, i) => i < 100);
|
||||||
|
|
||||||
const viz_data: VizData = {
|
const viz_data: VizData = {
|
||||||
drawn_motions: drawn_motions,
|
drawn_motions: drawn_motions,
|
||||||
overlay_motions: overlay_motions,
|
overlay_motions: overlay_motions,
|
||||||
events: events,
|
events: events,
|
||||||
}
|
current_item: current_item,
|
||||||
|
};
|
||||||
|
|
||||||
import { onMount, onDestroy } from "svelte";
|
import { onMount, onDestroy } from "svelte";
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
let scenes = [All, Timeline];
|
let scenes = [All, Timeline];
|
||||||
let currentSceneI = 0;
|
let currentSceneI = parseInt(window.location.hash[1] ?? 0);
|
||||||
let currentScene: Scene;
|
let currentScene: Scene;
|
||||||
|
|
||||||
function nextScene() {
|
function nextScene() {
|
||||||
|
@ -187,11 +191,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Next scene", currentSceneI);
|
console.log("Next scene", currentSceneI);
|
||||||
currentScene = new scenes[currentSceneI](
|
currentScene = new scenes[currentSceneI](data, viz_data, nextScene);
|
||||||
data,
|
|
||||||
viz_data,
|
|
||||||
nextScene,
|
|
||||||
);
|
|
||||||
currentSceneI = (currentSceneI + 1) % scenes.length;
|
currentSceneI = (currentSceneI + 1) % scenes.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,8 +226,10 @@
|
||||||
|
|
||||||
<h1>library of <span id="motiontitle">motions</span></h1>
|
<h1>library of <span id="motiontitle">motions</span></h1>
|
||||||
<div id="about">
|
<div id="about">
|
||||||
<span>Work by <em>Ruben van de Ven</em> for the
|
<span
|
||||||
<em>University of Amsterdam Library</em>.</span>
|
>Work by <em>Ruben van de Ven</em> for the
|
||||||
|
<em>University of Amsterdam Library</em>.</span
|
||||||
|
>
|
||||||
<span>Fonts by <em>Open Source Publishing</em>.</span>
|
<span>Fonts by <em>Open Source Publishing</em>.</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -235,7 +237,8 @@
|
||||||
<g id="movements">
|
<g id="movements">
|
||||||
{#each $drawn_motions as m}
|
{#each $drawn_motions as m}
|
||||||
<path
|
<path
|
||||||
in:draw|global={{ duration: m.duration }}
|
in:draw={{ duration: m.duration }}
|
||||||
|
out:fade={{ duration: 1000 }}
|
||||||
d={m.movement.d}
|
d={m.movement.d}
|
||||||
style="opacity: {opacity}"
|
style="opacity: {opacity}"
|
||||||
></path>
|
></path>
|
||||||
|
@ -243,34 +246,52 @@
|
||||||
</g>
|
</g>
|
||||||
<g id="overlay_motions">
|
<g id="overlay_motions">
|
||||||
{#each $overlay_motions as m}
|
{#each $overlay_motions as m}
|
||||||
<path in:draw|global={{ duration: m.duration }} d={m.movement.d}
|
<path out:fade={{ duration: 1000 }}
|
||||||
|
in:draw={{ duration: m.duration }}
|
||||||
|
d={m.movement.d}
|
||||||
></path>
|
></path>
|
||||||
{/each}
|
{/each}
|
||||||
</g>
|
</g>
|
||||||
<g id="libraries">
|
<g id="libraries">
|
||||||
{#each locations.values() as node}
|
<LibrariesSvg />
|
||||||
|
<!-- {#each locations.values() as node}
|
||||||
<g id={node.code} transform="translate({node.x}, {node.y})">
|
<g id={node.code} transform="translate({node.x}, {node.y})">
|
||||||
<circle r="5"></circle>
|
<circle r="5"></circle>
|
||||||
<circle r="20"></circle>
|
<circle r="20"></circle>
|
||||||
<text class="nodeTitle" y="13" x="35">{node.name}</text>
|
<text class="nodeTitle" y="13" x="35">{node.name}</text>
|
||||||
</g>
|
</g>
|
||||||
{/each}
|
{/each} -->
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
{#if currentScene?.rendered_elements.indexOf("timeline") >= 0}
|
<!-- {#if currentScene?.rendered_elements.indexOf("timeline") >= 0} -->
|
||||||
<div id="timeline">
|
<div id="timeline">
|
||||||
{#each $events as m}
|
{#if $current_item}
|
||||||
|
<div id="current">
|
||||||
|
<h2>{$current_item.title}</h2>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div id="events">
|
||||||
|
{#each $events as e}
|
||||||
<div class="entry" in:slide={{ duration: 200 }}>
|
<div class="entry" in:slide={{ duration: 200 }}>
|
||||||
<!-- {m['Title (Complete)']} -->
|
<!-- {m['Title (Complete)']} -->
|
||||||
<span class="date"
|
<span class="date"
|
||||||
>{m.movement._original["Request Completion Date"]}</span
|
>{e.date.toLocaleDateString("en-UK", {
|
||||||
|
weekday: "long",
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
})}</span
|
||||||
>
|
>
|
||||||
<span class="location">{m.movement.target.name}</span>
|
<div class="text">
|
||||||
|
<span class="title">{e.title}</span>
|
||||||
|
<span class="description">{e.description}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</div>
|
||||||
|
<!-- {/if} -->
|
||||||
|
|
||||||
{#if dev}
|
{#if dev}
|
||||||
<div id="fps">{$_fps}</div>
|
<div id="fps">{$_fps}</div>
|
||||||
|
@ -312,35 +333,30 @@
|
||||||
width: 450px;
|
width: 450px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
||||||
span{
|
span {
|
||||||
display:block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#libraries circle:first-child {
|
|
||||||
fill: white;
|
|
||||||
stroke: none;
|
|
||||||
}
|
|
||||||
#libraries circle {
|
|
||||||
fill: none;
|
|
||||||
stroke: white;
|
|
||||||
stroke-width: 10;
|
|
||||||
}
|
|
||||||
path {
|
path {
|
||||||
stroke: #bc89ff;
|
stroke: #bc89ff;
|
||||||
stroke-width: 2px;
|
stroke-width: 3px;
|
||||||
fill: none;
|
fill: none;
|
||||||
|
animation: highlight-on-insert 1s 1;
|
||||||
}
|
}
|
||||||
|
@keyframes highlight-on-insert{10% {opacity: .7; stroke:#ff89ff}}
|
||||||
#overlay_motions path {
|
#overlay_motions path {
|
||||||
stroke: red;
|
stroke: rgba(255, 255, 0, 0.641);
|
||||||
stroke-width: 4;
|
stroke-width: 5;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
text {
|
|
||||||
fill: white;
|
|
||||||
font-size: 30pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* path:not(.selected) {
|
/* path:not(.selected) {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
} */
|
} */
|
||||||
|
|
||||||
|
#events {
|
||||||
|
max-height: 50px;
|
||||||
|
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
font-weight: 200;
|
font-weight: 200;
|
||||||
color: black;
|
color: black;
|
||||||
/* background: linear-gradient(180deg, #3a294c 0%, #62487f 100%); */
|
/* background: linear-gradient(180deg, #3a294c 0%, #62487f 100%); */
|
||||||
background: rgb(45, 45, 45);
|
/* background: rgb(45, 45, 45); */
|
||||||
/* background: linear-gradient(to bottom right, #6fa8d9f7, #cdeed3); */
|
/* background: linear-gradient(to bottom right, #6fa8d9f7, #cdeed3); */
|
||||||
border-radius: 40px;
|
border-radius: 40px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,8 @@ export type Movement = {
|
||||||
item: Item,
|
item: Item,
|
||||||
_original: Object
|
_original: Object
|
||||||
// also contains additional request data (see requests.csv)
|
// also contains additional request data (see requests.csv)
|
||||||
d: String
|
d: String, // the svg path
|
||||||
|
date: Date,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Occurences = Map<Item, Movement[]>
|
export type Occurences = Map<Item, Movement[]>
|
||||||
|
@ -49,7 +50,8 @@ export type Log = {
|
||||||
export type VizData = {
|
export type VizData = {
|
||||||
drawn_motions: Writable<Motion[]>,
|
drawn_motions: Writable<Motion[]>,
|
||||||
overlay_motions: Writable<Motion[]>,
|
overlay_motions: Writable<Motion[]>,
|
||||||
events: Writable<Log[]>
|
events: Writable<Log[]>,
|
||||||
|
current_item: Writable<Item | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get_path_d(movement: Movement) {
|
export function get_path_d(movement: Movement) {
|
||||||
|
@ -87,8 +89,8 @@ export function get_path_d(movement: Movement) {
|
||||||
dy = m.target.y - m.source.y;
|
dy = m.target.y - m.source.y;
|
||||||
angle = Math.atan2(dx, dy);
|
angle = Math.atan2(dx, dy);
|
||||||
|
|
||||||
var srcSize = 5; //_mapGraph.getSizeForNode(m.source);
|
var srcSize = 31; //_mapGraph.getSizeForNode(m.source);
|
||||||
var tgtSize = 5; //_mapGraph.getSizeForNode(m.target);
|
var tgtSize = 31; //_mapGraph.getSizeForNode(m.target);
|
||||||
|
|
||||||
// Compute the line endpoint such that the arrow
|
// Compute the line endpoint such that the arrow
|
||||||
// it not in the center, but rather slightly out of it
|
// it not in the center, but rather slightly out of it
|
||||||
|
|
|
@ -1,31 +1,83 @@
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import type { Data, Item, Motion, Movement, VizData } from "../lib/types";
|
import type { Data, Item, Log, Motion, Movement, VizData } from "../lib/types";
|
||||||
|
|
||||||
export class Scene {
|
export class Scene {
|
||||||
rendered_elements: String[] = []
|
rendered_elements: String[] = []
|
||||||
|
data: Data;
|
||||||
|
viz_data: VizData;
|
||||||
|
nextScene: CallableFunction;
|
||||||
|
|
||||||
|
constructor(data: Data, viz_data: VizData, nextScene: CallableFunction) {
|
||||||
|
this.data = data;
|
||||||
|
this.viz_data = viz_data;
|
||||||
|
this.nextScene = nextScene;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawMovements(movements: Movement[], duration: number, in_overlay = false){
|
||||||
|
let motions: Motion[] = movements.map((m) => ({ duration: duration, movement: m }));
|
||||||
|
// TODO abstract in function drawMovements()
|
||||||
|
// TODO in there, setTimeout for arrival effect on node
|
||||||
|
|
||||||
|
const set = in_overlay ? this.viz_data.overlay_motions : this.viz_data.drawn_motions
|
||||||
|
setTimeout(() => {
|
||||||
|
set.update(items => ([...items, ...motions]))
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
motions.forEach((motion) => {
|
||||||
|
const movement = motion.movement
|
||||||
|
const srcEl = document.getElementById(movement.source.code)
|
||||||
|
const tgtEl = document.getElementById(movement.target.code)
|
||||||
|
// console.log(srcEl, tgtEl, movement.source.code, movement.target.code)
|
||||||
|
srcEl?.classList.remove('send', 'recv');
|
||||||
|
setTimeout(() => {
|
||||||
|
srcEl?.classList.add('send');
|
||||||
|
}, 200) // very brief to just trigger css anim
|
||||||
|
setTimeout(() => {
|
||||||
|
tgtEl?.classList.remove('send', 'recv');
|
||||||
|
}, duration - 280) // very brief to just trigger css anim
|
||||||
|
setTimeout(() => {
|
||||||
|
tgtEl?.classList.add('recv');
|
||||||
|
}, duration - 250) // very brief to just trigger css anim
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
console.log("TODO: stop timeout")
|
console.log("TODO: stop timeout")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class All extends Scene {
|
export class All extends Scene {
|
||||||
allMovements: Movement[]
|
|
||||||
motions: Writable<Motion[]>
|
|
||||||
interval: number
|
interval: number
|
||||||
step: number = 0
|
step: number = 0
|
||||||
nextScene: CallableFunction
|
|
||||||
locationCounts = new Map<Location, { in: number, out: number }>()
|
locationCounts = new Map<Location, { in: number, out: number }>()
|
||||||
|
selected_movements: Movement[];
|
||||||
|
|
||||||
|
options = {
|
||||||
|
interval_days: .1,
|
||||||
|
tick_interval: 1000, // ms
|
||||||
|
items_per_tick: 1,
|
||||||
|
}
|
||||||
|
|
||||||
constructor(data: Data, viz_data: VizData, nextScene: CallableFunction) {
|
constructor(data: Data, viz_data: VizData, nextScene: CallableFunction) {
|
||||||
super()
|
super(data, viz_data, nextScene);
|
||||||
|
|
||||||
this.nextScene = nextScene
|
|
||||||
|
|
||||||
// start setInterval to trigger additions per 100 or so to drawn_movements (rendered on map)
|
// start setInterval to trigger additions per 100 or so to drawn_movements (rendered on map)
|
||||||
// when done, trigger parent.done()
|
// when done, trigger parent.done()
|
||||||
this.allMovements = data.movements;
|
// this.allMovements = data.movements;
|
||||||
|
|
||||||
|
// sorted by date
|
||||||
|
data.movements.sort((a,b) => a.date - b.date);
|
||||||
|
const last_move_date = data.movements[data.movements.length-1].date
|
||||||
|
|
||||||
|
const interval_ms = this.options.interval_days * 24 * 3600 * 1000
|
||||||
|
|
||||||
|
const range = [new Date(last_move_date - interval_ms), last_move_date]
|
||||||
|
console.log(range)
|
||||||
|
this.selected_movements = data.movements.filter((movement) => movement.date > range[0] && movement.date <= range[1]);
|
||||||
|
console.log(`Draw ${this.selected_movements.length} movements, will take ${this.selected_movements.length / this.options.items_per_tick * this.options.tick_interval / 1000} seconds`)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.motions = viz_data.drawn_motions
|
|
||||||
// TODO: group by hour and have it last nr-of-hours * interval
|
// TODO: group by hour and have it last nr-of-hours * interval
|
||||||
// TODO: then, add a timeline
|
// TODO: then, add a timeline
|
||||||
// TODO: then, add an overlay with relevant events, e.g.
|
// TODO: then, add an overlay with relevant events, e.g.
|
||||||
|
@ -34,26 +86,24 @@ export class All extends Scene {
|
||||||
// TODO: .. and any other filter
|
// TODO: .. and any other filter
|
||||||
// TODO: then finish with a summary per location
|
// TODO: then finish with a summary per location
|
||||||
// TODO: .. (or add these in small prints as counters under the location names)
|
// TODO: .. (or add these in small prints as counters under the location names)
|
||||||
this.interval = setInterval(this.tick.bind(this), 500);
|
this.interval = setInterval(this.tick.bind(this), this.options.tick_interval);
|
||||||
|
|
||||||
// clear the motions when kicking off:
|
// clear the motions when kicking off:
|
||||||
this.motions.update(items => []);
|
this.viz_data.drawn_motions.update(items => []);
|
||||||
}
|
}
|
||||||
|
|
||||||
tick() {
|
tick() {
|
||||||
const n = 10
|
const n = this.options.items_per_tick
|
||||||
if (this.step >= this.allMovements.length) {
|
if (this.step >= this.selected_movements.length) {
|
||||||
console.log('this', 'done')
|
console.log('this', 'done')
|
||||||
// todo: ease out all entries
|
// todo: ease out all entries
|
||||||
this.nextScene()
|
this.nextScene()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let movements: Movement[] = this.allMovements.slice(this.step, this.step + n);
|
let movements: Movement[] = this.selected_movements.slice(this.step, this.step + n);
|
||||||
|
|
||||||
// duration 5000 + Math.random() * 10000
|
// duration 5000 + Math.random() * 10000
|
||||||
let motions: Motion[] = movements.map((m) => ({ duration: 5000, movement: m }));
|
this.drawMovements(movements, 3500)
|
||||||
|
|
||||||
this.motions.update(items => ([...items, ...motions]))
|
|
||||||
this.step += n;
|
this.step += n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,40 +118,56 @@ export class All extends Scene {
|
||||||
export class Timeline extends Scene {
|
export class Timeline extends Scene {
|
||||||
movements: Movement[]
|
movements: Movement[]
|
||||||
item: Item
|
item: Item
|
||||||
motions: Writable<Motion[]>
|
|
||||||
interval: number
|
interval: number
|
||||||
step: number = 0
|
step: number = 0
|
||||||
nextScene: CallableFunction
|
|
||||||
locationCounts = new Map<Location, { in: number, out: number }>()
|
locationCounts = new Map<Location, { in: number, out: number }>()
|
||||||
|
|
||||||
constructor(data: Data, viz_data: VizData, nextScene: CallableFunction) {
|
constructor(data: Data, viz_data: VizData, nextScene: CallableFunction) {
|
||||||
super()
|
super(data, viz_data, nextScene);
|
||||||
|
|
||||||
this.nextScene = nextScene
|
// clear canvas
|
||||||
|
// this.viz_data.drawn_motions.update(items => [])
|
||||||
|
|
||||||
// start setInterval to trigger additions per 100 or so to drawn_movements (rendered on map)
|
// start setInterval to trigger additions per 100 or so to drawn_movements (rendered on map)
|
||||||
// when done, trigger parent.done()
|
// when done, trigger parent.done()
|
||||||
console.log('s',data.occurences)
|
const min_occurences = 3;
|
||||||
const [ item, movements ]= this.pickMovements(data.occurences);
|
const pick = this.pickMovements(min_occurences);
|
||||||
this.item = item
|
if( pick === null) {
|
||||||
this.movements = movements
|
console.error(`No items which occur at least ${min_occurences} times`)
|
||||||
console.log(item, movements)
|
setTimeout(this.nextScene.bind(this), 1000);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const [ item, movements ]= pick;
|
||||||
|
this.item = item
|
||||||
|
this.viz_data.current_item.set(item);
|
||||||
|
this.movements = movements
|
||||||
|
console.log('start timeline', item, movements)
|
||||||
|
|
||||||
this.motions = viz_data.drawn_motions
|
|
||||||
this.motions.update(items => [])
|
|
||||||
|
|
||||||
this.interval = setInterval(this.tick.bind(this), 3000);
|
this.interval = setInterval(this.tick.bind(this), 3000);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pickMovements(occurences: Map<Item, Movement[]>) {
|
/**
|
||||||
|
* Pick a movement
|
||||||
|
* @param min_occurences number The minimum number of occurences an item should have
|
||||||
|
* @returns [Item, Movement[]]
|
||||||
|
*/
|
||||||
|
pickMovements(min_occurences: number) {
|
||||||
|
|
||||||
const item_movements = [...occurences]
|
const item_movements = [...this.data.occurences]
|
||||||
// TODO: variable lenght, prefer with most steps
|
// TODO: variable lenght, prefer with most steps
|
||||||
.filter(([item, movements]) => movements.length > 1)
|
.filter(([item, movements]) => movements.length >= min_occurences)
|
||||||
|
|
||||||
return item_movements[Math.floor(Math.random() * item_movements.length)]
|
if(!item_movements.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const pick = item_movements[Math.floor(Math.random() * item_movements.length)]
|
||||||
|
pick[1].sort((a: Movement, b: Movement) => (a.date - b.date))
|
||||||
|
return pick;
|
||||||
}
|
}
|
||||||
|
|
||||||
tick() {
|
tick() {
|
||||||
|
@ -113,13 +179,20 @@ export class Timeline extends Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
// duration 5000 + Math.random() * 10000
|
// duration 5000 + Math.random() * 10000
|
||||||
const motion: Motion = { duration: 2000, movement: this.movements[this.step] };
|
const mov = this.movements[this.step]
|
||||||
console.log(motion, motion.movement.source, motion.movement.target)
|
this.drawMovements([mov], 2000, true)
|
||||||
this.motions.update(items => ([...items, motion]))
|
// const motion: Motion = { duration: 2000, movement: mov };
|
||||||
|
const log: Log = { date: mov.date, title: `Transfer to ${mov.target.name}`, description: "" };
|
||||||
|
// console.log(motion, motion.movement.source, motion.movement.target)
|
||||||
|
// this.viz_data.overlay_motions.update(items => ([...items, motion]))
|
||||||
|
this.viz_data.events.update(items => ([...items, log]))
|
||||||
this.step += 1;
|
this.step += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
|
this.viz_data.current_item.set(null)
|
||||||
|
this.viz_data.events.set([])
|
||||||
|
this.viz_data.overlay_motions.set([])
|
||||||
clearInterval(this.interval)
|
clearInterval(this.interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue