"use strict";
var datastorm = datastorm || {};
datastorm.virus = (function(){
var my = {};
var elements = {};
var width, height;
var active = true;
var config = {};
var nodes = [], links = [];
var force = d3.layout.force()
.charge(-800)
.linkDistance(50)
.linkStrength(function(d) {
return d.strength;
})
.gravity(0.01)
.friction(0.1);
var timer;
var ctx = datastorm.canvas.ctx;
function init() {
d3.select('canvas')
.attr('width', config.width)
.attr('height', config.height);
force.size([config.width, config.height]);
}
function forceEnd() {
// updatePositions();
// updateNetwork();
}
function updateNetwork() {
// Clone, otherwise adding nodes whilst iterating is not clever :)
nodes = _.clone(datastorm.virus.sim.getUsers());
links = _.clone(datastorm.virus.sim.getLinks());
force.nodes(nodes)
.links(links)
.start();
}
function render() {
ctx.globalAlpha = 1;
ctx.fillStyle = "rgba(0, 0, 0, 0.6)";
ctx.fillRect(0, 0, config.width, config.height);
ctx.globalAlpha = 0.5;
// Links
ctx.strokeStyle = 'rgba(200, 200, 255, 0.7)';
ctx.lineWidth = 0.5;
_.each(links, function(d) {
datastorm.canvas.drawLine(d.source.x, d.source.y, d.target.x, d.target.y);
});
// // Nodes
// console.log('render', nodes.length);
ctx.fillStyle = 'rgba(200,200,255,1)';
_.each(nodes, function(d) {
// console.log(d);
// ctx.fillStyle = colorScale(d.subject * 2);
var activeMessages = _.filter(d.messages, function(dd) {
return dd.active;
});
var fill = '#ddd';
var radius = 3;
if(activeMessages.length > 0) {
fill = activeMessages[0].color;
radius = 6;
}
ctx.fillStyle = fill;
datastorm.canvas.drawCircle(d.x, d.y, radius);
});
}
function showMessages() {
elements.svg
.select('.nodes')
.selectAll('circle')
.each(function(d) {
var activeMessages = _.filter(d.messages, function(dd) {
return dd.active;
});
var fill = '#ddd';
if(activeMessages.length > 0)
fill = activeMessages[0].color;
d3.select(this)
.style('fill', fill)
.attr('r', activeMessages.length > 0 ? 6 : 3);
});
}
my.init = function(conf) {
_.assign(config, conf);
init();
};
my.start = function() {
datastorm.virus.sim.start();
setInterval(render, 100);
}
my.stop = function() {
datastorm.virus.sim.stop();
};
my.networkUpdate = function() {
updateNetwork();
}
return my;
}());
"use strict";
var datastorm = datastorm || {};
datastorm.virus.sim = (function(){
var my = {};
var timer;
var users = [], /*messages = [],*/ links = [];
var messageId = -1;
var config = {
addUserProbability: 0.2,
addMessageProbability: 0.6,
addFriendProbability: 1,
repeatMessageProbability: 0.9,
repeatMessageTimespan: 2000, // timespan (milliseconds) in which a message can be repeated
maxUsers: 120
};
/*-----
HELPERS
-----*/
function shouldDo(probability) {
// Decide, based on the given probability, whether event should occur
return Math.random() < probability;
}
function randomColor() {
var color = d3.rgb(Math.random() * 255, Math.random() * 255, Math.random() * 255);
color = color.toString();
// console.log(color);
return color;
}
/*----
EVENTS
----*/
function addUser() {
if(!shouldDo(config.addUserProbability))
return;
if(users.length > config.maxUsers)
return;
var newUser = {
id: users.length,
chatty: 0.01 + 0.02 * Math.random(), // how often user messages
friendly: 0.8 + 0.2 * Math.random(), // how likely user is to friend another user
interesting: Math.random(), // how interesting the user's messages are
subject: Math.floor(Math.random() * 10), // the user's interest area,
friends: [],
messages: [],
// isMessaging: false,
// isRepeatedMessage: false,
// messageTime: null,
// isRepeatingMessage: false,
x: 0.5 * config.width + 10 * Math.random(), //TODO
y: 0.5 * config.height + 10 * Math.random()//TODO
};
// console.log(newUser);
users.push(newUser);
datastorm.virus.networkUpdate();
}
function purgeMessages() {
var now = Date.now();
// messages = _.filter(messages, function(message) {
// return now - message.time < config.repeatMessageTimespan;
// });
_.each(users, function(user) {
_.each(user.messages, function(m) {
if(now - m.time > config.repeatMessageTimespan)
m.active = false;
});
// user.messages = _.filter(user.messages, function(m) {
// return now - m.time < config.repeatMessageTimespan;
// });
});
}
function addMessage() {
// Iterate through each user and make a message
if(!shouldDo(config.addMessageProbability))
return;
_.each(users, function(user) {
// console.log(user);
// Throttle message creation (w/out changing globals)
if(!shouldDo(0.05))
return;
if(!shouldDo(user.chatty))
return;
var now = Date.now();
messageId++;
var message = {
id: messageId,
originator: user.id,
time: now,
active: true,
color: randomColor()
};
user.messages.push(message);
});
}
function repeatMessage() {
if(!shouldDo(config.repeatMessageProbability))
return;
var now = Date.now();
_.each(users, function(user) {
if(!shouldDo(4 * user.chatty)) // 4 times more likely to repeat a message?
return;
// console.log("looking at friends")
// Go through each friend
_.each(user.friends, function(friend) {
// console.log('checking friend')
if(!shouldDo(friend.interesting))
return;
// Go through each of friend's messages
_.each(friend.messages, function(m) {
if(!m.active)
return;
// console.log('checking messages')
// check user hasn't already sent this one!
var hasAlreadySent = false;
_.each(user.messages, function(mm) {
if(mm.id === m.id)
hasAlreadySent = true;
});
if(hasAlreadySent)
return;
var reMessage = {
id: m.id,
originator: m.originator,
time: now,
color: m.color,
active: true
}
user.messages.push(reMessage);
// console.log('remessage!')
});
})
});
}
function addFriend() {
if(!shouldDo(config.addFriendProbability))
return;
_.each(users, function(thisUser) {
_.each(users, function(otherUser) {
if(thisUser === otherUser)
return;
var probability = otherUser.chatty * thisUser.friendly * otherUser.interesting;
// if(!shouldDo(otherUser.chatty))
// return;
// if(!shouldDo(thisUser.friendly))
// return;
// if(!shouldDo(otherUser.interesting))
// return;
if(thisUser.subject !== otherUser.subject)
probability *= 0.002;
if(!shouldDo(probability))
return;
var alreadyFriends = _.find(thisUser.friends, function(friend) {
return friend.id === otherUser.id;
});
if(alreadyFriends)
return;
// I don't think we have to do this reverse check...
alreadyFriends = _.find(otherUser.friends, function(friend) {
return friend.id === thisUser.id;
});
if(alreadyFriends)
return;
var strength = thisUser.subject === otherUser.subject ? 0.05 : 0.03;
links.push({
source: thisUser.id,
target: otherUser.id,
strength: strength
});
thisUser.friends.push(otherUser);
otherUser.friends.push(thisUser);
datastorm.virus.networkUpdate();
});
});
}
function update() {
addUser();
purgeMessages();
addMessage();
repeatMessage();
addFriend();
// console.log(users, messages, links);
}
my.init = function(conf) {
_.assign(config, conf);
};
my.start = function() {
timer = setInterval(update, 100);
}
my.stop = function() {
clearInterval(timer);
}
my.getUsers = function() {
return users;
}
my.getMessages = function() {
return messages;
}
my.getLinks = function() {
return links;
}
return my;
}());
(function(){
var wrapper = d3.select('.wrapper');
var width = wrapper.node().clientWidth;
var height = wrapper.node().clientHeight;
datastorm.virus.sim.init({
width: width,
height: height
});
datastorm.virus.init({
width: width,
height: height
});
datastorm.virus.start();
})();
See the Pen Datastorm - Virus by Genevieve Smith-Nunes (@readysaltedcode) on CodePen.