GarrettRauck-Final Project

sketch

//Garrett Rauck
//Section C
//grauck@andrew.cmu.edu
//Assignment-07a-Bar Chart

// prevent scrolling of page
// from "Single Touch with P5js" on coursescript.com
// http://coursescript.com/notes/interactivecomputing/mobile/touch/
document.ontouchmove = function(event){
  event.preventDefault();
}

/////////////////////////////////
// INIT VARIABLES
/////////////////////////////////
//MODEL
var mode;
var selection;
//temp node
var tempNodeX, tempNodeY;
var tempNodeContent = "";
//data
var nodes = [];
var links = [];
//VIEW
//canvas vars
var canvasWidth, canvasHeight;
//colors
var colorBG, colorNode, colorSelection, colorLink;
//font
var font, fontSize;


/////////////////////////////////
// NODE CLASS
/////////////////////////////////
//CREATE NEW NODE, called when user clicks the canvas
function newNode(xIn, yIn) {
	//update app state
	mode = "GETTING NODE CONTENT";
	//store temporary node location
	tempNodeX = xIn;
	tempNodeY = yIn;
	tempNodeContent = "";
}

function saveNewNode() {
	println("SAVE NEW NODE!");
	//get bounding box for node
	var bounds = fontRegular.textBounds(
		tempNodeContent, tempNodeX, tempNodeY, fontSize);
	var offset = 5;
	//create new node instance using temp data
	var newNode = {x: tempNodeX,
	               y: tempNodeY,
	               content: tempNodeContent,
	               left: bounds.x - offset,
	               right: bounds.x + bounds.w + offset,
	               top: bounds.y - offset,
	               bottom: bounds.y + bounds.h + offset,
	               move: moveNode,
	               draw: drawNode};
	//store the new node
	nodes.push(newNode);
	//update app state
	mode = "MAP";
}

function abortNewNode() {
	println("ABORT NEW NODE!");
	tempNodeContent = "";
	mode = "MAP";
}

//MOVE SPECIFIED NODE
function moveNode(dx,dy) {
	this.x += dx;
	this.y += dy;
	this.left += dx;
	this.top += dy;
	this.right += dx;
	this.bottom += dy;
}

//DRAW SPECIFIED NODE
function drawNode() {
	if (this == selection) {
		//draw node border
		setDrawingSettings("NODE BORDER SELECTION");
		rect(this.left,this.top,this.right,this.bottom);
		//draw node text
		setDrawingSettings("NODE TEXT SELECTION");
		text(this.content,this.x,this.y);
	}
	else {
		//draw node border
		setDrawingSettings("NODE BORDER");
		rect(this.left,this.top,this.right,this.bottom);
		//draw node text
		setDrawingSettings("NODE TEXT");
		text(this.content,this.x,this.y);
	}
}


/////////////////////////////////
// LINK CLASS
/////////////////////////////////
//CREATE LINK BETWEEN TWO NODES
function newLink(fromIn, toIn) {
	var newLink = {from: fromIn,
	               to: toIn,
	               draw: drawLink};
	links.push(newLink);
}

function drawLink() {
	setDrawingSettings("LINK");
	line(this.from.x, this.from.y, this.to.x, this.to.y);
}

/////////////////////////////////
// VIEW
/////////////////////////////////
function setDrawingSettings(setting) {
	if (setting == "NODE TEXT") {
		noStroke();
		fill(colorNode);
		textAlign(CENTER);
	}
	else if (setting == "NODE TEXT SELECTION") {
		noStroke();
		fill(colorSelection);
		textAlign(CENTER);
	}
	else if (setting == "NODE BORDER") {
		stroke(colorNode);
		fill(colorBG);
	}
	else if (setting == "NODE BORDER SELECTION") {
		stroke(colorSelection);
		fill(colorBG);
	}
	else if (setting == "LINK") {
		stroke(colorLink);
		noFill();
	}
}

//DRAW TEMPORARY NODE WHILE EDITING
function drawTempNode() {
	//drawing settings
	setDrawingSettings("NODE TEXT SELECTION");
	//draw text
	text(tempNodeContent, tempNodeX, tempNodeY);
}

//DRAW ALL NODES IN MAP
function drawAllNodes() {
	for (i=0; i<nodes.length; i++) {
		node = nodes[i];
		node.draw();
	}
}

function drawAllLinks() {
	for (i=0; i<links.length; i++) {
		link = links[i];
		link.draw();
	}
}

/////////////////////////////////
// CONTROLLER
/////////////////////////////////
//CHECK CLICK IS ON EXISTING NODE, RETURN THAT NODE
function clickIsNode() {
	//for each existing node...
	for (i=0; i<nodes.length; i++) {
		var node = nodes[i];
		//check if click was within the node bounding box
		if ((node.left <= mouseX) & (mouseX <= node.right) &&
			(node.top <= mouseY) && (mouseY <= node.bottom)) {
			//return the node object
			return node;
		}
	}
	return false;
}

function clickIsCanvas() {
	if ((mouseX > 0) & (mouseX < width) &&
		(mouseY > 0) && (mouseY < height)) {
		return true;
    }
    else return false;
}

function mousePressedMap() {
	//if node is already selected...
	if (selection) {
		//if user clicks the blank canvas, deselect
		if (! clickIsNode()) {
			println("DESELECT!");
			selection = null;
		}
		//else if user clicks another node, create link
	    else if (clickIsNode()) {
	    	if (keyCode == 16) { //shift-click another node, create link
	    		println("NEW LINK!");
	    		var secondSelection = clickIsNode();
	    		newLink(selection,secondSelection);
	    		//clear selection
	    		selection = null;
	    	}
	    	else selection = clickIsNode();	
	    }
	}
	//if nothing selected...
	else {
		//if user clicks an existing node, select that node
		if (clickIsNode()) {
			println("NODE SELECTED!");
			selection = clickIsNode(); // <-- figure out a more efficient method...
		}
		else if (! clickIsNode()) {
			//initialize new node
			println("NEW NODE!");
		    newNode(mouseX,mouseY);
		}
	}
}

function mousePressed() {
	if (mode == "MAP") {
		mousePressedMap();
	}
}

function mouseDraggedMap() {
	//if user is dragging a selected node
	if (selection) {
		//get drag data
		var dx = mouseX - pmouseX;
		var dy = mouseY - pmouseY;
		//move selected node
		selection.move(dx,dy);
	}
}

function mouseDragged() {
	if (mode == "MAP") {
		mouseDraggedMap();
	}
}

function keyPressedMap() {
	if (keyCode == 27) selection = null;
}

function keyPressedGettingNodeContent() {
	//"ENTER", save new node
	if (keyCode == 13) saveNewNode();
	//"ESCAPE", abort new node
	else if (keyCode === 27) abortNewNode();
	//"BACKSPACE", delete previous character
	else if (keyCode === 8) {
		tempNodeContent = 
			tempNodeContent.substring(0, tempNodeContent.length-1);
	}
	//Other character...
	else tempNodeContent += key;
}

function keyPressed() {
	println("key = " + key);
	println("keyCode = " + keyCode);

	if (mode == "MAP") {
		keyPressedMap();
	}
	else if (mode == "GETTING NODE CONTENT") {
		keyPressedGettingNodeContent();
	}
}

/////////////////////////////////
// RUN!
/////////////////////////////////
function preload() {
	fontRegular = loadFont('cour.ttf');
	fontBold = loadFont('courbd.ttf');
	fontItalic = loadFont('couri.ttf');
	fontBoldItalic = loadFont('courbi.ttf');
}

function setup() {
	////////// INIT VARS //////////
	// MODEL
	mode = "MAP"; // initial app state
	selection = null;
	//canvas
	canvasWidth = canvasHeight = 400;
	//font
	fontSize = 12;
	//color
	colorBG = color(255,255,255);
	colorNode = color(0,0,0);
	colorSelection = color(255,127,0);
	colorLink = colorNode;

	//DRAWING SETTINGS
	rectMode(CORNERS);
	textFont(fontBold);

	// CANVAS SETUP
    createCanvas(canvasWidth, canvasHeight);
}

function draw() {
	background(colorBG); //update background

	//draw nodes
	drawAllLinks();
	drawAllNodes();

	if (mode == "GETTING NODE CONTENT") {
		drawTempNode();
	}
}

My final project was an attempt to make a digital mind-mapping interface. A mind-map is a tool for thinking through complex problems in which the user draws nodes which represent ideas and then draws connections between those nodes to develop complex relationships. A digital mind-mapping interface would transcend the traditional limits of the paper medium.

Unfortunately, the sketch uploaded to WordPress can’t run because I have been unable to find a way to load a font, which I need in order to use the textBounds() function for click detection.

When it’s working locally on my computer, the user can click or touch the screen to begin creating a node. The user can type in text for the node, then press enter to finalize the node. The user can continue to do this to create multiple nodes. If the user clicks an existing node, that node becomes selected. The user can click and drag a node to move it on the canvas. If the user has one node selected and shift-clicks another node, a link is created between those nodes. As the user moves nodes that are linked, the link is preserved.

To be honest, I haven’t been able to put a lot of time into this project, so this is where it’s at. I also developed a touch-screen drawing interface. I was hoping to integrate the two sketches into a mind-mapping interface involving touch-face drawing. Here is that sketch:

sketch

//Garrett Rauck
//Section C
//grauck@andrew.cmu.edu
//Final Project - Sketch - 2016/11/26

// prevent scrolling of page
// from "Single Touch with P5js" on coursescript.com
// http://coursescript.com/notes/interactivecomputing/mobile/touch/
document.ontouchmove = function(event){
  event.preventDefault();
}

///////////////////////////////////////////////////////////////////////////////
// GLOBAL VARIABLES
///////////////////////////////////////////////////////////////////////////////
//MODEL VARIABLES
var mode; //the current mode. possible values: "MAP", "NODE EDIT"
var currentSelection; //the currently selected object. null if nothing selected
var OX, OY; //x and y location of the map origin, relative to 0,0
var nodes = []; //array to store all node instances
var links = []; //array to store all link instances
var currentScribble;
//EVENT VARIABLES
var timeOfLastClick, //time in milliseconds of the previous mouse click
    doubleClickThreshold; //time in milliseconds allowed for double-click

///////////////////////////////////////////////////////////////////////////////
// TEXT NODE CLASS
///////////////////////////////////////////////////////////////////////////////
function textNodeDraw() {
	return;
}

function textNodeUpdateVPLocation() {
	println("textNodeUpdateVPLocation()")
}
///////////////////////////////////////////////////////////////////////////////
// SCRIBBLE NODE CLASS
///////////////////////////////////////////////////////////////////////////////
function scribbleNodeAddPoint(x,y) {
	println(this.ptsX);
	this.ptsX.push(x);
	this.ptsY.push(y);
}

function scribbleNodeDraw() {
	stroke(255);
	beginShape();
	for (var i=0; i<this.ptsX.length; i++) {
		var x = this.ptsX[i];
		var y = this.ptsY[i];
		vertex(x,y);
	}
	endShape();
}

function scribbleNodeUpdateVPLocation(dx,dy) {
	//update drawing vertices relative to map origin
	for (var i=0; i<this.ptsX.length; i++) {
		this.ptsX[i] += dx;
		this.ptsY[i] += dy;
	}
}

///////////////////////////////////////////////////////////////////////////////
// MODEL HELPER FNS
///////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
//NODES
function newNode(type,x,y) {
	println("New " + type + " node!");
	var newNode;
	if (type == "TEXT") newNode = newTextNode(x,y);
	else if (type == "SCRIBBLE") newNode = newScribbleNode(x,y);
	//store new node in array of existing nodes
	nodes.push(newNode);
	//return new node
	return newNode;
}
function newTextNode(xIn,yIn) {
	println("Creating new TEXT node...");
	//create new text node instance
	var textNode = {x: xIn, //x pos of node relative to map origin
	                y: yIn, //y pos of node relative to map origin
	                type: "TEXT", //node type
	                updateVPLocation: textNodeUpdateVPLocation, //fn for update
	                draw: textNodeDraw}; //fn to draw node
	return textNode;
}
function newScribbleNode(xIn,yIn) {
	println("Creating new SCRIBBLE node...");
	//create new scribble node instance
	var scribbleNode = {x: xIn,
	                    y: yIn,
	                    type: "SCRIBBLE",
	                    ptsX: [xIn],
	                    ptsY: [yIn],
	                    addPoint: scribbleNodeAddPoint,
	                    updateVPLocation: scribbleNodeUpdateVPLocation,
	                    draw: scribbleNodeDraw};
	return scribbleNode;
}

//-----------------------------------------------------------------------------
//VIEW
function pan(dx,dy) {
	//update node locations
	for (var i=0; i<nodes.length; i++) {
		var node = nodes[i];
		node.updateVPLocation(dx,dy);
	}
}

///////////////////////////////////////////////////////////////////////////////
// DRAW HELPER FNS
///////////////////////////////////////////////////////////////////////////////
function drawAllNodes() {
	noFill();
	strokeWeight(3);
	for (var i=0; i<nodes.length; i++) {
		var node = nodes[i];
		node.draw();
	}
}
///////////////////////////////////////////////////////////////////////////////
// EVENTS
///////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
//TOUCH STARTED
//called when user touches the screen within the javascript canvas
function touchStarted() {
	println("Touch!");
	println(touches);
	if      (mode == "MAP")       touchStartedMap();
	else if (mode == "NODE EDIT") touchStartedNodeEdit();
	//Important! Returning false prevents mousePressed() from also firing,
	//which would confuse the double-click functionality. 
	return false;
}
//called when user touches the screen while in the MAP mode
function touchStartedMap() {
	var timeOfCurrentTouch = millis()
	var timeSinceLastTouch = timeOfCurrentTouch- timeOfLastClick;
	//if user touches blank space on canvas
		//if single-tap, DESELECT
		if ((timeSinceLastTouch > doubleClickThreshold) &
		    (touches.length < 2)) {
			//deselect
			currentSelection = null;
		}
		//else if double-tap, NEW TEXT NODE
		else if ((timeSinceLastTouch <= doubleClickThreshold) &
		         (touches.length < 2)) {
			//create new text node
			newNode("TEXT",touchX,touchY);
		}
	//else if user touches an existing node

	//store time of current click
	timeOfLastClick = timeOfCurrentTouch;
}
//-----------------------------------------------------------------------------
//TOUCH MOVED
//called when user touches and drags their finger
function touchMoved() {
	if      (mode == "MAP")       touchMovedMap();
	// else if (mode == "NODE EDIT") touchMovedNodeEdit();
}
//called when user touches and drags their finger while in MAP mode
function touchMovedMap() {
	//if single finger drag, SCRIBBLE
	if (touches.length == 1) {
		//if not already scribbling, NEW SCRIBBLE NODE 
		if (currentScribble == null) {
			currentScribble = newNode("SCRIBBLE",touchX,touchY);
		}
		//else if already scribbling, ADD POINT to current scribble
	    else currentScribble.addPoint(touchX,touchY);
	}
	//else if two finger drag, PAN
	else if (touches.length == 2) {
		println("Pan!")
		pan(touchX-ptouchX, touchY-ptouchY);
	}
}
//-----------------------------------------------------------------------------
//TOUCH ENDED
//called when user releases his/her finger from the screen
function touchEnded() {
	if      (mode == "MAP")       touchEndedMap();
	// else if (mode == "NODE EDIT") touchEndedNodeEdit();
}
//called when user realeases his/her finger from the screen while in map mode
function touchEndedMap() {
	//if user is drawing scribble, stop scribbling
	if (currentScribble != null) {
		currentScribble = null;
	}
}

///////////////////////////////////////////////////////////////////////////////
// RUN!
///////////////////////////////////////////////////////////////////////////////
//function used to initialize the variables
function initVars() {
	// INIT VARS
	timeOfLastClick = 0;
	doubleClickThreshold = 200;
	currentScribble = null;
	//SET MODEL STATE
	mode = "MAP";
	//Canvas
	canvasWidth = canvasHeight = 400;
}
//function used to initialize the canvas
function initCanvas() {
	//create canvas
    createCanvas(canvasWidth, canvasHeight);
}

function setup() {
	initVars();
	initCanvas();
}

function draw() {
	background(0); //update background
	drawAllNodes();
}

Leave a Reply