//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:
//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();
}