Compare commits
No commits in common. "5c35ce9a040c86e060a0b5e1d70bb19b51daba4b" and "c8187f3f5e36d0f98d12e386524233e128b92a60" have entirely different histories.
5c35ce9a04
...
c8187f3f5e
File diff suppressed because one or more lines are too long
20
index.html
20
index.html
|
@ -39,26 +39,6 @@
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
|
||||||
package = []
|
package = []
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.9"
|
python-versions = "^3.9"
|
||||||
content-hash = "c595a0588c25d58f3e3834ad7169126836d262b925fe6ca9b5d540dcf301d254"
|
content-hash = "5b80bcf39bc22fc51a0c05260792ccc8bde494467670046c58c64725c897eb75"
|
||||||
|
|
||||||
|
[metadata.files]
|
||||||
|
|
|
@ -1,207 +0,0 @@
|
||||||
<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>
|
|
104
src/Viz.svelte
104
src/Viz.svelte
|
@ -1,8 +1,7 @@
|
||||||
<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, fade } from "svelte/transition";
|
import { draw, slide } 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 {
|
||||||
|
@ -59,20 +58,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(
|
const locations = new Map(_nodes
|
||||||
_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"]) &&
|
||||||
|
@ -94,7 +93,6 @@
|
||||||
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
|
||||||
};
|
};
|
||||||
|
@ -153,8 +151,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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -162,27 +160,25 @@
|
||||||
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(.3, 70 / $drawn_motions.length));
|
$: opacity = Math.max(0.055, Math.min(1, 200 / $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 = parseInt(window.location.hash[1] ?? 0);
|
let currentSceneI = 0;
|
||||||
let currentScene: Scene;
|
let currentScene: Scene;
|
||||||
|
|
||||||
function nextScene() {
|
function nextScene() {
|
||||||
|
@ -191,7 +187,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Next scene", currentSceneI);
|
console.log("Next scene", currentSceneI);
|
||||||
currentScene = new scenes[currentSceneI](data, viz_data, nextScene);
|
currentScene = new scenes[currentSceneI](
|
||||||
|
data,
|
||||||
|
viz_data,
|
||||||
|
nextScene,
|
||||||
|
);
|
||||||
currentSceneI = (currentSceneI + 1) % scenes.length;
|
currentSceneI = (currentSceneI + 1) % scenes.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,10 +226,8 @@
|
||||||
|
|
||||||
<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
|
<span>Work by <em>Ruben van de Ven</em> for the
|
||||||
>Work by <em>Ruben van de Ven</em> for the
|
<em>University of Amsterdam Library</em>.</span>
|
||||||
<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>
|
||||||
|
|
||||||
|
@ -237,8 +235,7 @@
|
||||||
<g id="movements">
|
<g id="movements">
|
||||||
{#each $drawn_motions as m}
|
{#each $drawn_motions as m}
|
||||||
<path
|
<path
|
||||||
in:draw={{ duration: m.duration }}
|
in:draw|global={{ duration: m.duration }}
|
||||||
out:fade={{ duration: 1000 }}
|
|
||||||
d={m.movement.d}
|
d={m.movement.d}
|
||||||
style="opacity: {opacity}"
|
style="opacity: {opacity}"
|
||||||
></path>
|
></path>
|
||||||
|
@ -246,52 +243,34 @@
|
||||||
</g>
|
</g>
|
||||||
<g id="overlay_motions">
|
<g id="overlay_motions">
|
||||||
{#each $overlay_motions as m}
|
{#each $overlay_motions as m}
|
||||||
<path out:fade={{ duration: 1000 }}
|
<path in:draw|global={{ duration: m.duration }} d={m.movement.d}
|
||||||
in:draw={{ duration: m.duration }}
|
|
||||||
d={m.movement.d}
|
|
||||||
></path>
|
></path>
|
||||||
{/each}
|
{/each}
|
||||||
</g>
|
</g>
|
||||||
<g id="libraries">
|
<g id="libraries">
|
||||||
<LibrariesSvg />
|
{#each locations.values() as node}
|
||||||
<!-- {#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">
|
||||||
{#if $current_item}
|
{#each $events as m}
|
||||||
<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"
|
||||||
>{e.date.toLocaleDateString("en-UK", {
|
>{m.movement._original["Request Completion Date"]}</span
|
||||||
weekday: "long",
|
|
||||||
year: "numeric",
|
|
||||||
month: "long",
|
|
||||||
day: "numeric",
|
|
||||||
})}</span
|
|
||||||
>
|
>
|
||||||
<div class="text">
|
<span class="location">{m.movement.target.name}</span>
|
||||||
<span class="title">{e.title}</span>
|
|
||||||
<span class="description">{e.description}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
<!-- {/if} -->
|
|
||||||
|
|
||||||
{#if dev}
|
{#if dev}
|
||||||
<div id="fps">{$_fps}</div>
|
<div id="fps">{$_fps}</div>
|
||||||
|
@ -337,26 +316,31 @@
|
||||||
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: 3px;
|
stroke-width: 2px;
|
||||||
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: rgba(255, 255, 0, 0.641);
|
stroke: red;
|
||||||
stroke-width: 5;
|
stroke-width: 4;
|
||||||
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,8 +19,7 @@ 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, // the svg path
|
d: String
|
||||||
date: Date,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Occurences = Map<Item, Movement[]>
|
export type Occurences = Map<Item, Movement[]>
|
||||||
|
@ -50,8 +49,7 @@ 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) {
|
||||||
|
@ -89,8 +87,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 = 31; //_mapGraph.getSizeForNode(m.source);
|
var srcSize = 5; //_mapGraph.getSizeForNode(m.source);
|
||||||
var tgtSize = 31; //_mapGraph.getSizeForNode(m.target);
|
var tgtSize = 5; //_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,83 +1,31 @@
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import type { Data, Item, Log, Motion, Movement, VizData } from "../lib/types";
|
import type { Data, Item, 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(data, viz_data, nextScene);
|
super()
|
||||||
|
|
||||||
|
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.
|
||||||
|
@ -86,24 +34,26 @@ 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), this.options.tick_interval);
|
this.interval = setInterval(this.tick.bind(this), 500);
|
||||||
|
|
||||||
// clear the motions when kicking off:
|
// clear the motions when kicking off:
|
||||||
this.viz_data.drawn_motions.update(items => []);
|
this.motions.update(items => []);
|
||||||
}
|
}
|
||||||
|
|
||||||
tick() {
|
tick() {
|
||||||
const n = this.options.items_per_tick
|
const n = 10
|
||||||
if (this.step >= this.selected_movements.length) {
|
if (this.step >= this.allMovements.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.selected_movements.slice(this.step, this.step + n);
|
let movements: Movement[] = this.allMovements.slice(this.step, this.step + n);
|
||||||
|
|
||||||
// duration 5000 + Math.random() * 10000
|
// duration 5000 + Math.random() * 10000
|
||||||
this.drawMovements(movements, 3500)
|
let motions: Motion[] = movements.map((m) => ({ duration: 5000, movement: m }));
|
||||||
|
|
||||||
|
this.motions.update(items => ([...items, ...motions]))
|
||||||
this.step += n;
|
this.step += n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,56 +68,40 @@ 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(data, viz_data, nextScene);
|
super()
|
||||||
|
|
||||||
// clear canvas
|
this.nextScene = nextScene
|
||||||
// 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()
|
||||||
const min_occurences = 3;
|
console.log('s',data.occurences)
|
||||||
const pick = this.pickMovements(min_occurences);
|
const [ item, movements ]= this.pickMovements(data.occurences);
|
||||||
if( pick === null) {
|
|
||||||
console.error(`No items which occur at least ${min_occurences} times`)
|
|
||||||
setTimeout(this.nextScene.bind(this), 1000);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const [ item, movements ]= pick;
|
|
||||||
this.item = item
|
this.item = item
|
||||||
this.viz_data.current_item.set(item);
|
|
||||||
this.movements = movements
|
this.movements = movements
|
||||||
console.log('start timeline', item, movements)
|
console.log(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 = [...this.data.occurences]
|
const item_movements = [...occurences]
|
||||||
// TODO: variable lenght, prefer with most steps
|
// TODO: variable lenght, prefer with most steps
|
||||||
.filter(([item, movements]) => movements.length >= min_occurences)
|
.filter(([item, movements]) => movements.length > 1)
|
||||||
|
|
||||||
if(!item_movements.length) {
|
return item_movements[Math.floor(Math.random() * 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() {
|
||||||
|
@ -179,20 +113,13 @@ export class Timeline extends Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
// duration 5000 + Math.random() * 10000
|
// duration 5000 + Math.random() * 10000
|
||||||
const mov = this.movements[this.step]
|
const motion: Motion = { duration: 2000, movement: this.movements[this.step] };
|
||||||
this.drawMovements([mov], 2000, true)
|
console.log(motion, motion.movement.source, motion.movement.target)
|
||||||
// const motion: Motion = { duration: 2000, movement: mov };
|
this.motions.update(items => ([...items, motion]))
|
||||||
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 New Issue