"use strict";
var datastorm = datastorm || {};
datastorm.network = (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)
// .on('tick', render);
// var timer;
var ctx = datastorm.canvas.ctx;
var colorScale = d3.scale.category20();
function init() {
// elements.svg = d3.select('svg');
d3.select('canvas')
.attr('width', config.width)
.attr('height', config.height);
force.size([config.width, config.height]);
}
function updateNetwork() {
nodes = _.clone(datastorm.network.sim.getUsers());
links = _.clone(datastorm.network.sim.getLinks());
force.nodes(nodes)
.links(links)
.start();
}
function render() {
ctx.globalAlpha = 1;
ctx.fillStyle = "rgba(0, 0, 0, 0.4)";
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,0.5)';
_.each(nodes, function(d) {
// console.log(d);
ctx.fillStyle = colorScale(d.subject * 2);
datastorm.canvas.drawCircle(d.x, d.y, 3);
});
}
my.init = function(conf) {
_.assign(config, conf);
init();
setInterval(render, 100);
// updateNetwork();
};
my.stop = function() {
// clearInterval(timer);
datastorm.network.sim.stop();
};
my.networkUpdate = function() {
updateNetwork();
}
return my;
}());
datastorm.network.sim = (function(){
var my = {};
var timer;
var users = [], /*messages = [],*/ links = [];
var messageId = -1;
var config = {
addUserProbability: 0.1,
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: [],
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.network.networkUpdate();
}
function purgeMessages() {
var now = Date.now();
_.each(users, function(user) {
_.each(user.messages, function(m) {
if(now - m.time > config.repeatMessageTimespan)
m.active = false;
});
});
}
function addMessage() {
// Iterate through each user and make a message
if(!shouldDo(config.addMessageProbability))
return;
_.each(users, function(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;
// Go through each friend
_.each(user.friends, function(friend) {
if(!shouldDo(friend.interesting))
return;
// Go through each of friend's messages
_.each(friend.messages, function(m) {
if(!m.active)
return;
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(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.network.networkUpdate();
});
});
}
function update() {
addUser();
addFriend();
}
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.network.sim.init({
width: width,
height: height
});
datastorm.network.init({
width: width,
height: height
});
datastorm.network.sim.start();
})();
See the Pen Datastorm - Network by Genevieve Smith-Nunes (@readysaltedcode) on CodePen.