"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.