First Commit (Fobi)

This commit is contained in:
Frank Schiebel 2021-07-12 14:04:20 +02:00
commit 2bff291a51
336 changed files with 88781 additions and 0 deletions

601
graph/GraphPlotter.java Normal file
View file

@ -0,0 +1,601 @@
package graph;
import imp.*;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.MouseButton;
import java.nio.file.*;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import javafx.scene.control.Tooltip;
import javafx.util.Duration;
import org.apache.commons.io.FileUtils;
/**
* Der GraphPlotter ist das Herzstueck der Visualisierung und dient als Schnittstelle zur GUI.
*
* @author Thomas Schaller
* @version 09.12.2020 (v6.7)
*/
public class GraphPlotter extends PictureViewer {
// Anfang Attribute
private Graph graph;
private GraphOptions options;
private boolean multiselect = false;
private boolean editable = false;
private ArrayList<GraphElement> selected=new ArrayList<GraphElement>();
private Knoten dragKnoten = null;
private int dragMode = 0;
// Tooltip
private double mouseX, mouseY;
private Tooltip t;
private GraphElement restrictTo = null;
private Point2D offset = new Point2D(0,0);
// private JTextArea jTAMeldungen = new JTextArea("");
// private JScrollPane jTAMeldungenScrollPane = new JScrollPane(jTAMeldungen);
// Ende Attribute
/**
* Der Konstruktor legt sowohl Einstellungen des mxGraphen (Drag&Drop, Editable, ...) als auch des Graphen (gewichtet, gerichtet, ...) fest.
*
* @param boolean isDirected Gibt an, ob der Graph gerichtet oder ungerichtet ist
* @param boolean isWeighted Gibt an, ob der Graph gewichtet oder ungewichtet ist
* @param String hintergrundBild Gibt den Namen eines Hintergrundbildes an
*/
public GraphPlotter() {
options = new GraphOptions();
graph = new Graph();
this.setStyle("-fx-background:#FFFFE8");
// add(jTAMeldungenScrollPane, BorderLayout.SOUTH);
setOnMouseClicked(mouseEvent -> mouseClicked(mouseEvent));
// setOnMouseMoved(mouseEvent -> { mouseX = mouseEvent.getSceneX(); mouseY = mouseEvent.getSceneY(); t.hide();});
this.widthProperty().addListener((value, oldWidth, newWidth) -> updateImage());
this.heightProperty().addListener((value, oldWidth, newWidth) -> updateImage());
// t = new Tooltip();
// Tooltip.install(this, t);
// t.setPrefWidth(80);
// t.setWrapText(true);
// t.setHideOnEscape(true);
// t.setStyle("-fx-background: rgba(30,30,30); -fx-text-fill: black; -fx-background-color: rgba(230,230,90,0.8);"+
// "-fx-background-radius: 6px; -fx-background-insets: 0; -fx-padding: 0.667em 0.75em 0.667em 0.75em; "+
// " -fx-effect: dropshadow( three-pass-box , rgba(0,0,0,0.5) , 10, 0.0 , 0 , 3 ); -fx-font-size: 0.85em;");
// t.setShowDelay(Duration.seconds(1));
// t.setOnShowing(ev -> {// called just prior to being shown
// Point2D local = this.getContent().sceneToLocal(mouseX, mouseY);
// Knoten k = getKnotenAt((int) local.getX(), (int) local.getY());
// if(k == null) {
// t.hide();
// } else {
// t.setText("Knoten Nr. "+graph.getNummer(k)+"\nWert: "+k.getDoubleWert());
// }
// });
}
public void setEditable() {
editable = true;
//setOnMousePressed(mouseEvent -> mouseDown(mouseEvent)); // wird durch MousePressed in EditTab realisiert und dann hier aufgerufen
setOnMouseReleased(mouseEvent -> mouseUp(mouseEvent));
setOnDragDetected(mouseEvent -> startFullDrag());
setOnMouseDragOver(mouseEvent-> mouseDragged(mouseEvent));
setOnMouseClicked(null);
}
public void setGraph(Graph graph, GraphOptions options) {
this.graph = graph;
this.options = options;
updateImage();
}
public void setRestrictTo(GraphElement k) {
if(restrictTo != k) {
restrictTo = k;
selected.clear();
if (k!= null) selected.add(k);
updateImage();
}
}
public GraphElement getRestrictTo() {
return restrictTo;
}
public void mouseClicked(MouseEvent mouseEvent) {
Point2D local = this.getContent().sceneToLocal(mouseEvent.getSceneX(), mouseEvent.getSceneY());
GraphElement k = getKnotenAt((int) local.getX(), (int) local.getY());
if(k!=null && mouseEvent.isShiftDown() && restrictTo != null && restrictTo instanceof Knoten) { // mit Shift-Knotenklick Kante auswählen, damit unsichtbare Kanten gewählt werden können (für TSP)
k = graph.getKante((Knoten) restrictTo, (Knoten) k);
}
if(k!=null && mouseEvent.isShiftDown() && selected.size() == 1 && selected.get(0) instanceof Knoten) { // mit Shift-Knotenklick Kante auswählen, damit unsichtbare Kanten gewählt werden können (für TSP)
k = graph.getKante((Knoten) (selected.get(0)), (Knoten) k);
}
if(k==null) k = getKanteAt((int) local.getX(), (int) local.getY());
if(!multiselect) {
selected.clear();
}
if(k != null) {
if(selected.contains(k)) selected.remove(k);
else selected.add(k);
}
updateImage();
}
public void mouseDown(MouseEvent mouseEvent) {
if(mouseEvent.isPrimaryButtonDown()) {
Point2D local = this.getContent().sceneToLocal(mouseEvent.getSceneX(), mouseEvent.getSceneY());
dragKnoten = getKnotenAt((int) local.getX(), (int) local.getY());
dragMode = 3; // Linksclick aber nicht auf Knoten
if(dragKnoten != null) {
mouseEvent.setDragDetect(true);
double distance = Math.sqrt(Math.pow(local.getX()-dragKnoten.getX(),2)+Math.pow(local.getY()-dragKnoten.getY(),2));
if(distance < options.vertexSize/4)
dragMode = 1; // Knoten verschieben
else
dragMode = 2; // Kante ziehen
}
}
}
public void mouseDragged(MouseEvent mouseEvent) {
if(dragKnoten != null) {
Point2D local = this.getContent().sceneToLocal(mouseEvent.getSceneX(), mouseEvent.getSceneY());
if(dragMode == 1) {
dragKnoten.setX((int) (local.getX()));
dragKnoten.setY((int) (local.getY()));
}
updateImage();
if(dragMode == 2) {
Picture p = getImage();
p.stroke(0);
p.strokeWeight(3);
//p.line(dragKnoten.getX(), dragKnoten.getY(), (int) local.getX(), (int) local.getY());
drawArrow(p,dragKnoten.getX(), dragKnoten.getY(), (int) local.getX(), (int) local.getY());
setImage(p,false);
}
}
}
public void mouseUp(MouseEvent mouseEvent) {
Point2D local = this.getContent().sceneToLocal(mouseEvent.getSceneX(), mouseEvent.getSceneY());
Knoten k = getKnotenAt((int) local.getX(), (int) local.getY());
if(dragMode == 3 && k==null && getKanteAt((int) local.getX(), (int) local.getY())==null) { // neuer Knoten
graph.neuerKnoten(new Knoten((int)local.getX(), (int) local.getY())) ;
} else {
if(dragMode == 2 && k != null && k != dragKnoten) {
graph.neueKante(dragKnoten, k, 0.0);
}
}
dragKnoten = null;
dragMode = 0;
mouseEvent.setDragDetect(false);
updateImage();
}
private Knoten getKnotenAt(int x, int y) {
List<Knoten> knoten = graph.getAlleKnoten();
if(restrictTo != null) {
knoten.clear();
if(restrictTo instanceof Knoten) {
knoten = graph.getNachbarknoten((Knoten) restrictTo);
knoten.add((Knoten) restrictTo);
}
if(restrictTo instanceof Kante) {
knoten.add(((Kante) restrictTo).getStart());
knoten.add(((Kante) restrictTo).getZiel());
}
}
for(Knoten k : knoten) {
if(Math.sqrt(Math.pow(k.getX()-x,2)+Math.pow(k.getY()-y,2)) < options.vertexSize/2+1) {
return k;
}
}
return null;
}
private Kante getKanteAt(int x, int y) {
List<Kante> kanten = graph.getAlleKanten();
if(restrictTo != null) {
kanten.clear();
if(restrictTo instanceof Knoten) {
kanten = graph.getAusgehendeKanten((Knoten) restrictTo);
}
if(restrictTo instanceof Kante) {
kanten.add(((Kante) restrictTo));
}
}
for (Kante k: kanten) {
if (x>=Math.min(k.getStart().getX(), k.getZiel().getX())-2 &&
x<=Math.max(k.getStart().getX(), k.getZiel().getX())+2 &&
y>=Math.min(k.getStart().getY(), k.getZiel().getY())-2 &&
y<=Math.max(k.getStart().getY(), k.getZiel().getY())+2) {
double startX = k.getStart().getX();
double startY = k.getStart().getY();
double endX = k.getZiel().getX();
double endY = k.getZiel().getY();
double dy = (endY-startY);
double dx = (endX-startX);
double l = Math.sqrt(dx*dx+dy*dy);
dy = dy /l; dx = dx / l;
startX += dx * (options.vertexSize/2+1);
startY += dy * (options.vertexSize/2+1);
endX -= dx * (options.vertexSize/2+1);
endY -= dy * (options.vertexSize/2+1);
double dx2 = dy*5;
double dy2 = -dx*5;
if (graph.isGerichtet() && graph.getKante(k.getZiel(), k.getStart())!=null){
startX += dx2;
startY += dy2;
endX += dx2;
endY += dy2;
}
double nx = dy;
double ny = -dx;
double abx = x - startX;
double aby = y - startY;
double abs = Math.abs(abx*nx+aby*ny);
if (abs < 3) return k;
}
}
return null;
}
private void drawArrow(Picture p, int startx, int starty, int endx, int endy) {
double deltax = startx - endx;
double result;
if (deltax == 0.0d) {
result = (starty > endy ? Math.PI / 2 : -Math.PI / 2);
}
else {
result = Math.atan((starty - endy) / deltax) + (startx < endx ? Math.PI : 0);
}
double angle = result;
double arrowAngle = Math.PI / 8.0d;
double arrowSize = 10;
double x1 = arrowSize * Math.cos(angle - arrowAngle);
double y1 = arrowSize * Math.sin(angle - arrowAngle);
double x2 = arrowSize * Math.cos(angle + arrowAngle);
double y2 = arrowSize * Math.sin(angle + arrowAngle);
double cx = (arrowSize / 2.0f) * Math.cos(angle);
double cy = (arrowSize / 2.0f) * Math.sin(angle);
int x[] = {(int) endx, (int) (endx+x1), (int) (endx+x2), (int) endx};
int y[] = {(int) endy, (int) (endy+y1), (int) (endy+y2), (int) endy};
p.line(startx, starty, endx, endy);
p.polygon(x,y);
}
private String darker(String color) {
String red = color.substring(0, 2);
String green = color.substring(2,4);
String blue = color.substring(4,6);
long r = Long.parseLong(red,16)/2;
long g = Long.parseLong(green,16) /2;
long b = Long.parseLong(blue,16) / 2;
String sr = "0"+Long.toHexString(r);
String sg = "0"+Long.toHexString(g);
String sb = "0"+Long.toHexString(b);
return sr.substring(sr.length()-2)+sg.substring(sg.length()-2)+sb.substring(sb.length()-2);
}
private String brighter(String color) {
String red = color.substring(0, 2);
String green = color.substring(2,4);
String blue = color.substring(4,6);
long r = (Long.parseLong(red,16)+255)/2;
long g = (Long.parseLong(green,16)) /2;
long b = (Long.parseLong(blue,16)) / 2;
String sr = "0"+Long.toHexString(r);
String sg = "0"+Long.toHexString(g);
String sb = "0"+Long.toHexString(b);
return sr.substring(sr.length()-2)+sg.substring(sg.length()-2)+sb.substring(sb.length()-2);
}
private String format(double d) {
if((int) d == d) {
return ""+(int) d;
} else {
return ""+d;
}
}
public Picture updateImage() {
Knoten restrictToKnoten = null;
Kante restrictToKante = null;
if(restrictTo != null && restrictTo instanceof Knoten) restrictToKnoten = (Knoten) restrictTo;
if(restrictTo != null && restrictTo instanceof Kante) restrictToKante = (Kante) restrictTo;
List<Knoten> knoten = graph.getAlleKnoten();
List<Kante> kanten = graph.getAlleKanten();
int maxx = Integer.MIN_VALUE; int maxy = Integer.MIN_VALUE;
int minx = Integer.MAX_VALUE; int miny = Integer.MAX_VALUE;
for(Knoten k: knoten) {
maxx = Math.max(maxx,k.getX());
minx = Math.min(minx,k.getX());
maxy = Math.max(maxy,k.getY());
miny = Math.min(miny,k.getY());
}
Picture p = new Picture(2000,2000,"FFFFE8");
if(restrictToKnoten != null) {
knoten = graph.getNachbarknoten(restrictToKnoten);
kanten = graph.getAusgehendeKanten(restrictToKnoten);
knoten.add(restrictToKnoten);
// this.getContent().setTranslateY(this.getHeight()/2-restrictToKnoten.getY());
// this.getContent().setTranslateX(this.getWidth()/2-restrictToKnoten.getX());
this.getContent().setTranslateY(1000-(restrictToKnoten.getY()));
this.getContent().setTranslateX(1000-(restrictToKnoten.getX()));
} else if (restrictToKante != null ) {
kanten.clear();
kanten.add(restrictToKante);
knoten.clear();
knoten.add(restrictToKante.getStart());
knoten.add(restrictToKante.getZiel());
// this.getContent().setTranslateY(this.getHeight()/2-restrictToKnoten.getY());
// this.getContent().setTranslateX(this.getWidth()/2-restrictToKnoten.getX());
this.getContent().setTranslateX(1000-(restrictToKante.getStart().getX()+restrictToKante.getZiel().getX())/2);
this.getContent().setTranslateY(1000-(restrictToKante.getStart().getY()+restrictToKante.getZiel().getY())/2);
}
else
{
/*this.getContent().setTranslateX(1000-(minx+maxx)/2);
this.getContent().setTranslateY(1000-(miny+maxy)/2);*/
}
p.textMode(Picture.CENTER);
if(options.bildAnzeigen && !options.bildDatei.isEmpty()) {
Picture p2 = new Picture("./images/"+options.bildDatei);
p.getImage().getGraphics().drawImage(p2.getImage(), 0, 0, null);
minx = options.vertexSize;
miny = options.vertexSize;
maxx = p2.getWidth()-options.vertexSize;
maxy = p2.getHeight()-options.vertexSize;
}
// Zone in der Mitte markieren
if(restrictToKnoten!=null) {
p.fill("FFE8E8");
p.stroke("FFE8E8");
p.ellipse(restrictToKnoten.getX(), restrictToKnoten.getY(), options.vertexSize*2, options.vertexSize*2);
}
if(restrictToKante!=null) {
p.fill("FFE8E8");
p.stroke("FFE8E8");
p.strokeWeight(30);
p.line(restrictToKante.getStart().getX(), restrictToKante.getStart().getY(), restrictToKante.getZiel().getX(),restrictToKante.getZiel().getY());
}
for (Kante k : kanten) {
if (!options.farbenKanten[k.getFarbe()].equals("invisible") || selected.contains(k)) {
double startX = k.getStart().getX();
double startY = k.getStart().getY();
double endX = k.getZiel().getX();
double endY = k.getZiel().getY();
double dy = (endY-startY);
double dx = (endX-startX);
double l = Math.sqrt(dx*dx+dy*dy);
dy = dy /l; dx = dx / l;
startX += dx * (options.vertexSize/2+1);
startY += dy * (options.vertexSize/2+1);
endX -= dx * (options.vertexSize/2+1);
endY -= dy * (options.vertexSize/2+1);
double dx2 = dy*5;
double dy2 = -dx*5;
if (graph.isGerichtet() && graph.getKante(k.getZiel(), k.getStart())!=null){
startX += dx2;
startY += dy2;
endX += dx2;
endY += dy2;
}
if(selected.contains(k)) {
p.stroke("FF0000");
p.strokeWeight(4);
p.line((int) startX, (int) startY, (int) endX, (int) endY);
}
if (!options.farbenKanten[k.getFarbe()].equals("invisible")){
p.stroke(options.farbenKanten[k.getFarbe()]);
p.fill(options.farbenKanten[k.getFarbe()]);
}
else
{
p.stroke("505050");
p.fill("505050");
}
p.strokeWeight(2);
if(graph.isGerichtet()) {
drawArrow(p, (int) startX, (int) startY, (int) endX, (int) endY);
} else {
p.line((int) startX, (int) startY, (int) endX, (int) endY);
}
}
}
for (Kante k : kanten) {
if (!options.farbenKanten[k.getFarbe()].equals("invisible") || selected.contains(k)) {
double startX = k.getStart().getX();
double startY = k.getStart().getY();
double endX = k.getZiel().getX();
double endY = k.getZiel().getY();
double dy = (endY-startY);
double dx = (endX-startX);
double l = Math.sqrt(dx*dx+dy*dy);
dy = dy /l; dx = dx / l;
startX += dx * (options.vertexSize/2+1);
startY += dy * (options.vertexSize/2+1);
endX -= dx * (options.vertexSize/2+1);
endY -= dy * (options.vertexSize/2+1);
double dx2 = dy*5;
double dy2 = -dx*5;
if(graph.isGerichtet()) {
if (graph.getKante(k.getZiel(), k.getStart())!=null){
startX += dx2;
startY += dy2;
endX += dx2;
endY += dy2;
}
}
if(options.showEdgeWeights && graph.isGewichtet()) {
double my = (startY+startY+endY)/3;
double mx = (startX+startX+endX)/3;
p.fill(255);
p.stroke(0);
p.strokeWeight(1);
p.rect((int) mx-15, (int) my-7, 30, 16);
p.fill(0);
p.text(format(k.getGewicht()), (int) mx, (int) my);
}
}
}
for (Knoten k : knoten) {
p.fill(options.farbenKnoten[k.getFarbe()]);
p.stroke(darker(options.farbenKnoten[k.getFarbe()]));
p.strokeWeight(3);
p.ellipse(k.getX(), k.getY(), options.vertexSize, options.vertexSize);
if(selected.contains(k)) {
p.noFill();
p.strokeWeight(2);
p.stroke(255,0,0);
p.ellipse(k.getX(), k.getY(), options.vertexSize+2, options.vertexSize+2);
}
p.fill(0);
p.stroke(0);
p.strokeWeight(0);
p.textMode(Picture.CENTER);
if (options.showVertexText) {
p.text(""+graph.getNummer(k), k.getX(), k.getY());
} else {
if (options.showVertexValue) {
p.text(format(k.getDoubleWert()), k.getX(), k.getY());
}
}
// Knotenbezeichnung
p.textMode(Picture.CORNER);
p.fill("000080");
if(options.showVertexInfo && !k.getInfotext().equals("")) {
p.text(k.getInfotext(), k.getX()+options.vertexSize/2+5,k.getY());
}
}
this.setImage(p, false);
Picture zugeschnitten = new Picture(maxx-minx+2*options.vertexSize,maxy-miny+2*options.vertexSize);
zugeschnitten.getImage().getGraphics().drawImage(p.getImage(), 0, 0, maxx-minx+2*options.vertexSize, maxy-miny+2*options.vertexSize, minx-options.vertexSize, miny-options.vertexSize, maxx+options.vertexSize, maxy+options.vertexSize, null);
return zugeschnitten;
}
public GraphOptions getGraphOptions() {
return options;
}
/**
* Gibt den Graphen zurueck.
*
* @return Graph
*/
public Graph getGraph() {
return graph;
}
/**
* Gibt das selektierte Knotenobjekt zurueck.
*
* @return Object
*/
public Knoten getSelectedKnoten() {
if(selected.size()==1 && selected.get(0) instanceof Knoten)
return ((Knoten) (selected.get(0)));
else
return null;
}
/**
* Gibt die selektierte KnotenobjektListe (als Array) zurueck.
*
* @return Object[]
*/
public List<Knoten> getSelectedKnotenListe() {
List<Knoten> l = new ArrayList<Knoten>();
for(GraphElement g : selected) {
if(g instanceof Knoten) l.add((Knoten) g);
}
return l;
}
/**
* Gibt das selektierte Kantenobjekt zurueck.
*
* @return Object
*/
public Kante getSelectedKante() {
if(selected.size()==1 && selected.get(0) instanceof Kante)
return ((Kante) (selected.get(0)));
else
return null;
}
/**
* Ueberschreibt die Methode toString. Eine String-Repraesentation des GraphPlotters wird ausgegeben.
*
* @return String Die String-Repraesentation des GraphPlotters
*/
public String toString() {
String s = "";
if(graph.isGerichtet())
s += "Gerichteter, ";
else
s += "Ungerichteter, ";
if(graph.isGewichtet())
s += "gewichteter Graph mit Hintergrundbild: ";
else
s += "ungewichteter Graph mit Hintergrundbild: ";
if(options.bildDatei.equals(""))
s += " kein Bild!";
else
s += options.bildDatei + "!";
return s;
}
/**
* Gibt die String-Repraesentation des GraphPlotters auf der Konsole aus.
*/
public void ausgabe() {
System.out.println(toString() + "\n"+graph.toString());
}
// Ende Methoden
}