Compare commits

..

No commits in common. "5c35ce9a040c86e060a0b5e1d70bb19b51daba4b" and "c8187f3f5e36d0f98d12e386524233e128b92a60" have entirely different histories.

8 changed files with 99 additions and 423 deletions

File diff suppressed because one or more lines are too long

View file

@ -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;
} }

7
poetry.lock generated
View file

@ -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]

View file

@ -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>

View file

@ -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>
@ -333,30 +312,35 @@
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: 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>

View file

@ -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;
} }

View file

@ -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

View file

@ -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)
} }