Simple editor to create custom node network graphs aka ontologies
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ontology.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. import processing.core.*;
  2. import processing.data.*;
  3. import processing.event.*;
  4. import processing.opengl.*;
  5. import java.util.HashMap;
  6. import java.util.ArrayList;
  7. import java.io.File;
  8. import java.io.BufferedReader;
  9. import java.io.PrintWriter;
  10. import java.io.InputStream;
  11. import java.io.OutputStream;
  12. import java.io.IOException;
  13. public class ontology extends PApplet {
  14. Node nodes[] = new Node[1000];
  15. Link links[] = new Link[6000];
  16. int textSize = 14;
  17. boolean darkMode = true;
  18. public void setup() {
  19. surface.setResizable(true);
  20. textSize(textSize);
  21. initButtons();
  22. /**
  23. * When saving, a copy of the save-file is stored locally which is opened when starting the program
  24. */
  25. try {
  26. loadCSV(dataPath("save.csv"));
  27. }
  28. catch(Exception e) {
  29. println("No File found, starting blank");
  30. }
  31. }
  32. public void draw() {
  33. background(darkMode? 0 : 255);
  34. /**
  35. * Update and display links
  36. */
  37. for (int i = 0; i < linkCount; i++) {
  38. if (links[i].active) {
  39. links[i].update();
  40. links[i].display();
  41. }
  42. }
  43. /**
  44. * Update and display nodes
  45. */
  46. for (int i = 0; i < nodeCount; i++) {
  47. if (!nodes[i].deleted) {
  48. nodes[i].update();
  49. nodes[i].display();
  50. }
  51. }
  52. /**
  53. * Update and display ui, hide via exporting-flag for easy image-saving
  54. * Not all buttons always show, that's why this isn't in a loop
  55. */
  56. if (!exporting) {
  57. textSize(14);
  58. rectMode(CORNER);
  59. textAlign(CORNER);
  60. buttons[0].display();
  61. if (i_selectedNode != -1) buttons[1].display();
  62. buttons[2].display();
  63. buttons[3].display();
  64. buttons[4].display();
  65. buttons[5].display();
  66. rectMode(CENTER);
  67. textAlign(CENTER);
  68. textSize(textSize);
  69. } else {
  70. save(savePNGpath);
  71. exporting = false;
  72. }
  73. /**
  74. * Pull "wire" out of in/outlet before creating a link (see mouseReleased in ui)
  75. */
  76. if (dragLink) {
  77. stroke(darkMode? 255:0);
  78. strokeWeight(2);
  79. if (dragOriginLet == -1) {
  80. line(nodes[dragOriginId].inletCenter.x, nodes[dragOriginId].inletCenter.y, mouseX, mouseY);
  81. } else if (dragOriginLet == 1) {
  82. line(nodes[dragOriginId].outletCenter.x, nodes[dragOriginId].outletCenter.y, mouseX, mouseY);
  83. }
  84. }
  85. }
  86. int buttonCount = 6;
  87. int i_buttonId;
  88. public void initButtons() {
  89. buttons[0] = new Button(10, 10, "Add Node");
  90. buttons[1] = new Button(104, 10, "Delete Node");
  91. buttons[2] = new Button(10, 50, "Dark/Bright Mode");
  92. buttons[3] = new Button(10, 90, "Save");
  93. buttons[4] = new Button(10, 130, "Open");
  94. buttons[5] = new Button(10, 170, "Export Image");
  95. /* buttons[0] = new Button(10, 10, "Node Hinzufügen");
  96. buttons[1] = new Button(155, 10, "Löschen");
  97. buttons[2] = new Button(10, 50, "Farben umkehren");
  98. buttons[3] = new Button(10, 90, "Speichern");
  99. buttons[4] = new Button(10, 130, "Öffnen");
  100. buttons[5] = new Button(10, 170, "Bild Exportieren");*/
  101. }
  102. public void buttonFunctions(int functionID) {
  103. switch (functionID) {
  104. case(0):
  105. addNode(PApplet.parseInt(random(50, width-50)), PApplet.parseInt(random(30, height-150)), "");
  106. i_selectedNode = nodeCount-1;
  107. break;
  108. case(1):
  109. deleteSelectedNode();
  110. break;
  111. case(2):
  112. darkMode = !darkMode;
  113. break;
  114. case(3):
  115. saveCSV(dataPath("save.csv"));
  116. selectOutput("Where to save .csv file to?", "saveCSVFile");
  117. break;
  118. case(4):
  119. selectInput("Select csv File", "loadCSVFile");
  120. break;
  121. case(5):
  122. selectOutput("Where to export .png image file to?", "savePNGFile");
  123. break;
  124. default:
  125. break;
  126. }
  127. }
  128. Button buttons[] = new Button[buttonCount];
  129. class Button {
  130. int id, x, y, w, h;
  131. String label;
  132. public boolean hover() {
  133. return (mouseX > x && mouseX < x+w && mouseY > y && mouseY < y+h) ? true : false;
  134. }
  135. public void click() {
  136. buttonFunctions(id);
  137. }
  138. Button(int x_, int y_, String label_) {
  139. id = i_buttonId;
  140. i_buttonId++;
  141. x = x_;
  142. y = y_ ;
  143. label = label_;
  144. w = PApplet.parseInt(textWidth(label))+18;
  145. h = 32;
  146. }
  147. public void display() {
  148. stroke(0);
  149. strokeWeight(1);
  150. fill(255, 127);
  151. rect(x, y, w, h, 4);
  152. fill(0);
  153. text(label, x+9, y+h/2+5);
  154. }
  155. }
  156. int linkCount;
  157. boolean dragLink; //state of dragging a link
  158. int dragOriginLet = 0; //dragging from inlet: -1. dragging from outlet: 1.
  159. int dragOriginId = -1; //which nodes out- / inlet did user click on?
  160. public void Link(int parentNode, int targetNode) {
  161. /**
  162. * Check if user is adding or removing a link
  163. */
  164. int exists = -1;
  165. for (int i = 0; i < linkCount; i++) {
  166. if (links[i].parentNode == parentNode && links[i].targetNode == targetNode) {
  167. exists = i;
  168. break;
  169. }
  170. }
  171. if (exists != -1) {
  172. /**
  173. * !active state is pretty much the same as deleted state that the node-class comes with
  174. */
  175. links[exists].active = !links[exists].active;
  176. } else {
  177. links[linkCount] = new Link(parentNode, targetNode);
  178. linkCount++;
  179. }
  180. }
  181. class Link {
  182. int parentNode, targetNode;
  183. boolean active;
  184. PVector link = new PVector(0, 0);
  185. Link(int parentNode_, int targetNode_) {
  186. parentNode = parentNode_;
  187. targetNode = targetNode_;
  188. active = true;
  189. }
  190. /**
  191. * This could be an event that only gets called when nodes are moved or resized.
  192. */
  193. public void update() {
  194. link.x = nodes[targetNode].inletCenter.x - nodes[parentNode].outletCenter.x;
  195. link.y = nodes[targetNode].inletCenter.y - nodes[parentNode].outletCenter.y;
  196. }
  197. public void display() {
  198. stroke(darkMode? 255 : 0);
  199. strokeWeight(2);
  200. pushMatrix();
  201. translate(nodes[parentNode].outletCenter.x, nodes[parentNode].outletCenter.y);
  202. line(0, 0, link.x, link.y);
  203. 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);
  204. rotate(link.heading());
  205. fill(darkMode? 0 : 255);
  206. triangle(0, textSize/2, textSize, 0, 0, -textSize/2);
  207. popMatrix();
  208. }
  209. }
  210. int nodeCount;
  211. int i_selectedNode = -1;
  212. int padding = textSize/3*2; //Padding between text and surrounding box
  213. int letTolerance = 10; //Widens the clickable areas of in/outlets a bit
  214. public void addNode(int x, int y, String title) {
  215. nodes[nodeCount] = new Node(x, y, title);
  216. nodeCount++;
  217. }
  218. public void deselect() {
  219. i_selectedNode = -1;
  220. }
  221. public void deleteSelectedNode() { //Seperate from class for garbage collector reasons
  222. if (i_selectedNode != -1) {
  223. for (int i = 0; i < linkCount; i++) {
  224. if (links[i].targetNode == i_selectedNode || links[i].parentNode == i_selectedNode) {
  225. links[i].active = false;
  226. }
  227. }
  228. nodes[i_selectedNode].deleted = true;
  229. i_selectedNode = -1;
  230. }
  231. }
  232. class Node {
  233. String title;
  234. int id;
  235. int inlets = 2;
  236. int outlets = 2;
  237. int x, y;
  238. int h = textSize+2*padding;
  239. int w = textSize+2*padding;
  240. PVector dragMouseStartPoint = new PVector(0, 0);
  241. PVector outletCenter = new PVector(0, 0);
  242. PVector inletCenter = new PVector(0, 0);
  243. boolean deleted;
  244. boolean drag;
  245. public boolean hover() {
  246. if (mouseX > x-w/2 && mouseX < x+w/2 && mouseY > y-h/2 && mouseY < y+h/2) {
  247. return true;
  248. } else {
  249. return false;
  250. }
  251. }
  252. public boolean hoverOutlet() {
  253. 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) {
  254. return true;
  255. } else {
  256. return false;
  257. }
  258. }
  259. public boolean hoverInlet() {
  260. 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) {
  261. return true;
  262. } else {
  263. return false;
  264. }
  265. }
  266. Node(int x_, int y_, String title_) {
  267. id = nodeCount;
  268. title = title_;
  269. x = x_;
  270. y = y_;
  271. setVectors();
  272. refreshSize();
  273. }
  274. public void setVectors() {
  275. outletCenter.x = x;
  276. outletCenter.y = y-(padding+textSize/2)*1.3f;
  277. inletCenter.x = x;
  278. inletCenter.y = y+(padding+textSize/2)*1.3f;
  279. }
  280. /**
  281. * Called when changing nodes title or when zooming via mouse-wheel
  282. */
  283. public void refreshSize() {
  284. w = PApplet.parseInt(textWidth(title))+2*padding;
  285. h = textSize+2*padding;
  286. }
  287. /**
  288. * Update-function is really only necessary when dragging the node
  289. */
  290. public void update() {
  291. if (drag) {
  292. x -= dragMouseStartPoint.x - mouseX;
  293. dragMouseStartPoint.x = mouseX;
  294. y -= dragMouseStartPoint.y - mouseY;
  295. dragMouseStartPoint.y = mouseY;
  296. setVectors();
  297. }
  298. }
  299. public void display() {
  300. /**
  301. * Selected nodes border
  302. */
  303. if (i_selectedNode == id) {
  304. noStroke();
  305. fill(150, 100);
  306. rect(x, y, w+20, h+20, 4);
  307. }
  308. /**
  309. * Inlet & Outlet
  310. */
  311. stroke(255, darkMode? 255 : 0);
  312. strokeWeight(1);
  313. fill(255, 0, 0);
  314. ellipse(x, y-padding-textSize/2, textSize+padding, textSize+padding);
  315. fill(0);
  316. ellipse(x, y+padding+textSize/2, textSize+padding, textSize+padding);
  317. /**
  318. * Box & Title
  319. */
  320. stroke(127);
  321. strokeWeight(2);
  322. fill(50);
  323. rect(x, y, w, h, 4);
  324. fill(255);
  325. text(title, x, y+textSize/3);
  326. }
  327. }
  328. /**
  329. * Saving and loading is a messy hack!
  330. * You'll get a blank line in your save file for each deleted object.
  331. * That's because of my lack of skills of dodging the garbage collector.
  332. * I didn't loose data to corruption yet.
  333. * Don't touch the save files if you don't want to mess them up!
  334. **/
  335. boolean exporting;
  336. String savePNGpath = dataPath("save.png");
  337. public void savePNGFile(File selection) {
  338. savePNGpath = selection.getPath();
  339. exporting = true;
  340. }
  341. public void saveCSVFile(File selection) {
  342. saveCSV(selection.getPath());
  343. }
  344. public void loadCSVFile(File selection) {
  345. loadCSV(selection.getPath());
  346. }
  347. public void loadCSV(String path) {
  348. for (int i = 0; i < nodeCount; i++) {
  349. //nodes[i].deleted = true;
  350. i_selectedNode = i;
  351. deleteSelectedNode();
  352. }
  353. nodeCount = 0;
  354. for (int i = 0; i < linkCount; i++) {
  355. links[i].active = false;
  356. }
  357. linkCount = 0;
  358. String[] loadString = loadStrings(path);
  359. for (int i = 0; i < loadString.length-1; i++) {
  360. //This is ultra shitty
  361. if (loadString[i].length() == 0) {
  362. addNode(-1000, -1000, " ");
  363. i_selectedNode = nodeCount - 1;
  364. deleteSelectedNode();
  365. //nodes[nodeCount-1].deleted = true;
  366. } else {
  367. String nodeData[] = split(loadString[i], ',');
  368. addNode(PApplet.parseInt(nodeData[1]), PApplet.parseInt(nodeData[2]), nodeData[0]);
  369. }
  370. }
  371. for (int i = 0; i < loadString.length-1; i++) {
  372. String nodeData[] = split(loadString[i], ',');
  373. for (int j = 3; j < nodeData.length; j++) {
  374. Link(i, PApplet.parseInt(nodeData[j]));
  375. }
  376. }
  377. }
  378. public void saveCSV(String path) {
  379. String saveString = "";
  380. for (int i = 0; i < nodeCount; i++) {
  381. if (!nodes[i].deleted) {
  382. saveString += nodes[i].title + "," + nodes[i].x + "," + nodes[i].y;
  383. for (int j = 0; j < linkCount; j++) {
  384. if (links[j].parentNode == i && links[j].active) {
  385. saveString += "," + str(links[j].targetNode);
  386. }
  387. }
  388. }
  389. saveString += "\n";
  390. }
  391. String[] saveArray = split(saveString, '\n');
  392. saveStrings(path, saveArray);
  393. }
  394. /**
  395. * Keys only get caught for changing a nodes title.
  396. * Previous title is remembered in savedTitle and restored when hitting Escape
  397. */
  398. String savedTitle;
  399. public void keyPressed() {
  400. if (key == ESC) {
  401. key = 0;
  402. }
  403. if (i_selectedNode != -1) {
  404. if (keyCode == BACKSPACE) {
  405. if (nodes[i_selectedNode].title.length() > 0) {
  406. nodes[i_selectedNode].title = nodes[i_selectedNode].title.substring(0, nodes[i_selectedNode].title.length()-1);
  407. nodes[i_selectedNode].refreshSize();
  408. }
  409. } else if (keyCode == DELETE) {
  410. deleteSelectedNode();
  411. } else if (keyCode == ESC) {
  412. nodes[i_selectedNode].title = savedTitle;
  413. nodes[i_selectedNode].refreshSize();
  414. deselect();
  415. } else if (keyCode == RETURN || keyCode == ENTER) {
  416. deselect();
  417. } else if (keyCode != SHIFT && keyCode != RETURN && keyCode != ENTER && keyCode != CONTROL) {
  418. nodes[i_selectedNode].title = nodes[i_selectedNode].title + key;
  419. nodes[i_selectedNode].refreshSize();
  420. }
  421. }
  422. }
  423. public void mousePressed() {
  424. if (mouseButton == LEFT) {
  425. /**
  426. * Either press a button...
  427. */
  428. boolean buttonClicked = false;
  429. for (int i = 0; i < buttons.length; i++) {
  430. if (buttons[i].hover()) {
  431. buttons[i].click();
  432. //Not able to break out of here without using a flag
  433. buttonClicked = true;
  434. }
  435. }
  436. if (!buttonClicked) {
  437. for (int i = 0; i < nodeCount; i++) {
  438. /**
  439. * ...or select / drag a node...
  440. */
  441. if (nodes[i].hover()) {
  442. i_selectedNode = i;
  443. savedTitle = nodes[i].title;
  444. nodes[i].drag = true;
  445. nodes[i].dragMouseStartPoint.x = mouseX;
  446. nodes[i].dragMouseStartPoint.y = mouseY;
  447. break;
  448. }
  449. /**
  450. * ...or pull a new link out of an inlet or outlet.
  451. */
  452. else if (nodes[i].hoverInlet()) {
  453. dragLink = true;
  454. dragOriginLet = -1;
  455. dragOriginId = i;
  456. } else if (nodes[i].hoverOutlet()) {
  457. dragLink = true;
  458. dragOriginLet = 1;
  459. dragOriginId = i;
  460. }
  461. //deselecting the shit out of this for reasons I don't remember, no care to improve right now
  462. deselect();
  463. }
  464. }
  465. }
  466. }
  467. /**
  468. * Create links
  469. */
  470. public void mouseReleased() {
  471. for (int i = 0; i < nodeCount; i++) {
  472. nodes[i].drag = false;
  473. if (dragLink && dragOriginId != i) {
  474. if (nodes[i].hoverInlet() && dragOriginLet == 1) {
  475. Link(dragOriginId, i);
  476. }
  477. if (nodes[i].hoverOutlet() && dragOriginLet == -1) {
  478. Link(i, dragOriginId);
  479. }
  480. }
  481. }
  482. /**
  483. * Reset drag&drop states
  484. */
  485. dragLink = false;
  486. dragOriginLet = 0;
  487. dragOriginId = -1;
  488. }
  489. /**
  490. * Scaling is tied to textSize. Having the UI on a seperate PGraphics would be better, but it's good enough for now
  491. */
  492. int zoomDelta = 3;
  493. public void mouseWheel(MouseEvent event) {
  494. float zoom = event.getAmount();
  495. if (zoom < 0) {
  496. textSize += zoomDelta;
  497. } else if (zoom > 0) {
  498. if (textSize - zoomDelta > 0) {
  499. textSize -= zoomDelta;
  500. }
  501. }
  502. padding = textSize/3*2;
  503. for (int i = 0; i < nodeCount; i++) {
  504. nodes[i].refreshSize();
  505. nodes[i].setVectors();
  506. }
  507. }
  508. public void settings() { size(800, 600); }
  509. static public void main(String[] passedArgs) {
  510. String[] appletArgs = new String[] { "ontology" };
  511. if (passedArgs != null) {
  512. PApplet.main(concat(appletArgs, passedArgs));
  513. } else {
  514. PApplet.main(appletArgs);
  515. }
  516. }
  517. }