package automenta.vivisect.graph;
import automenta.vivisect.Vis;
import automenta.vivisect.swing.PCanvas;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.math3.util.FastMath;
import org.jgrapht.Graph;
import static processing.core.PConstants.MITER;
import static processing.core.PConstants.SQUARE;
import processing.core.PGraphics;
/**
*
* @author me
*/
abstract public class AbstractGraphVis<V, E> implements Vis {
Map<V, VertexVis<V,E>> vertices = new LinkedHashMap();
Map<E, EdgeVis<V,E>> edges = new LinkedHashMap();
Set<V> deadVertices = new LinkedHashSet();
Set<E> deadEdges = new LinkedHashSet();
Graph<V,E> currentGraph;
boolean updateNext = true;
//bounds of last positioned vertex
float minX = 0f, minY = 0f, maxX = 0f, maxY = 0f;
private GraphDisplay<V,E> display;
int maxNodesWithLabels = 5000;
int maxNodes = 5000;
int maxEdgesWithArrows = 10000;
int maxEdges = 10000;
float nodeSpeed = 0.1f;
float sx = 800f;
float sy = 800f;
float arrowHeadScale = 1f/16f;
static final float vertexTargetThreshold = 4f;
private PGraphics graphics;
private PCanvas canvas;
public AbstractGraphVis(GraphDisplay display) {
super();
this.display = display;
}
public void setUpdateNext() {
updateNext = true;
}
public void setDisplay(GraphDisplay display) {
this.display = display;
}
public GraphDisplay getDisplay() {
return display;
}
public VertexVis getVertexDisplay(V v) {
return vertices.get(v);
}
public VertexVis updateVertex(final V o) {
deadVertices.remove(o);
VertexVis v = vertices.get(o);
if (v != null) {
display.vertex(this, v);
return v;
}
v = new VertexVis(getCanvas(), o);
vertices.put(o, v);
return v;
}
public EdgeVis updateEdge(final E o) {
deadEdges.remove(o);
EdgeVis v = edges.get(o);
if (v != null) {
display.edge(this, v);
return v;
}
v = new EdgeVis(o);
edges.put(o, v);
return v;
}
public abstract Graph<V,E> getGraph();
abstract protected boolean hasUpdate();
/**
* called from NAR update thread, not swing thread
*/
public void updateGraph() {
if (hasUpdate() || (updateNext) || display.preUpdate(this)) {
updateNext = false;
/*synchronized (vertices)*/ {
deadVertices.clear();
deadEdges.clear();
currentGraph = getGraph();
if (currentGraph == null) {
vertices.clear();
edges.clear();
return;
}
deadVertices.addAll(vertices.keySet());
deadEdges.addAll(edges.keySet());
for (final V v : currentGraph.vertexSet())
updateVertex(v);
for (final E e : currentGraph.edgeSet())
updateEdge(e);
for (final V v : deadVertices)
vertices.remove(v);
for (final E e : deadEdges)
edges.remove(e);
}
}
}
@Override public void init(PCanvas canvas) {
this.canvas = canvas;
}
public PCanvas getCanvas() {
return canvas;
}
public PGraphics getGraphics() {
return graphics;
}
@Override
public boolean draw(PGraphics g) {
this.graphics = g;
//long start = System.nanoTime();
if (currentGraph == null) {
return true;
}
/*synchronized (vertices)*/ {
//for speed:
g.noFill();
g.strokeCap(SQUARE);
g.strokeJoin(MITER); //https://www.processing.org/reference/strokeJoin_.html
/*boolean changed = false;*/
int numEdges = currentGraph.edgeSet().size();
if (numEdges < maxEdges) {
for (final EdgeVis d : edges.values()) {
/*changed |= */d.draw(this, g);
}
}
g.noStroke();
int numNodes = vertices.size();
//boolean text = numNodes < maxNodesWithLabels;
if (numNodes < maxNodes) {
for (final VertexVis d : vertices.values()) {
/*changed |= */d.draw(this, g);
}
}
//drawn = !changed;
}
/*long end = System.nanoTime();
float time = end - start;
if (currentGraph!=null)
System.out.println(time + "ns for " + currentGraph.vertexSet().size() + "|" + currentGraph.edgeSet().size());
*/
display.postUpdate(this);
return true;
}
public void resurrectVertex(V v) {
deadVertices.remove(v);
}
/** TODO avoid using transform, just calculation coordinates in current transform */
void drawArrow(final PGraphics g, final float x1, final float y1, float x2, float y2, float destinationRadius) {
//float cx = (x1 + x2) / 2f;
//float cy = (y1 + y2) / 2f;
float dx = x2-x1;
float dy = y2-y1;
float angle = (float) (FastMath.atan2(dy, dx));
//if (len == 0) return;
//g.line(x1, y1, x2, y2);
final float arrowAngle = (float)Math.PI/12f + g.strokeWeight/200f;
final float arrowHeadRadius = /*len **/ arrowHeadScale * (g.strokeWeight*16f);
if (arrowHeadRadius > 0) {
float len = (float) FastMath.sqrt(dx*dx + dy*dy) - destinationRadius;
if (len <= 0) return;
x2 = (float)FastMath.cos(angle) * len + x1;
y2 = (float)FastMath.sin(angle) * len + y1;
float plx = (float)FastMath.cos(angle-Math.PI-arrowAngle) * arrowHeadRadius;
float ply = (float)FastMath.sin(angle-Math.PI-arrowAngle) * arrowHeadRadius;
//g.line(x2, y2, x2 + plx, y2 + ply);
float prx = (float)FastMath.cos(angle-Math.PI+arrowAngle) * arrowHeadRadius;
float pry = (float)FastMath.sin(angle-Math.PI+arrowAngle) * arrowHeadRadius;
//g.line(x2, y2, x2 + prx, y2 + pry);
g.fill(g.strokeColor);
g.noStroke();
//g.triangle(x2, y2, x2 + prx, y2 + pry, x2 + plx, y2 + ply);
g.quad(x2, y2, x2 + prx, y2 + pry, x1, y1, x2 + plx, y2 + ply);
g.noFill();
}
}
// void drawLine(final float x1, final float y1, final float x2, final float y2) {
// line(x1, y1, x2, y2);
// }
}