Simple editor to create custom node network graphs aka ontologies
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

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. }