Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -654,3 +654,35 @@ footer.unimplemented {
position: relative;
z-index: 2;
}

#progress {
position: fixed;
top: 0;
left: -6px;
width: 0%;
height: 2px;
background: red;
z-index: 3;
}

#progress:before,
#progress:after {
content: "";
position: absolute;
height: 2px;
opacity: 0.6;
box-shadow: red 1px 0 6px 1px;
border-radius: 100%;
}

#progress:before {
width: 20px;
right: 0;
clip: rect(-6px, 22px, 14px, 10px);
}

#progress:after {
width: 180px;
right: -80px;
clip: rect(-6px, 90px, 14px, -6px);
}
1 change: 1 addition & 0 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ <h2><span id="view">Graph</span> View</h2>
<div class="vis-content" id="visualization">
<div id="tooltip"></div>
</div>
<div id="progress"></div>
</section>
<footer class="unimplemented">
<div class="footer-toggle unimplemented">
Expand Down
152 changes: 89 additions & 63 deletions src/js/viz.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,69 +26,47 @@ const viz = {
this.defaultIcon = this.convertURIToImageData('images/defaultFavicon.svg');

this.updateCanvas(width, height);
this.draw(nodes, links);
this.addListeners();
},

draw(nodes, links) {
this.nodes = nodes;
this.links = links;

this.simulateForce();
this.drawOnCanvas();
},

simulateForce() {
if (!this.simulation) {
this.simulation = d3.forceSimulation(this.nodes);
this.simulation.on('tick', () => this.drawOnCanvas());
this.registerSimulationForces();
} else {
this.simulation.nodes(this.nodes);
}
this.registerLinkForce();
this.manualTick();
this.width = width;
this.height = height;
this.addListeners();
this.webWorker(nodes, links, width, height);
},

manualTick() {
this.simulation.alphaTarget(this.alphaTargetStart);
for (let i = 0; i < this.tickCount; i++) {
this.simulation.tick();
}
this.stopSimulation();
},
webWorker(nodes, links, width, height) {
this.worker = new Worker('moz-extension://dd1e07f4-7724-154c-80da-f501acf9262c/js/vizWorker.js');

restartSimulation() {
this.simulation.alphaTarget(this.alphaTargetStart);
this.simulation.restart();
},
this.worker.postMessage({
nodes,
links,
width,
height
});

stopSimulation() {
this.simulation.alphaTarget(this.alphaTargetStop);
this.worker.onmessage = (event) => {
switch (event.data.type) {
case 'tick': return this.ticked(event.data);
case 'end': return this.draw(event.data.nodes, event.data.links);
}
};
},

registerLinkForce() {
const linkForce = d3.forceLink(this.links);
linkForce.id((d) => d.hostname);
this.simulation.force('link', linkForce);
ticked(data) {
var progress = 100 * data.progress;
var meter = document.querySelector('#progress');
meter.style.width = `${progress}%`;
},

registerSimulationForces() {
const centerForce = d3.forceCenter(this.width / 2, this.height / 2);
this.simulation.force('center', centerForce);

const forceX = d3.forceX(this.width / 2);
this.simulation.force('x', forceX);

const forceY = d3.forceY(this.height / 2);
this.simulation.force('y', forceY);
draw(nodes, links) {
this.nodes = nodes;
this.links = links;

const chargeForce = d3.forceManyBody();
chargeForce.strength(this.chargeStrength);
this.simulation.force('charge', chargeForce);
if (this.animationFrame) {
window.cancelAnimationFrame(this.animationFrame);
}

const collisionForce = d3.forceCollide(this.collisionRadius);
this.simulation.force('collide', collisionForce);
this.drawOnCanvas();
},

createCanvas() {
Expand Down Expand Up @@ -134,6 +112,46 @@ const viz = {
this.context.restore();
},

drawAnimation(node) {
this.context.clearRect(0, 0, this.width, this.height);
this.context.save();
this.context.translate(this.transform.x, this.transform.y);
this.context.scale(this.transform.k, this.transform.k);
// this.drawAnimatedLinks(subject);
this.drawNodes();
this.drawAnimatedNode(node);
this.context.restore();
this.animationFrame = window.requestAnimationFrame(
() => this.drawAnimation(node));
},

drawAnimatedNode(node) {
if (!node) {
return;
}
const x = node.xArr.pop() || node.x;
const y = node.yArr.pop() || node.y;
let radius;

this.context.beginPath();
this.context.moveTo(x, y);

if (node.firstParty) {
radius = this.getRadius(node.thirdParties.length);
this.drawFirstParty(x, y, radius);
} else {
this.drawThirdParty(x, y);
}

if (node.shadow) {
// this.drawShadow(x, y, radius);
}

this.context.fillStyle = this.canvasColor;
this.context.closePath();
this.context.fill();
},

getRadius(thirdPartyLength) {
if (thirdPartyLength > 0) {
if (thirdPartyLength > this.collisionRadius) {
Expand All @@ -147,6 +165,10 @@ const viz = {

drawNodes() {
for (const node of this.nodes) {
if (node.fx) {
continue;
}

const x = node.fx || node.x;
const y = node.fy || node.y;
let radius;
Expand All @@ -170,7 +192,7 @@ const viz = {
this.context.fill();

if (node.favicon) {
this.drawFavicon(node, x, y);
// this.drawFavicon(node, x, y);
}
}
},
Expand Down Expand Up @@ -390,30 +412,34 @@ const viz = {
},

dragStart() {
if (!d3.event.active) {
this.restartSimulation();
}
d3.event.subject.shadow = true;
d3.event.subject.fx = d3.event.subject.x;
d3.event.subject.fy = d3.event.subject.y;
d3.event.subject.xArr = [];
d3.event.subject.yArr = [];
},

drag() {
d3.event.subject.fx = d3.event.x;
d3.event.subject.fy = d3.event.y;

d3.event.subject.xArr.push(d3.event.x);
d3.event.subject.yArr.push(d3.event.y);
this.drawOnCanvas();
this.hideTooltip();
},

dragEnd() {
if (!d3.event.active) {
this.stopSimulation();
}
d3.event.subject.x = d3.event.subject.fx;
d3.event.subject.y = d3.event.subject.fy;
d3.event.subject.fx = null;
d3.event.subject.fy = null;
d3.event.subject.shadow = false;

this.drawAnimation(d3.event.subject);

this.worker.postMessage({
nodes: this.nodes,
links: this.links,
width: this.width,
height: this.height,
subject: d3.event.subject
});
},

addZoom() {
Expand Down
83 changes: 83 additions & 0 deletions src/js/vizWorker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/* eslint-disable no-undef */
importScripts('https://d3js.org/d3-collection.v1.min.js');
importScripts('https://d3js.org/d3-dispatch.v1.min.js');
importScripts('https://d3js.org/d3-quadtree.v1.min.js');
importScripts('https://d3js.org/d3-timer.v1.min.js');
importScripts('https://d3js.org/d3-force.v1.min.js');

onmessage = (event) => {
d3Force.init(event.data);
};

const d3Force = {
init(data) {
const { nodes, links, width, height, subject } = data;
this.nodes = nodes;
this.links = links;
this.width = width;
this.height = height;
this.subject = subject;
d3Force.simulateForce();
},

simulateForce() {
if (!this.simulation) {
this.simulation = d3.forceSimulation(this.nodes);
this.simulation.on('tick', () => {
d3Force.simulateTick();
});
d3Force.registerSimulationForces();
} else {
this.simulation.nodes(this.nodes);
this.simulation.alpha(0.5);
this.simulation.restart();
// this.simulation.alpha(0);
}
d3Force.registerLinkForce();
},

simulateTick() {
const tickCounter = this.simulation.alpha();
postMessage({
type: 'tick',
progress: 1 - tickCounter
});
const tickPercent = Math.round((1 - tickCounter) * 100);
if (tickPercent === 99) {
if (this.subject) {
this.subject.fx = null;
this.subject.fy = null;
this.subject.xArr = null;
this.subject.yArr = null;
}
postMessage({
type: 'end',
nodes: d3Force.nodes,
links: d3Force.links
});
}
},

registerLinkForce() {
const linkForce = d3.forceLink(this.links);
linkForce.id((d) => d.hostname);
this.simulation.force('link', linkForce);
},

registerSimulationForces() {
const chargeStrength = -100,
collisionRadius = 10,
centerForce = d3.forceCenter(this.width / 2, this.height / 2),
forceX = d3.forceX(this.width / 2),
forceY = d3.forceY(this.height / 2),
chargeForce = d3.forceManyBody(),
collisionForce = d3.forceCollide(collisionRadius);

this.simulation.force('center', centerForce);
this.simulation.force('x', forceX);
this.simulation.force('y', forceY);
chargeForce.strength(chargeStrength);
this.simulation.force('charge', chargeForce);
this.simulation.force('collide', collisionForce);
}
};