@@ -0,0 +1,26 @@ | |||
# Simple Ontology | |||
## Define some truth ! | |||
**Create custom node network graphs aka ontologies.** | |||
Tool to make ontological network topologies with. | |||
Has saving and reopening functionality and exports images. | |||
Smallest alternative to protégé, ONTOLIS or OntoStudio. | |||
**How to use** | |||
Add nodes by clicking on "Add Node". | |||
Select a node by clicking on it and type on your keyboard to give it a title / rename it. | |||
Link nodes by dragging "wires" out of the inlets / outlets and plug them into another nodes inlets / outlets (inspired from Blenders node based shading and compositing system). | |||
You can only connect black with red / red with black. Black is inlet, red is outlet. You can not connect inlet with inlet or outlet with outlet. | |||
You need Java 1.8 to run the builds. | |||
**Ideas** | |||
* Subtitles and image-import for each node | |||
* Seperately render UI from workspace | |||
* Database | |||
* Make workspace dragable / movable (like Google maps) | |||
* draw only when necessary (more events) (ditch Processing?) | |||
* Test if it runs on windows | |||
* Even more testing! | |||
Pretty sure I'm gonna leave it as is for the time being though. |
@@ -0,0 +1,43 @@ | |||
Biological Process,395,537,32,33 | |||
Physiological Process,292,455,34 | |||
Cellular Process,483,455,34 | |||
Cellular Physiological Process,391,375,36,35 | |||
Cell Cycle,305,296,40,41 | |||
Cell Division,544,265,37 | |||
Cytokinesis,543,165,38 | |||
Cytokinesis after Meiosis,399,54 | |||
M Phase of Meiotic Cell Cycle,310,144,38 | |||
M Phase,239,225,39 | |||
Meiotic Cell Cycle,392,225,39 | |||
@@ -0,0 +1,5 @@ | |||
#!/bin/sh | |||
APPDIR=$(readlink -f "$0") | |||
APPDIR=$(dirname "$APPDIR") | |||
java -Xms64m -Xmx12000m -Djna.nosys=true -Djava.library.path="$APPDIR:$APPDIR/lib" -cp "$APPDIR:$APPDIR/lib/ontology.jar:$APPDIR/lib/core.jar:$APPDIR/lib/jogl-all.jar:$APPDIR/lib/gluegen-rt.jar:$APPDIR/lib/jogl-all-natives-linux-aarch64.jar:$APPDIR/lib/gluegen-rt-natives-linux-aarch64.jar" ontology "$@" |
@@ -0,0 +1,78 @@ | |||
int buttonCount = 6; | |||
int i_buttonId; | |||
void initButtons() { | |||
buttons[0] = new Button(10, 10, "Add Node"); | |||
buttons[1] = new Button(104, 10, "Delete Node"); | |||
buttons[2] = new Button(10, 50, "Dark/Bright Mode"); | |||
buttons[3] = new Button(10, 90, "Save"); | |||
buttons[4] = new Button(10, 130, "Open"); | |||
buttons[5] = new Button(10, 170, "Export Image"); | |||
/* buttons[0] = new Button(10, 10, "Node Hinzufügen"); | |||
buttons[1] = new Button(155, 10, "Löschen"); | |||
buttons[2] = new Button(10, 50, "Farben umkehren"); | |||
buttons[3] = new Button(10, 90, "Speichern"); | |||
buttons[4] = new Button(10, 130, "Öffnen"); | |||
buttons[5] = new Button(10, 170, "Bild Exportieren");*/ | |||
} | |||
void buttonFunctions(int functionID) { | |||
switch (functionID) { | |||
case(0): | |||
addNode(int(random(50, width-50)), int(random(30, height-150)), ""); | |||
i_selectedNode = nodeCount-1; | |||
break; | |||
case(1): | |||
deleteSelectedNode(); | |||
break; | |||
case(2): | |||
darkMode = !darkMode; | |||
break; | |||
case(3): | |||
saveCSV(dataPath("save.csv")); | |||
selectOutput("Where to save .csv file to?", "saveCSVFile"); | |||
break; | |||
case(4): | |||
selectInput("Select csv File", "loadCSVFile"); | |||
break; | |||
case(5): | |||
selectOutput("Where to export .png image file to?", "savePNGFile"); | |||
break; | |||
default: | |||
break; | |||
} | |||
} | |||
Button buttons[] = new Button[buttonCount]; | |||
class Button { | |||
int id, x, y, w, h; | |||
String label; | |||
boolean hover() { | |||
return (mouseX > x && mouseX < x+w && mouseY > y && mouseY < y+h) ? true : false; | |||
} | |||
void click() { | |||
buttonFunctions(id); | |||
} | |||
Button(int x_, int y_, String label_) { | |||
id = i_buttonId; | |||
i_buttonId++; | |||
x = x_; | |||
y = y_ ; | |||
label = label_; | |||
w = int(textWidth(label))+18; | |||
h = 32; | |||
} | |||
void display() { | |||
stroke(0); | |||
strokeWeight(1); | |||
fill(255, 127); | |||
rect(x, y, w, h, 4); | |||
fill(0); | |||
text(label, x+9, y+h/2+5); | |||
} | |||
} |
@@ -0,0 +1,60 @@ | |||
int linkCount; | |||
boolean dragLink; //state of dragging a link | |||
int dragOriginLet = 0; //dragging from inlet: -1. dragging from outlet: 1. | |||
int dragOriginId = -1; //which nodes out- / inlet did user click on? | |||
void Link(int parentNode, int targetNode) { | |||
/** | |||
* Check if user is adding or removing a link | |||
*/ | |||
int exists = -1; | |||
for (int i = 0; i < linkCount; i++) { | |||
if (links[i].parentNode == parentNode && links[i].targetNode == targetNode) { | |||
exists = i; | |||
break; | |||
} | |||
} | |||
if (exists != -1) { | |||
/** | |||
* !active state is pretty much the same as deleted state that the node-class comes with | |||
*/ | |||
links[exists].active = !links[exists].active; | |||
} else { | |||
links[linkCount] = new Link(parentNode, targetNode); | |||
linkCount++; | |||
} | |||
} | |||
class Link { | |||
int parentNode, targetNode; | |||
boolean active; | |||
PVector link = new PVector(0, 0); | |||
Link(int parentNode_, int targetNode_) { | |||
parentNode = parentNode_; | |||
targetNode = targetNode_; | |||
active = true; | |||
} | |||
/** | |||
* This could be an event that only gets called when nodes are moved or resized. | |||
*/ | |||
void update() { | |||
link.x = nodes[targetNode].inletCenter.x - nodes[parentNode].outletCenter.x; | |||
link.y = nodes[targetNode].inletCenter.y - nodes[parentNode].outletCenter.y; | |||
} | |||
void display() { | |||
stroke(darkMode? 255 : 0); | |||
strokeWeight(2); | |||
pushMatrix(); | |||
translate(nodes[parentNode].outletCenter.x, nodes[parentNode].outletCenter.y); | |||
line(0, 0, link.x, link.y); | |||
translate(-nodes[parentNode].outletCenter.x+(nodes[parentNode].outletCenter.x+nodes[targetNode].inletCenter.x)/2, -nodes[parentNode].outletCenter.y+(nodes[parentNode].outletCenter.y+nodes[targetNode].inletCenter.y)/2); | |||
rotate(link.heading()); | |||
fill(darkMode? 0 : 255); | |||
triangle(0, textSize/2, textSize, 0, 0, -textSize/2); | |||
popMatrix(); | |||
} | |||
} |
@@ -0,0 +1,129 @@ | |||
int nodeCount; | |||
int i_selectedNode = -1; | |||
int padding = textSize/3*2; //Padding between text and surrounding box | |||
int letTolerance = 10; //Widens the clickable areas of in/outlets a bit | |||
void addNode(int x, int y, String title) { | |||
nodes[nodeCount] = new Node(x, y, title); | |||
nodeCount++; | |||
} | |||
void deselect() { | |||
i_selectedNode = -1; | |||
} | |||
void deleteSelectedNode() { //Seperate from class for garbage collector reasons | |||
if (i_selectedNode != -1) { | |||
for (int i = 0; i < linkCount; i++) { | |||
if (links[i].targetNode == i_selectedNode || links[i].parentNode == i_selectedNode) { | |||
links[i].active = false; | |||
} | |||
} | |||
nodes[i_selectedNode].deleted = true; | |||
i_selectedNode = -1; | |||
} | |||
} | |||
class Node { | |||
String title; | |||
int id; | |||
int inlets = 2; | |||
int outlets = 2; | |||
int x, y; | |||
int h = textSize+2*padding; | |||
int w = textSize+2*padding; | |||
PVector dragMouseStartPoint = new PVector(0, 0); | |||
PVector outletCenter = new PVector(0, 0); | |||
PVector inletCenter = new PVector(0, 0); | |||
boolean deleted; | |||
boolean drag; | |||
boolean hover() { | |||
if (mouseX > x-w/2 && mouseX < x+w/2 && mouseY > y-h/2 && mouseY < y+h/2) { | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
boolean hoverOutlet() { | |||
if (mouseX > x-(textSize+padding)/2-letTolerance && mouseX < x+(textSize+padding)/2+letTolerance && mouseY > y-padding-textSize/2-(textSize+padding)/2-letTolerance && mouseY < y-padding-textSize/2) { | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
boolean hoverInlet() { | |||
if (mouseX > x-(textSize+padding)/2-letTolerance && mouseX < x+(textSize+padding)/2+letTolerance && mouseY > y+padding+textSize/2 && mouseY < y+padding+textSize/2+(textSize+padding)/2+letTolerance) { | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
Node(int x_, int y_, String title_) { | |||
id = nodeCount; | |||
title = title_; | |||
x = x_; | |||
y = y_; | |||
setVectors(); | |||
refreshSize(); | |||
} | |||
void setVectors() { | |||
outletCenter.x = x; | |||
outletCenter.y = y-(padding+textSize/2)*1.3; | |||
inletCenter.x = x; | |||
inletCenter.y = y+(padding+textSize/2)*1.3; | |||
} | |||
/** | |||
* Called when changing nodes title or when zooming via mouse-wheel | |||
*/ | |||
void refreshSize() { | |||
w = int(textWidth(title))+2*padding; | |||
h = textSize+2*padding; | |||
} | |||
/** | |||
* Update-function is really only necessary when dragging the node | |||
*/ | |||
void update() { | |||
if (drag) { | |||
x -= dragMouseStartPoint.x - mouseX; | |||
dragMouseStartPoint.x = mouseX; | |||
y -= dragMouseStartPoint.y - mouseY; | |||
dragMouseStartPoint.y = mouseY; | |||
setVectors(); | |||
} | |||
} | |||
void display() { | |||
/** | |||
* Selected nodes border | |||
*/ | |||
if (i_selectedNode == id) { | |||
noStroke(); | |||
fill(150, 100); | |||
rect(x, y, w+20, h+20, 4); | |||
} | |||
/** | |||
* Inlet & Outlet | |||
*/ | |||
stroke(255, darkMode? 255 : 0); | |||
strokeWeight(1); | |||
fill(255, 0, 0); | |||
ellipse(x, y-padding-textSize/2, textSize+padding, textSize+padding); | |||
fill(0); | |||
ellipse(x, y+padding+textSize/2, textSize+padding, textSize+padding); | |||
/** | |||
* Box & Title | |||
*/ | |||
stroke(127); | |||
strokeWeight(2); | |||
fill(50); | |||
rect(x, y, w, h, 4); | |||
fill(255); | |||
text(title, x, y+textSize/3); | |||
} | |||
} |
@@ -0,0 +1,577 @@ | |||
import processing.core.*; | |||
import processing.data.*; | |||
import processing.event.*; | |||
import processing.opengl.*; | |||
import java.util.HashMap; | |||
import java.util.ArrayList; | |||
import java.io.File; | |||
import java.io.BufferedReader; | |||
import java.io.PrintWriter; | |||
import java.io.InputStream; | |||
import java.io.OutputStream; | |||
import java.io.IOException; | |||
public class ontology extends PApplet { | |||
Node nodes[] = new Node[1000]; | |||
Link links[] = new Link[6000]; | |||
int textSize = 14; | |||
boolean darkMode = true; | |||
public void setup() { | |||
surface.setResizable(true); | |||
textSize(textSize); | |||
initButtons(); | |||
/** | |||
* When saving, a copy of the save-file is stored locally which is opened when starting the program | |||
*/ | |||
try { | |||
loadCSV(dataPath("save.csv")); | |||
} | |||
catch(Exception e) { | |||
println("No File found, starting blank"); | |||
} | |||
} | |||
public void draw() { | |||
background(darkMode? 0 : 255); | |||
/** | |||
* Update and display links | |||
*/ | |||
for (int i = 0; i < linkCount; i++) { | |||
if (links[i].active) { | |||
links[i].update(); | |||
links[i].display(); | |||
} | |||
} | |||
/** | |||
* Update and display nodes | |||
*/ | |||
for (int i = 0; i < nodeCount; i++) { | |||
if (!nodes[i].deleted) { | |||
nodes[i].update(); | |||
nodes[i].display(); | |||
} | |||
} | |||
/** | |||
* Update and display ui, hide via exporting-flag for easy image-saving | |||
* Not all buttons always show, that's why this isn't in a loop | |||
*/ | |||
if (!exporting) { | |||
textSize(14); | |||
rectMode(CORNER); | |||
textAlign(CORNER); | |||
buttons[0].display(); | |||
if (i_selectedNode != -1) buttons[1].display(); | |||
buttons[2].display(); | |||
buttons[3].display(); | |||
buttons[4].display(); | |||
buttons[5].display(); | |||
rectMode(CENTER); | |||
textAlign(CENTER); | |||
textSize(textSize); | |||
} else { | |||
save(savePNGpath); | |||
exporting = false; | |||
} | |||
/** | |||
* Pull "wire" out of in/outlet before creating a link (see mouseReleased in ui) | |||
*/ | |||
if (dragLink) { | |||
stroke(darkMode? 255:0); | |||
strokeWeight(2); | |||
if (dragOriginLet == -1) { | |||
line(nodes[dragOriginId].inletCenter.x, nodes[dragOriginId].inletCenter.y, mouseX, mouseY); | |||
} else if (dragOriginLet == 1) { | |||
line(nodes[dragOriginId].outletCenter.x, nodes[dragOriginId].outletCenter.y, mouseX, mouseY); | |||
} | |||
} | |||
} | |||
int buttonCount = 6; | |||
int i_buttonId; | |||
public void initButtons() { | |||
buttons[0] = new Button(10, 10, "Add Node"); | |||
buttons[1] = new Button(104, 10, "Delete Node"); | |||
buttons[2] = new Button(10, 50, "Dark/Bright Mode"); | |||
buttons[3] = new Button(10, 90, "Save"); | |||
buttons[4] = new Button(10, 130, "Open"); | |||
buttons[5] = new Button(10, 170, "Export Image"); | |||
/* buttons[0] = new Button(10, 10, "Node Hinzufügen"); | |||
buttons[1] = new Button(155, 10, "Löschen"); | |||
buttons[2] = new Button(10, 50, "Farben umkehren"); | |||
buttons[3] = new Button(10, 90, "Speichern"); | |||
buttons[4] = new Button(10, 130, "Öffnen"); | |||
buttons[5] = new Button(10, 170, "Bild Exportieren");*/ | |||
} | |||
public void buttonFunctions(int functionID) { | |||
switch (functionID) { | |||
case(0): | |||
addNode(PApplet.parseInt(random(50, width-50)), PApplet.parseInt(random(30, height-150)), ""); | |||
i_selectedNode = nodeCount-1; | |||
break; | |||
case(1): | |||
deleteSelectedNode(); | |||
break; | |||
case(2): | |||
darkMode = !darkMode; | |||
break; | |||
case(3): | |||
saveCSV(dataPath("save.csv")); | |||
selectOutput("Where to save .csv file to?", "saveCSVFile"); | |||
break; | |||
case(4): | |||
selectInput("Select csv File", "loadCSVFile"); | |||
break; | |||
case(5): | |||
selectOutput("Where to export .png image file to?", "savePNGFile"); | |||
break; | |||
default: | |||
break; | |||
} | |||
} | |||
Button buttons[] = new Button[buttonCount]; | |||
class Button { | |||
int id, x, y, w, h; | |||
String label; | |||
public boolean hover() { | |||
return (mouseX > x && mouseX < x+w && mouseY > y && mouseY < y+h) ? true : false; | |||
} | |||
public void click() { | |||
buttonFunctions(id); | |||
} | |||
Button(int x_, int y_, String label_) { | |||
id = i_buttonId; | |||
i_buttonId++; | |||
x = x_; | |||
y = y_ ; | |||
label = label_; | |||
w = PApplet.parseInt(textWidth(label))+18; | |||
h = 32; | |||
} | |||
public void display() { | |||
stroke(0); | |||
strokeWeight(1); | |||
fill(255, 127); | |||
rect(x, y, w, h, 4); | |||
fill(0); | |||
text(label, x+9, y+h/2+5); | |||
} | |||
} | |||
int linkCount; | |||
boolean dragLink; //state of dragging a link | |||
int dragOriginLet = 0; //dragging from inlet: -1. dragging from outlet: 1. | |||
int dragOriginId = -1; //which nodes out- / inlet did user click on? | |||
public void Link(int parentNode, int targetNode) { | |||
/** | |||
* Check if user is adding or removing a link | |||
*/ | |||
int exists = -1; | |||
for (int i = 0; i < linkCount; i++) { | |||
if (links[i].parentNode == parentNode && links[i].targetNode == targetNode) { | |||
exists = i; | |||
break; | |||
} | |||
} | |||
if (exists != -1) { | |||
/** | |||
* !active state is pretty much the same as deleted state that the node-class comes with | |||
*/ | |||
links[exists].active = !links[exists].active; | |||
} else { | |||
links[linkCount] = new Link(parentNode, targetNode); | |||
linkCount++; | |||
} | |||
} | |||
class Link { | |||
int parentNode, targetNode; | |||
boolean active; | |||
PVector link = new PVector(0, 0); | |||
Link(int parentNode_, int targetNode_) { | |||
parentNode = parentNode_; | |||
targetNode = targetNode_; | |||
active = true; | |||
} | |||
/** | |||
* This could be an event that only gets called when nodes are moved or resized. | |||
*/ | |||
public void update() { | |||
link.x = nodes[targetNode].inletCenter.x - nodes[parentNode].outletCenter.x; | |||
link.y = nodes[targetNode].inletCenter.y - nodes[parentNode].outletCenter.y; | |||
} | |||
public void display() { | |||
stroke(darkMode? 255 : 0); | |||
strokeWeight(2); | |||
pushMatrix(); | |||
translate(nodes[parentNode].outletCenter.x, nodes[parentNode].outletCenter.y); | |||
line(0, 0, link.x, link.y); | |||
translate(-nodes[parentNode].outletCenter.x+(nodes[parentNode].outletCenter.x+nodes[targetNode].inletCenter.x)/2, -nodes[parentNode].outletCenter.y+(nodes[parentNode].outletCenter.y+nodes[targetNode].inletCenter.y)/2); | |||
rotate(link.heading()); | |||
fill(darkMode? 0 : 255); | |||
triangle(0, textSize/2, textSize, 0, 0, -textSize/2); | |||
popMatrix(); | |||
} | |||
} | |||
int nodeCount; | |||
int i_selectedNode = -1; | |||
int padding = textSize/3*2; //Padding between text and surrounding box | |||
int letTolerance = 10; //Widens the clickable areas of in/outlets a bit | |||
public void addNode(int x, int y, String title) { | |||
nodes[nodeCount] = new Node(x, y, title); | |||
nodeCount++; | |||
} | |||
public void deselect() { | |||
i_selectedNode = -1; | |||
} | |||
public void deleteSelectedNode() { //Seperate from class for garbage collector reasons | |||
if (i_selectedNode != -1) { | |||
for (int i = 0; i < linkCount; i++) { | |||
if (links[i].targetNode == i_selectedNode || links[i].parentNode == i_selectedNode) { | |||
links[i].active = false; | |||
} | |||
} | |||
nodes[i_selectedNode].deleted = true; | |||
i_selectedNode = -1; | |||
} | |||
} | |||
class Node { | |||
String title; | |||
int id; | |||
int inlets = 2; | |||
int outlets = 2; | |||
int x, y; | |||
int h = textSize+2*padding; | |||
int w = textSize+2*padding; | |||
PVector dragMouseStartPoint = new PVector(0, 0); | |||
PVector outletCenter = new PVector(0, 0); | |||
PVector inletCenter = new PVector(0, 0); | |||
boolean deleted; | |||
boolean drag; | |||
public boolean hover() { | |||
if (mouseX > x-w/2 && mouseX < x+w/2 && mouseY > y-h/2 && mouseY < y+h/2) { | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
public boolean hoverOutlet() { | |||
if (mouseX > x-(textSize+padding)/2-letTolerance && mouseX < x+(textSize+padding)/2+letTolerance && mouseY > y-padding-textSize/2-(textSize+padding)/2-letTolerance && mouseY < y-padding-textSize/2) { | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
public boolean hoverInlet() { | |||
if (mouseX > x-(textSize+padding)/2-letTolerance && mouseX < x+(textSize+padding)/2+letTolerance && mouseY > y+padding+textSize/2 && mouseY < y+padding+textSize/2+(textSize+padding)/2+letTolerance) { | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
Node(int x_, int y_, String title_) { | |||
id = nodeCount; | |||
title = title_; | |||
x = x_; | |||
y = y_; | |||
setVectors(); | |||
refreshSize(); | |||
} | |||
public void setVectors() { | |||
outletCenter.x = x; | |||
outletCenter.y = y-(padding+textSize/2)*1.3f; | |||
inletCenter.x = x; | |||
inletCenter.y = y+(padding+textSize/2)*1.3f; | |||
} | |||
/** | |||
* Called when changing nodes title or when zooming via mouse-wheel | |||
*/ | |||
public void refreshSize() { | |||
w = PApplet.parseInt(textWidth(title))+2*padding; | |||
h = textSize+2*padding; | |||
} | |||
/** | |||
* Update-function is really only necessary when dragging the node | |||
*/ | |||
public void update() { | |||
if (drag) { | |||
x -= dragMouseStartPoint.x - mouseX; | |||
dragMouseStartPoint.x = mouseX; | |||
y -= dragMouseStartPoint.y - mouseY; | |||
dragMouseStartPoint.y = mouseY; | |||
setVectors(); | |||
} | |||
} | |||
public void display() { | |||
/** | |||
* Selected nodes border | |||
*/ | |||
if (i_selectedNode == id) { | |||
noStroke(); | |||
fill(150, 100); | |||
rect(x, y, w+20, h+20, 4); | |||
} | |||
/** | |||
* Inlet & Outlet | |||
*/ | |||
stroke(255, darkMode? 255 : 0); | |||
strokeWeight(1); | |||
fill(255, 0, 0); | |||
ellipse(x, y-padding-textSize/2, textSize+padding, textSize+padding); | |||
fill(0); | |||
ellipse(x, y+padding+textSize/2, textSize+padding, textSize+padding); | |||
/** | |||
* Box & Title | |||
*/ | |||
stroke(127); | |||
strokeWeight(2); | |||
fill(50); | |||
rect(x, y, w, h, 4); | |||
fill(255); | |||
text(title, x, y+textSize/3); | |||
} | |||
} | |||
/** | |||
* Saving and loading is a messy hack! | |||
* You'll get a blank line in your save file for each deleted object. | |||
* That's because of my lack of skills of dodging the garbage collector. | |||
* I didn't loose data to corruption yet. | |||
* Don't touch the save files if you don't want to mess them up! | |||
**/ | |||
boolean exporting; | |||
String savePNGpath = dataPath("save.png"); | |||
public void savePNGFile(File selection) { | |||
savePNGpath = selection.getPath(); | |||
exporting = true; | |||
} | |||
public void saveCSVFile(File selection) { | |||
saveCSV(selection.getPath()); | |||
} | |||
public void loadCSVFile(File selection) { | |||
loadCSV(selection.getPath()); | |||
} | |||
public void loadCSV(String path) { | |||
for (int i = 0; i < nodeCount; i++) { | |||
//nodes[i].deleted = true; | |||
i_selectedNode = i; | |||
deleteSelectedNode(); | |||
} | |||
nodeCount = 0; | |||
for (int i = 0; i < linkCount; i++) { | |||
links[i].active = false; | |||
} | |||
linkCount = 0; | |||
String[] loadString = loadStrings(path); | |||
for (int i = 0; i < loadString.length-1; i++) { | |||
//This is ultra shitty | |||
if (loadString[i].length() == 0) { | |||
addNode(-1000, -1000, " "); | |||
i_selectedNode = nodeCount - 1; | |||
deleteSelectedNode(); | |||
//nodes[nodeCount-1].deleted = true; | |||
} else { | |||
String nodeData[] = split(loadString[i], ','); | |||
addNode(PApplet.parseInt(nodeData[1]), PApplet.parseInt(nodeData[2]), nodeData[0]); | |||
} | |||
} | |||
for (int i = 0; i < loadString.length-1; i++) { | |||
String nodeData[] = split(loadString[i], ','); | |||
for (int j = 3; j < nodeData.length; j++) { | |||
Link(i, PApplet.parseInt(nodeData[j])); | |||
} | |||
} | |||
} | |||
public void saveCSV(String path) { | |||
String saveString = ""; | |||
for (int i = 0; i < nodeCount; i++) { | |||
if (!nodes[i].deleted) { | |||
saveString += nodes[i].title + "," + nodes[i].x + "," + nodes[i].y; | |||
for (int j = 0; j < linkCount; j++) { | |||
if (links[j].parentNode == i && links[j].active) { | |||
saveString += "," + str(links[j].targetNode); | |||
} | |||
} | |||
} | |||
saveString += "\n"; | |||
} | |||
String[] saveArray = split(saveString, '\n'); | |||
saveStrings(path, saveArray); | |||
} | |||
/** | |||
* Keys only get caught for changing a nodes title. | |||
* Previous title is remembered in savedTitle and restored when hitting Escape | |||
*/ | |||
String savedTitle; | |||
public void keyPressed() { | |||
if (key == ESC) { | |||
key = 0; | |||
} | |||
if (i_selectedNode != -1) { | |||
if (keyCode == BACKSPACE) { | |||
if (nodes[i_selectedNode].title.length() > 0) { | |||
nodes[i_selectedNode].title = nodes[i_selectedNode].title.substring(0, nodes[i_selectedNode].title.length()-1); | |||
nodes[i_selectedNode].refreshSize(); | |||
} | |||
} else if (keyCode == DELETE) { | |||
deleteSelectedNode(); | |||
} else if (keyCode == ESC) { | |||
nodes[i_selectedNode].title = savedTitle; | |||
nodes[i_selectedNode].refreshSize(); | |||
deselect(); | |||
} else if (keyCode == RETURN || keyCode == ENTER) { | |||
deselect(); | |||
} else if (keyCode != SHIFT && keyCode != RETURN && keyCode != ENTER && keyCode != CONTROL) { | |||
nodes[i_selectedNode].title = nodes[i_selectedNode].title + key; | |||
nodes[i_selectedNode].refreshSize(); | |||
} | |||
} | |||
} | |||
public void mousePressed() { | |||
if (mouseButton == LEFT) { | |||
/** | |||
* Either press a button... | |||
*/ | |||
boolean buttonClicked = false; | |||
for (int i = 0; i < buttons.length; i++) { | |||
if (buttons[i].hover()) { | |||
buttons[i].click(); | |||
//Not able to break out of here without using a flag | |||
buttonClicked = true; | |||
} | |||
} | |||
if (!buttonClicked) { | |||
for (int i = 0; i < nodeCount; i++) { | |||
/** | |||
* ...or select / drag a node... | |||
*/ | |||
if (nodes[i].hover()) { | |||
i_selectedNode = i; | |||
savedTitle = nodes[i].title; | |||
nodes[i].drag = true; | |||
nodes[i].dragMouseStartPoint.x = mouseX; | |||
nodes[i].dragMouseStartPoint.y = mouseY; | |||
break; | |||
} | |||
/** | |||
* ...or pull a new link out of an inlet or outlet. | |||
*/ | |||
else if (nodes[i].hoverInlet()) { | |||
dragLink = true; | |||
dragOriginLet = -1; | |||
dragOriginId = i; | |||
} else if (nodes[i].hoverOutlet()) { | |||
dragLink = true; | |||
dragOriginLet = 1; | |||
dragOriginId = i; | |||
} | |||
//deselecting the shit out of this for reasons I don't remember, no care to improve right now | |||
deselect(); | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* Create links | |||
*/ | |||
public void mouseReleased() { | |||
for (int i = 0; i < nodeCount; i++) { | |||
nodes[i].drag = false; | |||
if (dragLink && dragOriginId != i) { | |||
if (nodes[i].hoverInlet() && dragOriginLet == 1) { | |||
Link(dragOriginId, i); | |||
} | |||
if (nodes[i].hoverOutlet() && dragOriginLet == -1) { | |||
Link(i, dragOriginId); | |||
} | |||
} | |||
} | |||
/** | |||
* Reset drag&drop states | |||
*/ | |||
dragLink = false; | |||
dragOriginLet = 0; | |||
dragOriginId = -1; | |||
} | |||
/** | |||
* Scaling is tied to textSize. Having the UI on a seperate PGraphics would be better, but it's good enough for now | |||
*/ | |||
int zoomDelta = 3; | |||
public void mouseWheel(MouseEvent event) { | |||
float zoom = event.getAmount(); | |||
if (zoom < 0) { | |||
textSize += zoomDelta; | |||
} else if (zoom > 0) { | |||
if (textSize - zoomDelta > 0) { | |||
textSize -= zoomDelta; | |||
} | |||
} | |||
padding = textSize/3*2; | |||
for (int i = 0; i < nodeCount; i++) { | |||
nodes[i].refreshSize(); | |||
nodes[i].setVectors(); | |||
} | |||
} | |||
public void settings() { size(800, 600); } | |||
static public void main(String[] passedArgs) { | |||
String[] appletArgs = new String[] { "ontology" }; | |||
if (passedArgs != null) { | |||
PApplet.main(concat(appletArgs, passedArgs)); | |||
} else { | |||
PApplet.main(appletArgs); | |||
} | |||
} | |||
} |
@@ -0,0 +1,83 @@ | |||
Node nodes[] = new Node[1000]; | |||
Link links[] = new Link[6000]; | |||
int textSize = 14; | |||
boolean darkMode = true; | |||
void setup() { | |||
size(800, 600); | |||
surface.setResizable(true); | |||
textSize(textSize); | |||
initButtons(); | |||
/** | |||
* When saving, a copy of the save-file is stored locally which is opened when starting the program | |||
*/ | |||
try { | |||
loadCSV(dataPath("save.csv")); | |||
} | |||
catch(Exception e) { | |||
println("No File found, starting blank"); | |||
} | |||
} | |||
void draw() { | |||
background(darkMode? 0 : 255); | |||
/** | |||
* Update and display links | |||
*/ | |||
for (int i = 0; i < linkCount; i++) { | |||
if (links[i].active) { | |||
links[i].update(); | |||
links[i].display(); | |||
} | |||
} | |||
/** | |||
* Update and display nodes | |||
*/ | |||
for (int i = 0; i < nodeCount; i++) { | |||
if (!nodes[i].deleted) { | |||
nodes[i].update(); | |||
nodes[i].display(); | |||
} | |||
} | |||
/** | |||
* Update and display ui, hide via exporting-flag for easy image-saving | |||
* Not all buttons always show, that's why this isn't in a loop | |||
*/ | |||
if (!exporting) { | |||
textSize(14); | |||
rectMode(CORNER); | |||
textAlign(CORNER); | |||
buttons[0].display(); | |||
if (i_selectedNode != -1) buttons[1].display(); | |||
buttons[2].display(); | |||
buttons[3].display(); | |||
buttons[4].display(); | |||
buttons[5].display(); | |||
rectMode(CENTER); | |||
textAlign(CENTER); | |||
textSize(textSize); | |||
} else { | |||
save(savePNGpath); | |||
exporting = false; | |||
} | |||
/** | |||
* Pull "wire" out of in/outlet before creating a link (see mouseReleased in ui) | |||
*/ | |||
if (dragLink) { | |||
stroke(darkMode? 255:0); | |||
strokeWeight(2); | |||
if (dragOriginLet == -1) { | |||
line(nodes[dragOriginId].inletCenter.x, nodes[dragOriginId].inletCenter.y, mouseX, mouseY); | |||
} else if (dragOriginLet == 1) { | |||
line(nodes[dragOriginId].outletCenter.x, nodes[dragOriginId].outletCenter.y, mouseX, mouseY); | |||
} | |||
} | |||
} |
@@ -0,0 +1,74 @@ | |||
/** | |||
* Saving and loading is a messy hack! | |||
* You'll get a blank line in your save file for each deleted object. | |||
* That's because of my lack of skills of dodging the garbage collector. | |||
* I didn't loose data to corruption yet. | |||
* Don't touch the save files if you don't want to mess them up! | |||
**/ | |||
boolean exporting; | |||
String savePNGpath = dataPath("save.png"); | |||
void savePNGFile(File selection) { | |||
savePNGpath = selection.getPath(); | |||
exporting = true; | |||
} | |||
void saveCSVFile(File selection) { | |||
saveCSV(selection.getPath()); | |||
} | |||
void loadCSVFile(File selection) { | |||
loadCSV(selection.getPath()); | |||
} | |||
void loadCSV(String path) { | |||
for (int i = 0; i < nodeCount; i++) { | |||
//nodes[i].deleted = true; | |||
i_selectedNode = i; | |||
deleteSelectedNode(); | |||
} | |||
nodeCount = 0; | |||
for (int i = 0; i < linkCount; i++) { | |||
links[i].active = false; | |||
} | |||
linkCount = 0; | |||
String[] loadString = loadStrings(path); | |||
for (int i = 0; i < loadString.length-1; i++) { | |||
//This is ultra shitty | |||
if (loadString[i].length() == 0) { | |||
addNode(-1000, -1000, " "); | |||
i_selectedNode = nodeCount - 1; | |||
deleteSelectedNode(); | |||
//nodes[nodeCount-1].deleted = true; | |||
} else { | |||
String nodeData[] = split(loadString[i], ','); | |||
addNode(int(nodeData[1]), int(nodeData[2]), nodeData[0]); | |||
} | |||
} | |||
for (int i = 0; i < loadString.length-1; i++) { | |||
String nodeData[] = split(loadString[i], ','); | |||
for (int j = 3; j < nodeData.length; j++) { | |||
Link(i, int(nodeData[j])); | |||
} | |||
} | |||
} | |||
void saveCSV(String path) { | |||
String saveString = ""; | |||
for (int i = 0; i < nodeCount; i++) { | |||
if (!nodes[i].deleted) { | |||
saveString += nodes[i].title + "," + nodes[i].x + "," + nodes[i].y; | |||
for (int j = 0; j < linkCount; j++) { | |||
if (links[j].parentNode == i && links[j].active) { | |||
saveString += "," + str(links[j].targetNode); | |||
} | |||
} | |||
} | |||
saveString += "\n"; | |||
} | |||
String[] saveArray = split(saveString, '\n'); | |||
saveStrings(path, saveArray); | |||
} |
@@ -0,0 +1,124 @@ | |||
/** | |||
* Keys only get caught for changing a nodes title. | |||
* Previous title is remembered in savedTitle and restored when hitting Escape | |||
*/ | |||
String savedTitle; | |||
void keyPressed() { | |||
if (key == ESC) { | |||
key = 0; | |||
} | |||
if (i_selectedNode != -1) { | |||
if (keyCode == BACKSPACE) { | |||
if (nodes[i_selectedNode].title.length() > 0) { | |||
nodes[i_selectedNode].title = nodes[i_selectedNode].title.substring(0, nodes[i_selectedNode].title.length()-1); | |||
nodes[i_selectedNode].refreshSize(); | |||
} | |||
} else if (keyCode == DELETE) { | |||
deleteSelectedNode(); | |||
} else if (keyCode == ESC) { | |||
nodes[i_selectedNode].title = savedTitle; | |||
nodes[i_selectedNode].refreshSize(); | |||
deselect(); | |||
} else if (keyCode == RETURN || keyCode == ENTER) { | |||
deselect(); | |||
} else if (keyCode != SHIFT && keyCode != RETURN && keyCode != ENTER && keyCode != CONTROL) { | |||
nodes[i_selectedNode].title = nodes[i_selectedNode].title + key; | |||
nodes[i_selectedNode].refreshSize(); | |||
} | |||
} | |||
} | |||
void mousePressed() { | |||
if (mouseButton == LEFT) { | |||
/** | |||
* Either press a button... | |||
*/ | |||
boolean buttonClicked = false; | |||
for (int i = 0; i < buttons.length; i++) { | |||
if (buttons[i].hover()) { | |||
buttons[i].click(); | |||
//Not able to break out of here without using a flag | |||
buttonClicked = true; | |||
} | |||
} | |||
if (!buttonClicked) { | |||
for (int i = 0; i < nodeCount; i++) { | |||
/** | |||
* ...or select / drag a node... | |||
*/ | |||
if (nodes[i].hover()) { | |||
i_selectedNode = i; | |||
savedTitle = nodes[i].title; | |||
nodes[i].drag = true; | |||
nodes[i].dragMouseStartPoint.x = mouseX; | |||
nodes[i].dragMouseStartPoint.y = mouseY; | |||
break; | |||
} | |||
/** | |||
* ...or pull a new link out of an inlet or outlet. | |||
*/ | |||
else if (nodes[i].hoverInlet()) { | |||
dragLink = true; | |||
dragOriginLet = -1; | |||
dragOriginId = i; | |||
} else if (nodes[i].hoverOutlet()) { | |||
dragLink = true; | |||
dragOriginLet = 1; | |||
dragOriginId = i; | |||
} | |||
//deselecting the shit out of this for reasons I don't remember, no care to improve right now | |||
deselect(); | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* Create links | |||
*/ | |||
void mouseReleased() { | |||
for (int i = 0; i < nodeCount; i++) { | |||
nodes[i].drag = false; | |||
if (dragLink && dragOriginId != i) { | |||
if (nodes[i].hoverInlet() && dragOriginLet == 1) { | |||
Link(dragOriginId, i); | |||
} | |||
if (nodes[i].hoverOutlet() && dragOriginLet == -1) { | |||
Link(i, dragOriginId); | |||
} | |||
} | |||
} | |||
/** | |||
* Reset drag&drop states | |||
*/ | |||
dragLink = false; | |||
dragOriginLet = 0; | |||
dragOriginId = -1; | |||
} | |||
/** | |||
* Scaling is tied to textSize. Having the UI on a seperate PGraphics would be better, but it's good enough for now | |||
*/ | |||
int zoomDelta = 3; | |||
void mouseWheel(MouseEvent event) { | |||
float zoom = event.getAmount(); | |||
if (zoom < 0) { | |||
textSize += zoomDelta; | |||
} else if (zoom > 0) { | |||
if (textSize - zoomDelta > 0) { | |||
textSize -= zoomDelta; | |||
} | |||
} | |||
padding = textSize/3*2; | |||
for (int i = 0; i < nodeCount; i++) { | |||
nodes[i].refreshSize(); | |||
nodes[i].setVectors(); | |||
} | |||
} |
@@ -0,0 +1,43 @@ | |||
Biological Process,395,537,32,33 | |||
Physiological Process,292,455,34 | |||
Cellular Process,483,455,34 | |||
Cellular Physiological Process,391,375,36,35 | |||
Cell Cycle,305,296,40,41 | |||
Cell Division,544,265,37 | |||
Cytokinesis,543,165,38 | |||
Cytokinesis after Meiosis,399,54 | |||
M Phase of Meiotic Cell Cycle,310,144,38 | |||
M Phase,239,225,39 | |||
Meiotic Cell Cycle,392,225,39 | |||
@@ -0,0 +1,5 @@ | |||
#!/bin/sh | |||
APPDIR=$(readlink -f "$0") | |||
APPDIR=$(dirname "$APPDIR") | |||
java -Djna.nosys=true -Djava.library.path="$APPDIR:$APPDIR/lib" -cp "$APPDIR:$APPDIR/lib/ontology.jar:$APPDIR/lib/core.jar:$APPDIR/lib/jogl-all.jar:$APPDIR/lib/gluegen-rt.jar:$APPDIR/lib/jogl-all-natives-linux-armv6hf.jar:$APPDIR/lib/gluegen-rt-natives-linux-armv6hf.jar" ontology "$@" |
@@ -0,0 +1,78 @@ | |||
int buttonCount = 6; | |||
int i_buttonId; | |||
void initButtons() { | |||
buttons[0] = new Button(10, 10, "Add Node"); | |||
buttons[1] = new Button(104, 10, "Delete Node"); | |||
buttons[2] = new Button(10, 50, "Dark/Bright Mode"); | |||
buttons[3] = new Button(10, 90, "Save"); | |||
buttons[4] = new Button(10, 130, "Open"); | |||
buttons[5] = new Button(10, 170, "Export Image"); | |||
/* buttons[0] = new Button(10, 10, "Node Hinzufügen"); | |||
buttons[1] = new Button(155, 10, "Löschen"); | |||
buttons[2] = new Button(10, 50, "Farben umkehren"); | |||
buttons[3] = new Button(10, 90, "Speichern"); | |||
buttons[4] = new Button(10, 130, "Öffnen"); | |||
buttons[5] = new Button(10, 170, "Bild Exportieren");*/ | |||
} | |||
void buttonFunctions(int functionID) { | |||
switch (functionID) { | |||
case(0): | |||
addNode(int(random(50, width-50)), int(random(30, height-150)), ""); | |||
i_selectedNode = nodeCount-1; | |||
break; | |||
case(1): | |||
deleteSelectedNode(); | |||
break; | |||
case(2): | |||
darkMode = !darkMode; | |||
break; | |||
case(3): | |||
saveCSV(dataPath("save.csv")); | |||
selectOutput("Where to save .csv file to?", "saveCSVFile"); | |||
break; | |||
case(4): | |||
selectInput("Select csv File", "loadCSVFile"); | |||
break; | |||
case(5): | |||
selectOutput("Where to export .png image file to?", "savePNGFile"); | |||
break; | |||
default: | |||
break; | |||
} | |||
} | |||
Button buttons[] = new Button[buttonCount]; | |||
class Button { | |||
int id, x, y, w, h; | |||
String label; | |||
boolean hover() { | |||
return (mouseX > x && mouseX < x+w && mouseY > y && mouseY < y+h) ? true : false; | |||
} | |||
void click() { | |||
buttonFunctions(id); | |||
} | |||
Button(int x_, int y_, String label_) { | |||
id = i_buttonId; | |||
i_buttonId++; | |||
x = x_; | |||
y = y_ ; | |||
label = label_; | |||
w = int(textWidth(label))+18; | |||
h = 32; | |||
} | |||
void display() { | |||
stroke(0); | |||
strokeWeight(1); | |||
fill(255, 127); | |||
rect(x, y, w, h, 4); | |||
fill(0); | |||
text(label, x+9, y+h/2+5); | |||
} | |||
} |
@@ -0,0 +1,60 @@ | |||
int linkCount; | |||
boolean dragLink; //state of dragging a link | |||
int dragOriginLet = 0; //dragging from inlet: -1. dragging from outlet: 1. | |||
int dragOriginId = -1; //which nodes out- / inlet did user click on? | |||
void Link(int parentNode, int targetNode) { | |||
/** | |||
* Check if user is adding or removing a link | |||
*/ | |||
int exists = -1; | |||
for (int i = 0; i < linkCount; i++) { | |||
if (links[i].parentNode == parentNode && links[i].targetNode == targetNode) { | |||
exists = i; | |||
break; | |||
} | |||
} | |||
if (exists != -1) { | |||
/** | |||
* !active state is pretty much the same as deleted state that the node-class comes with | |||
*/ | |||
links[exists].active = !links[exists].active; | |||
} else { | |||
links[linkCount] = new Link(parentNode, targetNode); | |||
linkCount++; | |||
} | |||
} | |||
class Link { | |||
int parentNode, targetNode; | |||
boolean active; | |||
PVector link = new PVector(0, 0); | |||
Link(int parentNode_, int targetNode_) { | |||
parentNode = parentNode_; | |||
targetNode = targetNode_; | |||
active = true; | |||
} | |||
/** | |||
* This could be an event that only gets called when nodes are moved or resized. | |||
*/ | |||
void update() { | |||
link.x = nodes[targetNode].inletCenter.x - nodes[parentNode].outletCenter.x; | |||
link.y = nodes[targetNode].inletCenter.y - nodes[parentNode].outletCenter.y; | |||
} | |||
void display() { | |||
stroke(darkMode? 255 : 0); | |||
strokeWeight(2); | |||
pushMatrix(); | |||
translate(nodes[parentNode].outletCenter.x, nodes[parentNode].outletCenter.y); | |||
line(0, 0, link.x, link.y); | |||
translate(-nodes[parentNode].outletCenter.x+(nodes[parentNode].outletCenter.x+nodes[targetNode].inletCenter.x)/2, -nodes[parentNode].outletCenter.y+(nodes[parentNode].outletCenter.y+nodes[targetNode].inletCenter.y)/2); | |||
rotate(link.heading()); | |||
fill(darkMode? 0 : 255); | |||
triangle(0, textSize/2, textSize, 0, 0, -textSize/2); | |||
popMatrix(); | |||
} | |||
} |
@@ -0,0 +1,129 @@ | |||
int nodeCount; | |||
int i_selectedNode = -1; | |||
int padding = textSize/3*2; //Padding between text and surrounding box | |||
int letTolerance = 10; //Widens the clickable areas of in/outlets a bit | |||
void addNode(int x, int y, String title) { | |||
nodes[nodeCount] = new Node(x, y, title); | |||
nodeCount++; | |||
} | |||
void deselect() { | |||
i_selectedNode = -1; | |||
} | |||
void deleteSelectedNode() { //Seperate from class for garbage collector reasons | |||
if (i_selectedNode != -1) { | |||
for (int i = 0; i < linkCount; i++) { | |||
if (links[i].targetNode == i_selectedNode || links[i].parentNode == i_selectedNode) { | |||
links[i].active = false; | |||
} | |||
} | |||
nodes[i_selectedNode].deleted = true; | |||
i_selectedNode = -1; | |||
} | |||
} | |||
class Node { | |||
String title; | |||
int id; | |||
int inlets = 2; | |||
int outlets = 2; | |||
int x, y; | |||
int h = textSize+2*padding; | |||
int w = textSize+2*padding; | |||
PVector dragMouseStartPoint = new PVector(0, 0); | |||
PVector outletCenter = new PVector(0, 0); | |||
PVector inletCenter = new PVector(0, 0); | |||
boolean deleted; | |||
boolean drag; | |||
boolean hover() { | |||
if (mouseX > x-w/2 && mouseX < x+w/2 && mouseY > y-h/2 && mouseY < y+h/2) { | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
boolean hoverOutlet() { | |||
if (mouseX > x-(textSize+padding)/2-letTolerance && mouseX < x+(textSize+padding)/2+letTolerance && mouseY > y-padding-textSize/2-(textSize+padding)/2-letTolerance && mouseY < y-padding-textSize/2) { | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
boolean hoverInlet() { | |||
if (mouseX > x-(textSize+padding)/2-letTolerance && mouseX < x+(textSize+padding)/2+letTolerance && mouseY > y+padding+textSize/2 && mouseY < y+padding+textSize/2+(textSize+padding)/2+letTolerance) { | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
Node(int x_, int y_, String title_) { | |||
id = nodeCount; | |||
title = title_; | |||
x = x_; | |||
y = y_; | |||
setVectors(); | |||
refreshSize(); | |||
} | |||
void setVectors() { | |||
outletCenter.x = x; | |||
outletCenter.y = y-(padding+textSize/2)*1.3; | |||
inletCenter.x = x; | |||
inletCenter.y = y+(padding+textSize/2)*1.3; | |||
} | |||
/** | |||
* Called when changing nodes title or when zooming via mouse-wheel | |||
*/ | |||
void refreshSize() { | |||
w = int(textWidth(title))+2*padding; | |||
h = textSize+2*padding; | |||
} | |||
/** | |||
* Update-function is really only necessary when dragging the node | |||
*/ | |||
void update() { | |||
if (drag) { | |||
x -= dragMouseStartPoint.x - mouseX; | |||
dragMouseStartPoint.x = mouseX; | |||
y -= dragMouseStartPoint.y - mouseY; | |||
dragMouseStartPoint.y = mouseY; | |||
setVectors(); | |||
} | |||
} | |||
void display() { | |||
/** | |||
* Selected nodes border | |||
*/ | |||
if (i_selectedNode == id) { | |||
noStroke(); | |||
fill(150, 100); | |||
rect(x, y, w+20, h+20, 4); | |||
} | |||
/** | |||
* Inlet & Outlet | |||
*/ | |||
stroke(255, darkMode? 255 : 0); | |||
strokeWeight(1); | |||
fill(255, 0, 0); | |||
ellipse(x, y-padding-textSize/2, textSize+padding, textSize+padding); | |||
fill(0); | |||
ellipse(x, y+padding+textSize/2, textSize+padding, textSize+padding); | |||
/** | |||
* Box & Title | |||
*/ | |||
stroke(127); | |||
strokeWeight(2); | |||
fill(50); | |||
rect(x, y, w, h, 4); | |||
fill(255); | |||
text(title, x, y+textSize/3); | |||
} | |||
} |
@@ -0,0 +1,577 @@ | |||
import processing.core.*; | |||
import processing.data.*; | |||
import processing.event.*; | |||
import processing.opengl.*; | |||
import java.util.HashMap; | |||
import java.util.ArrayList; | |||
import java.io.File; | |||
import java.io.BufferedReader; | |||
import java.io.PrintWriter; | |||
import java.io.InputStream; | |||
import java.io.OutputStream; | |||
import java.io.IOException; | |||
public class ontology extends PApplet { | |||
Node nodes[] = new Node[1000]; | |||
Link links[] = new Link[6000]; | |||
int textSize = 14; | |||
boolean darkMode = true; | |||
public void setup() { | |||
surface.setResizable(true); | |||
textSize(textSize); | |||
initButtons(); | |||
/** | |||
* When saving, a copy of the save-file is stored locally which is opened when starting the program | |||
*/ | |||
try { | |||
loadCSV(dataPath("save.csv")); | |||
} | |||
catch(Exception e) { | |||
println("No File found, starting blank"); | |||
} | |||
} | |||
public void draw() { | |||
background(darkMode? 0 : 255); | |||
/** | |||
* Update and display links | |||
*/ | |||
for (int i = 0; i < linkCount; i++) { | |||
if (links[i].active) { | |||
links[i].update(); | |||
links[i].display(); | |||
} | |||
} | |||
/** | |||
* Update and display nodes | |||
*/ | |||
for (int i = 0; i < nodeCount; i++) { | |||
if (!nodes[i].deleted) { | |||
nodes[i].update(); | |||
nodes[i].display(); | |||
} | |||
} | |||
/** | |||
* Update and display ui, hide via exporting-flag for easy image-saving | |||
* Not all buttons always show, that's why this isn't in a loop | |||
*/ | |||
if (!exporting) { | |||
textSize(14); | |||
rectMode(CORNER); | |||
textAlign(CORNER); | |||
buttons[0].display(); | |||
if (i_selectedNode != -1) buttons[1].display(); | |||
buttons[2].display(); | |||
buttons[3].display(); | |||
buttons[4].display(); | |||
buttons[5].display(); | |||
rectMode(CENTER); | |||
textAlign(CENTER); | |||
textSize(textSize); | |||
} else { | |||
save(savePNGpath); | |||
exporting = false; | |||
} | |||
/** | |||
* Pull "wire" out of in/outlet before creating a link (see mouseReleased in ui) | |||
*/ | |||
if (dragLink) { | |||
stroke(darkMode? 255:0); | |||
strokeWeight(2); | |||
if (dragOriginLet == -1) { | |||
line(nodes[dragOriginId].inletCenter.x, nodes[dragOriginId].inletCenter.y, mouseX, mouseY); | |||
} else if (dragOriginLet == 1) { | |||
line(nodes[dragOriginId].outletCenter.x, nodes[dragOriginId].outletCenter.y, mouseX, mouseY); | |||
} | |||
} | |||
} | |||
int buttonCount = 6; | |||
int i_buttonId; | |||
public void initButtons() { | |||
buttons[0] = new Button(10, 10, "Add Node"); | |||
buttons[1] = new Button(104, 10, "Delete Node"); | |||
buttons[2] = new Button(10, 50, "Dark/Bright Mode"); | |||
buttons[3] = new Button(10, 90, "Save"); | |||
buttons[4] = new Button(10, 130, "Open"); | |||
buttons[5] = new Button(10, 170, "Export Image"); | |||
/* buttons[0] = new Button(10, 10, "Node Hinzufügen"); | |||
buttons[1] = new Button(155, 10, "Löschen"); | |||
buttons[2] = new Button(10, 50, "Farben umkehren"); | |||
buttons[3] = new Button(10, 90, "Speichern"); | |||
buttons[4] = new Button(10, 130, "Öffnen"); | |||
buttons[5] = new Button(10, 170, "Bild Exportieren");*/ | |||
} | |||
public void buttonFunctions(int functionID) { | |||
switch (functionID) { | |||
case(0): | |||
addNode(PApplet.parseInt(random(50, width-50)), PApplet.parseInt(random(30, height-150)), ""); | |||
i_selectedNode = nodeCount-1; | |||
break; | |||
case(1): | |||
deleteSelectedNode(); | |||
break; | |||
case(2): | |||
darkMode = !darkMode; | |||
break; | |||
case(3): | |||
saveCSV(dataPath("save.csv")); | |||
selectOutput("Where to save .csv file to?", "saveCSVFile"); | |||
break; | |||
case(4): | |||
selectInput("Select csv File", "loadCSVFile"); | |||
break; | |||
case(5): | |||
selectOutput("Where to export .png image file to?", "savePNGFile"); | |||
break; | |||
default: | |||
break; | |||
} | |||
} | |||
Button buttons[] = new Button[buttonCount]; | |||
class Button { | |||
int id, x, y, w, h; | |||
String label; | |||
public boolean hover() { | |||
return (mouseX > x && mouseX < x+w && mouseY > y && mouseY < y+h) ? true : false; | |||
} | |||
public void click() { | |||
buttonFunctions(id); | |||
} | |||
Button(int x_, int y_, String label_) { | |||
id = i_buttonId; | |||
i_buttonId++; | |||
x = x_; | |||
y = y_ ; | |||
label = label_; | |||
w = PApplet.parseInt(textWidth(label))+18; | |||
h = 32; | |||
} | |||
public void display() { | |||
stroke(0); | |||
strokeWeight(1); | |||
fill(255, 127); | |||
rect(x, y, w, h, 4); | |||
fill(0); | |||
text(label, x+9, y+h/2+5); | |||
} | |||
} | |||
int linkCount; | |||
boolean dragLink; //state of dragging a link | |||
int dragOriginLet = 0; //dragging from inlet: -1. dragging from outlet: 1. | |||
int dragOriginId = -1; //which nodes out- / inlet did user click on? | |||
public void Link(int parentNode, int targetNode) { | |||
/** | |||
* Check if user is adding or removing a link | |||
*/ | |||
int exists = -1; | |||
for (int i = 0; i < linkCount; i++) { | |||
if (links[i].parentNode == parentNode && links[i].targetNode == targetNode) { | |||
exists = i; | |||
break; | |||
} | |||
} | |||
if (exists != -1) { | |||
/** | |||
* !active state is pretty much the same as deleted state that the node-class comes with | |||
*/ | |||
links[exists].active = !links[exists].active; | |||
} else { | |||
links[linkCount] = new Link(parentNode, targetNode); | |||
linkCount++; | |||
} | |||
} | |||
class Link { | |||
int parentNode, targetNode; | |||
boolean active; | |||
PVector link = new PVector(0, 0); | |||
Link(int parentNode_, int targetNode_) { | |||
parentNode = parentNode_; | |||
targetNode = targetNode_; | |||
active = true; | |||
} | |||
/** | |||
* This could be an event that only gets called when nodes are moved or resized. | |||
*/ | |||
public void update() { | |||
link.x = nodes[targetNode].inletCenter.x - nodes[parentNode].outletCenter.x; | |||
link.y = nodes[targetNode].inletCenter.y - nodes[parentNode].outletCenter.y; | |||
} | |||
public void display() { | |||
stroke(darkMode? 255 : 0); | |||
strokeWeight(2); | |||
pushMatrix(); | |||
translate(nodes[parentNode].outletCenter.x, nodes[parentNode].outletCenter.y); | |||
line(0, 0, link.x, link.y); | |||
translate(-nodes[parentNode].outletCenter.x+(nodes[parentNode].outletCenter.x+nodes[targetNode].inletCenter.x)/2, -nodes[parentNode].outletCenter.y+(nodes[parentNode].outletCenter.y+nodes[targetNode].inletCenter.y)/2); | |||
rotate(link.heading()); | |||
fill(darkMode? 0 : 255); | |||
triangle(0, textSize/2, textSize, 0, 0, -textSize/2); | |||
popMatrix(); | |||
} | |||
} | |||
int nodeCount; | |||
int i_selectedNode = -1; | |||
int padding = textSize/3*2; //Padding between text and surrounding box | |||
int letTolerance = 10; //Widens the clickable areas of in/outlets a bit | |||
public void addNode(int x, int y, String title) { | |||
nodes[nodeCount] = new Node(x, y, title); | |||
nodeCount++; | |||
} | |||
public void deselect() { | |||
i_selectedNode = -1; | |||
} | |||
public void deleteSelectedNode() { //Seperate from class for garbage collector reasons | |||
if (i_selectedNode != -1) { | |||
for (int i = 0; i < linkCount; i++) { | |||
if (links[i].targetNode == i_selectedNode || links[i].parentNode == i_selectedNode) { | |||
links[i].active = false; | |||
} | |||
} | |||
nodes[i_selectedNode].deleted = true; | |||
i_selectedNode = -1; | |||
} | |||
} | |||
class Node { | |||
String title; | |||
int id; | |||
int inlets = 2; | |||
int outlets = 2; | |||
int x, y; | |||
int h = textSize+2*padding; | |||
int w = textSize+2*padding; | |||
PVector dragMouseStartPoint = new PVector(0, 0); | |||
PVector outletCenter = new PVector(0, 0); | |||
PVector inletCenter = new PVector(0, 0); | |||
boolean deleted; | |||
boolean drag; | |||
public boolean hover() { | |||
if (mouseX > x-w/2 && mouseX < x+w/2 && mouseY > y-h/2 && mouseY < y+h/2) { | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
public boolean hoverOutlet() { | |||
if (mouseX > x-(textSize+padding)/2-letTolerance && mouseX < x+(textSize+padding)/2+letTolerance && mouseY > y-padding-textSize/2-(textSize+padding)/2-letTolerance && mouseY < y-padding-textSize/2) { | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
public boolean hoverInlet() { | |||
if (mouseX > x-(textSize+padding)/2-letTolerance && mouseX < x+(textSize+padding)/2+letTolerance && mouseY > y+padding+textSize/2 && mouseY < y+padding+textSize/2+(textSize+padding)/2+letTolerance) { | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
Node(int x_, int y_, String title_) { | |||
id = nodeCount; | |||
title = title_; | |||
x = x_; | |||
y = y_; | |||
setVectors(); | |||
refreshSize(); | |||
} | |||
public void setVectors() { | |||
outletCenter.x = x; | |||
outletCenter.y = y-(padding+textSize/2)*1.3f; | |||
inletCenter.x = x; | |||
inletCenter.y = y+(padding+textSize/2)*1.3f; | |||
} | |||
/** | |||
* Called when changing nodes title or when zooming via mouse-wheel | |||
*/ | |||
public void refreshSize() { | |||
w = PApplet.parseInt(textWidth(title))+2*padding; | |||
h = textSize+2*padding; | |||
} | |||
/** | |||
* Update-function is really only necessary when dragging the node | |||
*/ | |||
public void update() { | |||
if (drag) { | |||
x -= dragMouseStartPoint.x - mouseX; | |||
dragMouseStartPoint.x = mouseX; | |||
y -= dragMouseStartPoint.y - mouseY; | |||
dragMouseStartPoint.y = mouseY; | |||
setVectors(); | |||
} | |||
} | |||
public void display() { | |||
/** | |||
* Selected nodes border | |||
*/ | |||
if (i_selectedNode == id) { | |||
noStroke(); | |||
fill(150, 100); | |||
rect(x, y, w+20, h+20, 4); | |||
} | |||
/** | |||
* Inlet & Outlet | |||
*/ | |||
stroke(255, darkMode? 255 : 0); | |||
strokeWeight(1); | |||
fill(255, 0, 0); | |||
ellipse(x, y-padding-textSize/2, textSize+padding, textSize+padding); | |||
fill(0); | |||
ellipse(x, y+padding+textSize/2, textSize+padding, textSize+padding); | |||
/** | |||
* Box & Title | |||
*/ | |||
stroke(127); | |||
strokeWeight(2); | |||
fill(50); | |||
rect(x, y, w, h, 4); | |||
fill(255); | |||
text(title, x, y+textSize/3); | |||
} | |||
} | |||
/** | |||
* Saving and loading is a messy hack! | |||
* You'll get a blank line in your save file for each deleted object. | |||
* That's because of my lack of skills of dodging the garbage collector. | |||
* I didn't loose data to corruption yet. | |||
* Don't touch the save files if you don't want to mess them up! | |||
**/ | |||
boolean exporting; | |||
String savePNGpath = dataPath("save.png"); | |||
public void savePNGFile(File selection) { | |||
savePNGpath = selection.getPath(); | |||
exporting = true; | |||
} | |||
public void saveCSVFile(File selection) { | |||
saveCSV(selection.getPath()); | |||
} | |||
public void loadCSVFile(File selection) { | |||
loadCSV(selection.getPath()); | |||
} | |||
public void loadCSV(String path) { | |||
for (int i = 0; i < nodeCount; i++) { | |||
//nodes[i].deleted = true; | |||
i_selectedNode = i; | |||
deleteSelectedNode(); | |||
} | |||
nodeCount = 0; | |||
for (int i = 0; i < linkCount; i++) { | |||
links[i].active = false; | |||
} | |||
linkCount = 0; | |||
String[] loadString = loadStrings(path); | |||
for (int i = 0; i < loadString.length-1; i++) { | |||
//This is ultra shitty | |||
if (loadString[i].length() == 0) { | |||
addNode(-1000, -1000, " "); | |||
i_selectedNode = nodeCount - 1; | |||
deleteSelectedNode(); | |||
//nodes[nodeCount-1].deleted = true; | |||
} else { | |||
String nodeData[] = split(loadString[i], ','); | |||
addNode(PApplet.parseInt(nodeData[1]), PApplet.parseInt(nodeData[2]), nodeData[0]); | |||
} | |||
} | |||
for (int i = 0; i < loadString.length-1; i++) { | |||
String nodeData[] = split(loadString[i], ','); | |||
for (int j = 3; j < nodeData.length; j++) { | |||
Link(i, PApplet.parseInt(nodeData[j])); | |||
} | |||
} | |||
} | |||
public void saveCSV(String path) { | |||
String saveString = ""; | |||
for (int i = 0; i < nodeCount; i++) { | |||
if (!nodes[i].deleted) { | |||
saveString += nodes[i].title + "," + nodes[i].x + "," + nodes[i].y; | |||
for (int j = 0; j < linkCount; j++) { | |||
if (links[j].parentNode == i && links[j].active) { | |||
saveString += "," + str(links[j].targetNode); | |||
} | |||
} | |||
} | |||
saveString += "\n"; | |||
} | |||
String[] saveArray = split(saveString, '\n'); | |||
saveStrings(path, saveArray); | |||
} | |||
/** | |||
* Keys only get caught for changing a nodes title. | |||
* Previous title is remembered in savedTitle and restored when hitting Escape | |||
*/ | |||
String savedTitle; | |||
public void keyPressed() { | |||
if (key == ESC) { | |||
key = 0; | |||
} | |||
if (i_selectedNode != -1) { | |||
if (keyCode == BACKSPACE) { | |||
if (nodes[i_selectedNode].title.length() > 0) { | |||
nodes[i_selectedNode].title = nodes[i_selectedNode].title.substring(0, nodes[i_selectedNode].title.length()-1); | |||
nodes[i_selectedNode].refreshSize(); | |||
} | |||
} else if (keyCode == DELETE) { | |||
deleteSelectedNode(); | |||
} else if (keyCode == ESC) { | |||
nodes[i_selectedNode].title = savedTitle; | |||
nodes[i_selectedNode].refreshSize(); | |||
deselect(); | |||
} else if (keyCode == RETURN || keyCode == ENTER) { | |||
deselect(); | |||
} else if (keyCode != SHIFT && keyCode != RETURN && keyCode != ENTER && keyCode != CONTROL) { | |||
nodes[i_selectedNode].title = nodes[i_selectedNode].title + key; | |||
nodes[i_selectedNode].refreshSize(); | |||
} | |||
} | |||
} | |||
public void mousePressed() { | |||
if (mouseButton == LEFT) { | |||
/** | |||
* Either press a button... | |||
*/ | |||
boolean buttonClicked = false; | |||
for (int i = 0; i < buttons.length; i++) { | |||
if (buttons[i].hover()) { | |||
buttons[i].click(); | |||
//Not able to break out of here without using a flag | |||
buttonClicked = true; | |||
} | |||
} | |||
if (!buttonClicked) { | |||
for (int i = 0; i < nodeCount; i++) { | |||
/** | |||
* ...or select / drag a node... | |||
*/ | |||
if (nodes[i].hover()) { | |||
i_selectedNode = i; | |||
savedTitle = nodes[i].title; | |||
nodes[i].drag = true; | |||
nodes[i].dragMouseStartPoint.x = mouseX; | |||
nodes[i].dragMouseStartPoint.y = mouseY; | |||
break; | |||
} | |||
/** | |||
* ...or pull a new link out of an inlet or outlet. | |||
*/ | |||
else if (nodes[i].hoverInlet()) { | |||
dragLink = true; | |||
dragOriginLet = -1; | |||
dragOriginId = i; | |||
} else if (nodes[i].hoverOutlet()) { | |||
dragLink = true; | |||
dragOriginLet = 1; | |||
dragOriginId = i; | |||
} | |||
//deselecting the shit out of this for reasons I don't remember, no care to improve right now | |||
deselect(); | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* Create links | |||
*/ | |||
public void mouseReleased() { | |||
for (int i = 0; i < nodeCount; i++) { | |||
nodes[i].drag = false; | |||
if (dragLink && dragOriginId != i) { | |||
if (nodes[i].hoverInlet() && dragOriginLet == 1) { | |||
Link(dragOriginId, i); | |||
} | |||
if (nodes[i].hoverOutlet() && dragOriginLet == -1) { | |||
Link(i, dragOriginId); | |||
} | |||
} | |||
} | |||
/** | |||
* Reset drag&drop states | |||
*/ | |||
dragLink = false; | |||
dragOriginLet = 0; | |||
dragOriginId = -1; | |||
} | |||
/** | |||
* Scaling is tied to textSize. Having the UI on a seperate PGraphics would be better, but it's good enough for now | |||
*/ | |||
int zoomDelta = 3; | |||
public void mouseWheel(MouseEvent event) { | |||