Trial with animations
This commit is contained in:
parent
9eda280eb5
commit
b2f49645e8
5 changed files with 430 additions and 245 deletions
12
index.html
12
index.html
|
@ -7,7 +7,7 @@
|
||||||
<title>UvA UB - movements</title>
|
<title>UvA UB - movements</title>
|
||||||
<style>
|
<style>
|
||||||
#canvas {
|
#canvas {
|
||||||
background: white;
|
/* background: white; */
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: #333;
|
background: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* animation root */
|
/* animation root */
|
||||||
|
@ -50,7 +50,9 @@
|
||||||
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(64, 64, 64)
|
background: rgb(45, 45, 45);
|
||||||
|
/* background: linear-gradient(to bottom right, #6fa8d9f7, #cdeed3); */
|
||||||
|
border-radius: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
@ -124,7 +126,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
svg circle{
|
svg circle{
|
||||||
fill: white
|
fill: white;
|
||||||
}
|
}
|
||||||
svg text{
|
svg text{
|
||||||
fill: white;
|
fill: white;
|
||||||
|
@ -148,7 +150,7 @@
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { viz } from './js/viz.jsx';
|
import { viz } from './js/viz.tsx';
|
||||||
|
|
||||||
viz(document.getElementById('viz'));
|
viz(document.getElementById('viz'));
|
||||||
</script>
|
</script>
|
||||||
|
|
240
js/viz.jsx
240
js/viz.jsx
|
@ -1,240 +0,0 @@
|
||||||
import * as d3 from "d3";
|
|
||||||
import { h, Fragment } from 'start-dom-jsx'
|
|
||||||
|
|
||||||
// const projection = d3.geoAitoff();
|
|
||||||
|
|
||||||
function degreesToRadians(degrees) {
|
|
||||||
return (degrees * Math.PI) / 180;
|
|
||||||
}
|
|
||||||
|
|
||||||
function latLonToOffsets(latitude, longitude, mapWidth, mapHeight) {
|
|
||||||
const FE = 180; // false easting
|
|
||||||
const radius = mapWidth / (2 * Math.PI);
|
|
||||||
|
|
||||||
const latRad = degreesToRadians(latitude);
|
|
||||||
const lonRad = degreesToRadians(longitude + FE);
|
|
||||||
|
|
||||||
const x = lonRad * radius;
|
|
||||||
|
|
||||||
const yFromEquator = radius * Math.log(Math.tan(Math.PI / 4 + latRad / 2));
|
|
||||||
const y = mapHeight / 2 - yFromEquator;
|
|
||||||
|
|
||||||
return { x, y };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function viz(vizEl) {
|
|
||||||
console.log(d3)
|
|
||||||
// vizEl.appendChild(<h2>Hello</h2>);
|
|
||||||
|
|
||||||
fetch("data/parsed_requests.json")
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then(start)
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
// alert("Data could not be loaded.")
|
|
||||||
});
|
|
||||||
|
|
||||||
// d3.queue()
|
|
||||||
// .defer(d3.json, "data/parsed_requests.json") // World shape
|
|
||||||
// .await(start)
|
|
||||||
}
|
|
||||||
|
|
||||||
function lerp(X, Y, t) {
|
|
||||||
return X * t + Y * (1 - t)
|
|
||||||
}
|
|
||||||
|
|
||||||
function inverse_lerp(a, b, x) {
|
|
||||||
return (x - a) / (b - a);
|
|
||||||
}
|
|
||||||
|
|
||||||
function start(parsed_requests) {
|
|
||||||
const nodes = parsed_requests.nodes;
|
|
||||||
const edges = parsed_requests.edges;
|
|
||||||
|
|
||||||
const svg = d3.select('svg');
|
|
||||||
const g_libraries = svg.append("g").attr("id", "libraries");
|
|
||||||
const g_movements = svg.append("g").attr("id", "movements");
|
|
||||||
const width = +svg.attr("width"),
|
|
||||||
height = +svg.attr("height");
|
|
||||||
|
|
||||||
// let libraries = {}
|
|
||||||
// console.log(nodes, edges)
|
|
||||||
const valid_nodes = nodes.filter((n) => n.lat && n.lon).map((node) => {
|
|
||||||
const { x: x1, y: y1 } = latLonToOffsets(node.lat, node.lon, 100000, 100000)
|
|
||||||
console.log(x1, y1)
|
|
||||||
const x2 = inverse_lerp(1358, 1380, x1);
|
|
||||||
const y2 = inverse_lerp(32862, 32900, y1);
|
|
||||||
|
|
||||||
const margin = 200;
|
|
||||||
const x = x2 * (width - 2 * margin) + margin;
|
|
||||||
const y = y2 * (height - 2 * margin) + margin;
|
|
||||||
// console.log(x2, y2)
|
|
||||||
// console.log(node)
|
|
||||||
|
|
||||||
node['x'] = x;
|
|
||||||
node['y'] = y;
|
|
||||||
return node
|
|
||||||
})
|
|
||||||
|
|
||||||
const nodeMap = Object.fromEntries(valid_nodes.map(d => [d['name'], d]));
|
|
||||||
|
|
||||||
const links = edges.filter(l => nodeMap[l['Owning Library Name']] && nodeMap[l['Pickup Location']]).map((l, idx) => {
|
|
||||||
l.source = nodeMap[l['Owning Library Name']];
|
|
||||||
l.target = nodeMap[l['Pickup Location']];
|
|
||||||
l.nr = idx;
|
|
||||||
return l;
|
|
||||||
});
|
|
||||||
// .filter((link, index, self) =>
|
|
||||||
// // remove incidental duplicates
|
|
||||||
// index === self.findIndex((l) => (
|
|
||||||
// l.source.id === link.source.id && l.target.id === link.target.id
|
|
||||||
// ))
|
|
||||||
// );
|
|
||||||
|
|
||||||
// Reformat the list of link. Note that columns in csv file are called long1, long2, lat1, lat2
|
|
||||||
// var link = []
|
|
||||||
// edges.forEach(function (row) {
|
|
||||||
// source = [+row.long1, +row.lat1]
|
|
||||||
// target = [+row.long2, +row.lat2]
|
|
||||||
// topush = { type: "LineString", coordinates: [source, target] }
|
|
||||||
// link.push(topush)
|
|
||||||
// })
|
|
||||||
|
|
||||||
// Add the path
|
|
||||||
const libraries = g_libraries.selectAll(".library");
|
|
||||||
libraries.data(valid_nodes, d => d.code)
|
|
||||||
.join((enter) => {
|
|
||||||
let group = enter.append("g")
|
|
||||||
// .attr("class", getClasses)
|
|
||||||
// .attr("id", (n) => getIdForTitle(n.fulltext));
|
|
||||||
.attr("id", (n) => n.code)
|
|
||||||
.attr("transform", (n) => `translate(${n.x}, ${n.y})`);
|
|
||||||
|
|
||||||
// group.on("click", (evt, n) => {
|
|
||||||
// evt.stopPropagation(); this.selectNode(n);
|
|
||||||
// });
|
|
||||||
// group.on("mouseover", (evt, n) => {
|
|
||||||
// this.hoverNode(evt, n);
|
|
||||||
// });
|
|
||||||
// group.on("mouseout", (evt, n) => {
|
|
||||||
// this.endHoverNode(n);
|
|
||||||
// });
|
|
||||||
group.append('circle').attr("r", 5 /*this.nodeSize*/);
|
|
||||||
// group.append('path')
|
|
||||||
// .attr('d', (n) => {
|
|
||||||
// return getSymbolForNode(n)(n);
|
|
||||||
// })
|
|
||||||
var nodeTitle = group.append('text').attr("class", "nodeTitle").attr("y", "4").attr('x', 5).text((n) => n.name);
|
|
||||||
return group
|
|
||||||
})
|
|
||||||
|
|
||||||
const movements_a = g_movements.selectAll(".movement");
|
|
||||||
const movements = movements_a
|
|
||||||
.data(links)
|
|
||||||
.join(
|
|
||||||
enter => {
|
|
||||||
let group = enter.append("g")
|
|
||||||
.attr("class", (l) => "link " /* TODO type of movement */)
|
|
||||||
.attr("id", (l) => `link${l.nr}`);
|
|
||||||
group.append("path")
|
|
||||||
// .attr("marker-end", "url(#arrowHead)")
|
|
||||||
.attr('id', (d, i) => 'linkpath_' + i)
|
|
||||||
// .on("mouseover", (ev, link) => {
|
|
||||||
// d3.select(ev.target).classed('hover', true);
|
|
||||||
// const nodes = document.getElementsByClassName('node');
|
|
||||||
// for (let n of nodes) {
|
|
||||||
// const d = d3.select(n).datum();
|
|
||||||
// if (d == link.target || d == link.source) {
|
|
||||||
// n.classList.add('linkHover');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// this.showRelationTooltip(link, ev);
|
|
||||||
// }).on("mouseout", (ev, link) => {
|
|
||||||
// this.hideTooltip();
|
|
||||||
// d3.select(ev.target).classed('hover', false);
|
|
||||||
// const nodes = document.getElementsByClassName('linkHover');
|
|
||||||
// while (nodes.length) {
|
|
||||||
// nodes[0].classList.remove('linkHover');
|
|
||||||
// }
|
|
||||||
// }).on("click", (ev, link) => {
|
|
||||||
// ev.stopPropagation();
|
|
||||||
// this.selectNode(link.source);
|
|
||||||
// })
|
|
||||||
;
|
|
||||||
// group.filter((l) => l.name != "City").append("text").attr("class", "labelText").text(function (l) {
|
|
||||||
// return l.name;
|
|
||||||
// });
|
|
||||||
// group.append("text")
|
|
||||||
// .attr("class", "labelText")
|
|
||||||
// .attr("dx", 20)
|
|
||||||
// .attr("dy", 0)
|
|
||||||
// .style("fill", "red")
|
|
||||||
// .append("textPath")
|
|
||||||
// .attr("xlink:href", function (d, i) { return "#linkid_" + i; })
|
|
||||||
// .attr("startOffset","50%")
|
|
||||||
// .text((d,i) => d.name );
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
;
|
|
||||||
|
|
||||||
movements.each(function (l) {
|
|
||||||
let sourceX, targetX, midX, dx, dy, angle;
|
|
||||||
|
|
||||||
// This mess makes the arrows exactly perfect.
|
|
||||||
// thanks to http://bl.ocks.org/curran/9b73eb564c1c8a3d8f3ab207de364bf4
|
|
||||||
if (l.source.x < l.target.x) {
|
|
||||||
sourceX = l.source.x;
|
|
||||||
targetX = l.target.x;
|
|
||||||
} else if (l.target.x < l.source.x) {
|
|
||||||
targetX = l.target.x;
|
|
||||||
sourceX = l.source.x;
|
|
||||||
} else if (l.target.isCircle) {
|
|
||||||
targetX = sourceX = l.target.x;
|
|
||||||
} else if (l.source.isCircle) {
|
|
||||||
targetX = sourceX = l.source.x;
|
|
||||||
} else {
|
|
||||||
midX = (l.source.x + l.target.x) / 2;
|
|
||||||
if (midX > l.target.x) {
|
|
||||||
midX = l.target.x;
|
|
||||||
} else if (midX > l.source.x) {
|
|
||||||
midX = l.source.x;
|
|
||||||
} else if (midX < l.target.x) {
|
|
||||||
midX = l.target.x;
|
|
||||||
} else if (midX < l.source.x) {
|
|
||||||
midX = l.source.x;
|
|
||||||
}
|
|
||||||
targetX = sourceX = midX;
|
|
||||||
}
|
|
||||||
|
|
||||||
dx = targetX - sourceX;
|
|
||||||
dy = l.target.y - l.source.y;
|
|
||||||
angle = Math.atan2(dx, dy);
|
|
||||||
|
|
||||||
var srcSize = 5; //_mapGraph.getSizeForNode(l.source);
|
|
||||||
var tgtSize = 5; //_mapGraph.getSizeForNode(l.target);
|
|
||||||
|
|
||||||
// Compute the line endpoint such that the arrow
|
|
||||||
// it not in the center, but rather slightly out of it
|
|
||||||
// use a small ofset for the angle to compensate roughly for the curve
|
|
||||||
l.sourceX = sourceX + Math.sin(angle+.5) * srcSize;
|
|
||||||
l.targetX = targetX - Math.sin(angle-.5) * tgtSize;
|
|
||||||
l.sourceY = l.source.y + Math.cos(angle+.5) * srcSize;
|
|
||||||
l.targetY = l.target.y - Math.cos(angle-.5) * tgtSize;
|
|
||||||
|
|
||||||
|
|
||||||
// find radius of arc based on distance between points
|
|
||||||
// add a jitter to spread out the lines when links are stacked
|
|
||||||
const dr = Math.sqrt(dx * dx + dy * dy) * (.7 + Math.random()*.6);
|
|
||||||
|
|
||||||
// "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y
|
|
||||||
let rel = d3.select(this);
|
|
||||||
rel.select("path")
|
|
||||||
.attr('d', `M ${l.sourceX},${l.sourceY} A ${dr},${dr} 0 0,1 ${l.targetX},${l.targetY}`)
|
|
||||||
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(libraries)
|
|
||||||
}
|
|
417
js/viz.tsx
Normal file
417
js/viz.tsx
Normal file
|
@ -0,0 +1,417 @@
|
||||||
|
import * as d3 from "d3";
|
||||||
|
// use jsx templating
|
||||||
|
import { h, Fragment } from 'start-dom-jsx';
|
||||||
|
import anime from 'animejs/lib/anime.es.js';
|
||||||
|
|
||||||
|
console.log(anime)
|
||||||
|
|
||||||
|
// const projection = d3.geoAitoff();
|
||||||
|
|
||||||
|
function degreesToRadians(degrees) {
|
||||||
|
return (degrees * Math.PI) / 180;
|
||||||
|
}
|
||||||
|
|
||||||
|
function latLonToOffsets(latitude, longitude, mapWidth, mapHeight) {
|
||||||
|
const FE = 180; // false easting
|
||||||
|
const radius = mapWidth / (2 * Math.PI);
|
||||||
|
|
||||||
|
const latRad = degreesToRadians(latitude);
|
||||||
|
const lonRad = degreesToRadians(longitude + FE);
|
||||||
|
|
||||||
|
const x = lonRad * radius;
|
||||||
|
|
||||||
|
const yFromEquator = radius * Math.log(Math.tan(Math.PI / 4 + latRad / 2));
|
||||||
|
const y = mapHeight / 2 - yFromEquator;
|
||||||
|
|
||||||
|
return { x, y };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function viz(vizEl) {
|
||||||
|
console.log(d3)
|
||||||
|
// vizEl.appendChild(<h2>Hello</h2>);
|
||||||
|
|
||||||
|
fetch("data/parsed_requests.json")
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then(start)
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
// alert("Data could not be loaded.")
|
||||||
|
});
|
||||||
|
|
||||||
|
// d3.queue()
|
||||||
|
// .defer(d3.json, "data/parsed_requests.json") // World shape
|
||||||
|
// .await(start)
|
||||||
|
}
|
||||||
|
|
||||||
|
function lerp(X, Y, t) {
|
||||||
|
return X * t + Y * (1 - t)
|
||||||
|
}
|
||||||
|
|
||||||
|
function inverse_lerp(a, b, x) {
|
||||||
|
return (x - a) / (b - a);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IndexObject {
|
||||||
|
[index: string]: Array<number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A scene
|
||||||
|
*/
|
||||||
|
class Scene {
|
||||||
|
// setup: CallableFunction;
|
||||||
|
animation: CallableFunction; // the function that returns an anime.js object
|
||||||
|
|
||||||
|
constructor(animation: CallableFunction) {
|
||||||
|
// this.setup = setup;
|
||||||
|
this.animation = animation;
|
||||||
|
}
|
||||||
|
|
||||||
|
async run () {
|
||||||
|
const anim = this.animation();
|
||||||
|
console.log('anim', anim);
|
||||||
|
await anim.finished
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Sequence {
|
||||||
|
scenes: Array<Scene>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.scenes = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
while(true) {
|
||||||
|
for(const scene of this.scenes){
|
||||||
|
console.log('scene', scene);
|
||||||
|
await scene.run();
|
||||||
|
console.log('ran')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function buildIndex(objectList: Array<Object>, field: string): IndexObject {
|
||||||
|
const index = {}
|
||||||
|
for (const idx in objectList) {
|
||||||
|
const v = objectList[idx][field]
|
||||||
|
if (!index.hasOwnProperty(v)) {
|
||||||
|
index[v] = [idx]
|
||||||
|
} else {
|
||||||
|
index[v].push(idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function start(parsed_requests) {
|
||||||
|
const nodes = parsed_requests.nodes;
|
||||||
|
const edges: Array<Object> = parsed_requests.edges;
|
||||||
|
|
||||||
|
const svg = d3.select('svg');
|
||||||
|
const g_libraries = svg.append("g").attr("id", "libraries");
|
||||||
|
const g_movements = svg.append("g").attr("id", "movements");
|
||||||
|
const width = +svg.attr("width"),
|
||||||
|
height = +svg.attr("height");
|
||||||
|
|
||||||
|
// let libraries = {}
|
||||||
|
// console.log(nodes, edges)
|
||||||
|
// filter nodes with only having both Latitude and Longitude.
|
||||||
|
// then map these coordinates to the canvas space
|
||||||
|
const valid_nodes = nodes.filter((n) => n.lat && n.lon).map((node) => {
|
||||||
|
const { x: x1, y: y1 } = latLonToOffsets(node.lat, node.lon, 100000, 100000)
|
||||||
|
console.log(x1, y1)
|
||||||
|
const x2 = inverse_lerp(1358, 1380, x1);
|
||||||
|
const y2 = inverse_lerp(32862, 32900, y1);
|
||||||
|
|
||||||
|
const margin = 200;
|
||||||
|
const x = x2 * (width - 2 * margin) + margin;
|
||||||
|
const y = y2 * (height - 2 * margin) + margin;
|
||||||
|
// console.log(x2, y2)
|
||||||
|
// console.log(node)
|
||||||
|
|
||||||
|
node['x'] = x;
|
||||||
|
node['y'] = y;
|
||||||
|
return node
|
||||||
|
})
|
||||||
|
|
||||||
|
// create an index to access the node objects by their name
|
||||||
|
const nodeMap = Object.fromEntries(valid_nodes.map(d => [d['name'], d]));
|
||||||
|
const edgeIndexBarcode = buildIndex(edges, 'Barcode');
|
||||||
|
// console.log(edgeIndexBarcode);
|
||||||
|
|
||||||
|
const links = edges.filter(l => nodeMap[l['Owning Library Name']] && nodeMap[l['Pickup Location']]).map((l, idx) => {
|
||||||
|
l.source = nodeMap[l['Owning Library Name']];
|
||||||
|
l.target = nodeMap[l['Pickup Location']];
|
||||||
|
l.nr = idx;
|
||||||
|
return l;
|
||||||
|
});
|
||||||
|
// .filter((link, index, self) =>
|
||||||
|
// // remove incidental duplicates
|
||||||
|
// index === self.findIndex((l) => (
|
||||||
|
// l.source.id === link.source.id && l.target.id === link.target.id
|
||||||
|
// ))
|
||||||
|
// );
|
||||||
|
|
||||||
|
// Reformat the list of link. Note that columns in csv file are called long1, long2, lat1, lat2
|
||||||
|
// var link = []
|
||||||
|
// edges.forEach(function (row) {
|
||||||
|
// source = [+row.long1, +row.lat1]
|
||||||
|
// target = [+row.long2, +row.lat2]
|
||||||
|
// topush = { type: "LineString", coordinates: [source, target] }
|
||||||
|
// link.push(topush)
|
||||||
|
// })
|
||||||
|
|
||||||
|
// Add the path
|
||||||
|
const libraries = g_libraries.selectAll(".library");
|
||||||
|
libraries.data(valid_nodes, d => d.code)
|
||||||
|
.join((enter) => {
|
||||||
|
let group = enter.append("g")
|
||||||
|
// .attr("class", getClasses)
|
||||||
|
// .attr("id", (n) => getIdForTitle(n.fulltext));
|
||||||
|
.attr("id", (n) => n.code)
|
||||||
|
.attr("transform", (n) => `translate(${n.x}, ${n.y})`);
|
||||||
|
|
||||||
|
// group.on("click", (evt, n) => {
|
||||||
|
// evt.stopPropagation(); this.selectNode(n);
|
||||||
|
// });
|
||||||
|
// group.on("mouseover", (evt, n) => {
|
||||||
|
// this.hoverNode(evt, n);
|
||||||
|
// });
|
||||||
|
// group.on("mouseout", (evt, n) => {
|
||||||
|
// this.endHoverNode(n);
|
||||||
|
// });
|
||||||
|
group.append('circle').attr("r", 5 /*this.nodeSize*/);
|
||||||
|
group.append('circle').attr("r", 20 /*this.nodeSize*/).style("fill", "none").style("stroke", "white").style("stroke-width", "10");
|
||||||
|
// group.append('path')
|
||||||
|
// .attr('d', (n) => {
|
||||||
|
// return getSymbolForNode(n)(n);
|
||||||
|
// })
|
||||||
|
var nodeTitle = group.append('text').attr("class", "nodeTitle").attr("y", "4").attr('x', 35).text((n) => n.name);
|
||||||
|
return group
|
||||||
|
})
|
||||||
|
|
||||||
|
const movements_a = g_movements.selectAll(".movement");
|
||||||
|
const movements = movements_a
|
||||||
|
.data(links)
|
||||||
|
.join(
|
||||||
|
enter => {
|
||||||
|
let group = enter.append("g")
|
||||||
|
.attr("class", (l) => "link " /* TODO type of movement */)
|
||||||
|
.attr("id", (l) => `link${l.nr}`);
|
||||||
|
group.append("path")
|
||||||
|
// .attr("marker-end", "url(#arrowHead)")
|
||||||
|
.attr('id', (d, i) => 'linkpath_' + i)
|
||||||
|
// .on("mouseover", (ev, link) => {
|
||||||
|
// d3.select(ev.target).classed('hover', true);
|
||||||
|
// const nodes = document.getElementsByClassName('node');
|
||||||
|
// for (let n of nodes) {
|
||||||
|
// const d = d3.select(n).datum();
|
||||||
|
// if (d == link.target || d == link.source) {
|
||||||
|
// n.classList.add('linkHover');
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// this.showRelationTooltip(link, ev);
|
||||||
|
// }).on("mouseout", (ev, link) => {
|
||||||
|
// this.hideTooltip();
|
||||||
|
// d3.select(ev.target).classed('hover', false);
|
||||||
|
// const nodes = document.getElementsByClassName('linkHover');
|
||||||
|
// while (nodes.length) {
|
||||||
|
// nodes[0].classList.remove('linkHover');
|
||||||
|
// }
|
||||||
|
// }).on("click", (ev, link) => {
|
||||||
|
// ev.stopPropagation();
|
||||||
|
// this.selectNode(link.source);
|
||||||
|
// })
|
||||||
|
;
|
||||||
|
// group.filter((l) => l.name != "City").append("text").attr("class", "labelText").text(function (l) {
|
||||||
|
// return l.name;
|
||||||
|
// });
|
||||||
|
// group.append("text")
|
||||||
|
// .attr("class", "labelText")
|
||||||
|
// .attr("dx", 20)
|
||||||
|
// .attr("dy", 0)
|
||||||
|
// .style("fill", "red")
|
||||||
|
// .append("textPath")
|
||||||
|
// .attr("xlink:href", function (d, i) { return "#linkid_" + i; })
|
||||||
|
// .attr("startOffset","50%")
|
||||||
|
// .text((d,i) => d.name );
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
;
|
||||||
|
|
||||||
|
movements.each(function (l) {
|
||||||
|
let sourceX, targetX, midX, dx, dy, angle;
|
||||||
|
|
||||||
|
// This mess makes the arrows exactly perfect.
|
||||||
|
// thanks to http://bl.ocks.org/curran/9b73eb564c1c8a3d8f3ab207de364bf4
|
||||||
|
if (l.source.x < l.target.x) {
|
||||||
|
sourceX = l.source.x;
|
||||||
|
targetX = l.target.x;
|
||||||
|
} else if (l.target.x < l.source.x) {
|
||||||
|
targetX = l.target.x;
|
||||||
|
sourceX = l.source.x;
|
||||||
|
} else if (l.target.isCircle) {
|
||||||
|
targetX = sourceX = l.target.x;
|
||||||
|
} else if (l.source.isCircle) {
|
||||||
|
targetX = sourceX = l.source.x;
|
||||||
|
} else {
|
||||||
|
midX = (l.source.x + l.target.x) / 2;
|
||||||
|
if (midX > l.target.x) {
|
||||||
|
midX = l.target.x;
|
||||||
|
} else if (midX > l.source.x) {
|
||||||
|
midX = l.source.x;
|
||||||
|
} else if (midX < l.target.x) {
|
||||||
|
midX = l.target.x;
|
||||||
|
} else if (midX < l.source.x) {
|
||||||
|
midX = l.source.x;
|
||||||
|
}
|
||||||
|
targetX = sourceX = midX;
|
||||||
|
}
|
||||||
|
|
||||||
|
dx = targetX - sourceX;
|
||||||
|
dy = l.target.y - l.source.y;
|
||||||
|
angle = Math.atan2(dx, dy);
|
||||||
|
|
||||||
|
var srcSize = 5; //_mapGraph.getSizeForNode(l.source);
|
||||||
|
var tgtSize = 5; //_mapGraph.getSizeForNode(l.target);
|
||||||
|
|
||||||
|
// Compute the line endpoint such that the arrow
|
||||||
|
// it not in the center, but rather slightly out of it
|
||||||
|
// use a small ofset for the angle to compensate roughly for the curve
|
||||||
|
l.sourceX = sourceX + Math.sin(angle + .5) * srcSize;
|
||||||
|
l.targetX = targetX - Math.sin(angle - .5) * tgtSize;
|
||||||
|
l.sourceY = l.source.y + Math.cos(angle + .5) * srcSize;
|
||||||
|
l.targetY = l.target.y - Math.cos(angle - .5) * tgtSize;
|
||||||
|
|
||||||
|
|
||||||
|
// find radius of arc based on distance between points
|
||||||
|
// add a jitter to spread out the lines when links are stacked
|
||||||
|
const dr = Math.sqrt(dx * dx + dy * dy) * (.7 + Math.random() * .6);
|
||||||
|
|
||||||
|
// "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y
|
||||||
|
let rel = d3.select(this);
|
||||||
|
rel.select("path")
|
||||||
|
.attr('d', `M ${l.sourceX},${l.sourceY} A ${dr},${dr} 0 0,1 ${l.targetX},${l.targetY}`)
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
// const edgesToHighlight = edgeIndexBarcode["0139900930"].map((i) => edges[i])
|
||||||
|
// const edgesToHighlight = edgeIndexBarcode["0182126011"].map((i) => edges[i])
|
||||||
|
// const edgesToHighlight = edgeIndexBarcode["0139204135"].map((i) => edges[i])
|
||||||
|
let edgesToHighlight: Array<Object> = [];
|
||||||
|
for (const code in edgeIndexBarcode) {
|
||||||
|
if (edgeIndexBarcode[code].length < 4) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const i of edgeIndexBarcode[code]) {
|
||||||
|
// console.log(i)
|
||||||
|
edgesToHighlight.push(edges[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const edgeToSelect = edges.filter(e => e.nr == 530)[0]
|
||||||
|
const edgeToSelect = edges.filter(e => e.Barcode == "0133040931")[0]
|
||||||
|
edgesToHighlight = edges.filter(e => e.Barcode == edgeToSelect.Barcode)
|
||||||
|
// edgeToSelect.
|
||||||
|
// console.log(530, edges[530].nr, edges[530])
|
||||||
|
// edgesToHighlight = [];
|
||||||
|
// edgesToHighlight.push(edges[530])
|
||||||
|
highlightTrajectory(edgesToHighlight);
|
||||||
|
// console.log(libraries)
|
||||||
|
|
||||||
|
function scene_01_FollowItem() {
|
||||||
|
const edgeToSelect = edges.filter(e => e.Barcode == "0133040931")[0]
|
||||||
|
edgesToHighlight = edges.filter(e => e.Barcode == edgeToSelect.Barcode)
|
||||||
|
|
||||||
|
const tl = anime.timeline({})
|
||||||
|
console.log(tl)
|
||||||
|
for(const edge of edgesToHighlight) {
|
||||||
|
tl.add({
|
||||||
|
targets: `#linkpath_${edge.nr}`,
|
||||||
|
stroke: ['rgb(255, 0, 0)', 'rgb(0, 255, 255)'],
|
||||||
|
// strokeDashoffset: [anime.setDashoffset, 0],
|
||||||
|
duration: 500,
|
||||||
|
// easing: 'easeOutElastic(1, .8)',
|
||||||
|
// direction: 'alternate'
|
||||||
|
})
|
||||||
|
.add({
|
||||||
|
targets: `#linkpath_${edge.nr}`,
|
||||||
|
// stroke: ['rgb(255, 0, 0)', 'rgb(0, 255, 255)'],
|
||||||
|
strokeDashoffset: [anime.setDashoffset, 0],
|
||||||
|
easing: 'easeInOutSine',
|
||||||
|
duration: 8000,
|
||||||
|
// easing: 'easeOutElastic(1, .8)',
|
||||||
|
// direction: 'alternate'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
tl.add({
|
||||||
|
targets: edgesToHighlight.map(edge => `#linkpath_${edge.nr}`),
|
||||||
|
stroke: ['rgb(0, 255, 255)', 'rgb(255, 0, 0)'],
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
return tl;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const sequence = new Sequence();
|
||||||
|
sequence.scenes.push(new Scene(scene_01_FollowItem))
|
||||||
|
console.log(sequence)
|
||||||
|
sequence.run()
|
||||||
|
|
||||||
|
// document.getElementById('linkpath_3903').style.stroke = 'orange';
|
||||||
|
// sequence.scenes.push(new Scene(() => {
|
||||||
|
// const tl = anime.timeline({})
|
||||||
|
// tl.add({
|
||||||
|
// targets: '#linkpath_532',
|
||||||
|
// stroke: ['rgb(255, 0, 0)', 'rgb(0, 255, 255)'],
|
||||||
|
// duration: 2000,
|
||||||
|
// // easing: 'easeOutElastic(1, .8)',
|
||||||
|
// direction: 'alternate'
|
||||||
|
// })
|
||||||
|
// tl.add({
|
||||||
|
// targets: '#linkpath_532',
|
||||||
|
// // stroke: ['rgb(255, 255, 0)', 'rgb(255, 0, 255)'],
|
||||||
|
// translateX: [0, -100, 0],
|
||||||
|
// duration: 4000,
|
||||||
|
// easing: 'easeOutElastic(1, .8)',
|
||||||
|
// // direction: 'alternate'
|
||||||
|
// })
|
||||||
|
// return tl
|
||||||
|
// }));
|
||||||
|
// sequence.scenes.push(new Scene(() => {
|
||||||
|
// return anime({
|
||||||
|
// targets: '#linkpath_530',
|
||||||
|
// translateX: [0, 500],
|
||||||
|
// duration: 2000,
|
||||||
|
// direction: 'alternate'
|
||||||
|
// })
|
||||||
|
// }));
|
||||||
|
// sequence.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: this is currently by barcode, but this should be the object itself.
|
||||||
|
function highlightTrajectory(edges: Array<Object>) {
|
||||||
|
// console.log(edges)
|
||||||
|
for (const edge of edges) {
|
||||||
|
console.log(`#linkpath_${edge.nr}`)
|
||||||
|
const selection = d3.select(`#linkpath_${edge.nr}`);
|
||||||
|
// console.log(selection);
|
||||||
|
selection.style("stroke", "red");
|
||||||
|
selection.style("stroke-width", "5");
|
||||||
|
selection.classed("selected", true);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"animejs": "^3.2.2",
|
||||||
"d3": "^7.9.0",
|
"d3": "^7.9.0",
|
||||||
"start-dom-jsx": "^1.0.0-beta.1",
|
"start-dom-jsx": "^1.0.0-beta.1",
|
||||||
"vite": "^5.2.8"
|
"vite": "^5.2.8"
|
||||||
|
|
|
@ -197,6 +197,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
|
||||||
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
|
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
|
||||||
|
|
||||||
|
animejs@^3.2.2:
|
||||||
|
version "3.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/animejs/-/animejs-3.2.2.tgz#59be98c58834339d5847f4a70ddba74ac75b6afc"
|
||||||
|
integrity sha512-Ao95qWLpDPXXM+WrmwcKbl6uNlC5tjnowlaRYtuVDHHoygjtIPfDUoK9NthrlZsQSKjZXlmji2TrBUAVbiH0LQ==
|
||||||
|
|
||||||
commander@7:
|
commander@7:
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
|
||||||
|
|
Loading…
Reference in a new issue