This commit is contained in:
Nicky Case 2018-04-01 12:34:52 -04:00
parent 74a6342e4a
commit fd39f09979
18 changed files with 466 additions and 169 deletions

View File

@ -2,13 +2,14 @@ html, body{
width:100%;
height:100%;
overflow: hidden;
cursor: none;
}
body{
margin:0;
font-family: "FuturaHandwritten";
font-size: 22px;
cursor: none;
line-height: 1.5em;
}
/* SIMULATION and SLIDESHOW */
@ -32,13 +33,16 @@ body{
-ms-user-select: none;
user-select: none;
}
#simulations canvas, #slideshow div{
#simulations canvas, #slideshow > div{
position: absolute;
}
#slideshow .box.image{
background-size: 100% 100%;
}
#slideshow .box.button{
#slideshow .next_button{
margin: 0 auto;
position: relative;
top: -10px;
width: 300px;
height: 20px;
padding: 40px 0;
@ -46,7 +50,7 @@ body{
background-size: 100% auto;
text-align: center;
}
#slideshow .box.button:hover{
#slideshow .next_button:hover{
background-position: 0 -100px;
}

View File

@ -43,6 +43,147 @@ Cursor is allowed to flow EVERYWHERE though...
</body>
</html>
<!-- - - - - - - - - - - - -->
<!-- THE SLIDESHOW'S WORDS -->
<!-- - - - - - - - - - - - -->
<!-- 0. Introduction -->
<words id="_0_title">
the
<br>
WISDOM and/or MADNESS
<br>
of CROWDS
</words>
<words id="_0_subtitle">
playing time: 30 min • by nicky case, april 2018
</words>
<words id="_0_loading">
loading...
</words>
<words id="_0_play">
let's play! &rarr;
</words>
<words id="_0_intro">
Why is it that the <i>same</i> people,
in <i>different</i> groups, can be kind, cruel, smart, stupid?
In this explorable explanation,
I'll show how the <i>network</i> of a group itself
can shape the people caught in its web.
<button onclick="slideshow.next()">NEXT</button>
</words>
<!-- 1. Networks -->
<words id="_1_tutorial_start">
blah blah blah blah blah<br>
let's make a network of friends!
</words>
<words id="_1_tutorial_connect">
draw to connect
</words>
<words id="_1_tutorial_disconnect">
scratch to&nbsp;&nbsp;&nbsp;disconnect
</words>
<words id="_1_tutorial_end">
feel free to play around! when you're done,
<next wiggle>let's continue &rarr;</next>
</words>
<words id="_1_threshold">
blah blah blah blah
blah blah blah blah
blah blah blah blah
blah blah blah blah
blah blah blah blah
thresholds NOT COUNTING THEMSELVES
</words>
<words id="_1_threshold_count1">
# of drinker friends / # of total friends
</words>
<words id="_1_threshold_count2">
% of friends are drinkers
</words>
<words id="_1_threshold_count3">
(black line shows the 50% "majority" mark)
</words>
<words id="_1_threshold_end">
<next>and next...</next>
</words>
<words id="_1_pre_puzzle">
blah blah drinking
<next>blah blah next</next>
</words>
<words id="_1_puzzle">
blah blah puzzle
</words>
<words id="_1_puzzle_metric">
HOW MANY PEEPS FOOLED:
</words>
<words id="_1_puzzle_end">
blah blah puzzle
<next wiggle>a winrar is you</next>
</words>
<words id="_1_post_puzzle">
blah blah post-puzzle
<next>simple contagion...</next>
</words>
<!-- 2. Simple Contagions -->
<!-- 3. Complex Contagions -->
<!-- 4. Bonding & Bridging -->
<!-- 5. Sandbox -->
<!-- 6. Conclusion -->
<!-- 7. Credits -->
<!-- x. misc -->
<words id="WIN">
WIN
</words>
<words id="sim_start">
start sim!
</words>
<words id="sim_next">
next day >>
</words>
<words id="sim_reset">
reset sim
</words>
<!-- - - - - - - - - - - - - -->
<!-- BONUS BOXES (footnotes) -->
<!-- - - - - - - - - - - - - -->
<bonus>
</bonus>
<!-- - - - - - -->
<!-- GLOSSARY -->
<!-- - - - - - -->
<glossary>
</glossary>
<!-- - - - - -->
<!-- SCRIPTS -->
<!-- - - - - -->
<script src="js/lib/helpers.js"></script>
<script src="js/lib/minpubsub.src.js"></script>
<script src="js/lib/Key.js"></script>
@ -70,60 +211,3 @@ Cursor is allowed to flow EVERYWHERE though...
<script src="js/main.js"></script>
<!-- - - - - - - - - - - - -->
<!-- THE SLIDESHOW'S WORDS -->
<!-- - - - - - - - - - - - -->
<!-- 0. Introduction -->
<words id="_0a">
Why is it that the <i>same</i> people,
in <i>different</i> groups, can be kind, cruel, smart, stupid?
In this explorable explanation,
I'll show how the <i>network</i> of a group itself
can shape the people caught in its web.
<button onclick="slideshow.next()">NEXT</button>
</words>
<words id="_0b">
herp derp herp derp
</words>
<!-- 1. Networks -->
<words id="_1_tutorial_start">
blah blah blah blah blah<br>
let's make a network of friends!
</words>
<words id="_1_tutorial_connect">
draw to connect
</words>
<words id="_1_tutorial_disconnect">
scratch to&nbsp;&nbsp;&nbsp;disconnect
</words>
<words id="_1_tutorial_end">
feel free to play around! when you're done,<br>
</words>
<words id="_1_tutorial_next">
let's continue &rarr;
</words>
<!-- 2. Simple Contagions -->
<!-- 3. Complex Contagions -->
<!-- 4. Bonding & Bridging -->
<!-- 5. Sandbox -->
<!-- 6. Conclusion -->
<!-- 7. Credits -->
<!-- - - - - - - - - - - - - -->
<!-- BONUS BOXES (footnotes) -->
<!-- - - - - - - - - - - - - -->
<bonus>
</bonus>
<!-- - - - - - -->
<!-- GLOSSARY -->
<!-- - - - - - -->
<glossary>
</glossary>

View File

@ -20,7 +20,11 @@ SLIDES.push(
type:"sim",
x:0, y:10,
fullscreen: true,
network: {"contagion":0,"peeps":[[44,184,0],[155,215,0],[237,105,0],[309,213,0],[646,211,0],[328,305,0],[629,308,0],[417,111,0],[539,375,0],[216,299,0],[107,311,0],[-61,220,0],[87,452,0],[733,147,0],[760,293,0],[753,448,0],[744,46,0],[134,33,0],[929,181,0],[848,111,0],[1013,330,0],[880,269,0],[538,128,0],[208,391,0],[853,356,0]],"connections":[[5,6]]}
network: {
"contagion":0,
"peeps":[[44,184,0],[155,215,0],[237,105,0],[309,213,0],[646,211,0],[328,305,0],[629,308,0],[417,111,0],[539,375,0],[216,299,0],[107,311,0],[-61,220,0],[87,452,0],[733,147,0],[760,293,0],[753,448,0],[744,46,0],[134,33,0],[929,181,0],[848,111,0],[1013,330,0],[880,269,0],[538,128,0],[208,391,0],[853,356,0]],
"connections":[[5,6]]
}
},
// "Connect" instruction (words & picture)
@ -52,18 +56,8 @@ SLIDES.push(
type:"box",
id:"end_words",
text:"_1_tutorial_end", x:230, y:425, w:500, h:70, align:"center",
hidden:true
},
{
type:"box",
id:"end_button",
button:"large", wiggle:true,
text:"_1_tutorial_next", x:330, y:440,
hidden:true,
onclick:function(){
slideshow.next();
}
},
//hidden:true
}
],
@ -93,7 +87,7 @@ SLIDES.push(
// If did both, show end
if(state.canConnect && state.canDisconnect){
boxes.showChildByID("end_words");
boxes.showChildByID("end_button");
//boxes.showChildByID("end_button");
}
// update # of connections in state
@ -104,20 +98,115 @@ SLIDES.push(
},
// PLAY AROUND: how the "threshold" model workds
// diagonal
{
chapter: "Networks-Threshold",
clear:true,
add:[
// TEXT
{
type:"box",
id:"_1_threshold",
text:"_1_threshold", x:80, y:25, w:300
},
{
type:"box",
id:"_1_threshold_end",
text:"_1_threshold_end", x:80, y:400, w:300
},
// SIMULATION: THRESHOLD
{
type:"sim",
x:400, y:70,
fullscreen: true,
network: {
"contagion":0.5,
"peeps":[[95,65,0],[417,380,1],[52,340,0],[399,92,1]],
"connections":[[2,3],[3,1]],
},
options:{
infectedFrame: 2,
scale: 2
}
}
]
},
// PUZZLE: The "Majority Illusion" puzzle
// pre-puzzle ramble
{
clear:true
remove:[
{ type:"box", id:"_1_threshold" },
{ type:"box", id:"_1_threshold_end" }
],
add:[
{
type:"box",
id:"_1_pre_puzzle",
text:"_1_pre_puzzle", x:80, y:25, w:325, h:540
}
]
},
// post-puzzle ramble
// PUZZLE: The "Majority Illusion" puzzle
{
clear:true
chapter: "Networks-Majority",
clear:true,
add:[
// The puzzle!
{
id:"puzzle",
type:"sim",
x:480-250, y:25,
fullscreen: true,
network: {
"contagion":0.5,
"peeps":[[106,106,1],[239,52,1],[376,110,1],[27,221,0],[54,365,0],[162,458,0],[308,467,0],[407,371,0],[453,241,0]],
"connections":[],
},
options:{
infectedFrame: 2,
scale: 1.5
}
},
// Done? Let's go... (hidden at first...)
{
type:"box",
id:"_1_puzzle_end",
text:"_1_puzzle_end", x:680, y:430, w:300, align:"center"
//hidden:true
}
]
},
// post-puzzle ramble, introduce simple contagion
{
remove:[
{ type:"box", id:"_1_puzzle_end" }
],
move:[
// shift sim to side
{type:"sim", id:"puzzle", x:0}
],
add:[
// new text
{
type:"box",
id:"_1_post_puzzle",
text:"_1_post_puzzle", x:600, y:0, w:300
}
]
}
);

View File

@ -0,0 +1,7 @@
// 0 - INTRODUCTION
SLIDES.push(
{
chapter: "Simple",
clear:true
}
);

View File

@ -0,0 +1,6 @@
// 0 - INTRODUCTION
SLIDES.push(
{
chapter: "Complex"
}
);

View File

@ -0,0 +1,6 @@
// 0 - INTRODUCTION
SLIDES.push(
{
chapter: "BB"
}
);

View File

@ -0,0 +1,6 @@
// 0 - INTRODUCTION
SLIDES.push(
{
chapter: "Sandbox"
}
);

View File

@ -0,0 +1,6 @@
// 0 - INTRODUCTION
SLIDES.push(
{
chapter: "Conclusion"
}
);

View File

@ -0,0 +1,6 @@
// 0 - INTRODUCTION
SLIDES.push(
{
chapter: "Credits"
}
);

View File

@ -45,4 +45,10 @@ function cloneObject(obj){
// Get words
function getWords(wordsID){
return $("words#"+wordsID).innerHTML;
}
}
// Remove from array
function removeFromArray(array, item){
var index = array.indexOf(item);
if(index>=0) array.splice(index,1);
}

View File

@ -21,14 +21,9 @@ function Connection(config){
// Draw
self.draw = function(ctx){
/*
ctx.strokeStyle = "#444";
ctx.lineWidth = self.uncuttable ? 6 : 3; // thick=uncuttable
ctx.beginPath();
ctx.moveTo(self.from.x, self.from.y);
ctx.lineTo(self.to.x, self.to.y);
ctx.stroke();
*/
var s = self.sim.options.scale || 1;
ctx.save();
ctx.translate(self.from.x, self.from.y);
var dx = self.to.x - self.from.x;
@ -37,9 +32,11 @@ function Connection(config){
var dist = Math.sqrt(dx*dx + dy*dy);
self.sprite.scaleX = dist/300;
self.sprite.scaleY = self.uncuttable ? 1 : 0.5; // thick=uncuttable
//self.sprite.scaleY *= s;
self.sprite.rotation = a;
self.sprite.draw(ctx);
ctx.restore();
};
// Hit Test with a LINE SEGMENT

View File

@ -95,14 +95,9 @@ function ConnectorCutter(config){
// Connecting!
if(self.state==1){
/*ctx.strokeStyle = "#ccc";
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(self.connectFrom.x, self.connectFrom.y);
ctx.lineTo(self.connectTo.x, self.connectTo.y);
ctx.stroke();*/
var tempConnection = new Connection({
from:self.connectFrom, to:self.connectTo
from:self.connectFrom, to:self.connectTo,
sim:self.sim
});
ctx.save();
ctx.globalAlpha = 0.5;

View File

@ -1,8 +1,3 @@
var PEEP_STATE_COLORS = {
1: "#ccc",
2: "#dd4040"
};
function Peep(config){
var self = this;
@ -52,6 +47,21 @@ function Peep(config){
if(friend.infected) self.numInfectedFriends++;
});
// Past threshold?
self.isPastThreshold = false;
if(self.sim.contagion==0){
// simple
if(self.numInfectedFriends>0) self.isPastThreshold = true;
}else{
// complex
if(self.numFriends>0){
var ratio = self.numInfectedFriends/self.numFriends;
if(ratio>=self.sim.contagion-0.0001){ // floating point errors
self.isPastThreshold = true;
}
}
}
};
// Body Sprite
@ -61,22 +71,50 @@ function Peep(config){
});
self.sprite.pivotX = 100;
self.sprite.pivotY = 100;
self.sprite.scale = 0.3;
var _initSpriteScale = 0.3;
self.sprite.scale = _initSpriteScale;
//self.sprite.gotoFrame(1);
// Draw
var radius = 25;
var barWidth = 30;
var barWidth = radius*1.75;
var barHeight = 10;
var bodyRotation = Math.TAU*Math.random();
var PEEP_COLORS = [
"#B4B4B4", // gray
"#F73C50", // red
"#FEE576", // yellow
"#86F5FB", // blue
"#7DE74E", // green
"#FBCBDC" // pink
];
self.draw = function(ctx){
ctx.save();
ctx.translate(self.x, self.y);
var s;
if(s = self.sim.options.scale) ctx.scale(s,s);
// Circle
var infectedFrame = self.sim.options.infectedFrame || 1;
var infectedColor = PEEP_COLORS[infectedFrame];
var myFrame = self.infected ? infectedFrame : 0;
var myColor = PEEP_COLORS[myFrame];
self.sprite.rotation = bodyRotation;
self.sprite.gotoFrame(self.infected ? 1 : 0);
if(self.isPastThreshold){ // highlight!
ctx.globalAlpha = 0.4;
self.sprite.scale = _initSpriteScale*1.25;
self.sprite.gotoFrame(infectedFrame);
self.sprite.draw(ctx);
ctx.globalAlpha = 1;
self.sprite.scale = _initSpriteScale;
}
self.sprite.gotoFrame(myFrame);
self.sprite.draw(ctx);
// Face
@ -91,63 +129,57 @@ function Peep(config){
// LABEL FOR INFECTED/FRIENDS, BAR, AND CONTAGION LEVEL //
//////////////////////////////////////////////////////////
// DON'T show bar if simple contagion
if(self.sim.contagion>0){
ctx.save();
// Say: Infected/Friends
ctx.translate(0,-42);
var labelNum = self.numInfectedFriends+"/"+self.numFriends;
var labelPercent = "";
if(self.numFriends>0){
labelPercent = Math.round(100*(self.numInfectedFriends/self.numFriends)) + "%";
}
ctx.font = '12px sans-serif';
ctx.fillStyle = myColor;
ctx.textAlign = "center";
var bgColor = "#eee";
var uiColor = "#666";
// Say: Infected/Friends (% then n/n)
ctx.translate(0,-43);
ctx.font = '10px FuturaHandwritten';
ctx.fillStyle = uiColor;
ctx.textBaseline = "middle";
ctx.fontWeight = "bold";
ctx.fillText(labelNum, 0, 0);
ctx.textAlign = "center";
if(self.numFriends>0){
var labelNum = self.numInfectedFriends+"/"+self.numFriends;
var labelPercent = Math.round(100*(self.numInfectedFriends/self.numFriends)) + "%";
var label = labelNum + "=" + labelPercent;
ctx.fillText(label, 0, 0);
}else{
ctx.fillText("∅", 0, -1);
}
// A nice bar
ctx.translate(0,12);
ctx.lineWidth = 1;
// the white fill
ctx.fillStyle = "#fff";
// the gray bg
ctx.translate(0,10);
ctx.fillStyle = bgColor;
ctx.beginPath();
ctx.rect(-barWidth/2, -barHeight/2, barWidth, barHeight);
ctx.fill();
// The color fills
// the color fill
if(self.numFriends>0){
ctx.fillStyle = PEEP_STATE_COLORS[2]; // state = 2 infected
ctx.fillStyle = infectedColor;
ctx.beginPath();
ctx.rect(-barWidth/2, -barHeight/2, barWidth*(self.numInfectedFriends/self.numFriends), barHeight);
ctx.fill();
}
// The outline
ctx.strokeStyle = myColor;
ctx.beginPath();
if(self.numFriends>0){
ctx.rect(-barWidth/2, -barHeight/2, barWidth, barHeight);
}else{
ctx.rect(-barWidth/2, 0, barWidth, 0);
}
ctx.stroke();
// a pointer for contagion level
ctx.translate(0, barHeight/2+2);
self._drawThreshold(ctx, self.sim.contagion);
// Percent
ctx.font = '8px sans-serif';
ctx.fillStyle = "rgba(0,0,0,0.8)";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fontWeight = "bold";
ctx.fillText(labelPercent, 0, -6);
ctx.translate(0, -barHeight/2);
ctx.save();
ctx.translate(barWidth*self.sim.contagion - barWidth/2, 0);
ctx.lineCap = "butt";
ctx.strokeStyle = uiColor;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(0,barHeight);
ctx.stroke();
ctx.restore();
ctx.restore();
@ -156,19 +188,6 @@ function Peep(config){
ctx.restore();
};
self._drawThreshold = function(ctx, threshold){
ctx.save();
ctx.translate(barWidth*threshold - barWidth/2, 0);
ctx.strokeStyle = "#000"; //PEEP_STATE_COLORS[2];
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(0,-14);
ctx.stroke();
ctx.restore();
}
// Hit Test
self.hitTest = function(x,y,buffer){
@ -177,6 +196,8 @@ function Peep(config){
var dy = self.y-y;
var dist2 = dx*dx+dy*dy;
var r = radius+buffer;
var s;
if(s = self.sim.options.scale) r*=s;
return (dist2<r*r);
};

View File

@ -45,6 +45,17 @@ function Simulations(){
});
};
///////////////////////
// HELPERS AND STUFF //
///////////////////////
// Get Child!
self.getChildByID = function(id){
return self.sims.find(function(sim){
return sim.id==id;
});
};
}
function Sim(config){
@ -53,17 +64,17 @@ function Sim(config){
self.config = config;
self.networkConfig = config.network;
self.container = config.container;
self.options = config.options || {};
self.id = config.id;
// Canvas
self.fullscreenOffset = {x:0, y:0};
if(config.fullscreen){
var container = $("#simulations_container");
var simOffset = self.container.dom.getBoundingClientRect();
self.canvas = createCanvas(container.clientWidth, container.clientHeight);
self.canvas.style.left = -simOffset.x;
self.canvas.style.top = -simOffset.y;
self.fullscreenOffset.x = config.x + simOffset.x;
self.fullscreenOffset.y = config.y + simOffset.y;
}else{
self.canvas = createCanvas(config.width||500, config.height||500);
self.canvas.style.left = config.x || 0;
@ -122,10 +133,12 @@ function Sim(config){
self.mouse.lastX -= canvasBounds.x;
self.mouse.lastY -= canvasBounds.y;
if(config.fullscreen){
self.mouse.x -= self.fullscreenOffset.x;
self.mouse.y -= self.fullscreenOffset.y;
self.mouse.lastX -= self.fullscreenOffset.x;
self.mouse.lastY -= self.fullscreenOffset.y;
var fullscreenOffsetX = config.x + simOffset.x;
var fullscreenOffsetY = config.y + simOffset.y;
self.mouse.x -= fullscreenOffsetX;
self.mouse.y -= fullscreenOffsetY;
self.mouse.lastX -= fullscreenOffsetX;
self.mouse.lastY -= fullscreenOffsetY;
}
// Connector-Cutter
@ -161,7 +174,9 @@ function Sim(config){
ctx.save();
ctx.scale(2,2);
if(config.fullscreen){
ctx.translate(self.fullscreenOffset.x, self.fullscreenOffset.y);
var fullscreenOffsetX = config.x + simOffset.x;
var fullscreenOffsetY = config.y + simOffset.y;
ctx.translate(fullscreenOffsetX, fullscreenOffsetY);
}
// Draw all of it!
@ -234,7 +249,11 @@ function Sim(config){
var toIndex = self.peeps.indexOf(c.to);
savedNetwork.connections.push([fromIndex, toIndex]);
});
return JSON.stringify(savedNetwork);
return '{\n'+
'\t"contagion":'+savedNetwork.contagion+",\n"+
'\t"peeps":'+JSON.stringify(savedNetwork.peeps)+",\n"+
'\t"connections":'+JSON.stringify(savedNetwork.connections)+",\n"+
'}';
};
////////////////
@ -249,7 +268,7 @@ function Sim(config){
};
self.removePeep = function(peep){
self.removeAllConnectedTo(peep); // delete all connections
self.peeps.splice(self.peeps.indexOf(peep),1); // BYE peep
removeFromArray(self.peeps, peep); // BYE peep
};
self.addConnection = function(from, to, uncuttable){

View File

@ -48,10 +48,21 @@ function Boxes(){
box.style.backgroundImage = "url("+config.img+")"
}
// button:
if(config.button){
box.classList.add("button");
if(config.onclick) box.onclick = config.onclick;
// Replace "next" buttons!
var next;
if(next = box.querySelector("next")){
// Create next button
var nextButton = document.createElement("div");
nextButton.className = "next_button";
nextButton.innerHTML = next.innerHTML;
nextButton.onclick = function(){
slideshow.next();
};
// Replace it in parent!
next.parentNode.replaceChild(nextButton, next);
}
// Add to array
@ -80,5 +91,10 @@ function Boxes(){
var toHide = self.getChildByID(id);
toHide.style.display = "none";
};
self.removeChildByID = function(id){
var removeBox = self.getChildByID(id);
self.dom.removeChild(removeBox);
removeFromArray(self.boxes, removeBox);
};
}

View File

@ -32,7 +32,36 @@ function Slideshow(){
// Clear?
if(slide.clear) self.clear();
// Remove stuff
slide.remove = slide.remove || [];
slide.remove.forEach(function(childConfig){
switch(childConfig.type){
case "box":
self.boxes.removeChildByID(childConfig.id);
break;
case "sim":
//self.simulations.removeChildByID(childConfig);
break;
}
});
// Move stuff
slide.move = slide.move || [];
slide.move.forEach(function(childConfig){
switch(childConfig.type){
case "box":
//self.boxes.add(childConfig);
break;
case "sim":
var sim = self.simulations.getChildByID(childConfig.id);
sim.config.x = (childConfig.x===undefined) ? sim.config.x : childConfig.x;
sim.config.y = (childConfig.y===undefined) ? sim.config.y : childConfig.y;
break;
}
});
// Add stuff
slide.add = slide.add || [];
slide.add.forEach(function(childConfig){
switch(childConfig.type){
case "box":

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 240 KiB