cobweb.Cobweb.java Source code

Java tutorial

Introduction

Here is the source code for cobweb.Cobweb.java

Source

/*
   Cobweb
   Copyright (C) 2010 Joachim von Eichborn
     
This file is part of Cobweb.
    
Cobweb is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
Cobweb is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with Cobweb.  If not, see <http://www.gnu.org/licenses/>.
 */

package cobweb;

import guicomponents.G4P;
import guicomponents.GButton;
import guicomponents.GCheckbox;
import guicomponents.GLabel;
import guicomponents.GPanel;
import guicomponents.GSlider;
import guicomponents.GTextField;
import guicomponents.GWSlider;

import java.awt.Cursor;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.Random;

import javax.imageio.ImageIO;

import netscape.javascript.JSObject;

import org.apache.commons.codec.binary.Base64;

import particlesystem.Edge;
import particlesystem.Node;
import particlesystem.ParticleSystem;
import particlesystem.Vector2D;
import processing.core.PApplet;
import processing.core.PFont;

/**
 * Main class of the applet. It's setup function gets called when the applet is
 * loaded. Handles the drawing of the network, the user interface and the
 * interaction with the user.
 * 
 * @author Joachim von Eichborn
 * @author http://bioinformatics.charite.de/cobweb
 */
@SuppressWarnings("serial")
public class Cobweb extends PApplet {
    /**
     * Window object that is used to execute Javascript functions on a webpage
     * if available
     */
    JSObject window = null;

    /**
     * Defines the strength of the edge between two nodes that are connected
     */
    static float edgeStrength;

    /**
     * Defines the strength of the repulsion between any two nodes
     */
    static float repulsionStrength;

    /**
     * Defines the size of each node
     */
    static final float NODE_SIZE = 36;

    /**
     * Defines the size of the arrow heads
     */
    static final float ARROW_HEAD = 9;

    /**
     * Defines the radius in which a node is regarded to be clicked on
     */
    static final int DISTANCE_CUTOFF = (int) (NODE_SIZE / 2 + 5);

    /**
     * Sets the transparency for all semi-transparent parts
     */
    static final int ALPHA = 180;

    // the control elements' labels
    final String SMOOTH_BUTTON_FASTER_TEXT = "Faster  ";
    final String SMOOTH_BUTTON_NICER_TEXT = "Smoother  ";

    /**
     * The particle systems that contains all nodes and edges and does the
     * layout of the network
     */
    ParticleSystem particleSys = null;

    /**
     * The currently selected node
     */
    private Node selectedNode = null;

    /**
     * Wether the selected node was fixed at the time he was clicked on
     */
    private boolean selectedNodeFixed = false;

    /**
     * Wether a selection frame is drawn
     */
    boolean selectionFrame = false;

    /**
     * Whether the nodes are fixed or not
     */
    boolean fixed = false;

    /**
     * Whether verbose information like FPS, number of nodes should be drawn
     */
    boolean verbose = false;

    /**
     * The format in which the network was given (xgmml or graphml). When
     * neighbourhood nodes are loaded, they are expected to be in the same
     * format
     */
    private String networkType = null;

    /**
     * Contains the parameters given in the XGMML-file
     */
    Parameters params = null;

    // variables to calculate the correct viewport after zooming and translating
    // the view
    private float translateMouseX = 0f;
    private float translateMouseY = 0f;
    private float translateZoomX = 0f;
    private float translateZoomY = 0f;
    private float origX = 0f;
    private float origY = 0f;

    /**
     * Zoom factor for the network view
     */
    public float scaleFactor;

    /**
     * Length of the edges in the network
     */
    public float edgeLength;

    /**
     * The color of the applets background
     */
    private int backgroundColor = (255 << 24) | Integer.parseInt("FFFFFF", 16);

    /**
     * The color that is used to draw the node names
     */
    private int textColor = (255 << 24) | Integer.parseInt("000000", 16);

    // control elements to change the display style
    GPanel displayPanel = null;

    GCheckbox displayNamesBox = null;
    GCheckbox displaySymbolsBox = null;
    GCheckbox displayPicturesBox = null;
    GWSlider scaleSlider = null;
    GButton fixButton = null;
    GWSlider edgeLengthSlider = null;
    GButton smoothButton = null;
    // control elements to perform graph functions
    GPanel graphPanel = null;

    GTextField nodeSearchTextfield = null;
    GButton selectCommonNeighboursButton = null;
    GButton selectNeighboursButton = null;
    GButton statisticsButton = null;
    GButton onePathButton = null;
    GButton allPathButton = null;
    GButton invertButton = null;
    GButton addNodeButton = null;
    GButton addEdgeButton = null;
    GButton shuffleButton = null;
    GButton relaxButton = null;
    GButton fitButton = null;
    /**
     * The font used to draw the node names
     */
    PFont font = null;

    /**
     * If two nodes are selected, connect them with an edge to each other
     */
    public void addEdgeByButton() {
        if (particleSys.numberOfSelectedNodes() == 1)
            particleSys.makeEdge(particleSys.getSelectedNode(0), particleSys.getSelectedNode(0), 1.0f, null, null,
                    null, edgeStrength, edgeStrength, edgeLength);
        else if (particleSys.numberOfSelectedNodes() == 2)
            particleSys.makeEdge(particleSys.getSelectedNode(0), particleSys.getSelectedNode(1), 1.0f, null, null,
                    null, edgeStrength, edgeStrength, edgeLength);
        else {
            String[] args = new String[] {
                    "Please select exactely two nodes (by selecting the source node and then keeping the CONTROL-key pressed while clicking on the target node)." };
            callJavascriptFunction("show_warning", args);
        }
    }

    /**
     * Add the specified network to the displayed network
     * 
     * @param network
     *            The network that is added
     */
    public void addNetwork(String network) {
        addNetwork(network, width / 2 - translateMouseX, height / 2 - translateMouseY);
    }

    /**
     * Add the specified network to the displayed network
     * 
     * @param network
     *            The specified network
     * @param x
     *            The x coordinate where new nodes are added
     * @param y
     *            The y coordinate where new nodes are added
     */
    public void addNetwork(String network, float x, float y) {
        callJavascriptFunctionStatusMessage("adding network", true);

        if (networkType != null) {
            if (networkType.equals("xgmml")) {
                XGMMLParser parser = new XGMMLParser(this, network);
                parser.setParameters(params);

                if (fixed)
                    parser.parseNodes(particleSys, x, y, true);
                else
                    parser.parseNodes(particleSys, x, y);

                parser.parseEdges(particleSys, edgeLength);
            } else if (networkType.equals("graphml")) {
                GraphMLParser parser = new GraphMLParser(this, network);
                parser.setParameters(params);

                if (fixed)
                    parser.parseNodes(particleSys, x, y, true);
                else
                    parser.parseNodes(particleSys, x, y);

                parser.parseEdges(particleSys, edgeLength);
            } else
                System.out.println("unknown network type: " + networkType);
        } else
            System.out.println("no network type defined");

        System.out.println(network);

        callJavascriptFunctionStatusMessage("added network");
    }

    /**
     * Add a new node with default values to the particle system
     */
    public void addNodeByButton() {
        String id = "new1";

        for (int i = 1; i < MAX_INT; ++i)
            if (!particleSys.containsNode("new" + i)) {
                id = "new" + i;
                break;
            }

        String name = id;

        Node n = particleSys.makeNode(id, name, null, null, params.getServerAdress() + params.getPicturePath(),
                null, null);

        if (n != null) {
            for (int i = 0; i < particleSys.numberOfNodes(); ++i) {
                Node q = particleSys.getNode(i);
                if (n != q)
                    particleSys.makeRepulsion(n, q, repulsionStrength, 20);
            }

            Random random = new Random();
            n.getPosition().set(width / 2 - translateMouseX + (random.nextFloat() * NODE_SIZE * 6 - NODE_SIZE * 3),
                    height / 2 - translateMouseY + (random.nextFloat() * NODE_SIZE * 6 - NODE_SIZE * 3));
            n.fix();

            particleSys.deselectAllNodes();
            selectNode(n);
        }
    }

    /**
     * Select a node by its Id without deselecting previously selected nodes
     * 
     * @param id
     *            The id of the node that is to be selected
     */
    public void addNodeToSelectionById(String id) {
        for (int i = 0; i < particleSys.numberOfNodes(); ++i)
            if (particleSys.getNode(i).getId().equals(id)) {
                selectNode(particleSys.getNode(i));
                break;
            }
    }

    /**
     * Call the given javascript-function
     * 
     * @param function
     *            Name of the function to be executed
     */
    public void callJavascriptFunction(String function) {
        callJavascriptFunction(function, null);
    }

    /**
     * Call the given javascript-function
     * 
     * @param function
     *            Name of the function to be executed
     * @param args
     *            The parameters that are given to the javascript function
     */
    public void callJavascriptFunction(String function, String[] args) {
        if (window != null) {
            try {
                window.call(function, args);
            } catch (Exception ex) {
                println(function + " exception " + args);
            }
        } else
            println(function + " " + args);
    }

    /**
     * Call the Javascript-function to clear the sidebar
     */
    public void callJavascriptFunctionClearSidebar() {
        String[] args = new String[] { params.getLabel(), getBackgroundColorAsHexString(),
                getTextColorAsHexString(), String.valueOf(edgeStrength), String.valueOf(repulsionStrength) };
        callJavascriptFunction("clear_sidebar", args);
    }

    /**
     * Call a java-script function to display the given message
     * 
     * @param message
     *            the message is to be displayed
     */
    public void callJavascriptFunctionStatusMessage(String message) {
        callJavascriptFunctionStatusMessage(message, false);
    }

    /**
     * Call a java-script function to display the given message
     * 
     * @param message
     *            the message that is to be displayed
     * @param spinner
     *            whether to show a loading-spinner or not
     */
    public void callJavascriptFunctionStatusMessage(String message, Boolean spinner) {
        String[] args = null;
        if (spinner)
            args = new String[] { message, "true" };
        else
            args = new String[] { message, "false" };

        callJavascriptFunction("set_status_message", args);
    }

    /**
     * Create the control elements (buttons and sliders) in the GUI
     */
    void createControlElements() {
        // Set the color scheme for the control elements. You can use one of the
        // predefined schemes (values 0 to 6). Alternatively a file named
        // user_col_schema.png with own schemes can be provided in the jar.
        // Please look at http://www.lagers.org.uk/g4p/colorscheme.html for
        // details on how this file has to look like.
        G4P.setColorScheme(this, 7);

        displayPanel = new GPanel(this, "  Display Panel   ", 0, 0, 662, 80);
        displayPanel.setCollapsed(false);
        displayPanel.setOpaque(true);
        displayPanel.setBorder(1);

        GLabel display = new GLabel(this, "Display style:", 0, 5, 85);
        displayPanel.add(display);
        displayNamesBox = new GCheckbox(this, "Names", 5, 24, 70);
        displayNamesBox.setSelected(true);
        displayPanel.add(displayNamesBox);
        displaySymbolsBox = new GCheckbox(this, "Symbols", 5, 44, 70);
        displayPanel.add(displaySymbolsBox);
        displayPicturesBox = new GCheckbox(this, "Pictures", 5, 64, 70);
        displayPicturesBox.setSelected(true);
        displayPanel.add(displayPicturesBox);

        GLabel zoom = new GLabel(this, "Zoom:", 95, 5, 45);
        displayPanel.add(zoom);
        scaleSlider = new GWSlider(this, 95, 34, 150);
        scaleSlider.setLimits(scaleFactor, 0.1f, 2.0f);
        scaleSlider.setValueType(GSlider.DECIMAL);
        displayPanel.add(scaleSlider);

        GLabel nodes = new GLabel(this, "Nodes:", 267, 5, 45);
        displayPanel.add(nodes);
        fixButton = new GButton(this, "Fix/Release Nodes  ", 267, 27, 95, 20);
        displayPanel.add(fixButton);

        GLabel length = new GLabel(this, "Edge length:", 400, 5, 80);
        displayPanel.add(length);
        edgeLengthSlider = new GWSlider(this, 400, 34, 150);
        edgeLengthSlider.setLimits(edgeLength, 1, 600);
        displayPanel.add(edgeLengthSlider);

        GLabel performance = new GLabel(this, "Performance:", 570, 5, 82);
        displayPanel.add(performance);
        smoothButton = new GButton(this, SMOOTH_BUTTON_FASTER_TEXT, 570, 27, 85, 20);
        displayPanel.add(smoothButton);

        graphPanel = new GPanel(this, "  Graph Panel   ", 0, 115, 662, 102);
        graphPanel.setCollapsed(false);
        graphPanel.setOpaque(true);
        graphPanel.setBorder(1);

        GLabel search = new GLabel(this, "Search Node:", 5, 5, 82);
        graphPanel.add(search);
        nodeSearchTextfield = new GTextField(this, "node name", 5, 25, 82, 20);
        graphPanel.add(nodeSearchTextfield);

        GLabel select = new GLabel(this, "Selection:", 94, 5, 120);
        graphPanel.add(select);
        selectCommonNeighboursButton = new GButton(this, "Common Neighbours  ", 94, 25, 136, 20);
        graphPanel.add(selectCommonNeighboursButton);
        selectNeighboursButton = new GButton(this, "All Neighbours  ", 94, 50, 136, 20);
        graphPanel.add(selectNeighboursButton);
        invertButton = new GButton(this, "Invert Selection  ", 94, 75, 136, 20);
        graphPanel.add(invertButton);

        GLabel path = new GLabel(this, "Shortest Path:", 236, 5, 90);
        graphPanel.add(path);
        onePathButton = new GButton(this, "Show one Path  ", 236, 25, 103, 20);
        graphPanel.add(onePathButton);
        allPathButton = new GButton(this, "Show all Paths  ", 236, 50, 103, 20);
        graphPanel.add(allPathButton);

        GLabel manipulate = new GLabel(this, "Manipulate:", 346, 5, 95);
        graphPanel.add(manipulate);
        addNodeButton = new GButton(this, "Add Node  ", 346, 25, 95, 20);
        graphPanel.add(addNodeButton);
        addEdgeButton = new GButton(this, "Add Edge  ", 346, 50, 95, 20);
        graphPanel.add(addEdgeButton);

        GLabel statistics = new GLabel(this, "Statistics:", 447, 5, 100);
        graphPanel.add(statistics);
        statisticsButton = new GButton(this, "Show Statistics  ", 447, 25, 104, 20);
        graphPanel.add(statisticsButton);

        GLabel shuffle = new GLabel(this, "Node Positions:", 559, 5, 95);
        graphPanel.add(shuffle);
        shuffleButton = new GButton(this, "Shuffle  ", 559, 25, 95, 20);
        graphPanel.add(shuffleButton);
        relaxButton = new GButton(this, "Relax  ", 559, 50, 95, 20);
        graphPanel.add(relaxButton);
        fitButton = new GButton(this, "Fit in Window  ", 559, 75, 95, 20);
        graphPanel.add(fitButton);

        displayPanel.setCollapsed(true);
        graphPanel.setCollapsed(true);

    }

    /**
     * Decrease the length of the edge between the given nodes by the given
     * value
     * 
     * @param sourceId
     *            The source node of the edge that is shortened
     * @param targetId
     *            The target node of the edge that is shortened
     * @param amount
     *            How much the edge is shortened
     */
    public void decreaseEdgeLength(String sourceId, String targetId, Float amount) {
        for (int i = 0; i < particleSys.numberOfEdges(); ++i)
            if (particleSys.getEdge(i).getSource().getId().equals(sourceId)
                    && particleSys.getEdge(i).getTarget().getId().equals(targetId))
                particleSys.getEdge(i).decreaseEdgeLength(amount);
    }

    /**
     * Remove the selected Edge
     */
    public void deleteEdge() {
        for (int i = 0; i < particleSys.numberOfEdges(); ++i)
            if (particleSys.getEdge(i).isHighlighted()) {
                particleSys.removeEdge(i);
                break;
            }
    }

    /**
     * Delete a given node
     * 
     * @param n
     *            Node to be deleted
     */
    public void deleteNode(Node n) {
        callJavascriptFunctionStatusMessage("removing " + n.getName(), true);

        String msg = "removed " + n.getName();

        particleSys.removeNode(n);

        callJavascriptFunctionStatusMessage(msg);
        callJavascriptFunctionClearSidebar();
    }

    /**
     * Delete a given node
     * 
     * @param id
     *            Id of the node to be deleted
     */
    public void deleteNode(String id) {
        Node n = particleSys.getNodeById(id);

        if (n != null)
            deleteNode(n);
    }

    /**
     * Delete all selected nodes
     */
    public void deleteSelectedNodes() {
        ArrayList<Node> removeNode = new ArrayList<Node>();

        for (int i = 0; i < particleSys.numberOfSelectedNodes(); ++i)
            removeNode.add(particleSys.getSelectedNode(i));

        for (Iterator<Node> partIt = removeNode.iterator(); partIt.hasNext();)
            deleteNode(partIt.next());
    }

    /**
     * Calculate the euclidian distance between two points
     * 
     * @param x1
     *            x-coordinate of the first point
     * @param y1
     *            y-coordinate of the second point
     * @param x2
     *            x-coordinate of the first point
     * @param y2
     *            y-coordinate of the second point
     * @return The distance between the two points
     */
    float distance(float x1, float y1, float x2, float y2) {
        float dx = x2 - x1;
        float dy = y2 - y1;
        return sqrt((dx * dx) + (dy * dy));
    }

    /**
     * Initiates that the particle system gets updated and draws the network
     */
    public void draw() {
        textFont(font);
        particleSys.tick();

        background(backgroundColor);

        if (verbose)
            text(particleSys.numberOfNodes() + " nodes\n" + particleSys.numberOfEdges() + " edges\n"
                    + (int) frameRate + " FPS", 40, 220);

        scale(scaleFactor);
        translate(translateMouseX + translateZoomX, translateMouseY + translateZoomY);

        drawEdges();
        drawNodes();

        // draw selection frame
        if (selectionFrame) {
            stroke(219, 139, 13);
            fill(219, 139, 13, 80);
            float width = Math.abs(getMouseX() - origX);
            float height = Math.abs(getMouseY() - origY);
            rect(origX + ((getMouseX() - origX) / 2), origY + ((getMouseY() - origY) / 2), width, height);
        }

        resetMatrix();
    }

    /**
     * Draw the caps of straight edges
     * 
     * @param ax
     *            the x position of the source node
     * @param ay
     *            the y position of the source node
     * @param bx
     *            the x position of the target node
     * @param by
     *            the x position of the target node
     * @param shape
     *            the shape of the edge
     */
    void drawEdgeEnding(float ax, float ay, float bx, float by, int shape) {
        float vx, vy, dist, xArrow, yArrow;

        float nodeCorona = DISTANCE_CUTOFF + 15;

        switch (shape) {
        case 0: // none
            break;
        case 1: // arrow
            vx = bx - ax;
            vy = by - ay;
            dist = (float) Math.sqrt(vx * vx + vy * vy);
            vx = vx / dist;
            vy = vy / dist;

            xArrow = (-vy - vx) * ARROW_HEAD;
            yArrow = (vx - vy) * ARROW_HEAD;
            line(bx - vx * nodeCorona, by - vy * nodeCorona, bx + xArrow - vx * nodeCorona,
                    by + yArrow - vy * nodeCorona);

            xArrow = (vy - vx) * ARROW_HEAD;
            yArrow = (-vx - vy) * ARROW_HEAD;
            line(bx - vx * nodeCorona, by - vy * nodeCorona, bx + xArrow - vx * nodeCorona,
                    by + yArrow - vy * nodeCorona);
            break;
        case 2: // dash
            vx = bx - ax;
            vy = by - ay;
            dist = (float) Math.sqrt(vx * vx + vy * vy);
            vx = vx / dist;
            vy = vy / dist;

            line(bx - vx * nodeCorona + vy * ARROW_HEAD, by - vy * nodeCorona - vx * ARROW_HEAD,
                    bx - vy * ARROW_HEAD - vx * nodeCorona, by + vx * ARROW_HEAD - vy * nodeCorona);
            break;
        case 3: // circle
            vx = bx - ax;
            vy = by - ay;
            dist = (float) Math.sqrt(vx * vx + vy * vy);
            vx = vx / dist;
            vy = vy / dist;

            fill(backgroundColor);
            ellipse(bx - vx * nodeCorona, by - vy * nodeCorona, ARROW_HEAD, ARROW_HEAD);
            noFill();
            break;
        }
    }

    /**
     * Draw the edges of the network
     */
    void drawEdges() {
        Node a = null;
        Node b = null;
        Edge e = null;

        float ax, ay, bx, by;

        float halfNodeSize = NODE_SIZE / 2;

        noFill();

        for (int i = particleSys.numberOfEdges() - 1; i >= 0; i--) {
            e = particleSys.getEdge(i);
            if (e.isVisible()) {
                a = e.getSource();
                b = e.getTarget();

                ax = a.getPosition().getX();
                ay = a.getPosition().getY();
                bx = b.getPosition().getX();
                by = b.getPosition().getY();

                strokeWeight(e.getStrokeWeight());

                if (e.isHighlighted())
                    stroke(219, 139, 13);
                else if (a.isHighlighted() || b.isHighlighted()) {
                    if (!particleSys.isDirected())
                        stroke(219, 139, 13);
                    else {
                        if (a.isHighlighted())
                            stroke(219, 65, 13);
                        else
                            stroke(219, 187, 13);
                    }
                } else
                    stroke(e.getColor());

                if (a.equals(b)) { // self-edges
                    arc(ax - halfNodeSize, ay - halfNodeSize, NODE_SIZE, NODE_SIZE, PI / 2, TWO_PI);

                    drawSelfEdgeEnding(ax, ay, e.getShape());
                } else { // edges on other nodes
                    line(ax, ay, bx, by);

                    drawEdgeEnding(ax, ay, bx, by, e.getShape());

                    if (!particleSys.isDirected())
                        drawEdgeEnding(b.getPosition().getX(), b.getPosition().getY(), a.getPosition().getX(),
                                a.getPosition().getY(), e.getShape());
                }
            }
        }

        strokeWeight(2);
    }

    /**
     * Draw the nodes of the network
     */
    void drawNodes() {
        int downShift = 4;
        if (displayPicturesBox.isSelected())
            downShift = 33;

        Boolean drawSymbols = displaySymbolsBox.isSelected();
        Boolean drawImages = displayPicturesBox.isSelected();
        Boolean drawNames = displayNamesBox.isSelected();

        stroke(200);

        Node v = null;
        for (int i = particleSys.numberOfNodes() - 1; i >= 0; i--) {
            v = particleSys.getNode(i);
            if (v.isVisible()) {
                if (v.isHighlighted()) {
                    noStroke();
                    fill(255, 255, 70, ALPHA);
                    ellipse(v.getPosition().getX(), v.getPosition().getY(), NODE_SIZE * 1.5f, NODE_SIZE * 1.5f);

                    stroke(219, 139, 13, ALPHA);
                    textSize(18);
                }

                if (drawSymbols)
                    drawSymbol(v.getPosition().getX(), v.getPosition().getY(), v.getShape(), v.getFillColor());

                if (drawImages) {
                    if (v.hasImage())
                        image(v.getImage(), v.getPosition().getX(), v.getPosition().getY());
                    else
                        drawSymbol(v.getPosition().getX(), v.getPosition().getY(), v.getShape(), v.getFillColor());
                }

                if (drawNames) {
                    fill(textColor);
                    text(v.getName(), v.getPosition().getX(), v.getPosition().getY() + downShift);
                }

                if (v.isHighlighted()) {
                    stroke(200);
                    textSize(14);
                }
            }
        }
    }

    /**
     * Draw the caps of self-edges
     * 
     * @param x
     *            the nodes x position
     * @param y
     *            the nodes y position
     * @param shape
     *            the shape of the edge
     */
    void drawSelfEdgeEnding(float x, float y, int shape) {
        float halfNodeSize = NODE_SIZE / 2;

        switch (shape) {
        case 0: // none
            break;
        case 1: // arrow
            line(x, y - halfNodeSize, x - ARROW_HEAD - 1, y - halfNodeSize - ARROW_HEAD);
            line(x, y - halfNodeSize, x + ARROW_HEAD - 1, y - halfNodeSize - ARROW_HEAD);
            break;
        case 2: // dash
            line(x - ARROW_HEAD, y - halfNodeSize, x + ARROW_HEAD, y - halfNodeSize);
            break;
        case 3: // circle
            fill(backgroundColor);
            ellipse(x, y - halfNodeSize, ARROW_HEAD, ARROW_HEAD);
            noFill();
            break;
        }
    }

    /**
     * Draw a circle representing a node
     * 
     * @param x
     *            x-coordinate of the circle
     * @param y
     *            y-coordinate of the circle
     * @param shape
     *            the shape of the symbol
     * @param col
     *            Fill-color for the circle
     */
    void drawSymbol(float x, float y, int shape, int col) {
        fill(col, ALPHA);

        switch (shape) {
        case 0: // circle
            ellipse(x, y, NODE_SIZE, NODE_SIZE);
            break;
        case 1: // triangle
            triangle(x, y - NODE_SIZE / 2, x + NODE_SIZE / 2, y + NODE_SIZE / 2, x - NODE_SIZE / 2,
                    y + NODE_SIZE / 2);
            break;
        case 2: // box
            rect(x, y, NODE_SIZE, NODE_SIZE);
            break;
        case 3: // rectangle
            rect(x, y, NODE_SIZE, NODE_SIZE / 2);
            break;
        case 4: // rhombus
            quad(x - NODE_SIZE / 2, y - NODE_SIZE / 2, x, y - NODE_SIZE / 2, x + NODE_SIZE / 2, y + NODE_SIZE / 2,
                    x, y + NODE_SIZE / 2);
            break;
        case 5: // hexagon
            beginShape();
            vertex(x - NODE_SIZE / 2, y);
            vertex(x - NODE_SIZE / 4, y - NODE_SIZE / 2);
            vertex(x + NODE_SIZE / 4, y - NODE_SIZE / 2);
            vertex(x + NODE_SIZE / 2, y);
            vertex(x + NODE_SIZE / 4, y + NODE_SIZE / 2);
            vertex(x - NODE_SIZE / 4, y + NODE_SIZE / 2);
            endShape(CLOSE);
            break;
        case 6: // octagon
            beginShape();
            vertex(x - NODE_SIZE / 2, y - NODE_SIZE / 4);
            vertex(x - NODE_SIZE / 4, y - NODE_SIZE / 2);
            vertex(x + NODE_SIZE / 4, y - NODE_SIZE / 2);
            vertex(x + NODE_SIZE / 2, y - NODE_SIZE / 4);
            vertex(x + NODE_SIZE / 2, y + NODE_SIZE / 4);
            vertex(x + NODE_SIZE / 4, y + NODE_SIZE / 2);
            vertex(x - NODE_SIZE / 4, y + NODE_SIZE / 2);
            vertex(x - NODE_SIZE / 2, y + NODE_SIZE / 4);
            endShape(CLOSE);
            break;
        case 7: // horizontal ellipsis
            ellipse(x, y, NODE_SIZE, NODE_SIZE / 2);
            break;
        case 8: // vertical ellipsis
            ellipse(x, y, NODE_SIZE / 2, NODE_SIZE);
            break;
        }
    }

    /**
     * Scales and translates the view to make the network fit into the window
     */
    public void fitNetworkInWindow() {
        Node n = null;
        float xMin = MAX_FLOAT;
        float xMax = MIN_FLOAT;
        float yMin = MAX_FLOAT;
        float yMax = MIN_FLOAT;

        for (int i = 0; i < particleSys.numberOfNodes(); ++i) {
            n = particleSys.getNode(i);
            if (n.isVisible()) {
                if (n.getPosition().getX() > xMax)
                    xMax = n.getPosition().getX();
                if (n.getPosition().getX() < xMin)
                    xMin = n.getPosition().getX();

                if (n.getPosition().getY() > yMax)
                    yMax = n.getPosition().getY();
                if (n.getPosition().getY() < yMin)
                    yMin = n.getPosition().getY();
            }
        }

        translateMouseX = width / 2 - (xMin + xMax) / 2;
        translateMouseY = height / 2 - (yMin + yMax) / 2;

        float newScaleFactor = width / (xMax - xMin + 2 * NODE_SIZE);
        if (width / (yMax - yMin) < newScaleFactor)
            newScaleFactor = height / (yMax - yMin + 2 * NODE_SIZE);

        if (newScaleFactor < 1.0f) {
            scaleSlider.setValue(newScaleFactor);
            // the values have to be set manually, otherwise fitting the network
            // when the applet opens will not work because both panels are
            // collapsed at that time
            scaleFactor = newScaleFactor;
            translateZoomX = width / scaleFactor / 2 - width / 2;
            translateZoomY = height / scaleFactor / 2 - height / 2;
        }
    }

    /**
     * Fix all selected nodes
     */
    public void fixSelectedNodePositions() {
        if (selectedNode != null) // if one node is dragged at the moment, don't
            // change its status but remember to change
            // it after dragging has ended
            selectedNodeFixed = !selectedNodeFixed;
        else
            // if no node is dragged
            for (int i = 0; i < particleSys.numberOfSelectedNodes(); ++i) {
                if (particleSys.getSelectedNode(i).isFixed()) {
                    particleSys.getSelectedNode(i).free();
                    callJavascriptFunctionStatusMessage(
                            "released node " + particleSys.getSelectedNode(i).getName());
                } else {
                    particleSys.getSelectedNode(i).fix();
                    callJavascriptFunctionStatusMessage("fixed node " + particleSys.getSelectedNode(i).getName());
                }
            }
    }

    /**
     * Return the applets background color as a hexadecimal string
     * 
     * @return The background color as a hexadecimal string
     */
    public String getBackgroundColorAsHexString() {
        return "#" + Integer.toHexString(backgroundColor).substring(2).toUpperCase();
    }

    /**
     * Return the x-coordinate of the mouse-pointer
     * 
     * @return The x-coordinate of the mouse pointer scaled and translated in
     *         accordance with the current view
     */
    public float getMouseX() {
        return (mouseX / scaleFactor - translateMouseX - translateZoomX);
    }

    /**
     * Return the y-coordinate of the mouse-pointer
     * 
     * @return The y-coordinate of the mouse pointer scaled and translated in
     *         accordance with the current view
     */
    public float getMouseY() {
        return (mouseY / scaleFactor - translateMouseY - translateZoomY);
    }

    /**
     * Return the edge on which the user has clicked, if any
     * 
     * @return The selected edge or null if no visible edge is nearby the
     *         clicked position
     */
    public Edge getNearestEdge() {
        float curX = getMouseX();
        float curY = getMouseY();

        Edge e = null;
        Edge nearest = null;
        float minDevSquared = 100;
        float devSquared = 0;

        for (int i = 0; i < particleSys.numberOfEdges(); ++i) {
            e = particleSys.getEdge(i);

            if (!e.isVisible())
                continue;

            if (e.getSource().equals(e.getTarget()))
                devSquared = e.getDistanceToPoint(curX, curY, NODE_SIZE);
            else
                devSquared = e.getDistanceToPoint(curX, curY);

            if (devSquared < minDevSquared) {
                minDevSquared = devSquared;
                nearest = e;
            }

        }
        return nearest;
    }

    /**
     * Return the node on which the user has clicked, if any
     * 
     * @return The selected node or null if no node is nearby the clicked
     *         position
     */
    public Node getNearestNode() {
        float mx = getMouseX();
        float my = getMouseY();

        Node n = null;
        float d = DISTANCE_CUTOFF;

        for (int i = 0; i < particleSys.numberOfNodes(); ++i) {
            Node v = particleSys.getNode(i);
            float dTemp = distance(mx, my, v.getPosition().getX(), v.getPosition().getY());
            if ((dTemp < d) && dTemp < DISTANCE_CUTOFF) {
                d = dTemp;
                n = v;
            }
        }

        return n;
    }

    /**
     * Return an Base64 encoded GraphML-representation of the current network
     * 
     * @return The Base64 encoded GraphML-string representing the current
     *         network
     */
    public String getNetworkAsGraphMLBase64String() {
        return Base64.encodeBase64String(getNetworkAsGraphMLString().getBytes());
    }

    /**
     * Return an GraphML-representation of the current network
     * 
     * @return The GraphML-string representing the current network
     */
    public String getNetworkAsGraphMLString() {
        String graphml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";

        graphml += "<graphml xmlns=\"http://graphml.graphdrawing.org/xmlns\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd\">\n";

        graphml += "<key id=\"picturepath\" for=\"graph\" attr.name=\"picturepath\" attr.type=\"string\" />\n";
        graphml += "<key id=\"neighbourhoodscript\" for=\"graph\" attr.name=\"neighbourhoodscript\" attr.type=\"string\" />\n";
        graphml += "<key id=\"name\" for=\"node\" attr.name=\"label\" attr.type=\"string\" />\n";
        graphml += "<key id=\"shape\" for=\"node\" attr.name=\"shape\" attr.type=\"string\" />\n";
        graphml += "<key id=\"r\" for=\"node\" attr.name=\"r\" attr.type=\"string\" />\n";
        graphml += "<key id=\"g\" for=\"node\" attr.name=\"g\" attr.type=\"string\" />\n";
        graphml += "<key id=\"b\" for=\"node\" attr.name=\"b\" attr.type=\"string\" />\n";
        graphml += "<key id=\"description\" for=\"node\" attr.name=\"description\" attr.type=\"string\" />\n";
        graphml += "<key id=\"picture\" for=\"node\" attr.name=\"picture\" attr.type=\"string\" />\n";
        graphml += "<key id=\"x\" for=\"node\" attr.name=\"x\" attr.type=\"float\" />\n";
        graphml += "<key id=\"y\" for=\"node\" attr.name=\"y\" attr.type=\"float\" />\n";
        graphml += "<key id=\"edgelabel\" for=\"edge\" attr.name=\"label\" attr.type=\"string\" />\n";
        graphml += "<key id=\"edgeshape\" for=\"edge\" attr.name=\"shape\" attr.type=\"string\" />\n";
        graphml += "<key id=\"weight\" for=\"edge\" attr.name=\"weight\" attr.type=\"float\" />\n";
        graphml += "<key id=\"edger\" for=\"edge\" attr.name=\"r\" attr.type=\"string\" />\n";
        graphml += "<key id=\"edgeg\" for=\"edge\" attr.name=\"g\" attr.type=\"string\" />\n";
        graphml += "<key id=\"edgeb\" for=\"edge\" attr.name=\"b\" attr.type=\"string\" />\n";

        graphml += "<graph id=\"" + params.getLabel() + "\" edgedefault=\""
                + ((particleSys.isDirected()) ? "directed" : "undirected") + "\">\n";

        graphml += params.toGraphML();

        for (int i = 0; i < particleSys.numberOfNodes(); i++)
            graphml += particleSys.getNode(i).toGraphML();

        for (int i = 0; i < particleSys.numberOfEdges(); i++)
            graphml += particleSys.getEdge(i).toGraphML();

        graphml += "</graph>\n</graphml>\n";

        try {
            return new String(graphml.getBytes(), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return graphml;
        }
    }

    /**
     * Return an Base64 encoded XGMML-representation of the current network
     * 
     * @return The Base64 encoded XGMML-string representing the current network
     */
    public String getNetworkAsXGMMLBase64String() {
        return Base64.encodeBase64String(getNetworkAsXGMMLString().getBytes());
    }

    /**
     * Return an XGMML-representation of the current network
     * 
     * @return The XGMML-string representing the current network
     */
    public String getNetworkAsXGMMLString() {
        String xgmml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";

        xgmml += "<graph label=\"" + params.getLabel() + "\" directed=\"" + ((particleSys.isDirected()) ? "1" : "0")
                + "\" xmlns=\"http://www.cs.rpi.edu/XGMML\" schemaLocation=\"http://www.cs.rpi.edu/~puninj/XGMML/xgmml.xsd\">\n";

        xgmml += params.toXGMML();

        for (int i = 0; i < particleSys.numberOfNodes(); i++)
            xgmml += particleSys.getNode(i).toXGMML();

        for (int i = 0; i < particleSys.numberOfEdges(); i++)
            xgmml += particleSys.getEdge(i).toXGMML();

        xgmml += "</graph>\n";

        try {
            return new String(xgmml.getBytes(), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return xgmml;
        }
    }

    /**
     * Returns the particle system
     * 
     * @return the particle system
     */
    public ParticleSystem getParticleSystem() {
        return particleSys;
    }

    /**
     * Return an Base64 encoded String containing a screenshot of the applet
     * content in gif format
     * 
     * @return The Base64 encoded String containing a screenshot of the applet
     *         content in gif format
     */
    public String getScreenshotAsBase64String() {
        try {
            // stop automatic redrawing and draw three time to get a complete
            // image of the network without the panels
            noLoop();
            draw();
            draw();
            draw();

            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            ImageIO.write((BufferedImage) this.g.image, "gif", baos);

            byte[] bytes = baos.toByteArray();

            loop(); // restart automatic drawing

            return Base64.encodeBase64String(bytes);
        } catch (Exception e) {
            loop(); // restart automatic drawing

            String[] args = new String[] { "Generating screenshot failed." };
            callJavascriptFunction("show_warning", args);

            return "";
        }
    }

    /**
     * Return the text color as a hexadecimal string
     * 
     * @return The text color as a hexadecimal string
     */
    public String getTextColorAsHexString() {
        return "#" + Integer.toHexString(textColor).substring(2).toUpperCase();
    }

    /**
     * Handle events generated by the buttons in the GUI
     * 
     * @param button
     *            The button event that was generated
     */
    public void handleButtonEvents(GButton button) {
        if (!(displayPanel.isCollapsed() && graphPanel.isCollapsed())) {
            if (button == fixButton && button.eventType == GButton.CLICKED) {
                if (fixed) {
                    for (int i = 0; i < particleSys.numberOfNodes(); ++i)
                        particleSys.getNode(i).free();
                } else {
                    for (int i = 0; i < particleSys.numberOfNodes(); ++i)
                        particleSys.getNode(i).fix();
                }
                fixed = !fixed;
            } else if (button == smoothButton && button.eventType == GButton.CLICKED) {
                if (smoothButton.getText().equals(SMOOTH_BUTTON_FASTER_TEXT)) {
                    smoothButton.setText(SMOOTH_BUTTON_NICER_TEXT);
                    noSmooth();
                } else {
                    smoothButton.setText(SMOOTH_BUTTON_FASTER_TEXT);
                    smooth();
                }
            } else if (button == selectCommonNeighboursButton && button.eventType == GButton.CLICKED) {
                selectCommonNeighboursOfSelectedNodes();
            } else if (button == selectNeighboursButton && button.eventType == GButton.CLICKED) {
                selectALLNeighboursOfSelectedNodes();
            } else if (button == statisticsButton && button.eventType == GButton.CLICKED) {
                showStatistics();
            } else if (button == onePathButton && button.eventType == GButton.CLICKED) {
                showOnePath();
            } else if (button == allPathButton && button.eventType == GButton.CLICKED) {
                showAllPath();
            } else if (button == invertButton && button.eventType == GButton.CLICKED) {
                invertSelection();
            } else if (button == addNodeButton && button.eventType == GButton.CLICKED) {
                addNodeByButton();
            } else if (button == addEdgeButton && button.eventType == GButton.CLICKED) {
                addEdgeByButton();
            } else if (button == shuffleButton && button.eventType == GButton.CLICKED) {
                Random rand = new Random();
                for (int i = 0; i < particleSys.numberOfNodes(); ++i) {
                    particleSys.getNode(i).free();
                    particleSys.getNode(i).setPosition(new Vector2D(rand.nextFloat() + width / 2 - translateMouseX,
                            rand.nextFloat() + height / 2 - translateMouseY));
                }

                for (int i = 0; i < 2000; ++i)
                    particleSys.tick();
                fitNetworkInWindow();
            } else if (button == relaxButton && button.eventType == GButton.CLICKED) {
                for (int i = 0; i < 2000; ++i)
                    particleSys.tick();
            } else if (button == fitButton && button.eventType == GButton.CLICKED) {
                fitNetworkInWindow();
            }
        }

    }

    /**
     * Handle events generated by checkboxes in the GUI
     * 
     * @param checkbox
     *            the checkbox that generated the event
     */
    public void handleCheckboxEvents(GCheckbox checkbox) {
        if (displayPanel.isCollapsed() && graphPanel.isCollapsed())
            checkbox.setSelected(!checkbox.isSelected());
    }

    /**
     * Handle events generated by the sliders in the GUI
     * 
     * @param slider
     *            The slider event that was generated
     */
    public void handleSliderEvents(GSlider slider) {
        if (!displayPanel.isCollapsed()) {
            if (slider == scaleSlider) {
                scaleFactor = slider.getValuef();
                translateZoomX = width / scaleFactor / 2 - width / 2;
                translateZoomY = height / scaleFactor / 2 - height / 2;
            } else if (slider == edgeLengthSlider) {
                edgeLength = slider.getValuef();
                for (int i = 0; i < particleSys.numberOfEdges(); ++i)
                    particleSys.getEdge(i).setRestLength(edgeLength);
            }
        }
    }

    /**
     * Handle events generated by the textfields in the gui
     * 
     * @param textfield
     *            the textfield event that was generated
     */
    public void handleTextFieldEvents(GTextField textfield) {
        if (!graphPanel.isCollapsed())
            if (textfield == nodeSearchTextfield)
                selectNodesByName(textfield.getText());
    }

    /**
     * Increase the length of the edge between the given nodes by the given
     * value
     * 
     * @param sourceId
     *            The source node of the edge that is elongated
     * @param targetId
     *            The target node of the edge that is elongated
     * @param amount
     *            How much the edge is elongated
     */
    public void increaseEdgeLength(String sourceId, String targetId, Float amount) {
        for (int i = 0; i < particleSys.numberOfEdges(); ++i)
            if (particleSys.getEdge(i).getSource().getId().equals(sourceId)
                    && particleSys.getEdge(i).getTarget().getId().equals(targetId))
                particleSys.getEdge(i).increaseEdgeLength(amount);
    }

    /**
     * Invert the current selection of nodes
     */
    public void invertSelection() {
        Node n = null;

        ArrayList<Node> unselectedNodes = new ArrayList<Node>();

        for (int i = 0; i < particleSys.numberOfNodes(); i++) {
            n = particleSys.getNode(i);

            if (n.isHighlighted())
                particleSys.deselectNode(n);
            else
                unselectedNodes.add(n);
        }

        callJavascriptFunctionClearSidebar();

        for (int i = 0; i < unselectedNodes.size(); ++i)
            selectNode(unselectedNodes.get(i));
    }

    /**
     * Handle key-press events
     */
    public void keyPressed() {
        if (key == ' ')
            fixSelectedNodePositions();
        else if (key == 'v')
            verbose = !verbose;
    }

    /**
     * Load the neighbourhood (conected nodes) of the given node by sending a
     * request to the server using the defined neighbourhood-script
     * 
     * @param n
     *            The node whose neighbourhood is to be loaded
     */
    public void loadNeighbourhood(Node n) {
        if (params.getNeighbourhoodScript() != null) {
            this.setCursor(new Cursor(Cursor.WAIT_CURSOR));
            callJavascriptFunctionStatusMessage("loading neighbourhood for " + n.getName(), true);

            String lines[] = loadStrings(
                    params.getServerAdress() + params.getNeighbourhoodScript() + "?id=" + n.getId());
            StringBuffer stringBuf = new StringBuffer();
            for (int i = 0; i < lines.length; i++)
                stringBuf.append(lines[i]);
            String content = stringBuf.toString();

            addNetwork(content, n.getPosition().getX(), n.getPosition().getY());

            this.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));

            callJavascriptFunctionStatusMessage("loaded neighbourhood for " + n.getName());
        }
    }

    /**
     * Load the neighbourhood (conected nodes) of the given node by sending a
     * request to the server using the defined neighbourhood-script
     * 
     * @param id
     *            The id of the node whose neighbourhood is to be loaded
     */
    public void loadNeighbourhood(String id) {
        Node n = particleSys.getNodeById(id);

        if (n != null)
            loadNeighbourhood(n);
    }

    /**
     * Handle mouse-click events
     */
    public void mouseClicked() {
        if (displayPanel.isOverPanel(mouseX, mouseY))
            return;

        if (mouseEvent.getClickCount() == 1)
            mouseSingleClicked();
        else if (mouseEvent.getClickCount() == 2)
            mouseDoubleClicked();
    }

    /**
     * Check whether a node was double-clicked. If so, it's neighbourhood is
     * loaded
     */
    public void mouseDoubleClicked() {
        if (mouseButton == LEFT) {
            Node n = getNearestNode();

            if ((n != null) && (params.getNeighbourhoodScript() != null))
                loadNeighbourhood(n);
        }
    }

    /**
     * Handle mouse drags. If the left mouse-button is pressed, the currently
     * selected node is moved to the mouse-pointer position. If the right
     * mouse-button is pressed, the view is translated.
     */
    public void mouseDragged() {
        if (mouseButton == LEFT) {
            if (selectedNode != null) {
                cursor(CROSS);
                selectedNode.getPosition().set(getMouseX(), getMouseY());
            }
        } else if (mouseButton == RIGHT || mouseButton == CENTER) {
            cursor(MOVE);
            translateMouseX = getMouseX() + translateMouseX - origX;
            translateMouseY = getMouseY() + translateMouseY - origY;
        }
    }

    /**
     * Make the panels transparent, if the mouse is not over them
     */
    public void mouseMoved() {
        if (displayPanel.isOverPanel(mouseX, mouseY)) {
            displayPanel.setAlpha(250);
            displayPanel.setCollapsed(false);
        } else {
            displayPanel.setAlpha(100);
            displayPanel.setCollapsed(true);
        }

        if (graphPanel.isOverPanel(mouseX, mouseY)) {
            graphPanel.setAlpha(250);
            graphPanel.setCollapsed(false);
        } else {
            graphPanel.setAlpha(100);
            graphPanel.setCollapsed(true);
        }
    }

    /**
     * Handle mouse-pressed events. Either selects a node, an edge or
     * initializes the drawing of a selection frame.
     */
    public void mousePressed() {
        if (displayPanel.isOverPanel(mouseX, mouseY) || graphPanel.isOverPanel(mouseX, mouseY))
            return;

        if (mouseButton == LEFT) {
            for (int j = 0; j < particleSys.numberOfEdges(); j++)
                particleSys.getEdge(j).dehighlight();

            if (!(keyPressed && keyCode == CONTROL))
                particleSys.deselectAllNodes();

            selectedNode = getNearestNode();

            if (selectedNode != null) {
                selectedNodeFixed = selectedNode.isFixed();
                selectedNode.fix();
                selectNode(selectedNode);
                cursor(CROSS);
            } else {
                Edge clickedEdge = getNearestEdge();

                if (clickedEdge != null) {
                    selectEdge(clickedEdge);
                } else {
                    origX = getMouseX();
                    origY = getMouseY();
                    selectionFrame = true;
                    callJavascriptFunctionClearSidebar();
                }
            }
        } else if (mouseButton == RIGHT || mouseButton == CENTER) {
            cursor(MOVE);
            origX = getMouseX();
            origY = getMouseY();
        }
    }

    /**
     * Handle mouse-released events. Releases the selected node if a node was
     * dragged. If a selection frame was active, the nodes inside the frame are
     * selected.
     */
    public void mouseReleased() {
        if (mouseButton == LEFT) {
            if (selectedNode != null) {
                if (!selectedNodeFixed)
                    selectedNode.free();
                selectedNode = null;
            } else if (selectionFrame) {
                selectionFrame = false;
                selectNodesByFrame();
            }
        }
        cursor(Cursor.DEFAULT_CURSOR);
    }

    /**
     * Delete a node if it was right-clicked while the SHIFT-key was pressed
     */
    public void mouseSingleClicked() {
        if ((mouseButton == RIGHT) && mouseEvent.isShiftDown()) {
            Node n = getNearestNode();

            if (n != null)
                deleteNode(n);
        }
    }

    /**
     * Change the selection to all neighbours of the currently selected nodes
     */
    public void selectALLNeighboursOfSelectedNodes() {
        if (particleSys.numberOfSelectedNodes() == 0) {
            String[] args = new String[] { "Please select at least one node" };
            callJavascriptFunction("show_warning", args);
        }

        ArrayList<Node> neighbours = GraphFunctions.getAllNeighbours(particleSys);

        particleSys.deselectAllNodes();

        if (neighbours.isEmpty()) {
            String[] args = new String[] { "No neighbours found." };
            callJavascriptFunction("show_warning", args);
        } else {
            for (int i = 0; i < neighbours.size(); ++i)
                particleSys.selectNode(neighbours.get(i));
        }
    }

    /**
     * Change the selection to the common neighbours of the currently selected
     * nodes
     */
    public void selectCommonNeighboursOfSelectedNodes() {
        if (particleSys.numberOfSelectedNodes() == 0) {
            String[] args = new String[] { "Please select at least one node" };
            callJavascriptFunction("show_warning", args);
        }

        ArrayList<Node> neighbours = GraphFunctions.getCommonNeighbours(particleSys);

        particleSys.deselectAllNodes();

        if (neighbours.isEmpty()) {
            String[] args = new String[] { "No common neighbours found." };
            callJavascriptFunction("show_warning", args);
        } else {
            for (int i = 0; i < neighbours.size(); ++i)
                particleSys.selectNode(neighbours.get(i));
        }
    }

    /**
     * Select the given Edge, i.e. highlight it and call a javascript-function
     * with details about the edge
     * 
     * @param e
     *            The edge that is to be selected
     */
    public void selectEdge(Edge e) {
        e.highlight();
        Node a = e.getSource();
        Node b = e.getTarget();
        String[] args = new String[] { a.getId(), a.getName(), a.getDescription(), b.getId(), b.getName(),
                b.getDescription(), e.getAnnotation(), String.valueOf(e.getShape()), String.valueOf(e.getWeight()),
                e.getColorAsHexString() };
        callJavascriptFunction("show_edge_details", args);
    }

    /**
     * Select the given Node, i.e. highlight it and call a javascript-funtion
     * with details about the node
     * 
     * @param n
     *            The node that is to be selected
     */
    public void selectNode(Node n) {
        if (!n.isVisible())
            return;

        if (n.isHighlighted())
            return;

        particleSys.selectNode(n);

        if (particleSys.numberOfSelectedNodes() == 1) {
            String[] args = new String[] { n.getId(), n.getName(), n.getDescription(), n.getPictureName(),
                    String.valueOf(n.getShape()), n.getFillColorAsHexString(), params.getNeighbourhoodScript() };
            callJavascriptFunction("show_node_details", args);
        } else if (particleSys.numberOfSelectedNodes() == 2) {
            String[] args = new String[] { n.getId(), n.getName(), n.getPictureName(), String.valueOf(n.getShape()),
                    n.getFillColorAsHexString(), params.getNeighbourhoodScript() };
            callJavascriptFunction("create_node_list", args);

            for (int i = 0; i < particleSys.numberOfSelectedNodes(); i++)
                if (!particleSys.getSelectedNode(i).equals(n)) {
                    Node n2 = particleSys.getSelectedNode(i);
                    args = new String[] { n2.getId(), n2.getName(), n2.getPictureName(),
                            String.valueOf(n2.getShape()), n2.getFillColorAsHexString(),
                            params.getNeighbourhoodScript() };
                    callJavascriptFunction("add_node_to_list", args);
                }
        } else {
            String[] args = new String[] { n.getId(), n.getName(), String.valueOf(n.getShape()),
                    n.getFillColorAsHexString(), params.getNeighbourhoodScript() };
            callJavascriptFunction("add_node_to_list", args);
        }
    }

    /**
     * Select a node by its Id
     * 
     * @param id
     *            The id of the node that is to be selected
     */
    public void selectNodeById(String id) {
        particleSys.deselectAllNodes();

        for (int i = 0; i < particleSys.numberOfNodes(); ++i)
            if (particleSys.getNode(i).getId().equals(id)) {
                selectNode(particleSys.getNode(i));
                break;
            }
    }

    /**
     * Select all nodes that are in the current selection frame
     */
    public void selectNodesByFrame() {
        float otherX = getMouseX();
        float otherY = getMouseY();

        float left = 0;
        float right = 0;
        float top = 0;
        float bottom = 0;

        if (otherX < origX) {
            left = otherX;
            right = origX;
        } else {
            left = origX;
            right = otherX;
        }

        if (otherY < origY) {
            top = otherY;
            bottom = origY;
        } else {
            top = origY;
            bottom = otherY;
        }

        Node n = null;
        for (int i = 0; i < particleSys.numberOfNodes(); i++) {
            n = particleSys.getNode(i);
            if (left < n.getPosition().getX() && n.getPosition().getX() < right && top < n.getPosition().getY()
                    && n.getPosition().getY() < bottom)
                selectNode(n);
        }

    }

    /**
     * Select nodes by their name
     * 
     * @param name
     *            The name of the nodes that is to be selected. All nodes whose
     *            name start with the given string are selected.
     */
    public void selectNodesByName(String name) {
        int nameLength = name.length();

        if (nameLength == 0) {
            particleSys.deselectAllNodes();
            return;
        }

        Node n = null;

        particleSys.deselectAllNodes();

        for (int i = 0; i < particleSys.numberOfNodes(); i++) {
            n = particleSys.getNode(i);

            if (n.getName().length() < nameLength)
                continue;

            if (particleSys.getNode(i).getName().substring(0, nameLength).equalsIgnoreCase(name))
                selectNode(particleSys.getNode(i));
        }
    }

    /**
     * Set the background color for the applet
     * 
     * @param c
     *            The background color as a hexadecimal string
     */
    public void setBackgroundColor(String c) {
        try {
            if (c.charAt(0) == '#')
                c = c.substring(1);
            this.backgroundColor = (255 << 24) | Integer.parseInt(c, 16);
        } catch (Exception e) {
            this.backgroundColor = 255;
        }
    }

    /**
     * Set the annotation of the currently selected edge
     * 
     * @param a
     *            The annotation that is set
     */
    public void setEdgeAnnotation(String a) {
        try {
            for (int i = 0; i < particleSys.numberOfEdges(); ++i)
                if (particleSys.getEdge(i).isHighlighted()) {
                    particleSys.getEdge(i).setAnnotation(a);
                    break;
                }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Set the color of the currently selected edge
     * 
     * @param c
     *            The color that is set
     */
    public void setEdgeColor(String c) {
        try {
            for (int i = 0; i < particleSys.numberOfEdges(); ++i)
                if (particleSys.getEdge(i).isHighlighted()) {
                    particleSys.getEdge(i).setColor(c);
                    break;
                }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Set the shape of the current edge
     * 
     * @param s
     *            The shape that is set
     */
    public void setEdgeShape(String s) {
        try {
            for (int i = 0; i < particleSys.numberOfEdges(); ++i)
                if (particleSys.getEdge(i).isHighlighted()) {
                    particleSys.getEdge(i).setShape(Integer.parseInt(s));
                    break;
                }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void setEdgeStrength(String s) {
        try {
            float newStrength = Float.parseFloat(s);

            if (newStrength > 1)
                newStrength = 1;
            else if (newStrength < 0.005)
                newStrength = 0.005f;

            Edge e = null;
            float factor = 0;

            for (int i = 0; i < particleSys.numberOfEdges(); i++) {
                e = particleSys.getEdge(i);
                factor = edgeStrength / e.getStrength();
                e.setStrength(newStrength / factor);
            }

            edgeStrength = newStrength;
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * Set the weight of the currently selected edge
     * 
     * @param w
     *            The weight that is set
     */
    public void setEdgeWeight(String w) {
        try {
            for (int i = 0; i < particleSys.numberOfEdges(); ++i)
                if (particleSys.getEdge(i).isHighlighted()) {
                    try {
                        particleSys.getEdge(i).setWeight(Float.valueOf(w));
                    } catch (Exception e) {
                        particleSys.getEdge(i).setWeight(1.0f);
                    }
                    break;
                }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Set the graph label
     * 
     * @param l
     *            The graph label that is set
     */
    public void setGraphLabel(String l) {
        try {
            params.setLabel(l);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * If one node is selected, set its description
     * 
     * @param d
     *            The description that is set
     */
    public void setNodeDescription(String d) {
        try {
            if (particleSys.numberOfSelectedNodes() == 1)
                particleSys.getSelectedNode(0).setDescription(d);
            else {
                String[] args = new String[] { "Please select exactely one node." };
                callJavascriptFunction("show_warning", args);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * If one node is selected, set its fill color
     * 
     * @param c
     *            The color that is set
     */
    public void setNodeFillColor(String c) {
        try {
            if (particleSys.numberOfSelectedNodes() == 1)
                particleSys.getSelectedNode(0).setFillColor(c);
            else {
                String[] args = new String[] { "Please select exactely one node." };
                callJavascriptFunction("show_warning", args);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * If one node is selected, set its name
     * 
     * @param n
     *            The name that is set
     */
    public void setNodeName(String n) {
        try {
            if (particleSys.numberOfSelectedNodes() == 1)
                particleSys.getSelectedNode(0).setName(n);
            else {
                String[] args = new String[] { "Please select exactely one node." };
                callJavascriptFunction("show_warning", args);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * If one node is selected, set its picture
     * 
     * @param p
     *            The picture that is set
     */
    public void setNodePicture(String p) {
        try {
            if (particleSys.numberOfSelectedNodes() == 1)
                particleSys.getSelectedNode(0).setPicture(p, params.picturePath);
            else {
                String[] args = new String[] { "Please select exactely one node." };
                callJavascriptFunction("show_warning", args);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * If one node is selected, set its shape
     * 
     * @param s
     *            The shape that is set
     */
    public void setNodeShape(String s) {
        try {
            if (particleSys.numberOfSelectedNodes() == 1)
                particleSys.getSelectedNode(0).setShape(Integer.parseInt(s));
            else {
                String[] args = new String[] { "Please select exactely one node." };
                callJavascriptFunction("show_warning", args);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Set the strength of repulsion forces between the node
     * 
     * @param s
     *            The strength of repulsion forces
     */
    public void setRepulsionStrength(String s) {
        try {
            repulsionStrength = Float.parseFloat(s);

            if (repulsionStrength > 500000)
                repulsionStrength = 500000;
            else if (repulsionStrength < 0)
                repulsionStrength = 0;

            for (int i = 0; i < particleSys.numberOfRepulsions(); i++)
                particleSys.getRepulsion(i).setStrength(repulsionStrength);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Set the color that is used to draw the node names
     * 
     * @param c
     *            The color that is used to draw the node names
     */
    public void setTextColor(String c) {
        try {
            if (c.charAt(0) == '#')
                c = c.substring(1);
            this.textColor = (255 << 24) | Integer.parseInt(c, 16);
        } catch (Exception e) {
            this.textColor = 0;
        }
    }

    /**
     * Initial method that get called when the applet is loaded. Sets some
     * constants, initializes the particle system, the control elements and the
     * handle to the javascript object, if available. Then it calls the parser
     * to read the given XGMML-data.
     */
    public void setup() {
        if (getParameter("edgeLength") != null)
            edgeLength = Float.valueOf(getParameter("edgeLength"));
        else
            edgeLength = 150;

        if (getParameter("scaleFactor") != null)
            scaleFactor = Float.valueOf(getParameter("scaleFactor"));
        else
            scaleFactor = 1;

        if (getParameter("edgeStrength") != null)
            edgeStrength = Float.valueOf(getParameter("edgeStrength"));
        else
            edgeStrength = 0.04f;

        if (getParameter("repulsionStrength") != null)
            repulsionStrength = Float.valueOf(getParameter("repulsionStrength"));
        else
            repulsionStrength = 4000;

        size(Integer.valueOf(getParameter("width")), Integer.valueOf(getParameter("height")));
        smooth();
        strokeWeight(2);
        ellipseMode(CENTER);
        rectMode(CENTER);
        imageMode(CENTER);
        textAlign(CENTER, CENTER);
        frameRate(30);

        particleSys = new ParticleSystem(0.2f);

        createControlElements();

        // any font in ttf format can be used to draw the node-names. Just
        // rename it to "font.ttf" and include it in the jar-file
        font = createFont("font.ttf", 14, true);
        textFont(font);

        try {
            window = JSObject.getWindow(this);
        } catch (Exception e) {
            println("window element not available");
        }

        String serverAddress = getCodeBase().toString();

        if (serverAddress.substring(0, 4).equals("file"))
            serverAddress = "http://localhost/cobweb_website/applet/";

        callJavascriptFunctionStatusMessage("parsing network data", true);

        String network = null;
        if (getParameter("networkXGMML") != null) {
            network = getParameter("networkXGMML");
            networkType = "xgmml";
        } else if (getParameter("fileXGMML") != null) {
            String lines[] = loadStrings(serverAddress + getParameter("fileXGMML"));
            StringBuffer stringBuf = new StringBuffer();
            for (int i = 0; i < lines.length; i++)
                stringBuf.append(lines[i]);
            network = stringBuf.toString();
            networkType = "xgmml";
        } else if (getParameter("networkGraphML") != null) {
            network = getParameter("networkGraphML");
            networkType = "graphml";
        } else if (getParameter("fileGraphML") != null) {
            String lines[] = loadStrings(serverAddress + getParameter("fileGraphML"));
            StringBuffer stringBuf = new StringBuffer();
            for (int i = 0; i < lines.length; i++)
                stringBuf.append(lines[i]);
            network = stringBuf.toString();
            networkType = "graphml";
        } else if (getParameter("networkSIF") != null) {
            network = getParameter("networkSIF");
            // newlines and tabs are not preserved in html PARAM-tags, so they
            // have to be encoded by <br> and <tab>
            network = network.replace("<br>", "\n");
            network = network.replace("<tab>", "\t");
            networkType = "sif";
        } else if (getParameter("fileSIF") != null) {
            String lines[] = loadStrings(serverAddress + getParameter("fileSIF"));
            StringBuffer stringBuf = new StringBuffer();
            for (int i = 0; i < lines.length; i++)
                stringBuf.append(lines[i] + "\n");
            network = stringBuf.toString();
            networkType = "sif";
        }

        if (networkType != null) {
            if (networkType.equals("xgmml")) {
                XGMMLParser parser = new XGMMLParser(this, network);
                parser.parseParameters(particleSys, serverAddress);
                parser.parseNodes(particleSys, width, height);
                parser.parseEdges(particleSys, edgeLength);
                params = parser.getParameters();
            } else if (networkType.equals("graphml")) {
                GraphMLParser parser = new GraphMLParser(this, network);
                parser.parseParameters(particleSys, serverAddress);
                parser.parseNodes(particleSys, width, height);
                parser.parseEdges(particleSys, edgeLength);
                params = parser.getParameters();
            } else if (networkType.equals("sif")) {
                SIFParser parser = new SIFParser(this, network);
                parser.parseParameters(particleSys, serverAddress);
                parser.parseNetwork(particleSys, width, height, edgeLength);
                params = parser.getParameters();
            } else
                System.out.println("unknown network type: " + networkType);
        } else
            System.out.println("no network type defined");

        // if no edges are present in the starting network, neighbours for all
        // nodes are loaded
        if (particleSys.numberOfEdges() == 0) {
            ArrayList<Node> n = new ArrayList<Node>();
            for (int i = 0; i < particleSys.numberOfNodes(); i++)
                n.add(particleSys.getNode(i));

            for (Iterator<Node> nIt = n.iterator(); nIt.hasNext();)
                loadNeighbourhood(nIt.next());
        }

        callJavascriptFunctionStatusMessage("computing node positions", true);

        for (int i = 0; i < 2000; i++)
            particleSys.tick();

        fitNetworkInWindow();

        callJavascriptFunctionStatusMessage("applet loaded");
        callJavascriptFunctionClearSidebar();
    }

    /**
     * If two node are selected, highlight all nodes that lie on all shortest
     * paths between the selected nodes
     */
    public void showAllPath() {
        if (particleSys.numberOfSelectedNodes() != 2) {
            String[] args = new String[] { "Please select exactely two nodes." };
            callJavascriptFunction("show_warning", args);
        } else {
            ArrayList<Node> path = new ArrayList<Node>();

            path = GraphFunctions.breadthFirstSearchAllPaths(particleSys, particleSys.getSelectedNode(0),
                    particleSys.getSelectedNode(1));

            if (particleSys.isDirected())
                path.addAll(GraphFunctions.breadthFirstSearchAllPaths(particleSys, particleSys.getSelectedNode(1),
                        particleSys.getSelectedNode(0)));

            if (path.size() == 0) {
                String[] args = new String[] { "No path exist between the selected nodes." };
                callJavascriptFunction("show_warning", args);
            } else {
                for (int i = 0; i < path.size(); ++i) {
                    selectNode(path.get(i));
                }
            }
        }
    }

    /**
     * If two node are selected, highlight all nodes that lie on one shortest
     * paths between the selected nodes
     */
    public void showOnePath() {
        if (particleSys.numberOfSelectedNodes() != 2) {
            String[] args = new String[] {
                    "Please select exactely two nodes (by selecting one node and then keeping the CONTROL-key pressed while clicking on the second one)." };
            callJavascriptFunction("show_warning", args);
        } else {
            ArrayList<Node> path = new ArrayList<Node>();

            path = GraphFunctions.breadthFirstSearch(particleSys, particleSys.getSelectedNode(0),
                    particleSys.getSelectedNode(1));

            if (particleSys.isDirected())
                path.addAll(GraphFunctions.breadthFirstSearch(particleSys, particleSys.getSelectedNode(1),
                        particleSys.getSelectedNode(0)));

            if (path.size() == 0) {
                String[] args = new String[] { "No path exist between the selected nodes." };
                callJavascriptFunction("show_warning", args);
            } else {
                for (int i = 0; i < path.size(); ++i) {
                    selectNode(path.get(i));
                }
            }
        }
    }

    /**
     * Get statistics about the network and show them in the sidebar
     */
    public void showStatistics() {
        String numNodes = String.valueOf(particleSys.numberOfNodes());
        String numEdges = String.valueOf(particleSys.numberOfEdges());

        // connected components
        ArrayList<ArrayList<Node>> components = GraphFunctions.connectedComponents(particleSys);
        String numComponents = String.valueOf(components.size());
        ArrayList<Integer> componentSizesList = new ArrayList<Integer>();
        for (int i = 0; i < components.size(); ++i)
            componentSizesList.add(components.get(i).size());
        Collections.sort(componentSizesList);
        String componentSizes = "";
        if (componentSizesList.size() > 0) {
            componentSizes += componentSizesList.get(componentSizesList.size() - 1).toString();
            for (int i = componentSizesList.size() - 2; i >= 0; --i)
                componentSizes += ", " + componentSizesList.get(i).toString();
        }

        String[] args = new String[] { numNodes, numEdges, numComponents, componentSizes };
        callJavascriptFunction("show_statistics", args);
    }

}