summaryrefslogtreecommitdiffstats
path: root/src/main/java/org/openslx/graph
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/openslx/graph')
-rw-r--r--src/main/java/org/openslx/graph/ClientNode.java24
-rw-r--r--src/main/java/org/openslx/graph/Edge.java128
-rw-r--r--src/main/java/org/openslx/graph/Graph.java559
-rw-r--r--src/main/java/org/openslx/graph/Node.java198
-rw-r--r--src/main/java/org/openslx/graph/PngRenderer.java46
-rw-r--r--src/main/java/org/openslx/graph/ServerNode.java23
6 files changed, 978 insertions, 0 deletions
diff --git a/src/main/java/org/openslx/graph/ClientNode.java b/src/main/java/org/openslx/graph/ClientNode.java
new file mode 100644
index 0000000..2d4d1f4
--- /dev/null
+++ b/src/main/java/org/openslx/graph/ClientNode.java
@@ -0,0 +1,24 @@
+package org.openslx.graph;
+
+import java.awt.Color;
+
+public class ClientNode extends Node
+{
+
+ private static final long serialVersionUID = 3569234002180936927L;
+
+ public ClientNode( String ip )
+ {
+ super( ip, ip, null );
+ setDesiredDistance( 2 );
+ setRadius( 4 );
+ setColor( Color.BLUE );
+ }
+
+ @Override
+ public int getMaxHealth()
+ {
+ return 5;
+ }
+
+}
diff --git a/src/main/java/org/openslx/graph/Edge.java b/src/main/java/org/openslx/graph/Edge.java
new file mode 100644
index 0000000..ece5250
--- /dev/null
+++ b/src/main/java/org/openslx/graph/Edge.java
@@ -0,0 +1,128 @@
+package org.openslx.graph;
+
+import java.awt.Color;
+
+import org.apache.commons.math3.util.FastMath;
+
+public class Edge implements java.io.Serializable
+{
+ private static final long serialVersionUID = 4401455477347084262L;
+ private Node _source;
+ private Node _target;
+ private double _weight;
+ private String _id = null;
+ private int _age = 0;
+ private static final Color[] COLORS = new Color[ 20 ];
+ private final int _maxHealth;
+ private int _health;
+
+ static
+ {
+ for ( int i = 0; i < COLORS.length; ++i ) {
+ int r = blend( 255, 100, i, COLORS.length - 1 );
+ int g = blend( 0, 100, i, COLORS.length - 1 );
+ int b = blend( 0, 255, i, COLORS.length - 1 );
+ COLORS[i] = new Color( r, g, b );
+ }
+ }
+
+ private static int blend( int a, int b, int current, int max )
+ {
+ a *= a;
+ b *= b;
+ return (int)FastMath.sqrt( ( ( b * current ) + ( a * ( max - current ) ) ) / max );
+ }
+
+ public Edge( Node source, Node target )
+ {
+ // Note that this graph is actually undirected.
+ _source = source;
+ _target = target;
+ _weight = 0;
+ _maxHealth = Math.max( source.getMaxHealth(), target.getMaxHealth() );
+ _health = _maxHealth;
+ }
+
+ public void resetWeight()
+ {
+ if ( _health == _maxHealth && _maxHealth != 1 && _source.wasActivated() && _target.wasActivated() ) {
+ _health = 1;
+ } else {
+ _health--;
+ }
+ _age++;
+ }
+
+ public boolean isAlive()
+ {
+ return _health > 0;
+ }
+
+ public void addWeight( double weight )
+ {
+ _weight += weight;
+ _health = _maxHealth;
+ }
+
+ public double getWeight()
+ {
+ return _weight;
+ }
+
+ public Node getSource()
+ {
+ return _source;
+ }
+
+ public Node getTarget()
+ {
+ return _target;
+ }
+
+ public boolean equals( Object o )
+ {
+ if ( o instanceof Edge ) {
+ Edge other = (Edge)o;
+ return ( _source.equals( other._source ) && _target.equals( other._target ) ) || ( _source.equals( other._target ) && _target.equals( other._source ) );
+ }
+ return false;
+ }
+
+ public boolean hasNode( Node node )
+ {
+ return _source.equals( node ) || _target.equals( node );
+ }
+
+ public double getLengthSquared()
+ {
+ final double dx = _source.getX() - _target.getX();
+ final double dy = _source.getY() - _target.getY();
+ return dx * dx + dy * dy;
+ }
+
+ public int hashCode()
+ {
+ return _source.hashCode() ^ _target.hashCode();
+ }
+
+ public String toString()
+ {
+ return _source + " " + _target + " w(" + _weight + ")";
+ }
+
+ public String getId()
+ {
+ if ( _id == null ) {
+ if ( _source.getId().compareTo( _target.getId() ) > 0 )
+ _id = _target.getId() + "==" + _source.getId();
+ _id = _source.getId() + "==" + _target.getId();
+ }
+ return _id;
+ }
+
+ public Color getColor()
+ {
+ return COLORS[_age >= COLORS.length ? COLORS.length - 1 : _age];
+ }
+
+}
diff --git a/src/main/java/org/openslx/graph/Graph.java b/src/main/java/org/openslx/graph/Graph.java
new file mode 100644
index 0000000..b3ab8b2
--- /dev/null
+++ b/src/main/java/org/openslx/graph/Graph.java
@@ -0,0 +1,559 @@
+package org.openslx.graph;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Stroke;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.math3.util.FastMath;
+
+import com.google.gson.annotations.Expose;
+
+/**
+ * The Graph stores the Nodes and Edges, and InferenceHeurisics to allow
+ * the structure of the graph to be modified.
+ */
+public class Graph implements java.io.Serializable
+{
+
+ private static final long serialVersionUID = 5488288903546706793L;
+ private String _label;
+ private String _caption = "servers are grey, clients are blue, sugar is sweet, and so are you";
+ private Map<Node, Node> _nodes = new HashMap<>();
+ private Map<Edge, Edge> _edges = new HashMap<>();
+
+ @Expose
+ private Collection<Node> nodes = _nodes.values();
+ @Expose
+ private Collection<Edge> edges = _edges.values();
+
+ private double minX = Double.POSITIVE_INFINITY;
+ private double maxX = Double.NEGATIVE_INFINITY;
+ private double minY = Double.POSITIVE_INFINITY;
+ private double maxY = Double.NEGATIVE_INFINITY;
+ private double maxWeight = 0;
+
+ private int _frameCount = 0;
+
+ private static final Color COLOR_BG = new Color( 250, 250, 255 );
+ private static final Color COLOR_TITLE = new Color( 200, 200, 255 );
+ private static final Color COLOR_LABEL = new Color( 0, 0, 20 );
+ private static final Color COLOR_LABEL_SHADOW = new Color( 220, 220, 220 );
+ private static final Color COLOR_TITLE2 = new Color( 190, 190, 210 );
+ private static final Color COLOR_EDGE = new Color( 100, 100, 255 );
+
+ private static final int IMG_X = 1280;
+ private static final int IMG_Y = 1024;
+
+ private final Graphics2D graphicsContext;
+ private final PngRenderer renderer;
+
+ public Graph( String label )
+ {
+ _label = label;
+ BufferedImage targetImage = new BufferedImage( IMG_X, IMG_Y, BufferedImage.TYPE_INT_ARGB );
+ graphicsContext = targetImage.createGraphics();
+ graphicsContext.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
+ renderer = new PngRenderer( targetImage );
+ }
+
+ // Add a Node to the Graph.
+ public void addNode( Node node )
+ {
+ // Only add the Node to the HashMap if it's not already in there.
+ if ( _nodes.containsKey( node ) )
+ return;
+ _nodes.put( node, node );
+ }
+
+ // Add an Edge to the Graph. Increment the weighting if it already exists.
+ public void setEdge( Node source, Node target, double weight )
+ {
+ // Do not add self-edges or weights that are not positive.
+ if ( source.equals( target ) || weight <= 0 ) {
+ return;
+ }
+
+ addNode( source );
+ addNode( target );
+
+ // Add the Edge to the HashMap, or find the existing entry.
+ Edge edge = _edges.get( new Edge( source, target ) );
+ if ( edge == null ) {
+ source = _nodes.get( source );
+ target = _nodes.get( target );
+ edge = new Edge( source, target );
+ _edges.put( edge, edge );
+ }
+ // Set the edge weight.
+ edge.addWeight( weight );
+ }
+
+ // Remove a Node from the Graph, along with all of its emanating Edges.
+ public boolean removeNode( Node node )
+ {
+ if ( !_nodes.containsKey( node ) )
+ return false;
+ // Remove the Node from the HashMap.
+ _nodes.remove( node );
+
+ // Remove all Edges that connect to the removed Node.
+ Iterator<Edge> edgeIt = _edges.keySet().iterator();
+ while ( edgeIt.hasNext() ) {
+ Edge edge = edgeIt.next();
+ if ( edge.getSource().equals( node ) || edge.getTarget().equals( node ) ) {
+ edgeIt.remove();
+ }
+ }
+ return true;
+ }
+
+ // Return true if the Graph contains the Node.
+ // (This does not necessarily imply that the Node is visible).
+ public boolean contains( Node node )
+ {
+ return _nodes.containsKey( node );
+ }
+
+ // Return true if the Graph contains the Edge.
+ public boolean contains( Edge edge )
+ {
+ return _edges.containsKey( edge );
+ }
+
+ // Return the Graph's Node that has the same nick as the supplied Node.
+ public Node get( Node node )
+ {
+ return _nodes.get( node );
+ }
+
+ // Return the Graph's Edge that matched the supplied Edge.
+ public Edge get( Edge edge )
+ {
+ return _edges.get( edge );
+ }
+
+ public String toString()
+ {
+ return "Graph: " + _nodes.size() + " nodes and " + _edges.size() + " edges.";
+ }
+
+ public String toString2()
+ {
+ return "Nodes:\n" + _nodes + "\nEdges:\n" + _edges;
+ }
+
+ // Apply the temporal decay to the Graph.
+ public void decay()
+ {
+ for ( Iterator<Edge> it = _edges.values().iterator(); it.hasNext(); ) {
+ Edge edge = it.next();
+ edge.resetWeight();
+ if ( !edge.isAlive() ) {
+ it.remove();
+ }
+ }
+
+ List<Node> toRemove = null;
+ for ( Node node : _nodes.values() ) {
+ node.age();
+ if ( !node.isAlive() ) {
+ if ( toRemove == null ) {
+ toRemove = new ArrayList<>();
+ }
+ toRemove.add( node );
+ }
+ }
+ if ( toRemove != null ) {
+ for ( Node node : toRemove ) {
+ removeNode( node );
+ }
+ }
+ }
+
+ // Returns the set of all Nodes that have emanating Edges.
+ // This therefore returns all Nodes that will be visible in the drawing.
+ public Set<Node> getConnectedNodes()
+ {
+ Set<Node> connectedNodes = new HashSet<>();
+ for ( Edge edge : _edges.values() ) {
+ connectedNodes.add( edge.getSource() );
+ connectedNodes.add( edge.getTarget() );
+ }
+ return connectedNodes;
+ }
+
+ public Collection<Edge> getEdges()
+ {
+ return _edges.values();
+ }
+
+ // Limit movement values to stop nodes flying into oblivion.
+ private static final double MAX_MOVE = 0.75;
+ private static final double K = 2;
+ private static final double MOVEMENT_SLOWDOWN = 0.001;
+
+ // Applies the spring embedder.
+ public void doLayout( Set<Node> visibleNodes, int iterations )
+ {
+
+ // For performance, copy each set into an array.
+ Node[] nodes = visibleNodes.toArray( new Node[ visibleNodes.size() ] );
+ Edge[] edges = _edges.keySet().toArray( new Edge[ _edges.size() ] );
+
+ if ( nodes.length == 0 )
+ return;
+
+ // For each iteration...
+ for ( int it = 0; it < iterations; it++ ) {
+
+ // Calculate forces acting on nodes due to node-node repulsions...
+ for ( int a = 0; a < nodes.length; a++ ) {
+ final Node nodeA = nodes[a];
+ for ( int b = a + 1; b < nodes.length; b++ ) {
+ final Node nodeB = nodes[b];
+
+ double deltaX = nodeB.getX() - nodeA.getX();
+ double deltaY = nodeB.getY() - nodeA.getY();
+ final double maxDistance = ( nodeA.getDesiredDistance() * nodeB.getDesiredDistance() ) * 0.9;
+
+ double distanceSquared = deltaX * deltaX + deltaY * deltaY;
+
+ if ( distanceSquared > maxDistance * maxDistance )
+ continue;
+
+ if ( distanceSquared < 0.01 ) {
+ deltaX = Math.random() / 10 + 0.1;
+ deltaY = Math.random() / 10 + 0.1;
+ distanceSquared = deltaX * deltaX + deltaY * deltaY;
+ }
+
+ final double distance = FastMath.sqrt( distanceSquared );
+ final double repulsiveForce = ( K * maxDistance / distance ) * 2;
+
+ final double sumDesired = nodeA.getDesiredDistance() + nodeB.getDesiredDistance();
+ // A gets pushed away stronger if B has higher desired distance than A
+ final double rfA = repulsiveForce * ( nodeB.getDesiredDistance() / sumDesired );
+ // Vice versa for B
+ final double rfB = repulsiveForce * ( nodeA.getDesiredDistance() / sumDesired );
+
+ nodeB.addForceX( ( rfB * deltaX / distance ) );
+ nodeB.addForceY( ( rfB * deltaY / distance ) );
+ nodeA.addForceX( - ( rfA * deltaX / distance ) );
+ nodeA.addForceY( - ( rfA * deltaY / distance ) );
+ }
+
+ }
+
+ // Calculate forces acting on nodes due to edge attractions.
+ for ( int e = 0; e < edges.length; e++ ) {
+ final Edge edge = edges[e];
+ final Node nodeA = edge.getSource();
+ final Node nodeB = edge.getTarget();
+
+ final double minDistance = ( nodeA.getDesiredDistance() * nodeB.getDesiredDistance() ) * 1.1;
+
+ final double deltaX = nodeB.getX() - nodeA.getX();
+ final double deltaY = nodeB.getY() - nodeA.getY();
+
+ double distanceSquared = deltaX * deltaX + deltaY * deltaY;
+
+ if ( distanceSquared < minDistance * minDistance )
+ continue;
+
+ double distance = FastMath.sqrt( distanceSquared );
+
+ if ( distance > minDistance * 2 ) {
+ distance = minDistance * 2;
+ distanceSquared = distance * distance;
+ }
+
+ final double attractiveForce = ( distanceSquared / K ) * 2;
+ final double sumDesired = nodeA.getDesiredDistance() + nodeB.getDesiredDistance();
+ // A gets pulled towards B stronger if B has higher desired distance than A
+ final double afA = attractiveForce * ( nodeB.getDesiredDistance() / sumDesired );
+ // Vice versa for B
+ final double afB = attractiveForce * ( nodeA.getDesiredDistance() / sumDesired );
+
+ nodeB.addForceX( -afB * deltaX / distance );
+ nodeB.addForceY( -afB * deltaY / distance );
+ nodeA.addForceX( afA * deltaX / distance );
+ nodeA.addForceY( afA * deltaY / distance );
+
+ }
+
+ // Now move each node to its new location...
+ for ( int a = 0; a < nodes.length; a++ ) {
+ Node node = nodes[a];
+
+ // General weak attraction towards (0|0)
+ if ( node.getDesiredDistance() > 5 ) {
+ node.addForceX( -node.getX() / 500 );
+ node.addForceY( -node.getY() / 500 );
+ }
+
+ double xMovement = MOVEMENT_SLOWDOWN * node.getFX();
+ double yMovement = MOVEMENT_SLOWDOWN * node.getFY();
+
+ if ( xMovement > MAX_MOVE ) {
+ xMovement = MAX_MOVE;
+ }
+ else if ( xMovement < -MAX_MOVE ) {
+ xMovement = -MAX_MOVE;
+ }
+ if ( yMovement > MAX_MOVE ) {
+ yMovement = MAX_MOVE;
+ }
+ else if ( yMovement < -MAX_MOVE ) {
+ yMovement = -MAX_MOVE;
+ }
+
+ node.setX( node.getX() + xMovement );
+ node.setY( node.getY() + yMovement );
+
+ // Reset the forces
+ node.resetForce();
+ }
+
+ // Swap two random nodes if it results in less stress on them
+ Node a = nodes[(int) ( Math.random() * nodes.length )];
+ Node b = nodes[(int) ( Math.random() * nodes.length )];
+ if ( !a.equals( b ) ) {
+ double initialLen = getEdgeLenSum( edges, a, b );
+ a.swapPos( b );
+ double swappedLen = getEdgeLenSum( edges, a, b );
+ if ( swappedLen + 0.5 > initialLen ) {
+ a.swapPos( b ); // Isn't any better, swap back
+ }
+ }
+
+ }
+
+ }
+
+ private double getEdgeLenSum( Edge[] edges, Node a, Node b )
+ {
+ double result = 0;
+ for ( Edge edge : edges ) {
+ if ( edge.hasNode( a ) || edge.hasNode( b ) ) {
+ result += edge.getLengthSquared();
+ }
+ }
+ return result;
+ }
+
+ // Work out the drawing boundaries...
+ public void calcBounds( Set<Node> nodes )
+ {
+
+ minX = Double.POSITIVE_INFINITY;
+ maxX = Double.NEGATIVE_INFINITY;
+ minY = Double.POSITIVE_INFINITY;
+ maxY = Double.NEGATIVE_INFINITY;
+ maxWeight = 0;
+
+ for ( Node node : nodes ) {
+
+ if ( node.getX() > maxX ) {
+ maxX = node.getX();
+ }
+ if ( node.getX() < minX ) {
+ minX = node.getX();
+ }
+ if ( node.getY() > maxY ) {
+ maxY = node.getY();
+ }
+ if ( node.getY() < minY ) {
+ minY = node.getY();
+ }
+ }
+
+ // Increase size if too small.
+ final double minSize = 10;
+ if ( maxX - minX < minSize ) {
+ double midX = ( maxX + minX ) / 2;
+ minX = midX - ( minSize / 2 );
+ maxX = midX + ( minSize / 2 );
+ }
+ if ( maxY - minY < minSize ) {
+ double midY = ( maxY + minY ) / 2;
+ minY = midY - ( minSize / 2 );
+ maxY = midY + ( minSize / 2 );
+ }
+
+ // Work out the maximum weight.
+ for ( Edge edge : _edges.values() ) {
+ if ( edge.getWeight() > maxWeight ) {
+ maxWeight = edge.getWeight();
+ }
+ }
+
+ // Jibble the boundaries to maintain the aspect ratio.
+ double xyRatio = ( ( maxX - minX ) / ( maxY - minY ) ) / ( IMG_X / IMG_Y );
+ if ( xyRatio > 1 ) {
+ // diagram is wider than it is high.
+ double dy = maxY - minY;
+ dy = dy * xyRatio - dy;
+ minY = minY - dy / 2;
+ maxY = maxY + dy / 2;
+ }
+ else if ( xyRatio < 1 ) {
+ // Diagram is higher than it is wide.
+ double dx = maxX - minX;
+ dx = dx / xyRatio - dx;
+ minX = minX - dx / 2;
+ maxX = maxX + dx / 2;
+ }
+
+ }
+
+ private static final Font FONT_64 = new Font( "SansSerif", Font.BOLD, 64 );
+ private static final Font FONT_18 = new Font( "SansSerif", Font.BOLD, 18 );
+ private static final Font FONT_12 = new Font( "SansSerif", Font.PLAIN, 12 );
+ private static final Font FONT_10 = new Font( "SansSerif", Font.PLAIN, 10 );
+ private static final Stroke STROKE_2 = new BasicStroke( 2.0f );
+
+ public void drawImage( Set<Node> nodes, double edgeThreshold )
+ {
+
+ // Now actually draw the thing...
+
+ graphicsContext.setColor( COLOR_BG );
+ graphicsContext.fillRect( 0, 0, IMG_X, IMG_Y );
+
+ graphicsContext.setColor( COLOR_TITLE );
+ graphicsContext.setFont( FONT_64 );
+ graphicsContext.drawString( _label, 20, 80 );
+
+ graphicsContext.setColor( COLOR_TITLE2 );
+ graphicsContext.setFont( FONT_18 );
+ graphicsContext.drawString( _caption, 0, IMG_Y - 5 - 50 );
+ graphicsContext.setFont( FONT_12 );
+ graphicsContext.drawString( "This frame was drawn at " + new Date(), 0, IMG_Y - 5 );
+
+ // Draw all edges...
+ for ( Edge edge : _edges.values() ) {
+ if ( edge.getWeight() < edgeThreshold ) {
+ continue;
+ }
+
+ double weight = edge.getWeight();
+
+ Node nodeA = edge.getSource();
+ Node nodeB = edge.getTarget();
+ int x1 = toImageX( nodeA );
+ int y1 = toImageY( nodeA );
+ int x2 = toImageX( nodeB );
+ int y2 = toImageY( nodeB );
+ graphicsContext.setStroke( new BasicStroke( (float) ( FastMath.log( weight + 1 ) * 0.1 ) + 1 ) );
+ final int alpha = 102 + (int) ( 153 * weight / maxWeight );
+ graphicsContext.setColor( new Color( ( edge.getColor().getRGB() & 0xffffff ) | ( alpha << 24 ), true ) );
+ graphicsContext.drawLine( x1, y1, x2, y2 );
+ }
+
+ // Draw all nodes...
+ graphicsContext.setStroke( STROKE_2 );
+ graphicsContext.setFont( FONT_10 );
+ for ( Node node : nodes ) {
+ final int x = toImageX( node );
+ final int y = toImageY( node );
+ final int nodeRadius = node.getRadius();
+ graphicsContext.setColor( node.getColor() );
+ graphicsContext.fillOval( x - nodeRadius, y - nodeRadius, nodeRadius * 2, nodeRadius * 2 );
+ graphicsContext.setColor( COLOR_EDGE );
+ graphicsContext.drawOval( x - nodeRadius, y - nodeRadius, nodeRadius * 2, nodeRadius * 2 );
+ graphicsContext.setColor( COLOR_LABEL_SHADOW );
+ graphicsContext.drawString( node.toString(), x - ( node.toString().length() * 3 ) + 1, y - nodeRadius - 2 + 1 );
+ graphicsContext.setColor( COLOR_LABEL );
+ graphicsContext.drawString( node.toString(), x - ( node.toString().length() * 3 ), y - nodeRadius - 2 );
+ }
+ }
+
+ private int toImageX( Node node )
+ {
+ return (int) ( ( IMG_X - 50 ) * ( node.getX() - minX ) / ( maxX - minX ) ) + 25;
+ }
+
+ private int toImageY( Node node )
+ {
+ return (int) ( ( IMG_Y - 55 ) * ( node.getY() - minY ) / ( maxY - minY ) ) + 35;
+ }
+
+ public int getFrameCount()
+ {
+ return _frameCount;
+ }
+
+ public String getLabel()
+ {
+ return _label;
+ }
+
+ public void setCaption( String caption )
+ {
+ _caption = caption;
+ }
+
+ public byte[] makeNextImage()
+ {
+ _frameCount++;
+
+ Set<Node> nodes = getConnectedNodes();
+
+ doLayout( nodes, 200 );
+ calcBounds( nodes );
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream( 100000 );
+
+ try {
+ drawImage( nodes, 0.1 );
+ //ImageIO.write( targetImage, "png", baos );
+ renderer.render( baos );
+
+ writeGraph();
+ return baos.toByteArray();
+ } catch ( Exception e ) {
+ System.out.println( "PieSpy has gone wibbly: " + e );
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ // Serialize this Graph and write it to a File.
+ public void writeGraph()
+ {
+ /*
+ try {
+ String strippedChannel = _label.toLowerCase().substring( 1 );
+ File dir = new File( config.outputDirectory, strippedChannel );
+ File file = new File( dir, strippedChannel + "-restore.dat" );
+ ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream( file ) );
+ oos.writeObject( SocialNetworkBot.VERSION );
+ oos.writeObject( this );
+ oos.flush();
+ oos.close();
+ } catch ( Exception e ) {
+ // Do nothing?
+ }
+ */
+ }
+
+ public Node getNode( String id )
+ {
+ return _nodes.get( new Node( id ) );
+ }
+
+}
diff --git a/src/main/java/org/openslx/graph/Node.java b/src/main/java/org/openslx/graph/Node.java
new file mode 100644
index 0000000..0bf2fab
--- /dev/null
+++ b/src/main/java/org/openslx/graph/Node.java
@@ -0,0 +1,198 @@
+package org.openslx.graph;
+
+import java.awt.Color;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+public class Node implements java.io.Serializable
+{
+ private static final long serialVersionUID = -6997045538537830300L;
+
+ private final String _id;
+ @Expose
+ @SerializedName( "name" )
+ private final String _displayName;
+ @Expose
+ @SerializedName( "distance" )
+ private double _distance = 2;
+ @Expose
+ @SerializedName( "radius" )
+ private int _radius = 2;
+ @Expose
+ @SerializedName( "color" )
+ private String _colorString;
+ @Expose
+ @SerializedName( "x" )
+ private double _x;
+ @Expose
+ @SerializedName( "y" )
+ private double _y;
+ private double _fx;
+ private double _fy;
+ private int _health;
+ private Color _color;
+
+ protected Node( String displayName )
+ {
+ this( displayName, displayName, null );
+ }
+
+ protected Node( String id, String displayName, Node startPos )
+ {
+ _displayName = displayName;
+ _id = id;
+ if ( startPos == null ) {
+ _x = Math.random() * 2;
+ _y = Math.random() * 2;
+ } else {
+ _x = startPos.getX();
+ _y = startPos.getY();
+ }
+ _fx = 0;
+ _fy = 0;
+ _health = getMaxHealth();
+ setColor( Color.BLUE );
+ }
+
+ public void swapPos( Node other )
+ {
+ final double tx = _x, ty = _y;
+ _x = other._x;
+ _y = other._y;
+ other._x = tx;
+ other._y = ty;
+ }
+
+ public void setX( double x )
+ {
+ _x = x;
+ }
+
+ public void setY( double y )
+ {
+ _y = y;
+ }
+
+ public void addForceX( double fx )
+ {
+ _fx += fx;
+ }
+
+ public void addForceY( double fy )
+ {
+ _fy += fy;
+ }
+
+ public void resetForce()
+ {
+ _fx = 0;
+ _fy = 0;
+ }
+
+ public double getX()
+ {
+ return _x;
+ }
+
+ public double getY()
+ {
+ return _y;
+ }
+
+ public double getFX()
+ {
+ return _fx;
+ }
+
+ public double getFY()
+ {
+ return _fy;
+ }
+
+ public String toString()
+ {
+ return _displayName;
+ }
+
+ public String getId()
+ {
+ return _id;
+ }
+
+ public final boolean equals( Object o )
+ {
+ if ( o instanceof Node ) {
+ Node other = (Node)o;
+ return _id.equals( other._id );
+ }
+ return false;
+ }
+
+ public final int hashCode()
+ {
+ return _id.hashCode();
+ }
+
+ public final void activate()
+ {
+ _health = getMaxHealth();
+ }
+
+ public final boolean isAlive()
+ {
+ return _health > 0;
+ }
+
+ public final void age()
+ {
+ _health--;
+ }
+
+ public final Color getColor()
+ {
+ return _color;
+ }
+
+ protected final void setColor( Color color )
+ {
+ _color = color;
+ _colorString = String.format( "#%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue() );
+ }
+
+ public final int getRadius()
+ {
+ return 5;
+ }
+
+ protected final void setRadius( int radius )
+ {
+ _radius = radius;
+ }
+
+ public int getMaxHealth()
+ {
+ return 1;
+ }
+
+ public final boolean wasActivated()
+ {
+ return _health == getMaxHealth();
+ }
+
+ public final double getDesiredDistance()
+ {
+ return _distance;
+ }
+
+ protected final void setDesiredDistance( double distance )
+ {
+ _distance = distance;
+ }
+
+ public final int getAlpha()
+ {
+ return 63 + ( _health * 192 ) / getMaxHealth();
+ }
+
+}
diff --git a/src/main/java/org/openslx/graph/PngRenderer.java b/src/main/java/org/openslx/graph/PngRenderer.java
new file mode 100644
index 0000000..e51889c
--- /dev/null
+++ b/src/main/java/org/openslx/graph/PngRenderer.java
@@ -0,0 +1,46 @@
+package org.openslx.graph;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.awt.image.SinglePixelPackedSampleModel;
+import java.io.OutputStream;
+
+import ar.com.hjg.pngj.ImageInfo;
+import ar.com.hjg.pngj.ImageLineByte;
+import ar.com.hjg.pngj.PngWriter;
+
+public class PngRenderer
+{
+ private final ImageInfo imageInfo;
+ private final SinglePixelPackedSampleModel samplemodel;
+ private final int[] srcImageRawData;
+ private final ImageLineByte line;
+ private final byte[] lineBytes;
+
+ public PngRenderer( BufferedImage image )
+ {
+ imageInfo = new ImageInfo( image.getWidth(), image.getHeight(), 8, false );
+ DataBufferInt db = ( (DataBufferInt)image.getRaster().getDataBuffer() );
+ samplemodel = (SinglePixelPackedSampleModel)image.getSampleModel();
+ line = new ImageLineByte( imageInfo );
+ lineBytes = line.getScanline();
+ srcImageRawData = db.getData();
+ }
+
+ public void render( OutputStream os )
+ {
+ PngWriter pngWriter = new PngWriter( os, imageInfo );
+ for ( int row = 0; row < imageInfo.rows; row++ ) {
+ int elem = samplemodel.getOffset( 0, row );
+ for ( int col = 0, j = 0; col < imageInfo.cols; col++ ) {
+ int sample = srcImageRawData[elem++];
+ lineBytes[j++] = (byte) ( ( sample & 0xFF0000 ) >> 16 ); // R
+ lineBytes[j++] = (byte) ( ( sample & 0xFF00 ) >> 8 ); // G
+ lineBytes[j++] = (byte) ( sample & 0xFF ); // B
+ }
+ pngWriter.writeRow( line, row );
+ }
+ pngWriter.end();
+ }
+
+}
diff --git a/src/main/java/org/openslx/graph/ServerNode.java b/src/main/java/org/openslx/graph/ServerNode.java
new file mode 100644
index 0000000..c7604fc
--- /dev/null
+++ b/src/main/java/org/openslx/graph/ServerNode.java
@@ -0,0 +1,23 @@
+package org.openslx.graph;
+
+import java.awt.Color;
+
+public class ServerNode extends Node
+{
+ private static final long serialVersionUID = 1642125227049150051L;
+
+ public ServerNode( String ip )
+ {
+ super( ip, ip, null );
+ setDesiredDistance( 6 );
+ setRadius( 15 );
+ setColor( Color.LIGHT_GRAY );
+ }
+
+ @Override
+ public int getMaxHealth()
+ {
+ return 15;
+ }
+
+}