Created: March 4th, 2015

0

Problem:

In studio based environment, sometimes, it is hard for “designer” to gather ideas from a huge amount of people to choose a “best design concept”. It is time consuming, it limited the number of concepts as well as people who can involved in the process to help designer choose their favorite or least favorite ideas.

Solution:

PinBox is a system that allow “designers” to outsource their user preference test campus wide, gather people's preferences quickly, efficiently and quantitatively.

Why PinBOX?

We use “robot character” boxes with the face of happy and unhappy as the “voting box”. We also got the inspiration from Pinterest, you will pin the one you like in it, so we just simulate the movement of like and dislike as “PIN”, it also seems like the sound when press the button, so that is kind of where our product’ name come from.

Scenario

0

Working

People who designed the project can get feedback and some suggestion using this product. We have two little box--figure, which have smiley face and upset face on it symbolizing like or dislike. The button on the two figures are connected to Spark core and our website, which will show all the designs from the student. Pressing on either like or dislike button will lead you to another page, and there are 8 pages in total. After all the quick feedback phase ends, the data of the results will be shown on the website, so the person will know which design people like most, which least. In this way, he/she can get a better understanding on how to revise the idea, and make it better.

0

Two box-figures

Function:

The figures eyes are made of two buttons, which are red and white. The red one symbolizing dislike, which has the upset face on it. The other on has a smiley face on it meaning like. These two boxes are also the container of all the other components. It has the sensors, battery and spark core inside.

0

Materials Required:

SparkCore *1

Breadboard *1

Red LED *1

Buttons (Massive Arcade Button with LED - 100mm Red) * 1

Buttons (Massive Arcade Button with LED - 100mm White) * 1

1kΩ Resistor *5

Small Signal NPN Transistor *2

Jump Wires * Several

5V Battery 

0

Prototype

0

Button Code

0
//pins
int ledPinYes = A0;
int ledPinNo = A1;
int buttonPinYes = D3;
int buttonPinNo = D4;
int ledPin = D0; //use to show button availability

//status
int buttonStateYes = 0;
int buttonStateNo = 0;
int available = 1;
bool buttonReleased = true;

void setup() {
  //buttons
  pinMode(buttonPinYes, INPUT_PULLDOWN);
  pinMode(buttonPinNo, INPUT_PULLDOWN);
  //leds
  pinMode(ledPinYes, OUTPUT);
  pinMode(ledPinNo, OUTPUT);
  pinMode(ledPin, OUTPUT);
  //wait for webpage to reset the button availability
  Spark.function("reset", reset);
}

void loop() {
  buttonStateYes = digitalRead(buttonPinYes);
  buttonStateNo = digitalRead(buttonPinNo);
  digitalWrite(ledPin, LOW);

  if(!buttonReleased && buttonStateYes == 0 && buttonStateNo == 0){
    buttonReleased = true;
  }

  if(available == 1 && buttonReleased){
    digitalWrite(ledPin, HIGH);

    if (buttonStateYes == 1) {
      digitalWrite(ledPinYes, HIGH);
      Spark.publish("vote/yes");
      available = 0;
      buttonReleased = false;
    }

    if (buttonStateNo == 1){
      digitalWrite(ledPinNo, HIGH);
      Spark.publish("vote/no");
      available = 0;
      buttonReleased = false;
    }
  }
}

//wait for webpage to reset the button availability
int reset(String Args){
  available = 1;
  return 1;
}
Click to Expand
0

Website Development

The website will show people's work in a sequence and also  record their vote

Function:

Showing different pictures of concepts of the original design, to let people choose from. And give out the final data analysis at the end.

Interface Function:

Given all the design concepts

Show your response of the design

Show your response history

Show how other concepts as a contrast

Give the final results of responses

0

Website Code

0
<!DOCTYPE html>
<html>
	<head>
		<title>Pin Box</title>
		<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
		<style>
			
			*{
				font-family: Helvetica;
			}


			body{
				width: 100%;
				margin: 0;
				padding: 0;
			}



			p#num{
				position: absolute;
				top: 40px;
				left: 40px;
				font-size: 18px;
				padding: 10px;
				color: white;
				background: #a4c6e4;
				border-radius: 2px;
			}

			div#img, div#info-graphic{
				position: fixed;
				top: 0;
				left: 0;
				width: 100vw;
				
			}

			div#img{
				margin: 2vh 0;
				height: 76vh;
				background-size: contain;
				background-repeat: no-repeat;
				background-position: 50% 50%;
			}

			div#info-graphic{
				display: none;
				height: 80vh;
			}

			div#yes, div#no{
				position: fixed;
				left: 50vw;
				top: 40vh;
				width: 200px;
				font-size: 72px;
				line-height: 80px;
				height: 80px;
				margin-left: -102px;
				margin-top: -42px;
				border-radius: 5px;
				text-align: center;
				background: white;
				border: 2px solid;
				display: none;
			}

			div#yes{ 
				color: #4dac16;
				border-color: #4dac16;
			} 
			div#no{ 
				color: #e64959;
				border-color: #e64959; 
			}
			

			div#bottom{
				width: 100%;
				height: 20vh;
				background: #a4c6e4;
				position: fixed;
				bottom: 0;
			}

			div#bottom div.thumb, div#info-graphic div.bar{
				position: relative;
				float: left;
				margin: 2vh 0 0 2vw;
				font-size: 24px;
				line-height: 40px;
				text-align: center;
				color: white;
			}

			div#info-graphic div.bar{
				background: #80b3e4;
				margin-top: 0;
			}

			div#info-graphic div.bar#highest-vote{
				background: #60e66a;
			}

			div#bottom div.thumb{
				background: white;
				background-size: contain;
				background-repeat: no-repeat;
				background-position: 50% 50%;
				height: 16vh;
			}

			div#thumb-highlight{
				position: absolute;
				margin: 2vh 0 0 2vw;
				height: 16vh;
				border: 4px solid #e5a033;
				transition: left 0.6s ease-in-out;
				-webkit-transform: translate(-4px, -4px);
				transform: translate(-4px, -4px);
			}
			

			div#bottom div.thumb div.yes-icon,
			div#bottom div.thumb div.no-icon{
				position: relative;
				left: 50%;
				top: 50%;
				width: 6vw;
				height: 6vw;
				margin-top: -3vw;
				margin-left: -3vw;
				background-size: contain;
				background-repeat: no-repeat;
			}

			div#bottom div.thumb div.yes-icon{
				background-image: url('yes.png');
			}

			div#bottom div.thumb div.no-icon{
				background-image: url('no.png');
			}

		</style>

		<script>

    		var SparkCore = function(deviceID, accessToken, name){
						this.deviceID = deviceID;
						this.accessToken = accessToken;
						this.name = name;
					};
				
			// @para x2: String, Function
			// @para x1: Object { String: Function [,String: Function] [,...] }
			SparkCore.prototype.subscribe = function(name, handler) {

				// if there is no event has been registered on this Spark
				if(!this.eventSource){
					this.eventSource = new EventSource("https://api.spark.io/v1/devices/" + this.deviceID + "/events/?access_token=" + this.accessToken);

					console.log('event source created on:', this.deviceID, this.accessToken);

					this.subscribe('error', function(e){console.log(e)});
				}

				//if passed in event, register it
				if(typeof name === 'string'){

					console.log('event registered:', name);

					this.eventSource.addEventListener(name, handler, false);
				}
					
				//if passed in objects, dissemble them and call subscribe again
				else if(typeof name === 'object')
					for (n in name){
						if(name.hasOwnProperty(n)){
							this.subscribe(n, name[n]);
						}
					}
				return this;
	    	};

	    	SparkCore.prototype.callFn = function(name, args){

	    		var url = "https://api.spark.io/v1/devices/" + this.deviceID + "/" + name + 
		                        "?access_token=" + this.accessToken;

		        $.post( url ,  { "args": args });

		        return this;
    		};

    		function addArray(sum, add){
    			console.log(sum, add);
				if(sum.length !== add.length) return;

				return sum.map(function(v, i){
					return v + add[i];
				})
			}


	    </script>

	</head>


	<body>


		<div id="img"></div>
		<div id="info-graphic"></div>
		<p id="num"></p>
		<div id="yes">YES</div>
		<div id="no">NO</div>
		<div id="bottom"></div>




		<script>

			var url = "REPLACE_NAME.jpg",
				spark = new SparkCore('54ff67066678574939510867', '2d07a5c34425054c40c827f3f8bb70e8933d797f'),
				switchDelay = 1000,


				imgGrp = [{
					'pfx': 'clock',
					'num': 8,
					'voteSum': [7, 3, 2, 4, 3, 2, 4, 1] // dummy data for demo
				},{
					'pfx': 'zimg',
					'num': 6,
					'voteSum': [2, 4, 3, 2, 4, 8] // dummy data for demo
				}],

				nowImgGrp = 0,
				nowImg = 0,
				voteBuffer = [],
				showInfoGraphic = false,

				model = {
						addVoteToSum: function addVoteToSum(){
							console.log(imgGrp[nowImgGrp].voteSum);
							imgGrp[nowImgGrp].voteSum = addArray(imgGrp[nowImgGrp].voteSum, voteBuffer);
							console.log(imgGrp[nowImgGrp].voteSum);

							return this;
						}
					},

				view = {
						showInfoGraphic: function showInfoGraphic(){
								$('#img').hide();
								$('#info-graphic').show();
								return this;
							},

						hideInfoGraphic: function hideInfoGraphic(){
								//$('#img').css('display', 'block');
								$('#img').show();
								$('#info-graphic').hide();
								return this;
							},

						replaceMainImage: function replaceMainImage(){
								if(nowImg < imgGrp[nowImgGrp].num){
									$('#img').css(
										'background-image', 
										'url(' + url.replace('REPLACE_NAME', imgGrp[nowImgGrp].pfx + nowImg ) + ')'
										);
								}
								
								return this;
							},

						dynamicThumbnailWidth: function(){
								return (100 - ( imgGrp[nowImgGrp].num + 1 )*2)/imgGrp[nowImgGrp].num
							},

						updateNumTag: function updateNumTag(){
								if(nowImg < imgGrp[nowImgGrp].num ){
									$('#num').show();
									$('#num').html((nowImg+1) + '/' + imgGrp[nowImgGrp].num);
								}else{
									$('#num').hide();
								}
								return this;
							}, 
					
						updateBottom: function updateBottom(){

								if(nowImg == 0){
									$('#bottom').empty();

									// create highlight border
									$('<div>')
										.attr('id', 'thumb-highlight')
										.css({
											'left': 0,
											'width':  view.dynamicThumbnailWidth() + 'vw'
										})
										.appendTo($('#bottom'))


									// create thumbnails
									for(var i = 0; i< imgGrp[nowImgGrp].num; i++){

										$('<div>')
											.attr('class', 'thumb')
											.css({
												'background-image': 'url(' + url.replace('REPLACE_NAME', imgGrp[nowImgGrp].pfx + i) + ')',
												'width': view.dynamicThumbnailWidth()  + 'vw'
											})
											.appendTo($('#bottom'));
									}


								}else if(nowImg < imgGrp[nowImgGrp].num){

									$('#thumb-highlight').css('left',
										nowImg * ( 2 + (100 - ( imgGrp[nowImgGrp].num + 1 )*2)/imgGrp[nowImgGrp].num) + 'vw'
									);

								}else{

									$('#thumb-highlight').hide();
								}

								return this;
							
							},

						generateInfoGraphic: function generateInfoGraphic(){
								var i,
									maxVote = Math.max.apply( null, imgGrp[nowImgGrp].voteSum );
									mapVote = function(vote){
										return vote/maxVote * 60;
									}

								for(i = 0; i < imgGrp[nowImgGrp].num ; i++) {
									$('<div>')
										.css({
											'height': mapVote(imgGrp[nowImgGrp].voteSum[i]) + 'vh',
											'width': view.dynamicThumbnailWidth() + 'vw',
											'top': (80 - mapVote(imgGrp[nowImgGrp].voteSum[i])) + 'vh'
										})
										.attr({
											'class': 'bar',
											'id': imgGrp[nowImgGrp].voteSum[i] === maxVote ? 'highest-vote' : undefined
										})
										.html( imgGrp[nowImgGrp].voteSum[i] )
										.appendTo($('#info-graphic'))
									
								}
								
								return this;
							},

						clearInfoGraphic: function clearInfoGraphic(){
								$('#info-graphic').empty();
							}
					};

			spark.subscribe({
				"vote/yes": voteYesHandler,
				"vote/no": voteNoHandler
			})

			function voteYesHandler(){
				if( nowImg < imgGrp[nowImgGrp].num){

					// debug
					console.log('Voted Yes');

					// model
					voteBuffer.push(1);

					// view
					$('#yes')
						.css('display', 'block');

					$('<div>')
						.attr('class', 'yes-icon')
						.appendTo($('#bottom .thumb:nth-child(' + (nowImg+2) + ')'));
				}

				// controller
				goNext();
			}

			function voteNoHandler(){
				if( nowImg < imgGrp[nowImgGrp].num){
					// debug
					console.log('Voted No');

					// model
					voteBuffer.push(0);

					// view
					$('#no').css('display', 'block');

					$('<div>')
						.attr('class', 'no-icon')
						.appendTo($('#bottom .thumb:nth-child(' + (nowImg+2) + ')'));
				}

				// controller
				goNext();
			}

			function goNext(){
				// model
				nowImg++;

				// end a critic:
				// process data -> show info graphic
				if(nowImg === imgGrp[nowImgGrp].num){
					
					imgGrp[nowImgGrp].votes = imgGrp[nowImgGrp].votes || [];
					imgGrp[nowImgGrp].votes.push(voteBuffer);

					model.addVoteToSum();

					//clear vote buffer
					voteBuffer = [];
					
					setTimeout(function(){
							view
							.generateInfoGraphic()
							.showInfoGraphic();	
						}, switchDelay)
					

				// end a group:
				// hide info graphic -> show next group
				}else if( nowImg === ( imgGrp[nowImgGrp].num + 1 ) ){

					nowImg = 0;
					nowImgGrp++;

					//end all critic
					if( nowImgGrp === imgGrp.length ){
						nowImgGrp = 0;
					}

					view
						.hideInfoGraphic()
						.clearInfoGraphic()
				}

				setTimeout(goNextExec, nowImg > 0 ? switchDelay : 0);
			}


			function goNextExec(){

				spark.callFn('reset');

				//view
				$('#yes, #no')
					.hide();

				view
					.replaceMainImage()
					.updateNumTag()
					.updateBottom()

			}
			
			
			function init(){
				view
					.replaceMainImage(imgGrp[nowImgGrp].pfx + nowImg)
					.updateNumTag()
					.updateBottom();
			}


			init();
			
			
		</script>
	</body>
</html>
Click to Expand
0

Video Demo

0
x